Timeline : exposes same methods as the rust type and use them by default on liveTimeline
This commit is contained in:
@@ -17,8 +17,20 @@
|
||||
package io.element.android.libraries.matrix.api.timeline
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||
import io.element.android.libraries.matrix.api.media.AudioInfo
|
||||
import io.element.android.libraries.matrix.api.media.FileInfo
|
||||
import io.element.android.libraries.matrix.api.media.ImageInfo
|
||||
import io.element.android.libraries.matrix.api.media.MediaUploadHandler
|
||||
import io.element.android.libraries.matrix.api.media.VideoInfo
|
||||
import io.element.android.libraries.matrix.api.poll.PollKind
|
||||
import io.element.android.libraries.matrix.api.room.Mention
|
||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.io.File
|
||||
|
||||
interface Timeline : AutoCloseable {
|
||||
|
||||
@@ -39,4 +51,119 @@ interface Timeline : AutoCloseable {
|
||||
suspend fun paginate(direction: PaginationDirection): Result<Boolean>
|
||||
fun paginationStatus(direction: PaginationDirection): StateFlow<PaginationStatus>
|
||||
val timelineItems: Flow<List<MatrixTimelineItem>>
|
||||
|
||||
|
||||
suspend fun sendMessage(body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit>
|
||||
|
||||
suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit>
|
||||
|
||||
suspend fun enterSpecialMode(eventId: EventId?): Result<Unit>
|
||||
|
||||
suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit>
|
||||
|
||||
suspend fun sendImage(
|
||||
file: File,
|
||||
thumbnailFile: File?,
|
||||
imageInfo: ImageInfo,
|
||||
body: String?,
|
||||
formattedBody: String?,
|
||||
progressCallback: ProgressCallback?
|
||||
): Result<MediaUploadHandler>
|
||||
|
||||
suspend fun sendVideo(
|
||||
file: File,
|
||||
thumbnailFile: File?,
|
||||
videoInfo: VideoInfo,
|
||||
body: String?,
|
||||
formattedBody: String?,
|
||||
progressCallback: ProgressCallback?
|
||||
): Result<MediaUploadHandler>
|
||||
|
||||
suspend fun sendAudio(file: File, audioInfo: AudioInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler>
|
||||
|
||||
suspend fun sendFile(file: File, fileInfo: FileInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler>
|
||||
|
||||
suspend fun toggleReaction(emoji: String, eventId: EventId): Result<Unit>
|
||||
|
||||
suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit>
|
||||
|
||||
suspend fun retrySendMessage(transactionId: TransactionId): Result<Unit>
|
||||
|
||||
suspend fun cancelSend(transactionId: TransactionId): Result<Unit>
|
||||
|
||||
/**
|
||||
* Share a location message in the room.
|
||||
*
|
||||
* @param body A human readable textual representation of the location.
|
||||
* @param geoUri A geo URI (RFC 5870) representing the location e.g. `geo:51.5008,0.1247;u=35`.
|
||||
* Respectively: latitude, longitude, and (optional) uncertainty.
|
||||
* @param description Optional description of the location to display to the user.
|
||||
* @param zoomLevel Optional zoom level to display the map at.
|
||||
* @param assetType Optional type of the location asset.
|
||||
* Set to SENDER if sharing own location. Set to PIN if sharing any location.
|
||||
*/
|
||||
suspend fun sendLocation(
|
||||
body: String,
|
||||
geoUri: String,
|
||||
description: String? = null,
|
||||
zoomLevel: Int? = null,
|
||||
assetType: AssetType? = null,
|
||||
): Result<Unit>
|
||||
|
||||
/**
|
||||
* Create a poll in the room.
|
||||
*
|
||||
* @param question The question to ask.
|
||||
* @param answers The list of answers.
|
||||
* @param maxSelections The maximum number of answers that can be selected.
|
||||
* @param pollKind The kind of poll to create.
|
||||
*/
|
||||
suspend fun createPoll(
|
||||
question: String,
|
||||
answers: List<String>,
|
||||
maxSelections: Int,
|
||||
pollKind: PollKind,
|
||||
): Result<Unit>
|
||||
|
||||
/**
|
||||
* Edit a poll in the room.
|
||||
*
|
||||
* @param pollStartId The event ID of the poll start event.
|
||||
* @param question The question to ask.
|
||||
* @param answers The list of answers.
|
||||
* @param maxSelections The maximum number of answers that can be selected.
|
||||
* @param pollKind The kind of poll to create.
|
||||
*/
|
||||
suspend fun editPoll(
|
||||
pollStartId: EventId,
|
||||
question: String,
|
||||
answers: List<String>,
|
||||
maxSelections: Int,
|
||||
pollKind: PollKind,
|
||||
): Result<Unit>
|
||||
|
||||
/**
|
||||
* Send a response to a poll.
|
||||
*
|
||||
* @param pollStartId The event ID of the poll start event.
|
||||
* @param answers The list of answer ids to send.
|
||||
*/
|
||||
suspend fun sendPollResponse(pollStartId: EventId, answers: List<String>): Result<Unit>
|
||||
|
||||
/**
|
||||
* Ends a poll in the room.
|
||||
*
|
||||
* @param pollStartId The event ID of the poll start event.
|
||||
* @param text Fallback text of the poll end event.
|
||||
*/
|
||||
suspend fun endPoll(pollStartId: EventId, text: String): Result<Unit>
|
||||
|
||||
suspend fun sendVoiceMessage(
|
||||
file: File,
|
||||
audioInfo: AudioInfo,
|
||||
waveform: List<Float>,
|
||||
progressCallback: ProgressCallback?
|
||||
): Result<MediaUploadHandler>
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -43,17 +43,12 @@ import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
|
||||
import io.element.android.libraries.matrix.api.room.roomNotificationSettings
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
|
||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
|
||||
import io.element.android.libraries.matrix.impl.core.toProgressWatcher
|
||||
import io.element.android.libraries.matrix.impl.media.MediaUploadHandlerImpl
|
||||
import io.element.android.libraries.matrix.impl.media.map
|
||||
import io.element.android.libraries.matrix.impl.media.toMSC3246range
|
||||
import io.element.android.libraries.matrix.impl.notificationsettings.RustNotificationSettingsService
|
||||
import io.element.android.libraries.matrix.impl.poll.toInner
|
||||
import io.element.android.libraries.matrix.impl.room.location.toInner
|
||||
import io.element.android.libraries.matrix.impl.room.member.RoomMemberListFetcher
|
||||
import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
|
||||
import io.element.android.libraries.matrix.impl.room.powerlevels.RoomPowerLevelsMapper
|
||||
@@ -80,8 +75,6 @@ import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.EventTimelineItem
|
||||
import org.matrix.rustcomponents.sdk.MessageFormat
|
||||
import org.matrix.rustcomponents.sdk.RoomInfo
|
||||
import org.matrix.rustcomponents.sdk.RoomInfoListener
|
||||
import org.matrix.rustcomponents.sdk.RoomListItem
|
||||
@@ -94,10 +87,8 @@ import org.matrix.rustcomponents.sdk.WidgetCapabilitiesProvider
|
||||
import org.matrix.rustcomponents.sdk.messageEventContentFromHtml
|
||||
import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
|
||||
import org.matrix.rustcomponents.sdk.use
|
||||
import timber.log.Timber
|
||||
import uniffi.matrix_sdk.RoomPowerLevelChanges
|
||||
import java.io.File
|
||||
import org.matrix.rustcomponents.sdk.FormattedBody as RustFormattedBody
|
||||
import org.matrix.rustcomponents.sdk.Room as InnerRoom
|
||||
import org.matrix.rustcomponents.sdk.Timeline as InnerTimeline
|
||||
|
||||
@@ -198,7 +189,6 @@ class RustMatrixRoom(
|
||||
liveTimeline.close()
|
||||
innerRoom.destroy()
|
||||
roomListItem.destroy()
|
||||
specialModeEventTimelineItem?.destroy()
|
||||
}
|
||||
|
||||
override val name: String?
|
||||
@@ -332,12 +322,8 @@ 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 {
|
||||
innerTimeline.send(content)
|
||||
}
|
||||
}
|
||||
override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit> {
|
||||
return liveTimeline.sendMessage(body, htmlBody, mentions)
|
||||
}
|
||||
|
||||
override suspend fun editMessage(
|
||||
@@ -346,45 +332,16 @@ class RustMatrixRoom(
|
||||
body: String,
|
||||
htmlBody: String?,
|
||||
mentions: List<Mention>,
|
||||
): Result<Unit> =
|
||||
withContext(roomDispatcher) {
|
||||
if (originalEventId != null) {
|
||||
runCatching {
|
||||
val editedEvent = specialModeEventTimelineItem ?: innerTimeline.getEventTimelineItemByEventId(originalEventId.value)
|
||||
editedEvent.use {
|
||||
innerTimeline.edit(
|
||||
newContent = messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()),
|
||||
editItem = it,
|
||||
)
|
||||
}
|
||||
specialModeEventTimelineItem = null
|
||||
}
|
||||
} else {
|
||||
runCatching {
|
||||
transactionId?.let { cancelSend(it) }
|
||||
innerTimeline.send(messageEventContentFromParts(body, htmlBody))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var specialModeEventTimelineItem: EventTimelineItem? = null
|
||||
|
||||
override suspend fun enterSpecialMode(eventId: EventId?): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
specialModeEventTimelineItem?.destroy()
|
||||
specialModeEventTimelineItem = null
|
||||
specialModeEventTimelineItem = eventId?.let { innerTimeline.getEventTimelineItemByEventId(it.value) }
|
||||
}
|
||||
): Result<Unit> {
|
||||
return liveTimeline.editMessage(originalEventId, transactionId, body, htmlBody, mentions)
|
||||
}
|
||||
|
||||
override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
val inReplyTo = specialModeEventTimelineItem ?: innerTimeline.getEventTimelineItemByEventId(eventId.value)
|
||||
inReplyTo.use { eventTimelineItem ->
|
||||
innerTimeline.sendReply(messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()), eventTimelineItem)
|
||||
}
|
||||
specialModeEventTimelineItem = null
|
||||
}
|
||||
override suspend fun enterSpecialMode(eventId: EventId?): Result<Unit> {
|
||||
return liveTimeline.enterSpecialMode(eventId)
|
||||
}
|
||||
|
||||
override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit>{
|
||||
return liveTimeline.replyMessage(eventId, body, htmlBody, mentions)
|
||||
}
|
||||
|
||||
override suspend fun redactEvent(eventId: EventId, reason: String?) = withContext(roomDispatcher) {
|
||||
@@ -467,18 +424,7 @@ class RustMatrixRoom(
|
||||
formattedBody: String?,
|
||||
progressCallback: ProgressCallback?,
|
||||
): Result<MediaUploadHandler> {
|
||||
return sendAttachment(listOfNotNull(file, thumbnailFile)) {
|
||||
innerTimeline.sendImage(
|
||||
url = file.path,
|
||||
thumbnailUrl = thumbnailFile?.path,
|
||||
imageInfo = imageInfo.map(),
|
||||
caption = body,
|
||||
formattedCaption = formattedBody?.let {
|
||||
RustFormattedBody(body = it, format = MessageFormat.Html)
|
||||
},
|
||||
progressWatcher = progressCallback?.toProgressWatcher()
|
||||
)
|
||||
}
|
||||
return liveTimeline.sendImage(file, thumbnailFile, imageInfo, body, formattedBody, progressCallback)
|
||||
}
|
||||
|
||||
override suspend fun sendVideo(
|
||||
@@ -489,63 +435,31 @@ class RustMatrixRoom(
|
||||
formattedBody: String?,
|
||||
progressCallback: ProgressCallback?,
|
||||
): Result<MediaUploadHandler> {
|
||||
return sendAttachment(listOfNotNull(file, thumbnailFile)) {
|
||||
innerTimeline.sendVideo(
|
||||
url = file.path,
|
||||
thumbnailUrl = thumbnailFile?.path,
|
||||
videoInfo = videoInfo.map(),
|
||||
caption = body,
|
||||
formattedCaption = formattedBody?.let {
|
||||
RustFormattedBody(body = it, format = MessageFormat.Html)
|
||||
},
|
||||
progressWatcher = progressCallback?.toProgressWatcher()
|
||||
)
|
||||
}
|
||||
return liveTimeline.sendVideo(file, thumbnailFile, videoInfo, body, formattedBody, progressCallback)
|
||||
}
|
||||
|
||||
override suspend fun sendAudio(file: File, audioInfo: AudioInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler> {
|
||||
return sendAttachment(listOf(file)) {
|
||||
innerTimeline.sendAudio(
|
||||
url = file.path,
|
||||
audioInfo = audioInfo.map(),
|
||||
// Maybe allow a caption in the future?
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
progressWatcher = progressCallback?.toProgressWatcher()
|
||||
)
|
||||
}
|
||||
return liveTimeline.sendAudio(file, audioInfo, progressCallback)
|
||||
}
|
||||
|
||||
override suspend fun sendFile(file: File, fileInfo: FileInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler> {
|
||||
return sendAttachment(listOf(file)) {
|
||||
innerTimeline.sendFile(file.path, fileInfo.map(), progressCallback?.toProgressWatcher())
|
||||
}
|
||||
return liveTimeline.sendFile(file, fileInfo, progressCallback)
|
||||
}
|
||||
|
||||
override suspend fun toggleReaction(emoji: String, eventId: EventId): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerTimeline.toggleReaction(key = emoji, eventId = eventId.value)
|
||||
}
|
||||
override suspend fun toggleReaction(emoji: String, eventId: EventId): Result<Unit>{
|
||||
return liveTimeline.toggleReaction(emoji, eventId)
|
||||
}
|
||||
|
||||
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
roomContentForwarder.forward(fromTimeline = innerTimeline, eventId = eventId, toRoomIds = roomIds)
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit>{
|
||||
return liveTimeline.forwardEvent(eventId, roomIds)
|
||||
}
|
||||
|
||||
override suspend fun retrySendMessage(transactionId: TransactionId): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerTimeline.retrySend(transactionId.value)
|
||||
}
|
||||
override suspend fun retrySendMessage(transactionId: TransactionId): Result<Unit> {
|
||||
return liveTimeline.retrySendMessage(transactionId)
|
||||
}
|
||||
|
||||
override suspend fun cancelSend(transactionId: TransactionId): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerTimeline.cancelSend(transactionId.value)
|
||||
}
|
||||
override suspend fun cancelSend(transactionId: TransactionId): Result<Unit>{
|
||||
return liveTimeline.cancelSend(transactionId)
|
||||
}
|
||||
|
||||
override suspend fun updateAvatar(mimeType: String, data: ByteArray): Result<Unit> = withContext(roomDispatcher) {
|
||||
@@ -623,16 +537,8 @@ class RustMatrixRoom(
|
||||
description: String?,
|
||||
zoomLevel: Int?,
|
||||
assetType: AssetType?,
|
||||
): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerTimeline.sendLocation(
|
||||
body = body,
|
||||
geoUri = geoUri,
|
||||
description = description,
|
||||
zoomLevel = zoomLevel?.toUByte(),
|
||||
assetType = assetType?.toInner(),
|
||||
)
|
||||
}
|
||||
): Result<Unit> {
|
||||
return liveTimeline.sendLocation(body, geoUri, description, zoomLevel, assetType)
|
||||
}
|
||||
|
||||
override suspend fun createPoll(
|
||||
@@ -640,15 +546,8 @@ class RustMatrixRoom(
|
||||
answers: List<String>,
|
||||
maxSelections: Int,
|
||||
pollKind: PollKind,
|
||||
): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerTimeline.createPoll(
|
||||
question = question,
|
||||
answers = answers,
|
||||
maxSelections = maxSelections.toUByte(),
|
||||
pollKind = pollKind.toInner(),
|
||||
)
|
||||
}
|
||||
): Result<Unit> {
|
||||
return liveTimeline.createPoll(question, answers, maxSelections, pollKind)
|
||||
}
|
||||
|
||||
override suspend fun editPoll(
|
||||
@@ -657,46 +556,22 @@ class RustMatrixRoom(
|
||||
answers: List<String>,
|
||||
maxSelections: Int,
|
||||
pollKind: PollKind,
|
||||
): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
val pollStartEvent =
|
||||
innerTimeline.getEventTimelineItemByEventId(
|
||||
eventId = pollStartId.value
|
||||
)
|
||||
pollStartEvent.use {
|
||||
innerTimeline.editPoll(
|
||||
question = question,
|
||||
answers = answers,
|
||||
maxSelections = maxSelections.toUByte(),
|
||||
pollKind = pollKind.toInner(),
|
||||
editItem = pollStartEvent,
|
||||
)
|
||||
}
|
||||
}
|
||||
): Result<Unit> {
|
||||
return liveTimeline.editPoll(pollStartId, question, answers, maxSelections, pollKind)
|
||||
}
|
||||
|
||||
override suspend fun sendPollResponse(
|
||||
pollStartId: EventId,
|
||||
answers: List<String>
|
||||
): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerTimeline.sendPollResponse(
|
||||
pollStartId = pollStartId.value,
|
||||
answers = answers,
|
||||
)
|
||||
}
|
||||
): Result<Unit> {
|
||||
return liveTimeline.sendPollResponse(pollStartId, answers)
|
||||
}
|
||||
|
||||
override suspend fun endPoll(
|
||||
pollStartId: EventId,
|
||||
text: String
|
||||
): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerTimeline.endPoll(
|
||||
pollStartId = pollStartId.value,
|
||||
text = text,
|
||||
)
|
||||
}
|
||||
): Result<Unit> {
|
||||
return liveTimeline.endPoll(pollStartId, text)
|
||||
}
|
||||
|
||||
override suspend fun sendVoiceMessage(
|
||||
@@ -704,16 +579,8 @@ class RustMatrixRoom(
|
||||
audioInfo: AudioInfo,
|
||||
waveform: List<Float>,
|
||||
progressCallback: ProgressCallback?,
|
||||
): Result<MediaUploadHandler> = sendAttachment(listOf(file)) {
|
||||
innerTimeline.sendVoiceMessage(
|
||||
url = file.path,
|
||||
audioInfo = audioInfo.map(),
|
||||
waveform = waveform.toMSC3246range(),
|
||||
// Maybe allow a caption in the future?
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
progressWatcher = progressCallback?.toProgressWatcher(),
|
||||
)
|
||||
): Result<MediaUploadHandler>{
|
||||
return liveTimeline.sendVoiceMessage(file, audioInfo, waveform, progressCallback)
|
||||
}
|
||||
|
||||
override suspend fun typingNotice(isTyping: Boolean) = runCatching {
|
||||
@@ -749,12 +616,6 @@ class RustMatrixRoom(
|
||||
innerRoom.matrixToEventPermalink(eventId.value)
|
||||
}
|
||||
|
||||
private fun sendAttachment(files: List<File>, handle: () -> SendAttachmentJoinHandle): Result<MediaUploadHandler> {
|
||||
return runCatching {
|
||||
MediaUploadHandlerImpl(files, handle())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTimeline(
|
||||
timeline: InnerTimeline,
|
||||
isLive: Boolean = true,
|
||||
@@ -769,19 +630,9 @@ class RustMatrixRoom(
|
||||
dispatcher = roomDispatcher,
|
||||
lastLoginTimestamp = sessionData.loginTimestamp,
|
||||
onNewSyncedEvent = onNewSyncedEvent,
|
||||
roomContentForwarder = roomContentForwarder,
|
||||
inner = timeline,
|
||||
fetchDetailsForEvent = { eventId ->
|
||||
runCatching {
|
||||
innerTimeline.getEventTimelineItemByEventId(eventId.value)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun messageEventContentFromParts(body: String, htmlBody: String?): RoomMessageEventContentWithoutRelation =
|
||||
if (htmlBody != null) {
|
||||
messageEventContentFromHtml(body, htmlBody)
|
||||
} else {
|
||||
messageEventContentFromMarkdown(body)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,30 @@
|
||||
package io.element.android.libraries.matrix.impl.timeline
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||
import io.element.android.libraries.matrix.api.media.AudioInfo
|
||||
import io.element.android.libraries.matrix.api.media.FileInfo
|
||||
import io.element.android.libraries.matrix.api.media.ImageInfo
|
||||
import io.element.android.libraries.matrix.api.media.MediaUploadHandler
|
||||
import io.element.android.libraries.matrix.api.media.VideoInfo
|
||||
import io.element.android.libraries.matrix.api.poll.PollKind
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.Mention
|
||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.TimelineException
|
||||
import io.element.android.libraries.matrix.impl.core.toProgressWatcher
|
||||
import io.element.android.libraries.matrix.impl.media.MediaUploadHandlerImpl
|
||||
import io.element.android.libraries.matrix.impl.media.map
|
||||
import io.element.android.libraries.matrix.impl.media.toMSC3246range
|
||||
import io.element.android.libraries.matrix.impl.poll.toInner
|
||||
import io.element.android.libraries.matrix.impl.room.RoomContentForwarder
|
||||
import io.element.android.libraries.matrix.impl.room.location.toInner
|
||||
import io.element.android.libraries.matrix.impl.room.map
|
||||
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.TimelineEventContentMapper
|
||||
@@ -48,10 +67,19 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.EventTimelineItem
|
||||
import org.matrix.rustcomponents.sdk.FormattedBody
|
||||
import org.matrix.rustcomponents.sdk.MessageFormat
|
||||
import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation
|
||||
import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle
|
||||
import org.matrix.rustcomponents.sdk.TimelineDiff
|
||||
import org.matrix.rustcomponents.sdk.TimelineItem
|
||||
import org.matrix.rustcomponents.sdk.messageEventContentFromHtml
|
||||
import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
|
||||
import org.matrix.rustcomponents.sdk.use
|
||||
import timber.log.Timber
|
||||
import uniffi.matrix_sdk_ui.EventItemOrigin
|
||||
import java.io.File
|
||||
import java.util.Date
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import org.matrix.rustcomponents.sdk.Timeline as InnerTimeline
|
||||
@@ -68,7 +96,7 @@ class RustTimeline(
|
||||
private val matrixRoom: MatrixRoom,
|
||||
private val dispatcher: CoroutineDispatcher,
|
||||
private val lastLoginTimestamp: Date?,
|
||||
private val fetchDetailsForEvent: suspend (EventId) -> Result<Unit>,
|
||||
private val roomContentForwarder: RoomContentForwarder,
|
||||
private val onNewSyncedEvent: () -> Unit,
|
||||
) : Timeline {
|
||||
|
||||
@@ -90,7 +118,7 @@ class RustTimeline(
|
||||
private val invisibleIndicatorPostProcessor = InvisibleIndicatorPostProcessor(isLive)
|
||||
|
||||
private val timelineItemFactory = MatrixTimelineItemMapper(
|
||||
fetchDetailsForEvent = fetchDetailsForEvent,
|
||||
fetchDetailsForEvent = this::fetchDetailsForEvent,
|
||||
roomCoroutineScope = roomCoroutineScope,
|
||||
virtualTimelineItemMapper = VirtualTimelineItemMapper(),
|
||||
eventTimelineItemMapper = EventTimelineItemMapper(
|
||||
@@ -138,7 +166,7 @@ class RustTimeline(
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePaginationStatus(direction: Timeline.PaginationDirection, update: (Timeline.PaginationStatus)->Timeline.PaginationStatus){
|
||||
private fun updatePaginationStatus(direction: Timeline.PaginationDirection, update: (Timeline.PaginationStatus) -> Timeline.PaginationStatus) {
|
||||
when (direction) {
|
||||
Timeline.PaginationDirection.BACKWARDS -> backPaginationStatus.getAndUpdate(update)
|
||||
Timeline.PaginationDirection.FORWARDS -> forwardPaginationStatus.getAndUpdate(update)
|
||||
@@ -204,6 +232,7 @@ class RustTimeline(
|
||||
|
||||
override fun close() {
|
||||
inner.close()
|
||||
specialModeEventTimelineItem?.destroy()
|
||||
}
|
||||
|
||||
private suspend fun fetchMembers() = withContext(dispatcher) {
|
||||
@@ -229,4 +258,266 @@ class RustTimeline(
|
||||
initLatch.await()
|
||||
timelineDiffProcessor.postDiffs(diffs)
|
||||
}
|
||||
|
||||
override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit> = withContext(dispatcher) {
|
||||
messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()).use { content ->
|
||||
runCatching {
|
||||
inner.send(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun editMessage(
|
||||
originalEventId: EventId?,
|
||||
transactionId: TransactionId?,
|
||||
body: String,
|
||||
htmlBody: String?,
|
||||
mentions: List<Mention>,
|
||||
): Result<Unit> =
|
||||
withContext(dispatcher) {
|
||||
if (originalEventId != null) {
|
||||
runCatching {
|
||||
val editedEvent = specialModeEventTimelineItem ?: inner.getEventTimelineItemByEventId(originalEventId.value)
|
||||
editedEvent.use {
|
||||
inner.edit(
|
||||
newContent = messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()),
|
||||
editItem = it,
|
||||
)
|
||||
}
|
||||
specialModeEventTimelineItem = null
|
||||
}
|
||||
} else {
|
||||
runCatching {
|
||||
transactionId?.let { cancelSend(it) }
|
||||
inner.send(messageEventContentFromParts(body, htmlBody))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var specialModeEventTimelineItem: EventTimelineItem? = null
|
||||
|
||||
override suspend fun enterSpecialMode(eventId: EventId?): Result<Unit> = withContext(dispatcher) {
|
||||
runCatching {
|
||||
specialModeEventTimelineItem?.destroy()
|
||||
specialModeEventTimelineItem = null
|
||||
specialModeEventTimelineItem = eventId?.let { inner.getEventTimelineItemByEventId(it.value) }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit> = withContext(dispatcher) {
|
||||
runCatching {
|
||||
val inReplyTo = specialModeEventTimelineItem ?: inner.getEventTimelineItemByEventId(eventId.value)
|
||||
inReplyTo.use { eventTimelineItem ->
|
||||
inner.sendReply(messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()), eventTimelineItem)
|
||||
}
|
||||
specialModeEventTimelineItem = null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendImage(
|
||||
file: File,
|
||||
thumbnailFile: File?,
|
||||
imageInfo: ImageInfo,
|
||||
body: String?,
|
||||
formattedBody: String?,
|
||||
progressCallback: ProgressCallback?,
|
||||
): Result<MediaUploadHandler> {
|
||||
return sendAttachment(listOfNotNull(file, thumbnailFile)) {
|
||||
inner.sendImage(
|
||||
url = file.path,
|
||||
thumbnailUrl = thumbnailFile?.path,
|
||||
imageInfo = imageInfo.map(),
|
||||
caption = body,
|
||||
formattedCaption = formattedBody?.let {
|
||||
FormattedBody(body = it, format = MessageFormat.Html)
|
||||
},
|
||||
progressWatcher = progressCallback?.toProgressWatcher()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendVideo(
|
||||
file: File,
|
||||
thumbnailFile: File?,
|
||||
videoInfo: VideoInfo,
|
||||
body: String?,
|
||||
formattedBody: String?,
|
||||
progressCallback: ProgressCallback?,
|
||||
): Result<MediaUploadHandler> {
|
||||
return sendAttachment(listOfNotNull(file, thumbnailFile)) {
|
||||
inner.sendVideo(
|
||||
url = file.path,
|
||||
thumbnailUrl = thumbnailFile?.path,
|
||||
videoInfo = videoInfo.map(),
|
||||
caption = body,
|
||||
formattedCaption = formattedBody?.let {
|
||||
FormattedBody(body = it, format = MessageFormat.Html)
|
||||
},
|
||||
progressWatcher = progressCallback?.toProgressWatcher()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendAudio(file: File, audioInfo: AudioInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler> {
|
||||
return sendAttachment(listOf(file)) {
|
||||
inner.sendAudio(
|
||||
url = file.path,
|
||||
audioInfo = audioInfo.map(),
|
||||
// Maybe allow a caption in the future?
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
progressWatcher = progressCallback?.toProgressWatcher()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendFile(file: File, fileInfo: FileInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler> {
|
||||
return sendAttachment(listOf(file)) {
|
||||
inner.sendFile(file.path, fileInfo.map(), progressCallback?.toProgressWatcher())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun toggleReaction(emoji: String, eventId: EventId): Result<Unit> = withContext(dispatcher) {
|
||||
runCatching {
|
||||
inner.toggleReaction(key = emoji, eventId = eventId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> = withContext(dispatcher) {
|
||||
runCatching {
|
||||
roomContentForwarder.forward(fromTimeline = inner, eventId = eventId, toRoomIds = roomIds)
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun retrySendMessage(transactionId: TransactionId): Result<Unit> = withContext(dispatcher) {
|
||||
runCatching {
|
||||
inner.retrySend(transactionId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun cancelSend(transactionId: TransactionId): Result<Unit> = withContext(dispatcher) {
|
||||
runCatching {
|
||||
inner.cancelSend(transactionId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendLocation(
|
||||
body: String,
|
||||
geoUri: String,
|
||||
description: String?,
|
||||
zoomLevel: Int?,
|
||||
assetType: AssetType?,
|
||||
): Result<Unit> = withContext(dispatcher) {
|
||||
runCatching {
|
||||
inner.sendLocation(
|
||||
body = body,
|
||||
geoUri = geoUri,
|
||||
description = description,
|
||||
zoomLevel = zoomLevel?.toUByte(),
|
||||
assetType = assetType?.toInner(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun createPoll(
|
||||
question: String,
|
||||
answers: List<String>,
|
||||
maxSelections: Int,
|
||||
pollKind: PollKind,
|
||||
): Result<Unit> = withContext(dispatcher) {
|
||||
runCatching {
|
||||
inner.createPoll(
|
||||
question = question,
|
||||
answers = answers,
|
||||
maxSelections = maxSelections.toUByte(),
|
||||
pollKind = pollKind.toInner(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun editPoll(
|
||||
pollStartId: EventId,
|
||||
question: String,
|
||||
answers: List<String>,
|
||||
maxSelections: Int,
|
||||
pollKind: PollKind,
|
||||
): Result<Unit> = withContext(dispatcher) {
|
||||
runCatching {
|
||||
val pollStartEvent =
|
||||
inner.getEventTimelineItemByEventId(
|
||||
eventId = pollStartId.value
|
||||
)
|
||||
pollStartEvent.use {
|
||||
inner.editPoll(
|
||||
question = question,
|
||||
answers = answers,
|
||||
maxSelections = maxSelections.toUByte(),
|
||||
pollKind = pollKind.toInner(),
|
||||
editItem = pollStartEvent,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendPollResponse(
|
||||
pollStartId: EventId,
|
||||
answers: List<String>
|
||||
): Result<Unit> = withContext(dispatcher) {
|
||||
runCatching {
|
||||
inner.sendPollResponse(
|
||||
pollStartId = pollStartId.value,
|
||||
answers = answers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun endPoll(
|
||||
pollStartId: EventId,
|
||||
text: String
|
||||
): Result<Unit> = withContext(dispatcher) {
|
||||
runCatching {
|
||||
inner.endPoll(
|
||||
pollStartId = pollStartId.value,
|
||||
text = text,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendVoiceMessage(
|
||||
file: File,
|
||||
audioInfo: AudioInfo,
|
||||
waveform: List<Float>,
|
||||
progressCallback: ProgressCallback?,
|
||||
): Result<MediaUploadHandler> = sendAttachment(listOf(file)) {
|
||||
inner.sendVoiceMessage(
|
||||
url = file.path,
|
||||
audioInfo = audioInfo.map(),
|
||||
waveform = waveform.toMSC3246range(),
|
||||
// Maybe allow a caption in the future?
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
progressWatcher = progressCallback?.toProgressWatcher(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun messageEventContentFromParts(body: String, htmlBody: String?): RoomMessageEventContentWithoutRelation =
|
||||
if (htmlBody != null) {
|
||||
messageEventContentFromHtml(body, htmlBody)
|
||||
} else {
|
||||
messageEventContentFromMarkdown(body)
|
||||
}
|
||||
|
||||
private fun sendAttachment(files: List<File>, handle: () -> SendAttachmentJoinHandle): Result<MediaUploadHandler> {
|
||||
return runCatching {
|
||||
MediaUploadHandlerImpl(files, handle())
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchDetailsForEvent(eventId: EventId): Result<Unit> {
|
||||
return runCatching {
|
||||
inner.getEventTimelineItemByEventId(eventId.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user