Send caption with media
This commit is contained in:
parent
17ea2aa5dc
commit
223eae9602
19 changed files with 301 additions and 76 deletions
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue