Room directory : implement simple join room

This commit is contained in:
ganfra
2024-03-26 12:32:15 +01:00
parent 83b6875394
commit 0700384ef0
10 changed files with 89 additions and 19 deletions

View File

@@ -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) }
}
})

View File

@@ -32,7 +32,7 @@ interface RoomDirectoryEntryPoint : FeatureEntryPoint {
}
interface Callback : Plugin {
fun onJoinRoom(roomId: RoomId)
fun onOpenRoom(roomId: RoomId)
}
}

View File

@@ -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
}

View File

@@ -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<RoomDirectoryEntryPoint.Callback>().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
)

View File

@@ -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<RoomDirectoryState> {
@@ -56,6 +62,9 @@ class RoomDirectoryPresenter @Inject constructor(
val hasMoreToLoad by produceState(initialValue = true, allRooms) {
value = roomDirectoryList.hasMoreToLoad()
}
val joinRoomAction: MutableState<AsyncAction<RoomId>> = 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<AsyncAction<RoomId>>, roomId: RoomId) = launch {
state.runUpdatingState {
matrixClient.joinRoom(roomId)
}
}
@Composable
private fun RoomDirectoryList.collectItemsAsState() = remember {
items.map { list ->

View File

@@ -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<RoomDescriptionUiModel>,
val displayLoadMoreIndicator: Boolean,
val joinRoomAction: AsyncAction<RoomId>,
val eventSink: (RoomDirectoryEvents) -> Unit
) {
val displayEmptyState = roomDescriptions.isEmpty() && !displayLoadMoreIndicator

View File

@@ -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<RoomDirec
fun aRoomDirectoryState(
query: String = "",
isSearchActive: Boolean = false,
displayLoadMoreIndicator: Boolean = false,
roomDescriptions: ImmutableList<RoomDescriptionUiModel> = persistentListOf(),
searchResults: SearchBarResultState<ImmutableList<RoomDescriptionUiModel>> = SearchBarResultState.Initial(),
joinRoomAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
) = RoomDirectoryState(
query = query,
roomDescriptions = roomDescriptions,
displayLoadMoreIndicator = displayLoadMoreIndicator,
joinRoomAction = joinRoomAction,
eventSink = {},
)

View File

@@ -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 = {},
)
}

View File

@@ -59,6 +59,7 @@ interface MatrixClient : Closeable {
suspend fun setDisplayName(displayName: String): Result<Unit>
suspend fun uploadAvatar(mimeType: String, data: ByteArray): Result<Unit>
suspend fun removeAvatar(): Result<Unit>
suspend fun joinRoom(roomId: RoomId): Result<RoomId>
fun syncService(): SyncService
fun sessionVerificationService(): SessionVerificationService
fun pushersService(): PushersService

View File

@@ -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<RoomListItem, Room>? {
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<RoomId> = 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