Feature/fga/sync states (#1042)
* Change RoomSummaryDataSource to RoomListService to better reflects the rust api * Better Sync management * Sync: improve sync spinner rendering * Sync: make test compiles * Sync: add more test for sync spinner * Sync: more clean-up * Sync: pr review --------- Co-authored-by: ganfra <francoisg@element.io>
This commit is contained in:
parent
4e94d4da6b
commit
226a3dbf28
44 changed files with 547 additions and 356 deletions
|
|
@ -65,6 +65,8 @@ dependencies {
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
|
testImplementation(projects.features.networkmonitor.test)
|
||||||
|
testImplementation(projects.tests.testutils)
|
||||||
testImplementation(projects.features.rageshake.test)
|
testImplementation(projects.features.rageshake.test)
|
||||||
testImplementation(projects.features.rageshake.impl)
|
testImplementation(projects.features.rageshake.impl)
|
||||||
testImplementation(projects.services.appnavstate.test)
|
testImplementation(projects.services.appnavstate.test)
|
||||||
|
|
|
||||||
|
|
@ -69,10 +69,13 @@ import io.element.android.libraries.matrix.ui.di.MatrixUIBindings
|
||||||
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
|
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
|
||||||
import io.element.android.services.appnavstate.api.AppNavigationStateService
|
import io.element.android.services.appnavstate.api.AppNavigationStateService
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
@ContributesNode(AppScope::class)
|
@ContributesNode(AppScope::class)
|
||||||
class LoggedInFlowNode @AssistedInject constructor(
|
class LoggedInFlowNode @AssistedInject constructor(
|
||||||
|
|
@ -123,7 +126,6 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
|
|
||||||
override fun onBuilt() {
|
override fun onBuilt() {
|
||||||
super.onBuilt()
|
super.onBuilt()
|
||||||
|
|
||||||
lifecycle.subscribe(
|
lifecycle.subscribe(
|
||||||
onCreate = {
|
onCreate = {
|
||||||
plugins<LifecycleCallback>().forEach { it.onFlowCreated(id, inputs.matrixClient) }
|
plugins<LifecycleCallback>().forEach { it.onFlowCreated(id, inputs.matrixClient) }
|
||||||
|
|
@ -138,12 +140,8 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
backstack.push(NavTarget.Ftue)
|
backstack.push(NavTarget.Ftue)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onStart = {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
syncService.startSync()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onStop = {
|
onStop = {
|
||||||
|
//Counterpart startSync is done in observeSyncStateAndNetworkStatus method.
|
||||||
syncService.stopSync()
|
syncService.stopSync()
|
||||||
},
|
},
|
||||||
onDestroy = {
|
onDestroy = {
|
||||||
|
|
@ -153,22 +151,24 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
loggedInFlowProcessor.stopObserving()
|
loggedInFlowProcessor.stopObserving()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
observeSyncStateAndNetworkStatus()
|
observeSyncStateAndNetworkStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
private fun observeSyncStateAndNetworkStatus() {
|
private fun observeSyncStateAndNetworkStatus() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
combine(
|
combine(
|
||||||
syncService.syncState,
|
// small debounce to avoid spamming startSync when the state is changing quickly in case of error.
|
||||||
|
syncService.syncState.debounce(100),
|
||||||
networkMonitor.connectivity
|
networkMonitor.connectivity
|
||||||
) { syncState, networkStatus ->
|
) { syncState, networkStatus ->
|
||||||
syncState == SyncState.Error && networkStatus == NetworkStatus.Online
|
Pair(syncState, networkStatus)
|
||||||
}
|
}
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.collect { restartSync ->
|
.collect { (syncState, networkStatus) ->
|
||||||
if (restartSync) {
|
Timber.d("Sync state: $syncState, network status: $networkStatus")
|
||||||
|
if (syncState != SyncState.Running && networkStatus == NetworkStatus.Online) {
|
||||||
syncService.startSync()
|
syncService.startSync()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -351,3 +351,4 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
backstack.push(NavTarget.InviteList)
|
backstack.push(NavTarget.InviteList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,16 +21,27 @@ import android.os.Build
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||||
|
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
||||||
import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter
|
import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter
|
||||||
import io.element.android.libraries.push.api.PushService
|
import io.element.android.libraries.push.api.PushService
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val DELAY_BEFORE_SHOWING_SYNC_SPINNER_IN_MILLIS = 1500L
|
||||||
|
|
||||||
class LoggedInPresenter @Inject constructor(
|
class LoggedInPresenter @Inject constructor(
|
||||||
private val matrixClient: MatrixClient,
|
private val matrixClient: MatrixClient,
|
||||||
private val permissionsPresenterFactory: PermissionsPresenter.Factory,
|
private val permissionsPresenterFactory: PermissionsPresenter.Factory,
|
||||||
|
private val networkMonitor: NetworkMonitor,
|
||||||
private val pushService: PushService,
|
private val pushService: PushService,
|
||||||
) : Presenter<LoggedInState> {
|
) : Presenter<LoggedInState> {
|
||||||
|
|
||||||
|
|
@ -53,18 +64,25 @@ class LoggedInPresenter @Inject constructor(
|
||||||
pushService.registerWith(matrixClient, pushProvider, distributor)
|
pushService.registerWith(matrixClient, pushProvider, distributor)
|
||||||
}
|
}
|
||||||
|
|
||||||
val syncState = matrixClient.syncService().syncState.collectAsState()
|
val roomListState by matrixClient.roomListService.state.collectAsState()
|
||||||
|
val networkStatus by networkMonitor.connectivity.collectAsState()
|
||||||
val permissionsState = postNotificationPermissionsPresenter.present()
|
val permissionsState = postNotificationPermissionsPresenter.present()
|
||||||
|
var showSyncSpinner by remember {
|
||||||
// fun handleEvents(event: LoggedInEvents) {
|
mutableStateOf(false)
|
||||||
// when (event) {
|
}
|
||||||
// }
|
LaunchedEffect(roomListState, networkStatus) {
|
||||||
// }
|
showSyncSpinner = when {
|
||||||
|
networkStatus == NetworkStatus.Offline -> false
|
||||||
|
roomListState == RoomListService.State.Running -> false
|
||||||
|
else -> {
|
||||||
|
delay(DELAY_BEFORE_SHOWING_SYNC_SPINNER_IN_MILLIS)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return LoggedInState(
|
return LoggedInState(
|
||||||
syncState = syncState.value,
|
showSyncSpinner = showSyncSpinner,
|
||||||
permissionsState = permissionsState,
|
permissionsState = permissionsState,
|
||||||
// eventSink = ::handleEvents
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,9 @@
|
||||||
|
|
||||||
package io.element.android.appnav.loggedin
|
package io.element.android.appnav.loggedin
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
|
||||||
import io.element.android.libraries.permissions.api.PermissionsState
|
import io.element.android.libraries.permissions.api.PermissionsState
|
||||||
|
|
||||||
data class LoggedInState(
|
data class LoggedInState(
|
||||||
val syncState: SyncState,
|
val showSyncSpinner: Boolean,
|
||||||
val permissionsState: PermissionsState,
|
val permissionsState: PermissionsState,
|
||||||
// val eventSink: (LoggedInEvents) -> Unit
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,22 +17,20 @@
|
||||||
package io.element.android.appnav.loggedin
|
package io.element.android.appnav.loggedin
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
|
||||||
import io.element.android.libraries.permissions.api.createDummyPostNotificationPermissionsState
|
import io.element.android.libraries.permissions.api.createDummyPostNotificationPermissionsState
|
||||||
|
|
||||||
open class LoggedInStateProvider : PreviewParameterProvider<LoggedInState> {
|
open class LoggedInStateProvider : PreviewParameterProvider<LoggedInState> {
|
||||||
override val values: Sequence<LoggedInState>
|
override val values: Sequence<LoggedInState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aLoggedInState(),
|
aLoggedInState(false),
|
||||||
aLoggedInState(syncState = SyncState.Idle),
|
aLoggedInState(true),
|
||||||
// Add other state here
|
// Add other state here
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aLoggedInState(
|
fun aLoggedInState(
|
||||||
syncState: SyncState = SyncState.Running,
|
showSyncSpinner: Boolean = true,
|
||||||
) = LoggedInState(
|
) = LoggedInState(
|
||||||
syncState = syncState,
|
showSyncSpinner = showSyncSpinner,
|
||||||
permissionsState = createDummyPostNotificationPermissionsState(),
|
permissionsState = createDummyPostNotificationPermissionsState(),
|
||||||
// eventSink = {}
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ fun LoggedInView(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(top = 8.dp)
|
.padding(top = 8.dp)
|
||||||
.align(Alignment.TopCenter),
|
.align(Alignment.TopCenter),
|
||||||
syncState = state.syncState,
|
isVisible = state.showSyncSpinner,
|
||||||
)
|
)
|
||||||
PermissionsView(
|
PermissionsView(
|
||||||
state = state.permissionsState,
|
state = state.permissionsState,
|
||||||
|
|
|
||||||
|
|
@ -38,19 +38,18 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||||
import io.element.android.libraries.designsystem.theme.components.Surface
|
import io.element.android.libraries.designsystem.theme.components.Surface
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
|
||||||
import io.element.android.libraries.theme.ElementTheme
|
import io.element.android.libraries.theme.ElementTheme
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SyncStateView(
|
fun SyncStateView(
|
||||||
syncState: SyncState,
|
isVisible: Boolean,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val animationSpec = spring<Float>(stiffness = 500F)
|
val animationSpec = spring<Float>(stiffness = 500F)
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
visible = syncState.mustBeVisible(),
|
visible = isVisible,
|
||||||
enter = fadeIn(animationSpec = animationSpec),
|
enter = fadeIn(animationSpec = animationSpec),
|
||||||
exit = fadeOut(animationSpec = animationSpec),
|
exit = fadeOut(animationSpec = animationSpec),
|
||||||
) {
|
) {
|
||||||
|
|
@ -60,15 +59,15 @@ fun SyncStateView(
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(color = ElementTheme.colors.bgSubtleSecondary)
|
.background(color = ElementTheme.colors.bgSubtleSecondary)
|
||||||
.padding(horizontal = 24.dp, vertical = 10.dp),
|
.padding(horizontal = 24.dp, vertical = 10.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
||||||
) {
|
) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.progressSemantics()
|
.progressSemantics()
|
||||||
.size(12.dp),
|
.size(12.dp),
|
||||||
color = ElementTheme.colors.textPrimary,
|
color = ElementTheme.colors.textPrimary,
|
||||||
strokeWidth = 1.5.dp,
|
strokeWidth = 1.5.dp,
|
||||||
)
|
)
|
||||||
|
|
@ -82,20 +81,13 @@ fun SyncStateView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun SyncState.mustBeVisible() = when (this) {
|
|
||||||
SyncState.Idle -> true /* Cold start of the app */
|
|
||||||
SyncState.Running -> false
|
|
||||||
SyncState.Error -> false /* In this case, the network error banner can be displayed */
|
|
||||||
SyncState.Terminated -> true /* The app is resumed and the sync is started again */
|
|
||||||
}
|
|
||||||
|
|
||||||
@DayNightPreviews
|
@DayNightPreviews
|
||||||
@Composable
|
@Composable
|
||||||
internal fun SyncStateViewPreview() = ElementPreview {
|
internal fun SyncStateViewPreview() = ElementPreview {
|
||||||
// Add a box to see the shadow
|
// Add a box to see the shadow
|
||||||
Box(modifier = Modifier.padding(24.dp)) {
|
Box(modifier = Modifier.padding(24.dp)) {
|
||||||
SyncStateView(
|
SyncStateView(
|
||||||
syncState = SyncState.Idle
|
isVisible = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,18 @@ import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||||
|
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
|
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||||
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
||||||
import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter
|
import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter
|
||||||
import io.element.android.libraries.push.api.PushService
|
import io.element.android.libraries.push.api.PushService
|
||||||
import io.element.android.libraries.pushproviders.api.Distributor
|
import io.element.android.libraries.pushproviders.api.Distributor
|
||||||
import io.element.android.libraries.pushproviders.api.PushProvider
|
import io.element.android.libraries.pushproviders.api.PushProvider
|
||||||
|
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
|
@ -42,14 +47,33 @@ class LoggedInPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createPresenter(): LoggedInPresenter {
|
@Test
|
||||||
|
fun `present - show sync spinner`() = runTest {
|
||||||
|
val roomListService = FakeRoomListService()
|
||||||
|
val presenter = createPresenter(roomListService, NetworkStatus.Online)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
assertThat(initialState.showSyncSpinner).isFalse()
|
||||||
|
consumeItemsUntilPredicate { it.showSyncSpinner }
|
||||||
|
roomListService.postState(RoomListService.State.Running)
|
||||||
|
consumeItemsUntilPredicate { !it.showSyncSpinner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createPresenter(
|
||||||
|
roomListService: RoomListService = FakeRoomListService(),
|
||||||
|
networkStatus: NetworkStatus = NetworkStatus.Offline
|
||||||
|
): LoggedInPresenter {
|
||||||
return LoggedInPresenter(
|
return LoggedInPresenter(
|
||||||
matrixClient = FakeMatrixClient(),
|
matrixClient = FakeMatrixClient(roomListService = roomListService),
|
||||||
permissionsPresenterFactory = object : PermissionsPresenter.Factory {
|
permissionsPresenterFactory = object : PermissionsPresenter.Factory {
|
||||||
override fun create(permission: String): PermissionsPresenter {
|
override fun create(permission: String): PermissionsPresenter {
|
||||||
return NoopPermissionsPresenter()
|
return NoopPermissionsPresenter()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
networkMonitor = FakeNetworkMonitor(networkStatus),
|
||||||
pushService = object : PushService {
|
pushService = object : PushService {
|
||||||
override fun notificationStyleChanged() {
|
override fun notificationStyleChanged() {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,12 @@ package io.element.android.appnav.room
|
||||||
|
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth
|
import com.google.common.truth.Truth
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
|
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||||
import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource
|
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
|
@ -47,29 +47,29 @@ class LoadingRoomStateFlowFactoryTest {
|
||||||
@Test
|
@Test
|
||||||
fun `flow should emit Loading and then Loaded when there is a room in cache after SS is loaded`() = runTest {
|
fun `flow should emit Loading and then Loaded when there is a room in cache after SS is loaded`() = runTest {
|
||||||
val room = FakeMatrixRoom(sessionId= A_SESSION_ID, roomId = A_ROOM_ID)
|
val room = FakeMatrixRoom(sessionId= A_SESSION_ID, roomId = A_ROOM_ID)
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource()
|
val roomListService = FakeRoomListService()
|
||||||
val matrixClient = FakeMatrixClient(A_SESSION_ID, roomSummaryDataSource = roomSummaryDataSource)
|
val matrixClient = FakeMatrixClient(A_SESSION_ID, roomListService = roomListService)
|
||||||
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
||||||
flowFactory
|
flowFactory
|
||||||
.create(this, A_ROOM_ID)
|
.create(this, A_ROOM_ID)
|
||||||
.test {
|
.test {
|
||||||
Truth.assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading)
|
Truth.assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading)
|
||||||
matrixClient.givenGetRoomResult(A_ROOM_ID, room)
|
matrixClient.givenGetRoomResult(A_ROOM_ID, room)
|
||||||
roomSummaryDataSource.postLoadingState(RoomSummaryDataSource.LoadingState.Loaded(1))
|
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||||
Truth.assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loaded(room))
|
Truth.assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loaded(room))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `flow should emit Loading and then Error when there is no room in cache after SS is loaded`() = runTest {
|
fun `flow should emit Loading and then Error when there is no room in cache after SS is loaded`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource()
|
val roomListService = FakeRoomListService()
|
||||||
val matrixClient = FakeMatrixClient(A_SESSION_ID, roomSummaryDataSource = roomSummaryDataSource)
|
val matrixClient = FakeMatrixClient(A_SESSION_ID, roomListService = roomListService)
|
||||||
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
||||||
flowFactory
|
flowFactory
|
||||||
.create(this, A_ROOM_ID)
|
.create(this, A_ROOM_ID)
|
||||||
.test {
|
.test {
|
||||||
Truth.assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading)
|
Truth.assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading)
|
||||||
roomSummaryDataSource.postLoadingState(RoomSummaryDataSource.LoadingState.Loaded(1))
|
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||||
Truth.assertThat(awaitItem()).isEqualTo(LoadingRoomState.Error)
|
Truth.assertThat(awaitItem()).isEqualTo(LoadingRoomState.Error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
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.libraries.matrix.api.room.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
|
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
import io.element.android.services.analytics.api.extensions.toAnalyticsJoinedRoom
|
import io.element.android.services.analytics.api.extensions.toAnalyticsJoinedRoom
|
||||||
|
|
@ -56,8 +56,9 @@ class InviteListPresenter @Inject constructor(
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): InviteListState {
|
override fun present(): InviteListState {
|
||||||
val invites by client
|
val invites by client
|
||||||
.roomSummaryDataSource
|
.roomListService
|
||||||
.inviteRooms()
|
.invites()
|
||||||
|
.summaries
|
||||||
.collectAsState()
|
.collectAsState()
|
||||||
|
|
||||||
var seenInvites by remember { mutableStateOf<Set<RoomId>>(emptySet()) }
|
var seenInvites by remember { mutableStateOf<Set<RoomId>>(emptySet()) }
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,8 @@ 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.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummaryDetails
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
|
||||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
||||||
|
|
@ -40,7 +40,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID
|
||||||
import io.element.android.libraries.matrix.test.A_USER_NAME
|
import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||||
import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource
|
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||||
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
|
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
|
||||||
import io.element.android.libraries.push.test.notifications.FakeNotificationDrawerManager
|
import io.element.android.libraries.push.test.notifications.FakeNotificationDrawerManager
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
|
|
@ -51,9 +51,9 @@ class InviteListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - starts empty, adds invites when received`() = runTest {
|
fun `present - starts empty, adds invites when received`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource()
|
val roomListService = FakeRoomListService()
|
||||||
val presenter = createPresenter(
|
val presenter = createPresenter(
|
||||||
FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource)
|
FakeMatrixClient(roomListService = roomListService)
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -61,7 +61,7 @@ class InviteListPresenterTests {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
Truth.assertThat(initialState.inviteList).isEmpty()
|
Truth.assertThat(initialState.inviteList).isEmpty()
|
||||||
|
|
||||||
roomSummaryDataSource.postInviteRooms(listOf(aRoomSummary()))
|
roomListService.postInviteRooms(listOf(aRoomSummary()))
|
||||||
|
|
||||||
val withInviteState = awaitItem()
|
val withInviteState = awaitItem()
|
||||||
Truth.assertThat(withInviteState.inviteList.size).isEqualTo(1)
|
Truth.assertThat(withInviteState.inviteList.size).isEqualTo(1)
|
||||||
|
|
@ -72,9 +72,9 @@ class InviteListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - uses user ID and avatar for direct invites`() = runTest {
|
fun `present - uses user ID and avatar for direct invites`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource().withDirectChatInvitation()
|
val roomListService = FakeRoomListService().withDirectChatInvitation()
|
||||||
val presenter = createPresenter(
|
val presenter = createPresenter(
|
||||||
FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource)
|
FakeMatrixClient(roomListService = roomListService)
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -98,9 +98,9 @@ class InviteListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - includes sender details for room invites`() = runTest {
|
fun `present - includes sender details for room invites`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
|
val roomListService = FakeRoomListService().withRoomInvitation()
|
||||||
val presenter = createPresenter(
|
val presenter = createPresenter(
|
||||||
FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource)
|
FakeMatrixClient(roomListService = roomListService)
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -122,10 +122,10 @@ class InviteListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - shows confirm dialog for declining direct chat invites`() = runTest {
|
fun `present - shows confirm dialog for declining direct chat invites`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource().withDirectChatInvitation()
|
val roomListService = FakeRoomListService().withDirectChatInvitation()
|
||||||
val presenter = InviteListPresenter(
|
val presenter = InviteListPresenter(
|
||||||
FakeMatrixClient(
|
FakeMatrixClient(
|
||||||
roomSummaryDataSource = roomSummaryDataSource,
|
roomListService = roomListService,
|
||||||
),
|
),
|
||||||
FakeSeenInvitesStore(),
|
FakeSeenInvitesStore(),
|
||||||
FakeAnalyticsService(),
|
FakeAnalyticsService(),
|
||||||
|
|
@ -148,9 +148,9 @@ class InviteListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - shows confirm dialog for declining room invites`() = runTest {
|
fun `present - shows confirm dialog for declining room invites`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
|
val roomListService = FakeRoomListService().withRoomInvitation()
|
||||||
val presenter = createPresenter(
|
val presenter = createPresenter(
|
||||||
FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource)
|
FakeMatrixClient(roomListService = roomListService)
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -169,9 +169,9 @@ class InviteListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - hides confirm dialog when cancelling`() = runTest {
|
fun `present - hides confirm dialog when cancelling`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
|
val roomListService = FakeRoomListService().withRoomInvitation()
|
||||||
val presenter = createPresenter(
|
val presenter = createPresenter(
|
||||||
FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource)
|
FakeMatrixClient(roomListService = roomListService)
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -190,10 +190,10 @@ class InviteListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - declines invite after confirming`() = runTest {
|
fun `present - declines invite after confirming`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
|
val roomListService = FakeRoomListService().withRoomInvitation()
|
||||||
val fakeNotificationDrawerManager = FakeNotificationDrawerManager()
|
val fakeNotificationDrawerManager = FakeNotificationDrawerManager()
|
||||||
val client = FakeMatrixClient(
|
val client = FakeMatrixClient(
|
||||||
roomSummaryDataSource = roomSummaryDataSource,
|
roomListService = roomListService,
|
||||||
)
|
)
|
||||||
val room = FakeMatrixRoom()
|
val room = FakeMatrixRoom()
|
||||||
val presenter = createPresenter(client = client, notificationDrawerManager = fakeNotificationDrawerManager)
|
val presenter = createPresenter(client = client, notificationDrawerManager = fakeNotificationDrawerManager)
|
||||||
|
|
@ -217,9 +217,9 @@ class InviteListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - declines invite after confirming and sets state on error`() = runTest {
|
fun `present - declines invite after confirming and sets state on error`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
|
val roomListService = FakeRoomListService().withRoomInvitation()
|
||||||
val client = FakeMatrixClient(
|
val client = FakeMatrixClient(
|
||||||
roomSummaryDataSource = roomSummaryDataSource,
|
roomListService = roomListService,
|
||||||
)
|
)
|
||||||
val room = FakeMatrixRoom()
|
val room = FakeMatrixRoom()
|
||||||
val presenter = createPresenter(client)
|
val presenter = createPresenter(client)
|
||||||
|
|
@ -247,9 +247,9 @@ class InviteListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - dismisses declining error state`() = runTest {
|
fun `present - dismisses declining error state`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
|
val roomListService = FakeRoomListService().withRoomInvitation()
|
||||||
val client = FakeMatrixClient(
|
val client = FakeMatrixClient(
|
||||||
roomSummaryDataSource = roomSummaryDataSource,
|
roomListService = roomListService,
|
||||||
)
|
)
|
||||||
val room = FakeMatrixRoom()
|
val room = FakeMatrixRoom()
|
||||||
val presenter = createPresenter(client)
|
val presenter = createPresenter(client)
|
||||||
|
|
@ -279,10 +279,10 @@ class InviteListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - accepts invites and sets state on success`() = runTest {
|
fun `present - accepts invites and sets state on success`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
|
val roomListService = FakeRoomListService().withRoomInvitation()
|
||||||
val fakeNotificationDrawerManager = FakeNotificationDrawerManager()
|
val fakeNotificationDrawerManager = FakeNotificationDrawerManager()
|
||||||
val client = FakeMatrixClient(
|
val client = FakeMatrixClient(
|
||||||
roomSummaryDataSource = roomSummaryDataSource,
|
roomListService = roomListService,
|
||||||
)
|
)
|
||||||
val room = FakeMatrixRoom()
|
val room = FakeMatrixRoom()
|
||||||
val presenter = createPresenter(client = client, notificationDrawerManager = fakeNotificationDrawerManager)
|
val presenter = createPresenter(client = client, notificationDrawerManager = fakeNotificationDrawerManager)
|
||||||
|
|
@ -303,9 +303,9 @@ class InviteListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - accepts invites and sets state on error`() = runTest {
|
fun `present - accepts invites and sets state on error`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
|
val roomListService = FakeRoomListService().withRoomInvitation()
|
||||||
val client = FakeMatrixClient(
|
val client = FakeMatrixClient(
|
||||||
roomSummaryDataSource = roomSummaryDataSource,
|
roomListService = roomListService,
|
||||||
)
|
)
|
||||||
val room = FakeMatrixRoom()
|
val room = FakeMatrixRoom()
|
||||||
val presenter = createPresenter(client)
|
val presenter = createPresenter(client)
|
||||||
|
|
@ -325,9 +325,9 @@ class InviteListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - dismisses accepting error state`() = runTest {
|
fun `present - dismisses accepting error state`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
|
val roomListService = FakeRoomListService().withRoomInvitation()
|
||||||
val client = FakeMatrixClient(
|
val client = FakeMatrixClient(
|
||||||
roomSummaryDataSource = roomSummaryDataSource,
|
roomListService = roomListService,
|
||||||
)
|
)
|
||||||
val room = FakeMatrixRoom()
|
val room = FakeMatrixRoom()
|
||||||
val presenter = createPresenter(client)
|
val presenter = createPresenter(client)
|
||||||
|
|
@ -352,11 +352,11 @@ class InviteListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - stores seen invites when received`() = runTest {
|
fun `present - stores seen invites when received`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource()
|
val roomListService = FakeRoomListService()
|
||||||
val store = FakeSeenInvitesStore()
|
val store = FakeSeenInvitesStore()
|
||||||
val presenter = InviteListPresenter(
|
val presenter = InviteListPresenter(
|
||||||
FakeMatrixClient(
|
FakeMatrixClient(
|
||||||
roomSummaryDataSource = roomSummaryDataSource,
|
roomListService = roomListService,
|
||||||
),
|
),
|
||||||
store,
|
store,
|
||||||
FakeAnalyticsService(),
|
FakeAnalyticsService(),
|
||||||
|
|
@ -368,19 +368,19 @@ class InviteListPresenterTests {
|
||||||
awaitItem()
|
awaitItem()
|
||||||
|
|
||||||
// When one invite is received, that ID is saved
|
// When one invite is received, that ID is saved
|
||||||
roomSummaryDataSource.postInviteRooms(listOf(aRoomSummary()))
|
roomListService.postInviteRooms(listOf(aRoomSummary()))
|
||||||
|
|
||||||
awaitItem()
|
awaitItem()
|
||||||
Truth.assertThat(store.getProvidedRoomIds()).isEqualTo(setOf(A_ROOM_ID))
|
Truth.assertThat(store.getProvidedRoomIds()).isEqualTo(setOf(A_ROOM_ID))
|
||||||
|
|
||||||
// When a second is added, both are saved
|
// When a second is added, both are saved
|
||||||
roomSummaryDataSource.postInviteRooms(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2)))
|
roomListService.postInviteRooms(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2)))
|
||||||
|
|
||||||
awaitItem()
|
awaitItem()
|
||||||
Truth.assertThat(store.getProvidedRoomIds()).isEqualTo(setOf(A_ROOM_ID, A_ROOM_ID_2))
|
Truth.assertThat(store.getProvidedRoomIds()).isEqualTo(setOf(A_ROOM_ID, A_ROOM_ID_2))
|
||||||
|
|
||||||
// When they're both dismissed, an empty set is saved
|
// When they're both dismissed, an empty set is saved
|
||||||
roomSummaryDataSource.postInviteRooms(listOf())
|
roomListService.postInviteRooms(listOf())
|
||||||
|
|
||||||
awaitItem()
|
awaitItem()
|
||||||
Truth.assertThat(store.getProvidedRoomIds()).isEmpty()
|
Truth.assertThat(store.getProvidedRoomIds()).isEmpty()
|
||||||
|
|
@ -389,12 +389,12 @@ class InviteListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - marks invite as new if they're unseen`() = runTest {
|
fun `present - marks invite as new if they're unseen`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource()
|
val roomListService = FakeRoomListService()
|
||||||
val store = FakeSeenInvitesStore()
|
val store = FakeSeenInvitesStore()
|
||||||
store.publishRoomIds(setOf(A_ROOM_ID))
|
store.publishRoomIds(setOf(A_ROOM_ID))
|
||||||
val presenter = InviteListPresenter(
|
val presenter = InviteListPresenter(
|
||||||
FakeMatrixClient(
|
FakeMatrixClient(
|
||||||
roomSummaryDataSource = roomSummaryDataSource,
|
roomListService = roomListService,
|
||||||
),
|
),
|
||||||
store,
|
store,
|
||||||
FakeAnalyticsService(),
|
FakeAnalyticsService(),
|
||||||
|
|
@ -405,7 +405,7 @@ class InviteListPresenterTests {
|
||||||
}.test {
|
}.test {
|
||||||
awaitItem()
|
awaitItem()
|
||||||
|
|
||||||
roomSummaryDataSource.postInviteRooms(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2)))
|
roomListService.postInviteRooms(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2)))
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
|
|
||||||
val withInviteState = awaitItem()
|
val withInviteState = awaitItem()
|
||||||
|
|
@ -417,7 +417,7 @@ class InviteListPresenterTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun FakeRoomSummaryDataSource.withRoomInvitation(): FakeRoomSummaryDataSource {
|
private suspend fun FakeRoomListService.withRoomInvitation(): FakeRoomListService {
|
||||||
postInviteRooms(
|
postInviteRooms(
|
||||||
listOf(
|
listOf(
|
||||||
RoomSummary.Filled(
|
RoomSummary.Filled(
|
||||||
|
|
@ -446,7 +446,7 @@ class InviteListPresenterTests {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun FakeRoomSummaryDataSource.withDirectChatInvitation(): FakeRoomSummaryDataSource {
|
private suspend fun FakeRoomListService.withDirectChatInvitation(): FakeRoomListService {
|
||||||
postInviteRooms(
|
postInviteRooms(
|
||||||
listOf(
|
listOf(
|
||||||
RoomSummary.Filled(
|
RoomSummary.Filled(
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.forward
|
package io.element.android.features.messages.impl.forward
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummaryDetails
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
|
||||||
|
|
||||||
sealed interface ForwardMessagesEvents {
|
sealed interface ForwardMessagesEvents {
|
||||||
data class SetSelectedRoom(val room: RoomSummaryDetails) : ForwardMessagesEvents
|
data class SetSelectedRoom(val room: RoomSummaryDetails) : ForwardMessagesEvents
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,8 @@ 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.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.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummaryDetails
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
|
@ -65,7 +65,7 @@ class ForwardMessagesPresenter @AssistedInject constructor(
|
||||||
var results: SearchBarResultState<ImmutableList<RoomSummaryDetails>> by remember { mutableStateOf(SearchBarResultState.NotSearching()) }
|
var results: SearchBarResultState<ImmutableList<RoomSummaryDetails>> by remember { mutableStateOf(SearchBarResultState.NotSearching()) }
|
||||||
val forwardingActionState: MutableState<Async<ImmutableList<RoomId>>> = remember { mutableStateOf(Async.Uninitialized) }
|
val forwardingActionState: MutableState<Async<ImmutableList<RoomId>>> = remember { mutableStateOf(Async.Uninitialized) }
|
||||||
|
|
||||||
val summaries by client.roomSummaryDataSource.allRooms().collectAsState()
|
val summaries by client.roomListService.allRooms().summaries.collectAsState()
|
||||||
|
|
||||||
LaunchedEffect(query, summaries) {
|
LaunchedEffect(query, summaries) {
|
||||||
val filteredSummaries = summaries.filterIsInstance<RoomSummary.Filled>()
|
val filteredSummaries = summaries.filterIsInstance<RoomSummary.Filled>()
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ package io.element.android.features.messages.impl.forward
|
||||||
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
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.RoomSummaryDetails
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
data class ForwardMessagesState(
|
data class ForwardMessagesState(
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
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.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummaryDetails
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
|
||||||
import io.element.android.libraries.matrix.api.room.message.RoomMessage
|
import io.element.android.libraries.matrix.api.room.message.RoomMessage
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.designsystem.theme.roomListRoomMessage
|
import io.element.android.libraries.designsystem.theme.roomListRoomMessage
|
||||||
import io.element.android.libraries.designsystem.theme.roomListRoomName
|
import io.element.android.libraries.designsystem.theme.roomListRoomName
|
||||||
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.RoomSummaryDetails
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
|
||||||
import io.element.android.libraries.matrix.ui.components.SelectedRoom
|
import io.element.android.libraries.matrix.ui.components.SelectedRoom
|
||||||
import io.element.android.libraries.theme.ElementTheme
|
import io.element.android.libraries.theme.ElementTheme
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,12 @@ import io.element.android.features.messages.impl.forward.ForwardMessagesEvents
|
||||||
import io.element.android.features.messages.impl.forward.ForwardMessagesPresenter
|
import io.element.android.features.messages.impl.forward.ForwardMessagesPresenter
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
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.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||||
import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource
|
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomSummaryDetail
|
import io.element.android.libraries.matrix.test.room.aRoomSummaryDetail
|
||||||
|
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -76,10 +76,10 @@ class ForwardMessagesPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - update query`() = runTest {
|
fun `present - update query`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource().apply {
|
val roomListService = FakeRoomListService().apply {
|
||||||
postAllRooms(listOf(RoomSummary.Filled(aRoomSummaryDetail())))
|
postAllRooms(listOf(RoomSummary.Filled(aRoomSummaryDetail())))
|
||||||
}
|
}
|
||||||
val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource)
|
val client = FakeMatrixClient(roomListService = roomListService)
|
||||||
val presenter = aPresenter(client = client)
|
val presenter = aPresenter(client = client)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -166,11 +166,10 @@ class ForwardMessagesPresenterTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.aPresenter(
|
private fun CoroutineScope.aPresenter(
|
||||||
eventId: EventId = AN_EVENT_ID,
|
eventId: EventId = AN_EVENT_ID,
|
||||||
fakeMatrixRoom: FakeMatrixRoom = FakeMatrixRoom(),
|
fakeMatrixRoom: FakeMatrixRoom = FakeMatrixRoom(),
|
||||||
coroutineScope: CoroutineScope = this,
|
coroutineScope: CoroutineScope = this,
|
||||||
client: FakeMatrixClient = FakeMatrixClient(),
|
client: FakeMatrixClient = FakeMatrixClient(),
|
||||||
) = ForwardMessagesPresenter(eventId.value, fakeMatrixRoom, coroutineScope, client)
|
) = ForwardMessagesPresenter(eventId.value, fakeMatrixRoom, coroutineScope, client)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,6 @@ class RoomListPresenter @Inject constructor(
|
||||||
// Safe to give bigger size than room list
|
// Safe to give bigger size than room list
|
||||||
val extendedRangeEnd = range.last + midExtendedRangeSize
|
val extendedRangeEnd = range.last + midExtendedRangeSize
|
||||||
val extendedRange = IntRange(extendedRangeStart, extendedRangeEnd)
|
val extendedRange = IntRange(extendedRangeStart, extendedRangeEnd)
|
||||||
client.roomSummaryDataSource.updateAllRoomsVisibleRange(extendedRange)
|
client.roomListService.updateAllRoomsVisibleRange(extendedRange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
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.libraries.matrix.api.room.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -44,8 +44,9 @@ class DefaultInviteStateDataSource @Inject constructor(
|
||||||
@Composable
|
@Composable
|
||||||
override fun inviteState(): InvitesState {
|
override fun inviteState(): InvitesState {
|
||||||
val invites by client
|
val invites by client
|
||||||
.roomSummaryDataSource
|
.roomListService
|
||||||
.inviteRooms()
|
.invites()
|
||||||
|
.summaries
|
||||||
.collectAsState()
|
.collectAsState()
|
||||||
|
|
||||||
val seenInvites by seenInvitesStore
|
val seenInvites by seenInvitesStore
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
||||||
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.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
@ -44,7 +44,7 @@ import kotlinx.coroutines.withContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomListDataSource @Inject constructor(
|
class RoomListDataSource @Inject constructor(
|
||||||
private val roomSummaryDataSource: RoomSummaryDataSource,
|
private val roomListService: RoomListService,
|
||||||
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
|
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
|
||||||
private val roomLastMessageFormatter: RoomLastMessageFormatter,
|
private val roomLastMessageFormatter: RoomLastMessageFormatter,
|
||||||
private val coroutineDispatchers: CoroutineDispatchers,
|
private val coroutineDispatchers: CoroutineDispatchers,
|
||||||
|
|
@ -61,8 +61,9 @@ class RoomListDataSource @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun launchIn(coroutineScope: CoroutineScope) {
|
fun launchIn(coroutineScope: CoroutineScope) {
|
||||||
roomSummaryDataSource
|
roomListService
|
||||||
.allRooms()
|
.allRooms()
|
||||||
|
.summaries
|
||||||
.onEach { roomSummaries ->
|
.onEach { roomSummaries ->
|
||||||
replaceWith(roomSummaries)
|
replaceWith(roomSummaries)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,8 @@ import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||||
import io.element.android.libraries.matrix.test.A_USER_NAME
|
import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource
|
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
|
import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
|
||||||
|
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||||
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
|
|
@ -111,9 +111,9 @@ class RoomListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - load 1 room with success`() = runTest {
|
fun `present - load 1 room with success`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource()
|
val roomListService = FakeRoomListService()
|
||||||
val matrixClient = FakeMatrixClient(
|
val matrixClient = FakeMatrixClient(
|
||||||
roomSummaryDataSource = roomSummaryDataSource
|
roomListService = roomListService
|
||||||
)
|
)
|
||||||
val presenter = createRoomListPresenter(matrixClient)
|
val presenter = createRoomListPresenter(matrixClient)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|
@ -123,7 +123,7 @@ class RoomListPresenterTests {
|
||||||
// Room list is loaded with 16 placeholders
|
// Room list is loaded with 16 placeholders
|
||||||
Truth.assertThat(initialState.roomList.size).isEqualTo(16)
|
Truth.assertThat(initialState.roomList.size).isEqualTo(16)
|
||||||
Truth.assertThat(initialState.roomList.all { it.isPlaceholder }).isTrue()
|
Truth.assertThat(initialState.roomList.all { it.isPlaceholder }).isTrue()
|
||||||
roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled()))
|
roomListService.postAllRooms(listOf(aRoomSummaryFilled()))
|
||||||
val withRoomState = consumeItemsUntilPredicate { state -> state.roomList.size == 1 }.last()
|
val withRoomState = consumeItemsUntilPredicate { state -> state.roomList.size == 1 }.last()
|
||||||
Truth.assertThat(withRoomState.roomList.size).isEqualTo(1)
|
Truth.assertThat(withRoomState.roomList.size).isEqualTo(1)
|
||||||
Truth.assertThat(withRoomState.roomList.first())
|
Truth.assertThat(withRoomState.roomList.first())
|
||||||
|
|
@ -133,15 +133,15 @@ class RoomListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - load 1 room with success and filter rooms`() = runTest {
|
fun `present - load 1 room with success and filter rooms`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource()
|
val roomListService = FakeRoomListService()
|
||||||
val matrixClient = FakeMatrixClient(
|
val matrixClient = FakeMatrixClient(
|
||||||
roomSummaryDataSource = roomSummaryDataSource
|
roomListService = roomListService
|
||||||
)
|
)
|
||||||
val presenter = createRoomListPresenter(matrixClient)
|
val presenter = createRoomListPresenter(matrixClient)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled()))
|
roomListService.postAllRooms(listOf(aRoomSummaryFilled()))
|
||||||
val loadedState = consumeItemsUntilPredicate { state -> state.roomList.size == 1 }.last()
|
val loadedState = consumeItemsUntilPredicate { state -> state.roomList.size == 1 }.last()
|
||||||
// Test filtering with result
|
// Test filtering with result
|
||||||
loadedState.eventSink.invoke(RoomListEvents.UpdateFilter(A_ROOM_NAME.substring(0, 3)))
|
loadedState.eventSink.invoke(RoomListEvents.UpdateFilter(A_ROOM_NAME.substring(0, 3)))
|
||||||
|
|
@ -160,39 +160,39 @@ class RoomListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - update visible range`() = runTest {
|
fun `present - update visible range`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource()
|
val roomListService = FakeRoomListService()
|
||||||
val matrixClient = FakeMatrixClient(
|
val matrixClient = FakeMatrixClient(
|
||||||
roomSummaryDataSource = roomSummaryDataSource
|
roomListService = roomListService
|
||||||
)
|
)
|
||||||
val presenter = createRoomListPresenter(matrixClient)
|
val presenter = createRoomListPresenter(matrixClient)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled()))
|
roomListService.postAllRooms(listOf(aRoomSummaryFilled()))
|
||||||
val loadedState = awaitItem()
|
val loadedState = awaitItem()
|
||||||
// check initial value
|
// check initial value
|
||||||
Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange).isNull()
|
Truth.assertThat(roomListService.latestSlidingSyncRange).isNull()
|
||||||
// Test empty range
|
// Test empty range
|
||||||
loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(1, 0)))
|
loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(1, 0)))
|
||||||
Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange).isNull()
|
Truth.assertThat(roomListService.latestSlidingSyncRange).isNull()
|
||||||
// Update visible range and check that range is transmitted to the SDK after computation
|
// Update visible range and check that range is transmitted to the SDK after computation
|
||||||
loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(0, 0)))
|
loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(0, 0)))
|
||||||
Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange)
|
Truth.assertThat(roomListService.latestSlidingSyncRange)
|
||||||
.isEqualTo(IntRange(0, 20))
|
.isEqualTo(IntRange(0, 20))
|
||||||
loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(0, 1)))
|
loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(0, 1)))
|
||||||
Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange)
|
Truth.assertThat(roomListService.latestSlidingSyncRange)
|
||||||
.isEqualTo(IntRange(0, 21))
|
.isEqualTo(IntRange(0, 21))
|
||||||
loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(19, 29)))
|
loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(19, 29)))
|
||||||
Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange)
|
Truth.assertThat(roomListService.latestSlidingSyncRange)
|
||||||
.isEqualTo(IntRange(0, 49))
|
.isEqualTo(IntRange(0, 49))
|
||||||
loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(49, 59)))
|
loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(49, 59)))
|
||||||
Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange)
|
Truth.assertThat(roomListService.latestSlidingSyncRange)
|
||||||
.isEqualTo(IntRange(29, 79))
|
.isEqualTo(IntRange(29, 79))
|
||||||
loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(149, 159)))
|
loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(149, 159)))
|
||||||
Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange)
|
Truth.assertThat(roomListService.latestSlidingSyncRange)
|
||||||
.isEqualTo(IntRange(129, 179))
|
.isEqualTo(IntRange(129, 179))
|
||||||
loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(149, 259)))
|
loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(149, 259)))
|
||||||
Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange)
|
Truth.assertThat(roomListService.latestSlidingSyncRange)
|
||||||
.isEqualTo(IntRange(129, 279))
|
.isEqualTo(IntRange(129, 279))
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
|
|
@ -200,9 +200,9 @@ class RoomListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - handle DismissRequestVerificationPrompt`() = runTest {
|
fun `present - handle DismissRequestVerificationPrompt`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource()
|
val roomListService = FakeRoomListService()
|
||||||
val matrixClient = FakeMatrixClient(
|
val matrixClient = FakeMatrixClient(
|
||||||
roomSummaryDataSource = roomSummaryDataSource
|
roomListService = roomListService
|
||||||
)
|
)
|
||||||
val presenter = createRoomListPresenter(
|
val presenter = createRoomListPresenter(
|
||||||
client = matrixClient,
|
client = matrixClient,
|
||||||
|
|
@ -317,7 +317,7 @@ class RoomListPresenterTests {
|
||||||
inviteStateDataSource = inviteStateDataSource,
|
inviteStateDataSource = inviteStateDataSource,
|
||||||
leaveRoomPresenter = leaveRoomPresenter,
|
leaveRoomPresenter = leaveRoomPresenter,
|
||||||
roomListDataSource = RoomListDataSource(
|
roomListDataSource = RoomListDataSource(
|
||||||
client.roomSummaryDataSource,
|
client.roomListService,
|
||||||
lastMessageTimestampFormatter,
|
lastMessageTimestampFormatter,
|
||||||
roomLastMessageFormatter,
|
roomLastMessageFormatter,
|
||||||
coroutineDispatchers = testCoroutineDispatchers()
|
coroutineDispatchers = testCoroutineDispatchers()
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ import io.element.android.features.roomlist.impl.InvitesState
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource
|
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
|
import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
|
||||||
|
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -35,8 +35,8 @@ internal class DefaultInviteStateDataSourceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `emits NoInvites state if invites list is empty`() = runTest {
|
fun `emits NoInvites state if invites list is empty`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource()
|
val roomListService = FakeRoomListService()
|
||||||
val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource)
|
val client = FakeMatrixClient(roomListService = roomListService)
|
||||||
val seenStore = FakeSeenInvitesStore()
|
val seenStore = FakeSeenInvitesStore()
|
||||||
val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers())
|
val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers())
|
||||||
|
|
||||||
|
|
@ -49,9 +49,9 @@ internal class DefaultInviteStateDataSourceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `emits NewInvites state if unseen invite exists`() = runTest {
|
fun `emits NewInvites state if unseen invite exists`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource()
|
val roomListService = FakeRoomListService()
|
||||||
roomSummaryDataSource.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID)))
|
roomListService.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID)))
|
||||||
val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource)
|
val client = FakeMatrixClient(roomListService = roomListService)
|
||||||
val seenStore = FakeSeenInvitesStore()
|
val seenStore = FakeSeenInvitesStore()
|
||||||
val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers())
|
val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers())
|
||||||
|
|
||||||
|
|
@ -65,9 +65,9 @@ internal class DefaultInviteStateDataSourceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `emits NewInvites state if multiple invites exist and at least one is unseen`() = runTest {
|
fun `emits NewInvites state if multiple invites exist and at least one is unseen`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource()
|
val roomListService = FakeRoomListService()
|
||||||
roomSummaryDataSource.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(roomId = A_ROOM_ID_2)))
|
roomListService.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(roomId = A_ROOM_ID_2)))
|
||||||
val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource)
|
val client = FakeMatrixClient(roomListService = roomListService)
|
||||||
val seenStore = FakeSeenInvitesStore()
|
val seenStore = FakeSeenInvitesStore()
|
||||||
seenStore.publishRoomIds(setOf(A_ROOM_ID))
|
seenStore.publishRoomIds(setOf(A_ROOM_ID))
|
||||||
val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers(useUnconfinedTestDispatcher = true))
|
val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers(useUnconfinedTestDispatcher = true))
|
||||||
|
|
@ -82,9 +82,9 @@ internal class DefaultInviteStateDataSourceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `emits SeenInvites state if invite exists in seen store`() = runTest {
|
fun `emits SeenInvites state if invite exists in seen store`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource()
|
val roomListService = FakeRoomListService()
|
||||||
roomSummaryDataSource.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID)))
|
roomListService.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID)))
|
||||||
val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource)
|
val client = FakeMatrixClient(roomListService = roomListService)
|
||||||
val seenStore = FakeSeenInvitesStore()
|
val seenStore = FakeSeenInvitesStore()
|
||||||
seenStore.publishRoomIds(setOf(A_ROOM_ID))
|
seenStore.publishRoomIds(setOf(A_ROOM_ID))
|
||||||
val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers(useUnconfinedTestDispatcher = true))
|
val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers(useUnconfinedTestDispatcher = true))
|
||||||
|
|
@ -100,8 +100,8 @@ internal class DefaultInviteStateDataSourceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `emits new state in response to upstream events`() = runTest {
|
fun `emits new state in response to upstream events`() = runTest {
|
||||||
val roomSummaryDataSource = FakeRoomSummaryDataSource()
|
val roomListService = FakeRoomListService()
|
||||||
val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource)
|
val client = FakeMatrixClient(roomListService = roomListService)
|
||||||
val seenStore = FakeSeenInvitesStore()
|
val seenStore = FakeSeenInvitesStore()
|
||||||
val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers())
|
val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers())
|
||||||
|
|
||||||
|
|
@ -112,7 +112,7 @@ internal class DefaultInviteStateDataSourceTest {
|
||||||
Truth.assertThat(awaitItem()).isEqualTo(InvitesState.NoInvites)
|
Truth.assertThat(awaitItem()).isEqualTo(InvitesState.NoInvites)
|
||||||
|
|
||||||
// When a single invite is received, state should be NewInvites
|
// When a single invite is received, state should be NewInvites
|
||||||
roomSummaryDataSource.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID)))
|
roomListService.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID)))
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
Truth.assertThat(awaitItem()).isEqualTo(InvitesState.NewInvites)
|
Truth.assertThat(awaitItem()).isEqualTo(InvitesState.NewInvites)
|
||||||
|
|
||||||
|
|
@ -122,12 +122,12 @@ internal class DefaultInviteStateDataSourceTest {
|
||||||
Truth.assertThat(awaitItem()).isEqualTo(InvitesState.SeenInvites)
|
Truth.assertThat(awaitItem()).isEqualTo(InvitesState.SeenInvites)
|
||||||
|
|
||||||
// Another new invite resets it to NewInvites
|
// Another new invite resets it to NewInvites
|
||||||
roomSummaryDataSource.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(roomId = A_ROOM_ID_2)))
|
roomListService.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(roomId = A_ROOM_ID_2)))
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
Truth.assertThat(awaitItem()).isEqualTo(InvitesState.NewInvites)
|
Truth.assertThat(awaitItem()).isEqualTo(InvitesState.NewInvites)
|
||||||
|
|
||||||
// All of the invites going away reverts to NoInvites
|
// All of the invites going away reverts to NoInvites
|
||||||
roomSummaryDataSource.postInviteRooms(emptyList())
|
roomListService.postInviteRooms(emptyList())
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
Truth.assertThat(awaitItem()).isEqualTo(InvitesState.NoInvites)
|
Truth.assertThat(awaitItem()).isEqualTo(InvitesState.NoInvites)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import io.element.android.libraries.matrix.api.notification.NotificationService
|
||||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
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.user.MatrixSearchUserResults
|
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
|
@ -35,7 +35,7 @@ import java.io.Closeable
|
||||||
|
|
||||||
interface MatrixClient : Closeable {
|
interface MatrixClient : Closeable {
|
||||||
val sessionId: SessionId
|
val sessionId: SessionId
|
||||||
val roomSummaryDataSource: RoomSummaryDataSource
|
val roomListService: RoomListService
|
||||||
val mediaLoader: MatrixMediaLoader
|
val mediaLoader: MatrixMediaLoader
|
||||||
suspend fun getRoom(roomId: RoomId): MatrixRoom?
|
suspend fun getRoom(roomId: RoomId): MatrixRoom?
|
||||||
suspend fun findDM(userId: UserId): MatrixRoom?
|
suspend fun findDM(userId: UserId): MatrixRoom?
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.api.room
|
package io.element.android.libraries.matrix.api.roomlist
|
||||||
|
|
||||||
import kotlinx.coroutines.TimeoutCancellationException
|
import kotlinx.coroutines.TimeoutCancellationException
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
@ -23,25 +23,34 @@ import kotlinx.coroutines.withTimeout
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
interface RoomSummaryDataSource {
|
/**
|
||||||
|
* Holds some flows related to a specific set of rooms.
|
||||||
|
* Can be retrieved from [RoomListService] methods.
|
||||||
|
*/
|
||||||
|
interface RoomList {
|
||||||
sealed class LoadingState {
|
sealed class LoadingState {
|
||||||
object NotLoaded : LoadingState()
|
object NotLoaded : LoadingState()
|
||||||
data class Loaded(val numberOfRooms: Int) : LoadingState()
|
data class Loaded(val numberOfRooms: Int) : LoadingState()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateAllRoomsVisibleRange(range: IntRange)
|
/**
|
||||||
fun allRoomsLoadingState(): StateFlow<LoadingState>
|
* The list of room summaries as a flow.
|
||||||
fun allRooms(): StateFlow<List<RoomSummary>>
|
*/
|
||||||
fun inviteRooms(): StateFlow<List<RoomSummary>>
|
val summaries: StateFlow<List<RoomSummary>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The loading state of the room list as a flow.
|
||||||
|
* This is useful to know if a specific set of rooms is loaded or not.
|
||||||
|
*/
|
||||||
|
val loadingState: StateFlow<LoadingState>
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun RoomSummaryDataSource.awaitAllRoomsAreLoaded(timeout: Duration = Duration.INFINITE) {
|
suspend fun RoomList.awaitLoaded(timeout: Duration = Duration.INFINITE) {
|
||||||
try {
|
try {
|
||||||
Timber.d("awaitAllRoomsAreLoaded: wait")
|
Timber.d("awaitAllRoomsAreLoaded: wait")
|
||||||
withTimeout(timeout) {
|
withTimeout(timeout) {
|
||||||
allRoomsLoadingState().firstOrNull {
|
loadingState.firstOrNull {
|
||||||
it is RoomSummaryDataSource.LoadingState.Loaded
|
it is RoomList.LoadingState.Loaded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (timeoutException: TimeoutCancellationException) {
|
} catch (timeoutException: TimeoutCancellationException) {
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.api.roomlist
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry point for the room list api.
|
||||||
|
* This service will provide different sets of rooms (all, invites, etc.).
|
||||||
|
* It requires the SyncService to be started to receive updates.
|
||||||
|
*/
|
||||||
|
interface RoomListService {
|
||||||
|
|
||||||
|
sealed class State {
|
||||||
|
object Idle : State()
|
||||||
|
object Running : State()
|
||||||
|
object Error : State()
|
||||||
|
object Terminated : State()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns a [RoomList] object of all rooms we want to display.
|
||||||
|
* This will exclude some rooms like the invites, or spaces.
|
||||||
|
*/
|
||||||
|
fun allRooms(): RoomList
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns a [RoomList] object of all invites.
|
||||||
|
*/
|
||||||
|
fun invites(): RoomList
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will set the visible range of all rooms.
|
||||||
|
* This is useful to load more data when the user scrolls down.
|
||||||
|
*/
|
||||||
|
fun updateAllRoomsVisibleRange(range: IntRange)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The state of the service as a flow.
|
||||||
|
*/
|
||||||
|
val state: StateFlow<State>
|
||||||
|
}
|
||||||
|
|
@ -14,9 +14,10 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.api.room
|
package io.element.android.libraries.matrix.api.roomlist
|
||||||
|
|
||||||
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.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.message.RoomMessage
|
import io.element.android.libraries.matrix.api.room.message.RoomMessage
|
||||||
|
|
||||||
sealed interface RoomSummary {
|
sealed interface RoomSummary {
|
||||||
|
|
@ -32,8 +32,8 @@ import io.element.android.libraries.matrix.api.notification.NotificationService
|
||||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.matrix.api.room.awaitAllRoomsAreLoaded
|
import io.element.android.libraries.matrix.api.roomlist.awaitLoaded
|
||||||
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.sync.SyncState
|
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
|
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
|
||||||
|
|
@ -45,9 +45,8 @@ import io.element.android.libraries.matrix.impl.notification.RustNotificationSer
|
||||||
import io.element.android.libraries.matrix.impl.pushers.RustPushersService
|
import io.element.android.libraries.matrix.impl.pushers.RustPushersService
|
||||||
import io.element.android.libraries.matrix.impl.room.RoomContentForwarder
|
import io.element.android.libraries.matrix.impl.room.RoomContentForwarder
|
||||||
import io.element.android.libraries.matrix.impl.room.RustMatrixRoom
|
import io.element.android.libraries.matrix.impl.room.RustMatrixRoom
|
||||||
import io.element.android.libraries.matrix.impl.room.RustRoomSummaryDataSource
|
import io.element.android.libraries.matrix.impl.roomlist.RustRoomListService
|
||||||
import io.element.android.libraries.matrix.impl.room.roomOrNull
|
import io.element.android.libraries.matrix.impl.roomlist.roomOrNull
|
||||||
import io.element.android.libraries.matrix.impl.room.stateFlow
|
|
||||||
import io.element.android.libraries.matrix.impl.sync.RustSyncService
|
import io.element.android.libraries.matrix.impl.sync.RustSyncService
|
||||||
import io.element.android.libraries.matrix.impl.usersearch.UserProfileMapper
|
import io.element.android.libraries.matrix.impl.usersearch.UserProfileMapper
|
||||||
import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper
|
import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper
|
||||||
|
|
@ -90,11 +89,11 @@ class RustMatrixClient constructor(
|
||||||
) : MatrixClient {
|
) : MatrixClient {
|
||||||
|
|
||||||
override val sessionId: UserId = UserId(client.userId())
|
override val sessionId: UserId = UserId(client.userId())
|
||||||
private val roomListService = syncService.roomListService()
|
private val innerRoomListService = syncService.roomListService()
|
||||||
private val sessionDispatcher = dispatchers.io.limitedParallelism(64)
|
private val sessionDispatcher = dispatchers.io.limitedParallelism(64)
|
||||||
private val sessionCoroutineScope = appCoroutineScope.childScope(dispatchers.main, "Session-${sessionId}")
|
private val sessionCoroutineScope = appCoroutineScope.childScope(dispatchers.main, "Session-${sessionId}")
|
||||||
private val verificationService = RustSessionVerificationService()
|
private val verificationService = RustSessionVerificationService()
|
||||||
private val rustSyncService = RustSyncService(syncService, roomListService.stateFlow(), sessionCoroutineScope)
|
private val rustSyncService = RustSyncService(syncService, sessionCoroutineScope)
|
||||||
private val pushersService = RustPushersService(
|
private val pushersService = RustPushersService(
|
||||||
client = client,
|
client = client,
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
|
|
@ -122,15 +121,15 @@ class RustMatrixClient constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val rustRoomSummaryDataSource: RustRoomSummaryDataSource =
|
private val rustRoomListService: RoomListService =
|
||||||
RustRoomSummaryDataSource(
|
RustRoomListService(
|
||||||
roomListService = roomListService,
|
innerRoomListService = innerRoomListService,
|
||||||
sessionCoroutineScope = sessionCoroutineScope,
|
sessionCoroutineScope = sessionCoroutineScope,
|
||||||
dispatcher = sessionDispatcher,
|
dispatcher = sessionDispatcher,
|
||||||
)
|
)
|
||||||
|
|
||||||
override val roomSummaryDataSource: RoomSummaryDataSource
|
override val roomListService: RoomListService
|
||||||
get() = rustRoomSummaryDataSource
|
get() = rustRoomListService
|
||||||
|
|
||||||
private val rustMediaLoader = RustMediaLoader(baseCacheDirectory, dispatchers, client)
|
private val rustMediaLoader = RustMediaLoader(baseCacheDirectory, dispatchers, client)
|
||||||
override val mediaLoader: MatrixMediaLoader
|
override val mediaLoader: MatrixMediaLoader
|
||||||
|
|
@ -138,7 +137,7 @@ class RustMatrixClient constructor(
|
||||||
|
|
||||||
private val roomMembershipObserver = RoomMembershipObserver()
|
private val roomMembershipObserver = RoomMembershipObserver()
|
||||||
|
|
||||||
private val roomContentForwarder = RoomContentForwarder(roomListService)
|
private val roomContentForwarder = RoomContentForwarder(innerRoomListService)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
client.setDelegate(clientDelegate)
|
client.setDelegate(clientDelegate)
|
||||||
|
|
@ -156,7 +155,7 @@ class RustMatrixClient constructor(
|
||||||
var cachedPairOfRoom = pairOfRoom(roomId)
|
var cachedPairOfRoom = pairOfRoom(roomId)
|
||||||
if (cachedPairOfRoom == null) {
|
if (cachedPairOfRoom == null) {
|
||||||
//... otherwise, lets wait for the SS to load all rooms and check again.
|
//... otherwise, lets wait for the SS to load all rooms and check again.
|
||||||
roomSummaryDataSource.awaitAllRoomsAreLoaded()
|
roomListService.allRooms().awaitLoaded()
|
||||||
cachedPairOfRoom = pairOfRoom(roomId)
|
cachedPairOfRoom = pairOfRoom(roomId)
|
||||||
}
|
}
|
||||||
cachedPairOfRoom?.let { (roomListItem, fullRoom) ->
|
cachedPairOfRoom?.let { (roomListItem, fullRoom) ->
|
||||||
|
|
@ -174,7 +173,7 @@ class RustMatrixClient constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun pairOfRoom(roomId: RoomId): Pair<RoomListItem, Room>? {
|
private fun pairOfRoom(roomId: RoomId): Pair<RoomListItem, Room>? {
|
||||||
val cachedRoomListItem = roomListService.roomOrNull(roomId.value)
|
val cachedRoomListItem = innerRoomListService.roomOrNull(roomId.value)
|
||||||
val fullRoom = cachedRoomListItem?.fullRoom()
|
val fullRoom = cachedRoomListItem?.fullRoom()
|
||||||
return if (cachedRoomListItem == null || fullRoom == null) {
|
return if (cachedRoomListItem == null || fullRoom == null) {
|
||||||
Timber.d("No room cached for $roomId")
|
Timber.d("No room cached for $roomId")
|
||||||
|
|
@ -225,7 +224,7 @@ class RustMatrixClient constructor(
|
||||||
|
|
||||||
// Wait to receive the room back from the sync
|
// Wait to receive the room back from the sync
|
||||||
withTimeout(30_000L) {
|
withTimeout(30_000L) {
|
||||||
roomSummaryDataSource.allRooms()
|
roomListService.allRooms().summaries
|
||||||
.filter { roomSummaries ->
|
.filter { roomSummaries ->
|
||||||
roomSummaries.map { it.identifier() }.contains(roomId.value)
|
roomSummaries.map { it.identifier() }.contains(roomId.value)
|
||||||
}
|
}
|
||||||
|
|
@ -273,7 +272,7 @@ class RustMatrixClient constructor(
|
||||||
client.setDelegate(null)
|
client.setDelegate(null)
|
||||||
verificationService.destroy()
|
verificationService.destroy()
|
||||||
syncService.destroy()
|
syncService.destroy()
|
||||||
roomListService.destroy()
|
innerRoomListService.destroy()
|
||||||
notificationClient.destroy()
|
notificationClient.destroy()
|
||||||
client.destroy()
|
client.destroy()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
|
|
@ -40,8 +40,8 @@ object SessionMatrixModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideRoomSummaryDataSource(matrixClient: MatrixClient): RoomSummaryDataSource {
|
fun providesRoomListService(matrixClient: MatrixClient): RoomListService {
|
||||||
return matrixClient.roomSummaryDataSource
|
return matrixClient.roomListService
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import io.element.android.libraries.core.coroutine.parallelMap
|
||||||
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.room.ForwardEventException
|
import io.element.android.libraries.matrix.api.room.ForwardEventException
|
||||||
|
import io.element.android.libraries.matrix.impl.roomlist.roomOrNull
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import org.matrix.rustcomponents.sdk.Room
|
import org.matrix.rustcomponents.sdk.Room
|
||||||
|
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2023 New Vector Ltd
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.impl.room
|
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummary
|
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
|
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.matrix.rustcomponents.sdk.RoomList
|
|
||||||
import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate
|
|
||||||
import org.matrix.rustcomponents.sdk.RoomListException
|
|
||||||
import org.matrix.rustcomponents.sdk.RoomListInput
|
|
||||||
import org.matrix.rustcomponents.sdk.RoomListLoadingState
|
|
||||||
import org.matrix.rustcomponents.sdk.RoomListRange
|
|
||||||
import org.matrix.rustcomponents.sdk.RoomListService
|
|
||||||
import org.matrix.rustcomponents.sdk.RoomListServiceState
|
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
internal class RustRoomSummaryDataSource(
|
|
||||||
private val roomListService: RoomListService,
|
|
||||||
private val sessionCoroutineScope: CoroutineScope,
|
|
||||||
dispatcher: CoroutineDispatcher,
|
|
||||||
roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
|
|
||||||
) : RoomSummaryDataSource {
|
|
||||||
|
|
||||||
private val allRooms = MutableStateFlow<List<RoomSummary>>(emptyList())
|
|
||||||
private val inviteRooms = MutableStateFlow<List<RoomSummary>>(emptyList())
|
|
||||||
|
|
||||||
private val allRoomsLoadingState: MutableStateFlow<RoomSummaryDataSource.LoadingState> = MutableStateFlow(RoomSummaryDataSource.LoadingState.NotLoaded)
|
|
||||||
private val allRoomsListProcessor = RoomSummaryListProcessor(allRooms, roomListService, roomSummaryDetailsFactory, shouldFetchFullRoom = false)
|
|
||||||
private val inviteRoomsListProcessor = RoomSummaryListProcessor(inviteRooms, roomListService, roomSummaryDetailsFactory, shouldFetchFullRoom = true)
|
|
||||||
|
|
||||||
init {
|
|
||||||
sessionCoroutineScope.launch(dispatcher) {
|
|
||||||
val allRooms = roomListService.allRooms()
|
|
||||||
allRooms
|
|
||||||
.observeEntriesWithProcessor(allRoomsListProcessor)
|
|
||||||
.launchIn(this)
|
|
||||||
|
|
||||||
allRooms
|
|
||||||
.loadingStateFlow()
|
|
||||||
.map { it.toRoomSummaryDataSourceLoadingState() }
|
|
||||||
.onEach {
|
|
||||||
allRoomsLoadingState.value = it
|
|
||||||
}
|
|
||||||
.launchIn(this)
|
|
||||||
|
|
||||||
launch {
|
|
||||||
// Wait until running, as invites is only available after that
|
|
||||||
roomListService.stateFlow().first {
|
|
||||||
it == RoomListServiceState.RUNNING
|
|
||||||
}
|
|
||||||
roomListService.invites()
|
|
||||||
.observeEntriesWithProcessor(inviteRoomsListProcessor)
|
|
||||||
.launchIn(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun allRooms(): StateFlow<List<RoomSummary>> {
|
|
||||||
return allRooms
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun inviteRooms(): StateFlow<List<RoomSummary>> {
|
|
||||||
return inviteRooms
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun allRoomsLoadingState(): StateFlow<RoomSummaryDataSource.LoadingState> {
|
|
||||||
return allRoomsLoadingState
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateAllRoomsVisibleRange(range: IntRange) {
|
|
||||||
Timber.v("setVisibleRange=$range")
|
|
||||||
sessionCoroutineScope.launch {
|
|
||||||
try {
|
|
||||||
val ranges = listOf(RoomListRange(range.first.toUInt(), range.last.toUInt()))
|
|
||||||
roomListService.applyInput(
|
|
||||||
RoomListInput.Viewport(ranges)
|
|
||||||
)
|
|
||||||
} catch (exception: RoomListException) {
|
|
||||||
Timber.e(exception, "Failed updating visible range")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun RoomListLoadingState.toRoomSummaryDataSourceLoadingState(): RoomSummaryDataSource.LoadingState {
|
|
||||||
return when (this) {
|
|
||||||
is RoomListLoadingState.Loaded -> RoomSummaryDataSource.LoadingState.Loaded(maximumNumberOfRooms?.toInt() ?: 0)
|
|
||||||
is RoomListLoadingState.NotLoaded -> RoomSummaryDataSource.LoadingState.NotLoaded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun RoomList.observeEntriesWithProcessor(processor: RoomSummaryListProcessor): Flow<List<RoomListEntriesUpdate>> {
|
|
||||||
return entriesFlow { roomListEntries ->
|
|
||||||
processor.postEntries(roomListEntries)
|
|
||||||
}.onEach { update ->
|
|
||||||
processor.postUpdate(update)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.impl.room
|
package io.element.android.libraries.matrix.impl.roomlist
|
||||||
|
|
||||||
import io.element.android.libraries.core.data.tryOrNull
|
import io.element.android.libraries.core.data.tryOrNull
|
||||||
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
|
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
|
||||||
|
|
@ -14,10 +14,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.impl.room
|
package io.element.android.libraries.matrix.impl.roomlist
|
||||||
|
|
||||||
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.RoomSummaryDetails
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
|
||||||
|
import io.element.android.libraries.matrix.impl.room.RoomMemberMapper
|
||||||
import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory
|
import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory
|
||||||
import org.matrix.rustcomponents.sdk.Room
|
import org.matrix.rustcomponents.sdk.Room
|
||||||
import org.matrix.rustcomponents.sdk.RoomListItem
|
import org.matrix.rustcomponents.sdk.RoomListItem
|
||||||
|
|
@ -14,10 +14,10 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.impl.room
|
package io.element.android.libraries.matrix.impl.roomlist
|
||||||
|
|
||||||
import io.element.android.libraries.core.coroutine.parallelMap
|
import io.element.android.libraries.core.coroutine.parallelMap
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.impl.roomlist
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple implementation of [RoomList] where state flows are provided through constructor.
|
||||||
|
*/
|
||||||
|
class RustRoomList(
|
||||||
|
override val summaries: StateFlow<List<RoomSummary>>,
|
||||||
|
override val loadingState: StateFlow<RoomList.LoadingState>
|
||||||
|
) : RoomList
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.impl.roomlist
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
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.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate
|
||||||
|
import org.matrix.rustcomponents.sdk.RoomListException
|
||||||
|
import org.matrix.rustcomponents.sdk.RoomListInput
|
||||||
|
import org.matrix.rustcomponents.sdk.RoomListLoadingState
|
||||||
|
import org.matrix.rustcomponents.sdk.RoomListRange
|
||||||
|
import org.matrix.rustcomponents.sdk.RoomListServiceState
|
||||||
|
import timber.log.Timber
|
||||||
|
import org.matrix.rustcomponents.sdk.RoomListService as InnerRustRoomListService
|
||||||
|
|
||||||
|
class RustRoomListService(
|
||||||
|
private val innerRoomListService: InnerRustRoomListService,
|
||||||
|
private val sessionCoroutineScope: CoroutineScope,
|
||||||
|
dispatcher: CoroutineDispatcher,
|
||||||
|
roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
|
||||||
|
) : RoomListService {
|
||||||
|
|
||||||
|
private val allRooms = MutableStateFlow<List<RoomSummary>>(emptyList())
|
||||||
|
private val inviteRooms = MutableStateFlow<List<RoomSummary>>(emptyList())
|
||||||
|
|
||||||
|
private val allRoomsLoadingState: MutableStateFlow<RoomList.LoadingState> = MutableStateFlow(RoomList.LoadingState.NotLoaded)
|
||||||
|
private val allRoomsListProcessor = RoomSummaryListProcessor(allRooms, innerRoomListService, roomSummaryDetailsFactory, shouldFetchFullRoom = false)
|
||||||
|
private val invitesLoadingState: MutableStateFlow<RoomList.LoadingState> = MutableStateFlow(RoomList.LoadingState.NotLoaded)
|
||||||
|
private val inviteRoomsListProcessor = RoomSummaryListProcessor(inviteRooms, innerRoomListService, roomSummaryDetailsFactory, shouldFetchFullRoom = true)
|
||||||
|
|
||||||
|
init {
|
||||||
|
sessionCoroutineScope.launch(dispatcher) {
|
||||||
|
val allRooms = innerRoomListService.allRooms()
|
||||||
|
allRooms
|
||||||
|
.observeEntriesWithProcessor(allRoomsListProcessor)
|
||||||
|
.launchIn(this)
|
||||||
|
allRooms
|
||||||
|
.observeLoadingState(allRoomsLoadingState)
|
||||||
|
.launchIn(this)
|
||||||
|
|
||||||
|
|
||||||
|
launch {
|
||||||
|
// Wait until running, as invites is only available after that
|
||||||
|
innerRoomListService.stateFlow().first {
|
||||||
|
it == RoomListServiceState.RUNNING
|
||||||
|
}
|
||||||
|
val invites = innerRoomListService.invites()
|
||||||
|
invites
|
||||||
|
.observeEntriesWithProcessor(inviteRoomsListProcessor)
|
||||||
|
.launchIn(this)
|
||||||
|
invites
|
||||||
|
.observeLoadingState(invitesLoadingState)
|
||||||
|
.launchIn(this)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun allRooms(): RoomList {
|
||||||
|
return RustRoomList(allRooms, allRoomsLoadingState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invites(): RoomList {
|
||||||
|
return RustRoomList(inviteRooms, invitesLoadingState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateAllRoomsVisibleRange(range: IntRange) {
|
||||||
|
Timber.v("setVisibleRange=$range")
|
||||||
|
sessionCoroutineScope.launch {
|
||||||
|
try {
|
||||||
|
val ranges = listOf(RoomListRange(range.first.toUInt(), range.last.toUInt()))
|
||||||
|
innerRoomListService.applyInput(
|
||||||
|
RoomListInput.Viewport(ranges)
|
||||||
|
)
|
||||||
|
} catch (exception: RoomListException) {
|
||||||
|
Timber.e(exception, "Failed updating visible range")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val state: StateFlow<RoomListService.State> =
|
||||||
|
innerRoomListService.stateFlow()
|
||||||
|
.map { it.toRoomListState() }
|
||||||
|
.onEach { state ->
|
||||||
|
Timber.d("RoomList state=$state")
|
||||||
|
}
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.stateIn(sessionCoroutineScope, SharingStarted.Eagerly, RoomListService.State.Idle)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun RoomListLoadingState.toLoadingState(): RoomList.LoadingState {
|
||||||
|
return when (this) {
|
||||||
|
is RoomListLoadingState.Loaded -> RoomList.LoadingState.Loaded(maximumNumberOfRooms?.toInt() ?: 0)
|
||||||
|
RoomListLoadingState.NotLoaded -> RoomList.LoadingState.NotLoaded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun RoomListServiceState.toRoomListState(): RoomListService.State {
|
||||||
|
return when (this) {
|
||||||
|
RoomListServiceState.INIT,
|
||||||
|
RoomListServiceState.SETTING_UP -> RoomListService.State.Idle
|
||||||
|
RoomListServiceState.RUNNING -> RoomListService.State.Running
|
||||||
|
RoomListServiceState.ERROR -> RoomListService.State.Error
|
||||||
|
RoomListServiceState.TERMINATED -> RoomListService.State.Terminated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun org.matrix.rustcomponents.sdk.RoomList.observeEntriesWithProcessor(processor: RoomSummaryListProcessor): Flow<List<RoomListEntriesUpdate>> {
|
||||||
|
return entriesFlow { roomListEntries ->
|
||||||
|
processor.postEntries(roomListEntries)
|
||||||
|
}.onEach { update ->
|
||||||
|
processor.postUpdate(update)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun org.matrix.rustcomponents.sdk.RoomList.observeLoadingState(stateFlow: MutableStateFlow<RoomList.LoadingState>): Flow<RoomList.LoadingState> {
|
||||||
|
return loadingStateFlow()
|
||||||
|
.map { it.toLoadingState() }
|
||||||
|
.onEach {
|
||||||
|
stateFlow.value = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -17,19 +17,8 @@
|
||||||
package io.element.android.libraries.matrix.impl.sync
|
package io.element.android.libraries.matrix.impl.sync
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||||
import org.matrix.rustcomponents.sdk.RoomListServiceState
|
|
||||||
import org.matrix.rustcomponents.sdk.SyncServiceState
|
import org.matrix.rustcomponents.sdk.SyncServiceState
|
||||||
|
|
||||||
internal fun RoomListServiceState.toSyncState(): SyncState {
|
|
||||||
return when (this) {
|
|
||||||
RoomListServiceState.INIT,
|
|
||||||
RoomListServiceState.SETTING_UP -> SyncState.Idle
|
|
||||||
RoomListServiceState.RUNNING -> SyncState.Running
|
|
||||||
RoomListServiceState.ERROR -> SyncState.Error
|
|
||||||
RoomListServiceState.TERMINATED -> SyncState.Terminated
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun SyncServiceState.toSyncState(): SyncState {
|
internal fun SyncServiceState.toSyncState(): SyncState {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
SyncServiceState.IDLE -> SyncState.Idle
|
SyncServiceState.IDLE -> SyncState.Idle
|
||||||
|
|
|
||||||
|
|
@ -19,36 +19,38 @@ package io.element.android.libraries.matrix.impl.sync
|
||||||
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.sync.SyncState
|
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import org.matrix.rustcomponents.sdk.RoomListServiceState
|
|
||||||
import org.matrix.rustcomponents.sdk.SyncServiceInterface
|
import org.matrix.rustcomponents.sdk.SyncServiceInterface
|
||||||
|
import org.matrix.rustcomponents.sdk.SyncServiceState
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class RustSyncService(
|
class RustSyncService(
|
||||||
private val innerSyncService: SyncServiceInterface,
|
private val innerSyncService: SyncServiceInterface,
|
||||||
roomListStateFlow: Flow<RoomListServiceState>,
|
|
||||||
sessionCoroutineScope: CoroutineScope
|
sessionCoroutineScope: CoroutineScope
|
||||||
) : SyncService {
|
) : SyncService {
|
||||||
|
|
||||||
override suspend fun startSync() = runCatching {
|
override suspend fun startSync() = runCatching {
|
||||||
Timber.i("Start sync")
|
Timber.i("Start sync")
|
||||||
innerSyncService.start()
|
innerSyncService.start()
|
||||||
|
}.onFailure {
|
||||||
|
Timber.d("Start sync failed: $it")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stopSync() = runCatching {
|
override fun stopSync() = runCatching {
|
||||||
Timber.i("Stop sync")
|
Timber.i("Stop sync")
|
||||||
innerSyncService.pause()
|
innerSyncService.pause()
|
||||||
|
}.onFailure {
|
||||||
|
Timber.d("Stop sync failed: $it")
|
||||||
}
|
}
|
||||||
|
|
||||||
override val syncState: StateFlow<SyncState> =
|
override val syncState: StateFlow<SyncState> =
|
||||||
roomListStateFlow
|
innerSyncService.stateFlow()
|
||||||
.map(RoomListServiceState::toSyncState)
|
.map(SyncServiceState::toSyncState)
|
||||||
.onEach { state ->
|
.onEach { state ->
|
||||||
Timber.i("Sync state=$state")
|
Timber.i("Sync state=$state")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,11 @@ import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.channels.trySendBlocking
|
import kotlinx.coroutines.channels.trySendBlocking
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.buffer
|
import kotlinx.coroutines.flow.buffer
|
||||||
import org.matrix.rustcomponents.sdk.SyncService
|
import org.matrix.rustcomponents.sdk.SyncServiceInterface
|
||||||
import org.matrix.rustcomponents.sdk.SyncServiceState
|
import org.matrix.rustcomponents.sdk.SyncServiceState
|
||||||
import org.matrix.rustcomponents.sdk.SyncServiceStateObserver
|
import org.matrix.rustcomponents.sdk.SyncServiceStateObserver
|
||||||
|
|
||||||
fun SyncService.stateFlow(): Flow<SyncServiceState> =
|
fun SyncServiceInterface.stateFlow(): Flow<SyncServiceState> =
|
||||||
mxCallbackFlow {
|
mxCallbackFlow {
|
||||||
val listener = object : SyncServiceStateObserver {
|
val listener = object : SyncServiceStateObserver {
|
||||||
override fun onUpdate(state: SyncServiceState) {
|
override fun onUpdate(state: SyncServiceState) {
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import io.element.android.libraries.matrix.api.notification.NotificationService
|
||||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
|
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||||
|
|
@ -35,7 +35,7 @@ import io.element.android.libraries.matrix.test.media.FakeMediaLoader
|
||||||
import io.element.android.libraries.matrix.test.notification.FakeNotificationService
|
import io.element.android.libraries.matrix.test.notification.FakeNotificationService
|
||||||
import io.element.android.libraries.matrix.test.pushers.FakePushersService
|
import io.element.android.libraries.matrix.test.pushers.FakePushersService
|
||||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||||
import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource
|
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||||
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
||||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||||
import io.element.android.tests.testutils.simulateLongTask
|
import io.element.android.tests.testutils.simulateLongTask
|
||||||
|
|
@ -45,7 +45,7 @@ class FakeMatrixClient(
|
||||||
override val sessionId: SessionId = A_SESSION_ID,
|
override val sessionId: SessionId = A_SESSION_ID,
|
||||||
private val userDisplayName: Result<String> = Result.success(A_USER_NAME),
|
private val userDisplayName: Result<String> = Result.success(A_USER_NAME),
|
||||||
private val userAvatarURLString: Result<String> = Result.success(AN_AVATAR_URL),
|
private val userAvatarURLString: Result<String> = Result.success(AN_AVATAR_URL),
|
||||||
override val roomSummaryDataSource: RoomSummaryDataSource = FakeRoomSummaryDataSource(),
|
override val roomListService: RoomListService = FakeRoomListService(),
|
||||||
override val mediaLoader: MatrixMediaLoader = FakeMediaLoader(),
|
override val mediaLoader: MatrixMediaLoader = FakeMediaLoader(),
|
||||||
private val sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(),
|
private val sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(),
|
||||||
private val pushersService: FakePushersService = FakePushersService(),
|
private val pushersService: FakePushersService = FakePushersService(),
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ 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.TransactionId
|
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||||
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.room.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummaryDetails
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
|
||||||
import io.element.android.libraries.matrix.api.room.message.RoomMessage
|
import io.element.android.libraries.matrix.api.room.message.RoomMessage
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
||||||
|
|
|
||||||
|
|
@ -14,18 +14,21 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.test.room
|
package io.element.android.libraries.matrix.test.roomlist
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
class FakeRoomSummaryDataSource : RoomSummaryDataSource {
|
class FakeRoomListService : RoomListService {
|
||||||
|
|
||||||
private val allRoomSummariesFlow = MutableStateFlow<List<RoomSummary>>(emptyList())
|
private val allRoomSummariesFlow = MutableStateFlow<List<RoomSummary>>(emptyList())
|
||||||
private val inviteRoomSummariesFlow = MutableStateFlow<List<RoomSummary>>(emptyList())
|
private val inviteRoomSummariesFlow = MutableStateFlow<List<RoomSummary>>(emptyList())
|
||||||
private val allRoomsLoadingStateFlow = MutableStateFlow<RoomSummaryDataSource.LoadingState>(RoomSummaryDataSource.LoadingState.NotLoaded)
|
private val allRoomsLoadingStateFlow = MutableStateFlow<RoomList.LoadingState>(RoomList.LoadingState.NotLoaded)
|
||||||
|
private val inviteRoomsLoadingStateFlow = MutableStateFlow<RoomList.LoadingState>(RoomList.LoadingState.NotLoaded)
|
||||||
|
private val roomListStateFlow = MutableStateFlow<RoomListService.State>(RoomListService.State.Idle)
|
||||||
|
|
||||||
suspend fun postAllRooms(roomSummaries: List<RoomSummary>) {
|
suspend fun postAllRooms(roomSummaries: List<RoomSummary>) {
|
||||||
allRoomSummariesFlow.emit(roomSummaries)
|
allRoomSummariesFlow.emit(roomSummaries)
|
||||||
|
|
@ -35,20 +38,16 @@ class FakeRoomSummaryDataSource : RoomSummaryDataSource {
|
||||||
inviteRoomSummariesFlow.emit(roomSummaries)
|
inviteRoomSummariesFlow.emit(roomSummaries)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun postLoadingState(loadingState: RoomSummaryDataSource.LoadingState) {
|
suspend fun postAllRoomsLoadingState(loadingState: RoomList.LoadingState) {
|
||||||
allRoomsLoadingStateFlow.emit(loadingState)
|
allRoomsLoadingStateFlow.emit(loadingState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun allRoomsLoadingState(): StateFlow<RoomSummaryDataSource.LoadingState> {
|
suspend fun postInviteRoomsLoadingState(loadingState: RoomList.LoadingState) {
|
||||||
return allRoomsLoadingStateFlow
|
inviteRoomsLoadingStateFlow.emit(loadingState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun allRooms(): StateFlow<List<RoomSummary>> {
|
suspend fun postState(state: RoomListService.State) {
|
||||||
return allRoomSummariesFlow
|
roomListStateFlow.emit(state)
|
||||||
}
|
|
||||||
|
|
||||||
override fun inviteRooms(): StateFlow<List<RoomSummary>> {
|
|
||||||
return inviteRoomSummariesFlow
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var latestSlidingSyncRange: IntRange? = null
|
var latestSlidingSyncRange: IntRange? = null
|
||||||
|
|
@ -57,4 +56,20 @@ class FakeRoomSummaryDataSource : RoomSummaryDataSource {
|
||||||
override fun updateAllRoomsVisibleRange(range: IntRange) {
|
override fun updateAllRoomsVisibleRange(range: IntRange) {
|
||||||
latestSlidingSyncRange = range
|
latestSlidingSyncRange = range
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun allRooms(): RoomList {
|
||||||
|
return SimpleRoomList(
|
||||||
|
summaries = allRoomSummariesFlow,
|
||||||
|
loadingState = allRoomsLoadingStateFlow
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invites(): RoomList {
|
||||||
|
return SimpleRoomList(
|
||||||
|
summaries = inviteRoomSummariesFlow,
|
||||||
|
loadingState = inviteRoomsLoadingStateFlow
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val state: StateFlow<RoomListService.State> = roomListStateFlow
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.test.roomlist
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
data class SimpleRoomList(
|
||||||
|
override val summaries: StateFlow<List<RoomSummary>>,
|
||||||
|
override val loadingState: StateFlow<RoomList.LoadingState>
|
||||||
|
) : RoomList
|
||||||
|
|
@ -46,7 +46,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.Surface
|
import io.element.android.libraries.designsystem.theme.components.Surface
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
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.RoomSummaryDetails
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ class RoomListScreen(
|
||||||
inviteStateDataSource = DefaultInviteStateDataSource(matrixClient, DefaultSeenInvitesStore(context), coroutineDispatchers),
|
inviteStateDataSource = DefaultInviteStateDataSource(matrixClient, DefaultSeenInvitesStore(context), coroutineDispatchers),
|
||||||
leaveRoomPresenter = LeaveRoomPresenterImpl(matrixClient, RoomMembershipObserver(), coroutineDispatchers),
|
leaveRoomPresenter = LeaveRoomPresenterImpl(matrixClient, RoomMembershipObserver(), coroutineDispatchers),
|
||||||
roomListDataSource = RoomListDataSource(
|
roomListDataSource = RoomListDataSource(
|
||||||
roomSummaryDataSource = matrixClient.roomSummaryDataSource,
|
roomListService = matrixClient.roomListService,
|
||||||
lastMessageTimestampFormatter = DefaultLastMessageTimestampFormatter(dateTimeProvider, dateFormatters),
|
lastMessageTimestampFormatter = DefaultLastMessageTimestampFormatter(dateTimeProvider, dateFormatters),
|
||||||
roomLastMessageFormatter = DefaultRoomLastMessageFormatter(
|
roomLastMessageFormatter = DefaultRoomLastMessageFormatter(
|
||||||
sp = stringProvider,
|
sp = stringProvider,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue