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:
parent
c292a732d0
commit
77be19bf3b
12 changed files with 180 additions and 53 deletions
|
|
@ -39,7 +39,6 @@ import com.bumble.appyx.navmodel.backstack.operation.replace
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.singleTop
|
import com.bumble.appyx.navmodel.backstack.operation.singleTop
|
||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
|
||||||
import io.element.android.annotations.ContributesNode
|
import io.element.android.annotations.ContributesNode
|
||||||
import io.element.android.appnav.loggedin.LoggedInNode
|
import io.element.android.appnav.loggedin.LoggedInNode
|
||||||
import io.element.android.appnav.loggedin.MediaPreviewConfigMigration
|
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.UserId
|
||||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
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.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.sync.SyncService
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener
|
||||||
import io.element.android.libraries.matrix.api.verification.VerificationRequest
|
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.minutes
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
import kotlin.time.toKotlinDuration
|
import kotlin.time.toKotlinDuration
|
||||||
|
import im.vector.app.features.analytics.plan.JoinedRoom as JoinedRoomAnalyticsEvent
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
|
|
@ -265,7 +266,7 @@ class LoggedInFlowNode(
|
||||||
data class Room(
|
data class Room(
|
||||||
val roomIdOrAlias: RoomIdOrAlias,
|
val roomIdOrAlias: RoomIdOrAlias,
|
||||||
val serverNames: List<String> = emptyList(),
|
val serverNames: List<String> = emptyList(),
|
||||||
val trigger: JoinedRoom.Trigger? = null,
|
val trigger: JoinedRoomAnalyticsEvent.Trigger? = null,
|
||||||
val roomDescription: RoomDescription? = null,
|
val roomDescription: RoomDescription? = null,
|
||||||
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Root(),
|
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Root(),
|
||||||
val targetId: UUID = UUID.randomUUID(),
|
val targetId: UUID = UUID.randomUUID(),
|
||||||
|
|
@ -315,8 +316,13 @@ class LoggedInFlowNode(
|
||||||
}
|
}
|
||||||
NavTarget.Home -> {
|
NavTarget.Home -> {
|
||||||
val callback = object : HomeEntryPoint.Callback {
|
val callback = object : HomeEntryPoint.Callback {
|
||||||
override fun navigateToRoom(roomId: RoomId) {
|
override fun navigateToRoom(roomId: RoomId, joinedRoom: JoinedRoom?) {
|
||||||
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias()))
|
backstack.push(
|
||||||
|
NavTarget.Room(
|
||||||
|
roomIdOrAlias = roomId.toRoomIdOrAlias(),
|
||||||
|
initialElement = RoomNavigationTarget.Root(joinedRoom = joinedRoom)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun navigateToSettings() {
|
override fun navigateToSettings() {
|
||||||
|
|
@ -365,7 +371,7 @@ class LoggedInFlowNode(
|
||||||
val target = NavTarget.Room(
|
val target = NavTarget.Room(
|
||||||
roomIdOrAlias = data.roomIdOrAlias,
|
roomIdOrAlias = data.roomIdOrAlias,
|
||||||
serverNames = data.viaParameters,
|
serverNames = data.viaParameters,
|
||||||
trigger = JoinedRoom.Trigger.Timeline,
|
trigger = JoinedRoomAnalyticsEvent.Trigger.Timeline,
|
||||||
initialElement = RoomNavigationTarget.Root(data.eventId),
|
initialElement = RoomNavigationTarget.Root(data.eventId),
|
||||||
)
|
)
|
||||||
if (pushToBackstack) {
|
if (pushToBackstack) {
|
||||||
|
|
@ -479,7 +485,7 @@ class LoggedInFlowNode(
|
||||||
NavTarget.Room(
|
NavTarget.Room(
|
||||||
roomIdOrAlias = roomDescription.roomId.toRoomIdOrAlias(),
|
roomIdOrAlias = roomDescription.roomId.toRoomIdOrAlias(),
|
||||||
roomDescription = roomDescription,
|
roomDescription = roomDescription,
|
||||||
trigger = JoinedRoom.Trigger.RoomDirectory,
|
trigger = JoinedRoomAnalyticsEvent.Trigger.RoomDirectory,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -519,7 +525,7 @@ class LoggedInFlowNode(
|
||||||
suspend fun attachRoom(
|
suspend fun attachRoom(
|
||||||
roomIdOrAlias: RoomIdOrAlias,
|
roomIdOrAlias: RoomIdOrAlias,
|
||||||
serverNames: List<String> = emptyList(),
|
serverNames: List<String> = emptyList(),
|
||||||
trigger: JoinedRoom.Trigger? = null,
|
trigger: JoinedRoomAnalyticsEvent.Trigger? = null,
|
||||||
eventId: EventId? = null,
|
eventId: EventId? = null,
|
||||||
clearBackstack: Boolean,
|
clearBackstack: Boolean,
|
||||||
): RoomFlowNode {
|
): 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.Plugin
|
||||||
import com.bumble.appyx.core.plugin.plugins
|
import com.bumble.appyx.core.plugin.plugins
|
||||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||||
|
import com.bumble.appyx.navmodel.backstack.active
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.newRoot
|
import com.bumble.appyx.navmodel.backstack.operation.newRoot
|
||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
|
||||||
import io.element.android.annotations.ContributesNode
|
import io.element.android.annotations.ContributesNode
|
||||||
import io.element.android.appnav.room.joined.JoinedRoomFlowNode
|
import io.element.android.appnav.room.joined.JoinedRoomFlowNode
|
||||||
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
|
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.onEach
|
||||||
import kotlinx.coroutines.flow.shareIn
|
import kotlinx.coroutines.flow.shareIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
import kotlin.jvm.optionals.getOrNull
|
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)
|
@ContributesNode(SessionScope::class)
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
|
|
@ -77,7 +80,14 @@ class RoomFlowNode(
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
) : BaseFlowNode<RoomFlowNode.NavTarget>(
|
) : BaseFlowNode<RoomFlowNode.NavTarget>(
|
||||||
backstack = BackStack(
|
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,
|
savedStateMap = buildContext.savedStateMap,
|
||||||
),
|
),
|
||||||
buildContext = buildContext,
|
buildContext = buildContext,
|
||||||
|
|
@ -87,7 +97,7 @@ class RoomFlowNode(
|
||||||
val roomIdOrAlias: RoomIdOrAlias,
|
val roomIdOrAlias: RoomIdOrAlias,
|
||||||
val roomDescription: Optional<RoomDescription>,
|
val roomDescription: Optional<RoomDescription>,
|
||||||
val serverNames: List<String>,
|
val serverNames: List<String>,
|
||||||
val trigger: Optional<JoinedRoom.Trigger>,
|
val trigger: Optional<JoinedRoomAnalyticsEvent.Trigger>,
|
||||||
val initialElement: RoomNavigationTarget,
|
val initialElement: RoomNavigationTarget,
|
||||||
) : NodeInputs
|
) : NodeInputs
|
||||||
|
|
||||||
|
|
@ -104,11 +114,16 @@ class RoomFlowNode(
|
||||||
data class JoinRoom(
|
data class JoinRoom(
|
||||||
val roomId: RoomId,
|
val roomId: RoomId,
|
||||||
val serverNames: List<String>,
|
val serverNames: List<String>,
|
||||||
val trigger: im.vector.app.features.analytics.plan.JoinedRoom.Trigger,
|
val trigger: JoinedRoomAnalyticsEvent.Trigger,
|
||||||
) : NavTarget
|
) : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
@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() {
|
override fun onBuilt() {
|
||||||
|
|
@ -133,7 +148,9 @@ class RoomFlowNode(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun subscribeToRoomInfoFlow(roomId: RoomId, serverNames: List<String>) {
|
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
|
// This observes the local membership changes for the room
|
||||||
val membershipUpdateFlow = membershipObserver.updates
|
val membershipUpdateFlow = membershipObserver.updates
|
||||||
|
|
@ -149,6 +166,11 @@ class RoomFlowNode(
|
||||||
currentMembershipFlow.onEach { (previousMembership, membership) ->
|
currentMembershipFlow.onEach { (previousMembership, membership) ->
|
||||||
Timber.d("Room membership: $membership")
|
Timber.d("Room membership: $membership")
|
||||||
if (membership == CurrentUserMembership.JOINED) {
|
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))
|
backstack.newRoot(NavTarget.JoinedRoom(roomId))
|
||||||
} else {
|
} else {
|
||||||
val leavingFromCurrentDevice =
|
val leavingFromCurrentDevice =
|
||||||
|
|
@ -163,7 +185,7 @@ class RoomFlowNode(
|
||||||
NavTarget.JoinRoom(
|
NavTarget.JoinRoom(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
serverNames = serverNames,
|
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 roomFlowNodeCallback = plugins<JoinedRoomLoadedFlowNode.Callback>()
|
||||||
val inputs = JoinedRoomFlowNode.Inputs(
|
val inputs = JoinedRoomFlowNode.Inputs(
|
||||||
roomId = navTarget.roomId,
|
roomId = navTarget.roomId,
|
||||||
initialElement = inputs.initialElement
|
initialElement = inputs.initialElement,
|
||||||
|
joinedRoom = navTarget.joinedRoom,
|
||||||
)
|
)
|
||||||
createNode<JoinedRoomFlowNode>(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback)
|
createNode<JoinedRoomFlowNode>(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,15 @@ package io.element.android.appnav.room
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
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
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
sealed interface RoomNavigationTarget : Parcelable {
|
sealed interface RoomNavigationTarget : Parcelable {
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Root(
|
data class Root(
|
||||||
val eventId: EventId? = null,
|
val eventId: EventId? = null,
|
||||||
|
@IgnoredOnParcel val joinedRoom: JoinedRoom? = null,
|
||||||
) : RoomNavigationTarget
|
) : RoomNavigationTarget
|
||||||
|
|
||||||
@Parcelize
|
@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.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
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.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.LoadingRoomState
|
||||||
import io.element.android.libraries.matrix.ui.room.LoadingRoomStateFlowFactory
|
import io.element.android.libraries.matrix.ui.room.LoadingRoomStateFlowFactory
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
|
@ -63,11 +64,12 @@ class JoinedRoomFlowNode(
|
||||||
) {
|
) {
|
||||||
data class Inputs(
|
data class Inputs(
|
||||||
val roomId: RoomId,
|
val roomId: RoomId,
|
||||||
|
val joinedRoom: JoinedRoom?,
|
||||||
val initialElement: RoomNavigationTarget,
|
val initialElement: RoomNavigationTarget,
|
||||||
) : NodeInputs
|
) : NodeInputs
|
||||||
|
|
||||||
private val inputs: Inputs = inputs()
|
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 {
|
sealed interface NavTarget : Parcelable {
|
||||||
@Parcelize
|
@Parcelize
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,19 @@ import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class LoadingBaseRoomStateFlowFactoryTest {
|
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
|
@Test
|
||||||
fun `flow should emit Loading and then Loaded when there is a room in cache`() = runTest {
|
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))
|
val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(sessionId = A_SESSION_ID, roomId = A_ROOM_ID))
|
||||||
|
|
@ -31,7 +44,7 @@ class LoadingBaseRoomStateFlowFactoryTest {
|
||||||
}
|
}
|
||||||
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
||||||
flowFactory
|
flowFactory
|
||||||
.create(this, A_ROOM_ID)
|
.create(lifecycleScope = this, roomId = A_ROOM_ID, joinedRoom = null)
|
||||||
.test {
|
.test {
|
||||||
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading)
|
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading)
|
||||||
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loaded(room))
|
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loaded(room))
|
||||||
|
|
@ -45,7 +58,7 @@ class LoadingBaseRoomStateFlowFactoryTest {
|
||||||
val matrixClient = FakeMatrixClient(A_SESSION_ID, roomListService = roomListService)
|
val matrixClient = FakeMatrixClient(A_SESSION_ID, roomListService = roomListService)
|
||||||
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
||||||
flowFactory
|
flowFactory
|
||||||
.create(this, A_ROOM_ID)
|
.create(lifecycleScope = this, roomId = A_ROOM_ID, joinedRoom = null)
|
||||||
.test {
|
.test {
|
||||||
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading)
|
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading)
|
||||||
matrixClient.givenGetRoomResult(A_ROOM_ID, room)
|
matrixClient.givenGetRoomResult(A_ROOM_ID, room)
|
||||||
|
|
@ -60,7 +73,7 @@ class LoadingBaseRoomStateFlowFactoryTest {
|
||||||
val matrixClient = FakeMatrixClient(A_SESSION_ID, roomListService = roomListService)
|
val matrixClient = FakeMatrixClient(A_SESSION_ID, roomListService = roomListService)
|
||||||
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
||||||
flowFactory
|
flowFactory
|
||||||
.create(this, A_ROOM_ID)
|
.create(lifecycleScope = this, roomId = A_ROOM_ID, joinedRoom = null)
|
||||||
.test {
|
.test {
|
||||||
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading)
|
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading)
|
||||||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import com.bumble.appyx.core.node.Node
|
||||||
import com.bumble.appyx.core.plugin.Plugin
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
import io.element.android.libraries.architecture.FeatureEntryPoint
|
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
|
|
||||||
interface HomeEntryPoint : FeatureEntryPoint {
|
interface HomeEntryPoint : FeatureEntryPoint {
|
||||||
fun createNode(
|
fun createNode(
|
||||||
|
|
@ -22,7 +23,7 @@ interface HomeEntryPoint : FeatureEntryPoint {
|
||||||
): Node
|
): Node
|
||||||
|
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun navigateToRoom(roomId: RoomId)
|
fun navigateToRoom(roomId: RoomId, joinedRoom: JoinedRoom?)
|
||||||
fun navigateToCreateRoom()
|
fun navigateToCreateRoom()
|
||||||
fun navigateToSettings()
|
fun navigateToSettings()
|
||||||
fun navigateToSetUpRecovery()
|
fun navigateToSetUpRecovery()
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ import androidx.activity.compose.LocalActivity
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.coroutineScope
|
import androidx.lifecycle.coroutineScope
|
||||||
|
|
@ -41,20 +43,33 @@ import io.element.android.features.logout.api.direct.DirectLogoutView
|
||||||
import io.element.android.features.reportroom.api.ReportRoomEntryPoint
|
import io.element.android.features.reportroom.api.ReportRoomEntryPoint
|
||||||
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint
|
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint
|
||||||
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType
|
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType
|
||||||
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.BackstackView
|
import io.element.android.libraries.architecture.BackstackView
|
||||||
import io.element.android.libraries.architecture.BaseFlowNode
|
import io.element.android.libraries.architecture.BaseFlowNode
|
||||||
import io.element.android.libraries.architecture.appyx.launchMolecule
|
import io.element.android.libraries.architecture.appyx.launchMolecule
|
||||||
import io.element.android.libraries.architecture.callback
|
import io.element.android.libraries.architecture.callback
|
||||||
|
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||||
import io.element.android.libraries.deeplink.api.usecase.InviteFriendsUseCase
|
import io.element.android.libraries.deeplink.api.usecase.InviteFriendsUseCase
|
||||||
|
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||||
|
import io.element.android.libraries.designsystem.utils.DelayedVisibility
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.NonCancellable
|
import kotlinx.coroutines.NonCancellable
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import timber.log.Timber
|
||||||
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
|
|
@ -71,6 +86,7 @@ class HomeFlowNode(
|
||||||
private val declineInviteAndBlockUserEntryPoint: DeclineInviteAndBlockEntryPoint,
|
private val declineInviteAndBlockUserEntryPoint: DeclineInviteAndBlockEntryPoint,
|
||||||
private val changeRoomMemberRolesEntryPoint: ChangeRoomMemberRolesEntryPoint,
|
private val changeRoomMemberRolesEntryPoint: ChangeRoomMemberRolesEntryPoint,
|
||||||
private val leaveRoomRenderer: LeaveRoomRenderer,
|
private val leaveRoomRenderer: LeaveRoomRenderer,
|
||||||
|
@SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope,
|
||||||
) : BaseFlowNode<HomeFlowNode.NavTarget>(
|
) : BaseFlowNode<HomeFlowNode.NavTarget>(
|
||||||
backstack = BackStack(
|
backstack = BackStack(
|
||||||
initialElement = NavTarget.Root,
|
initialElement = NavTarget.Root,
|
||||||
|
|
@ -150,9 +166,58 @@ class HomeFlowNode(
|
||||||
return node(buildContext) { modifier ->
|
return node(buildContext) { modifier ->
|
||||||
val state by stateFlow.collectAsState()
|
val state by stateFlow.collectAsState()
|
||||||
val activity = requireNotNull(LocalActivity.current)
|
val activity = requireNotNull(LocalActivity.current)
|
||||||
|
|
||||||
|
val loadingJoinedRoomJob = remember { mutableStateOf<AsyncData<Job>>(AsyncData.Uninitialized) }
|
||||||
|
if (loadingJoinedRoomJob.value.isLoading()) {
|
||||||
|
DelayedVisibility(duration = 400.milliseconds) {
|
||||||
|
ProgressDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
loadingJoinedRoomJob.value.dataOrNull()?.cancel()
|
||||||
|
loadingJoinedRoomJob.value = AsyncData.Uninitialized
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun navigateToRoom(
|
||||||
|
roomId: RoomId,
|
||||||
|
) {
|
||||||
|
if (!loadingJoinedRoomJob.value.isUninitialized()) {
|
||||||
|
Timber.w("Already loading a room, ignoring navigateToRoom for $roomId")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val job = sessionCoroutineScope.launch {
|
||||||
|
runCatchingExceptions {
|
||||||
|
matrixClient.getJoinedRoom(roomId)
|
||||||
|
}.fold(
|
||||||
|
onSuccess = { joinedRoom ->
|
||||||
|
if (isActive) {
|
||||||
|
callback.navigateToRoom(roomId, joinedRoom)
|
||||||
|
loadingJoinedRoomJob.value = AsyncData.Success(coroutineContext.job)
|
||||||
|
// Wait a bit before resetting the state to avoid allowing to open several rooms
|
||||||
|
delay(200.milliseconds)
|
||||||
|
loadingJoinedRoomJob.value = AsyncData.Uninitialized
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
// If the operation wasn't cancelled, navigate without the room, using the room id
|
||||||
|
if (it !is CancellationException) {
|
||||||
|
callback.navigateToRoom(roomId, null)
|
||||||
|
}
|
||||||
|
loadingJoinedRoomJob.value = AsyncData.Failure(error = it, prevData = coroutineContext.job)
|
||||||
|
// Wait a bit before resetting the state to avoid allowing to open several rooms
|
||||||
|
delay(200.milliseconds)
|
||||||
|
loadingJoinedRoomJob.value = AsyncData.Uninitialized
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
loadingJoinedRoomJob.value = AsyncData.Loading(job)
|
||||||
|
}
|
||||||
|
|
||||||
HomeView(
|
HomeView(
|
||||||
homeState = state,
|
homeState = state,
|
||||||
onRoomClick = callback::navigateToRoom,
|
onRoomClick = ::navigateToRoom,
|
||||||
onSettingsClick = callback::navigateToSettings,
|
onSettingsClick = callback::navigateToSettings,
|
||||||
onStartChatClick = callback::navigateToCreateRoom,
|
onStartChatClick = callback::navigateToCreateRoom,
|
||||||
onSetUpRecoveryClick = callback::navigateToSetUpRecovery,
|
onSetUpRecoveryClick = callback::navigateToSetUpRecovery,
|
||||||
|
|
@ -165,7 +230,7 @@ class HomeFlowNode(
|
||||||
acceptDeclineInviteView = {
|
acceptDeclineInviteView = {
|
||||||
acceptDeclineInviteView.Render(
|
acceptDeclineInviteView.Render(
|
||||||
state = state.roomListState.acceptDeclineInviteState,
|
state = state.roomListState.acceptDeclineInviteState,
|
||||||
onAcceptInviteSuccess = callback::navigateToRoom,
|
onAcceptInviteSuccess = ::navigateToRoom,
|
||||||
onDeclineInviteSuccess = { },
|
onDeclineInviteSuccess = { },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -13,17 +13,19 @@ import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.home.api.HomeEntryPoint
|
import io.element.android.features.home.api.HomeEntryPoint
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
import io.element.android.tests.testutils.node.TestParentNode
|
import io.element.android.tests.testutils.node.TestParentNode
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class DefaultHomeEntryPointTest {
|
class DefaultHomeEntryPointTest {
|
||||||
@Test
|
@Test
|
||||||
fun `test node builder`() {
|
fun `test node builder`() = runTest {
|
||||||
val entryPoint = DefaultHomeEntryPoint()
|
val entryPoint = DefaultHomeEntryPoint()
|
||||||
val parentNode = TestParentNode.create { buildContext, plugins ->
|
val parentNode = TestParentNode.create { buildContext, plugins ->
|
||||||
HomeFlowNode(
|
HomeFlowNode(
|
||||||
|
|
@ -39,10 +41,11 @@ class DefaultHomeEntryPointTest {
|
||||||
declineInviteAndBlockUserEntryPoint = { _, _, _ -> lambdaError() },
|
declineInviteAndBlockUserEntryPoint = { _, _, _ -> lambdaError() },
|
||||||
changeRoomMemberRolesEntryPoint = { _, _, _, _ -> lambdaError() },
|
changeRoomMemberRolesEntryPoint = { _, _, _, _ -> lambdaError() },
|
||||||
leaveRoomRenderer = { _, _, _ -> lambdaError() },
|
leaveRoomRenderer = { _, _, _ -> lambdaError() },
|
||||||
|
sessionCoroutineScope = backgroundScope,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val callback = object : HomeEntryPoint.Callback {
|
val callback = object : HomeEntryPoint.Callback {
|
||||||
override fun navigateToRoom(roomId: RoomId) = lambdaError()
|
override fun navigateToRoom(roomId: RoomId, joinedRoom: JoinedRoom?) = lambdaError()
|
||||||
override fun navigateToCreateRoom() = lambdaError()
|
override fun navigateToCreateRoom() = lambdaError()
|
||||||
override fun navigateToSettings() = lambdaError()
|
override fun navigateToSettings() = lambdaError()
|
||||||
override fun navigateToSetUpRecovery() = lambdaError()
|
override fun navigateToSetUpRecovery() = lambdaError()
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -261,6 +262,7 @@ class TimelinePresenter(
|
||||||
items
|
items
|
||||||
}
|
}
|
||||||
.onEach(redactedVoiceMessageManager::onEachMatrixTimelineItem)
|
.onEach(redactedVoiceMessageManager::onEachMatrixTimelineItem)
|
||||||
|
.flowOn(dispatchers.computation)
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import kotlinx.coroutines.withContext
|
||||||
import org.matrix.rustcomponents.sdk.DateDividerMode
|
import org.matrix.rustcomponents.sdk.DateDividerMode
|
||||||
import org.matrix.rustcomponents.sdk.Membership
|
import org.matrix.rustcomponents.sdk.Membership
|
||||||
import org.matrix.rustcomponents.sdk.Room
|
import org.matrix.rustcomponents.sdk.Room
|
||||||
|
import org.matrix.rustcomponents.sdk.RoomInfo
|
||||||
import org.matrix.rustcomponents.sdk.TimelineConfiguration
|
import org.matrix.rustcomponents.sdk.TimelineConfiguration
|
||||||
import org.matrix.rustcomponents.sdk.TimelineFilter
|
import org.matrix.rustcomponents.sdk.TimelineFilter
|
||||||
import org.matrix.rustcomponents.sdk.TimelineFocus
|
import org.matrix.rustcomponents.sdk.TimelineFocus
|
||||||
|
|
@ -60,7 +61,7 @@ class RustRoomFactory(
|
||||||
private val roomInfoMapper: RoomInfoMapper,
|
private val roomInfoMapper: RoomInfoMapper,
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
) {
|
) {
|
||||||
private val dispatcher = dispatchers.io.limitedParallelism(1)
|
private val dispatcher = dispatchers.computation.limitedParallelism(1)
|
||||||
private val mutex = Mutex()
|
private val mutex = Mutex()
|
||||||
private val isDestroyed: AtomicBoolean = AtomicBoolean(false)
|
private val isDestroyed: AtomicBoolean = AtomicBoolean(false)
|
||||||
|
|
||||||
|
|
@ -86,24 +87,21 @@ class RustRoomFactory(
|
||||||
return@withContext null
|
return@withContext null
|
||||||
}
|
}
|
||||||
val room = awaitRoomInRoomList(roomId) ?: return@withContext null
|
val room = awaitRoomInRoomList(roomId) ?: return@withContext null
|
||||||
getBaseRoom(room)
|
getBaseRoom(sdkRoom = room, roomInfo = room.roomInfo())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getBaseRoom(sdkRoom: Room): RustBaseRoom {
|
private fun getBaseRoom(sdkRoom: Room, roomInfo: RoomInfo) = RustBaseRoom(
|
||||||
val initialRoomInfo = sdkRoom.roomInfo()
|
sessionId = sessionId,
|
||||||
return RustBaseRoom(
|
deviceId = deviceId,
|
||||||
sessionId = sessionId,
|
innerRoom = sdkRoom,
|
||||||
deviceId = deviceId,
|
coroutineDispatchers = dispatchers,
|
||||||
innerRoom = sdkRoom,
|
roomSyncSubscriber = roomSyncSubscriber,
|
||||||
coroutineDispatchers = dispatchers,
|
roomMembershipObserver = roomMembershipObserver,
|
||||||
roomSyncSubscriber = roomSyncSubscriber,
|
roomInfoMapper = roomInfoMapper,
|
||||||
roomMembershipObserver = roomMembershipObserver,
|
initialRoomInfo = roomInfoMapper.map(roomInfo),
|
||||||
roomInfoMapper = roomInfoMapper,
|
sessionCoroutineScope = sessionCoroutineScope,
|
||||||
initialRoomInfo = roomInfoMapper.map(initialRoomInfo),
|
)
|
||||||
sessionCoroutineScope = sessionCoroutineScope,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getJoinedRoomOrPreview(roomId: RoomId, serverNames: List<String>): GetRoomResult? = withContext(dispatcher) {
|
suspend fun getJoinedRoomOrPreview(roomId: RoomId, serverNames: List<String>): GetRoomResult? = withContext(dispatcher) {
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
|
|
@ -113,10 +111,11 @@ class RustRoomFactory(
|
||||||
}
|
}
|
||||||
|
|
||||||
val sdkRoom = awaitRoomInRoomList(roomId) ?: return@withLock null
|
val sdkRoom = awaitRoomInRoomList(roomId) ?: return@withLock null
|
||||||
|
val roomInfo = sdkRoom.roomInfo()
|
||||||
|
|
||||||
val parentTransaction = analyticsService.getLongRunningTransaction(AnalyticsLongRunningTransaction.OpenRoom)
|
val parentTransaction = analyticsService.getLongRunningTransaction(AnalyticsLongRunningTransaction.OpenRoom)
|
||||||
|
|
||||||
if (sdkRoom.membership() == Membership.JOINED) {
|
if (roomInfo.membership == Membership.JOINED) {
|
||||||
analyticsService.recordTransaction(
|
analyticsService.recordTransaction(
|
||||||
name = "Get joined room",
|
name = "Get joined room",
|
||||||
operation = "RustRoomFactory.getJoinedRoomOrPreview",
|
operation = "RustRoomFactory.getJoinedRoomOrPreview",
|
||||||
|
|
@ -140,10 +139,9 @@ class RustRoomFactory(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val baseRoom = transaction.recordChildTransaction(operation = "getBaseRoom", description = "Get room from SDK") { getBaseRoom(sdkRoom) }
|
|
||||||
GetRoomResult.Joined(
|
GetRoomResult.Joined(
|
||||||
JoinedRustRoom(
|
JoinedRustRoom(
|
||||||
baseRoom = baseRoom,
|
baseRoom = getBaseRoom(sdkRoom, roomInfo),
|
||||||
notificationSettingsService = notificationSettingsService,
|
notificationSettingsService = notificationSettingsService,
|
||||||
roomContentForwarder = roomContentForwarder,
|
roomContentForwarder = roomContentForwarder,
|
||||||
liveInnerTimeline = timeline,
|
liveInnerTimeline = timeline,
|
||||||
|
|
@ -169,7 +167,7 @@ class RustRoomFactory(
|
||||||
GetRoomResult.NotJoined(
|
GetRoomResult.NotJoined(
|
||||||
NotJoinedRustRoom(
|
NotJoinedRustRoom(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
localRoom = getBaseRoom(sdkRoom),
|
localRoom = getBaseRoom(sdkRoom, roomInfo),
|
||||||
previewInfo = RoomPreviewInfoMapper.map(preview.info()),
|
previewInfo = RoomPreviewInfoMapper.map(preview.info()),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asFlow
|
import kotlinx.coroutines.flow.asFlow
|
||||||
|
|
@ -39,16 +40,21 @@ open class LoadingRoomStateProvider : PreviewParameterProvider<LoadingRoomState>
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
class LoadingRoomStateFlowFactory(private val matrixClient: MatrixClient) {
|
class LoadingRoomStateFlowFactory(private val matrixClient: MatrixClient) {
|
||||||
fun create(lifecycleScope: CoroutineScope, roomId: RoomId): StateFlow<LoadingRoomState> =
|
fun create(lifecycleScope: CoroutineScope, roomId: RoomId, joinedRoom: JoinedRoom?): StateFlow<LoadingRoomState> {
|
||||||
getJoinedRoomFlow(roomId)
|
return if (joinedRoom != null) {
|
||||||
.map { room ->
|
MutableStateFlow<LoadingRoomState>(LoadingRoomState.Loaded(joinedRoom))
|
||||||
if (room != null) {
|
} else {
|
||||||
LoadingRoomState.Loaded(room)
|
getJoinedRoomFlow(roomId)
|
||||||
} else {
|
.map { room ->
|
||||||
LoadingRoomState.Error
|
if (room != null) {
|
||||||
|
LoadingRoomState.Loaded(room)
|
||||||
|
} else {
|
||||||
|
LoadingRoomState.Error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.stateIn(lifecycleScope, SharingStarted.Eagerly, LoadingRoomState.Loading)
|
||||||
.stateIn(lifecycleScope, SharingStarted.Eagerly, LoadingRoomState.Loading)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getJoinedRoomFlow(roomId: RoomId): Flow<JoinedRoom?> = suspend {
|
private fun getJoinedRoomFlow(roomId: RoomId): Flow<JoinedRoom?> = suspend {
|
||||||
matrixClient.getJoinedRoom(roomId = roomId)
|
matrixClient.getJoinedRoom(roomId = roomId)
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,11 @@ android {
|
||||||
namespace = "ui"
|
namespace = "ui"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.withType(Test::class.java) {
|
||||||
|
// Don't fail the test run if there are no tests, this can happen if we run them with screenshot test disabled
|
||||||
|
failOnNoDiscoveredTests = false
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Paparazzi 1.3.2 workaround (see https://github.com/cashapp/paparazzi/blob/master/CHANGELOG.md#132---2024-01-13)
|
// Paparazzi 1.3.2 workaround (see https://github.com/cashapp/paparazzi/blob/master/CHANGELOG.md#132---2024-01-13)
|
||||||
constraints.add("testImplementation", "com.google.guava:guava") {
|
constraints.add("testImplementation", "com.google.guava:guava") {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue