Room/Timeline: simplify the apis

This commit is contained in:
ganfra 2023-06-21 16:25:18 +02:00
parent ea21ea2ace
commit 7c8df186f6
13 changed files with 134 additions and 150 deletions

View file

@ -18,7 +18,6 @@ package io.element.android.appnav
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.composable.Children
@ -83,6 +82,7 @@ class RoomFlowNode @AssistedInject constructor(
lifecycle.subscribe( lifecycle.subscribe(
onCreate = { onCreate = {
Timber.v("OnCreate") Timber.v("OnCreate")
inputs.room.open()
plugins<LifecycleCallback>().forEach { it.onFlowCreated(id, inputs.room) } plugins<LifecycleCallback>().forEach { it.onFlowCreated(id, inputs.room) }
appNavigationStateService.onNavigateToRoom(id, inputs.room.roomId) appNavigationStateService.onNavigateToRoom(id, inputs.room.roomId)
fetchRoomMembers() fetchRoomMembers()
@ -149,18 +149,8 @@ class RoomFlowNode @AssistedInject constructor(
data class RoomMemberDetails(val userId: UserId) : NavTarget data class RoomMemberDetails(val userId: UserId) : NavTarget
} }
private val timeline = inputs.room.timeline()
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
DisposableEffect(Unit) {
timeline.initialize()
onDispose {
timeline.dispose()
}
}
Children( Children(
navModel = backstack, navModel = backstack,
modifier = modifier, modifier = modifier,

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.core.coroutine
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.job
import kotlinx.coroutines.plus
fun childScopeOf(
parentScope: CoroutineScope,
dispatcher: CoroutineDispatcher,
name: String,
): CoroutineScope = run {
val supervisorJob = SupervisorJob(parent = parentScope.coroutineContext.job)
parentScope + dispatcher + supervisorJob + CoroutineName(name)
}

View file

@ -61,6 +61,8 @@ interface MatrixRoom : Closeable {
fun timeline(): MatrixTimeline fun timeline(): MatrixTimeline
fun open(): Result<Unit>
suspend fun userDisplayName(userId: UserId): Result<String?> suspend fun userDisplayName(userId: UserId): Result<String?>
suspend fun userAvatarUrl(userId: UserId): Result<String?> suspend fun userAvatarUrl(userId: UserId): Result<String?>

View file

@ -28,20 +28,8 @@ interface MatrixTimeline {
) )
fun paginationState(): StateFlow<PaginationState> fun paginationState(): StateFlow<PaginationState>
fun timelineItems(): Flow<List<MatrixTimelineItem>> fun timelineItems(): Flow<List<MatrixTimelineItem>>
suspend fun paginateBackwards(requestSize: Int, untilNumberOfItems: Int): Result<Unit> suspend fun paginateBackwards(requestSize: Int, untilNumberOfItems: Int): Result<Unit>
fun initialize()
fun dispose()
/**
* @param message markdown message
*/
suspend fun sendMessage(message: String): Result<Unit>
suspend fun editMessage(originalEventId: EventId, message: String): Result<Unit>
suspend fun replyMessage(inReplyToEventId: EventId, message: String): Result<Unit>
suspend fun fetchDetailsForEvent(eventId: EventId): Result<Unit> suspend fun fetchDetailsForEvent(eventId: EventId): Result<Unit>
} }

View file

@ -19,6 +19,7 @@
package io.element.android.libraries.matrix.impl package io.element.android.libraries.matrix.impl
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.coroutine.childScopeOf
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
@ -39,6 +40,7 @@ import io.element.android.libraries.matrix.impl.notification.RustNotificationSer
import io.element.android.libraries.matrix.impl.pushers.RustPushersService import io.element.android.libraries.matrix.impl.pushers.RustPushersService
import io.element.android.libraries.matrix.impl.room.RustMatrixRoom import io.element.android.libraries.matrix.impl.room.RustMatrixRoom
import io.element.android.libraries.matrix.impl.room.RustRoomSummaryDataSource import io.element.android.libraries.matrix.impl.room.RustRoomSummaryDataSource
import io.element.android.libraries.matrix.impl.room.roomOrNull
import io.element.android.libraries.matrix.impl.usersearch.UserProfileMapper import io.element.android.libraries.matrix.impl.usersearch.UserProfileMapper
import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper
import io.element.android.libraries.matrix.impl.verification.RustSessionVerificationService import io.element.android.libraries.matrix.impl.verification.RustSessionVerificationService
@ -47,7 +49,7 @@ import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -66,7 +68,7 @@ import org.matrix.rustcomponents.sdk.RoomVisibility as RustRoomVisibility
class RustMatrixClient constructor( class RustMatrixClient constructor(
private val client: Client, private val client: Client,
private val sessionStore: SessionStore, private val sessionStore: SessionStore,
private val coroutineScope: CoroutineScope, private val appCoroutineScope: CoroutineScope,
private val dispatchers: CoroutineDispatchers, private val dispatchers: CoroutineDispatchers,
private val baseDirectory: File, private val baseDirectory: File,
private val baseCacheDirectory: File, private val baseCacheDirectory: File,
@ -75,13 +77,13 @@ class RustMatrixClient constructor(
override val sessionId: UserId = UserId(client.userId()) override val sessionId: UserId = UserId(client.userId())
private val sessionCoroutineScope = childScopeOf(appCoroutineScope, dispatchers.main, "Session-${sessionId}")
private val verificationService = RustSessionVerificationService() private val verificationService = RustSessionVerificationService()
private val pushersService = RustPushersService( private val pushersService = RustPushersService(
client = client, client = client,
dispatchers = dispatchers, dispatchers = dispatchers,
) )
private val notificationService = RustNotificationService(client) private val notificationService = RustNotificationService(client)
private var slidingSyncUpdateJob: Job? = null
private val clientDelegate = object : ClientDelegate { private val clientDelegate = object : ClientDelegate {
override fun didReceiveAuthError(isSoftLogout: Boolean) { override fun didReceiveAuthError(isSoftLogout: Boolean) {
@ -95,6 +97,7 @@ class RustMatrixClient constructor(
private val rustRoomSummaryDataSource: RustRoomSummaryDataSource = private val rustRoomSummaryDataSource: RustRoomSummaryDataSource =
RustRoomSummaryDataSource( RustRoomSummaryDataSource(
roomList, roomList,
sessionCoroutineScope,
dispatchers, dispatchers,
) )
@ -104,6 +107,7 @@ class RustMatrixClient constructor(
private val rustInvitesDataSource: RustRoomSummaryDataSource = private val rustInvitesDataSource: RustRoomSummaryDataSource =
RustRoomSummaryDataSource( RustRoomSummaryDataSource(
roomList, roomList,
sessionCoroutineScope,
dispatchers, dispatchers,
) )
@ -127,14 +131,15 @@ class RustMatrixClient constructor(
} }
override fun getRoom(roomId: RoomId): MatrixRoom? { override fun getRoom(roomId: RoomId): MatrixRoom? {
val roomListItem = roomList.room(roomId.value) val roomListItem = roomList.roomOrNull(roomId.value) ?: return null
val fullRoom = roomListItem.fullRoom() val fullRoom = roomListItem.fullRoom()
return RustMatrixRoom( return RustMatrixRoom(
sessionId = sessionId, sessionId = sessionId,
roomListItem = roomListItem, roomListItem = roomListItem,
innerRoom = fullRoom, innerRoom = fullRoom,
coroutineScope = coroutineScope, sessionCoroutineScope = sessionCoroutineScope,
coroutineDispatchers = dispatchers, coroutineDispatchers = dispatchers,
systemClock = clock
) )
} }
@ -231,10 +236,8 @@ class RustMatrixClient constructor(
} }
override fun close() { override fun close() {
slidingSyncUpdateJob?.cancel()
stopSync() stopSync()
rustRoomSummaryDataSource.close() sessionCoroutineScope.cancel()
rustInvitesDataSource.close()
client.setDelegate(null) client.setDelegate(null)
verificationService.destroy() verificationService.destroy()
roomList.destroy() roomList.destroy()

View file

@ -52,7 +52,7 @@ import org.matrix.rustcomponents.sdk.AuthenticationService as RustAuthentication
class RustMatrixAuthenticationService @Inject constructor( class RustMatrixAuthenticationService @Inject constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
private val baseDirectory: File, private val baseDirectory: File,
private val coroutineScope: CoroutineScope, private val appCoroutineScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers, private val coroutineDispatchers: CoroutineDispatchers,
private val sessionStore: SessionStore, private val sessionStore: SessionStore,
private val clock: SystemClock, private val clock: SystemClock,
@ -179,7 +179,7 @@ class RustMatrixAuthenticationService @Inject constructor(
return RustMatrixClient( return RustMatrixClient(
client = client, client = client,
sessionStore = sessionStore, sessionStore = sessionStore,
coroutineScope = coroutineScope, appCoroutineScope = appCoroutineScope,
dispatchers = coroutineDispatchers, dispatchers = coroutineDispatchers,
baseDirectory = baseDirectory, baseDirectory = baseDirectory,
baseCacheDirectory = context.cacheDir, baseCacheDirectory = context.cacheDir,

View file

@ -7,10 +7,12 @@ import org.matrix.rustcomponents.sdk.RoomList
import org.matrix.rustcomponents.sdk.RoomListEntriesListener import org.matrix.rustcomponents.sdk.RoomListEntriesListener
import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate
import org.matrix.rustcomponents.sdk.RoomListEntry import org.matrix.rustcomponents.sdk.RoomListEntry
import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.RoomListState import org.matrix.rustcomponents.sdk.RoomListState
import org.matrix.rustcomponents.sdk.RoomListStateListener import org.matrix.rustcomponents.sdk.RoomListStateListener
import org.matrix.rustcomponents.sdk.SlidingSyncListLoadingState import org.matrix.rustcomponents.sdk.SlidingSyncListLoadingState
import org.matrix.rustcomponents.sdk.SlidingSyncListStateObserver import org.matrix.rustcomponents.sdk.SlidingSyncListStateObserver
import timber.log.Timber
fun RoomList.stateFlow(): Flow<RoomListState> = fun RoomList.stateFlow(): Flow<RoomListState> =
mxCallbackFlow { mxCallbackFlow {
@ -46,3 +48,11 @@ fun RoomList.roomListEntriesUpdateFlow(onInitialList: suspend (List<RoomListEntr
result.entriesStream result.entriesStream
} }
fun RoomList.roomOrNull(roomId: String): RoomListItem? {
return try {
room(roomId)
} catch (failure: Throwable) {
Timber.e(failure, "Failed finding room with id=$roomId")
return null
}
}

View file

@ -17,6 +17,7 @@
package io.element.android.libraries.matrix.impl.room package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.coroutine.childScopeOf
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
@ -30,22 +31,25 @@ import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.api.room.roomMembers import io.element.android.libraries.matrix.api.room.roomMembers
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
import io.element.android.libraries.matrix.impl.media.map import io.element.android.libraries.matrix.impl.media.map
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
import io.element.android.libraries.matrix.impl.timeline.timelineDiffFlow
import io.element.android.services.toolbox.api.systemclock.SystemClock import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.RequiredState
import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate
import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.RoomMember import org.matrix.rustcomponents.sdk.RoomMember
import org.matrix.rustcomponents.sdk.RoomSubscription
import org.matrix.rustcomponents.sdk.genTransactionId import org.matrix.rustcomponents.sdk.genTransactionId
import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
import java.io.File import java.io.File
@ -54,40 +58,73 @@ class RustMatrixRoom(
override val sessionId: SessionId, override val sessionId: SessionId,
private val roomListItem: RoomListItem, private val roomListItem: RoomListItem,
private val innerRoom: Room, private val innerRoom: Room,
private val coroutineScope: CoroutineScope, sessionCoroutineScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers, private val coroutineDispatchers: CoroutineDispatchers,
private val systemClock: SystemClock,
) : MatrixRoom { ) : MatrixRoom {
override val roomId = RoomId(innerRoom.id())
private val roomCoroutineScope = childScopeOf(sessionCoroutineScope, coroutineDispatchers.main, "RoomScope-$roomId")
override val membersStateFlow: StateFlow<MatrixRoomMembersState> override val membersStateFlow: StateFlow<MatrixRoomMembersState>
get() = _membersStateFlow get() = _membersStateFlow
private var _membersStateFlow = MutableStateFlow<MatrixRoomMembersState>(MatrixRoomMembersState.Unknown) private var _membersStateFlow = MutableStateFlow<MatrixRoomMembersState>(MatrixRoomMembersState.Unknown)
private val isInit = MutableStateFlow(false)
private val syncUpdateFlow = MutableStateFlow(systemClock.epochMillis())
private val timeline by lazy { private val timeline by lazy {
RustMatrixTimeline( RustMatrixTimeline(
matrixRoom = this, matrixRoom = this,
innerRoom = innerRoom, innerRoom = innerRoom,
roomListItem = roomListItem, coroutineScope = roomCoroutineScope,
coroutineScope = coroutineScope,
coroutineDispatchers = coroutineDispatchers coroutineDispatchers = coroutineDispatchers
) )
} }
override fun syncUpdateFlow(): Flow<Long> { override fun syncUpdateFlow(): Flow<Long> {
//TODO branch this somehow... return syncUpdateFlow
return emptyFlow()
} }
override fun timeline(): MatrixTimeline { override fun timeline(): MatrixTimeline {
return timeline return timeline
} }
override fun close() { override fun open(): Result<Unit> {
innerRoom.destroy() if (isInit.value) return Result.failure(IllegalStateException("Listener already registered"))
roomListItem.destroy() val settings = RoomSubscription(
requiredState = listOf(
RequiredState(key = EventType.STATE_ROOM_CANONICAL_ALIAS, value = ""),
RequiredState(key = EventType.STATE_ROOM_TOPIC, value = ""),
RequiredState(key = EventType.STATE_ROOM_JOIN_RULES, value = ""),
RequiredState(key = EventType.STATE_ROOM_POWER_LEVELS, value = ""),
),
timelineLimit = null
)
roomListItem.subscribe(settings)
innerRoom.timelineDiffFlow { initialList ->
timeline.postItems(initialList)
}.onEach {
syncUpdateFlow.value = systemClock.epochMillis()
timeline.postDiff(it)
}.launchIn(roomCoroutineScope)
roomCoroutineScope.launch {
fetchMembers()
}
isInit.value = true
return Result.success(Unit)
} }
override val roomId = RoomId(innerRoom.id()) override fun close() {
if(isInit.value) {
isInit.value = false
roomCoroutineScope.cancel()
roomListItem.unsubscribe()
innerRoom.destroy()
roomListItem.destroy()
}
}
override val name: String? override val name: String?
get() { get() {
@ -264,7 +301,7 @@ class RustMatrixRoom(
} }
} }
override suspend fun cancelSend(transactionId: String): Result<Unit> = override suspend fun cancelSend(transactionId: String): Result<Unit> =
withContext(coroutineDispatchers.io) { withContext(coroutineDispatchers.io) {
runCatching { runCatching {
innerRoom.cancelSend(transactionId) innerRoom.cancelSend(transactionId)
@ -299,4 +336,10 @@ class RustMatrixRoom(
innerRoom.setTopic(topic) innerRoom.setTopic(topic)
} }
} }
private suspend fun fetchMembers() = withContext(coroutineDispatchers.io) {
runCatching {
innerRoom.fetchMembers()
}
}
} }

View file

@ -20,8 +20,6 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.room.RoomSummary import io.element.android.libraries.matrix.api.room.RoomSummary
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@ -33,24 +31,21 @@ import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate
import org.matrix.rustcomponents.sdk.RoomListEntry import org.matrix.rustcomponents.sdk.RoomListEntry
import org.matrix.rustcomponents.sdk.RoomListInput import org.matrix.rustcomponents.sdk.RoomListInput
import org.matrix.rustcomponents.sdk.RoomListRange import org.matrix.rustcomponents.sdk.RoomListRange
import org.matrix.rustcomponents.sdk.SlidingSyncListLoadingState
import timber.log.Timber import timber.log.Timber
import java.io.Closeable
import java.util.UUID import java.util.UUID
internal class RustRoomSummaryDataSource( internal class RustRoomSummaryDataSource(
private val roomList: RoomList, private val roomList: RoomList,
private val sessionCoroutineScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers, private val coroutineDispatchers: CoroutineDispatchers,
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(), private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
) : RoomSummaryDataSource, Closeable { ) : RoomSummaryDataSource {
private val coroutineScope = CoroutineScope(SupervisorJob() + coroutineDispatchers.io)
private val roomSummaries = MutableStateFlow<List<RoomSummary>>(emptyList()) private val roomSummaries = MutableStateFlow<List<RoomSummary>>(emptyList())
private val loadingState = MutableStateFlow(RoomSummaryDataSource.LoadingState.NotLoaded) private val loadingState = MutableStateFlow(RoomSummaryDataSource.LoadingState.NotLoaded)
fun subscribeIfNeeded() { fun subscribeIfNeeded() {
coroutineScope.launch { sessionCoroutineScope.launch {
roomList.roomListEntriesUpdateFlow { roomListEntries -> roomList.roomListEntriesUpdateFlow { roomListEntries ->
val summaries = roomListEntries.map(::buildSummaryForRoomListEntry) val summaries = roomListEntries.map(::buildSummaryForRoomListEntry)
updateRoomSummaries { updateRoomSummaries {
@ -64,10 +59,6 @@ internal class RustRoomSummaryDataSource(
} }
} }
override fun close() {
coroutineScope.cancel()
}
override fun roomSummaries(): StateFlow<List<RoomSummary>> { override fun roomSummaries(): StateFlow<List<RoomSummary>> {
return roomSummaries return roomSummaries
} }
@ -78,7 +69,7 @@ internal class RustRoomSummaryDataSource(
override fun setSlidingSyncRange(range: IntRange) { override fun setSlidingSyncRange(range: IntRange) {
Timber.v("setVisibleRange=$range") Timber.v("setVisibleRange=$range")
coroutineScope.launch { sessionCoroutineScope.launch {
val ranges = listOf(RoomListRange(range.first.toUInt(), range.last.toUInt())) val ranges = listOf(RoomListRange(range.first.toUInt(), range.last.toUInt()))
roomList.applyInput( roomList.applyInput(
RoomListInput.Viewport(ranges) RoomListInput.Viewport(ranges)
@ -148,7 +139,8 @@ internal class RustRoomSummaryDataSource(
} }
private fun buildRoomSummaryForIdentifier(identifier: String): RoomSummary { private fun buildRoomSummaryForIdentifier(identifier: String): RoomSummary {
return roomList.room(identifier).use { roomListItem -> val roomListItem = roomList.roomOrNull(identifier) ?: return RoomSummary.Empty(identifier)
return roomListItem.use {
roomListItem.fullRoom().use { fullRoom -> roomListItem.fullRoom().use { fullRoom ->
RoomSummary.Filled( RoomSummary.Filled(
details = roomSummaryDetailsFactory.create(roomListItem, fullRoom) details = roomSummaryDetailsFactory.create(roomListItem, fullRoom)

View file

@ -36,7 +36,7 @@ internal class MatrixTimelineDiffProcessor(
private val timelineItemFactory: MatrixTimelineItemMapper, private val timelineItemFactory: MatrixTimelineItemMapper,
) { ) {
fun onUpdate(diff: TimelineDiff) { fun postDiff(diff: TimelineDiff) {
coroutineScope.launch { coroutineScope.launch {
updateTimelineItems { updateTimelineItems {
applyDiff(diff) applyDiff(diff)
@ -122,4 +122,5 @@ internal class MatrixTimelineDiffProcessor(
private fun TimelineItem.asMatrixTimelineItem(): MatrixTimelineItem { private fun TimelineItem.asMatrixTimelineItem(): MatrixTimelineItem {
return timelineItemFactory.map(this) return timelineItemFactory.map(this)
} }
} }

View file

@ -21,40 +21,30 @@ import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
import io.element.android.libraries.matrix.impl.timeline.item.event.EventMessageMapper import io.element.android.libraries.matrix.impl.timeline.item.event.EventMessageMapper
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper
import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper
import io.element.android.libraries.matrix.impl.util.TaskHandleBag
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.PaginationOptions import org.matrix.rustcomponents.sdk.PaginationOptions
import org.matrix.rustcomponents.sdk.RequiredState
import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.TimelineDiff
import org.matrix.rustcomponents.sdk.RoomSubscription import org.matrix.rustcomponents.sdk.TimelineItem
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
class RustMatrixTimeline( class RustMatrixTimeline(
coroutineScope: CoroutineScope,
private val matrixRoom: MatrixRoom, private val matrixRoom: MatrixRoom,
private val innerRoom: Room, private val innerRoom: Room,
private val roomListItem: RoomListItem,
private val coroutineScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers, private val coroutineDispatchers: CoroutineDispatchers,
) : MatrixTimeline { ) : MatrixTimeline {
private val isInit = AtomicBoolean(false)
private val timelineItems: MutableStateFlow<List<MatrixTimelineItem>> = private val timelineItems: MutableStateFlow<List<MatrixTimelineItem>> =
MutableStateFlow(emptyList()) MutableStateFlow(emptyList())
@ -81,7 +71,6 @@ class RustMatrixTimeline(
timelineItemFactory = timelineItemFactory, timelineItemFactory = timelineItemFactory,
) )
private val listenerTokens = TaskHandleBag()
override fun paginationState(): StateFlow<MatrixTimeline.PaginationState> { override fun paginationState(): StateFlow<MatrixTimeline.PaginationState> {
return paginationState return paginationState
} }
@ -91,38 +80,12 @@ class RustMatrixTimeline(
return timelineItems.sample(50) return timelineItems.sample(50)
} }
override fun initialize() { internal fun postItems(items: List<TimelineItem>) {
Timber.v("Init timeline for room ${matrixRoom.roomId}") timelineItems.value = items.map(timelineItemFactory::map)
coroutineScope.launch {
subscribeAndAddListener(this)
.onSuccess {
isInit.set(true)
}
.onFailure {
Timber.e("Failed adding timeline listener on room with identifier: ${matrixRoom.roomId})")
}
}
} }
override fun dispose() { internal fun postDiff(timelineDiff: TimelineDiff) {
Timber.v("Dispose timeline for room ${matrixRoom.roomId}") timelineDiffProcessor.postDiff(timelineDiff)
listenerTokens.dispose()
isInit.set(false)
}
/**
* @param message markdown message
*/
override suspend fun sendMessage(message: String): Result<Unit> {
return matrixRoom.sendMessage(message)
}
override suspend fun editMessage(originalEventId: EventId, message: String): Result<Unit> {
return matrixRoom.editMessage(originalEventId, message = message)
}
override suspend fun replyMessage(inReplyToEventId: EventId, message: String): Result<Unit> {
return matrixRoom.replyMessage(inReplyToEventId, message)
} }
override suspend fun fetchDetailsForEvent(eventId: EventId): Result<Unit> = withContext(coroutineDispatchers.io) { override suspend fun fetchDetailsForEvent(eventId: EventId): Result<Unit> = withContext(coroutineDispatchers.io) {
@ -134,9 +97,6 @@ class RustMatrixTimeline(
override suspend fun paginateBackwards(requestSize: Int, untilNumberOfItems: Int): Result<Unit> = withContext(coroutineDispatchers.io) { override suspend fun paginateBackwards(requestSize: Int, untilNumberOfItems: Int): Result<Unit> = withContext(coroutineDispatchers.io) {
runCatching { runCatching {
Timber.v("Start back paginating for room ${matrixRoom.roomId} ") Timber.v("Start back paginating for room ${matrixRoom.roomId} ")
if (!isInit.get()) {
throw IllegalStateException("Timeline is not init yet")
}
val paginationOptions = PaginationOptions.UntilNumItems( val paginationOptions = PaginationOptions.UntilNumItems(
eventLimit = requestSize.toUShort(), eventLimit = requestSize.toUShort(),
items = untilNumberOfItems.toUShort(), items = untilNumberOfItems.toUShort(),
@ -149,30 +109,4 @@ class RustMatrixTimeline(
Timber.v("Success back paginating for room ${matrixRoom.roomId}") Timber.v("Success back paginating for room ${matrixRoom.roomId}")
} }
} }
private fun subscribeAndAddListener(coroutineScope: CoroutineScope): Result<Unit> {
return runCatching {
val settings = RoomSubscription(
requiredState = listOf(
RequiredState(key = EventType.STATE_ROOM_CANONICAL_ALIAS, value = ""),
RequiredState(key = EventType.STATE_ROOM_TOPIC, value = ""),
RequiredState(key = EventType.STATE_ROOM_JOIN_RULES, value = ""),
RequiredState(key = EventType.STATE_ROOM_POWER_LEVELS, value = ""),
),
timelineLimit = null
)
roomListItem.subscribe(settings)
innerRoom.timelineDiffFlow { initialList ->
timelineItems.value = initialList.map(timelineItemFactory::map)
}.onEach {
timelineDiffProcessor.onUpdate(it)
}.launchIn(coroutineScope)
}
}
private suspend fun fetchMembers() = withContext(coroutineDispatchers.io) {
runCatching {
innerRoom.fetchMembers()
}
}
} }

View file

@ -71,18 +71,6 @@ class FakeMatrixTimeline(
isInitialized = false isInitialized = false
} }
override suspend fun sendMessage(message: String): Result<Unit> {
return Result.success(Unit)
}
override suspend fun editMessage(originalEventId: EventId, message: String): Result<Unit> {
return Result.success(Unit)
}
override suspend fun replyMessage(inReplyToEventId: EventId, message: String): Result<Unit> {
return Result.success(Unit)
}
override suspend fun fetchDetailsForEvent(eventId: EventId): Result<Unit> { override suspend fun fetchDetailsForEvent(eventId: EventId): Result<Unit> {
return Result.success(Unit) return Result.success(Unit)
} }

View file

@ -42,7 +42,7 @@ class MainActivity : ComponentActivity() {
RustMatrixAuthenticationService( RustMatrixAuthenticationService(
context = applicationContext, context = applicationContext,
baseDirectory = baseDirectory, baseDirectory = baseDirectory,
coroutineScope = Singleton.appScope, appCoroutineScope = Singleton.appScope,
coroutineDispatchers = Singleton.coroutineDispatchers, coroutineDispatchers = Singleton.coroutineDispatchers,
sessionStore = InMemorySessionStore(), sessionStore = InMemorySessionStore(),
clock = DefaultSystemClock() clock = DefaultSystemClock()