Load JoinedRoom in home screen, pass it to the room flow (#5817)
* Load `JoinedRoom` in `HomeFlowNode.navigateToRoom`, then pass it to the next navigation nodes * Add delayed loading indicator for cases when loading the room takes too long * Avoid an extra FFI call in `RustRoomFactory`. Use `RoomInfo.membership` instead. Also use `computation` dispatcher, since it should reduce the delay when switching contexts. * Remove the dispatcher usage when loading the room in `HomeFlowNode`, we immediately call a method that changes the dispatcher used * Make sure only a single room is opened at a time
This commit is contained in:
committed by
GitHub
parent
c292a732d0
commit
77be19bf3b
@@ -39,7 +39,6 @@ import com.bumble.appyx.navmodel.backstack.operation.replace
|
||||
import com.bumble.appyx.navmodel.backstack.operation.singleTop
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.appnav.loggedin.LoggedInNode
|
||||
import io.element.android.appnav.loggedin.MediaPreviewConfigMigration
|
||||
@@ -84,6 +83,7 @@ import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationRequest
|
||||
@@ -109,6 +109,7 @@ import java.util.UUID
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.time.toKotlinDuration
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom as JoinedRoomAnalyticsEvent
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
@AssistedInject
|
||||
@@ -265,7 +266,7 @@ class LoggedInFlowNode(
|
||||
data class Room(
|
||||
val roomIdOrAlias: RoomIdOrAlias,
|
||||
val serverNames: List<String> = emptyList(),
|
||||
val trigger: JoinedRoom.Trigger? = null,
|
||||
val trigger: JoinedRoomAnalyticsEvent.Trigger? = null,
|
||||
val roomDescription: RoomDescription? = null,
|
||||
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Root(),
|
||||
val targetId: UUID = UUID.randomUUID(),
|
||||
@@ -315,8 +316,13 @@ class LoggedInFlowNode(
|
||||
}
|
||||
NavTarget.Home -> {
|
||||
val callback = object : HomeEntryPoint.Callback {
|
||||
override fun navigateToRoom(roomId: RoomId) {
|
||||
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias()))
|
||||
override fun navigateToRoom(roomId: RoomId, joinedRoom: JoinedRoom?) {
|
||||
backstack.push(
|
||||
NavTarget.Room(
|
||||
roomIdOrAlias = roomId.toRoomIdOrAlias(),
|
||||
initialElement = RoomNavigationTarget.Root(joinedRoom = joinedRoom)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun navigateToSettings() {
|
||||
@@ -365,7 +371,7 @@ class LoggedInFlowNode(
|
||||
val target = NavTarget.Room(
|
||||
roomIdOrAlias = data.roomIdOrAlias,
|
||||
serverNames = data.viaParameters,
|
||||
trigger = JoinedRoom.Trigger.Timeline,
|
||||
trigger = JoinedRoomAnalyticsEvent.Trigger.Timeline,
|
||||
initialElement = RoomNavigationTarget.Root(data.eventId),
|
||||
)
|
||||
if (pushToBackstack) {
|
||||
@@ -479,7 +485,7 @@ class LoggedInFlowNode(
|
||||
NavTarget.Room(
|
||||
roomIdOrAlias = roomDescription.roomId.toRoomIdOrAlias(),
|
||||
roomDescription = roomDescription,
|
||||
trigger = JoinedRoom.Trigger.RoomDirectory,
|
||||
trigger = JoinedRoomAnalyticsEvent.Trigger.RoomDirectory,
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -519,7 +525,7 @@ class LoggedInFlowNode(
|
||||
suspend fun attachRoom(
|
||||
roomIdOrAlias: RoomIdOrAlias,
|
||||
serverNames: List<String> = emptyList(),
|
||||
trigger: JoinedRoom.Trigger? = null,
|
||||
trigger: JoinedRoomAnalyticsEvent.Trigger? = null,
|
||||
eventId: EventId? = null,
|
||||
clearBackstack: Boolean,
|
||||
): RoomFlowNode {
|
||||
|
||||
@@ -19,10 +19,10 @@ import com.bumble.appyx.core.node.node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.bumble.appyx.core.plugin.plugins
|
||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||
import com.bumble.appyx.navmodel.backstack.active
|
||||
import com.bumble.appyx.navmodel.backstack.operation.newRoot
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.appnav.room.joined.JoinedRoomFlowNode
|
||||
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
|
||||
@@ -60,10 +60,13 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import timber.log.Timber
|
||||
import java.util.Optional
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom as JoinedRoomAnalyticsEvent
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom as JoinedRoomInstance
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
@AssistedInject
|
||||
@@ -77,7 +80,14 @@ class RoomFlowNode(
|
||||
private val analyticsService: AnalyticsService,
|
||||
) : BaseFlowNode<RoomFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.Loading,
|
||||
initialElement = run {
|
||||
val joinedRoom = (plugins.filterIsInstance<Inputs>().first().initialElement as? RoomNavigationTarget.Root)?.joinedRoom
|
||||
if (joinedRoom != null) {
|
||||
NavTarget.JoinedRoom(joinedRoom)
|
||||
} else {
|
||||
NavTarget.Loading
|
||||
}
|
||||
},
|
||||
savedStateMap = buildContext.savedStateMap,
|
||||
),
|
||||
buildContext = buildContext,
|
||||
@@ -87,7 +97,7 @@ class RoomFlowNode(
|
||||
val roomIdOrAlias: RoomIdOrAlias,
|
||||
val roomDescription: Optional<RoomDescription>,
|
||||
val serverNames: List<String>,
|
||||
val trigger: Optional<JoinedRoom.Trigger>,
|
||||
val trigger: Optional<JoinedRoomAnalyticsEvent.Trigger>,
|
||||
val initialElement: RoomNavigationTarget,
|
||||
) : NodeInputs
|
||||
|
||||
@@ -104,11 +114,16 @@ class RoomFlowNode(
|
||||
data class JoinRoom(
|
||||
val roomId: RoomId,
|
||||
val serverNames: List<String>,
|
||||
val trigger: im.vector.app.features.analytics.plan.JoinedRoom.Trigger,
|
||||
val trigger: JoinedRoomAnalyticsEvent.Trigger,
|
||||
) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class JoinedRoom(val roomId: RoomId) : NavTarget
|
||||
data class JoinedRoom(
|
||||
val roomId: RoomId,
|
||||
@IgnoredOnParcel val joinedRoom: JoinedRoomInstance? = null,
|
||||
) : NavTarget {
|
||||
constructor(joinedRoom: JoinedRoomInstance) : this(joinedRoom.roomId, joinedRoom)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBuilt() {
|
||||
@@ -133,7 +148,9 @@ class RoomFlowNode(
|
||||
}
|
||||
|
||||
private fun subscribeToRoomInfoFlow(roomId: RoomId, serverNames: List<String>) {
|
||||
val roomInfoFlow = client.getRoomInfoFlow(roomId)
|
||||
val joinedRoom = (inputs.initialElement as? RoomNavigationTarget.Root)?.joinedRoom
|
||||
val roomInfoFlow = joinedRoom?.roomInfoFlow?.map { Optional.of(it) }
|
||||
?: client.getRoomInfoFlow(roomId)
|
||||
|
||||
// This observes the local membership changes for the room
|
||||
val membershipUpdateFlow = membershipObserver.updates
|
||||
@@ -149,6 +166,11 @@ class RoomFlowNode(
|
||||
currentMembershipFlow.onEach { (previousMembership, membership) ->
|
||||
Timber.d("Room membership: $membership")
|
||||
if (membership == CurrentUserMembership.JOINED) {
|
||||
val currentNavTarget = backstack.active?.key?.navTarget
|
||||
if (currentNavTarget is NavTarget.JoinedRoom && currentNavTarget.roomId == roomId) {
|
||||
Timber.d("Already in JoinedRoom $roomId, do nothing")
|
||||
return@onEach
|
||||
}
|
||||
backstack.newRoot(NavTarget.JoinedRoom(roomId))
|
||||
} else {
|
||||
val leavingFromCurrentDevice =
|
||||
@@ -163,7 +185,7 @@ class RoomFlowNode(
|
||||
NavTarget.JoinRoom(
|
||||
roomId = roomId,
|
||||
serverNames = serverNames,
|
||||
trigger = inputs.trigger.getOrNull() ?: JoinedRoom.Trigger.Invite,
|
||||
trigger = inputs.trigger.getOrNull() ?: JoinedRoomAnalyticsEvent.Trigger.Invite,
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -209,7 +231,8 @@ class RoomFlowNode(
|
||||
val roomFlowNodeCallback = plugins<JoinedRoomLoadedFlowNode.Callback>()
|
||||
val inputs = JoinedRoomFlowNode.Inputs(
|
||||
roomId = navTarget.roomId,
|
||||
initialElement = inputs.initialElement
|
||||
initialElement = inputs.initialElement,
|
||||
joinedRoom = navTarget.joinedRoom,
|
||||
)
|
||||
createNode<JoinedRoomFlowNode>(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback)
|
||||
}
|
||||
|
||||
@@ -10,12 +10,15 @@ 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.room.JoinedRoom
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
sealed interface RoomNavigationTarget : Parcelable {
|
||||
@Parcelize
|
||||
data class Root(
|
||||
val eventId: EventId? = null,
|
||||
@IgnoredOnParcel val joinedRoom: JoinedRoom? = null,
|
||||
) : RoomNavigationTarget
|
||||
|
||||
@Parcelize
|
||||
|
||||
@@ -38,6 +38,7 @@ 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.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.ui.room.LoadingRoomState
|
||||
import io.element.android.libraries.matrix.ui.room.LoadingRoomStateFlowFactory
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
@@ -63,11 +64,12 @@ class JoinedRoomFlowNode(
|
||||
) {
|
||||
data class Inputs(
|
||||
val roomId: RoomId,
|
||||
val joinedRoom: JoinedRoom?,
|
||||
val initialElement: RoomNavigationTarget,
|
||||
) : NodeInputs
|
||||
|
||||
private val inputs: Inputs = inputs()
|
||||
private val loadingRoomStateStateFlow = loadingRoomStateFlowFactory.create(lifecycleScope, inputs.roomId)
|
||||
private val loadingRoomStateStateFlow = loadingRoomStateFlowFactory.create(lifecycleScope, inputs.roomId, inputs.joinedRoom)
|
||||
|
||||
sealed interface NavTarget : Parcelable {
|
||||
@Parcelize
|
||||
|
||||
@@ -23,6 +23,19 @@ import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class LoadingBaseRoomStateFlowFactoryTest {
|
||||
@Test
|
||||
fun `flow should emit only Loaded when we already pass a JoinedRoom`() = runTest {
|
||||
val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(sessionId = A_SESSION_ID, roomId = A_ROOM_ID))
|
||||
val matrixClient = FakeMatrixClient(A_SESSION_ID)
|
||||
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
||||
flowFactory
|
||||
.create(lifecycleScope = this, roomId = A_ROOM_ID, joinedRoom = room)
|
||||
.test {
|
||||
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loaded(room))
|
||||
ensureAllEventsConsumed()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow should emit Loading and then Loaded when there is a room in cache`() = runTest {
|
||||
val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(sessionId = A_SESSION_ID, roomId = A_ROOM_ID))
|
||||
@@ -31,7 +44,7 @@ class LoadingBaseRoomStateFlowFactoryTest {
|
||||
}
|
||||
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
||||
flowFactory
|
||||
.create(this, A_ROOM_ID)
|
||||
.create(lifecycleScope = this, roomId = A_ROOM_ID, joinedRoom = null)
|
||||
.test {
|
||||
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading)
|
||||
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loaded(room))
|
||||
@@ -45,7 +58,7 @@ class LoadingBaseRoomStateFlowFactoryTest {
|
||||
val matrixClient = FakeMatrixClient(A_SESSION_ID, roomListService = roomListService)
|
||||
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
||||
flowFactory
|
||||
.create(this, A_ROOM_ID)
|
||||
.create(lifecycleScope = this, roomId = A_ROOM_ID, joinedRoom = null)
|
||||
.test {
|
||||
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading)
|
||||
matrixClient.givenGetRoomResult(A_ROOM_ID, room)
|
||||
@@ -60,7 +73,7 @@ class LoadingBaseRoomStateFlowFactoryTest {
|
||||
val matrixClient = FakeMatrixClient(A_SESSION_ID, roomListService = roomListService)
|
||||
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
||||
flowFactory
|
||||
.create(this, A_ROOM_ID)
|
||||
.create(lifecycleScope = this, roomId = A_ROOM_ID, joinedRoom = null)
|
||||
.test {
|
||||
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading)
|
||||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
|
||||
Reference in New Issue
Block a user