diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 7487c31b03..7b1f829f8e 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -507,20 +507,18 @@ class LoggedInFlowNode( serverNames: List = emptyList(), trigger: JoinedRoom.Trigger? = null, eventId: EventId? = null, - threadId: ThreadId? = null, clearBackstack: Boolean, - ) { + ): RoomFlowNode { waitForNavTargetAttached { navTarget -> navTarget is NavTarget.Home } - attachChild { + return attachChild { val roomNavTarget = NavTarget.Room( roomIdOrAlias = roomIdOrAlias, serverNames = serverNames, trigger = trigger, initialElement = RoomNavigationTarget.Messages( focusedEventId = eventId, - inThreadId = threadId, ) ) backstack.accept(AttachRoomOperation(roomNavTarget, clearBackstack)) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index c16bbdefef..b979534b70 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -31,6 +31,7 @@ import io.element.android.annotations.ContributesNode import io.element.android.appnav.di.MatrixSessionCache import io.element.android.appnav.intent.IntentResolver import io.element.android.appnav.intent.ResolvedIntent +import io.element.android.appnav.room.RoomFlowNode import io.element.android.appnav.root.RootNavStateFlowFactory import io.element.android.appnav.root.RootPresenter import io.element.android.appnav.root.RootView @@ -49,7 +50,9 @@ import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.deeplink.api.DeeplinkData import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.oidc.api.OidcAction @@ -392,10 +395,9 @@ class RootFlowNode( roomIdOrAlias = permalinkData.roomIdOrAlias, trigger = JoinedRoom.Trigger.MobilePermalink, serverNames = permalinkData.viaParameters, - eventId = permalinkData.eventId, - threadId = permalinkData.threadId, + eventId = permalinkData.eventId.takeIf { permalinkData.threadId == null }, clearBackstack = true - ) + ).maybeAttachThread(permalinkData.threadId, permalinkData.eventId) } is PermalinkData.UserLink -> { attachUser(permalinkData.userId) @@ -403,17 +405,22 @@ class RootFlowNode( } } + private suspend fun RoomFlowNode.maybeAttachThread(threadId: ThreadId?, focusedEventId: EventId?) { + if (threadId != null) { + attachThread(threadId, focusedEventId) + } + } + private suspend fun navigateTo(deeplinkData: DeeplinkData) { Timber.d("Navigating to $deeplinkData") - attachSession(deeplinkData.sessionId).apply { + attachSession(deeplinkData.sessionId).let { loggedInFlowNode -> when (deeplinkData) { is DeeplinkData.Root -> Unit // The room list will always be shown, observing FtueState - is DeeplinkData.Room -> attachRoom( + is DeeplinkData.Room -> loggedInFlowNode.attachRoom( roomIdOrAlias = deeplinkData.roomId.toRoomIdOrAlias(), - eventId = deeplinkData.eventId, - threadId = deeplinkData.threadId, + eventId = deeplinkData.eventId.takeIf { deeplinkData.threadId == null }, clearBackstack = true - ) + ).maybeAttachThread(deeplinkData.threadId, deeplinkData.eventId) } } } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt index 01683c01e7..a4acdb9a9b 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt @@ -9,7 +9,6 @@ package io.element.android.appnav.room import android.os.Parcelable import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.lifecycleScope import com.bumble.appyx.core.modality.BuildContext @@ -41,9 +40,11 @@ import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.coroutine.withPreviousValue import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomIdOrAlias +import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias @@ -218,6 +219,11 @@ class RoomFlowNode( } } + suspend fun attachThread(threadId: ThreadId, focusedEventId: EventId?) { + waitForChildAttached() + .attachThread(threadId, focusedEventId) + } + private fun loadingNode(buildContext: BuildContext) = node(buildContext) { modifier -> LoadingRoomNodeView( state = LoadingRoomState.Loading, diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomNavigationTarget.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomNavigationTarget.kt index c2ae4eebf4..e1b961c4e1 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomNavigationTarget.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomNavigationTarget.kt @@ -9,14 +9,12 @@ package io.element.android.appnav.room import android.os.Parcelable import io.element.android.libraries.matrix.api.core.EventId -import io.element.android.libraries.matrix.api.core.ThreadId import kotlinx.parcelize.Parcelize sealed interface RoomNavigationTarget : Parcelable { @Parcelize data class Messages( val focusedEventId: EventId? = null, - val inThreadId: ThreadId? = null, ) : RoomNavigationTarget @Parcelize diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt index 5874145377..96e39926fc 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt @@ -34,7 +34,9 @@ import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.ui.room.LoadingRoomState import io.element.android.libraries.matrix.ui.room.LoadingRoomStateFlowFactory import kotlinx.coroutines.flow.distinctUntilChanged @@ -121,6 +123,11 @@ class JoinedRoomFlowNode( ) } + suspend fun attachThread(threadId: ThreadId, focusedEventId: EventId?) { + waitForChildAttached() + .attachThread(threadId, focusedEventId) + } + @Composable override fun View(modifier: Modifier) { BackstackView( diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt index f46979b905..22fa898cd2 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt @@ -23,11 +23,13 @@ import io.element.android.annotations.ContributesNode import io.element.android.appnav.di.RoomGraphFactory import io.element.android.appnav.room.RoomNavigationTarget import io.element.android.features.messages.api.MessagesEntryPoint +import io.element.android.features.messages.api.MessagesEntryPointNode import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.architecture.waitForChildAttached import io.element.android.libraries.di.DependencyInjectionGraphOwner import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.annotations.SessionCoroutineScope @@ -61,7 +63,7 @@ class JoinedRoomLoadedFlowNode( ) : BaseFlowNode( backstack = BackStack( initialElement = when (val input = plugins.filterIsInstance().first().initialElement) { - is RoomNavigationTarget.Messages -> NavTarget.Messages(input.focusedEventId, input.inThreadId) + is RoomNavigationTarget.Messages -> NavTarget.Messages(input.focusedEventId) RoomNavigationTarget.Details -> NavTarget.RoomDetails RoomNavigationTarget.NotificationSettings -> NavTarget.RoomNotificationSettings }, @@ -180,7 +182,7 @@ class JoinedRoomLoadedFlowNode( } } val params = MessagesEntryPoint.Params( - MessagesEntryPoint.InitialTarget.Messages(navTarget.focusedEventId, navTarget.inThreadId) + MessagesEntryPoint.InitialTarget.Messages(navTarget.focusedEventId) ) return messagesEntryPoint.nodeBuilder(this, buildContext) .params(params) @@ -192,7 +194,6 @@ class JoinedRoomLoadedFlowNode( @Parcelize data class Messages( val focusedEventId: EventId? = null, - val inThreadId: ThreadId? = null, ) : NavTarget @Parcelize @@ -205,6 +206,13 @@ class JoinedRoomLoadedFlowNode( data object RoomNotificationSettings : NavTarget } + suspend fun attachThread(threadId: ThreadId, focusedEventId: EventId?) { + val messageNode = waitForChildAttached { navTarget -> + navTarget is NavTarget.Messages + } + (messageNode as? MessagesEntryPointNode)?.attachThread(threadId, focusedEventId) + } + @Composable override fun View(modifier: Modifier) { BackstackView() diff --git a/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/MessagesEntryPoint.kt b/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/MessagesEntryPoint.kt index e61f53218a..d9cc52f180 100644 --- a/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/MessagesEntryPoint.kt +++ b/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/MessagesEntryPoint.kt @@ -25,7 +25,6 @@ interface MessagesEntryPoint : FeatureEntryPoint { @Parcelize data class Messages( val focusedEventId: EventId?, - val inThreadId: ThreadId?, ) : InitialTarget @Parcelize @@ -49,3 +48,7 @@ interface MessagesEntryPoint : FeatureEntryPoint { fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder } + +interface MessagesEntryPointNode { + suspend fun attachThread(threadId: ThreadId, focusedEventId: EventId?) +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPoint.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPoint.kt index 4df9ac46d0..9085bcb283 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPoint.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPoint.kt @@ -41,6 +41,6 @@ class DefaultMessagesEntryPoint : MessagesEntryPoint { } internal fun MessagesEntryPoint.InitialTarget.toNavTarget() = when (this) { - is MessagesEntryPoint.InitialTarget.Messages -> MessagesFlowNode.NavTarget.Messages(focusedEventId, inThreadId) + is MessagesEntryPoint.InitialTarget.Messages -> MessagesFlowNode.NavTarget.Messages(focusedEventId) MessagesEntryPoint.InitialTarget.PinnedMessages -> MessagesFlowNode.NavTarget.PinnedMessagesList } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt index b1ce6171e2..a0420dd411 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt @@ -31,6 +31,7 @@ import io.element.android.features.location.api.LocationService import io.element.android.features.location.api.SendLocationEntryPoint import io.element.android.features.location.api.ShowLocationEntryPoint import io.element.android.features.messages.api.MessagesEntryPoint +import io.element.android.features.messages.api.MessagesEntryPointNode import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewNode import io.element.android.features.messages.impl.forward.ForwardMessagesNode @@ -124,11 +125,12 @@ class MessagesFlowNode( savedStateMap = buildContext.savedStateMap, ), buildContext = buildContext, - plugins = plugins -) { + plugins = plugins, +), + MessagesEntryPointNode { sealed interface NavTarget : Parcelable { @Parcelize - data class Messages(val focusedEventId: EventId?, val inThreadId: ThreadId?) : NavTarget + data class Messages(val focusedEventId: EventId?) : NavTarget @Parcelize data class MediaViewer( @@ -285,7 +287,7 @@ class MessagesFlowNode( backstack.push(NavTarget.OpenThread(threadRootId, focusedEventId)) } } - val inputs = MessagesNode.Inputs(focusedEventId = navTarget.focusedEventId, navTarget.inThreadId) + val inputs = MessagesNode.Inputs(focusedEventId = navTarget.focusedEventId) createNode(buildContext, listOf(callback, inputs)) } is NavTarget.MediaViewer -> { @@ -583,6 +585,11 @@ class MessagesFlowNode( ) } + override suspend fun attachThread(threadId: ThreadId, focusedEventId: EventId?) { + waitForChildAttached() + backstack.push(NavTarget.OpenThread(threadId, focusedEventId)) + } + @Composable override fun View(modifier: Modifier) { mentionSpanTheme.updateStyles() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index 01ee7c7c97..bd2d7a0596 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -96,7 +96,6 @@ class MessagesNode( data class Inputs( val focusedEventId: EventId?, - val openThreadId: ThreadId?, ) : NodeInputs private val inputs = inputs() @@ -330,16 +329,8 @@ class MessagesNode( var focusedEventId by rememberSaveable { mutableStateOf(inputs.focusedEventId) } - var openThreadId by rememberSaveable { - mutableStateOf(inputs.openThreadId) - } LaunchedEffect(Unit) { when { - openThreadId != null -> { - state.timelineState.eventSink(TimelineEvents.OpenThread(threadRootEventId = openThreadId!!, focusedEventId)) - openThreadId = null - focusedEventId = null - } focusedEventId != null -> { state.timelineState.eventSink(TimelineEvents.FocusOnEvent(focusedEventId!!)) focusedEventId = null