Merge pull request #2695 from element-hq/feature/fga/room_navigation

Room navigation
This commit is contained in:
ganfra 2024-04-15 12:55:29 +02:00 committed by GitHub
commit 1dcdc713a4
133 changed files with 2824 additions and 934 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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.
*/

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -105,4 +105,9 @@ object TestTags {
* Search field.
*/
val searchTextField = TestTag("search_text_field")
/**
* Generic call to action.
*/
val callToAction = TestTag("call_to_action")
}