Room navigation : more reliable roomInfoFlow method

This commit is contained in:
ganfra
2024-04-10 11:41:06 +02:00
parent 23604e0549
commit fc20b7399a
14 changed files with 75 additions and 74 deletions

View File

@@ -44,13 +44,11 @@ import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize
@@ -61,7 +59,7 @@ import kotlin.jvm.optionals.getOrNull
class RoomFlowNode @AssistedInject constructor(
@Assisted val buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val roomListService: RoomListService,
private val client: MatrixClient,
private val roomMembershipObserver: RoomMembershipObserver,
private val joinRoomEntryPoint: JoinRoomEntryPoint,
) : BaseFlowNode<RoomFlowNode.NavTarget>(
@@ -92,18 +90,16 @@ class RoomFlowNode @AssistedInject constructor(
override fun onBuilt() {
super.onBuilt()
roomListService.getUserMembershipForRoom(
client.getRoomInfoFlow(
inputs.roomId
).flowOn(Dispatchers.Default)
.onEach { membership ->
Timber.d("RoomMembership = $membership")
if (membership.getOrNull() == CurrentUserMembership.JOINED) {
backstack.newRoot(NavTarget.JoinedRoom)
} else {
backstack.newRoot(NavTarget.JoinRoom)
}
).onEach { roomInfo ->
Timber.d("Room membership: ${roomInfo.map { it.currentUserMembership }}")
if (roomInfo.getOrNull()?.currentUserMembership == CurrentUserMembership.JOINED) {
backstack.newRoot(NavTarget.JoinedRoom)
} else {
backstack.newRoot(NavTarget.JoinRoom)
}
.flowOn(Dispatchers.Main)
}
.launchIn(lifecycleScope)
roomMembershipObserver.updates

View File

@@ -30,14 +30,13 @@ import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
import java.util.Optional
import kotlin.jvm.optionals.getOrNull
class JoinRoomPresenter @AssistedInject constructor(
@Assisted private val roomId: RoomId,
private val matrixClient: MatrixClient,
private val roomListService: RoomListService,
private val acceptDeclineInvitePresenter: AcceptDeclineInvitePresenter,
) : Presenter<JoinRoomState> {
@@ -47,23 +46,24 @@ class JoinRoomPresenter @AssistedInject constructor(
@Composable
override fun present(): JoinRoomState {
val userMembership by roomListService.getUserMembershipForRoom(roomId).collectAsState(initial = Optional.empty())
val joinAuthorisationStatus = joinAuthorisationStatus(userMembership)
val mxRoomInfo by matrixClient.getRoomInfoFlow(roomId).collectAsState(initial = Optional.empty())
val joinAuthorisationStatus = joinAuthorisationStatus(mxRoomInfo)
val acceptDeclineInviteState = acceptDeclineInvitePresenter.present()
val roomInfo by produceState<AsyncData<RoomInfo>>(initialValue = AsyncData.Uninitialized, key1 = userMembership) {
val roomInfo by produceState<AsyncData<RoomInfo>>(initialValue = AsyncData.Uninitialized, key1 = mxRoomInfo) {
value = when {
userMembership.isPresent -> {
val roomInfo = matrixClient.getRoom(roomId)?.use {
mxRoomInfo.isPresent -> {
val roomInfo = mxRoomInfo.get().let {
RoomInfo(
roomId = it.roomId,
roomName = it.displayName,
roomAlias = it.alias,
memberCount = it.activeMemberCount,
roomId = roomId,
roomName = it.name,
roomAlias = it.canonicalAlias,
memberCount = it.activeMembersCount,
isDirect = it.isDirect,
topic = it.topic,
roomAvatarUrl = it.avatarUrl
)
}
roomInfo?.let { AsyncData.Success(it) } ?: AsyncData.Failure(Exception("Failed to load room info"))
AsyncData.Success(roomInfo)
}
else -> AsyncData.Uninitialized
}
@@ -103,9 +103,10 @@ class JoinRoomPresenter @AssistedInject constructor(
}
@Composable
private fun joinAuthorisationStatus(userMembership: Optional<CurrentUserMembership>): JoinAuthorisationStatus {
private fun joinAuthorisationStatus(roomInfo: Optional<MatrixRoomInfo>): JoinAuthorisationStatus {
val userMembership = roomInfo.getOrNull()?.currentUserMembership
return when {
userMembership.getOrNull() == CurrentUserMembership.INVITED -> return JoinAuthorisationStatus.IsInvited
userMembership == CurrentUserMembership.INVITED -> return JoinAuthorisationStatus.IsInvited
else -> JoinAuthorisationStatus.Unknown
}
}

View File

@@ -29,15 +29,16 @@ data class JoinRoomState(
val joinAuthorisationStatus: JoinAuthorisationStatus,
val acceptDeclineInviteState: AcceptDeclineInviteState,
val eventSink: (JoinRoomEvents) -> Unit
){
) {
val showMemberCount = roomInfo.dataOrNull()?.memberCount != null
}
data class RoomInfo(
val roomId: RoomId,
val roomName: String,
val roomName: String?,
val roomAlias: String?,
val memberCount: Long?,
val topic: String?,
val isDirect: Boolean,
val roomAvatarUrl: String?,
) {

View File

@@ -48,6 +48,7 @@ fun aJoinRoomState(
roomAlias = "#exa:matrix.org",
memberCount = null,
isDirect = false,
topic = null,
roomAvatarUrl = null
)
),

View File

@@ -203,7 +203,11 @@ private fun JoinRoomTopBar(
when (asyncRoomInfo) {
is AsyncData.Success -> {
val roomInfo = asyncRoomInfo.data
RoomAvatarAndNameRow(roomName = roomInfo.roomName, roomAvatar = roomInfo.avatarData(AvatarSize.TimelineRoom))
if(roomInfo.roomName == null){
IconTitlePlaceholdersRowMolecule(iconSize = AvatarSize.TimelineRoom.dp)
}else {
RoomAvatarAndNameRow(roomName = roomInfo.roomName, roomAvatar = roomInfo.avatarData(AvatarSize.TimelineRoom))
}
}
else -> {
IconTitlePlaceholdersRowMolecule(iconSize = AvatarSize.TimelineRoom.dp)

View File

@@ -40,7 +40,6 @@ object JoinRoomModule {
return JoinRoomPresenter(
roomId = roomId,
matrixClient = client,
roomListService = roomListService,
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
)
}

View File

@@ -60,7 +60,7 @@ class RoomListDataSource @Inject constructor(
fun launchIn(coroutineScope: CoroutineScope) {
roomListService
.allRooms
.summaries
.filteredSummaries
.onEach { roomSummaries ->
replaceWith(roomSummaries)
}

View File

@@ -45,7 +45,7 @@ class RoomListSearchDataSource @Inject constructor(
source = RoomList.Source.All,
)
val roomSummaries: Flow<PersistentList<RoomListRoomSummary>> = roomList.summaries
val roomSummaries: Flow<PersistentList<RoomListRoomSummary>> = roomList.filteredSummaries
.map { roomSummaries ->
roomSummaries
.filterIsInstance<RoomSummary.Filled>()

View File

@@ -27,7 +27,9 @@ import io.element.android.libraries.matrix.api.notification.NotificationService
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
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.CurrentUserMembership
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 +39,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 +93,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

@@ -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,15 +17,9 @@
package io.element.android.libraries.matrix.api.roomlist
import androidx.compose.runtime.Immutable
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import java.util.Optional
import kotlinx.coroutines.flow.filterIsInstance
/**
* Entry point for the room list api.
@@ -86,10 +80,10 @@ interface RoomListService {
*/
val state: StateFlow<State>
/**
* Get a flow of the room summary for a given room id.
*/
fun getUserMembershipForRoom(roomId: RoomId): Flow<Optional<CurrentUserMembership>>
}
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
@@ -524,6 +530,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,24 +16,16 @@
package io.element.android.libraries.matrix.impl.roomlist
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.roomlist.DynamicRoomList
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.RoomListService
import io.element.android.libraries.matrix.api.roomlist.awaitLoaded
import io.element.android.libraries.matrix.api.roomlist.loadAllIncrementally
import io.element.android.libraries.matrix.impl.room.map
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
@@ -43,9 +35,7 @@ import org.matrix.rustcomponents.sdk.RoomListInput
import org.matrix.rustcomponents.sdk.RoomListRange
import org.matrix.rustcomponents.sdk.RoomListServiceState
import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicator
import org.matrix.rustcomponents.sdk.use
import timber.log.Timber
import java.util.Optional
import org.matrix.rustcomponents.sdk.RoomListService as InnerRustRoomListService
private const val DEFAULT_PAGE_SIZE = 20
@@ -122,24 +112,6 @@ internal class RustRoomListService(
}
.distinctUntilChanged()
.stateIn(sessionCoroutineScope, SharingStarted.Eagerly, RoomListService.State.Idle)
override fun getUserMembershipForRoom(roomId: RoomId): Flow<Optional<CurrentUserMembership>> {
return combine(
allRooms.loadedStateFlow(),
invites.loadedStateFlow(),
) { _, _ ->
val membership = innerRoomListService.roomOrNull(roomId.value)?.use {
it.roomInfo().use { roomInfo ->
roomInfo.membership.map()
}
}
Optional.ofNullable(membership)
}.distinctUntilChanged()
}
private fun RoomList.loadedStateFlow(): Flow<RoomList.LoadingState.Loaded> {
return loadingState.filterIsInstance()
}
}
private fun RoomListServiceState.toRoomListState(): RoomListService.State {