Merge pull request #2405 from element-hq/feature/fga/room_list_refact_search
RoomList : rework how search is done to prepare for later filtering
This commit is contained in:
commit
615ce72131
49 changed files with 623 additions and 255 deletions
|
|
@ -20,7 +20,6 @@ import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
sealed interface RoomListEvents {
|
sealed interface RoomListEvents {
|
||||||
data class UpdateFilter(val newFilter: String) : RoomListEvents
|
|
||||||
data class UpdateVisibleRange(val range: IntRange) : RoomListEvents
|
data class UpdateVisibleRange(val range: IntRange) : RoomListEvents
|
||||||
data object DismissRequestVerificationPrompt : RoomListEvents
|
data object DismissRequestVerificationPrompt : RoomListEvents
|
||||||
data object DismissRecoveryKeyPrompt : RoomListEvents
|
data object DismissRecoveryKeyPrompt : RoomListEvents
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ 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.InviteStateDataSource
|
||||||
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
|
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
|
||||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter
|
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter
|
||||||
|
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
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||||
|
|
@ -76,6 +78,7 @@ class RoomListPresenter @Inject constructor(
|
||||||
private val encryptionService: EncryptionService,
|
private val encryptionService: EncryptionService,
|
||||||
private val featureFlagService: FeatureFlagService,
|
private val featureFlagService: FeatureFlagService,
|
||||||
private val indicatorService: IndicatorService,
|
private val indicatorService: IndicatorService,
|
||||||
|
private val searchPresenter: Presenter<RoomListSearchState>,
|
||||||
private val migrationScreenPresenter: MigrationScreenPresenter,
|
private val migrationScreenPresenter: MigrationScreenPresenter,
|
||||||
private val sessionPreferencesStore: SessionPreferencesStore,
|
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||||
) : Presenter<RoomListState> {
|
) : Presenter<RoomListState> {
|
||||||
|
|
@ -89,9 +92,8 @@ class RoomListPresenter @Inject constructor(
|
||||||
val roomList by produceState(initialValue = AsyncData.Loading()) {
|
val roomList by produceState(initialValue = AsyncData.Loading()) {
|
||||||
roomListDataSource.allRooms.collect { value = AsyncData.Success(it) }
|
roomListDataSource.allRooms.collect { value = AsyncData.Success(it) }
|
||||||
}
|
}
|
||||||
val filteredRoomList by roomListDataSource.filteredRooms.collectAsState()
|
|
||||||
val filter by roomListDataSource.filter.collectAsState()
|
|
||||||
val networkConnectionStatus by networkMonitor.connectivity.collectAsState()
|
val networkConnectionStatus by networkMonitor.connectivity.collectAsState()
|
||||||
|
val searchState = searchPresenter.present()
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
roomListDataSource.launchIn(this)
|
roomListDataSource.launchIn(this)
|
||||||
|
|
@ -122,21 +124,14 @@ class RoomListPresenter @Inject constructor(
|
||||||
// Avatar indicator
|
// Avatar indicator
|
||||||
val showAvatarIndicator by indicatorService.showRoomListTopBarIndicator()
|
val showAvatarIndicator by indicatorService.showRoomListTopBarIndicator()
|
||||||
|
|
||||||
var displaySearchResults by rememberSaveable { mutableStateOf(false) }
|
|
||||||
val contextMenu = remember { mutableStateOf<RoomListState.ContextMenu>(RoomListState.ContextMenu.Hidden) }
|
val contextMenu = remember { mutableStateOf<RoomListState.ContextMenu>(RoomListState.ContextMenu.Hidden) }
|
||||||
|
|
||||||
fun handleEvents(event: RoomListEvents) {
|
fun handleEvents(event: RoomListEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is RoomListEvents.UpdateFilter -> roomListDataSource.updateFilter(event.newFilter)
|
|
||||||
is RoomListEvents.UpdateVisibleRange -> updateVisibleRange(event.range)
|
is RoomListEvents.UpdateVisibleRange -> updateVisibleRange(event.range)
|
||||||
RoomListEvents.DismissRequestVerificationPrompt -> verificationPromptDismissed = true
|
RoomListEvents.DismissRequestVerificationPrompt -> verificationPromptDismissed = true
|
||||||
RoomListEvents.DismissRecoveryKeyPrompt -> recoveryKeyPromptDismissed = true
|
RoomListEvents.DismissRecoveryKeyPrompt -> recoveryKeyPromptDismissed = true
|
||||||
RoomListEvents.ToggleSearchResults -> {
|
RoomListEvents.ToggleSearchResults -> searchState.eventSink(RoomListSearchEvents.ToggleSearchVisibility)
|
||||||
if (displaySearchResults) {
|
|
||||||
roomListDataSource.updateFilter("")
|
|
||||||
}
|
|
||||||
displaySearchResults = !displaySearchResults
|
|
||||||
}
|
|
||||||
is RoomListEvents.ShowContextMenu -> {
|
is RoomListEvents.ShowContextMenu -> {
|
||||||
coroutineScope.showContextMenu(event, contextMenu)
|
coroutineScope.showContextMenu(event, contextMenu)
|
||||||
}
|
}
|
||||||
|
|
@ -175,16 +170,14 @@ class RoomListPresenter @Inject constructor(
|
||||||
matrixUser = matrixUser.value,
|
matrixUser = matrixUser.value,
|
||||||
showAvatarIndicator = showAvatarIndicator,
|
showAvatarIndicator = showAvatarIndicator,
|
||||||
roomList = roomList,
|
roomList = roomList,
|
||||||
filter = filter,
|
|
||||||
filteredRoomList = filteredRoomList,
|
|
||||||
displayVerificationPrompt = displayVerificationPrompt,
|
displayVerificationPrompt = displayVerificationPrompt,
|
||||||
displayRecoveryKeyPrompt = displayRecoveryKeyPrompt,
|
displayRecoveryKeyPrompt = displayRecoveryKeyPrompt,
|
||||||
snackbarMessage = snackbarMessage,
|
snackbarMessage = snackbarMessage,
|
||||||
hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online,
|
hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online,
|
||||||
invitesState = inviteStateDataSource.inviteState(),
|
invitesState = inviteStateDataSource.inviteState(),
|
||||||
displaySearchResults = displaySearchResults,
|
|
||||||
contextMenu = contextMenu.value,
|
contextMenu = contextMenu.value,
|
||||||
leaveRoomState = leaveRoomState,
|
leaveRoomState = leaveRoomState,
|
||||||
|
searchState = searchState,
|
||||||
displayMigrationStatus = isMigrating,
|
displayMigrationStatus = isMigrating,
|
||||||
eventSink = ::handleEvents
|
eventSink = ::handleEvents
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package io.element.android.features.roomlist.impl
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||||
|
import io.element.android.features.roomlist.impl.search.RoomListSearchState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
@ -30,16 +31,14 @@ data class RoomListState(
|
||||||
val matrixUser: MatrixUser?,
|
val matrixUser: MatrixUser?,
|
||||||
val showAvatarIndicator: Boolean,
|
val showAvatarIndicator: Boolean,
|
||||||
val roomList: AsyncData<ImmutableList<RoomListRoomSummary>>,
|
val roomList: AsyncData<ImmutableList<RoomListRoomSummary>>,
|
||||||
val filter: String?,
|
|
||||||
val filteredRoomList: ImmutableList<RoomListRoomSummary>,
|
|
||||||
val displayVerificationPrompt: Boolean,
|
val displayVerificationPrompt: Boolean,
|
||||||
val displayRecoveryKeyPrompt: Boolean,
|
val displayRecoveryKeyPrompt: Boolean,
|
||||||
val hasNetworkConnection: Boolean,
|
val hasNetworkConnection: Boolean,
|
||||||
val snackbarMessage: SnackbarMessage?,
|
val snackbarMessage: SnackbarMessage?,
|
||||||
val invitesState: InvitesState,
|
val invitesState: InvitesState,
|
||||||
val displaySearchResults: Boolean,
|
|
||||||
val contextMenu: ContextMenu,
|
val contextMenu: ContextMenu,
|
||||||
val leaveRoomState: LeaveRoomState,
|
val leaveRoomState: LeaveRoomState,
|
||||||
|
val searchState: RoomListSearchState,
|
||||||
val displayMigrationStatus: Boolean,
|
val displayMigrationStatus: Boolean,
|
||||||
val eventSink: (RoomListEvents) -> Unit,
|
val eventSink: (RoomListEvents) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import io.element.android.features.leaveroom.api.aLeaveRoomState
|
||||||
import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory
|
import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory
|
||||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
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.model.aRoomListRoomSummary
|
||||||
|
import io.element.android.features.roomlist.impl.search.aRoomListSearchState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
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
|
||||||
|
|
@ -41,14 +42,13 @@ open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
|
||||||
aRoomListState().copy(hasNetworkConnection = false),
|
aRoomListState().copy(hasNetworkConnection = false),
|
||||||
aRoomListState().copy(invitesState = InvitesState.SeenInvites),
|
aRoomListState().copy(invitesState = InvitesState.SeenInvites),
|
||||||
aRoomListState().copy(invitesState = InvitesState.NewInvites),
|
aRoomListState().copy(invitesState = InvitesState.NewInvites),
|
||||||
aRoomListState().copy(displaySearchResults = true, filter = "", filteredRoomList = persistentListOf()),
|
|
||||||
aRoomListState().copy(displaySearchResults = true),
|
|
||||||
aRoomListState().copy(contextMenu = aContextMenuShown(roomName = "A nice room name")),
|
aRoomListState().copy(contextMenu = aContextMenuShown(roomName = "A nice room name")),
|
||||||
aRoomListState().copy(contextMenu = aContextMenuShown(isFavorite = true)),
|
aRoomListState().copy(contextMenu = aContextMenuShown(isFavorite = true)),
|
||||||
aRoomListState().copy(displayRecoveryKeyPrompt = true),
|
aRoomListState().copy(displayRecoveryKeyPrompt = true),
|
||||||
aRoomListState().copy(roomList = AsyncData.Success(persistentListOf())),
|
aRoomListState().copy(roomList = AsyncData.Success(persistentListOf())),
|
||||||
aRoomListState().copy(roomList = AsyncData.Loading(prevData = RoomListRoomSummaryFactory.createFakeList())),
|
aRoomListState().copy(roomList = AsyncData.Loading(prevData = RoomListRoomSummaryFactory.createFakeList())),
|
||||||
aRoomListState().copy(matrixUser = null, displayMigrationStatus = true),
|
aRoomListState().copy(matrixUser = null, displayMigrationStatus = true),
|
||||||
|
aRoomListState().copy(searchState = aRoomListSearchState(isSearchActive = true, query = "Test")),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,16 +56,14 @@ internal fun aRoomListState() = RoomListState(
|
||||||
matrixUser = MatrixUser(userId = UserId("@id:domain"), displayName = "User#1"),
|
matrixUser = MatrixUser(userId = UserId("@id:domain"), displayName = "User#1"),
|
||||||
showAvatarIndicator = false,
|
showAvatarIndicator = false,
|
||||||
roomList = AsyncData.Success(aRoomListRoomSummaryList()),
|
roomList = AsyncData.Success(aRoomListRoomSummaryList()),
|
||||||
filter = "filter",
|
|
||||||
filteredRoomList = aRoomListRoomSummaryList(),
|
|
||||||
hasNetworkConnection = true,
|
hasNetworkConnection = true,
|
||||||
snackbarMessage = null,
|
snackbarMessage = null,
|
||||||
displayVerificationPrompt = false,
|
displayVerificationPrompt = false,
|
||||||
displayRecoveryKeyPrompt = false,
|
displayRecoveryKeyPrompt = false,
|
||||||
invitesState = InvitesState.NoInvites,
|
invitesState = InvitesState.NoInvites,
|
||||||
displaySearchResults = false,
|
|
||||||
contextMenu = RoomListState.ContextMenu.Hidden,
|
contextMenu = RoomListState.ContextMenu.Hidden,
|
||||||
leaveRoomState = aLeaveRoomState(),
|
leaveRoomState = aLeaveRoomState(),
|
||||||
|
searchState = aRoomListSearchState(),
|
||||||
displayMigrationStatus = false,
|
displayMigrationStatus = false,
|
||||||
eventSink = {}
|
eventSink = {}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ import io.element.android.features.roomlist.impl.components.RoomListTopBar
|
||||||
import io.element.android.features.roomlist.impl.components.RoomSummaryRow
|
import io.element.android.features.roomlist.impl.components.RoomSummaryRow
|
||||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenView
|
import io.element.android.features.roomlist.impl.migration.MigrationScreenView
|
||||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||||
import io.element.android.features.roomlist.impl.search.RoomListSearchResultView
|
import io.element.android.features.roomlist.impl.search.RoomListSearchView
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
|
@ -118,8 +118,8 @@ fun RoomListView(
|
||||||
onMenuActionClicked = onMenuActionClicked,
|
onMenuActionClicked = onMenuActionClicked,
|
||||||
)
|
)
|
||||||
// This overlaid view will only be visible when state.displaySearchResults is true
|
// This overlaid view will only be visible when state.displaySearchResults is true
|
||||||
RoomListSearchResultView(
|
RoomListSearchView(
|
||||||
state = state,
|
state = state.searchState,
|
||||||
onRoomClicked = onRoomClicked,
|
onRoomClicked = onRoomClicked,
|
||||||
onRoomLongClicked = { onRoomLongClicked(it) },
|
onRoomLongClicked = { onRoomLongClicked(it) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -207,8 +207,7 @@ private fun RoomListContent(
|
||||||
RoomListTopBar(
|
RoomListTopBar(
|
||||||
matrixUser = state.matrixUser,
|
matrixUser = state.matrixUser,
|
||||||
showAvatarIndicator = state.showAvatarIndicator,
|
showAvatarIndicator = state.showAvatarIndicator,
|
||||||
areSearchResultsDisplayed = state.displaySearchResults,
|
areSearchResultsDisplayed = state.searchState.isSearchActive,
|
||||||
onFilterChanged = { state.eventSink(RoomListEvents.UpdateFilter(it)) },
|
|
||||||
onToggleSearch = { state.eventSink(RoomListEvents.ToggleSearchResults) },
|
onToggleSearch = { state.eventSink(RoomListEvents.ToggleSearchResults) },
|
||||||
onMenuActionClicked = onMenuActionClicked,
|
onMenuActionClicked = onMenuActionClicked,
|
||||||
onOpenSettings = onOpenSettings,
|
onOpenSettings = onOpenSettings,
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package io.element.android.features.roomlist.impl.components
|
package io.element.android.features.roomlist.impl.components
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
|
@ -87,7 +86,6 @@ fun RoomListTopBar(
|
||||||
matrixUser: MatrixUser?,
|
matrixUser: MatrixUser?,
|
||||||
showAvatarIndicator: Boolean,
|
showAvatarIndicator: Boolean,
|
||||||
areSearchResultsDisplayed: Boolean,
|
areSearchResultsDisplayed: Boolean,
|
||||||
onFilterChanged: (String) -> Unit,
|
|
||||||
onToggleSearch: () -> Unit,
|
onToggleSearch: () -> Unit,
|
||||||
onMenuActionClicked: (RoomListMenuAction) -> Unit,
|
onMenuActionClicked: (RoomListMenuAction) -> Unit,
|
||||||
onOpenSettings: () -> Unit,
|
onOpenSettings: () -> Unit,
|
||||||
|
|
@ -95,15 +93,6 @@ fun RoomListTopBar(
|
||||||
displayMenuItems: Boolean,
|
displayMenuItems: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
fun closeFilter() {
|
|
||||||
onFilterChanged("")
|
|
||||||
}
|
|
||||||
|
|
||||||
BackHandler(enabled = areSearchResultsDisplayed) {
|
|
||||||
closeFilter()
|
|
||||||
onToggleSearch()
|
|
||||||
}
|
|
||||||
|
|
||||||
DefaultRoomListTopBar(
|
DefaultRoomListTopBar(
|
||||||
matrixUser = matrixUser,
|
matrixUser = matrixUser,
|
||||||
showAvatarIndicator = showAvatarIndicator,
|
showAvatarIndicator = showAvatarIndicator,
|
||||||
|
|
|
||||||
|
|
@ -25,15 +25,11 @@ 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.RoomListService
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
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.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
@ -54,9 +50,7 @@ class RoomListDataSource @Inject constructor(
|
||||||
observeNotificationSettings()
|
observeNotificationSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _filter = MutableStateFlow("")
|
|
||||||
private val _allRooms = MutableSharedFlow<ImmutableList<RoomListRoomSummary>>(replay = 1)
|
private val _allRooms = MutableSharedFlow<ImmutableList<RoomListRoomSummary>>(replay = 1)
|
||||||
private val _filteredRooms = MutableStateFlow<ImmutableList<RoomListRoomSummary>>(persistentListOf())
|
|
||||||
|
|
||||||
private val lock = Mutex()
|
private val lock = Mutex()
|
||||||
private val diffCache = MutableListDiffCache<RoomListRoomSummary>()
|
private val diffCache = MutableListDiffCache<RoomListRoomSummary>()
|
||||||
|
|
@ -72,29 +66,9 @@ class RoomListDataSource @Inject constructor(
|
||||||
replaceWith(roomSummaries)
|
replaceWith(roomSummaries)
|
||||||
}
|
}
|
||||||
.launchIn(coroutineScope)
|
.launchIn(coroutineScope)
|
||||||
|
|
||||||
combine(
|
|
||||||
_filter,
|
|
||||||
_allRooms
|
|
||||||
) { filterValue, allRoomsValue ->
|
|
||||||
when {
|
|
||||||
filterValue.isEmpty() -> emptyList()
|
|
||||||
else -> allRoomsValue.filter { it.name.contains(filterValue, ignoreCase = true) }
|
|
||||||
}.toImmutableList()
|
|
||||||
}
|
|
||||||
.onEach {
|
|
||||||
_filteredRooms.value = it
|
|
||||||
}
|
|
||||||
.launchIn(coroutineScope)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateFilter(filterValue: String) {
|
|
||||||
_filter.value = filterValue
|
|
||||||
}
|
|
||||||
|
|
||||||
val filter: StateFlow<String> = _filter
|
|
||||||
val allRooms: SharedFlow<ImmutableList<RoomListRoomSummary>> = _allRooms
|
val allRooms: SharedFlow<ImmutableList<RoomListRoomSummary>> = _allRooms
|
||||||
val filteredRooms: StateFlow<ImmutableList<RoomListRoomSummary>> = _filteredRooms
|
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
@OptIn(FlowPreview::class)
|
||||||
private fun observeNotificationSettings() {
|
private fun observeNotificationSettings() {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* 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.di
|
||||||
|
|
||||||
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
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
|
||||||
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
|
||||||
|
@ContributesTo(SessionScope::class)
|
||||||
|
@Module
|
||||||
|
interface RoomListModule {
|
||||||
|
@Binds
|
||||||
|
fun bindSearchPresenter(presenter: RoomListSearchPresenter): Presenter<RoomListSearchState>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* 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.search
|
||||||
|
|
||||||
|
sealed interface RoomListSearchEvents {
|
||||||
|
data object ToggleSearchVisibility : RoomListSearchEvents
|
||||||
|
data class QueryChanged(val query: String) : RoomListSearchEvents
|
||||||
|
data object ClearQuery : RoomListSearchEvents
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
/*
|
||||||
|
* 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.search
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory
|
||||||
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.loadAllIncrementally
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val PAGE_SIZE = 50
|
||||||
|
|
||||||
|
class RoomListSearchPresenter @Inject constructor(
|
||||||
|
private val roomListService: RoomListService,
|
||||||
|
private val roomSummaryFactory: RoomListRoomSummaryFactory,
|
||||||
|
private val coroutineDispatchers: CoroutineDispatchers,
|
||||||
|
) : Presenter<RoomListSearchState> {
|
||||||
|
@Composable
|
||||||
|
override fun present(): RoomListSearchState {
|
||||||
|
var isSearchActive by rememberSaveable {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
var searchQuery by rememberSaveable {
|
||||||
|
mutableStateOf("")
|
||||||
|
}
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val roomList = remember {
|
||||||
|
roomListService.createRoomList(
|
||||||
|
coroutineScope = coroutineScope,
|
||||||
|
pageSize = PAGE_SIZE,
|
||||||
|
initialFilter = RoomListFilter.all(RoomListFilter.None),
|
||||||
|
source = RoomList.Source.All,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
roomList.loadAllIncrementally(this)
|
||||||
|
}
|
||||||
|
LaunchedEffect(key1 = searchQuery) {
|
||||||
|
val filter = if (searchQuery.isBlank()) {
|
||||||
|
RoomListFilter.all(RoomListFilter.None)
|
||||||
|
} else {
|
||||||
|
RoomListFilter.all(RoomListFilter.NonLeft, RoomListFilter.NormalizedMatchRoomName(searchQuery))
|
||||||
|
}
|
||||||
|
roomList.updateFilter(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleEvents(event: RoomListSearchEvents) {
|
||||||
|
when (event) {
|
||||||
|
RoomListSearchEvents.ClearQuery -> {
|
||||||
|
searchQuery = ""
|
||||||
|
}
|
||||||
|
is RoomListSearchEvents.QueryChanged -> {
|
||||||
|
searchQuery = event.query
|
||||||
|
}
|
||||||
|
RoomListSearchEvents.ToggleSearchVisibility -> {
|
||||||
|
isSearchActive = !isSearchActive
|
||||||
|
searchQuery = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val searchResults by roomList
|
||||||
|
.rememberMappedSummaries()
|
||||||
|
.collectAsState(initial = persistentListOf())
|
||||||
|
|
||||||
|
return RoomListSearchState(
|
||||||
|
isSearchActive = isSearchActive,
|
||||||
|
query = searchQuery,
|
||||||
|
results = searchResults,
|
||||||
|
eventSink = ::handleEvents
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RoomList.rememberMappedSummaries() = remember {
|
||||||
|
summaries
|
||||||
|
.map { roomSummaries ->
|
||||||
|
roomSummaries
|
||||||
|
.filterIsInstance<RoomSummary.Filled>()
|
||||||
|
.map(roomSummaryFactory::create)
|
||||||
|
.toPersistentList()
|
||||||
|
}
|
||||||
|
.flowOn(coroutineDispatchers.computation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* 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.search
|
||||||
|
|
||||||
|
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
|
data class RoomListSearchState(
|
||||||
|
val isSearchActive: Boolean,
|
||||||
|
val query: String,
|
||||||
|
val results: ImmutableList<RoomListRoomSummary>,
|
||||||
|
val eventSink: (RoomListSearchEvents) -> Unit
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* 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.search
|
||||||
|
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import io.element.android.features.roomlist.impl.aRoomListRoomSummaryList
|
||||||
|
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
|
class RoomListSearchStateProvider : PreviewParameterProvider<RoomListSearchState> {
|
||||||
|
override val values: Sequence<RoomListSearchState>
|
||||||
|
get() = sequenceOf(
|
||||||
|
aRoomListSearchState(),
|
||||||
|
aRoomListSearchState(
|
||||||
|
isSearchActive = true,
|
||||||
|
query = "Test",
|
||||||
|
results = aRoomListRoomSummaryList()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun aRoomListSearchState(
|
||||||
|
isSearchActive: Boolean = false,
|
||||||
|
query: String = "",
|
||||||
|
results: ImmutableList<RoomListRoomSummary> = persistentListOf(),
|
||||||
|
) = RoomListSearchState(
|
||||||
|
isSearchActive = isSearchActive,
|
||||||
|
query = query,
|
||||||
|
results = results,
|
||||||
|
eventSink = { },
|
||||||
|
)
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package io.element.android.features.roomlist.impl.search
|
package io.element.android.features.roomlist.impl.search
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
|
|
@ -25,32 +26,23 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.TextFieldDefaults
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.drawBehind
|
import androidx.compose.ui.draw.drawBehind
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.Velocity
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.roomlist.impl.RoomListEvents
|
|
||||||
import io.element.android.features.roomlist.impl.RoomListState
|
|
||||||
import io.element.android.features.roomlist.impl.aRoomListState
|
|
||||||
import io.element.android.features.roomlist.impl.components.RoomSummaryRow
|
import io.element.android.features.roomlist.impl.components.RoomSummaryRow
|
||||||
import io.element.android.features.roomlist.impl.contentType
|
import io.element.android.features.roomlist.impl.contentType
|
||||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||||
|
|
@ -68,26 +60,30 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun RoomListSearchResultView(
|
internal fun RoomListSearchView(
|
||||||
state: RoomListState,
|
state: RoomListSearchState,
|
||||||
onRoomClicked: (RoomId) -> Unit,
|
onRoomClicked: (RoomId) -> Unit,
|
||||||
onRoomLongClicked: (RoomListRoomSummary) -> Unit,
|
onRoomLongClicked: (RoomListRoomSummary) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
BackHandler(enabled = state.isSearchActive) {
|
||||||
|
state.eventSink(RoomListSearchEvents.ToggleSearchVisibility)
|
||||||
|
}
|
||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = state.displaySearchResults,
|
visible = state.isSearchActive,
|
||||||
enter = fadeIn(),
|
enter = fadeIn(),
|
||||||
exit = fadeOut(),
|
exit = fadeOut(),
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.applyIf(state.displaySearchResults, ifTrue = {
|
.applyIf(state.isSearchActive, ifTrue = {
|
||||||
// Disable input interaction to underlying views
|
// Disable input interaction to underlying views
|
||||||
pointerInput(Unit) {}
|
pointerInput(Unit) {}
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
if (state.displaySearchResults) {
|
if (state.isSearchActive) {
|
||||||
RoomListSearchResultContent(
|
RoomListSearchContent(
|
||||||
state = state,
|
state = state,
|
||||||
onRoomClicked = onRoomClicked,
|
onRoomClicked = onRoomClicked,
|
||||||
onRoomLongClicked = onRoomLongClicked,
|
onRoomLongClicked = onRoomLongClicked,
|
||||||
|
|
@ -99,15 +95,15 @@ internal fun RoomListSearchResultView(
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun RoomListSearchResultContent(
|
private fun RoomListSearchContent(
|
||||||
state: RoomListState,
|
state: RoomListSearchState,
|
||||||
onRoomClicked: (RoomId) -> Unit,
|
onRoomClicked: (RoomId) -> Unit,
|
||||||
onRoomLongClicked: (RoomListRoomSummary) -> Unit,
|
onRoomLongClicked: (RoomListRoomSummary) -> Unit,
|
||||||
) {
|
) {
|
||||||
val borderColor = MaterialTheme.colorScheme.tertiary
|
val borderColor = MaterialTheme.colorScheme.tertiary
|
||||||
val strokeWidth = 1.dp
|
val strokeWidth = 1.dp
|
||||||
fun onBackButtonPressed() {
|
fun onBackButtonPressed() {
|
||||||
state.eventSink(RoomListEvents.ToggleSearchResults)
|
state.eventSink(RoomListSearchEvents.ToggleSearchVisibility)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onRoomClicked(room: RoomListRoomSummary) {
|
fun onRoomClicked(room: RoomListRoomSummary) {
|
||||||
|
|
@ -126,7 +122,7 @@ private fun RoomListSearchResultContent(
|
||||||
},
|
},
|
||||||
navigationIcon = { BackButton(onClick = ::onBackButtonPressed) },
|
navigationIcon = { BackButton(onClick = ::onBackButtonPressed) },
|
||||||
title = {
|
title = {
|
||||||
val filter = state.filter.orEmpty()
|
val filter = state.query
|
||||||
val focusRequester = FocusRequester()
|
val focusRequester = FocusRequester()
|
||||||
TextField(
|
TextField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -134,7 +130,7 @@ private fun RoomListSearchResultContent(
|
||||||
.focusRequester(focusRequester),
|
.focusRequester(focusRequester),
|
||||||
value = filter,
|
value = filter,
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
onValueChange = { state.eventSink(RoomListEvents.UpdateFilter(it)) },
|
onValueChange = { state.eventSink(RoomListSearchEvents.QueryChanged(it)) },
|
||||||
colors = TextFieldDefaults.colors(
|
colors = TextFieldDefaults.colors(
|
||||||
focusedContainerColor = Color.Transparent,
|
focusedContainerColor = Color.Transparent,
|
||||||
unfocusedContainerColor = Color.Transparent,
|
unfocusedContainerColor = Color.Transparent,
|
||||||
|
|
@ -147,7 +143,7 @@ private fun RoomListSearchResultContent(
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
if (filter.isNotEmpty()) {
|
if (filter.isNotEmpty()) {
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
state.eventSink(RoomListEvents.UpdateFilter(""))
|
state.eventSink(RoomListSearchEvents.ClearQuery)
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = CompoundIcons.Close(),
|
imageVector = CompoundIcons.Close(),
|
||||||
|
|
@ -158,8 +154,8 @@ private fun RoomListSearchResultContent(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
LaunchedEffect(state.displaySearchResults) {
|
LaunchedEffect(state.isSearchActive) {
|
||||||
if (state.displaySearchResults) {
|
if (state.isSearchActive) {
|
||||||
focusRequester.requestFocus()
|
focusRequester.requestFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -168,39 +164,16 @@ private fun RoomListSearchResultContent(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { padding ->
|
) { padding ->
|
||||||
val lazyListState = rememberLazyListState()
|
|
||||||
val visibleRange by remember {
|
|
||||||
derivedStateOf {
|
|
||||||
val layoutInfo = lazyListState.layoutInfo
|
|
||||||
val firstItemIndex = layoutInfo.visibleItemsInfo.firstOrNull()?.index ?: 0
|
|
||||||
val size = layoutInfo.visibleItemsInfo.size
|
|
||||||
firstItemIndex until firstItemIndex + size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val nestedScrollConnection = remember {
|
|
||||||
object : NestedScrollConnection {
|
|
||||||
override suspend fun onPostFling(
|
|
||||||
consumed: Velocity,
|
|
||||||
available: Velocity
|
|
||||||
): Velocity {
|
|
||||||
state.eventSink(RoomListEvents.UpdateVisibleRange(visibleRange))
|
|
||||||
return super.onPostFling(consumed, available)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
.consumeWindowInsets(padding)
|
.consumeWindowInsets(padding)
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier
|
modifier = Modifier.weight(1f),
|
||||||
.weight(1f)
|
|
||||||
.nestedScroll(nestedScrollConnection),
|
|
||||||
state = lazyListState,
|
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
items = state.filteredRoomList,
|
items = state.results,
|
||||||
contentType = { room -> room.contentType() },
|
contentType = { room -> room.contentType() },
|
||||||
) { room ->
|
) { room ->
|
||||||
RoomSummaryRow(
|
RoomSummaryRow(
|
||||||
|
|
@ -216,9 +189,9 @@ private fun RoomListSearchResultContent(
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun RoomListSearchResultContentPreview() = ElementPreview {
|
internal fun RoomListSearchResultContentPreview(@PreviewParameter(RoomListSearchStateProvider::class) state: RoomListSearchState) = ElementPreview {
|
||||||
RoomListSearchResultContent(
|
RoomListSearchContent(
|
||||||
state = aRoomListState(),
|
state = state,
|
||||||
onRoomClicked = {},
|
onRoomClicked = {},
|
||||||
onRoomLongClicked = {}
|
onRoomLongClicked = {}
|
||||||
)
|
)
|
||||||
|
|
@ -33,6 +33,9 @@ import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryF
|
||||||
import io.element.android.features.roomlist.impl.migration.InMemoryMigrationScreenStore
|
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.MigrationScreenPresenter
|
||||||
import io.element.android.features.roomlist.impl.model.createRoomListRoomSummary
|
import io.element.android.features.roomlist.impl.model.createRoomListRoomSummary
|
||||||
|
import io.element.android.features.roomlist.impl.search.RoomListSearchState
|
||||||
|
import io.element.android.features.roomlist.impl.search.aRoomListSearchState
|
||||||
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
|
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
|
||||||
import io.element.android.libraries.dateformatter.test.A_FORMATTED_DATE
|
import io.element.android.libraries.dateformatter.test.A_FORMATTED_DATE
|
||||||
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter
|
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter
|
||||||
|
|
@ -54,7 +57,6 @@ import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatu
|
||||||
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.AN_EXCEPTION
|
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_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
|
||||||
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.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
|
||||||
|
|
@ -77,6 +79,7 @@ import kotlinx.coroutines.test.TestScope
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
class RoomListPresenterTests {
|
class RoomListPresenterTests {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
|
|
@ -145,24 +148,6 @@ class RoomListPresenterTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - should filter room with success`() = runTest {
|
|
||||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
|
||||||
val presenter = createRoomListPresenter(coroutineScope = scope)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
skipItems(1)
|
|
||||||
val withUserState = awaitItem()
|
|
||||||
assertThat(withUserState.filter).isEqualTo("")
|
|
||||||
withUserState.eventSink.invoke(RoomListEvents.UpdateFilter("t"))
|
|
||||||
val withFilterState = awaitItem()
|
|
||||||
assertThat(withFilterState.filter).isEqualTo("t")
|
|
||||||
cancelAndIgnoreRemainingEvents()
|
|
||||||
scope.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - load 1 room with success`() = runTest {
|
fun `present - load 1 room with success`() = runTest {
|
||||||
val roomListService = FakeRoomListService()
|
val roomListService = FakeRoomListService()
|
||||||
|
|
@ -174,7 +159,7 @@ class RoomListPresenterTests {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = consumeItemsUntilPredicate { state -> state.roomList.dataOrNull()?.size == 16 }.last()
|
val initialState = consumeItemsUntilPredicate(timeout = 3.seconds) { state -> state.roomList.dataOrNull()?.size == 16 }.last()
|
||||||
// Room list is loaded with 16 placeholders
|
// Room list is loaded with 16 placeholders
|
||||||
val initialItems = initialState.roomList.dataOrNull().orEmpty()
|
val initialItems = initialState.roomList.dataOrNull().orEmpty()
|
||||||
assertThat(initialItems.size).isEqualTo(16)
|
assertThat(initialItems.size).isEqualTo(16)
|
||||||
|
|
@ -196,51 +181,7 @@ class RoomListPresenterTests {
|
||||||
numberOfUnreadMessages = 2,
|
numberOfUnreadMessages = 2,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
scope.cancel()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - load 1 room with success and filter rooms`() = runTest {
|
|
||||||
val roomListService = FakeRoomListService()
|
|
||||||
val matrixClient = FakeMatrixClient(
|
|
||||||
roomListService = roomListService
|
|
||||||
)
|
|
||||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
|
||||||
val presenter = createRoomListPresenter(client = matrixClient, coroutineScope = scope)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
roomListService.postAllRooms(
|
|
||||||
listOf(
|
|
||||||
aRoomSummaryFilled(
|
|
||||||
numUnreadMentions = 1,
|
|
||||||
numUnreadMessages = 2,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
skipItems(3)
|
|
||||||
val loadedState = awaitItem()
|
|
||||||
// Test filtering with result
|
|
||||||
assertThat(loadedState.roomList.dataOrNull().orEmpty().size).isEqualTo(1)
|
|
||||||
loadedState.eventSink.invoke(RoomListEvents.UpdateFilter(A_ROOM_NAME.substring(0, 3)))
|
|
||||||
skipItems(1)
|
|
||||||
val withFilteredRoomState = awaitItem()
|
|
||||||
assertThat(withFilteredRoomState.filteredRoomList.size).isEqualTo(1)
|
|
||||||
assertThat(withFilteredRoomState.filter).isEqualTo(A_ROOM_NAME.substring(0, 3))
|
|
||||||
assertThat(withFilteredRoomState.filteredRoomList.size).isEqualTo(1)
|
|
||||||
assertThat(withFilteredRoomState.filteredRoomList.first()).isEqualTo(
|
|
||||||
createRoomListRoomSummary(
|
|
||||||
numberOfUnreadMentions = 1,
|
|
||||||
numberOfUnreadMessages = 2,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
// Test filtering without result
|
|
||||||
withFilteredRoomState.eventSink.invoke(RoomListEvents.UpdateFilter("tada"))
|
|
||||||
skipItems(1)
|
|
||||||
val withNotFilteredRoomState = awaitItem()
|
|
||||||
assertThat(withNotFilteredRoomState.filter).isEqualTo("tada")
|
|
||||||
assertThat(withNotFilteredRoomState.filteredRoomList).isEmpty()
|
|
||||||
scope.cancel()
|
scope.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -515,7 +456,6 @@ class RoomListPresenterTests {
|
||||||
|
|
||||||
// The migration screen is not shown anymore
|
// The migration screen is not shown anymore
|
||||||
assertThat(awaitItem().displayMigrationStatus).isFalse()
|
assertThat(awaitItem().displayMigrationStatus).isFalse()
|
||||||
cancelAndIgnoreRemainingEvents()
|
|
||||||
scope.cancel()
|
scope.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -572,7 +512,8 @@ class RoomListPresenterTests {
|
||||||
migrationScreenPresenter: MigrationScreenPresenter = MigrationScreenPresenter(
|
migrationScreenPresenter: MigrationScreenPresenter = MigrationScreenPresenter(
|
||||||
matrixClient = client,
|
matrixClient = client,
|
||||||
migrationScreenStore = InMemoryMigrationScreenStore(),
|
migrationScreenStore = InMemoryMigrationScreenStore(),
|
||||||
)
|
),
|
||||||
|
searchPresenter: Presenter<RoomListSearchState> = Presenter { aRoomListSearchState() },
|
||||||
) = RoomListPresenter(
|
) = RoomListPresenter(
|
||||||
client = client,
|
client = client,
|
||||||
sessionVerificationService = sessionVerificationService,
|
sessionVerificationService = sessionVerificationService,
|
||||||
|
|
@ -598,6 +539,7 @@ class RoomListPresenterTests {
|
||||||
featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SecureStorage.key to true)),
|
featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SecureStorage.key to true)),
|
||||||
),
|
),
|
||||||
migrationScreenPresenter = migrationScreenPresenter,
|
migrationScreenPresenter = migrationScreenPresenter,
|
||||||
|
searchPresenter = searchPresenter,
|
||||||
sessionPreferencesStore = sessionPreferencesStore,
|
sessionPreferencesStore = sessionPreferencesStore,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
/*
|
||||||
|
* 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.search
|
||||||
|
|
||||||
|
import app.cash.molecule.RecompositionMode
|
||||||
|
import app.cash.molecule.moleculeFlow
|
||||||
|
import app.cash.turbine.test
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory
|
||||||
|
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter
|
||||||
|
import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
|
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 kotlinx.coroutines.test.TestScope
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class RoomListSearchPresenterTests {
|
||||||
|
@Test
|
||||||
|
fun `present - initial state`() = runTest {
|
||||||
|
val presenter = createRoomListSearchPresenter()
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
awaitItem().let { state ->
|
||||||
|
assertThat(state.isSearchActive).isFalse()
|
||||||
|
assertThat(state.query).isEmpty()
|
||||||
|
assertThat(state.results).isEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - toggle search visibility`() = runTest {
|
||||||
|
val presenter = createRoomListSearchPresenter()
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
awaitItem().let { state ->
|
||||||
|
assertThat(state.isSearchActive).isFalse()
|
||||||
|
state.eventSink(RoomListSearchEvents.ToggleSearchVisibility)
|
||||||
|
}
|
||||||
|
awaitItem().let { state ->
|
||||||
|
assertThat(state.isSearchActive).isTrue()
|
||||||
|
state.eventSink(RoomListSearchEvents.ToggleSearchVisibility)
|
||||||
|
}
|
||||||
|
awaitItem().let { state ->
|
||||||
|
assertThat(state.isSearchActive).isFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - query search changes`() = runTest {
|
||||||
|
val roomListService = FakeRoomListService()
|
||||||
|
val presenter = createRoomListSearchPresenter(roomListService)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
awaitItem().let { state ->
|
||||||
|
assertThat(
|
||||||
|
roomListService.allRooms.currentFilter.value
|
||||||
|
).isEqualTo(
|
||||||
|
RoomListFilter.all(
|
||||||
|
RoomListFilter.None,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
state.eventSink(RoomListSearchEvents.QueryChanged("Search"))
|
||||||
|
}
|
||||||
|
awaitItem().let { state ->
|
||||||
|
assertThat(state.query).isEqualTo("Search")
|
||||||
|
assertThat(
|
||||||
|
roomListService.allRooms.currentFilter.value
|
||||||
|
).isEqualTo(
|
||||||
|
RoomListFilter.all(
|
||||||
|
RoomListFilter.NonLeft,
|
||||||
|
RoomListFilter.NormalizedMatchRoomName("Search")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
state.eventSink(RoomListSearchEvents.ClearQuery)
|
||||||
|
}
|
||||||
|
awaitItem().let { state ->
|
||||||
|
assertThat(state.query).isEmpty()
|
||||||
|
assertThat(
|
||||||
|
roomListService.allRooms.currentFilter.value
|
||||||
|
).isEqualTo(
|
||||||
|
RoomListFilter.all(
|
||||||
|
RoomListFilter.None,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - room list changes`() = runTest {
|
||||||
|
val roomListService = FakeRoomListService()
|
||||||
|
val presenter = createRoomListSearchPresenter(roomListService)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
awaitItem().let { state ->
|
||||||
|
assertThat(state.results).isEmpty()
|
||||||
|
}
|
||||||
|
roomListService.postAllRooms(
|
||||||
|
listOf(
|
||||||
|
RoomSummary.Empty("1"),
|
||||||
|
aRoomSummaryFilled()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
awaitItem().let { state ->
|
||||||
|
assertThat(state.results).hasSize(1)
|
||||||
|
}
|
||||||
|
roomListService.postAllRooms(emptyList())
|
||||||
|
awaitItem().let { state ->
|
||||||
|
assertThat(state.results).isEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun TestScope.createRoomListSearchPresenter(
|
||||||
|
roomListService: RoomListService = FakeRoomListService(),
|
||||||
|
): RoomListSearchPresenter {
|
||||||
|
return RoomListSearchPresenter(
|
||||||
|
roomListService = roomListService,
|
||||||
|
roomSummaryFactory = RoomListRoomSummaryFactory(
|
||||||
|
lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(),
|
||||||
|
roomLastMessageFormatter = FakeRoomLastMessageFormatter(),
|
||||||
|
),
|
||||||
|
coroutineDispatchers = testCoroutineDispatchers(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -28,11 +28,26 @@ import kotlin.time.Duration
|
||||||
* Can be retrieved from [RoomListService] methods.
|
* Can be retrieved from [RoomListService] methods.
|
||||||
*/
|
*/
|
||||||
interface RoomList {
|
interface RoomList {
|
||||||
|
/**
|
||||||
|
* The loading state of the room list.
|
||||||
|
*/
|
||||||
sealed interface LoadingState {
|
sealed interface LoadingState {
|
||||||
data object NotLoaded : LoadingState
|
data object NotLoaded : LoadingState
|
||||||
data class Loaded(val numberOfRooms: Int) : LoadingState
|
data class Loaded(val numberOfRooms: Int) : LoadingState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The source of the room list data.
|
||||||
|
* All: all rooms except invites.
|
||||||
|
* Invites: only invites.
|
||||||
|
*
|
||||||
|
* To apply some dynamic filtering on top of that, use [DynamicRoomList].
|
||||||
|
*/
|
||||||
|
enum class Source {
|
||||||
|
All,
|
||||||
|
Invites,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of room summaries as a flow.
|
* The list of room summaries as a flow.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -18,38 +18,68 @@ package io.element.android.libraries.matrix.api.roomlist
|
||||||
|
|
||||||
sealed interface RoomListFilter {
|
sealed interface RoomListFilter {
|
||||||
companion object {
|
companion object {
|
||||||
|
/**
|
||||||
|
* Create a filter that matches all the given filters.
|
||||||
|
*/
|
||||||
fun all(vararg filters: RoomListFilter): RoomListFilter {
|
fun all(vararg filters: RoomListFilter): RoomListFilter {
|
||||||
return All(filters.toList())
|
return All(filters.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a filter that matches any of the given filters.
|
||||||
|
*/
|
||||||
fun any(vararg filters: RoomListFilter): RoomListFilter {
|
fun any(vararg filters: RoomListFilter): RoomListFilter {
|
||||||
return Any(filters.toList())
|
return Any(filters.toList())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filter that matches all the given filters.
|
||||||
|
*/
|
||||||
data class All(
|
data class All(
|
||||||
val filters: List<RoomListFilter>
|
val filters: List<RoomListFilter>
|
||||||
) : RoomListFilter
|
) : RoomListFilter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filter that matches any of the given filters.
|
||||||
|
*/
|
||||||
data class Any(
|
data class Any(
|
||||||
val filters: List<RoomListFilter>
|
val filters: List<RoomListFilter>
|
||||||
) : RoomListFilter
|
) : RoomListFilter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filter that matches rooms that are not left.
|
||||||
|
*/
|
||||||
data object NonLeft : RoomListFilter
|
data object NonLeft : RoomListFilter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filter that matches rooms that are unread.
|
||||||
|
*/
|
||||||
data object Unread : RoomListFilter
|
data object Unread : RoomListFilter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filter that matches either Group or People rooms.
|
||||||
|
*/
|
||||||
sealed interface Category : RoomListFilter {
|
sealed interface Category : RoomListFilter {
|
||||||
data object Group : Category
|
data object Group : Category
|
||||||
data object People : Category
|
data object People : Category
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filter that matches no room.
|
||||||
|
*/
|
||||||
data object None : RoomListFilter
|
data object None : RoomListFilter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filter that matches rooms with a name using a normalized match.
|
||||||
|
*/
|
||||||
data class NormalizedMatchRoomName(
|
data class NormalizedMatchRoomName(
|
||||||
val pattern: String
|
val pattern: String
|
||||||
) : RoomListFilter
|
) : RoomListFilter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filter that matches rooms with a name using a fuzzy match.
|
||||||
|
*/
|
||||||
data class FuzzyMatchRoomName(
|
data class FuzzyMatchRoomName(
|
||||||
val pattern: String
|
val pattern: String
|
||||||
) : RoomListFilter
|
) : RoomListFilter
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package io.element.android.libraries.matrix.api.roomlist
|
package io.element.android.libraries.matrix.api.roomlist
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -39,6 +40,20 @@ interface RoomListService {
|
||||||
data object Hide : SyncIndicator
|
data object Hide : SyncIndicator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a room list that can be used to load more rooms and filter them dynamically.
|
||||||
|
* @param coroutineScope the scope to use for the room list. When the scope will be closed, the room list will be closed too.
|
||||||
|
* @param pageSize the number of rooms to load at once.
|
||||||
|
* @param initialFilter the initial filter to apply to the rooms.
|
||||||
|
* @param source the source of the rooms, either all rooms or invites.
|
||||||
|
*/
|
||||||
|
fun createRoomList(
|
||||||
|
coroutineScope: CoroutineScope,
|
||||||
|
pageSize: Int,
|
||||||
|
initialFilter: RoomListFilter,
|
||||||
|
source: RoomList.Source,
|
||||||
|
): DynamicRoomList
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns a [DynamicRoomList] object of all rooms we want to display.
|
* returns a [DynamicRoomList] object of all rooms we want to display.
|
||||||
* This will exclude some rooms like the invites, or spaces.
|
* This will exclude some rooms like the invites, or spaces.
|
||||||
|
|
|
||||||
|
|
@ -197,10 +197,10 @@ class RustMatrixClient(
|
||||||
RustRoomListService(
|
RustRoomListService(
|
||||||
innerRoomListService = innerRoomListService,
|
innerRoomListService = innerRoomListService,
|
||||||
sessionCoroutineScope = sessionCoroutineScope,
|
sessionCoroutineScope = sessionCoroutineScope,
|
||||||
|
sessionDispatcher = sessionDispatcher,
|
||||||
roomListFactory = RoomListFactory(
|
roomListFactory = RoomListFactory(
|
||||||
innerRoomListService = innerRoomListService,
|
innerRoomListService = innerRoomListService,
|
||||||
coroutineScope = sessionCoroutineScope,
|
sessionCoroutineScope = sessionCoroutineScope,
|
||||||
dispatcher = sessionDispatcher,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import io.element.android.libraries.matrix.api.roomlist.DynamicRoomList
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
@ -30,13 +29,14 @@ import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.rustcomponents.sdk.RoomListLoadingState
|
import org.matrix.rustcomponents.sdk.RoomListLoadingState
|
||||||
|
import org.matrix.rustcomponents.sdk.RoomListService
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
import org.matrix.rustcomponents.sdk.RoomList as InnerRoomList
|
import org.matrix.rustcomponents.sdk.RoomList as InnerRoomList
|
||||||
import org.matrix.rustcomponents.sdk.RoomListService as InnerRoomListService
|
|
||||||
|
|
||||||
internal class RoomListFactory(
|
internal class RoomListFactory(
|
||||||
private val innerRoomListService: InnerRoomListService,
|
private val innerRoomListService: RoomListService,
|
||||||
private val coroutineScope: CoroutineScope,
|
private val sessionCoroutineScope: CoroutineScope,
|
||||||
private val dispatcher: CoroutineDispatcher,
|
|
||||||
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
|
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
|
|
@ -44,18 +44,21 @@ internal class RoomListFactory(
|
||||||
*/
|
*/
|
||||||
fun createRoomList(
|
fun createRoomList(
|
||||||
pageSize: Int,
|
pageSize: Int,
|
||||||
|
coroutineScope: CoroutineScope = sessionCoroutineScope,
|
||||||
|
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
||||||
initialFilter: RoomListFilter = RoomListFilter.all(),
|
initialFilter: RoomListFilter = RoomListFilter.all(),
|
||||||
innerProvider: suspend () -> InnerRoomList
|
innerProvider: suspend () -> InnerRoomList
|
||||||
): DynamicRoomList {
|
): DynamicRoomList {
|
||||||
val loadingStateFlow: MutableStateFlow<RoomList.LoadingState> = MutableStateFlow(RoomList.LoadingState.NotLoaded)
|
val loadingStateFlow: MutableStateFlow<RoomList.LoadingState> = MutableStateFlow(RoomList.LoadingState.NotLoaded)
|
||||||
val summariesFlow = MutableStateFlow<List<RoomSummary>>(emptyList())
|
val summariesFlow = MutableStateFlow<List<RoomSummary>>(emptyList())
|
||||||
val processor = RoomSummaryListProcessor(summariesFlow, innerRoomListService, dispatcher, roomSummaryDetailsFactory)
|
val processor = RoomSummaryListProcessor(summariesFlow, innerRoomListService, coroutineContext, roomSummaryDetailsFactory)
|
||||||
// Makes sure we don't miss any events
|
// Makes sure we don't miss any events
|
||||||
val dynamicEvents = MutableSharedFlow<RoomListDynamicEvents>(replay = 100)
|
val dynamicEvents = MutableSharedFlow<RoomListDynamicEvents>(replay = 100)
|
||||||
val currentFilter = MutableStateFlow(initialFilter)
|
val currentFilter = MutableStateFlow(initialFilter)
|
||||||
val loadedPages = MutableStateFlow(1)
|
val loadedPages = MutableStateFlow(1)
|
||||||
var innerRoomList: InnerRoomList? = null
|
var innerRoomList: InnerRoomList? = null
|
||||||
coroutineScope.launch(dispatcher) {
|
|
||||||
|
coroutineScope.launch(coroutineContext) {
|
||||||
innerRoomList = innerProvider()
|
innerRoomList = innerProvider()
|
||||||
innerRoomList?.let { innerRoomList ->
|
innerRoomList?.let { innerRoomList ->
|
||||||
innerRoomList.entriesFlow(
|
innerRoomList.entriesFlow(
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
package io.element.android.libraries.matrix.impl.roomlist
|
package io.element.android.libraries.matrix.impl.roomlist
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
@ -28,11 +27,12 @@ import org.matrix.rustcomponents.sdk.RoomListServiceInterface
|
||||||
import org.matrix.rustcomponents.sdk.use
|
import org.matrix.rustcomponents.sdk.use
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
class RoomSummaryListProcessor(
|
class RoomSummaryListProcessor(
|
||||||
private val roomSummaries: MutableStateFlow<List<RoomSummary>>,
|
private val roomSummaries: MutableStateFlow<List<RoomSummary>>,
|
||||||
private val roomListService: RoomListServiceInterface,
|
private val roomListService: RoomListServiceInterface,
|
||||||
private val dispatcher: CoroutineDispatcher,
|
private val coroutineContext: CoroutineContext,
|
||||||
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
|
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
|
||||||
) {
|
) {
|
||||||
private val roomSummariesByIdentifier = HashMap<String, RoomSummary>()
|
private val roomSummariesByIdentifier = HashMap<String, RoomSummary>()
|
||||||
|
|
@ -130,7 +130,7 @@ class RoomSummaryListProcessor(
|
||||||
return builtRoomSummary
|
return builtRoomSummary
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateRoomSummaries(block: suspend MutableList<RoomSummary>.() -> Unit) = withContext(dispatcher) {
|
private suspend fun updateRoomSummaries(block: suspend MutableList<RoomSummary>.() -> Unit) = withContext(coroutineContext) {
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
val mutableRoomSummaries = roomSummaries.value.toMutableList()
|
val mutableRoomSummaries = roomSummaries.value.toMutableList()
|
||||||
block(mutableRoomSummaries)
|
block(mutableRoomSummaries)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.matrix.api.roomlist.loadAllIncrementally
|
import io.element.android.libraries.matrix.api.roomlist.loadAllIncrementally
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
@ -42,10 +43,31 @@ private const val DEFAULT_PAGE_SIZE = 20
|
||||||
internal class RustRoomListService(
|
internal class RustRoomListService(
|
||||||
private val innerRoomListService: InnerRustRoomListService,
|
private val innerRoomListService: InnerRustRoomListService,
|
||||||
private val sessionCoroutineScope: CoroutineScope,
|
private val sessionCoroutineScope: CoroutineScope,
|
||||||
roomListFactory: RoomListFactory,
|
private val sessionDispatcher: CoroutineDispatcher,
|
||||||
|
private val roomListFactory: RoomListFactory,
|
||||||
) : RoomListService {
|
) : RoomListService {
|
||||||
|
override fun createRoomList(
|
||||||
|
coroutineScope: CoroutineScope,
|
||||||
|
pageSize: Int,
|
||||||
|
initialFilter: RoomListFilter,
|
||||||
|
source: RoomList.Source
|
||||||
|
): DynamicRoomList {
|
||||||
|
return roomListFactory.createRoomList(
|
||||||
|
pageSize = pageSize,
|
||||||
|
initialFilter = initialFilter,
|
||||||
|
coroutineScope = coroutineScope,
|
||||||
|
coroutineContext = sessionDispatcher,
|
||||||
|
) {
|
||||||
|
when (source) {
|
||||||
|
RoomList.Source.All -> innerRoomListService.allRooms()
|
||||||
|
RoomList.Source.Invites -> innerRoomListService.invites()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override val allRooms: DynamicRoomList = roomListFactory.createRoomList(
|
override val allRooms: DynamicRoomList = roomListFactory.createRoomList(
|
||||||
pageSize = DEFAULT_PAGE_SIZE,
|
pageSize = DEFAULT_PAGE_SIZE,
|
||||||
|
coroutineContext = sessionDispatcher,
|
||||||
initialFilter = RoomListFilter.all(RoomListFilter.NonLeft),
|
initialFilter = RoomListFilter.all(RoomListFilter.NonLeft),
|
||||||
) {
|
) {
|
||||||
innerRoomListService.allRooms()
|
innerRoomListService.allRooms()
|
||||||
|
|
@ -53,6 +75,7 @@ internal class RustRoomListService(
|
||||||
|
|
||||||
override val invites: RoomList = roomListFactory.createRoomList(
|
override val invites: RoomList = roomListFactory.createRoomList(
|
||||||
pageSize = Int.MAX_VALUE,
|
pageSize = Int.MAX_VALUE,
|
||||||
|
coroutineContext = sessionDispatcher,
|
||||||
) {
|
) {
|
||||||
innerRoomListService.invites()
|
innerRoomListService.invites()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ class RoomSummaryListProcessorTests {
|
||||||
private fun TestScope.createProcessor() = RoomSummaryListProcessor(
|
private fun TestScope.createProcessor() = RoomSummaryListProcessor(
|
||||||
summaries,
|
summaries,
|
||||||
fakeRoomListService,
|
fakeRoomListService,
|
||||||
dispatcher = StandardTestDispatcher(testScheduler),
|
coroutineContext = StandardTestDispatcher(testScheduler),
|
||||||
roomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
|
roomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,10 @@ fun aRoomSummaryFilled(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun aRoomSummaryFilled(
|
||||||
|
details: RoomSummaryDetails = aRoomSummaryDetails(),
|
||||||
|
) = RoomSummary.Filled(details)
|
||||||
|
|
||||||
fun aRoomSummaryDetails(
|
fun aRoomSummaryDetails(
|
||||||
roomId: RoomId = A_ROOM_ID,
|
roomId: RoomId = A_ROOM_ID,
|
||||||
name: String = A_ROOM_NAME,
|
name: String = A_ROOM_NAME,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
|
@ -59,13 +60,20 @@ class FakeRoomListService : RoomListService {
|
||||||
var latestSlidingSyncRange: IntRange? = null
|
var latestSlidingSyncRange: IntRange? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
override val allRooms: DynamicRoomList = SimplePagedRoomList(
|
override fun createRoomList(coroutineScope: CoroutineScope, pageSize: Int, initialFilter: RoomListFilter, source: RoomList.Source): DynamicRoomList {
|
||||||
|
return when (source) {
|
||||||
|
RoomList.Source.All -> allRooms
|
||||||
|
RoomList.Source.Invites -> invites
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val allRooms = SimplePagedRoomList(
|
||||||
allRoomSummariesFlow,
|
allRoomSummariesFlow,
|
||||||
allRoomsLoadingStateFlow,
|
allRoomsLoadingStateFlow,
|
||||||
MutableStateFlow(RoomListFilter.all())
|
MutableStateFlow(RoomListFilter.all())
|
||||||
)
|
)
|
||||||
|
|
||||||
override val invites: RoomList = SimplePagedRoomList(
|
override val invites = SimplePagedRoomList(
|
||||||
inviteRoomSummariesFlow,
|
inviteRoomSummariesFlow,
|
||||||
inviteRoomsLoadingStateFlow,
|
inviteRoomsLoadingStateFlow,
|
||||||
MutableStateFlow(RoomListFilter.all())
|
MutableStateFlow(RoomListFilter.all())
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.getAndUpdate
|
import kotlinx.coroutines.flow.getAndUpdate
|
||||||
|
|
||||||
data class SimplePagedRoomList(
|
data class SimplePagedRoomList(
|
||||||
override val summaries: StateFlow<List<RoomSummary>>,
|
override val summaries: MutableStateFlow<List<RoomSummary>>,
|
||||||
override val loadingState: StateFlow<RoomList.LoadingState>,
|
override val loadingState: StateFlow<RoomList.LoadingState>,
|
||||||
override val currentFilter: MutableStateFlow<RoomListFilter>
|
override val currentFilter: MutableStateFlow<RoomListFilter>
|
||||||
) : DynamicRoomList {
|
) : DynamicRoomList {
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,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.datasource.RoomListRoomSummaryFactory
|
||||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter
|
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.migration.SharedPrefsMigrationScreenStore
|
||||||
|
import io.element.android.features.roomlist.impl.search.RoomListSearchPresenter
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.dateformatter.impl.DateFormatters
|
import io.element.android.libraries.dateformatter.impl.DateFormatters
|
||||||
import io.element.android.libraries.dateformatter.impl.DefaultLastMessageTimestampFormatter
|
import io.element.android.libraries.dateformatter.impl.DefaultLastMessageTimestampFormatter
|
||||||
|
|
@ -71,6 +72,21 @@ class RoomListScreen(
|
||||||
private val featureFlagService = DefaultFeatureFlagService(
|
private val featureFlagService = DefaultFeatureFlagService(
|
||||||
providers = setOf(StaticFeatureFlagProvider())
|
providers = setOf(StaticFeatureFlagProvider())
|
||||||
)
|
)
|
||||||
|
private val roomListRoomSummaryFactory = RoomListRoomSummaryFactory(
|
||||||
|
lastMessageTimestampFormatter = DefaultLastMessageTimestampFormatter(
|
||||||
|
localDateTimeProvider = dateTimeProvider,
|
||||||
|
dateFormatters = dateFormatters
|
||||||
|
),
|
||||||
|
roomLastMessageFormatter = DefaultRoomLastMessageFormatter(
|
||||||
|
sp = stringProvider,
|
||||||
|
roomMembershipContentFormatter = RoomMembershipContentFormatter(
|
||||||
|
matrixClient = matrixClient,
|
||||||
|
sp = stringProvider
|
||||||
|
),
|
||||||
|
profileChangeContentFormatter = ProfileChangeContentFormatter(stringProvider),
|
||||||
|
stateContentFormatter = StateContentFormatter(stringProvider),
|
||||||
|
),
|
||||||
|
)
|
||||||
private val presenter = RoomListPresenter(
|
private val presenter = RoomListPresenter(
|
||||||
client = matrixClient,
|
client = matrixClient,
|
||||||
sessionVerificationService = sessionVerificationService,
|
sessionVerificationService = sessionVerificationService,
|
||||||
|
|
@ -80,21 +96,7 @@ class RoomListScreen(
|
||||||
leaveRoomPresenter = LeaveRoomPresenterImpl(matrixClient, RoomMembershipObserver(), coroutineDispatchers),
|
leaveRoomPresenter = LeaveRoomPresenterImpl(matrixClient, RoomMembershipObserver(), coroutineDispatchers),
|
||||||
roomListDataSource = RoomListDataSource(
|
roomListDataSource = RoomListDataSource(
|
||||||
roomListService = matrixClient.roomListService,
|
roomListService = matrixClient.roomListService,
|
||||||
roomListRoomSummaryFactory = RoomListRoomSummaryFactory(
|
roomListRoomSummaryFactory = roomListRoomSummaryFactory,
|
||||||
lastMessageTimestampFormatter = DefaultLastMessageTimestampFormatter(
|
|
||||||
localDateTimeProvider = dateTimeProvider,
|
|
||||||
dateFormatters = dateFormatters
|
|
||||||
),
|
|
||||||
roomLastMessageFormatter = DefaultRoomLastMessageFormatter(
|
|
||||||
sp = stringProvider,
|
|
||||||
roomMembershipContentFormatter = RoomMembershipContentFormatter(
|
|
||||||
matrixClient = matrixClient,
|
|
||||||
sp = stringProvider
|
|
||||||
),
|
|
||||||
profileChangeContentFormatter = ProfileChangeContentFormatter(stringProvider),
|
|
||||||
stateContentFormatter = StateContentFormatter(stringProvider),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
coroutineDispatchers = coroutineDispatchers,
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
notificationSettingsService = matrixClient.notificationSettingsService(),
|
notificationSettingsService = matrixClient.notificationSettingsService(),
|
||||||
appScope = Singleton.appScope
|
appScope = Singleton.appScope
|
||||||
|
|
@ -110,6 +112,11 @@ class RoomListScreen(
|
||||||
matrixClient = matrixClient,
|
matrixClient = matrixClient,
|
||||||
migrationScreenStore = SharedPrefsMigrationScreenStore(context.getSharedPreferences("migration", Context.MODE_PRIVATE))
|
migrationScreenStore = SharedPrefsMigrationScreenStore(context.getSharedPreferences("migration", Context.MODE_PRIVATE))
|
||||||
),
|
),
|
||||||
|
searchPresenter = RoomListSearchPresenter(
|
||||||
|
roomListService = matrixClient.roomListService,
|
||||||
|
roomSummaryFactory = roomListRoomSummaryFactory,
|
||||||
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
|
),
|
||||||
sessionPreferencesStore = DefaultSessionPreferencesStore(
|
sessionPreferencesStore = DefaultSessionPreferencesStore(
|
||||||
context = context,
|
context = context,
|
||||||
sessionId = matrixClient.sessionId,
|
sessionId = matrixClient.sessionId,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:b2dc223ad7ca556a74aaff1e0ec6a94c10b951457c7b9614804a4c477c4e6e8e
|
|
||||||
size 29974
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:7f313e09baa93bb4ff9eda9a3126b2cf49867fac3c1d53f11ec027b4dad7fbff
|
||||||
|
size 4909
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:1625ac34428f660c235d1d64f5de867baa6c0ca296f0a93d81588d633d6a74bf
|
||||||
|
size 30082
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:199f1cf052e17b08810d026921b53ee35b1a942f9b661beb2286e6891843898c
|
|
||||||
size 29867
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:6e5a9aba28b5a7dfa41b8ca47e74ebe629f717d9bd51836d71c31adb436083af
|
||||||
|
size 4861
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:264d9373767b6b59d0cea4bfa4d148a453058be70e7805581fe96d7448ef5232
|
||||||
|
size 29978
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:5f8802121f779f48dd556a9a3a08e6ac1a63e86d5695f6f8c133fbb346d76ef6
|
oid sha256:13dadbd502163a9bfe81cb1e67f9aa0933c4b7f4fd3c8f731e930a28709485d2
|
||||||
size 89779
|
size 51948
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:2523a28a889fd1739fe1e0c94edce5d86a5ebcbe6c3973a3c49fbb8fb8a19f79
|
oid sha256:64c4eb481f40871925405ae317cb80927caf31cb552a8aa9549bfb5658ca91e4
|
||||||
size 55617
|
size 137589
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:13dadbd502163a9bfe81cb1e67f9aa0933c4b7f4fd3c8f731e930a28709485d2
|
oid sha256:af42c9891a6670cdde07cf054e139ce1f83e877bb68f60463fcad6dd28d8e049
|
||||||
size 51948
|
size 6867
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:64c4eb481f40871925405ae317cb80927caf31cb552a8aa9549bfb5658ca91e4
|
|
||||||
size 137589
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:7f313e09baa93bb4ff9eda9a3126b2cf49867fac3c1d53f11ec027b4dad7fbff
|
oid sha256:3764d8bd7dc2783a8af43aad65a217d7e533ed17c4d4367b7994470bf35b62b0
|
||||||
size 4909
|
size 4462
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:b2dc223ad7ca556a74aaff1e0ec6a94c10b951457c7b9614804a4c477c4e6e8e
|
oid sha256:3764d8bd7dc2783a8af43aad65a217d7e533ed17c4d4367b7994470bf35b62b0
|
||||||
size 29974
|
size 4462
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3764d8bd7dc2783a8af43aad65a217d7e533ed17c4d4367b7994470bf35b62b0
|
oid sha256:5f8802121f779f48dd556a9a3a08e6ac1a63e86d5695f6f8c133fbb346d76ef6
|
||||||
size 4462
|
size 89779
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3764d8bd7dc2783a8af43aad65a217d7e533ed17c4d4367b7994470bf35b62b0
|
oid sha256:2523a28a889fd1739fe1e0c94edce5d86a5ebcbe6c3973a3c49fbb8fb8a19f79
|
||||||
size 4462
|
size 55617
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:7cdd93c157565f7f8d3fa23a43c30e02531dab40578a315c5d0d10a99e74f259
|
oid sha256:0a00391ef16a762a14dd7e348ce97b500ca1b42a32ee3ff926a10865f46cd06c
|
||||||
size 91394
|
size 53590
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:2b25e023153dd094299a0c794d5e488bdc6fc0c478abbfbce7337e52b233715d
|
oid sha256:a7bf47b0a25c455b108d7b3585a42849f03c6652af73a175fe2147cb1ad62a66
|
||||||
size 57463
|
size 161125
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:0a00391ef16a762a14dd7e348ce97b500ca1b42a32ee3ff926a10865f46cd06c
|
oid sha256:6e95dac6e75d3f615ccf4d8e98f1780d8aeace1cddf0b33bba8b485860d3216b
|
||||||
size 53590
|
size 6688
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:a7bf47b0a25c455b108d7b3585a42849f03c6652af73a175fe2147cb1ad62a66
|
|
||||||
size 161125
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6e5a9aba28b5a7dfa41b8ca47e74ebe629f717d9bd51836d71c31adb436083af
|
oid sha256:3764d8bd7dc2783a8af43aad65a217d7e533ed17c4d4367b7994470bf35b62b0
|
||||||
size 4861
|
size 4462
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:199f1cf052e17b08810d026921b53ee35b1a942f9b661beb2286e6891843898c
|
oid sha256:3764d8bd7dc2783a8af43aad65a217d7e533ed17c4d4367b7994470bf35b62b0
|
||||||
size 29867
|
size 4462
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3764d8bd7dc2783a8af43aad65a217d7e533ed17c4d4367b7994470bf35b62b0
|
oid sha256:7cdd93c157565f7f8d3fa23a43c30e02531dab40578a315c5d0d10a99e74f259
|
||||||
size 4462
|
size 91394
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3764d8bd7dc2783a8af43aad65a217d7e533ed17c4d4367b7994470bf35b62b0
|
oid sha256:2b25e023153dd094299a0c794d5e488bdc6fc0c478abbfbce7337e52b233715d
|
||||||
size 4462
|
size 57463
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue