Room/Timeline: simplify the apis
This commit is contained in:
parent
ea21ea2ace
commit
7c8df186f6
13 changed files with 134 additions and 150 deletions
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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?>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue