Send caption with media

This commit is contained in:
Benoit Marty 2024-11-04 12:54:11 +01:00 committed by Benoit Marty
parent 17ea2aa5dc
commit 223eae9602
19 changed files with 301 additions and 76 deletions

View file

@ -132,8 +132,8 @@ interface MatrixRoom : Closeable {
file: File,
thumbnailFile: File?,
imageInfo: ImageInfo,
body: String?,
formattedBody: String?,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?
): Result<MediaUploadHandler>
@ -141,8 +141,8 @@ interface MatrixRoom : Closeable {
file: File,
thumbnailFile: File?,
videoInfo: VideoInfo,
body: String?,
formattedBody: String?,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?
): Result<MediaUploadHandler>

View file

@ -75,8 +75,8 @@ interface Timeline : AutoCloseable {
file: File,
thumbnailFile: File?,
imageInfo: ImageInfo,
body: String?,
formattedBody: String?,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?
): Result<MediaUploadHandler>
@ -84,8 +84,8 @@ interface Timeline : AutoCloseable {
file: File,
thumbnailFile: File?,
videoInfo: VideoInfo,
body: String?,
formattedBody: String?,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?
): Result<MediaUploadHandler>

View file

@ -445,22 +445,22 @@ class RustMatrixRoom(
file: File,
thumbnailFile: File?,
imageInfo: ImageInfo,
body: String?,
formattedBody: String?,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
): Result<MediaUploadHandler> {
return liveTimeline.sendImage(file, thumbnailFile, imageInfo, body, formattedBody, progressCallback)
return liveTimeline.sendImage(file, thumbnailFile, imageInfo, caption, formattedCaption, progressCallback)
}
override suspend fun sendVideo(
file: File,
thumbnailFile: File?,
videoInfo: VideoInfo,
body: String?,
formattedBody: String?,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
): Result<MediaUploadHandler> {
return liveTimeline.sendVideo(file, thumbnailFile, videoInfo, body, formattedBody, progressCallback)
return liveTimeline.sendVideo(file, thumbnailFile, videoInfo, caption, formattedCaption, progressCallback)
}
override suspend fun sendAudio(file: File, audioInfo: AudioInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler> {

View file

@ -326,8 +326,8 @@ class RustTimeline(
file: File,
thumbnailFile: File?,
imageInfo: ImageInfo,
body: String?,
formattedBody: String?,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
): Result<MediaUploadHandler> {
return sendAttachment(listOfNotNull(file, thumbnailFile)) {
@ -335,8 +335,8 @@ class RustTimeline(
url = file.path,
thumbnailUrl = thumbnailFile?.path,
imageInfo = imageInfo.map(),
caption = body,
formattedCaption = formattedBody?.let {
caption = caption,
formattedCaption = formattedCaption?.let {
FormattedBody(body = it, format = MessageFormat.Html)
},
storeInCache = true,
@ -349,8 +349,8 @@ class RustTimeline(
file: File,
thumbnailFile: File?,
videoInfo: VideoInfo,
body: String?,
formattedBody: String?,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
): Result<MediaUploadHandler> {
return sendAttachment(listOfNotNull(file, thumbnailFile)) {
@ -358,8 +358,8 @@ class RustTimeline(
url = file.path,
thumbnailUrl = thumbnailFile?.path,
videoInfo = videoInfo.map(),
caption = body,
formattedCaption = formattedBody?.let {
caption = caption,
formattedCaption = formattedCaption?.let {
FormattedBody(body = it, format = MessageFormat.Html)
},
storeInCache = true,

View file

@ -61,6 +61,7 @@ const val A_ROOM_RAW_NAME = "A room raw name"
const val A_MESSAGE = "Hello world!"
const val A_REPLY = "OK, I'll be there!"
const val ANOTHER_MESSAGE = "Hello universe!"
const val A_CAPTION = "A media caption"
const val A_REDACTION_REASON = "A redaction reason"

View file

@ -321,8 +321,8 @@ class FakeMatrixRoom(
file: File,
thumbnailFile: File?,
imageInfo: ImageInfo,
body: String?,
formattedBody: String?,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?
): Result<MediaUploadHandler> = simulateLongTask {
simulateSendMediaProgress(progressCallback)
@ -330,8 +330,8 @@ class FakeMatrixRoom(
file,
thumbnailFile,
imageInfo,
body,
formattedBody,
caption,
formattedCaption,
progressCallback,
)
}
@ -340,8 +340,8 @@ class FakeMatrixRoom(
file: File,
thumbnailFile: File?,
videoInfo: VideoInfo,
body: String?,
formattedBody: String?,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?
): Result<MediaUploadHandler> = simulateLongTask {
simulateSendMediaProgress(progressCallback)
@ -349,8 +349,8 @@ class FakeMatrixRoom(
file,
thumbnailFile,
videoInfo,
body,
formattedBody,
caption,
formattedCaption,
progressCallback,
)
}

View file

@ -131,15 +131,15 @@ class FakeTimeline(
file: File,
thumbnailFile: File?,
imageInfo: ImageInfo,
body: String?,
formattedBody: String?,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
): Result<MediaUploadHandler> = sendImageLambda(
file,
thumbnailFile,
imageInfo,
body,
formattedBody,
caption,
formattedCaption,
progressCallback
)
@ -158,15 +158,15 @@ class FakeTimeline(
file: File,
thumbnailFile: File?,
videoInfo: VideoInfo,
body: String?,
formattedBody: String?,
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
): Result<MediaUploadHandler> = sendVideoLambda(
file,
thumbnailFile,
videoInfo,
body,
formattedBody,
caption,
formattedCaption,
progressCallback
)

View file

@ -106,8 +106,8 @@ class MediaSender @Inject constructor(
file = uploadInfo.file,
thumbnailFile = uploadInfo.thumbnailFile,
imageInfo = uploadInfo.imageInfo,
body = caption,
formattedBody = formattedCaption,
caption = caption,
formattedCaption = formattedCaption,
progressCallback = progressCallback
)
}
@ -116,8 +116,8 @@ class MediaSender @Inject constructor(
file = uploadInfo.file,
thumbnailFile = uploadInfo.thumbnailFile,
videoInfo = uploadInfo.videoInfo,
body = caption,
formattedBody = formattedCaption,
caption = caption,
formattedCaption = formattedCaption,
progressCallback = progressCallback
)
}

View file

@ -11,6 +11,8 @@ import android.net.Uri
import io.element.android.libraries.core.mimetype.MimeTypes
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.VideoInfo
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
import io.element.android.tests.testutils.simulateLongTask
@ -61,4 +63,45 @@ class FakeMediaPreProcessor : MediaPreProcessor {
)
)
}
fun givenImageResult() {
givenResult(
Result.success(
MediaUploadInfo.Image(
file = File("image.jpg"),
imageInfo = ImageInfo(
height = 100,
width = 100,
mimetype = MimeTypes.Jpeg,
size = 1000,
thumbnailInfo = null,
thumbnailSource = null,
blurhash = null,
),
thumbnailFile = null,
)
)
)
}
fun givenVideoResult() {
givenResult(
Result.success(
MediaUploadInfo.Video(
file = File("image.jpg"),
videoInfo = VideoInfo(
duration = 1000.seconds,
height = 100,
width = 100,
mimetype = MimeTypes.Mp4,
size = 1000,
thumbnailInfo = null,
thumbnailSource = null,
blurhash = null,
),
thumbnailFile = null,
)
)
)
}
}

View file

@ -125,16 +125,22 @@ fun TextComposer(
val composerOptionsButton: @Composable () -> Unit = remember {
@Composable {
ComposerOptionsButton(
modifier = Modifier
.size(48.dp),
onClick = onAddAttachment
)
if (composerMode == MessageComposerMode.Caption) {
Spacer(modifier = Modifier.width(9.dp))
} else {
ComposerOptionsButton(
modifier = Modifier
.size(48.dp),
onClick = onAddAttachment
)
}
}
}
val placeholder = if (composerMode.inThread) {
stringResource(id = CommonStrings.action_reply_in_thread)
} else if (composerMode == MessageComposerMode.Caption) {
stringResource(id = R.string.rich_text_editor_composer_caption_placeholder)
} else {
stringResource(id = R.string.rich_text_editor_composer_placeholder)
}
@ -180,7 +186,7 @@ fun TextComposer(
}
}
val canSendMessage = markdown.isNotBlank()
val canSendMessage = markdown.isNotBlank() || composerMode == MessageComposerMode.Caption
val sendButton = @Composable {
SendButton(
canSendMessage = canSendMessage,
@ -592,6 +598,21 @@ internal fun TextComposerReplyPreview(@PreviewParameter(InReplyToDetailsProvider
}
}
@PreviewsDayNight
@Composable
internal fun TextComposerCaptionPreview(@PreviewParameter(InReplyToDetailsProvider::class) inReplyToDetails: InReplyToDetails) = ElementPreview {
PreviewColumn(
items = aTextEditorStateMarkdownList()
) { textEditorState ->
ATextComposer(
state = textEditorState,
voiceMessageState = VoiceMessageState.Idle,
composerMode = MessageComposerMode.Caption,
enableVoiceMessages = false,
)
}
}
@PreviewsDayNight
@Composable
internal fun TextComposerVoicePreview() = ElementPreview {

View file

@ -18,6 +18,8 @@ import io.element.android.libraries.matrix.ui.messages.reply.eventId
sealed interface MessageComposerMode {
data object Normal : MessageComposerMode
data object Caption : MessageComposerMode
sealed interface Special : MessageComposerMode
data class Edit(
@ -34,7 +36,8 @@ sealed interface MessageComposerMode {
val relatedEventId: EventId?
get() = when (this) {
is Normal -> null
is Normal,
is Caption -> null
is Edit -> eventOrTransactionId.eventId
is Reply -> eventId
}

View file

@ -36,6 +36,7 @@ sealed interface TextEditorState {
is Rich -> richTextEditorState.hasFocus
}
// Note: for test only
suspend fun setHtml(html: String) {
when (this) {
is Markdown -> Unit
@ -43,6 +44,7 @@ sealed interface TextEditorState {
}
}
// Note: for test only
suspend fun setMarkdown(text: String) {
when (this) {
is Markdown -> state.text.update(text, true)