RoomList: refactor and fix tests
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.features.roomlist.impl
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
open class RoomListContentStateProvider : PreviewParameterProvider<RoomListContentState> {
|
||||
override val values: Sequence<RoomListContentState>
|
||||
get() = sequenceOf(
|
||||
aRoomsContentState(),
|
||||
aSkeletonContentState(),
|
||||
anEmptyContentState(),
|
||||
aMigrationContentState(),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun aRoomsContentState(
|
||||
invitesState: InvitesState = InvitesState.NoInvites,
|
||||
securityBannerState: SecurityBannerState = SecurityBannerState.None,
|
||||
summaries: ImmutableList<RoomListRoomSummary> = aRoomListRoomSummaryList(),
|
||||
) = RoomListContentState.Rooms(
|
||||
invitesState = invitesState,
|
||||
securityBannerState = securityBannerState,
|
||||
summaries = summaries,
|
||||
)
|
||||
|
||||
internal fun aMigrationContentState() = RoomListContentState.Migration
|
||||
|
||||
internal fun aSkeletonContentState() = RoomListContentState.Skeleton(16)
|
||||
|
||||
internal fun anEmptyContentState(
|
||||
invitesState: InvitesState = InvitesState.NoInvites,
|
||||
) = RoomListContentState.Empty(invitesState)
|
||||
@@ -40,7 +40,7 @@ import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource
|
||||
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenState
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchEvents
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
@@ -89,7 +89,7 @@ class RoomListPresenter @Inject constructor(
|
||||
private val indicatorService: IndicatorService,
|
||||
private val filtersPresenter: Presenter<RoomListFiltersState>,
|
||||
private val searchPresenter: Presenter<RoomListSearchState>,
|
||||
private val migrationScreenPresenter: MigrationScreenPresenter,
|
||||
private val migrationScreenPresenter: Presenter<MigrationScreenState>,
|
||||
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||
private val analyticsService: AnalyticsService,
|
||||
) : Presenter<RoomListState> {
|
||||
@@ -196,23 +196,23 @@ class RoomListPresenter @Inject constructor(
|
||||
}
|
||||
val loadingState by roomListDataSource.loadingState.collectAsState()
|
||||
val showMigration = migrationScreenPresenter.present().isMigrating
|
||||
val showSkeleton by remember {
|
||||
derivedStateOf {
|
||||
loadingState == RoomList.LoadingState.NotLoaded || roomSummaries is AsyncData.Loading
|
||||
}
|
||||
}
|
||||
val showEmpty by remember {
|
||||
derivedStateOf {
|
||||
(loadingState as? RoomList.LoadingState.Loaded)?.numberOfRooms == 0
|
||||
}
|
||||
}
|
||||
val showSkeleton by remember {
|
||||
derivedStateOf {
|
||||
loadingState == RoomList.LoadingState.NotLoaded || roomSummaries is AsyncData.Loading
|
||||
}
|
||||
}
|
||||
return when {
|
||||
showMigration -> RoomListContentState.Migration
|
||||
showSkeleton -> RoomListContentState.Skeleton(count = 16)
|
||||
showEmpty -> {
|
||||
val invitesState = inviteStateDataSource.inviteState()
|
||||
RoomListContentState.Empty(invitesState)
|
||||
}
|
||||
showSkeleton -> RoomListContentState.Skeleton(count = 16)
|
||||
else -> {
|
||||
val invitesState = inviteStateDataSource.inviteState()
|
||||
val securityBannerState by securityBannerState(securityBannerDismissed)
|
||||
|
||||
@@ -67,6 +67,7 @@ enum class SecurityBannerState {
|
||||
RecoveryKeyConfirmation,
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed interface RoomListContentState {
|
||||
data object Migration : RoomListContentState
|
||||
data class Skeleton(val count: Int) : RoomListContentState
|
||||
|
||||
@@ -25,7 +25,6 @@ import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.features.roomlist.impl.model.aRoomListRoomSummary
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchState
|
||||
import io.element.android.features.roomlist.impl.search.aRoomListSearchState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
@@ -35,7 +34,6 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
|
||||
override val values: Sequence<RoomListState>
|
||||
@@ -43,15 +41,15 @@ open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
|
||||
aRoomListState(),
|
||||
aRoomListState(snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete)),
|
||||
aRoomListState(hasNetworkConnection = false),
|
||||
aRoomListState(invitesState = InvitesState.SeenInvites),
|
||||
aRoomListState(invitesState = InvitesState.NewInvites),
|
||||
aRoomListState(contentState = aRoomsContentState(invitesState = InvitesState.SeenInvites)),
|
||||
aRoomListState(contentState = aRoomsContentState(invitesState = InvitesState.NewInvites)),
|
||||
aRoomListState(contextMenu = aContextMenuShown(roomName = "A nice room name")),
|
||||
aRoomListState(contextMenu = aContextMenuShown(isFavorite = true)),
|
||||
aRoomListState(securityBannerState = SecurityBannerState.SessionVerification),
|
||||
aRoomListState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation),
|
||||
aRoomListState(roomList = AsyncData.Success(persistentListOf())),
|
||||
//aRoomListState(roomList = AsyncData.Loading(prevData = RoomListRoomSummaryFactory.createFakeList())),
|
||||
aRoomListState(matrixUser = null, displayMigrationStatus = true),
|
||||
aRoomListState(contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SessionVerification)),
|
||||
aRoomListState(contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation)),
|
||||
aRoomListState(contentState = anEmptyContentState()),
|
||||
aRoomListState(contentState = aSkeletonContentState()),
|
||||
aRoomListState(matrixUser = null, contentState = aMigrationContentState()),
|
||||
aRoomListState(searchState = aRoomListSearchState(isSearchActive = true, query = "Test")),
|
||||
aRoomListState(filtersState = aRoomListFiltersState(isFeatureEnabled = true)),
|
||||
)
|
||||
@@ -60,16 +58,13 @@ open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
|
||||
internal fun aRoomListState(
|
||||
matrixUser: MatrixUser? = MatrixUser(userId = UserId("@id:domain"), displayName = "User#1"),
|
||||
showAvatarIndicator: Boolean = false,
|
||||
roomList: AsyncData<ImmutableList<RoomListRoomSummary>> = AsyncData.Success(aRoomListRoomSummaryList()),
|
||||
hasNetworkConnection: Boolean = true,
|
||||
snackbarMessage: SnackbarMessage? = null,
|
||||
securityBannerState: SecurityBannerState = SecurityBannerState.None,
|
||||
invitesState: InvitesState = InvitesState.NoInvites,
|
||||
contextMenu: RoomListState.ContextMenu = RoomListState.ContextMenu.Hidden,
|
||||
leaveRoomState: LeaveRoomState = aLeaveRoomState(),
|
||||
searchState: RoomListSearchState = aRoomListSearchState(),
|
||||
filtersState: RoomListFiltersState = aRoomListFiltersState(isFeatureEnabled = false),
|
||||
displayMigrationStatus: Boolean = false,
|
||||
contentState: RoomListContentState = aRoomsContentState(),
|
||||
eventSink: (RoomListEvents) -> Unit = {}
|
||||
) = RoomListState(
|
||||
matrixUser = matrixUser,
|
||||
@@ -80,11 +75,7 @@ internal fun aRoomListState(
|
||||
leaveRoomState = leaveRoomState,
|
||||
filtersState = filtersState,
|
||||
searchState = searchState,
|
||||
contentState = RoomListContentState.Rooms(
|
||||
invitesState = invitesState,
|
||||
securityBannerState = securityBannerState,
|
||||
summaries = roomList.dataOrNull().orEmpty().toPersistentList(),
|
||||
),
|
||||
contentState = contentState,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package io.element.android.features.roomlist.impl.components
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -40,6 +39,7 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.Velocity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
@@ -48,19 +48,24 @@ import io.element.android.features.roomlist.impl.InvitesEntryPointView
|
||||
import io.element.android.features.roomlist.impl.InvitesState
|
||||
import io.element.android.features.roomlist.impl.R
|
||||
import io.element.android.features.roomlist.impl.RoomListContentState
|
||||
import io.element.android.features.roomlist.impl.RoomListContentStateProvider
|
||||
import io.element.android.features.roomlist.impl.RoomListEvents
|
||||
import io.element.android.features.roomlist.impl.SecurityBannerState
|
||||
import io.element.android.features.roomlist.impl.contentType
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFilter
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersEmptyStateResources
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
|
||||
import io.element.android.features.roomlist.impl.filters.aRoomListFiltersState
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenView
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Composable
|
||||
fun RoomListContentView(
|
||||
@@ -214,7 +219,6 @@ private fun RoomsViewList(
|
||||
modifier = modifier.nestedScroll(nestedScrollConnection),
|
||||
// FAB height is 56dp, bottom padding is 16dp, we add 8dp as extra margin -> 56+16+8 = 80
|
||||
contentPadding = PaddingValues(bottom = 80.dp)
|
||||
|
||||
) {
|
||||
when (state.securityBannerState) {
|
||||
SecurityBannerState.SessionVerification -> {
|
||||
@@ -261,7 +265,7 @@ private fun RoomsViewList(
|
||||
|
||||
@Composable
|
||||
private fun EmptyViewForFilterStates(
|
||||
selectedFilters: List<RoomListFilter>,
|
||||
selectedFilters: ImmutableList<RoomListFilter>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val emptyStateResources = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters) ?: return
|
||||
@@ -301,3 +305,18 @@ private fun EmptyScaffold(
|
||||
action?.invoke(this)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun RoomListContentViewPreview(@PreviewParameter(RoomListContentStateProvider::class) state: RoomListContentState) = ElementPreview {
|
||||
RoomListContentView(
|
||||
contentState = state,
|
||||
filtersState = aRoomListFiltersState(),
|
||||
eventSink = {},
|
||||
onVerifyClicked = { },
|
||||
onConfirmRecoveryKeyClicked = { },
|
||||
onRoomClicked = {},
|
||||
onRoomLongClicked = {},
|
||||
onCreateRoomClicked = { },
|
||||
onInvitesClicked = { })
|
||||
}
|
||||
|
||||
@@ -29,12 +29,9 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@@ -24,8 +24,6 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import javax.inject.Inject
|
||||
|
||||
class RoomListRoomSummaryFactory @Inject constructor(
|
||||
@@ -52,7 +50,6 @@ class RoomListRoomSummaryFactory @Inject constructor(
|
||||
isFavorite = false,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun create(roomSummary: RoomSummary.Filled): RoomListRoomSummary {
|
||||
|
||||
@@ -21,6 +21,8 @@ import dagger.Binds
|
||||
import dagger.Module
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersPresenter
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenState
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchPresenter
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchState
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
@@ -34,4 +36,7 @@ interface RoomListModule {
|
||||
|
||||
@Binds
|
||||
fun bindFiltersPresenter(presenter: RoomListFiltersPresenter): Presenter<RoomListFiltersState>
|
||||
|
||||
@Binds
|
||||
fun bindMigrationScreenPresenter(presenter: MigrationScreenPresenter): Presenter<MigrationScreenState>
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ data class RoomListFiltersEmptyStateResources(
|
||||
@StringRes val title: Int,
|
||||
@StringRes val subtitle: Int,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create a [RoomListFiltersEmptyStateResources] from a list of selected filters.
|
||||
|
||||
@@ -34,14 +34,11 @@ class RoomListFiltersPresenter @Inject constructor(
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val filterSelectionStrategy: FilterSelectionStrategy,
|
||||
) : Presenter<RoomListFiltersState> {
|
||||
|
||||
@Composable
|
||||
override fun present(): RoomListFiltersState {
|
||||
|
||||
val isFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.RoomListFilters).collectAsState(false)
|
||||
val filters by filterSelectionStrategy.filterSelectionStates.collectAsState()
|
||||
|
||||
|
||||
fun handleEvents(event: RoomListFiltersEvents) {
|
||||
when (event) {
|
||||
RoomListFiltersEvents.ClearSelectedFilters -> {
|
||||
@@ -75,7 +72,6 @@ class RoomListFiltersPresenter @Inject constructor(
|
||||
roomListService.allRooms.updateFilter(allRoomsFilter)
|
||||
}
|
||||
|
||||
|
||||
return RoomListFiltersState(
|
||||
filterSelectionStates = filters.toPersistentList(),
|
||||
isFeatureEnabled = isFeatureEnabled,
|
||||
|
||||
@@ -18,6 +18,7 @@ package io.element.android.features.roomlist.impl.filters
|
||||
|
||||
import io.element.android.features.roomlist.impl.filters.selection.FilterSelectionState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
data class RoomListFiltersState(
|
||||
val filterSelectionStates: ImmutableList<FilterSelectionState>,
|
||||
@@ -26,7 +27,10 @@ data class RoomListFiltersState(
|
||||
) {
|
||||
val hasAnyFilterSelected = filterSelectionStates.any { it.isSelected }
|
||||
|
||||
fun selectedFilters(): List<RoomListFilter> {
|
||||
return filterSelectionStates.filter { it.isSelected }.map { it.filter }
|
||||
fun selectedFilters(): ImmutableList<RoomListFilter> {
|
||||
return filterSelectionStates
|
||||
.filter { it.isSelected }
|
||||
.map { it.filter }
|
||||
.toPersistentList()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ fun RoomListFiltersView(
|
||||
state: RoomListFiltersState,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
|
||||
fun onClearFiltersClicked() {
|
||||
state.eventSink(RoomListFiltersEvents.ClearSelectedFilters)
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultFilterSelectionStrategy @Inject constructor() : FilterSelectionStrategy {
|
||||
|
||||
private val selectedFilters = LinkedHashSet<RoomListFilter>()
|
||||
|
||||
override val filterSelectionStates = MutableStateFlow(buildFilters())
|
||||
|
||||
@@ -20,7 +20,6 @@ import io.element.android.features.roomlist.impl.filters.RoomListFilter
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface FilterSelectionStrategy {
|
||||
|
||||
val filterSelectionStates: StateFlow<Set<FilterSelectionState>>
|
||||
|
||||
fun select(filter: RoomListFilter)
|
||||
|
||||
@@ -33,8 +33,7 @@ import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
|
||||
import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
|
||||
import io.element.android.features.roomlist.impl.filters.aRoomListFiltersState
|
||||
import io.element.android.features.roomlist.impl.migration.InMemoryMigrationScreenStore
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenState
|
||||
import io.element.android.features.roomlist.impl.model.createRoomListRoomSummary
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchEvents
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchState
|
||||
@@ -54,13 +53,12 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
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_USER_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
@@ -75,6 +73,7 @@ import io.element.android.libraries.matrix.test.verification.FakeSessionVerifica
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.MutablePresenter
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
@@ -127,7 +126,6 @@ class RoomListPresenterTests {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.showAvatarIndicator).isTrue()
|
||||
sessionVerificationService.givenCanVerifySession(false)
|
||||
@@ -169,11 +167,9 @@ class RoomListPresenterTests {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = consumeItemsUntilPredicate(timeout = 3.seconds) { state -> state.roomList.dataOrNull()?.size == 16 }.last()
|
||||
// Room list is loaded with 16 placeholders
|
||||
val initialItems = initialState.roomList.dataOrNull().orEmpty()
|
||||
assertThat(initialItems.size).isEqualTo(16)
|
||||
assertThat(initialItems.all { it.isPlaceholder }).isTrue()
|
||||
val initialState = consumeItemsUntilPredicate(timeout = 3.seconds) { state -> state.contentState is RoomListContentState.Skeleton }.last()
|
||||
assertThat(initialState.contentState).isInstanceOf(RoomListContentState.Skeleton::class.java)
|
||||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
roomListService.postAllRooms(
|
||||
listOf(
|
||||
aRoomSummaryFilled(
|
||||
@@ -182,10 +178,10 @@ class RoomListPresenterTests {
|
||||
)
|
||||
)
|
||||
)
|
||||
val withRoomState = consumeItemsUntilPredicate { state -> state.roomList.dataOrNull()?.size == 1 }.last()
|
||||
val withRoomStateItems = withRoomState.roomList.dataOrNull().orEmpty()
|
||||
assertThat(withRoomStateItems.size).isEqualTo(1)
|
||||
assertThat(withRoomStateItems.first()).isEqualTo(
|
||||
val withRoomsState =
|
||||
consumeItemsUntilPredicate { state -> state.contentState is RoomListContentState.Rooms && state.contentAsRooms().summaries.isNotEmpty() }.last()
|
||||
assertThat(withRoomsState.contentAsRooms().summaries).hasSize(1)
|
||||
assertThat(withRoomsState.contentAsRooms().summaries.first()).isEqualTo(
|
||||
createRoomListRoomSummary(
|
||||
numberOfUnreadMentions = 1,
|
||||
numberOfUnreadMessages = 2,
|
||||
@@ -241,23 +237,28 @@ class RoomListPresenterTests {
|
||||
@Test
|
||||
fun `present - handle RecoveryKeyConfirmation last session`() = runTest {
|
||||
val scope = CoroutineScope(context = coroutineContext + SupervisorJob())
|
||||
val roomListService = FakeRoomListService().apply {
|
||||
postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
}
|
||||
val presenter = createRoomListPresenter(
|
||||
coroutineScope = scope,
|
||||
client = FakeMatrixClient(
|
||||
encryptionService = FakeEncryptionService().apply {
|
||||
emitIsLastDevice(true)
|
||||
}
|
||||
},
|
||||
roomListService = roomListService
|
||||
),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val eventSink = awaitItem().eventSink
|
||||
val eventSink = consumeItemsUntilPredicate {
|
||||
it.contentState is RoomListContentState.Rooms
|
||||
}.last().eventSink
|
||||
// For the last session, the state is not SessionVerification, but RecoveryKeyConfirmation
|
||||
assertThat(awaitItem().securityBannerState).isEqualTo(SecurityBannerState.RecoveryKeyConfirmation)
|
||||
assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.RecoveryKeyConfirmation)
|
||||
eventSink(RoomListEvents.DismissRequestVerificationPrompt)
|
||||
assertThat(awaitItem().securityBannerState).isEqualTo(SecurityBannerState.None)
|
||||
assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None)
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
@@ -265,16 +266,22 @@ class RoomListPresenterTests {
|
||||
@Test
|
||||
fun `present - handle DismissRequestVerificationPrompt`() = runTest {
|
||||
val scope = CoroutineScope(context = coroutineContext + SupervisorJob())
|
||||
val roomListService = FakeRoomListService().apply {
|
||||
postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
}
|
||||
val presenter = createRoomListPresenter(
|
||||
client = FakeMatrixClient(roomListService = roomListService),
|
||||
coroutineScope = scope,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val eventSink = awaitItem().eventSink
|
||||
assertThat(awaitItem().securityBannerState).isEqualTo(SecurityBannerState.SessionVerification)
|
||||
val eventSink = consumeItemsUntilPredicate {
|
||||
it.contentState is RoomListContentState.Rooms
|
||||
}.last().eventSink
|
||||
assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.SessionVerification)
|
||||
eventSink(RoomListEvents.DismissRequestVerificationPrompt)
|
||||
assertThat(awaitItem().securityBannerState).isEqualTo(SecurityBannerState.None)
|
||||
assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None)
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
@@ -282,7 +289,11 @@ class RoomListPresenterTests {
|
||||
@Test
|
||||
fun `present - handle DismissRecoveryKeyPrompt`() = runTest {
|
||||
val encryptionService = FakeEncryptionService()
|
||||
val roomListService = FakeRoomListService().apply {
|
||||
postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
}
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService,
|
||||
encryptionService = encryptionService,
|
||||
sessionVerificationService = FakeSessionVerificationService().apply {
|
||||
givenCanVerifySession(false)
|
||||
@@ -297,15 +308,16 @@ class RoomListPresenterTests {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.securityBannerState).isEqualTo(SecurityBannerState.None)
|
||||
val initialState = consumeItemsUntilPredicate {
|
||||
it.contentState is RoomListContentState.Rooms
|
||||
}.last()
|
||||
assertThat(initialState.contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None)
|
||||
encryptionService.emitRecoveryState(RecoveryState.INCOMPLETE)
|
||||
val nextState = awaitItem()
|
||||
assertThat(nextState.securityBannerState).isEqualTo(SecurityBannerState.RecoveryKeyConfirmation)
|
||||
assertThat(nextState.contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.RecoveryKeyConfirmation)
|
||||
nextState.eventSink(RoomListEvents.DismissRecoveryKeyPrompt)
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.securityBannerState).isEqualTo(SecurityBannerState.None)
|
||||
assertThat(finalState.contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None)
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
@@ -314,22 +326,30 @@ class RoomListPresenterTests {
|
||||
fun `present - sets invite state`() = runTest {
|
||||
val inviteStateFlow = MutableStateFlow(InvitesState.NoInvites)
|
||||
val inviteStateDataSource = FakeInviteDataSource(inviteStateFlow)
|
||||
val roomListService = FakeRoomListService()
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val presenter = createRoomListPresenter(inviteStateDataSource = inviteStateDataSource, coroutineScope = scope)
|
||||
val presenter = createRoomListPresenter(
|
||||
inviteStateDataSource = inviteStateDataSource,
|
||||
coroutineScope = scope,
|
||||
client = FakeMatrixClient(roomListService = roomListService),
|
||||
)
|
||||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
assertThat(awaitItem().invitesState).isEqualTo(InvitesState.NoInvites)
|
||||
consumeItemsUntilPredicate {
|
||||
it.contentState is RoomListContentState.Rooms
|
||||
}
|
||||
assertThat(awaitItem().contentAsRooms().invitesState).isEqualTo(InvitesState.NoInvites)
|
||||
|
||||
inviteStateFlow.value = InvitesState.SeenInvites
|
||||
assertThat(awaitItem().invitesState).isEqualTo(InvitesState.SeenInvites)
|
||||
assertThat(awaitItem().contentAsRooms().invitesState).isEqualTo(InvitesState.SeenInvites)
|
||||
|
||||
inviteStateFlow.value = InvitesState.NewInvites
|
||||
assertThat(awaitItem().invitesState).isEqualTo(InvitesState.NewInvites)
|
||||
assertThat(awaitItem().contentAsRooms().invitesState).isEqualTo(InvitesState.NewInvites)
|
||||
|
||||
inviteStateFlow.value = InvitesState.NoInvites
|
||||
assertThat(awaitItem().invitesState).isEqualTo(InvitesState.NoInvites)
|
||||
assertThat(awaitItem().contentAsRooms().invitesState).isEqualTo(InvitesState.NoInvites)
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
@@ -477,6 +497,7 @@ class RoomListPresenterTests {
|
||||
val userDefinedMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
|
||||
val notificationSettingsService = FakeNotificationSettingsService()
|
||||
val roomListService = FakeRoomListService()
|
||||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
roomListService.postAllRooms(listOf(aRoomSummaryFilled(notificationMode = userDefinedMode)))
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService,
|
||||
@@ -488,12 +509,13 @@ class RoomListPresenterTests {
|
||||
presenter.present()
|
||||
}.test {
|
||||
notificationSettingsService.setRoomNotificationMode(A_ROOM_ID, userDefinedMode)
|
||||
|
||||
val updatedState = consumeItemsUntilPredicate { state ->
|
||||
state.roomList.dataOrNull().orEmpty().any { it.id == A_ROOM_ID.value && it.userDefinedNotificationMode == userDefinedMode }
|
||||
(state.contentState as? RoomListContentState.Rooms)?.summaries.orEmpty().any { summary ->
|
||||
summary.id == A_ROOM_ID.value && summary.userDefinedNotificationMode == userDefinedMode
|
||||
}
|
||||
}.last()
|
||||
|
||||
val room = updatedState.roomList.dataOrNull()?.find { it.id == A_ROOM_ID.value }
|
||||
val room = updatedState.contentAsRooms().summaries.find { it.id == A_ROOM_ID.value }
|
||||
assertThat(room?.userDefinedNotificationMode).isEqualTo(userDefinedMode)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
scope.cancel()
|
||||
@@ -526,30 +548,46 @@ class RoomListPresenterTests {
|
||||
}
|
||||
}
|
||||
|
||||
fun `present - change in migration presenter state modifies isMigrating`() = runTest {
|
||||
val client = FakeMatrixClient(sessionId = A_SESSION_ID)
|
||||
val migrationStore = InMemoryMigrationScreenStore()
|
||||
val migrationScreenPresenter = MigrationScreenPresenter(client, migrationStore)
|
||||
@Test
|
||||
fun `present - change in migration presenter state modifies contentState`() = runTest {
|
||||
val migrationScreenPresenter = MutablePresenter(MigrationScreenState(true))
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val presenter = createRoomListPresenter(
|
||||
client = client,
|
||||
coroutineScope = scope,
|
||||
migrationScreenPresenter = migrationScreenPresenter,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
// The migration screen is shown if the migration screen has not been shown before
|
||||
assertThat(initialState.displayMigrationStatus).isTrue()
|
||||
skipItems(2)
|
||||
|
||||
assertThat(initialState.contentState).isInstanceOf(RoomListContentState.Migration::class.java)
|
||||
// Set migration as done and set the room list service as running to trigger a refresh of the presenter value
|
||||
(client.roomListService as FakeRoomListService).postState(RoomListService.State.Running)
|
||||
migrationStore.setMigrationScreenShown(A_SESSION_ID)
|
||||
|
||||
migrationScreenPresenter.updateState(MigrationScreenState(false))
|
||||
// The migration screen is not shown anymore
|
||||
assertThat(awaitItem().displayMigrationStatus).isFalse()
|
||||
assertThat(awaitItem().contentState).isInstanceOf(RoomListContentState.Skeleton::class.java)
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room service returns no room, then contentState is Empty `() = runTest {
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val roomListService = FakeRoomListService()
|
||||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(0))
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService,
|
||||
)
|
||||
val presenter = createRoomListPresenter(
|
||||
client = matrixClient,
|
||||
coroutineScope = scope,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
assertThat(awaitItem().contentState).isInstanceOf(RoomListContentState.Empty::class.java)
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
@@ -609,10 +647,7 @@ class RoomListPresenterTests {
|
||||
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
||||
coroutineScope: CoroutineScope,
|
||||
migrationScreenPresenter: MigrationScreenPresenter = MigrationScreenPresenter(
|
||||
matrixClient = client,
|
||||
migrationScreenStore = InMemoryMigrationScreenStore(),
|
||||
),
|
||||
migrationScreenPresenter: Presenter<MigrationScreenState> = Presenter { MigrationScreenState(false) },
|
||||
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
||||
filtersPresenter: Presenter<RoomListFiltersState> = Presenter { aRoomListFiltersState() },
|
||||
searchPresenter: Presenter<RoomListSearchState> = Presenter { aRoomListSearchState() },
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.features.roomlist.impl
|
||||
|
||||
internal fun RoomListState.contentAsRooms() = contentState as RoomListContentState.Rooms
|
||||
@@ -26,7 +26,6 @@ import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performTouchInput
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.features.roomlist.impl.components.RoomListMenuAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
@@ -35,7 +34,6 @@ import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.clickOn
|
||||
import io.element.android.tests.testutils.ensureCalledOnce
|
||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
@@ -50,7 +48,7 @@ class RoomListViewTest {
|
||||
val eventsRecorder = EventsRecorder<RoomListEvents>()
|
||||
rule.setRoomListView(
|
||||
state = aRoomListState(
|
||||
securityBannerState = SecurityBannerState.SessionVerification,
|
||||
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SessionVerification),
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
)
|
||||
@@ -65,7 +63,7 @@ class RoomListViewTest {
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setRoomListView(
|
||||
state = aRoomListState(
|
||||
securityBannerState = SecurityBannerState.SessionVerification,
|
||||
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SessionVerification),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onVerifyClicked = callback,
|
||||
@@ -79,7 +77,7 @@ class RoomListViewTest {
|
||||
val eventsRecorder = EventsRecorder<RoomListEvents>()
|
||||
rule.setRoomListView(
|
||||
state = aRoomListState(
|
||||
securityBannerState = SecurityBannerState.RecoveryKeyConfirmation,
|
||||
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation),
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
)
|
||||
@@ -94,7 +92,7 @@ class RoomListViewTest {
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setRoomListView(
|
||||
state = aRoomListState(
|
||||
securityBannerState = SecurityBannerState.RecoveryKeyConfirmation,
|
||||
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onConfirmRecoveryKeyClicked = callback,
|
||||
@@ -110,7 +108,7 @@ class RoomListViewTest {
|
||||
rule.setRoomListView(
|
||||
state = aRoomListState(
|
||||
eventSink = eventsRecorder,
|
||||
roomList = AsyncData.Success(persistentListOf()),
|
||||
contentState = anEmptyContentState(),
|
||||
),
|
||||
onCreateRoomClicked = callback,
|
||||
)
|
||||
@@ -124,7 +122,7 @@ class RoomListViewTest {
|
||||
val state = aRoomListState(
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
val room0 = state.roomList.dataOrNull()!!.first()
|
||||
val room0 = state.contentAsRooms().summaries.first()
|
||||
ensureCalledOnceWithParam(room0.roomId) { callback ->
|
||||
rule.setRoomListView(
|
||||
state = state,
|
||||
@@ -140,7 +138,7 @@ class RoomListViewTest {
|
||||
val state = aRoomListState(
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
val room0 = state.roomList.dataOrNull()!!.first()
|
||||
val room0 = state.contentAsRooms().summaries.first()
|
||||
rule.setRoomListView(
|
||||
state = state,
|
||||
)
|
||||
@@ -170,7 +168,7 @@ class RoomListViewTest {
|
||||
fun `clicking on invites invokes the expected callback`() {
|
||||
val eventsRecorder = EventsRecorder<RoomListEvents>()
|
||||
val state = aRoomListState(
|
||||
invitesState = InvitesState.NewInvites,
|
||||
contentState = aRoomsContentState(invitesState = InvitesState.NewInvites),
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
ensureCalledOnce { callback ->
|
||||
|
||||
@@ -29,6 +29,7 @@ import io.element.android.features.roomlist.impl.datasource.DefaultInviteStateDa
|
||||
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
|
||||
import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersPresenter
|
||||
import io.element.android.features.roomlist.impl.filters.selection.DefaultFilterSelectionStrategy
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter
|
||||
import io.element.android.features.roomlist.impl.migration.SharedPrefsMigrationScreenStore
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchDataSource
|
||||
@@ -127,6 +128,7 @@ class RoomListScreen(
|
||||
filtersPresenter = RoomListFiltersPresenter(
|
||||
roomListService = matrixClient.roomListService,
|
||||
featureFlagService = featureFlagService,
|
||||
filterSelectionStrategy = DefaultFilterSelectionStrategy(),
|
||||
),
|
||||
analyticsService = NoopAnalyticsService(),
|
||||
)
|
||||
|
||||
@@ -30,6 +30,7 @@ dependencies {
|
||||
implementation(libs.test.junit)
|
||||
implementation(libs.test.truth)
|
||||
implementation(libs.coroutines.test)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(libs.test.turbine)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.tests.testutils
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
class MutablePresenter<State>(initialState: State) : Presenter<State> {
|
||||
private val stateFlow = MutableStateFlow(initialState)
|
||||
|
||||
fun updateState(state: State) {
|
||||
stateFlow.value = state
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): State {
|
||||
return stateFlow.collectAsState().value
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user