Merge pull request #2695 from element-hq/feature/fga/room_navigation
Room navigation
This commit is contained in:
commit
1dcdc713a4
133 changed files with 2824 additions and 934 deletions
|
|
@ -21,6 +21,7 @@ import androidx.compose.foundation.layout.Row
|
|||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
|
|
@ -29,11 +30,14 @@ import io.element.android.libraries.designsystem.theme.components.TextButton
|
|||
@Composable
|
||||
fun ButtonRowMolecule(
|
||||
modifier: Modifier = Modifier,
|
||||
horizontalArrangement: Arrangement.Horizontal = Arrangement.SpaceBetween,
|
||||
verticalAlignment: Alignment.Vertical = Alignment.Top,
|
||||
content: @Composable RowScope.() -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
horizontalArrangement = horizontalArrangement,
|
||||
verticalAlignment = verticalAlignment,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package io.element.android.libraries.designsystem.atomic.pages
|
|||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -34,6 +35,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
|||
|
||||
/**
|
||||
* @param modifier Classical modifier.
|
||||
* @param paddingValues padding values to apply to the content.
|
||||
* @param background optional background component.
|
||||
* @param topBar optional topBar.
|
||||
* @param header optional header.
|
||||
|
|
@ -43,6 +45,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
|||
@Composable
|
||||
fun HeaderFooterPage(
|
||||
modifier: Modifier = Modifier,
|
||||
paddingValues: PaddingValues = PaddingValues(20.dp),
|
||||
background: @Composable () -> Unit = {},
|
||||
topBar: @Composable () -> Unit = {},
|
||||
header: @Composable () -> Unit = {},
|
||||
|
|
@ -57,7 +60,7 @@ fun HeaderFooterPage(
|
|||
background()
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(all = 20.dp)
|
||||
.padding(paddingValues = paddingValues)
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.notificationsettings.Notification
|
|||
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
|
||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||
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
|
||||
|
|
@ -37,8 +38,10 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
|
|||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.io.Closeable
|
||||
import java.util.Optional
|
||||
|
||||
interface MatrixClient : Closeable {
|
||||
val sessionId: SessionId
|
||||
|
|
@ -89,6 +92,7 @@ interface MatrixClient : Closeable {
|
|||
suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result<String?>
|
||||
suspend fun uploadMedia(mimeType: String, data: ByteArray, progressCallback: ProgressCallback?): Result<String>
|
||||
fun roomMembershipObserver(): RoomMembershipObserver
|
||||
fun getRoomInfoFlow(roomId: RoomId): Flow<Optional<MatrixRoomInfo>>
|
||||
|
||||
fun isMe(userId: UserId?) = userId == sessionId
|
||||
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ class RoomMembershipObserver {
|
|||
val change: MembershipChange,
|
||||
)
|
||||
|
||||
private val _updates = MutableSharedFlow<RoomMembershipUpdate>(replay = 1)
|
||||
private val _updates = MutableSharedFlow<RoomMembershipUpdate>(extraBufferCapacity = 10)
|
||||
val updates = _updates.asSharedFlow()
|
||||
|
||||
fun notifyUserLeftRoom(roomId: RoomId) {
|
||||
_updates.tryEmit(RoomMembershipUpdate(roomId, false, MembershipChange.LEFT))
|
||||
suspend fun notifyUserLeftRoom(roomId: RoomId) {
|
||||
_updates.emit(RoomMembershipUpdate(roomId, false, MembershipChange.LEFT))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package io.element.android.libraries.matrix.api.roomlist
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
|
@ -32,6 +33,8 @@ interface DynamicRoomList : RoomList {
|
|||
val loadedPages: StateFlow<Int>
|
||||
val pageSize: Int
|
||||
|
||||
val filteredSummaries: SharedFlow<List<RoomSummary>>
|
||||
|
||||
/**
|
||||
* Load more rooms into the list if possible.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@
|
|||
package io.element.android.libraries.matrix.api.roomlist
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
|
||||
/**
|
||||
* Entry point for the room list api.
|
||||
|
|
@ -78,3 +80,7 @@ interface RoomListService {
|
|||
*/
|
||||
val state: StateFlow<State>
|
||||
}
|
||||
|
||||
fun RoomList.loadedStateFlow(): Flow<RoomList.LoadingState.Loaded> {
|
||||
return loadingState.filterIsInstance()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import io.element.android.libraries.matrix.api.notificationsettings.Notification
|
|||
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
|
||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||
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
|
||||
|
|
@ -79,12 +80,15 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.buffer
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -104,8 +108,10 @@ import org.matrix.rustcomponents.sdk.TimelineEventTypeFilter
|
|||
import org.matrix.rustcomponents.sdk.use
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.INFINITE
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import org.matrix.rustcomponents.sdk.CreateRoomParameters as RustCreateRoomParameters
|
||||
import org.matrix.rustcomponents.sdk.RoomPreset as RustRoomPreset
|
||||
|
|
@ -540,6 +546,22 @@ class RustMatrixClient(
|
|||
|
||||
override fun roomMembershipObserver(): RoomMembershipObserver = roomMembershipObserver
|
||||
|
||||
override fun getRoomInfoFlow(roomId: RoomId): Flow<Optional<MatrixRoomInfo>> {
|
||||
return flow {
|
||||
var room = getRoom(roomId)
|
||||
if (room == null) {
|
||||
emit(Optional.empty())
|
||||
awaitRoom(roomId, INFINITE)
|
||||
room = getRoom(roomId)
|
||||
}
|
||||
room?.use {
|
||||
room.roomInfoFlow
|
||||
.map { roomInfo -> Optional.of(roomInfo) }
|
||||
.collect(this)
|
||||
}
|
||||
}.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private suspend fun File.getCacheSize(
|
||||
includeCryptoDb: Boolean = false,
|
||||
): Long = withContext(sessionDispatcher) {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.getAndUpdate
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
|
@ -92,7 +93,8 @@ internal class RoomListFactory(
|
|||
innerRoomList?.destroy()
|
||||
}
|
||||
return RustDynamicRoomList(
|
||||
summaries = filteredSummariesFlow,
|
||||
summaries = summariesFlow,
|
||||
filteredSummaries = filteredSummariesFlow,
|
||||
loadingState = loadingStateFlow,
|
||||
currentFilter = currentFilter,
|
||||
loadedPages = loadedPages,
|
||||
|
|
@ -105,6 +107,7 @@ internal class RoomListFactory(
|
|||
|
||||
private class RustDynamicRoomList(
|
||||
override val summaries: MutableSharedFlow<List<RoomSummary>>,
|
||||
override val filteredSummaries: SharedFlow<List<RoomSummary>>,
|
||||
override val loadingState: MutableStateFlow<RoomList.LoadingState>,
|
||||
override val currentFilter: MutableStateFlow<RoomListFilter>,
|
||||
override val loadedPages: MutableStateFlow<Int>,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package io.element.android.libraries.matrix.impl.util
|
||||
|
||||
import io.element.android.libraries.core.data.tryOrNull
|
||||
import kotlinx.coroutines.channels.ProducerScope
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
|
|
@ -23,7 +24,9 @@ import org.matrix.rustcomponents.sdk.TaskHandle
|
|||
|
||||
internal fun <T> mxCallbackFlow(block: suspend ProducerScope<T>.() -> TaskHandle?) =
|
||||
callbackFlow {
|
||||
val taskHandle: TaskHandle? = block(this)
|
||||
val taskHandle: TaskHandle? = tryOrNull {
|
||||
block(this)
|
||||
}
|
||||
awaitClose {
|
||||
taskHandle?.cancelAndDestroy()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.api.notificationsettings.Notification
|
|||
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
|
||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||
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
|
||||
|
|
@ -51,7 +52,9 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import java.util.Optional
|
||||
|
||||
class FakeMatrixClient(
|
||||
override val sessionId: SessionId = A_SESSION_ID,
|
||||
|
|
@ -94,10 +97,14 @@ class FakeMatrixClient(
|
|||
private var setDisplayNameResult: Result<Unit> = Result.success(Unit)
|
||||
private var uploadAvatarResult: Result<Unit> = Result.success(Unit)
|
||||
private var removeAvatarResult: Result<Unit> = Result.success(Unit)
|
||||
var joinRoomLambda: suspend (RoomId) -> Result<RoomId> = {
|
||||
var joinRoomLambda: (RoomId) -> Result<RoomId> = {
|
||||
Result.success(it)
|
||||
}
|
||||
|
||||
var getRoomInfoFlowLambda = { _: RoomId ->
|
||||
flowOf<Optional<MatrixRoomInfo>>(Optional.empty())
|
||||
}
|
||||
|
||||
override suspend fun getRoom(roomId: RoomId): MatrixRoom? {
|
||||
return getRoomResults[roomId]
|
||||
}
|
||||
|
|
@ -267,4 +274,6 @@ class FakeMatrixClient(
|
|||
override suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>> {
|
||||
return Result.success(visitedRoomsId)
|
||||
}
|
||||
|
||||
override fun getRoomInfoFlow(roomId: RoomId) = getRoomInfoFlowLambda(roomId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ class FakeMatrixRoom(
|
|||
var removedAvatar: Boolean = false
|
||||
private set
|
||||
|
||||
private var leaveRoomError: Throwable? = null
|
||||
var leaveRoomLambda: (() -> Result<Unit>) = { Result.success(Unit) }
|
||||
|
||||
private val _roomInfoFlow: MutableSharedFlow<MatrixRoomInfo> = MutableSharedFlow(replay = 1)
|
||||
override val roomInfoFlow: Flow<MatrixRoomInfo> = _roomInfoFlow
|
||||
|
|
@ -315,8 +315,9 @@ class FakeMatrixRoom(
|
|||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun leave(): Result<Unit> =
|
||||
leaveRoomError?.let { Result.failure(it) } ?: Result.success(Unit)
|
||||
override suspend fun leave(): Result<Unit> {
|
||||
return leaveRoomLambda()
|
||||
}
|
||||
|
||||
override suspend fun join(): Result<Unit> {
|
||||
return joinRoomResult
|
||||
|
|
@ -542,10 +543,6 @@ class FakeMatrixRoom(
|
|||
|
||||
override fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result<MatrixWidgetDriver> = getWidgetDriverResult
|
||||
|
||||
fun givenLeaveRoomError(throwable: Throwable?) {
|
||||
this.leaveRoomError = throwable
|
||||
}
|
||||
|
||||
fun givenRoomMembersState(state: MatrixRoomMembersState) {
|
||||
membersStateFlow.value = state
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.RoomSummary
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.getAndUpdate
|
||||
|
||||
|
|
@ -32,6 +33,8 @@ data class SimplePagedRoomList(
|
|||
override val pageSize: Int = Int.MAX_VALUE
|
||||
override val loadedPages = MutableStateFlow(1)
|
||||
|
||||
override val filteredSummaries: SharedFlow<List<RoomSummary>> = summaries
|
||||
|
||||
override suspend fun loadMore() {
|
||||
// No-op
|
||||
loadedPages.getAndUpdate { it + 1 }
|
||||
|
|
|
|||
|
|
@ -105,4 +105,9 @@ object TestTags {
|
|||
* Search field.
|
||||
*/
|
||||
val searchTextField = TestTag("search_text_field")
|
||||
|
||||
/**
|
||||
* Generic call to action.
|
||||
*/
|
||||
val callToAction = TestTag("call_to_action")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue