Room directory : implement simple join room
This commit is contained in:
@@ -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) }
|
||||
}
|
||||
})
|
||||
|
||||
@@ -32,7 +32,7 @@ interface RoomDirectoryEntryPoint : FeatureEntryPoint {
|
||||
}
|
||||
|
||||
interface Callback : Plugin {
|
||||
fun onJoinRoom(roomId: RoomId)
|
||||
fun onOpenRoom(roomId: RoomId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = {},
|
||||
)
|
||||
|
||||
@@ -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 = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user