Merge branch 'develop' into jonny/timeline-poll-edited

This commit is contained in:
Benoit Marty 2023-12-04 16:01:09 +01:00 committed by GitHub
commit f6ec76b5ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
602 changed files with 5298 additions and 1508 deletions

View file

@ -77,6 +77,7 @@ import org.matrix.rustcomponents.sdk.BackupState
import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.ClientDelegate
import org.matrix.rustcomponents.sdk.NotificationProcessSetup
import org.matrix.rustcomponents.sdk.PowerLevels
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.TaskHandle
@ -214,6 +215,7 @@ class RustMatrixClient constructor(
isKeyBackupEnabled = client.encryption().backupState() == BackupState.ENABLED,
roomListItem = roomListItem,
innerRoom = fullRoom,
innerTimeline = fullRoom.timeline(),
roomNotificationSettingsService = notificationSettingsService,
sessionCoroutineScope = sessionCoroutineScope,
coroutineDispatchers = dispatchers,
@ -239,9 +241,8 @@ class RustMatrixClient constructor(
}
}
override suspend fun findDM(userId: UserId): MatrixRoom? {
val roomId = client.getDmRoom(userId.value)?.use { RoomId(it.id()) }
return roomId?.let { getRoom(it) }
override suspend fun findDM(userId: UserId): RoomId? {
return client.getDmRoom(userId.value)?.use { RoomId(it.id()) }
}
override suspend fun ignoreUser(userId: UserId): Result<Unit> = withContext(sessionDispatcher) {
@ -274,6 +275,7 @@ class RustMatrixClient constructor(
},
invite = createRoomParams.invite?.map { it.value },
avatar = createRoomParams.avatar,
powerLevelContentOverride = defaultRoomCreationPowerLevels,
)
val roomId = RoomId(client.createRoom(rustParams))
@ -296,7 +298,7 @@ class RustMatrixClient constructor(
isDirect = true,
visibility = RoomVisibility.PRIVATE,
preset = RoomPreset.TRUSTED_PRIVATE_CHAT,
invite = listOf(userId)
invite = listOf(userId),
)
return createRoom(createRoomParams)
}
@ -481,3 +483,18 @@ class RustMatrixClient constructor(
}
}
private val defaultRoomCreationPowerLevels = PowerLevels(
usersDefault = null,
eventsDefault = null,
stateDefault = null,
ban = null,
kick = null,
redact = null,
invite = null,
notifications = null,
users = mapOf(),
events = mapOf(
"m.call.member" to 0,
"org.matrix.msc3401.call.member" to 0,
)
)

View file

@ -22,11 +22,6 @@ import org.matrix.rustcomponents.sdk.BackupUploadState as RustBackupUploadState
class BackupUploadStateMapper {
fun map(rustEnableProgress: RustBackupUploadState): BackupUploadState {
return when (rustEnableProgress) {
is RustBackupUploadState.CheckingIfUploadNeeded ->
BackupUploadState.CheckingIfUploadNeeded(
backedUpCount = rustEnableProgress.backedUpCount.toInt(),
totalCount = rustEnableProgress.totalCount.toInt(),
)
RustBackupUploadState.Done ->
BackupUploadState.Done
is RustBackupUploadState.Uploading ->

View file

@ -22,12 +22,14 @@ import org.matrix.rustcomponents.sdk.EnableRecoveryProgress as RustEnableRecover
class EnableRecoveryProgressMapper {
fun map(rustEnableProgress: RustEnableRecoveryProgress): EnableRecoveryProgress {
return when (rustEnableProgress) {
is RustEnableRecoveryProgress.CreatingRecoveryKey -> EnableRecoveryProgress.CreatingRecoveryKey
is RustEnableRecoveryProgress.Starting -> EnableRecoveryProgress.Starting
is RustEnableRecoveryProgress.CreatingBackup -> EnableRecoveryProgress.CreatingBackup
is RustEnableRecoveryProgress.CreatingRecoveryKey -> EnableRecoveryProgress.CreatingRecoveryKey
is RustEnableRecoveryProgress.BackingUp -> EnableRecoveryProgress.BackingUp(
backedUpCount = rustEnableProgress.backedUpCount.toInt(),
totalCount = rustEnableProgress.totalCount.toInt(),
)
is RustEnableRecoveryProgress.RoomKeyUploadError -> EnableRecoveryProgress.RoomKeyUploadError
is RustEnableRecoveryProgress.Done -> EnableRecoveryProgress.Done(
recoveryKey = rustEnableProgress.recoveryKey
)

View file

@ -86,7 +86,7 @@ internal class RustEncryptionService(
}
}.stateIn(sessionCoroutineScope, SharingStarted.Eagerly, RecoveryState.WAITING_FOR_SYNC)
override val enableRecoveryProgressStateFlow: MutableStateFlow<EnableRecoveryProgress> = MutableStateFlow(EnableRecoveryProgress.Unknown)
override val enableRecoveryProgressStateFlow: MutableStateFlow<EnableRecoveryProgress> = MutableStateFlow(EnableRecoveryProgress.Starting)
fun start() {
service.backupStateListener(object : BackupStateListener {
@ -181,7 +181,7 @@ internal class RustEncryptionService(
override suspend fun fixRecoveryIssues(recoveryKey: String): Result<Unit> = withContext(dispatchers.io) {
runCatching {
service.fixRecoveryIssues(recoveryKey)
service.recover(recoveryKey)
}
}
}

View file

@ -23,13 +23,13 @@ class SteadyStateExceptionMapper {
fun map(data: RustSteadyStateException): SteadyStateException {
return when (data) {
is RustSteadyStateException.BackupDisabled -> SteadyStateException.BackupDisabled(
message = data.message
message = data.message.orEmpty()
)
is RustSteadyStateException.Connection -> SteadyStateException.Connection(
message = data.message
message = data.message.orEmpty()
)
is RustSteadyStateException.Laged -> SteadyStateException.Lagged(
message = data.message
is RustSteadyStateException.Lagged -> SteadyStateException.Lagged(
message = data.message.orEmpty()
)
}
}

View file

@ -17,15 +17,18 @@
package io.element.android.libraries.matrix.impl.media
import io.element.android.libraries.matrix.api.media.AudioDetails
import kotlinx.collections.immutable.toImmutableList
import kotlin.time.toJavaDuration
import kotlin.time.toKotlinDuration
import org.matrix.rustcomponents.sdk.UnstableAudioDetailsContent as RustAudioDetails
fun RustAudioDetails.map(): AudioDetails = AudioDetails(
duration = duration,
waveform = waveform.fromMSC3246range(),
duration = duration.toKotlinDuration(),
waveform = waveform.fromMSC3246range().toImmutableList(),
)
fun AudioDetails.map(): RustAudioDetails = RustAudioDetails(
duration = duration,
duration = duration.toJavaDuration(),
waveform = waveform.toMSC3246range()
)

View file

@ -17,16 +17,18 @@
package io.element.android.libraries.matrix.impl.media
import io.element.android.libraries.matrix.api.media.AudioInfo
import kotlin.time.toJavaDuration
import kotlin.time.toKotlinDuration
import org.matrix.rustcomponents.sdk.AudioInfo as RustAudioInfo
fun RustAudioInfo.map(): AudioInfo = AudioInfo(
duration = duration,
duration = duration?.toKotlinDuration(),
size = size?.toLong(),
mimetype = mimetype
)
fun AudioInfo.map(): RustAudioInfo = RustAudioInfo(
duration = duration,
duration = duration?.toJavaDuration(),
size = size?.toULong(),
mimetype = mimetype,
)

View file

@ -82,7 +82,7 @@ class RustMediaLoader(
val mediaFile = innerClient.getMediaFile(
mediaSource = mediaSource,
body = body,
mimeType = mimeType ?: MimeTypes.OctetStream,
mimeType = mimeType?.takeIf { MimeTypes.hasSubtype(it) } ?: MimeTypes.OctetStream,
useCache = useCache,
tempDir = cacheDirectory.path,
)

View file

@ -17,10 +17,12 @@
package io.element.android.libraries.matrix.impl.media
import io.element.android.libraries.matrix.api.media.VideoInfo
import kotlin.time.toJavaDuration
import kotlin.time.toKotlinDuration
import org.matrix.rustcomponents.sdk.VideoInfo as RustVideoInfo
fun RustVideoInfo.map(): VideoInfo = VideoInfo(
duration = duration,
duration = duration?.toKotlinDuration(),
height = height?.toLong(),
width = width?.toLong(),
mimetype = mimetype,
@ -31,7 +33,7 @@ fun RustVideoInfo.map(): VideoInfo = VideoInfo(
)
fun VideoInfo.map(): RustVideoInfo = RustVideoInfo(
duration = duration,
duration = duration?.toJavaDuration(),
height = height?.toULong(),
width = width?.toULong(),
mimetype = mimetype,

View file

@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
import kotlinx.collections.immutable.toImmutableList
import org.matrix.rustcomponents.sdk.use
import org.matrix.rustcomponents.sdk.Membership as RustMembership
import org.matrix.rustcomponents.sdk.RoomInfo as RustRoomInfo
@ -40,7 +41,7 @@ class MatrixRoomInfoMapper(
isSpace = it.isSpace,
isTombstoned = it.isTombstoned,
canonicalAlias = it.canonicalAlias,
alternativeAliases = it.alternativeAliases,
alternativeAliases = it.alternativeAliases.toImmutableList(),
currentUserMembership = it.membership.map(),
latestEvent = it.latestEvent?.use (timelineItemMapper::map),
inviter = it.inviter?.use(RoomMemberMapper::map),
@ -51,7 +52,7 @@ class MatrixRoomInfoMapper(
notificationCount = it.notificationCount.toLong(),
userDefinedNotificationMode = it.userDefinedNotificationMode?.map(),
hasRoomCall = it.hasRoomCall,
activeRoomCallParticipants = it.activeRoomCallParticipants
activeRoomCallParticipants = it.activeRoomCallParticipants.toImmutableList()
)
}
}

View file

@ -24,8 +24,8 @@ import io.element.android.libraries.matrix.impl.roomlist.roomOrNull
import io.element.android.libraries.matrix.impl.timeline.runWithTimelineListenerRegistered
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.withTimeout
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomListService
import org.matrix.rustcomponents.sdk.Timeline
import kotlin.time.Duration.Companion.milliseconds
/**
@ -37,19 +37,19 @@ class RoomContentForwarder(
) {
/**
* Forwards the event with the given [eventId] from the [fromRoom] to the given [toRoomIds].
* @param fromRoom the room to forward the event from
* Forwards the event with the given [eventId] from the [fromTimeline] to the given [toRoomIds].
* @param fromTimeline the room to forward the event from
* @param eventId the id of the event to forward
* @param toRoomIds the ids of the rooms to forward the event to
* @param timeoutMs the maximum time in milliseconds to wait for the event to be sent to a room
*/
suspend fun forward(
fromRoom: Room,
fromTimeline: Timeline,
eventId: EventId,
toRoomIds: List<RoomId>,
timeoutMs: Long = 5000L
) {
val content = fromRoom.getTimelineEventContentByEventId(eventId.value)
val content = fromTimeline.getTimelineEventContentByEventId(eventId.value)
val targetSlidingSyncRooms = toRoomIds.mapNotNull { roomId -> roomListService.roomOrNull(roomId.value) }
val targetRooms = targetSlidingSyncRooms.mapNotNull { slidingSyncRoom -> slidingSyncRoom.use { it.fullRoom() } }
val failedForwardingTo = mutableSetOf<RoomId>()
@ -57,9 +57,9 @@ class RoomContentForwarder(
room.use { targetRoom ->
runCatching {
// Sending a message requires a registered timeline listener
targetRoom.runWithTimelineListenerRegistered {
targetRoom.timeline().runWithTimelineListenerRegistered {
withTimeout(timeoutMs.milliseconds) {
targetRoom.send(content)
targetRoom.timeline().send(content)
}
}
}

View file

@ -57,6 +57,7 @@ import io.element.android.libraries.matrix.impl.widget.RustWidgetDriver
import io.element.android.libraries.matrix.impl.widget.generateWidgetWebViewUrl
import io.element.android.libraries.sessionstorage.api.SessionData
import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -76,6 +77,7 @@ import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.RoomMember
import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation
import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle
import org.matrix.rustcomponents.sdk.Timeline
import org.matrix.rustcomponents.sdk.WidgetCapabilities
import org.matrix.rustcomponents.sdk.WidgetCapabilitiesProvider
import org.matrix.rustcomponents.sdk.messageEventContentFromHtml
@ -87,9 +89,10 @@ import java.io.File
@OptIn(ExperimentalCoroutinesApi::class)
class RustMatrixRoom(
override val sessionId: SessionId,
isKeyBackupEnabled: Boolean,
private val isKeyBackupEnabled: Boolean,
private val roomListItem: RoomListItem,
private val innerRoom: Room,
private val innerTimeline: Timeline,
private val roomNotificationSettingsService: RustNotificationSettingsService,
sessionCoroutineScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers,
@ -130,7 +133,7 @@ class RustMatrixRoom(
override val timeline = RustMatrixTimeline(
isKeyBackupEnabled = isKeyBackupEnabled,
matrixRoom = this,
innerRoom = innerRoom,
innerTimeline = innerTimeline,
roomCoroutineScope = roomCoroutineScope,
dispatcher = roomDispatcher,
lastLoginTimestamp = sessionData.loginTimestamp,
@ -147,6 +150,7 @@ class RustMatrixRoom(
override fun destroy() {
roomCoroutineScope.cancel()
innerTimeline.destroy()
innerRoom.destroy()
roomListItem.destroy()
specialModeEventTimelineItem?.destroy()
@ -195,7 +199,7 @@ class RustMatrixRoom(
override suspend fun updateMembers(): Result<Unit> = withContext(roomMembersDispatcher) {
val currentState = _membersStateFlow.value
val currentMembers = currentState.roomMembers()
val currentMembers = currentState.roomMembers()?.toImmutableList()
_membersStateFlow.value = MatrixRoomMembersState.Pending(prevRoomMembers = currentMembers)
var rustMembers: List<RoomMember>? = null
try {
@ -210,7 +214,7 @@ class RustMatrixRoom(
}
}
val mappedMembers = rustMembers.parallelMap(RoomMemberMapper::map)
_membersStateFlow.value = MatrixRoomMembersState.Ready(mappedMembers)
_membersStateFlow.value = MatrixRoomMembersState.Ready(mappedMembers.toImmutableList())
Result.success(Unit)
} catch (exception: CancellationException) {
_membersStateFlow.value = MatrixRoomMembersState.Error(prevRoomMembers = currentMembers, failure = exception)
@ -254,7 +258,7 @@ class RustMatrixRoom(
override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit> = withContext(roomDispatcher) {
messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()).use { content ->
runCatching {
innerRoom.send(content)
innerTimeline.send(content)
}
}
}
@ -269,9 +273,9 @@ class RustMatrixRoom(
withContext(roomDispatcher) {
if (originalEventId != null) {
runCatching {
val editedEvent = specialModeEventTimelineItem ?: innerRoom.getEventTimelineItemByEventId(originalEventId.value)
val editedEvent = specialModeEventTimelineItem ?: innerTimeline.getEventTimelineItemByEventId(originalEventId.value)
editedEvent.use {
innerRoom.edit(
innerTimeline.edit(
newContent = messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()),
editItem = it,
)
@ -281,7 +285,7 @@ class RustMatrixRoom(
} else {
runCatching {
transactionId?.let { cancelSend(it) }
innerRoom.send(messageEventContentFromParts(body, htmlBody))
innerTimeline.send(messageEventContentFromParts(body, htmlBody))
}
}
}
@ -292,15 +296,15 @@ class RustMatrixRoom(
runCatching {
specialModeEventTimelineItem?.destroy()
specialModeEventTimelineItem = null
specialModeEventTimelineItem = eventId?.let { innerRoom.getEventTimelineItemByEventId(it.value) }
specialModeEventTimelineItem = eventId?.let { innerTimeline.getEventTimelineItemByEventId(it.value) }
}
}
override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit> = withContext(roomDispatcher) {
runCatching {
val inReplyTo = specialModeEventTimelineItem ?: innerRoom.getEventTimelineItemByEventId(eventId.value)
val inReplyTo = specialModeEventTimelineItem ?: innerTimeline.getEventTimelineItemByEventId(eventId.value)
inReplyTo.use { eventTimelineItem ->
innerRoom.sendReply(messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()), eventTimelineItem)
innerTimeline.sendReply(messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()), eventTimelineItem)
}
specialModeEventTimelineItem = null
}
@ -360,39 +364,45 @@ class RustMatrixRoom(
}
}
override suspend fun canUserJoinCall(userId: UserId): Result<Boolean> {
return runCatching {
innerRoom.canUserSendState(userId.value, StateEventType.ROOM_MEMBER_EVENT.map())
}
}
override suspend fun sendImage(file: File, thumbnailFile: File, imageInfo: ImageInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler> {
return sendAttachment(listOf(file, thumbnailFile)) {
innerRoom.sendImage(file.path, thumbnailFile.path, imageInfo.map(), progressCallback?.toProgressWatcher())
innerTimeline.sendImage(file.path, thumbnailFile.path, imageInfo.map(), progressCallback?.toProgressWatcher())
}
}
override suspend fun sendVideo(file: File, thumbnailFile: File, videoInfo: VideoInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler> {
return sendAttachment(listOf(file, thumbnailFile)) {
innerRoom.sendVideo(file.path, thumbnailFile.path, videoInfo.map(), progressCallback?.toProgressWatcher())
innerTimeline.sendVideo(file.path, thumbnailFile.path, videoInfo.map(), progressCallback?.toProgressWatcher())
}
}
override suspend fun sendAudio(file: File, audioInfo: AudioInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler> {
return sendAttachment(listOf(file)) {
innerRoom.sendAudio(file.path, audioInfo.map(), progressCallback?.toProgressWatcher())
innerTimeline.sendAudio(file.path, audioInfo.map(), progressCallback?.toProgressWatcher())
}
}
override suspend fun sendFile(file: File, fileInfo: FileInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler> {
return sendAttachment(listOf(file)) {
innerRoom.sendFile(file.path, fileInfo.map(), progressCallback?.toProgressWatcher())
innerTimeline.sendFile(file.path, fileInfo.map(), progressCallback?.toProgressWatcher())
}
}
override suspend fun toggleReaction(emoji: String, eventId: EventId): Result<Unit> = withContext(roomDispatcher) {
runCatching {
innerRoom.toggleReaction(key = emoji, eventId = eventId.value)
innerTimeline.toggleReaction(key = emoji, eventId = eventId.value)
}
}
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> = withContext(roomDispatcher) {
runCatching {
roomContentForwarder.forward(fromRoom = innerRoom, eventId = eventId, toRoomIds = roomIds)
roomContentForwarder.forward(fromTimeline = innerTimeline, eventId = eventId, toRoomIds = roomIds)
}.onFailure {
Timber.e(it)
}
@ -400,13 +410,13 @@ class RustMatrixRoom(
override suspend fun retrySendMessage(transactionId: TransactionId): Result<Unit> = withContext(roomDispatcher) {
runCatching {
innerRoom.retrySend(transactionId.value)
innerTimeline.retrySend(transactionId.value)
}
}
override suspend fun cancelSend(transactionId: TransactionId): Result<Unit> = withContext(roomDispatcher) {
runCatching {
innerRoom.cancelSend(transactionId.value)
innerTimeline.cancelSend(transactionId.value)
}
}
@ -451,7 +461,7 @@ class RustMatrixRoom(
assetType: AssetType?,
): Result<Unit> = withContext(roomDispatcher) {
runCatching {
innerRoom.sendLocation(
innerTimeline.sendLocation(
body = body,
geoUri = geoUri,
description = description,
@ -468,7 +478,7 @@ class RustMatrixRoom(
pollKind: PollKind,
): Result<Unit> = withContext(roomDispatcher) {
runCatching {
innerRoom.createPoll(
innerTimeline.createPoll(
question = question,
answers = answers,
maxSelections = maxSelections.toUByte(),
@ -486,11 +496,11 @@ class RustMatrixRoom(
): Result<Unit> = withContext(roomDispatcher) {
runCatching {
val pollStartEvent =
innerRoom.getEventTimelineItemByEventId(
innerTimeline.getEventTimelineItemByEventId(
eventId = pollStartId.value
)
pollStartEvent.use {
innerRoom.editPoll(
innerTimeline.editPoll(
question = question,
answers = answers,
maxSelections = maxSelections.toUByte(),
@ -506,7 +516,7 @@ class RustMatrixRoom(
answers: List<String>
): Result<Unit> = withContext(roomDispatcher) {
runCatching {
innerRoom.sendPollResponse(
innerTimeline.sendPollResponse(
pollStartId = pollStartId.value,
answers = answers,
)
@ -518,7 +528,7 @@ class RustMatrixRoom(
text: String
): Result<Unit> = withContext(roomDispatcher) {
runCatching {
innerRoom.endPoll(
innerTimeline.endPoll(
pollStartId = pollStartId.value,
text = text,
)
@ -531,7 +541,7 @@ class RustMatrixRoom(
waveform: List<Float>,
progressCallback: ProgressCallback?,
): Result<MediaUploadHandler> = sendAttachment(listOf(file)) {
innerRoom.sendVoiceMessage(
innerTimeline.sendVoiceMessage(
url = file.path,
audioInfo = audioInfo.map(),
waveform = waveform.toMSC3246range(),
@ -560,7 +570,17 @@ class RustMatrixRoom(
)
}
private suspend fun sendAttachment(files: List<File>, handle: () -> SendAttachmentJoinHandle): Result<MediaUploadHandler> {
override suspend fun pollHistory() = RustMatrixTimeline(
isKeyBackupEnabled = isKeyBackupEnabled,
matrixRoom = this,
innerTimeline = innerRoom.pollHistory(),
roomCoroutineScope = roomCoroutineScope,
dispatcher = roomDispatcher,
lastLoginTimestamp = sessionData.loginTimestamp,
onNewSyncedEvent = { _syncUpdateFlow.value = systemClock.epochMillis() }
)
private fun sendAttachment(files: List<File>, handle: () -> SendAttachmentJoinHandle): Result<MediaUploadHandler> {
return runCatching {
MediaUploadHandlerImpl(files, handle())
}

View file

@ -65,7 +65,6 @@ fun RoomListInterface.loadingStateFlow(): Flow<RoomListLoadingState> =
internal fun RoomListInterface.entriesFlow(
pageSize: Int,
numberOfPages: Int,
roomListDynamicEvents: Flow<RoomListDynamicEvents>,
initialFilterKind: RoomListEntriesDynamicFilterKind
): Flow<List<RoomListEntriesUpdate>> =
@ -84,9 +83,7 @@ internal fun RoomListInterface.entriesFlow(
controller.setFilter(controllerEvents.filter)
}
is RoomListDynamicEvents.LoadMore -> {
repeat(numberOfPages) {
controller.addOnePage()
}
controller.addOnePage()
}
is RoomListDynamicEvents.Reset -> {
controller.resetToOnePage()

View file

@ -23,6 +23,7 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@ -39,57 +40,28 @@ internal class RoomListFactory(
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
) {
/**
* Creates a room list that will load all rooms in a single page.
* It mimics the usage of the old api.
*/
fun createRoomList(
innerProvider: suspend () -> InnerRoomList,
): RoomList {
return createRustRoomList(
pageSize = Int.MAX_VALUE,
numberOfPages = 1,
initialFilterKind = RoomListEntriesDynamicFilterKind.AllNonLeft,
innerRoomListProvider = innerProvider
)
}
/**
* Creates a room list that can be used to load more rooms and filter them dynamically.
*/
fun createDynamicRoomList(
pageSize: Int = DynamicRoomList.DEFAULT_PAGE_SIZE,
pagesToLoad: Int = DynamicRoomList.DEFAULT_PAGES_TO_LOAD,
initialFilter: DynamicRoomList.Filter = DynamicRoomList.Filter.None,
fun createRoomList(
pageSize: Int,
initialFilter: DynamicRoomList.Filter = DynamicRoomList.Filter.All,
innerProvider: suspend () -> InnerRoomList
): DynamicRoomList {
return createRustRoomList(
pageSize = pageSize,
numberOfPages = pagesToLoad,
initialFilterKind = initialFilter.toRustFilter(),
innerRoomListProvider = innerProvider
)
}
private fun createRustRoomList(
pageSize: Int,
numberOfPages: Int,
initialFilterKind: RoomListEntriesDynamicFilterKind,
innerRoomListProvider: suspend () -> InnerRoomList
): RustDynamicRoomList {
val loadingStateFlow: MutableStateFlow<RoomList.LoadingState> = MutableStateFlow(RoomList.LoadingState.NotLoaded)
val summariesFlow = MutableStateFlow<List<RoomSummary>>(emptyList())
val processor = RoomSummaryListProcessor(summariesFlow, innerRoomListService, dispatcher, roomSummaryDetailsFactory)
val dynamicEvents = MutableSharedFlow<RoomListDynamicEvents>()
// Makes sure we don't miss any events
val dynamicEvents = MutableSharedFlow<RoomListDynamicEvents>(replay = 100)
val currentFilter = MutableStateFlow(initialFilter)
val loadedPages = MutableStateFlow(1)
var innerRoomList: InnerRoomList? = null
coroutineScope.launch(dispatcher) {
innerRoomList = innerRoomListProvider()
innerRoomList = innerProvider()
innerRoomList?.let { innerRoomList ->
innerRoomList.entriesFlow(
pageSize = pageSize,
numberOfPages = numberOfPages,
initialFilterKind = initialFilterKind,
initialFilterKind = initialFilter.toRustFilter(),
roomListDynamicEvents = dynamicEvents
).onEach { update ->
processor.postUpdate(update)
@ -105,15 +77,26 @@ internal class RoomListFactory(
}.invokeOnCompletion {
innerRoomList?.destroy()
}
return RustDynamicRoomList(summariesFlow, loadingStateFlow, dynamicEvents, processor)
return RustDynamicRoomList(
summaries = summariesFlow,
loadingState = loadingStateFlow,
currentFilter = currentFilter,
loadedPages = loadedPages,
dynamicEvents = dynamicEvents,
processor = processor,
pageSize = pageSize,
)
}
}
private class RustDynamicRoomList(
override val summaries: MutableStateFlow<List<RoomSummary>>,
override val loadingState: MutableStateFlow<RoomList.LoadingState>,
override val currentFilter: MutableStateFlow<DynamicRoomList.Filter>,
override val loadedPages: MutableStateFlow<Int>,
private val dynamicEvents: MutableSharedFlow<RoomListDynamicEvents>,
private val processor: RoomSummaryListProcessor,
override val pageSize: Int,
) : DynamicRoomList {
override suspend fun rebuildSummaries() {
@ -121,16 +104,19 @@ private class RustDynamicRoomList(
}
override suspend fun updateFilter(filter: DynamicRoomList.Filter) {
currentFilter.emit(filter)
val filterEvent = RoomListDynamicEvents.SetFilter(filter.toRustFilter())
dynamicEvents.emit(filterEvent)
}
override suspend fun loadMore() {
dynamicEvents.emit(RoomListDynamicEvents.LoadMore)
loadedPages.getAndUpdate { it + 1 }
}
override suspend fun reset() {
dynamicEvents.emit(RoomListDynamicEvents.Reset)
loadedPages.emit(1)
}
}
@ -146,6 +132,7 @@ private fun DynamicRoomList.Filter.toRustFilter(): RoomListEntriesDynamicFilterK
DynamicRoomList.Filter.All -> RoomListEntriesDynamicFilterKind.All
is DynamicRoomList.Filter.NormalizedMatchRoomName -> RoomListEntriesDynamicFilterKind.NormalizedMatchRoomName(this.pattern)
DynamicRoomList.Filter.None -> RoomListEntriesDynamicFilterKind.None
DynamicRoomList.Filter.AllNonLeft -> RoomListEntriesDynamicFilterKind.AllNonLeft
}
}

View file

@ -16,8 +16,10 @@
package io.element.android.libraries.matrix.impl.roomlist
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.RoomListService
import io.element.android.libraries.matrix.api.roomlist.loadAllIncrementally
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@ -34,20 +36,31 @@ import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicator
import timber.log.Timber
import org.matrix.rustcomponents.sdk.RoomListService as InnerRustRoomListService
private const val DEFAULT_PAGE_SIZE = 20
internal class RustRoomListService(
private val innerRoomListService: InnerRustRoomListService,
private val sessionCoroutineScope: CoroutineScope,
roomListFactory: RoomListFactory,
) : RoomListService {
override val allRooms: RoomList = roomListFactory.createRoomList {
override val allRooms: DynamicRoomList = roomListFactory.createRoomList(
pageSize = DEFAULT_PAGE_SIZE,
initialFilter = DynamicRoomList.Filter.AllNonLeft,
) {
innerRoomListService.allRooms()
}
override val invites: RoomList = roomListFactory.createRoomList {
override val invites: RoomList = roomListFactory.createRoomList(
pageSize = Int.MAX_VALUE,
) {
innerRoomListService.invites()
}
init {
allRooms.loadAllIncrementally(sessionCoroutineScope)
}
override fun updateAllRoomsVisibleRange(range: IntRange) {
Timber.v("setVisibleRange=$range")
sessionCoroutineScope.launch {

View file

@ -29,29 +29,28 @@ import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.catch
import org.matrix.rustcomponents.sdk.BackPaginationStatus
import org.matrix.rustcomponents.sdk.BackPaginationStatusListener
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.Timeline
import org.matrix.rustcomponents.sdk.TimelineDiff
import org.matrix.rustcomponents.sdk.TimelineItem
import org.matrix.rustcomponents.sdk.TimelineListener
import timber.log.Timber
internal fun Room.timelineDiffFlow(onInitialList: suspend (List<TimelineItem>) -> Unit): Flow<List<TimelineDiff>> =
internal fun Timeline.timelineDiffFlow(onInitialList: suspend (List<TimelineItem>) -> Unit): Flow<List<TimelineDiff>> =
callbackFlow {
val listener = object : TimelineListener {
override fun onUpdate(diff: List<TimelineDiff>) {
trySendBlocking(diff)
}
}
val roomId = id()
Timber.d("Open timelineDiffFlow for room $roomId")
val result = addTimelineListener(listener)
Timber.d("Open timelineDiffFlow for TimelineInterface ${this@timelineDiffFlow}")
val result = addListener(listener)
try {
onInitialList(result.items)
} catch (exception: Exception) {
Timber.d(exception, "Catch failure in timelineDiffFlow of room $roomId")
Timber.d(exception, "Catch failure in timelineDiffFlow of TimelineInterface ${this@timelineDiffFlow}")
}
awaitClose {
Timber.d("Close timelineDiffFlow for room $roomId")
Timber.d("Close timelineDiffFlow for TimelineInterface ${this@timelineDiffFlow}")
result.itemsStream.cancelAndDestroy()
result.items.destroyAll()
}
@ -59,7 +58,7 @@ internal fun Room.timelineDiffFlow(onInitialList: suspend (List<TimelineItem>) -
Timber.d(it, "timelineDiffFlow() failed")
}.buffer(Channel.UNLIMITED)
internal fun Room.backPaginationStatusFlow(): Flow<BackPaginationStatus> =
internal fun Timeline.backPaginationStatusFlow(): Flow<BackPaginationStatus> =
mxCallbackFlow {
val listener = object : BackPaginationStatusListener {
override fun onUpdate(status: BackPaginationStatus) {
@ -71,8 +70,8 @@ internal fun Room.backPaginationStatusFlow(): Flow<BackPaginationStatus> =
}
}.buffer(Channel.UNLIMITED)
internal suspend fun Room.runWithTimelineListenerRegistered(action: suspend () -> Unit) {
val result = addTimelineListener(NoOpTimelineListener)
internal suspend fun Timeline.runWithTimelineListenerRegistered(action: suspend () -> Unit) {
val result = addListener(NoOpTimelineListener)
try {
action()
} finally {

View file

@ -46,7 +46,7 @@ import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.BackPaginationStatus
import org.matrix.rustcomponents.sdk.EventItemOrigin
import org.matrix.rustcomponents.sdk.PaginationOptions
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.Timeline
import org.matrix.rustcomponents.sdk.TimelineDiff
import org.matrix.rustcomponents.sdk.TimelineItem
import timber.log.Timber
@ -59,9 +59,9 @@ class RustMatrixTimeline(
roomCoroutineScope: CoroutineScope,
isKeyBackupEnabled: Boolean,
private val matrixRoom: MatrixRoom,
private val innerRoom: Room,
private val innerTimeline: Timeline,
private val dispatcher: CoroutineDispatcher,
private val lastLoginTimestamp: Date?,
lastLoginTimestamp: Date?,
private val onNewSyncedEvent: () -> Unit,
) : MatrixTimeline {
@ -109,7 +109,7 @@ class RustMatrixTimeline(
Timber.d("Initialize timeline for room ${matrixRoom.roomId}")
roomCoroutineScope.launch(dispatcher) {
innerRoom.timelineDiffFlow { initialList ->
innerTimeline.timelineDiffFlow { initialList ->
postItems(initialList)
}.onEach { diffs ->
if (diffs.any { diff -> diff.eventOrigin() == EventItemOrigin.SYNC }) {
@ -118,7 +118,7 @@ class RustMatrixTimeline(
postDiffs(diffs)
}.launchIn(this)
innerRoom.backPaginationStatusFlow()
innerTimeline.backPaginationStatusFlow()
.onEach {
postPaginationStatus(it)
}
@ -130,7 +130,7 @@ class RustMatrixTimeline(
private suspend fun fetchMembers() = withContext(dispatcher) {
initLatch.await()
innerRoom.fetchMembers()
innerTimeline.fetchMembers()
}
@OptIn(ExperimentalCoroutinesApi::class)
@ -188,7 +188,7 @@ class RustMatrixTimeline(
override suspend fun fetchDetailsForEvent(eventId: EventId): Result<Unit> = withContext(dispatcher) {
runCatching {
innerRoom.fetchDetailsForEvent(eventId.value)
innerTimeline.fetchDetailsForEvent(eventId.value)
}
}
@ -201,7 +201,7 @@ class RustMatrixTimeline(
items = untilNumberOfItems.toUShort(),
waitForToken = true,
)
innerRoom.paginateBackwards(paginationOptions)
innerTimeline.paginateBackwards(paginationOptions)
}.onFailure { error ->
if (error is TimelineException.CannotPaginate) {
Timber.d("Can't paginate backwards on room ${matrixRoom.roomId}, we're already at the start")
@ -219,10 +219,14 @@ class RustMatrixTimeline(
override suspend fun sendReadReceipt(eventId: EventId) = withContext(dispatcher) {
runCatching {
innerRoom.sendReadReceipt(eventId = eventId.value)
innerTimeline.sendReadReceipt(eventId = eventId.value)
}
}
override fun close() {
innerTimeline.close()
}
fun getItemById(eventId: EventId): MatrixTimelineItem.Event? {
return _timelineItems.value.firstOrNull { (it as? MatrixTimelineItem.Event)?.eventId == eventId } as? MatrixTimelineItem.Event
}

View file

@ -27,6 +27,9 @@ import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimeli
import io.element.android.libraries.matrix.api.timeline.item.event.ReactionSender
import io.element.android.libraries.matrix.api.timeline.item.event.Receipt
import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import org.matrix.rustcomponents.sdk.Reaction
import org.matrix.rustcomponents.sdk.EventItemOrigin as RustEventItemOrigin
import org.matrix.rustcomponents.sdk.EventSendState as RustEventSendState
@ -81,7 +84,7 @@ fun RustEventSendState?.map(): LocalEventSendState? {
}
}
private fun List<Reaction>?.map(): List<EventReaction> {
private fun List<Reaction>?.map(): ImmutableList<EventReaction> {
return this?.map {
EventReaction(
key = it.key,
@ -90,18 +93,20 @@ private fun List<Reaction>?.map(): List<EventReaction> {
senderId = UserId(sender.senderId),
timestamp = sender.timestamp.toLong()
)
}
}.toImmutableList()
)
} ?: emptyList()
}?.toImmutableList() ?: persistentListOf()
}
private fun Map<String, RustReceipt>.map(): List<Receipt> {
private fun Map<String, RustReceipt>.map(): ImmutableList<Receipt> {
return map {
Receipt(
userId = UserId(it.key),
timestamp = it.value.timestamp?.toLong() ?: 0
)
}.sortedByDescending { it.timestamp }
Receipt(
userId = UserId(it.key),
timestamp = it.value.timestamp?.toLong() ?: 0
)
}
.sortedByDescending { it.timestamp }
.toImmutableList()
}
private fun RustEventTimelineItemDebugInfo.map(): TimelineItemDebugInfo {

View file

@ -32,6 +32,8 @@ import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecry
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent
import io.element.android.libraries.matrix.impl.media.map
import io.element.android.libraries.matrix.impl.poll.map
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableMap
import org.matrix.rustcomponents.sdk.TimelineItemContent
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
import org.matrix.rustcomponents.sdk.use
@ -106,10 +108,10 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap
question = kind.question,
kind = kind.kind.map(),
maxSelections = kind.maxSelections,
answers = kind.answers.map { answer -> answer.map() },
answers = kind.answers.map { answer -> answer.map() }.toImmutableList(),
votes = kind.votes.mapValues { vote ->
vote.value.map { userId -> UserId(userId) }
},
vote.value.map { userId -> UserId(userId) }.toImmutableList()
}.toImmutableMap(),
endTime = kind.endTime,
isEdited = kind.hasBeenEdited,
)

View file

@ -17,13 +17,14 @@
package io.element.android.libraries.matrix.impl.usersearch
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import kotlinx.collections.immutable.toImmutableList
import org.matrix.rustcomponents.sdk.SearchUsersResults
object UserSearchResultMapper {
fun map(result: SearchUsersResults): MatrixSearchUserResults {
return MatrixSearchUserResults(
results = result.results.map(UserProfileMapper::map),
results = result.results.map(UserProfileMapper::map).toImmutableList(),
limited = result.limited,
)
}

View file

@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatu
import io.element.android.libraries.matrix.api.verification.VerificationEmoji
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
import io.element.android.libraries.matrix.impl.sync.RustSyncService
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -106,8 +107,9 @@ class RustSessionVerificationService(
override fun didReceiveVerificationData(data: List<SessionVerificationEmoji>) {
val emojis = data.map { emoji ->
emoji.use { VerificationEmoji(it.symbol(), it.description()) }
}
emoji.use { VerificationEmoji(it.symbol(), it.description()) }
}
.toImmutableList()
_verificationFlowState.value = VerificationFlowState.ReceivedVerificationData(emojis)
}