From 0700384ef0d3e01948b589d8b375aeef811a929c Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 26 Mar 2024 12:32:15 +0100 Subject: [PATCH] Room directory : implement simple join room --- .../android/appnav/LoggedInFlowNode.kt | 2 +- .../api/RoomDirectoryEntryPoint.kt | 2 +- .../impl/root/RoomDirectoryEvents.kt | 4 ++ .../impl/root/RoomDirectoryNode.kt | 6 +-- .../impl/root/RoomDirectoryPresenter.kt | 22 ++++++++++ .../impl/root/RoomDirectoryState.kt | 3 ++ .../impl/root/RoomDirectoryStateProvider.kt | 6 +-- .../impl/root/RoomDirectoryView.kt | 18 ++++++-- .../libraries/matrix/api/MatrixClient.kt | 1 + .../libraries/matrix/impl/RustMatrixClient.kt | 44 +++++++++++++++---- 10 files changed, 89 insertions(+), 19 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 82a645b636..a9dd485285 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -389,7 +389,7 @@ class LoggedInFlowNode @AssistedInject constructor( NavTarget.RoomDirectorySearch -> { roomDirectoryEntryPoint.nodeBuilder(this, buildContext) .callback(object : RoomDirectoryEntryPoint.Callback { - override fun onJoinRoom(roomId: RoomId) { + override fun onOpenRoom(roomId: RoomId) { coroutineScope.launch { attachRoom(roomId) } } }) diff --git a/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt b/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt index a665f9f23c..8fbf342697 100644 --- a/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt +++ b/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt @@ -32,7 +32,7 @@ interface RoomDirectoryEntryPoint : FeatureEntryPoint { } interface Callback : Plugin { - fun onJoinRoom(roomId: RoomId) + fun onOpenRoom(roomId: RoomId) } } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt index f105a19c53..37e0ffb3c6 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt @@ -16,7 +16,11 @@ package io.element.android.features.roomdirectory.impl.root +import io.element.android.libraries.matrix.api.core.RoomId + sealed interface RoomDirectoryEvents { + data class JoinRoom(val roomId: RoomId) : RoomDirectoryEvents data class Search(val query: String) : RoomDirectoryEvents data object LoadMore : RoomDirectoryEvents + data object JoinRoomDismissError : RoomDirectoryEvents } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt index 5c2cbf654c..c99d30640a 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt @@ -36,9 +36,9 @@ class RoomDirectoryNode @AssistedInject constructor( private val presenter: RoomDirectoryPresenter, ) : Node(buildContext, plugins = plugins) { - private fun onJoinRoom(roomId: RoomId) { + private fun onRoomJoined(roomId: RoomId) { plugins().forEach { - it.onJoinRoom(roomId) + it.onOpenRoom(roomId) } } @@ -47,7 +47,7 @@ class RoomDirectoryNode @AssistedInject constructor( val state = presenter.present() RoomDirectoryView( state = state, - onJoinRoom = ::onJoinRoom, + onRoomJoined = ::onRoomJoined, onBackPressed = ::navigateUp, modifier = modifier ) 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 bfd88f4e28..9e91193114 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 @@ -18,6 +18,7 @@ package io.element.android.features.roomdirectory.impl.root import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -27,13 +28,17 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import io.element.android.features.roomdirectory.impl.root.model.toUiModel +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -41,6 +46,7 @@ import javax.inject.Inject class RoomDirectoryPresenter @Inject constructor( private val dispatchers: CoroutineDispatchers, + private val matrixClient: MatrixClient, roomDirectoryService: RoomDirectoryService, ) : Presenter { @@ -56,6 +62,9 @@ class RoomDirectoryPresenter @Inject constructor( val hasMoreToLoad by produceState(initialValue = true, allRooms) { value = roomDirectoryList.hasMoreToLoad() } + val joinRoomAction: MutableState> = remember { + mutableStateOf(AsyncAction.Uninitialized) + } val coroutineScope = rememberCoroutineScope() LaunchedEffect(searchQuery) { roomDirectoryList.filter(searchQuery, 20) @@ -70,6 +79,12 @@ class RoomDirectoryPresenter @Inject constructor( is RoomDirectoryEvents.Search -> { searchQuery = event.query } + is RoomDirectoryEvents.JoinRoom -> { + coroutineScope.joinRoom(joinRoomAction, event.roomId) + } + RoomDirectoryEvents.JoinRoomDismissError -> { + joinRoomAction.value = AsyncAction.Uninitialized + } } } @@ -77,10 +92,17 @@ class RoomDirectoryPresenter @Inject constructor( query = searchQuery, roomDescriptions = allRooms, displayLoadMoreIndicator = hasMoreToLoad, + joinRoomAction = joinRoomAction.value, eventSink = ::handleEvents ) } + private fun CoroutineScope.joinRoom(state: MutableState>, roomId: RoomId) = launch { + state.runUpdatingState { + matrixClient.joinRoom(roomId) + } + } + @Composable private fun RoomDirectoryList.collectItemsAsState() = remember { items.map { list -> 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 1afd53f146..937f78a69b 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 @@ -17,12 +17,15 @@ package io.element.android.features.roomdirectory.impl.root import io.element.android.features.roomdirectory.impl.root.model.RoomDescriptionUiModel +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.core.RoomId import kotlinx.collections.immutable.ImmutableList data class RoomDirectoryState( val query: String, val roomDescriptions: ImmutableList, val displayLoadMoreIndicator: Boolean, + val joinRoomAction: AsyncAction, 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 30acb14dba..8261959016 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 @@ -18,9 +18,9 @@ package io.element.android.features.roomdirectory.impl.root import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.roomdirectory.impl.root.model.RoomDescriptionUiModel +import io.element.android.libraries.architecture.AsyncAction 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 import io.element.android.libraries.matrix.api.core.RoomId import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -63,13 +63,13 @@ open class RoomDirectorySearchStateProvider : PreviewParameterProvider = persistentListOf(), - searchResults: SearchBarResultState> = SearchBarResultState.Initial(), + joinRoomAction: AsyncAction = AsyncAction.Uninitialized, ) = RoomDirectoryState( query = query, roomDescriptions = roomDescriptions, displayLoadMoreIndicator = displayLoadMoreIndicator, + joinRoomAction = joinRoomAction, 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 fecebf572f..bfd25d7f3d 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 @@ -47,6 +47,7 @@ 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.RoomDescriptionUiModel +import io.element.android.libraries.designsystem.components.async.AsyncActionView 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 @@ -66,10 +67,15 @@ import kotlinx.collections.immutable.ImmutableList @Composable fun RoomDirectoryView( state: RoomDirectoryState, + onRoomJoined: (RoomId) -> Unit, onBackPressed: () -> Unit, - onJoinRoom: (RoomId) -> Unit, modifier: Modifier = Modifier, ) { + + fun joinRoom(roomId: RoomId) { + state.eventSink(RoomDirectoryEvents.JoinRoom(roomId)) + } + Scaffold( modifier = modifier, topBar = { @@ -78,13 +84,19 @@ fun RoomDirectoryView( content = { padding -> RoomDirectoryContent( state = state, - onResultClicked = onJoinRoom, + onResultClicked = ::joinRoom, modifier = Modifier .padding(padding) .consumeWindowInsets(padding) ) } ) + AsyncActionView( + async = state.joinRoomAction, + onSuccess = onRoomJoined, + onErrorDismiss = { + state.eventSink(RoomDirectoryEvents.JoinRoomDismissError) + }) } @OptIn(ExperimentalMaterial3Api::class) @@ -295,7 +307,7 @@ private fun RoomDirectoryRoomRow( fun RoomDirectorySearchViewLightPreview(@PreviewParameter(RoomDirectorySearchStateProvider::class) state: RoomDirectoryState) = ElementPreview { RoomDirectoryView( state = state, - onJoinRoom = {}, + onRoomJoined = {}, onBackPressed = {}, ) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 43ca8f6687..e6c536ee72 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -59,6 +59,7 @@ interface MatrixClient : Closeable { suspend fun setDisplayName(displayName: String): Result suspend fun uploadAvatar(mimeType: String, data: ByteArray): Result suspend fun removeAvatar(): Result + suspend fun joinRoom(roomId: RoomId): Result fun syncService(): SyncService fun sessionVerificationService(): SessionVerificationService fun pushersService(): PushersService diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 280b56724b..277288205f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -73,6 +73,7 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow @@ -103,6 +104,8 @@ import org.matrix.rustcomponents.sdk.use import timber.log.Timber import java.io.File import java.util.concurrent.atomic.AtomicBoolean +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds import org.matrix.rustcomponents.sdk.CreateRoomParameters as RustCreateRoomParameters import org.matrix.rustcomponents.sdk.RoomPreset as RustRoomPreset import org.matrix.rustcomponents.sdk.RoomVisibility as RustRoomVisibility @@ -320,6 +323,22 @@ class RustMatrixClient( } } + /** + * Wait for the room to be available in the room list. + * @param roomId the room id to wait for + * @param timeout the timeout to wait for the room to be available + * @throws TimeoutCancellationException if the room is not available after the timeout + */ + private suspend fun awaitRoom(roomId: RoomId, timeout: Duration) { + withTimeout(timeout) { + roomListService.allRooms.summaries + .filter { roomSummaries -> + roomSummaries.map { it.identifier() }.contains(roomId.value) + } + .first() + } + } + private suspend fun pairOfRoom(roomId: RoomId): Pair? { val cachedRoomListItem = innerRoomListService.roomOrNull(roomId.value) val fullRoom = cachedRoomListItem?.fullRoomWithTimeline(filter = eventFilters) @@ -369,14 +388,11 @@ class RustMatrixClient( powerLevelContentOverride = defaultRoomCreationPowerLevels, ) val roomId = RoomId(client.createRoom(rustParams)) - - // Wait to receive the room back from the sync - withTimeout(30_000L) { - roomListService.allRooms.summaries - .filter { roomSummaries -> - roomSummaries.map { it.identifier() }.contains(roomId.value) - } - .first() + // Wait to receive the room back from the sync but do not returns failure if it fails. + try { + awaitRoom(roomId, 30.seconds) + } catch (e: Exception) { + Timber.e(e, "Timeout waiting for the room to be available in the room list") } roomId } @@ -425,6 +441,18 @@ class RustMatrixClient( runCatching { client.removeAvatar() } } + override suspend fun joinRoom(roomId: RoomId): Result = withContext(sessionDispatcher) { + runCatching { + client.joinRoomById(roomId.value).destroy() + try { + awaitRoom(roomId, 10.seconds) + } catch (e: Exception) { + Timber.e(e, "Timeout waiting for the room to be available in the room list") + } + roomId + } + } + override fun syncService(): SyncService = rustSyncService override fun sessionVerificationService(): SessionVerificationService = verificationService