diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt index 5ab2dfe6eb..78300287bf 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt @@ -21,73 +21,98 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState +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.roomdirectory.impl.root.datasource.RoomDirectoryDataSource +import io.element.android.features.roomdirectory.impl.root.model.RoomDescriptionUiModel +import io.element.android.features.roomdirectory.impl.root.model.toUiModel import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.MatrixClient -import io.element.android.libraries.matrix.api.core.RoomId -import kotlinx.coroutines.CoroutineScope +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject class RoomDirectoryPresenter @Inject constructor( private val client: MatrixClient, - private val dataSource: RoomDirectoryDataSource, + private val roomDirectoryService: RoomDirectoryService, ) : Presenter { + private val roomDirectoryList = roomDirectoryService.createRoomDirectoryList() + @Composable override fun present(): RoomDirectoryState { var searchQuery by rememberSaveable { mutableStateOf("") } - var isSearchActive by rememberSaveable { - mutableStateOf(false) + val allRooms by roomDirectoryList.collectItemsAsState() + val hasMoreToLoad by produceState(initialValue = true, allRooms) { + value = roomDirectoryList.hasMoreToLoad() } - - val roomSummaries by dataSource.all.collectAsState() - val coroutineScope = rememberCoroutineScope() - LaunchedEffect(searchQuery) { - dataSource.updateSearchQuery(searchQuery) + roomDirectoryList.filter(searchQuery, 20) } - fun handleEvents(event: RoomDirectoryEvents) { when (event) { is RoomDirectoryEvents.JoinRoom -> { - coroutineScope.joinRoom(event.roomId) + //coroutineScope.joinRoom(event.roomId) } RoomDirectoryEvents.LoadMore -> { coroutineScope.launch { - dataSource.loadMore() + roomDirectoryList.loadMore() } } is RoomDirectoryEvents.Search -> { searchQuery = event.query } is RoomDirectoryEvents.SearchActiveChange -> { - isSearchActive = event.isActive - if (!isSearchActive) { - searchQuery = "" - } + } } } return RoomDirectoryState( query = searchQuery, - isSearchActive = isSearchActive, - roomSummaries = roomSummaries, - searchResults = SearchBarResultState.Initial(), + roomDescriptions = allRooms, + displayLoadMoreIndicator = hasMoreToLoad, eventSink = ::handleEvents ) } - private fun CoroutineScope.joinRoom(roomId: RoomId) = launch { - client.getRoom(roomId)?.join() + @Composable + private fun searchResults( + filteredRooms: ImmutableList, + hasMoreToLoad: Boolean, + isSearchActive: Boolean, + ): SearchBarResultState> { + return if (!isSearchActive) { + SearchBarResultState.Initial() + } else { + if (filteredRooms.isEmpty() && !hasMoreToLoad) { + SearchBarResultState.NoResultsFound() + } else { + SearchBarResultState.Results(filteredRooms) + } + } } + + @Composable + private fun RoomDirectoryList.collectItemsAsState() = remember { + items.map { list -> + list + .map { roomDescription -> roomDescription.toUiModel() } + .toImmutableList() + }.flowOn(Dispatchers.Default) + }.collectAsState(persistentListOf()) } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt index 3abf725a24..1afd53f146 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt @@ -16,14 +16,14 @@ package io.element.android.features.roomdirectory.impl.root -import io.element.android.features.roomdirectory.impl.root.model.RoomDirectoryRoomSummary -import io.element.android.libraries.designsystem.theme.components.SearchBarResultState +import io.element.android.features.roomdirectory.impl.root.model.RoomDescriptionUiModel import kotlinx.collections.immutable.ImmutableList data class RoomDirectoryState( val query: String, - val roomSummaries: ImmutableList, - val searchResults: SearchBarResultState>, - val isSearchActive: Boolean, + val roomDescriptions: ImmutableList, + val displayLoadMoreIndicator: Boolean, val eventSink: (RoomDirectoryEvents) -> Unit -) +) { + val displayEmptyState = roomDescriptions.isEmpty() && !displayLoadMoreIndicator +} diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt index a9d1f2287f..30acb14dba 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt @@ -17,7 +17,7 @@ package io.element.android.features.roomdirectory.impl.root import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.roomdirectory.impl.root.model.RoomDirectoryRoomSummary +import io.element.android.features.roomdirectory.impl.root.model.RoomDescriptionUiModel import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.theme.components.SearchBarResultState @@ -31,8 +31,8 @@ open class RoomDirectorySearchStateProvider : PreviewParameterProvider = persistentListOf(), - searchResults: SearchBarResultState> = SearchBarResultState.Initial(), + displayLoadMoreIndicator: Boolean = false, + roomDescriptions: ImmutableList = persistentListOf(), + searchResults: SearchBarResultState> = SearchBarResultState.Initial(), ) = RoomDirectoryState( query = query, - isSearchActive = isSearchActive, - roomSummaries = roomSummaries, - searchResults = searchResults, + roomDescriptions = roomDescriptions, + displayLoadMoreIndicator = displayLoadMoreIndicator, eventSink = {}, ) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt index b79e4cfd20..0bff02cdc2 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt @@ -17,37 +17,33 @@ package io.element.android.features.roomdirectory.impl.root import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.TextFieldColors import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons -import io.element.android.features.roomdirectory.impl.root.model.RoomDirectoryRoomSummary +import io.element.android.features.roomdirectory.impl.root.model.RoomDescriptionUiModel import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview @@ -56,7 +52,6 @@ import io.element.android.libraries.designsystem.theme.aliasScreenTitle import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.Scaffold -import io.element.android.libraries.designsystem.theme.components.SearchBar import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextField import io.element.android.libraries.designsystem.theme.components.TopAppBar @@ -109,71 +104,107 @@ private fun RoomDirectoryTopBar( ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable private fun RoomDirectoryContent( state: RoomDirectoryState, onResultClicked: (RoomId) -> Unit, modifier: Modifier = Modifier, ) { - Column( - modifier = modifier, - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - SearchBar( + Column(modifier = modifier) { + SearchTextField( query = state.query, - onQueryChange = { query -> - state.eventSink(RoomDirectoryEvents.Search(query)) - }, - active = state.isSearchActive, - onActiveChange = { - state.eventSink(RoomDirectoryEvents.SearchActiveChange(it)) - }, - resultState = state.searchResults, - placeHolderTitle = stringResource(id = CommonStrings.action_search), - ) { results -> - RoomDirectoryRoomList( - rooms = results, - onResultClicked = onResultClicked, - ) - } - if (!state.isSearchActive) { - RoomDirectoryRoomList( - rooms = state.roomSummaries, - onResultClicked = onResultClicked, - ) - } + onQueryChange = { state.eventSink(RoomDirectoryEvents.Search(it)) }, + placeholder = stringResource(id = CommonStrings.action_search), + modifier = Modifier.fillMaxWidth(), + ) + RoomDirectoryRoomList( + roomDescriptions = state.roomDescriptions, + onResultClicked = onResultClicked, + ) } } @Composable private fun RoomDirectoryRoomList( - rooms: ImmutableList, + roomDescriptions: ImmutableList, onResultClicked: (RoomId) -> Unit, modifier: Modifier = Modifier, ) { LazyColumn( modifier = modifier, ) { - items(rooms) { room -> + items(roomDescriptions) { roomDescription -> RoomDirectoryRoomRow( - room = room, + roomDescription = roomDescription, onClick = onResultClicked, ) } } } +@Composable +private fun SearchTextField( + query: String, + onQueryChange: (String) -> Unit, + placeholder: String, + modifier: Modifier = Modifier, + colors: TextFieldColors = TextFieldDefaults.colors( + focusedContainerColor = Color.Transparent, + unfocusedContainerColor = Color.Transparent, + unfocusedPlaceholderColor = ElementTheme.colors.textPlaceholder, + focusedPlaceholderColor = ElementTheme.colors.textPlaceholder, + focusedTextColor = ElementTheme.colors.textPrimary, + unfocusedTextColor = ElementTheme.colors.textPrimary, + focusedIndicatorColor = ElementTheme.colors.borderInteractiveSecondary, + unfocusedIndicatorColor = ElementTheme.colors.borderInteractiveSecondary, + ), +) { + val focusManager = LocalFocusManager.current + TextField( + modifier = modifier, + textStyle = ElementTheme.typography.fontBodyLgRegular, + singleLine = true, + value = query, + onValueChange = onQueryChange, + keyboardActions = KeyboardActions( + onSearch = { + focusManager.clearFocus() + } + ), + colors = colors, + placeholder = { Text(placeholder) }, + trailingIcon = { + if (query.isNotEmpty()) { + IconButton( + onClick = { + onQueryChange("") + } + ) { + Icon( + imageVector = CompoundIcons.Close(), + contentDescription = stringResource(CommonStrings.action_clear), + ) + } + } else { + Icon( + imageVector = CompoundIcons.Search(), + contentDescription = stringResource(CommonStrings.action_search), + ) + } + }, + ) +} + @Composable private fun RoomDirectoryRoomRow( - room: RoomDirectoryRoomSummary, + roomDescription: RoomDescriptionUiModel, onClick: (RoomId) -> Unit, modifier: Modifier = Modifier, ) { Row( modifier = modifier .fillMaxWidth() - .clickable { onClick(room.roomId) } + .clickable { onClick(roomDescription.roomId) } .padding( top = 12.dp, bottom = 12.dp, @@ -182,7 +213,7 @@ private fun RoomDirectoryRoomRow( .height(IntrinsicSize.Min), ) { Avatar( - avatarData = room.avatarData, + avatarData = roomDescription.avatarData, modifier = Modifier.align(Alignment.CenterVertically) ) Column( @@ -191,21 +222,21 @@ private fun RoomDirectoryRoomRow( .padding(start = 16.dp) ) { Text( - text = room.name, + text = roomDescription.name, maxLines = 1, style = ElementTheme.typography.fontBodyLgRegular, color = ElementTheme.colors.textPrimary, overflow = TextOverflow.Ellipsis, ) Text( - text = room.description, + text = roomDescription.description, maxLines = 1, style = ElementTheme.typography.fontBodyMdRegular, color = ElementTheme.colors.textSecondary, overflow = TextOverflow.Ellipsis, ) } - if (room.canBeJoined) { + if (roomDescription.canBeJoined) { Text( text = stringResource(id = CommonStrings.action_join), color = ElementTheme.colors.textSuccessPrimary, @@ -219,64 +250,6 @@ private fun RoomDirectoryRoomRow( } } -@Composable -@OptIn(ExperimentalMaterial3Api::class) -private fun RoomDirectorySearchTopBar( - query: String, - onQueryChanged: (String) -> Unit, - onBackPressed: () -> Unit, - modifier: Modifier = Modifier, -) { - val borderColor = ElementTheme.colors.borderInteractivePrimary - val borderStroke = 1.dp - TopAppBar( - modifier = modifier.drawBehind { - drawLine( - color = borderColor, - start = Offset(0f, size.height), - end = Offset(size.width, size.height), - strokeWidth = borderStroke.value - ) - }, - title = { - val focusRequester = FocusRequester() - TextField( - modifier = Modifier - .fillMaxWidth() - .focusRequester(focusRequester), - value = query, - singleLine = true, - onValueChange = onQueryChanged, - colors = TextFieldDefaults.colors( - focusedContainerColor = Color.Transparent, - unfocusedContainerColor = Color.Transparent, - disabledContainerColor = Color.Transparent, - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - disabledIndicatorColor = Color.Transparent, - errorIndicatorColor = Color.Transparent, - ), - trailingIcon = { - if (query.isNotEmpty()) { - IconButton(onClick = { - onQueryChanged("") - }) { - Icon( - imageVector = CompoundIcons.Close(), - contentDescription = stringResource(CommonStrings.action_cancel), - ) - } - } - } - ) - LaunchedEffect(Unit) { - focusRequester.requestFocus() - } - }, - navigationIcon = { BackButton(onClick = onBackPressed) }, - ) -} - @PreviewsDayNight @Composable fun RoomDirectorySearchViewLightPreview(@PreviewParameter(RoomDirectorySearchStateProvider::class) state: RoomDirectoryState) = ElementPreview { diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/datasource/RoomDirectoryDataSource.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/datasource/RoomDirectoryDataSource.kt deleted file mode 100644 index 2441c694ee..0000000000 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/datasource/RoomDirectoryDataSource.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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.roomdirectory.impl.root.datasource - -import io.element.android.features.roomdirectory.impl.root.model.RoomDirectoryRoomSummary -import io.element.android.libraries.designsystem.components.avatar.AvatarData -import io.element.android.libraries.designsystem.components.avatar.AvatarSize -import io.element.android.libraries.matrix.api.core.RoomId -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.flowOf -import javax.inject.Inject - -class RoomDirectoryDataSource @Inject constructor() { - private val _searchResults = MutableStateFlow>(persistentListOf()) - - suspend fun updateSearchQuery(searchQuery: String) { - //TODO branch to matrix sdk - if (searchQuery.isEmpty()) { - _searchResults.value = persistentListOf() - } else { - delay(100) - _searchResults.value = all.value.filter { - it.name.contains(searchQuery) - }.toImmutableList() - } - } - - suspend fun loadMore() { - //TODO branch to matrix sdk - } - - - val all: StateFlow> = MutableStateFlow( - persistentListOf( - RoomDirectoryRoomSummary( - roomId = RoomId("!exa:matrix.org"), - name = "Element X Android", - description = "Element X is a secure, private and decentralized messenger.", - avatarData = AvatarData( - id = "!exa:matrix.org", - name = "Element X Android", - url = null, - size = AvatarSize.RoomDirectorySearchItem - ), - canBeJoined = true, - ), - RoomDirectoryRoomSummary( - roomId = RoomId("!exi:matrix.org"), - name = "Element X iOS", - description = "Element X is a secure, private and decentralized messenger.", - avatarData = AvatarData( - id = "!exi:matrix.org", - name = "Element X iOS", - url = null, - size = AvatarSize.RoomDirectorySearchItem - ), - canBeJoined = false, - ) - ) - ) - val searchResults: StateFlow> = _searchResults -} diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryRoomSummary.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescriptionUiModel.kt similarity index 60% rename from features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryRoomSummary.kt rename to features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescriptionUiModel.kt index 7aedc1305b..c7424eebf0 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryRoomSummary.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescriptionUiModel.kt @@ -17,12 +17,29 @@ package io.element.android.features.roomdirectory.impl.root.model import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription -data class RoomDirectoryRoomSummary( +data class RoomDescriptionUiModel( val roomId: RoomId, val name: String, val description: String, val avatarData: AvatarData, val canBeJoined: Boolean, ) + +fun RoomDescription.toUiModel(): RoomDescriptionUiModel { + return RoomDescriptionUiModel( + roomId = roomId, + name = name ?: "", + description = topic ?: "", + avatarData = AvatarData( + id = roomId.value, + name = name ?: "", + url = avatarUrl, + size = AvatarSize.RoomDirectoryItem, + ), + canBeJoined = joinRule == RoomDescription.JoinRule.PUBLIC, + ) +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt index 382aac5468..2dc6c8875f 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt @@ -53,5 +53,5 @@ enum class AvatarSize(val dp: Dp) { CustomRoomNotificationSetting(36.dp), - RoomDirectorySearchItem(36.dp), + RoomDirectoryItem(36.dp), } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectorySearch.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt similarity index 79% rename from libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectorySearch.kt rename to libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt index 1902c7aa3a..ce50125b65 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectorySearch.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt @@ -16,11 +16,11 @@ package io.element.android.libraries.matrix.api.roomdirectory -import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.Flow -interface RoomDirectorySearch { - suspend fun updateQuery(query: String?, batchSize: Int) +interface RoomDirectoryList { + suspend fun filter(filter: String?, batchSize: Int) suspend fun loadMore() suspend fun hasMoreToLoad(): Boolean - val results: SharedFlow> + val items: Flow> } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt index ebcff30671..fe9beb27d6 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt @@ -17,5 +17,5 @@ package io.element.android.libraries.matrix.api.roomdirectory interface RoomDirectoryService { - fun search(): RoomDirectorySearch + fun createRoomDirectoryList(): RoomDirectoryList } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt index 17ea8ee444..bf5c4c601f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt @@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.room.RoomMembershipObserver +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.verification.SessionVerificationService import kotlinx.coroutines.CoroutineScope @@ -68,4 +69,9 @@ object SessionMatrixModule { fun provideSessionCoroutineScope(matrixClient: MatrixClient): CoroutineScope { return matrixClient.sessionCoroutineScope } + + @Provides + fun providesRoomDirectoryService(matrixClient: MatrixClient): RoomDirectoryService { + return matrixClient.roomDirectoryService() + } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectorySearch.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt similarity index 71% rename from libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectorySearch.kt rename to libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt index 448a229538..1dbd62317c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectorySearch.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt @@ -17,26 +17,28 @@ package io.element.android.libraries.matrix.impl.roomdirectory import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription -import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectorySearch +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import kotlin.time.Duration.Companion.milliseconds import org.matrix.rustcomponents.sdk.RoomDirectorySearch as InnerRoomDirectorySearch -class RustRoomDirectorySearch( +class RustRoomDirectoryList( private val inner: InnerRoomDirectorySearch, private val sessionCoroutineScope: CoroutineScope, private val sessionDispatcher: CoroutineDispatcher, -) : RoomDirectorySearch { +) : RoomDirectoryList { - private val _results: MutableStateFlow> = - MutableStateFlow(emptyList()) + private val _items = MutableSharedFlow>() - private val processor = RoomDirectorySearchProcessor(_results, sessionDispatcher, RoomDescriptionMapper()) + private val processor = RoomDirectorySearchProcessor(_items, sessionDispatcher, RoomDescriptionMapper()) init { sessionCoroutineScope.launch(sessionDispatcher) { @@ -49,8 +51,8 @@ class RustRoomDirectorySearch( } } - override suspend fun updateQuery(query: String?, batchSize: Int) { - inner.search(query, batchSize.toUInt()) + override suspend fun filter(filter: String?, batchSize: Int) { + inner.search(filter, batchSize.toUInt()) } override suspend fun loadMore() { @@ -61,5 +63,6 @@ class RustRoomDirectorySearch( return !inner.isAtLastPage() } - override val results: SharedFlow> = _results + @OptIn(FlowPreview::class) + override val items: Flow> = _items.debounce(200.milliseconds) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt index 9644d5db7a..7022b2e21b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt @@ -16,7 +16,7 @@ package io.element.android.libraries.matrix.impl.roomdirectory -import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectorySearch +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -28,8 +28,8 @@ class RustRoomDirectoryService( private val sessionDispatcher: CoroutineDispatcher, ) : RoomDirectoryService { - override fun search(): RoomDirectorySearch { - return RustRoomDirectorySearch(client.roomDirectorySearch(), sessionCoroutineScope, sessionDispatcher) + override fun createRoomDirectoryList(): RoomDirectoryList { + return RustRoomDirectoryList(client.roomDirectorySearch(), sessionCoroutineScope, sessionDispatcher) } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt index f4b99f09a1..9281a41bc8 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt @@ -16,11 +16,11 @@ package io.element.android.libraries.matrix.test.roomdirectory -import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectorySearch +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService class FakeRoomDirectoryService : RoomDirectoryService { - override fun search(): RoomDirectorySearch { + override fun createRoomDirectoryList(): RoomDirectoryList { TODO("Not yet implemented") } }