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))
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
|
||||
interface HomeEntryPoint : FeatureEntryPoint {
|
||||
fun createNode(
|
||||
@@ -22,7 +23,7 @@ interface HomeEntryPoint : FeatureEntryPoint {
|
||||
): Node
|
||||
|
||||
interface Callback : Plugin {
|
||||
fun navigateToRoom(roomId: RoomId)
|
||||
fun navigateToRoom(roomId: RoomId, joinedRoom: JoinedRoom?)
|
||||
fun navigateToCreateRoom()
|
||||
fun navigateToSettings()
|
||||
fun navigateToSetUpRecovery()
|
||||
|
||||
@@ -14,6 +14,8 @@ import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.Lifecycle
|
||||
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.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint
|
||||
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.BaseFlowNode
|
||||
import io.element.android.libraries.architecture.appyx.launchMolecule
|
||||
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.designsystem.components.ProgressDialog
|
||||
import io.element.android.libraries.designsystem.utils.DelayedVisibility
|
||||
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.core.RoomId
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
@AssistedInject
|
||||
@@ -71,6 +86,7 @@ class HomeFlowNode(
|
||||
private val declineInviteAndBlockUserEntryPoint: DeclineInviteAndBlockEntryPoint,
|
||||
private val changeRoomMemberRolesEntryPoint: ChangeRoomMemberRolesEntryPoint,
|
||||
private val leaveRoomRenderer: LeaveRoomRenderer,
|
||||
@SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope,
|
||||
) : BaseFlowNode<HomeFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.Root,
|
||||
@@ -150,9 +166,58 @@ class HomeFlowNode(
|
||||
return node(buildContext) { modifier ->
|
||||
val state by stateFlow.collectAsState()
|
||||
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(
|
||||
homeState = state,
|
||||
onRoomClick = callback::navigateToRoom,
|
||||
onRoomClick = ::navigateToRoom,
|
||||
onSettingsClick = callback::navigateToSettings,
|
||||
onStartChatClick = callback::navigateToCreateRoom,
|
||||
onSetUpRecoveryClick = callback::navigateToSetUpRecovery,
|
||||
@@ -165,7 +230,7 @@ class HomeFlowNode(
|
||||
acceptDeclineInviteView = {
|
||||
acceptDeclineInviteView.Render(
|
||||
state = state.roomListState.acceptDeclineInviteState,
|
||||
onAcceptInviteSuccess = callback::navigateToRoom,
|
||||
onAcceptInviteSuccess = ::navigateToRoom,
|
||||
onDeclineInviteSuccess = { },
|
||||
modifier = Modifier
|
||||
)
|
||||
|
||||
@@ -13,17 +13,19 @@ import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.home.api.HomeEntryPoint
|
||||
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.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import io.element.android.tests.testutils.node.TestParentNode
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class DefaultHomeEntryPointTest {
|
||||
@Test
|
||||
fun `test node builder`() {
|
||||
fun `test node builder`() = runTest {
|
||||
val entryPoint = DefaultHomeEntryPoint()
|
||||
val parentNode = TestParentNode.create { buildContext, plugins ->
|
||||
HomeFlowNode(
|
||||
@@ -39,10 +41,11 @@ class DefaultHomeEntryPointTest {
|
||||
declineInviteAndBlockUserEntryPoint = { _, _, _ -> lambdaError() },
|
||||
changeRoomMemberRolesEntryPoint = { _, _, _, _ -> lambdaError() },
|
||||
leaveRoomRenderer = { _, _, _ -> lambdaError() },
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
)
|
||||
}
|
||||
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 navigateToSettings() = lambdaError()
|
||||
override fun navigateToSetUpRecovery() = lambdaError()
|
||||
|
||||
@@ -65,6 +65,7 @@ import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -261,6 +262,7 @@ class TimelinePresenter(
|
||||
items
|
||||
}
|
||||
.onEach(redactedVoiceMessageManager::onEachMatrixTimelineItem)
|
||||
.flowOn(dispatchers.computation)
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.DateDividerMode
|
||||
import org.matrix.rustcomponents.sdk.Membership
|
||||
import org.matrix.rustcomponents.sdk.Room
|
||||
import org.matrix.rustcomponents.sdk.RoomInfo
|
||||
import org.matrix.rustcomponents.sdk.TimelineConfiguration
|
||||
import org.matrix.rustcomponents.sdk.TimelineFilter
|
||||
import org.matrix.rustcomponents.sdk.TimelineFocus
|
||||
@@ -60,7 +61,7 @@ class RustRoomFactory(
|
||||
private val roomInfoMapper: RoomInfoMapper,
|
||||
private val analyticsService: AnalyticsService,
|
||||
) {
|
||||
private val dispatcher = dispatchers.io.limitedParallelism(1)
|
||||
private val dispatcher = dispatchers.computation.limitedParallelism(1)
|
||||
private val mutex = Mutex()
|
||||
private val isDestroyed: AtomicBoolean = AtomicBoolean(false)
|
||||
|
||||
@@ -86,24 +87,21 @@ class RustRoomFactory(
|
||||
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 {
|
||||
val initialRoomInfo = sdkRoom.roomInfo()
|
||||
return RustBaseRoom(
|
||||
sessionId = sessionId,
|
||||
deviceId = deviceId,
|
||||
innerRoom = sdkRoom,
|
||||
coroutineDispatchers = dispatchers,
|
||||
roomSyncSubscriber = roomSyncSubscriber,
|
||||
roomMembershipObserver = roomMembershipObserver,
|
||||
roomInfoMapper = roomInfoMapper,
|
||||
initialRoomInfo = roomInfoMapper.map(initialRoomInfo),
|
||||
sessionCoroutineScope = sessionCoroutineScope,
|
||||
)
|
||||
}
|
||||
private fun getBaseRoom(sdkRoom: Room, roomInfo: RoomInfo) = RustBaseRoom(
|
||||
sessionId = sessionId,
|
||||
deviceId = deviceId,
|
||||
innerRoom = sdkRoom,
|
||||
coroutineDispatchers = dispatchers,
|
||||
roomSyncSubscriber = roomSyncSubscriber,
|
||||
roomMembershipObserver = roomMembershipObserver,
|
||||
roomInfoMapper = roomInfoMapper,
|
||||
initialRoomInfo = roomInfoMapper.map(roomInfo),
|
||||
sessionCoroutineScope = sessionCoroutineScope,
|
||||
)
|
||||
|
||||
suspend fun getJoinedRoomOrPreview(roomId: RoomId, serverNames: List<String>): GetRoomResult? = withContext(dispatcher) {
|
||||
mutex.withLock {
|
||||
@@ -113,10 +111,11 @@ class RustRoomFactory(
|
||||
}
|
||||
|
||||
val sdkRoom = awaitRoomInRoomList(roomId) ?: return@withLock null
|
||||
val roomInfo = sdkRoom.roomInfo()
|
||||
|
||||
val parentTransaction = analyticsService.getLongRunningTransaction(AnalyticsLongRunningTransaction.OpenRoom)
|
||||
|
||||
if (sdkRoom.membership() == Membership.JOINED) {
|
||||
if (roomInfo.membership == Membership.JOINED) {
|
||||
analyticsService.recordTransaction(
|
||||
name = "Get joined room",
|
||||
operation = "RustRoomFactory.getJoinedRoomOrPreview",
|
||||
@@ -140,10 +139,9 @@ class RustRoomFactory(
|
||||
)
|
||||
}
|
||||
|
||||
val baseRoom = transaction.recordChildTransaction(operation = "getBaseRoom", description = "Get room from SDK") { getBaseRoom(sdkRoom) }
|
||||
GetRoomResult.Joined(
|
||||
JoinedRustRoom(
|
||||
baseRoom = baseRoom,
|
||||
baseRoom = getBaseRoom(sdkRoom, roomInfo),
|
||||
notificationSettingsService = notificationSettingsService,
|
||||
roomContentForwarder = roomContentForwarder,
|
||||
liveInnerTimeline = timeline,
|
||||
@@ -169,7 +167,7 @@ class RustRoomFactory(
|
||||
GetRoomResult.NotJoined(
|
||||
NotJoinedRustRoom(
|
||||
sessionId = sessionId,
|
||||
localRoom = getBaseRoom(sdkRoom),
|
||||
localRoom = getBaseRoom(sdkRoom, roomInfo),
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
@@ -39,16 +40,21 @@ open class LoadingRoomStateProvider : PreviewParameterProvider<LoadingRoomState>
|
||||
|
||||
@Inject
|
||||
class LoadingRoomStateFlowFactory(private val matrixClient: MatrixClient) {
|
||||
fun create(lifecycleScope: CoroutineScope, roomId: RoomId): StateFlow<LoadingRoomState> =
|
||||
getJoinedRoomFlow(roomId)
|
||||
.map { room ->
|
||||
if (room != null) {
|
||||
LoadingRoomState.Loaded(room)
|
||||
} else {
|
||||
LoadingRoomState.Error
|
||||
fun create(lifecycleScope: CoroutineScope, roomId: RoomId, joinedRoom: JoinedRoom?): StateFlow<LoadingRoomState> {
|
||||
return if (joinedRoom != null) {
|
||||
MutableStateFlow<LoadingRoomState>(LoadingRoomState.Loaded(joinedRoom))
|
||||
} else {
|
||||
getJoinedRoomFlow(roomId)
|
||||
.map { room ->
|
||||
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 {
|
||||
matrixClient.getJoinedRoom(roomId = roomId)
|
||||
|
||||
@@ -20,6 +20,11 @@ android {
|
||||
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 {
|
||||
// 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") {
|
||||
|
||||
Reference in New Issue
Block a user