Merge pull request #3574 from element-hq/feature/bma/improveMediaModel
Clarify model for Event with attachment
This commit is contained in:
@@ -50,7 +50,6 @@ import io.element.android.features.poll.api.create.CreatePollMode
|
||||
import io.element.android.libraries.architecture.BackstackWithOverlayBox
|
||||
import io.element.android.libraries.architecture.BaseFlowNode
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.architecture.overlay.Overlay
|
||||
import io.element.android.libraries.architecture.overlay.operation.show
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
@@ -324,7 +323,8 @@ class MessagesFlowNode @AssistedInject constructor(
|
||||
is TimelineItemImageContent -> {
|
||||
val navTarget = NavTarget.MediaViewer(
|
||||
mediaInfo = MediaInfo(
|
||||
name = event.content.filename ?: event.content.body,
|
||||
filename = event.content.filename,
|
||||
caption = event.content.caption,
|
||||
mimeType = event.content.mimeType,
|
||||
formattedFileSize = event.content.formattedFileSize,
|
||||
fileExtension = event.content.fileExtension
|
||||
@@ -341,7 +341,8 @@ class MessagesFlowNode @AssistedInject constructor(
|
||||
if (event.content.preferredMediaSource != null) {
|
||||
val navTarget = NavTarget.MediaViewer(
|
||||
mediaInfo = MediaInfo(
|
||||
name = event.content.body,
|
||||
filename = event.content.filename,
|
||||
caption = event.content.caption,
|
||||
mimeType = event.content.mimeType,
|
||||
formattedFileSize = event.content.formattedFileSize,
|
||||
fileExtension = event.content.fileExtension
|
||||
@@ -358,7 +359,8 @@ class MessagesFlowNode @AssistedInject constructor(
|
||||
is TimelineItemVideoContent -> {
|
||||
val navTarget = NavTarget.MediaViewer(
|
||||
mediaInfo = MediaInfo(
|
||||
name = event.content.filename ?: event.content.body,
|
||||
filename = event.content.filename,
|
||||
caption = event.content.caption,
|
||||
mimeType = event.content.mimeType,
|
||||
formattedFileSize = event.content.formattedFileSize,
|
||||
fileExtension = event.content.fileExtension
|
||||
@@ -372,7 +374,8 @@ class MessagesFlowNode @AssistedInject constructor(
|
||||
is TimelineItemFileContent -> {
|
||||
val navTarget = NavTarget.MediaViewer(
|
||||
mediaInfo = MediaInfo(
|
||||
name = event.content.body,
|
||||
filename = event.content.filename,
|
||||
caption = event.content.caption,
|
||||
mimeType = event.content.mimeType,
|
||||
formattedFileSize = event.content.formattedFileSize,
|
||||
fileExtension = event.content.fileExtension
|
||||
@@ -386,7 +389,8 @@ class MessagesFlowNode @AssistedInject constructor(
|
||||
is TimelineItemAudioContent -> {
|
||||
val navTarget = NavTarget.MediaViewer(
|
||||
mediaInfo = MediaInfo(
|
||||
name = event.content.body,
|
||||
filename = event.content.filename,
|
||||
caption = event.content.caption,
|
||||
mimeType = event.content.mimeType,
|
||||
formattedFileSize = event.content.formattedFileSize,
|
||||
fileExtension = event.content.fileExtension
|
||||
|
||||
@@ -269,19 +269,19 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif
|
||||
content = { ContentForBody(stringResource(CommonStrings.common_shared_location)) }
|
||||
}
|
||||
is TimelineItemImageContent -> {
|
||||
content = { ContentForBody(event.content.body) }
|
||||
content = { ContentForBody(event.content.bestDescription) }
|
||||
}
|
||||
is TimelineItemStickerContent -> {
|
||||
content = { ContentForBody(event.content.body) }
|
||||
content = { ContentForBody(event.content.bestDescription) }
|
||||
}
|
||||
is TimelineItemVideoContent -> {
|
||||
content = { ContentForBody(event.content.body) }
|
||||
content = { ContentForBody(event.content.bestDescription) }
|
||||
}
|
||||
is TimelineItemFileContent -> {
|
||||
content = { ContentForBody(event.content.body) }
|
||||
content = { ContentForBody(event.content.bestDescription) }
|
||||
}
|
||||
is TimelineItemAudioContent -> {
|
||||
content = { ContentForBody(event.content.body) }
|
||||
content = { ContentForBody(event.content.bestDescription) }
|
||||
}
|
||||
is TimelineItemVoiceContent -> {
|
||||
content = { ContentForBody(textContent) }
|
||||
|
||||
@@ -631,7 +631,7 @@ internal fun TimelineItemEventRowPreview() = ElementPreview {
|
||||
ATimelineItemEventRow(
|
||||
event = aTimelineItemEvent(
|
||||
isMine = isMine,
|
||||
content = aTimelineItemImageContent().copy(
|
||||
content = aTimelineItemImageContent(
|
||||
aspectRatio = 2.5f
|
||||
),
|
||||
groupPosition = TimelineItemGroupPosition.Last,
|
||||
|
||||
@@ -38,7 +38,7 @@ internal fun TimelineItemEventRowForDirectRoomPreview() = ElementPreview {
|
||||
ATimelineItemEventRow(
|
||||
event = aTimelineItemEvent(
|
||||
isMine = it,
|
||||
content = aTimelineItemImageContent().copy(
|
||||
content = aTimelineItemImageContent(
|
||||
aspectRatio = 5f
|
||||
),
|
||||
groupPosition = TimelineItemGroupPosition.Last,
|
||||
|
||||
@@ -45,7 +45,7 @@ internal fun TimelineItemEventRowShieldPreview() = ElementPreview {
|
||||
ATimelineItemEventRow(
|
||||
event = aTimelineItemEvent(
|
||||
isMine = true,
|
||||
content = aTimelineItemImageContent().copy(
|
||||
content = aTimelineItemImageContent(
|
||||
aspectRatio = 2.5f
|
||||
),
|
||||
groupPosition = TimelineItemGroupPosition.Last,
|
||||
@@ -54,7 +54,7 @@ internal fun TimelineItemEventRowShieldPreview() = ElementPreview {
|
||||
)
|
||||
ATimelineItemEventRow(
|
||||
event = aTimelineItemEvent(
|
||||
content = aTimelineItemImageContent().copy(
|
||||
content = aTimelineItemImageContent(
|
||||
aspectRatio = 2.5f
|
||||
),
|
||||
groupPosition = TimelineItemGroupPosition.Last,
|
||||
|
||||
@@ -49,7 +49,7 @@ internal fun TimelineItemEventRowWithReplyContentToPreview(
|
||||
event = aTimelineItemEvent(
|
||||
isMine = it,
|
||||
timelineItemReactions = aTimelineItemReactions(count = 0),
|
||||
content = aTimelineItemImageContent().copy(
|
||||
content = aTimelineItemImageContent(
|
||||
aspectRatio = 2.5f
|
||||
),
|
||||
inReplyTo = inReplyToDetails,
|
||||
|
||||
@@ -63,7 +63,7 @@ fun TimelineItemAudioView(
|
||||
Spacer(Modifier.width(spacing))
|
||||
Column {
|
||||
Text(
|
||||
text = content.body,
|
||||
text = content.bestDescription,
|
||||
color = ElementTheme.materialColors.primary,
|
||||
maxLines = 2,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
|
||||
@@ -64,7 +64,7 @@ fun TimelineItemFileView(
|
||||
Spacer(Modifier.width(spacing))
|
||||
Column {
|
||||
Text(
|
||||
text = content.body,
|
||||
text = content.bestDescription,
|
||||
color = ElementTheme.materialColors.primary,
|
||||
maxLines = 2,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
|
||||
@@ -91,7 +91,7 @@ fun TimelineItemImageView(
|
||||
model = MediaRequestData(
|
||||
source = content.preferredMediaSource,
|
||||
kind = MediaRequestData.Kind.File(
|
||||
body = content.filename ?: content.body,
|
||||
fileName = content.filename,
|
||||
mimeType = content.mimeType,
|
||||
),
|
||||
),
|
||||
@@ -108,7 +108,9 @@ fun TimelineItemImageView(
|
||||
val caption = if (LocalInspectionMode.current) {
|
||||
SpannedString(content.caption)
|
||||
} else {
|
||||
content.formatted?.body?.takeIf { content.formatted.format == MessageFormat.HTML } ?: SpannedString(content.caption)
|
||||
content.formattedCaption?.body
|
||||
?.takeIf { content.formattedCaption.format == MessageFormat.HTML }
|
||||
?: SpannedString(content.caption)
|
||||
}
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides ElementTheme.colors.textPrimary,
|
||||
@@ -158,9 +160,9 @@ internal fun TimelineImageWithCaptionRowPreview() = ElementPreview {
|
||||
ATimelineItemEventRow(
|
||||
event = aTimelineItemEvent(
|
||||
isMine = isMine,
|
||||
content = aTimelineItemImageContent().copy(
|
||||
content = aTimelineItemImageContent(
|
||||
filename = "image.jpg",
|
||||
body = "A long caption that may wrap into several lines",
|
||||
caption = "A long caption that may wrap into several lines",
|
||||
aspectRatio = 2.5f,
|
||||
),
|
||||
groupPosition = TimelineItemGroupPosition.Last,
|
||||
@@ -170,9 +172,9 @@ internal fun TimelineImageWithCaptionRowPreview() = ElementPreview {
|
||||
ATimelineItemEventRow(
|
||||
event = aTimelineItemEvent(
|
||||
isMine = false,
|
||||
content = aTimelineItemImageContent().copy(
|
||||
content = aTimelineItemImageContent(
|
||||
filename = "image.jpg",
|
||||
body = "Image with null aspectRatio",
|
||||
caption = "Image with null aspectRatio",
|
||||
aspectRatio = null,
|
||||
),
|
||||
groupPosition = TimelineItemGroupPosition.Last,
|
||||
|
||||
@@ -43,7 +43,7 @@ fun TimelineItemStickerView(
|
||||
onShowClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val description = content.body.takeIf { it.isNotEmpty() } ?: stringResource(CommonStrings.common_image)
|
||||
val description = content.bestDescription.takeIf { it.isNotEmpty() } ?: stringResource(CommonStrings.common_image)
|
||||
Column(
|
||||
modifier = modifier.semantics { contentDescription = description },
|
||||
) {
|
||||
@@ -65,7 +65,7 @@ fun TimelineItemStickerView(
|
||||
model = MediaRequestData(
|
||||
source = content.preferredMediaSource,
|
||||
kind = MediaRequestData.Kind.File(
|
||||
body = content.body,
|
||||
fileName = content.filename,
|
||||
mimeType = content.mimeType,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -76,8 +76,8 @@ fun TimelineItemVideoView(
|
||||
) {
|
||||
val containerModifier = if (content.showCaption) {
|
||||
Modifier
|
||||
.padding(top = 6.dp)
|
||||
.clip(RoundedCornerShape(6.dp))
|
||||
.padding(top = 6.dp)
|
||||
.clip(RoundedCornerShape(6.dp))
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
@@ -93,12 +93,12 @@ fun TimelineItemVideoView(
|
||||
var isLoaded by remember { mutableStateOf(false) }
|
||||
AsyncImage(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.then(if (isLoaded) Modifier.background(Color.White) else Modifier),
|
||||
.fillMaxWidth()
|
||||
.then(if (isLoaded) Modifier.background(Color.White) else Modifier),
|
||||
model = MediaRequestData(
|
||||
source = content.thumbnailSource,
|
||||
kind = MediaRequestData.Kind.File(
|
||||
body = content.filename ?: content.body,
|
||||
fileName = content.filename,
|
||||
mimeType = content.mimeType
|
||||
)
|
||||
),
|
||||
@@ -126,7 +126,9 @@ fun TimelineItemVideoView(
|
||||
val caption = if (LocalInspectionMode.current) {
|
||||
SpannedString(content.caption)
|
||||
} else {
|
||||
content.formatted?.body?.takeIf { content.formatted.format == MessageFormat.HTML } ?: SpannedString(content.caption)
|
||||
content.formattedCaption?.body
|
||||
?.takeIf { content.formattedCaption.format == MessageFormat.HTML }
|
||||
?: SpannedString(content.caption)
|
||||
}
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides ElementTheme.colors.textPrimary,
|
||||
@@ -178,7 +180,7 @@ internal fun TimelineVideoWithCaptionRowPreview() = ElementPreview {
|
||||
isMine = isMine,
|
||||
content = aTimelineItemVideoContent().copy(
|
||||
filename = "video.mp4",
|
||||
body = "A long caption that may wrap into several lines",
|
||||
caption = "A long caption that may wrap into several lines",
|
||||
aspectRatio = 2.5f,
|
||||
),
|
||||
groupPosition = TimelineItemGroupPosition.Last,
|
||||
@@ -190,7 +192,7 @@ internal fun TimelineVideoWithCaptionRowPreview() = ElementPreview {
|
||||
isMine = false,
|
||||
content = aTimelineItemVideoContent().copy(
|
||||
filename = "video.mp4",
|
||||
body = "Video with null aspect ratio",
|
||||
caption = "Video with null aspect ratio",
|
||||
aspectRatio = null,
|
||||
),
|
||||
groupPosition = TimelineItemGroupPosition.Last,
|
||||
|
||||
@@ -84,9 +84,9 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
||||
is ImageMessageType -> {
|
||||
val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height)
|
||||
TimelineItemImageContent(
|
||||
body = messageType.body.trimEnd(),
|
||||
formatted = messageType.formatted,
|
||||
filename = messageType.filename,
|
||||
caption = messageType.caption?.trimEnd(),
|
||||
formattedCaption = messageType.formattedCaption,
|
||||
mediaSource = messageType.source,
|
||||
thumbnailSource = messageType.info?.thumbnailSource,
|
||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||
@@ -95,13 +95,15 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
||||
height = messageType.info?.height?.toInt(),
|
||||
aspectRatio = aspectRatio,
|
||||
formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0),
|
||||
fileExtension = messageType.filename?.let { fileExtensionExtractor.extractFromName(it) }.orEmpty()
|
||||
fileExtension = fileExtensionExtractor.extractFromName(messageType.filename)
|
||||
)
|
||||
}
|
||||
is StickerMessageType -> {
|
||||
val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height)
|
||||
TimelineItemStickerContent(
|
||||
body = messageType.body.trimEnd(),
|
||||
filename = messageType.filename,
|
||||
caption = messageType.caption?.trimEnd(),
|
||||
formattedCaption = messageType.formattedCaption,
|
||||
mediaSource = messageType.source,
|
||||
thumbnailSource = messageType.info?.thumbnailSource,
|
||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||
@@ -110,7 +112,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
||||
height = messageType.info?.height?.toInt(),
|
||||
aspectRatio = aspectRatio,
|
||||
formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0),
|
||||
fileExtension = fileExtensionExtractor.extractFromName(messageType.body)
|
||||
fileExtension = fileExtensionExtractor.extractFromName(messageType.filename)
|
||||
)
|
||||
}
|
||||
is LocationMessageType -> {
|
||||
@@ -136,9 +138,9 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
||||
is VideoMessageType -> {
|
||||
val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height)
|
||||
TimelineItemVideoContent(
|
||||
body = messageType.body.trimEnd(),
|
||||
formatted = messageType.formatted,
|
||||
filename = messageType.filename,
|
||||
caption = messageType.caption?.trimEnd(),
|
||||
formattedCaption = messageType.formattedCaption,
|
||||
thumbnailSource = messageType.info?.thumbnailSource,
|
||||
videoSource = messageType.source,
|
||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||
@@ -148,17 +150,19 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
||||
blurHash = messageType.info?.blurhash,
|
||||
aspectRatio = aspectRatio,
|
||||
formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0),
|
||||
fileExtension = messageType.filename?.let { fileExtensionExtractor.extractFromName(it) }.orEmpty(),
|
||||
fileExtension = fileExtensionExtractor.extractFromName(messageType.filename),
|
||||
)
|
||||
}
|
||||
is AudioMessageType -> {
|
||||
TimelineItemAudioContent(
|
||||
body = messageType.body.trimEnd(),
|
||||
filename = messageType.filename,
|
||||
caption = messageType.caption?.trimEnd(),
|
||||
formattedCaption = messageType.formattedCaption,
|
||||
mediaSource = messageType.source,
|
||||
duration = messageType.info?.duration ?: Duration.ZERO,
|
||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||
formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0),
|
||||
fileExtension = fileExtensionExtractor.extractFromName(messageType.body),
|
||||
fileExtension = fileExtensionExtractor.extractFromName(messageType.filename),
|
||||
)
|
||||
}
|
||||
is VoiceMessageType -> {
|
||||
@@ -166,7 +170,9 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
||||
true -> {
|
||||
TimelineItemVoiceContent(
|
||||
eventId = eventId,
|
||||
body = messageType.body.trimEnd(),
|
||||
filename = messageType.filename,
|
||||
caption = messageType.caption?.trimEnd(),
|
||||
formattedCaption = messageType.formattedCaption,
|
||||
mediaSource = messageType.source,
|
||||
duration = messageType.info?.duration ?: Duration.ZERO,
|
||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||
@@ -175,20 +181,24 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
||||
}
|
||||
false -> {
|
||||
TimelineItemAudioContent(
|
||||
body = messageType.body.trimEnd(),
|
||||
filename = messageType.filename,
|
||||
caption = messageType.caption?.trimEnd(),
|
||||
formattedCaption = messageType.formattedCaption,
|
||||
mediaSource = messageType.source,
|
||||
duration = messageType.info?.duration ?: Duration.ZERO,
|
||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||
formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0),
|
||||
fileExtension = fileExtensionExtractor.extractFromName(messageType.body),
|
||||
fileExtension = fileExtensionExtractor.extractFromName(messageType.filename),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is FileMessageType -> {
|
||||
val fileExtension = fileExtensionExtractor.extractFromName(messageType.body)
|
||||
val fileExtension = fileExtensionExtractor.extractFromName(messageType.filename)
|
||||
TimelineItemFileContent(
|
||||
body = messageType.body.trimEnd(),
|
||||
filename = messageType.filename,
|
||||
caption = messageType.caption?.trimEnd(),
|
||||
formattedCaption = messageType.formattedCaption,
|
||||
thumbnailSource = messageType.info?.thumbnailSource,
|
||||
fileSource = messageType.source,
|
||||
mimeType = messageType.info?.mimetype ?: MimeTypes.fromFileExtension(fileExtension),
|
||||
|
||||
@@ -33,7 +33,9 @@ class TimelineItemContentStickerFactory @Inject constructor(
|
||||
val aspectRatio = aspectRatioOf(content.info.width, content.info.height)
|
||||
|
||||
return TimelineItemStickerContent(
|
||||
body = content.body,
|
||||
filename = content.filename,
|
||||
caption = content.body,
|
||||
formattedCaption = null,
|
||||
mediaSource = content.source,
|
||||
thumbnailSource = content.info.thumbnailSource,
|
||||
mimeType = content.info.mimetype ?: MimeTypes.OctetStream,
|
||||
@@ -42,7 +44,7 @@ class TimelineItemContentStickerFactory @Inject constructor(
|
||||
height = content.info.height?.toInt(),
|
||||
aspectRatio = aspectRatio,
|
||||
formattedFileSize = fileSizeFormatter.format(content.info.size ?: 0),
|
||||
fileExtension = fileExtensionExtractor.extractFromName(content.body)
|
||||
fileExtension = fileExtensionExtractor.extractFromName(content.filename)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,17 +8,20 @@
|
||||
package io.element.android.features.messages.impl.timeline.model.event
|
||||
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
|
||||
import io.element.android.libraries.mediaviewer.api.helper.formatFileExtensionAndSize
|
||||
import kotlin.time.Duration
|
||||
|
||||
data class TimelineItemAudioContent(
|
||||
val body: String,
|
||||
override val filename: String,
|
||||
override val caption: String?,
|
||||
override val formattedCaption: FormattedBody?,
|
||||
val duration: Duration,
|
||||
val mediaSource: MediaSource,
|
||||
val mimeType: String,
|
||||
val formattedFileSize: String,
|
||||
val fileExtension: String,
|
||||
) : TimelineItemEventContent {
|
||||
) : TimelineItemEventContentWithAttachment {
|
||||
val fileExtensionAndSize =
|
||||
formatFileExtensionAndSize(
|
||||
fileExtension,
|
||||
|
||||
@@ -22,8 +22,10 @@ open class TimelineItemAudioContentProvider : PreviewParameterProvider<TimelineI
|
||||
}
|
||||
|
||||
fun aTimelineItemAudioContent(fileName: String = "A sound.mp3") = TimelineItemAudioContent(
|
||||
body = fileName,
|
||||
mimeType = MimeTypes.Pdf,
|
||||
filename = fileName,
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
mimeType = MimeTypes.Mp3,
|
||||
formattedFileSize = "100kB",
|
||||
fileExtension = "mp3",
|
||||
duration = 100.milliseconds,
|
||||
|
||||
@@ -8,12 +8,23 @@
|
||||
package io.element.android.features.messages.impl.timeline.model.event
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
|
||||
|
||||
@Immutable
|
||||
sealed interface TimelineItemEventContent {
|
||||
val type: String
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed interface TimelineItemEventContentWithAttachment : TimelineItemEventContent {
|
||||
val filename: String
|
||||
val caption: String?
|
||||
val formattedCaption: FormattedBody?
|
||||
|
||||
val bestDescription: String
|
||||
get() = caption ?: filename
|
||||
}
|
||||
|
||||
/**
|
||||
* Only text based content can be copied.
|
||||
*/
|
||||
|
||||
@@ -8,16 +8,19 @@
|
||||
package io.element.android.features.messages.impl.timeline.model.event
|
||||
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
|
||||
import io.element.android.libraries.mediaviewer.api.helper.formatFileExtensionAndSize
|
||||
|
||||
data class TimelineItemFileContent(
|
||||
val body: String,
|
||||
override val filename: String,
|
||||
override val caption: String?,
|
||||
override val formattedCaption: FormattedBody?,
|
||||
val fileSource: MediaSource,
|
||||
val thumbnailSource: MediaSource?,
|
||||
val formattedFileSize: String,
|
||||
val fileExtension: String,
|
||||
val mimeType: String,
|
||||
) : TimelineItemEventContent {
|
||||
) : TimelineItemEventContentWithAttachment {
|
||||
override val type: String = "TimelineItemFileContent"
|
||||
|
||||
val fileExtensionAndSize = formatFileExtensionAndSize(fileExtension, formattedFileSize)
|
||||
|
||||
@@ -20,8 +20,12 @@ open class TimelineItemFileContentProvider : PreviewParameterProvider<TimelineIt
|
||||
)
|
||||
}
|
||||
|
||||
fun aTimelineItemFileContent(fileName: String = "A file.pdf") = TimelineItemFileContent(
|
||||
body = fileName,
|
||||
fun aTimelineItemFileContent(
|
||||
fileName: String = "A file.pdf",
|
||||
) = TimelineItemFileContent(
|
||||
filename = fileName,
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
thumbnailSource = null,
|
||||
fileSource = MediaSource(url = ""),
|
||||
mimeType = MimeTypes.Pdf,
|
||||
|
||||
@@ -12,9 +12,9 @@ import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
|
||||
|
||||
data class TimelineItemImageContent(
|
||||
val body: String,
|
||||
val formatted: FormattedBody?,
|
||||
val filename: String?,
|
||||
override val filename: String,
|
||||
override val caption: String?,
|
||||
override val formattedCaption: FormattedBody?,
|
||||
val mediaSource: MediaSource,
|
||||
val thumbnailSource: MediaSource?,
|
||||
val formattedFileSize: String,
|
||||
@@ -24,11 +24,10 @@ data class TimelineItemImageContent(
|
||||
val width: Int?,
|
||||
val height: Int?,
|
||||
val aspectRatio: Float?
|
||||
) : TimelineItemEventContent {
|
||||
) : TimelineItemEventContentWithAttachment {
|
||||
override val type: String = "TimelineItemImageContent"
|
||||
|
||||
val showCaption = filename != null && filename != body
|
||||
val caption = if (showCaption) body else ""
|
||||
val showCaption = caption != null
|
||||
|
||||
val preferredMediaSource = if (mimeType == MimeTypes.Gif) {
|
||||
mediaSource
|
||||
|
||||
@@ -23,12 +23,14 @@ open class TimelineItemImageContentProvider : PreviewParameterProvider<TimelineI
|
||||
}
|
||||
|
||||
fun aTimelineItemImageContent(
|
||||
aspectRatio: Float = 0.5f,
|
||||
aspectRatio: Float? = 0.5f,
|
||||
blurhash: String? = A_BLUR_HASH,
|
||||
filename: String = "A picture.jpg",
|
||||
caption: String? = null,
|
||||
) = TimelineItemImageContent(
|
||||
body = "a body",
|
||||
formatted = null,
|
||||
filename = null,
|
||||
filename = filename,
|
||||
caption = caption,
|
||||
formattedCaption = null,
|
||||
mediaSource = MediaSource(""),
|
||||
thumbnailSource = null,
|
||||
mimeType = MimeTypes.IMAGE_JPEG,
|
||||
|
||||
@@ -8,9 +8,12 @@
|
||||
package io.element.android.features.messages.impl.timeline.model.event
|
||||
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
|
||||
|
||||
data class TimelineItemStickerContent(
|
||||
val body: String,
|
||||
override val filename: String,
|
||||
override val caption: String?,
|
||||
override val formattedCaption: FormattedBody?,
|
||||
val mediaSource: MediaSource,
|
||||
val thumbnailSource: MediaSource?,
|
||||
val formattedFileSize: String,
|
||||
@@ -20,7 +23,7 @@ data class TimelineItemStickerContent(
|
||||
val width: Int?,
|
||||
val height: Int?,
|
||||
val aspectRatio: Float?
|
||||
) : TimelineItemEventContent {
|
||||
) : TimelineItemEventContentWithAttachment {
|
||||
override val type: String = "TimelineItemStickerContent"
|
||||
|
||||
/* Stickers are supposed to be small images so
|
||||
|
||||
@@ -26,7 +26,9 @@ fun aTimelineItemStickerContent(
|
||||
aspectRatio: Float = 0.5f,
|
||||
blurhash: String? = A_BLUR_HASH,
|
||||
) = TimelineItemStickerContent(
|
||||
body = "a body",
|
||||
filename = "a sticker.gif",
|
||||
caption = "a body",
|
||||
formattedCaption = null,
|
||||
mediaSource = MediaSource(""),
|
||||
thumbnailSource = null,
|
||||
mimeType = MimeTypes.IMAGE_JPEG,
|
||||
|
||||
@@ -12,9 +12,9 @@ import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
|
||||
import kotlin.time.Duration
|
||||
|
||||
data class TimelineItemVideoContent(
|
||||
val body: String,
|
||||
val formatted: FormattedBody?,
|
||||
val filename: String?,
|
||||
override val filename: String,
|
||||
override val caption: String?,
|
||||
override val formattedCaption: FormattedBody?,
|
||||
val duration: Duration,
|
||||
val videoSource: MediaSource,
|
||||
val thumbnailSource: MediaSource?,
|
||||
@@ -25,9 +25,8 @@ data class TimelineItemVideoContent(
|
||||
val mimeType: String,
|
||||
val formattedFileSize: String,
|
||||
val fileExtension: String,
|
||||
) : TimelineItemEventContent {
|
||||
) : TimelineItemEventContentWithAttachment {
|
||||
override val type: String = "TimelineItemImageContent"
|
||||
|
||||
val showCaption = filename != null && filename != body
|
||||
val caption = if (showCaption) body else ""
|
||||
val showCaption = caption != null
|
||||
}
|
||||
|
||||
@@ -27,9 +27,9 @@ fun aTimelineItemVideoContent(
|
||||
aspectRatio: Float = 0.5f,
|
||||
blurhash: String? = A_BLUR_HASH,
|
||||
) = TimelineItemVideoContent(
|
||||
body = "Video.mp4",
|
||||
formatted = null,
|
||||
filename = null,
|
||||
filename = "Video.mp4",
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
thumbnailSource = null,
|
||||
blurHash = blurhash,
|
||||
aspectRatio = aspectRatio,
|
||||
|
||||
@@ -9,16 +9,19 @@ package io.element.android.features.messages.impl.timeline.model.event
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlin.time.Duration
|
||||
|
||||
data class TimelineItemVoiceContent(
|
||||
val eventId: EventId?,
|
||||
val body: String,
|
||||
override val filename: String,
|
||||
override val caption: String?,
|
||||
override val formattedCaption: FormattedBody?,
|
||||
val duration: Duration,
|
||||
val mediaSource: MediaSource,
|
||||
val mimeType: String,
|
||||
val waveform: ImmutableList<Float>,
|
||||
) : TimelineItemEventContent {
|
||||
) : TimelineItemEventContentWithAttachment {
|
||||
override val type: String = "TimelineItemAudioContent"
|
||||
}
|
||||
|
||||
@@ -35,17 +35,21 @@ open class TimelineItemVoiceContentProvider : PreviewParameterProvider<TimelineI
|
||||
}
|
||||
|
||||
fun aTimelineItemVoiceContent(
|
||||
eventId: String? = "\$anEventId",
|
||||
body: String = "body doesn't really matter for a voice message",
|
||||
eventId: EventId? = EventId("\$anEventId"),
|
||||
filename: String = "filename doesn't really matter for a voice message",
|
||||
caption: String? = "body doesn't really matter for a voice message",
|
||||
duration: Duration = 61_000.milliseconds,
|
||||
contentUri: String = "mxc://matrix.org/1234567890abcdefg",
|
||||
mimeType: String = MimeTypes.Ogg,
|
||||
mediaSource: MediaSource = MediaSource(contentUri),
|
||||
waveform: List<Float> = listOf(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 8f, 7f, 6f, 5f, 4f, 3f, 2f, 1f, 0f),
|
||||
) = TimelineItemVoiceContent(
|
||||
eventId = eventId?.let { EventId(it) },
|
||||
body = body,
|
||||
eventId = eventId,
|
||||
filename = filename,
|
||||
caption = caption,
|
||||
formattedCaption = null,
|
||||
duration = duration,
|
||||
mediaSource = MediaSource(contentUri),
|
||||
mediaSource = mediaSource,
|
||||
mimeType = mimeType,
|
||||
waveform = waveform.toPersistentList(),
|
||||
)
|
||||
|
||||
@@ -59,7 +59,7 @@ class VoiceMessagePresenter @AssistedInject constructor(
|
||||
eventId = content.eventId,
|
||||
mediaSource = content.mediaSource,
|
||||
mimeType = content.mimeType,
|
||||
body = content.body,
|
||||
body = content.caption,
|
||||
)
|
||||
|
||||
private val play = mutableStateOf<AsyncData<Unit>>(AsyncData.Uninitialized)
|
||||
|
||||
@@ -335,9 +335,9 @@ class MessagesPresenterTest {
|
||||
val initialState = awaitItem()
|
||||
val mediaMessage = aMessageEvent(
|
||||
content = TimelineItemImageContent(
|
||||
body = "image.jpg",
|
||||
formatted = null,
|
||||
filename = null,
|
||||
filename = "image.jpg",
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
mediaSource = MediaSource(AN_AVATAR_URL),
|
||||
thumbnailSource = null,
|
||||
mimeType = MimeTypes.Jpeg,
|
||||
@@ -374,9 +374,9 @@ class MessagesPresenterTest {
|
||||
val initialState = awaitItem()
|
||||
val mediaMessage = aMessageEvent(
|
||||
content = TimelineItemVideoContent(
|
||||
body = "video.mp4",
|
||||
formatted = null,
|
||||
filename = null,
|
||||
filename = "video.mp4",
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
duration = 10.milliseconds,
|
||||
videoSource = MediaSource(AN_AVATAR_URL),
|
||||
thumbnailSource = MediaSource(AN_AVATAR_URL),
|
||||
@@ -414,7 +414,9 @@ class MessagesPresenterTest {
|
||||
val initialState = awaitItem()
|
||||
val mediaMessage = aMessageEvent(
|
||||
content = TimelineItemFileContent(
|
||||
body = "file.pdf",
|
||||
filename = "file.pdf",
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
fileSource = MediaSource(AN_AVATAR_URL),
|
||||
thumbnailSource = MediaSource(AN_AVATAR_URL),
|
||||
formattedFileSize = "10 MB",
|
||||
|
||||
@@ -69,7 +69,7 @@ class PinnedMessagesListViewTest {
|
||||
state = state,
|
||||
onEventClick = callback
|
||||
)
|
||||
rule.onAllNodesWithText(content.body).onFirst().performClick()
|
||||
rule.onAllNodesWithText(content.filename).onFirst().performClick()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ class PinnedMessagesListViewTest {
|
||||
rule.setPinnedMessagesListView(
|
||||
state = state,
|
||||
)
|
||||
rule.onAllNodesWithText(content.body).onFirst()
|
||||
rule.onAllNodesWithText(content.filename).onFirst()
|
||||
.performTouchInput {
|
||||
longClick()
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageT
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.media.aMediaSource
|
||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
||||
import io.element.android.libraries.matrix.test.timeline.aStickerContent
|
||||
import io.element.android.libraries.matrix.ui.components.A_BLUR_HASH
|
||||
import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractorWithoutValidation
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
@@ -228,14 +229,14 @@ class TimelineItemContentMessageFactoryTest {
|
||||
fun `test create VideoMessageType`() = runTest {
|
||||
val sut = createTimelineItemContentMessageFactory()
|
||||
val result = sut.create(
|
||||
content = createMessageContent(type = VideoMessageType("body", null, null, MediaSource("url"), null)),
|
||||
content = createMessageContent(type = VideoMessageType("filename", null, null, MediaSource("url"), null)),
|
||||
senderDisambiguatedDisplayName = "Bob",
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
val expected = TimelineItemVideoContent(
|
||||
body = "body",
|
||||
formatted = null,
|
||||
filename = null,
|
||||
filename = "filename",
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
duration = Duration.ZERO,
|
||||
videoSource = MediaSource(url = "url", json = null),
|
||||
thumbnailSource = null,
|
||||
@@ -256,9 +257,9 @@ class TimelineItemContentMessageFactoryTest {
|
||||
val result = sut.create(
|
||||
content = createMessageContent(
|
||||
type = VideoMessageType(
|
||||
body = "body.mp4 caption",
|
||||
formatted = FormattedBody(MessageFormat.HTML, "formatted"),
|
||||
filename = "body.mp4",
|
||||
caption = "body.mp4 caption",
|
||||
formattedCaption = FormattedBody(MessageFormat.HTML, "formatted"),
|
||||
source = MediaSource("url"),
|
||||
info = VideoInfo(
|
||||
duration = 1.minutes,
|
||||
@@ -281,9 +282,9 @@ class TimelineItemContentMessageFactoryTest {
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
val expected = TimelineItemVideoContent(
|
||||
body = "body.mp4 caption",
|
||||
formatted = FormattedBody(MessageFormat.HTML, "formatted"),
|
||||
filename = "body.mp4",
|
||||
caption = "body.mp4 caption",
|
||||
formattedCaption = FormattedBody(MessageFormat.HTML, "formatted"),
|
||||
duration = 1.minutes,
|
||||
videoSource = MediaSource(url = "url", json = null),
|
||||
thumbnailSource = MediaSource("url_thumbnail"),
|
||||
@@ -302,12 +303,14 @@ class TimelineItemContentMessageFactoryTest {
|
||||
fun `test create AudioMessageType`() = runTest {
|
||||
val sut = createTimelineItemContentMessageFactory()
|
||||
val result = sut.create(
|
||||
content = createMessageContent(type = AudioMessageType("body", MediaSource("url"), null)),
|
||||
content = createMessageContent(type = AudioMessageType("filename", null, null, MediaSource("url"), null)),
|
||||
senderDisambiguatedDisplayName = "Bob",
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
val expected = TimelineItemAudioContent(
|
||||
body = "body",
|
||||
filename = "filename",
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
duration = Duration.ZERO,
|
||||
mediaSource = MediaSource(url = "url", json = null),
|
||||
mimeType = MimeTypes.OctetStream,
|
||||
@@ -323,7 +326,9 @@ class TimelineItemContentMessageFactoryTest {
|
||||
val result = sut.create(
|
||||
content = createMessageContent(
|
||||
type = AudioMessageType(
|
||||
body = "body.mp3",
|
||||
filename = "body.mp3",
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
source = MediaSource("url"),
|
||||
info = AudioInfo(
|
||||
duration = 1.minutes,
|
||||
@@ -336,7 +341,9 @@ class TimelineItemContentMessageFactoryTest {
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
val expected = TimelineItemAudioContent(
|
||||
body = "body.mp3",
|
||||
filename = "body.mp3",
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
duration = 1.minutes,
|
||||
mediaSource = MediaSource(url = "url", json = null),
|
||||
mimeType = MimeTypes.Mp3,
|
||||
@@ -350,13 +357,15 @@ class TimelineItemContentMessageFactoryTest {
|
||||
fun `test create VoiceMessageType`() = runTest {
|
||||
val sut = createTimelineItemContentMessageFactory()
|
||||
val result = sut.create(
|
||||
content = createMessageContent(type = VoiceMessageType("body", MediaSource("url"), null, null)),
|
||||
content = createMessageContent(type = VoiceMessageType("filename", null, null, MediaSource("url"), null, null)),
|
||||
senderDisambiguatedDisplayName = "Bob",
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
val expected = TimelineItemVoiceContent(
|
||||
filename = "filename",
|
||||
eventId = AN_EVENT_ID,
|
||||
body = "body",
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
duration = Duration.ZERO,
|
||||
mediaSource = MediaSource(url = "url", json = null),
|
||||
mimeType = MimeTypes.OctetStream,
|
||||
@@ -371,7 +380,9 @@ class TimelineItemContentMessageFactoryTest {
|
||||
val result = sut.create(
|
||||
content = createMessageContent(
|
||||
type = VoiceMessageType(
|
||||
body = "body.ogg",
|
||||
filename = "body.ogg",
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
source = MediaSource("url"),
|
||||
info = AudioInfo(
|
||||
duration = 1.minutes,
|
||||
@@ -389,7 +400,9 @@ class TimelineItemContentMessageFactoryTest {
|
||||
)
|
||||
val expected = TimelineItemVoiceContent(
|
||||
eventId = AN_EVENT_ID,
|
||||
body = "body.ogg",
|
||||
filename = "body.ogg",
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
duration = 1.minutes,
|
||||
mediaSource = MediaSource(url = "url", json = null),
|
||||
mimeType = MimeTypes.Ogg,
|
||||
@@ -408,12 +421,14 @@ class TimelineItemContentMessageFactoryTest {
|
||||
)
|
||||
)
|
||||
val result = sut.create(
|
||||
content = createMessageContent(type = VoiceMessageType("body", MediaSource("url"), null, null)),
|
||||
content = createMessageContent(type = VoiceMessageType("filename", null, null, MediaSource("url"), null, null)),
|
||||
senderDisambiguatedDisplayName = "Bob",
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
val expected = TimelineItemAudioContent(
|
||||
body = "body",
|
||||
filename = "filename",
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
duration = Duration.ZERO,
|
||||
mediaSource = MediaSource(url = "url", json = null),
|
||||
mimeType = MimeTypes.OctetStream,
|
||||
@@ -427,14 +442,14 @@ class TimelineItemContentMessageFactoryTest {
|
||||
fun `test create ImageMessageType`() = runTest {
|
||||
val sut = createTimelineItemContentMessageFactory()
|
||||
val result = sut.create(
|
||||
content = createMessageContent(type = ImageMessageType("body", null, null, MediaSource("url"), null)),
|
||||
content = createMessageContent(type = ImageMessageType("filename", "body", null, MediaSource("url"), null)),
|
||||
senderDisambiguatedDisplayName = "Bob",
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
val expected = TimelineItemImageContent(
|
||||
body = "body",
|
||||
formatted = null,
|
||||
filename = null,
|
||||
filename = "filename",
|
||||
caption = "body",
|
||||
formattedCaption = null,
|
||||
mediaSource = MediaSource(url = "url", json = null),
|
||||
thumbnailSource = null,
|
||||
formattedFileSize = "0 Bytes",
|
||||
@@ -453,13 +468,15 @@ class TimelineItemContentMessageFactoryTest {
|
||||
val sut = createTimelineItemContentStickerFactory()
|
||||
val result = sut.create(
|
||||
content = createStickerContent(
|
||||
"body",
|
||||
ImageInfo(32, 32, "image/webp", 8192, null, MediaSource("thumbnail://url"), null),
|
||||
"url"
|
||||
filename = "filename",
|
||||
inImageInfo = ImageInfo(32, 32, "image/webp", 8192, null, MediaSource("thumbnail://url"), null),
|
||||
inUrl = "url"
|
||||
)
|
||||
)
|
||||
val expected = TimelineItemStickerContent(
|
||||
body = "body",
|
||||
filename = "filename",
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
mediaSource = MediaSource(url = "url", json = null),
|
||||
thumbnailSource = MediaSource(url = "thumbnail://url", json = null),
|
||||
formattedFileSize = "8192 Bytes",
|
||||
@@ -479,9 +496,9 @@ class TimelineItemContentMessageFactoryTest {
|
||||
val result = sut.create(
|
||||
content = createMessageContent(
|
||||
type = ImageMessageType(
|
||||
body = "body.jpg caption",
|
||||
formatted = FormattedBody(MessageFormat.HTML, "formatted"),
|
||||
filename = "body.jpg",
|
||||
caption = "body.jpg caption",
|
||||
formattedCaption = FormattedBody(MessageFormat.HTML, "formatted"),
|
||||
source = MediaSource("url"),
|
||||
info = ImageInfo(
|
||||
height = 10L,
|
||||
@@ -503,9 +520,9 @@ class TimelineItemContentMessageFactoryTest {
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
val expected = TimelineItemImageContent(
|
||||
body = "body.jpg caption",
|
||||
formatted = FormattedBody(MessageFormat.HTML, "formatted"),
|
||||
filename = "body.jpg",
|
||||
formattedCaption = FormattedBody(MessageFormat.HTML, "formatted"),
|
||||
caption = "body.jpg caption",
|
||||
mediaSource = MediaSource(url = "url", json = null),
|
||||
thumbnailSource = MediaSource("url_thumbnail"),
|
||||
formattedFileSize = "888 Bytes",
|
||||
@@ -523,12 +540,14 @@ class TimelineItemContentMessageFactoryTest {
|
||||
fun `test create FileMessageType`() = runTest {
|
||||
val sut = createTimelineItemContentMessageFactory()
|
||||
val result = sut.create(
|
||||
content = createMessageContent(type = FileMessageType("body", MediaSource("url"), null)),
|
||||
content = createMessageContent(type = FileMessageType("filename", null, null, MediaSource("url"), null)),
|
||||
senderDisambiguatedDisplayName = "Bob",
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
val expected = TimelineItemFileContent(
|
||||
body = "body",
|
||||
filename = "filename",
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
fileSource = MediaSource(url = "url", json = null),
|
||||
thumbnailSource = null,
|
||||
formattedFileSize = "0 Bytes",
|
||||
@@ -544,7 +563,9 @@ class TimelineItemContentMessageFactoryTest {
|
||||
val result = sut.create(
|
||||
content = createMessageContent(
|
||||
type = FileMessageType(
|
||||
body = "body.pdf",
|
||||
filename = "body.pdf",
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
source = MediaSource("url"),
|
||||
info = FileInfo(
|
||||
mimetype = MimeTypes.Pdf,
|
||||
@@ -563,7 +584,9 @@ class TimelineItemContentMessageFactoryTest {
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
val expected = TimelineItemFileContent(
|
||||
body = "body.pdf",
|
||||
filename = "body.pdf",
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
fileSource = MediaSource(url = "url", json = null),
|
||||
thumbnailSource = MediaSource("url_thumbnail"),
|
||||
formattedFileSize = "123 Bytes",
|
||||
@@ -749,14 +772,16 @@ class TimelineItemContentMessageFactoryTest {
|
||||
)
|
||||
|
||||
private fun createStickerContent(
|
||||
body: String = "Body",
|
||||
filename: String = "filename",
|
||||
inImageInfo: ImageInfo,
|
||||
inUrl: String
|
||||
inUrl: String,
|
||||
body: String? = null,
|
||||
): StickerContent {
|
||||
return StickerContent(
|
||||
return aStickerContent(
|
||||
filename = filename,
|
||||
body = body,
|
||||
info = inImageInfo,
|
||||
source = aMediaSource(url = inUrl),
|
||||
mediaSource = aMediaSource(url = inUrl),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -206,7 +206,8 @@ class RoomDetailsFlowNode @AssistedInject constructor(
|
||||
val mimeType = MimeTypes.Images
|
||||
val input = MediaViewerNode.Inputs(
|
||||
mediaInfo = MediaInfo(
|
||||
name = navTarget.name,
|
||||
filename = navTarget.name,
|
||||
caption = null,
|
||||
mimeType = mimeType,
|
||||
formattedFileSize = "",
|
||||
fileExtension = ""
|
||||
|
||||
@@ -84,10 +84,11 @@ class UserProfileFlowNode @AssistedInject constructor(
|
||||
val mimeType = MimeTypes.Images
|
||||
val input = MediaViewerNode.Inputs(
|
||||
mediaInfo = MediaInfo(
|
||||
name = navTarget.name,
|
||||
filename = navTarget.name,
|
||||
caption = null,
|
||||
mimeType = mimeType,
|
||||
formattedFileSize = "",
|
||||
fileExtension = ""
|
||||
fileExtension = "",
|
||||
),
|
||||
mediaSource = MediaSource(url = navTarget.avatarUrl),
|
||||
thumbnailSource = null,
|
||||
|
||||
@@ -46,7 +46,8 @@ class DefaultPinnedMessagesBannerFormatter @Inject constructor(
|
||||
return when (val content = event.content) {
|
||||
is MessageContent -> processMessageContents(event, content)
|
||||
is StickerContent -> {
|
||||
content.body.prefixWith(CommonStrings.common_sticker)
|
||||
val text = content.body ?: content.filename
|
||||
text.prefixWith(CommonStrings.common_sticker)
|
||||
}
|
||||
is UnableToDecryptContent -> {
|
||||
sp.getString(CommonStrings.common_waiting_for_decryption_key)
|
||||
@@ -76,25 +77,25 @@ class DefaultPinnedMessagesBannerFormatter @Inject constructor(
|
||||
messageType.toPlainText(permalinkParser)
|
||||
}
|
||||
is VideoMessageType -> {
|
||||
messageType.body.prefixWith(CommonStrings.common_video)
|
||||
messageType.bestDescription.prefixWith(CommonStrings.common_video)
|
||||
}
|
||||
is ImageMessageType -> {
|
||||
messageType.body.prefixWith(CommonStrings.common_image)
|
||||
messageType.bestDescription.prefixWith(CommonStrings.common_image)
|
||||
}
|
||||
is StickerMessageType -> {
|
||||
messageType.body.prefixWith(CommonStrings.common_sticker)
|
||||
messageType.bestDescription.prefixWith(CommonStrings.common_sticker)
|
||||
}
|
||||
is LocationMessageType -> {
|
||||
messageType.body.prefixWith(CommonStrings.common_shared_location)
|
||||
}
|
||||
is FileMessageType -> {
|
||||
messageType.body.prefixWith(CommonStrings.common_file)
|
||||
messageType.bestDescription.prefixWith(CommonStrings.common_file)
|
||||
}
|
||||
is AudioMessageType -> {
|
||||
messageType.body.prefixWith(CommonStrings.common_audio)
|
||||
messageType.bestDescription.prefixWith(CommonStrings.common_audio)
|
||||
}
|
||||
is VoiceMessageType -> {
|
||||
messageType.body.prefixWith(CommonStrings.common_voice_message)
|
||||
messageType.bestDescription.prefixWith(CommonStrings.common_voice_message)
|
||||
}
|
||||
is OtherMessageType -> {
|
||||
messageType.body
|
||||
|
||||
@@ -67,7 +67,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
|
||||
message.prefixIfNeeded(senderDisambiguatedDisplayName, isDmRoom, isOutgoing)
|
||||
}
|
||||
is StickerContent -> {
|
||||
val message = sp.getString(CommonStrings.common_sticker) + " (" + content.body + ")"
|
||||
val message = sp.getString(CommonStrings.common_sticker) + " (" + content.bestDescription + ")"
|
||||
message.prefixIfNeeded(senderDisambiguatedDisplayName, isDmRoom, isOutgoing)
|
||||
}
|
||||
is UnableToDecryptContent -> {
|
||||
|
||||
@@ -32,7 +32,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.OtherState
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
|
||||
@@ -46,6 +45,7 @@ import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
||||
import io.element.android.libraries.matrix.test.timeline.aPollContent
|
||||
import io.element.android.libraries.matrix.test.timeline.aProfileChangeMessageContent
|
||||
import io.element.android.libraries.matrix.test.timeline.aProfileTimelineDetails
|
||||
import io.element.android.libraries.matrix.test.timeline.aStickerContent
|
||||
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.services.toolbox.impl.strings.AndroidStringProvider
|
||||
@@ -91,7 +91,7 @@ class DefaultPinnedMessagesBannerFormatterTest {
|
||||
fun `Sticker content`() {
|
||||
val body = "a sticker body"
|
||||
val info = ImageInfo(null, null, null, null, null, null, null)
|
||||
val message = createRoomEvent(false, null, StickerContent(body, info, aMediaSource(url = "url")))
|
||||
val message = createRoomEvent(false, null, aStickerContent(body, info, aMediaSource(url = "url")))
|
||||
val result = formatter.format(message)
|
||||
val expectedBody = "Sticker: a sticker body"
|
||||
assertThat(result.toString()).isEqualTo(expectedBody)
|
||||
@@ -135,11 +135,11 @@ class DefaultPinnedMessagesBannerFormatterTest {
|
||||
val sharedContentMessagesTypes = arrayOf(
|
||||
TextMessageType(body, null),
|
||||
VideoMessageType(body, null, null, MediaSource("url"), null),
|
||||
AudioMessageType(body, MediaSource("url"), null),
|
||||
VoiceMessageType(body, MediaSource("url"), null, null),
|
||||
AudioMessageType(body, null, null, MediaSource("url"), null),
|
||||
VoiceMessageType(body, null, null, MediaSource("url"), null, null),
|
||||
ImageMessageType(body, null, null, MediaSource("url"), null),
|
||||
StickerMessageType(body, MediaSource("url"), null),
|
||||
FileMessageType(body, MediaSource("url"), null),
|
||||
StickerMessageType(body, null, null, MediaSource("url"), null),
|
||||
FileMessageType(body, null, null, MediaSource("url"), null),
|
||||
LocationMessageType(body, "geo:1,2", null),
|
||||
NoticeMessageType(body, null),
|
||||
EmoteMessageType(body, null),
|
||||
|
||||
@@ -32,7 +32,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.OtherState
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
|
||||
@@ -46,6 +45,7 @@ import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
||||
import io.element.android.libraries.matrix.test.timeline.aPollContent
|
||||
import io.element.android.libraries.matrix.test.timeline.aProfileChangeMessageContent
|
||||
import io.element.android.libraries.matrix.test.timeline.aProfileTimelineDetails
|
||||
import io.element.android.libraries.matrix.test.timeline.aStickerContent
|
||||
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
|
||||
import io.element.android.services.toolbox.impl.strings.AndroidStringProvider
|
||||
import org.junit.Before
|
||||
@@ -98,7 +98,7 @@ class DefaultRoomLastMessageFormatterTest {
|
||||
fun `Sticker content`() {
|
||||
val body = "a sticker body"
|
||||
val info = ImageInfo(null, null, null, null, null, null, null)
|
||||
val message = createRoomEvent(false, null, StickerContent(body, info, aMediaSource(url = "url")))
|
||||
val message = createRoomEvent(false, null, aStickerContent(body, info, aMediaSource(url = "url")))
|
||||
val result = formatter.format(message, false)
|
||||
val expectedBody = someoneElseId.toString() + ": Sticker (a sticker body)"
|
||||
assertThat(result.toString()).isEqualTo(expectedBody)
|
||||
@@ -179,11 +179,11 @@ class DefaultRoomLastMessageFormatterTest {
|
||||
val sharedContentMessagesTypes = arrayOf(
|
||||
TextMessageType(body, null),
|
||||
VideoMessageType(body, null, null, MediaSource("url"), null),
|
||||
AudioMessageType(body, MediaSource("url"), null),
|
||||
VoiceMessageType(body, MediaSource("url"), null, null),
|
||||
AudioMessageType(body, null, null, MediaSource("url"), null),
|
||||
VoiceMessageType(body, null, null, MediaSource("url"), null, null),
|
||||
ImageMessageType(body, null, null, MediaSource("url"), null),
|
||||
StickerMessageType(body, MediaSource("url"), null),
|
||||
FileMessageType(body, MediaSource("url"), null),
|
||||
StickerMessageType(body, null, null, MediaSource("url"), null),
|
||||
FileMessageType(body, null, null, MediaSource("url"), null),
|
||||
LocationMessageType(body, "geo:1,2", null),
|
||||
NoticeMessageType(body, null),
|
||||
EmoteMessageType(body, null),
|
||||
|
||||
@@ -30,10 +30,14 @@ data class MessageContent(
|
||||
data object RedactedContent : EventContent
|
||||
|
||||
data class StickerContent(
|
||||
val body: String,
|
||||
val filename: String,
|
||||
val body: String?,
|
||||
val info: ImageInfo,
|
||||
val source: MediaSource,
|
||||
) : EventContent
|
||||
) : EventContent {
|
||||
val bestDescription: String
|
||||
get() = body ?: filename
|
||||
}
|
||||
|
||||
data class PollContent(
|
||||
val question: String,
|
||||
|
||||
@@ -18,24 +18,37 @@ import io.element.android.libraries.matrix.api.media.VideoInfo
|
||||
@Immutable
|
||||
sealed interface MessageType
|
||||
|
||||
@Immutable
|
||||
sealed interface MessageTypeWithAttachment : MessageType {
|
||||
val filename: String
|
||||
val caption: String?
|
||||
val formattedCaption: FormattedBody?
|
||||
|
||||
val bestDescription: String
|
||||
get() = caption ?: filename
|
||||
}
|
||||
|
||||
data class EmoteMessageType(
|
||||
val body: String,
|
||||
val formatted: FormattedBody?
|
||||
) : MessageType
|
||||
|
||||
data class ImageMessageType(
|
||||
val body: String,
|
||||
val formatted: FormattedBody?,
|
||||
val filename: String?,
|
||||
override val filename: String,
|
||||
override val caption: String?,
|
||||
override val formattedCaption: FormattedBody?,
|
||||
val source: MediaSource,
|
||||
val info: ImageInfo?
|
||||
) : MessageType
|
||||
) : MessageTypeWithAttachment
|
||||
|
||||
// FIXME This is never used in production code.
|
||||
data class StickerMessageType(
|
||||
val body: String,
|
||||
override val filename: String,
|
||||
override val caption: String?,
|
||||
override val formattedCaption: FormattedBody?,
|
||||
val source: MediaSource,
|
||||
val info: ImageInfo?
|
||||
) : MessageType
|
||||
) : MessageTypeWithAttachment
|
||||
|
||||
data class LocationMessageType(
|
||||
val body: String,
|
||||
@@ -44,31 +57,37 @@ data class LocationMessageType(
|
||||
) : MessageType
|
||||
|
||||
data class AudioMessageType(
|
||||
val body: String,
|
||||
override val filename: String,
|
||||
override val caption: String?,
|
||||
override val formattedCaption: FormattedBody?,
|
||||
val source: MediaSource,
|
||||
val info: AudioInfo?,
|
||||
) : MessageType
|
||||
) : MessageTypeWithAttachment
|
||||
|
||||
data class VoiceMessageType(
|
||||
val body: String,
|
||||
override val filename: String,
|
||||
override val caption: String?,
|
||||
override val formattedCaption: FormattedBody?,
|
||||
val source: MediaSource,
|
||||
val info: AudioInfo?,
|
||||
val details: AudioDetails?,
|
||||
) : MessageType
|
||||
) : MessageTypeWithAttachment
|
||||
|
||||
data class VideoMessageType(
|
||||
val body: String,
|
||||
val formatted: FormattedBody?,
|
||||
val filename: String?,
|
||||
override val filename: String,
|
||||
override val caption: String?,
|
||||
override val formattedCaption: FormattedBody?,
|
||||
val source: MediaSource,
|
||||
val info: VideoInfo?
|
||||
) : MessageType
|
||||
) : MessageTypeWithAttachment
|
||||
|
||||
data class FileMessageType(
|
||||
val body: String,
|
||||
override val filename: String,
|
||||
override val caption: String?,
|
||||
override val formattedCaption: FormattedBody?,
|
||||
val source: MediaSource,
|
||||
val info: FileInfo?
|
||||
) : MessageType
|
||||
) : MessageTypeWithAttachment
|
||||
|
||||
data class NoticeMessageType(
|
||||
val body: String,
|
||||
|
||||
@@ -50,14 +50,18 @@ class EventMessageMapper {
|
||||
when (type.content.voice) {
|
||||
null -> {
|
||||
AudioMessageType(
|
||||
body = type.content.body,
|
||||
filename = type.content.filename,
|
||||
caption = type.content.caption,
|
||||
formattedCaption = type.content.formattedCaption?.map(),
|
||||
source = type.content.source.map(),
|
||||
info = type.content.info?.map(),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
VoiceMessageType(
|
||||
body = type.content.body,
|
||||
filename = type.content.filename,
|
||||
caption = type.content.caption,
|
||||
formattedCaption = type.content.formattedCaption?.map(),
|
||||
source = type.content.source.map(),
|
||||
info = type.content.info?.map(),
|
||||
details = type.content.audio?.map(),
|
||||
@@ -66,10 +70,22 @@ class EventMessageMapper {
|
||||
}
|
||||
}
|
||||
is RustMessageType.File -> {
|
||||
FileMessageType(type.content.body, type.content.source.map(), type.content.info?.map())
|
||||
FileMessageType(
|
||||
filename = type.content.filename,
|
||||
caption = type.content.caption,
|
||||
formattedCaption = type.content.formattedCaption?.map(),
|
||||
source = type.content.source.map(),
|
||||
info = type.content.info?.map(),
|
||||
)
|
||||
}
|
||||
is RustMessageType.Image -> {
|
||||
ImageMessageType(type.content.body, type.content.formatted?.map(), type.content.filename, type.content.source.map(), type.content.info?.map())
|
||||
ImageMessageType(
|
||||
filename = type.content.filename,
|
||||
caption = type.content.caption,
|
||||
formattedCaption = type.content.formattedCaption?.map(),
|
||||
source = type.content.source.map(),
|
||||
info = type.content.info?.map(),
|
||||
)
|
||||
}
|
||||
is RustMessageType.Notice -> {
|
||||
NoticeMessageType(type.content.body, type.content.formatted?.map())
|
||||
@@ -81,7 +97,13 @@ class EventMessageMapper {
|
||||
EmoteMessageType(type.content.body, type.content.formatted?.map())
|
||||
}
|
||||
is RustMessageType.Video -> {
|
||||
VideoMessageType(type.content.body, type.content.formatted?.map(), type.content.filename, type.content.source.map(), type.content.info?.map())
|
||||
VideoMessageType(
|
||||
filename = type.content.filename,
|
||||
caption = type.content.caption,
|
||||
formattedCaption = type.content.formattedCaption?.map(),
|
||||
source = type.content.source.map(),
|
||||
info = type.content.info?.map(),
|
||||
)
|
||||
}
|
||||
is RustMessageType.Location -> {
|
||||
LocationMessageType(type.content.body, type.content.geoUri, type.content.description)
|
||||
|
||||
@@ -84,7 +84,8 @@ class TimelineEventContentMapper(
|
||||
}
|
||||
is TimelineItemContent.Sticker -> {
|
||||
StickerContent(
|
||||
body = it.body,
|
||||
filename = it.body,
|
||||
body = null,
|
||||
info = it.info.map(),
|
||||
source = it.source.map(),
|
||||
)
|
||||
|
||||
@@ -10,6 +10,8 @@ package io.element.android.libraries.matrix.test.timeline
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.media.ImageInfo
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.api.poll.PollAnswer
|
||||
import io.element.android.libraries.matrix.api.poll.PollKind
|
||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||
@@ -25,6 +27,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.Receipt
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
@@ -110,6 +113,18 @@ fun aMessageContent(
|
||||
type = messageType
|
||||
)
|
||||
|
||||
fun aStickerContent(
|
||||
filename: String = "filename",
|
||||
info: ImageInfo,
|
||||
mediaSource: MediaSource,
|
||||
body: String? = null,
|
||||
) = StickerContent(
|
||||
filename = filename,
|
||||
body = body,
|
||||
info = info,
|
||||
source = mediaSource,
|
||||
)
|
||||
|
||||
fun aTimelineItemDebugInfo(
|
||||
model: String = "Rust(Model())",
|
||||
originalJson: String? = null,
|
||||
|
||||
@@ -44,7 +44,7 @@ internal class CoilMediaFetcher(
|
||||
*
|
||||
*/
|
||||
private suspend fun fetchFile(mediaSource: MediaSource, kind: MediaRequestData.Kind.File): FetchResult? {
|
||||
return mediaLoader.downloadMediaFile(mediaSource, kind.mimeType, kind.body)
|
||||
return mediaLoader.downloadMediaFile(mediaSource, kind.mimeType, kind.fileName)
|
||||
.map { mediaFile ->
|
||||
val file = mediaFile.toFile()
|
||||
SourceResult(
|
||||
|
||||
@@ -26,7 +26,12 @@ data class MediaRequestData(
|
||||
) {
|
||||
sealed interface Kind {
|
||||
data object Content : Kind
|
||||
data class File(val body: String?, val mimeType: String) : Kind
|
||||
|
||||
data class File(
|
||||
val fileName: String,
|
||||
val mimeType: String,
|
||||
) : Kind
|
||||
|
||||
data class Thumbnail(val width: Long, val height: Long) : Kind {
|
||||
constructor(size: Long) : this(size, size)
|
||||
}
|
||||
|
||||
@@ -49,11 +49,11 @@ open class InReplyToDetailsProvider : PreviewParameterProvider<InReplyToDetails>
|
||||
),
|
||||
aMessageContent(
|
||||
body = "Audio",
|
||||
type = AudioMessageType("Audio", MediaSource("url"), null),
|
||||
type = AudioMessageType("Audio", null, null, MediaSource("url"), null),
|
||||
),
|
||||
aMessageContent(
|
||||
body = "Voice",
|
||||
type = VoiceMessageType("Voice", MediaSource("url"), null, null),
|
||||
type = VoiceMessageType("Voice", null, null, MediaSource("url"), null, null),
|
||||
),
|
||||
aMessageContent(
|
||||
body = "Image",
|
||||
@@ -61,11 +61,11 @@ open class InReplyToDetailsProvider : PreviewParameterProvider<InReplyToDetails>
|
||||
),
|
||||
aMessageContent(
|
||||
body = "Sticker",
|
||||
type = StickerMessageType("Image", MediaSource("url"), null),
|
||||
type = StickerMessageType("Image", null, null, MediaSource("url"), null),
|
||||
),
|
||||
aMessageContent(
|
||||
body = "File",
|
||||
type = FileMessageType("File", MediaSource("url"), null),
|
||||
type = FileMessageType("File", null, null, MediaSource("url"), null),
|
||||
),
|
||||
aMessageContent(
|
||||
body = "Location",
|
||||
|
||||
@@ -75,9 +75,9 @@ class InReplyToMetadataKtTest {
|
||||
anInReplyToDetailsReady(
|
||||
eventContent = aMessageContent(
|
||||
messageType = ImageMessageType(
|
||||
body = "body",
|
||||
formatted = null,
|
||||
filename = null,
|
||||
filename = "filename",
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
source = aMediaSource(),
|
||||
info = anImageInfo(),
|
||||
)
|
||||
@@ -105,9 +105,9 @@ class InReplyToMetadataKtTest {
|
||||
anInReplyToDetailsReady(
|
||||
eventContent = aMessageContent(
|
||||
messageType = ImageMessageType(
|
||||
body = "body",
|
||||
formatted = null,
|
||||
filename = null,
|
||||
filename = "filename",
|
||||
caption = "caption",
|
||||
formattedCaption = null,
|
||||
source = aMediaSource(),
|
||||
info = anImageInfo(),
|
||||
)
|
||||
@@ -134,6 +134,7 @@ class InReplyToMetadataKtTest {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
anInReplyToDetailsReady(
|
||||
eventContent = StickerContent(
|
||||
filename = "filename",
|
||||
body = "body",
|
||||
info = anImageInfo(),
|
||||
source = aMediaSource(url = "url")
|
||||
@@ -160,6 +161,7 @@ class InReplyToMetadataKtTest {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
anInReplyToDetailsReady(
|
||||
eventContent = StickerContent(
|
||||
filename = "filename",
|
||||
body = "body",
|
||||
info = anImageInfo(),
|
||||
source = aMediaSource(url = "url")
|
||||
@@ -187,9 +189,9 @@ class InReplyToMetadataKtTest {
|
||||
anInReplyToDetailsReady(
|
||||
eventContent = aMessageContent(
|
||||
messageType = VideoMessageType(
|
||||
body = "body",
|
||||
formatted = null,
|
||||
filename = null,
|
||||
filename = "filename",
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
source = aMediaSource(),
|
||||
info = aVideoInfo(),
|
||||
)
|
||||
@@ -217,9 +219,9 @@ class InReplyToMetadataKtTest {
|
||||
anInReplyToDetailsReady(
|
||||
eventContent = aMessageContent(
|
||||
messageType = VideoMessageType(
|
||||
body = "body",
|
||||
formatted = null,
|
||||
filename = null,
|
||||
filename = "filename",
|
||||
caption = "caption",
|
||||
formattedCaption = null,
|
||||
source = aMediaSource(),
|
||||
info = aVideoInfo(),
|
||||
)
|
||||
@@ -247,7 +249,9 @@ class InReplyToMetadataKtTest {
|
||||
anInReplyToDetailsReady(
|
||||
eventContent = aMessageContent(
|
||||
messageType = FileMessageType(
|
||||
body = "body",
|
||||
filename = "filename",
|
||||
caption = "caption",
|
||||
formattedCaption = null,
|
||||
source = aMediaSource(),
|
||||
info = FileInfo(
|
||||
mimetype = null,
|
||||
@@ -280,7 +284,9 @@ class InReplyToMetadataKtTest {
|
||||
anInReplyToDetailsReady(
|
||||
eventContent = aMessageContent(
|
||||
messageType = FileMessageType(
|
||||
body = "body",
|
||||
filename = "filename",
|
||||
caption = "caption",
|
||||
formattedCaption = null,
|
||||
source = aMediaSource(),
|
||||
info = FileInfo(
|
||||
mimetype = null,
|
||||
@@ -313,7 +319,9 @@ class InReplyToMetadataKtTest {
|
||||
anInReplyToDetailsReady(
|
||||
eventContent = aMessageContent(
|
||||
messageType = AudioMessageType(
|
||||
body = "body",
|
||||
filename = "filename",
|
||||
caption = "caption",
|
||||
formattedCaption = null,
|
||||
source = aMediaSource(),
|
||||
info = AudioInfo(
|
||||
duration = null,
|
||||
@@ -375,7 +383,9 @@ class InReplyToMetadataKtTest {
|
||||
anInReplyToDetailsReady(
|
||||
eventContent = aMessageContent(
|
||||
messageType = VoiceMessageType(
|
||||
body = "body",
|
||||
filename = "filename",
|
||||
caption = "caption",
|
||||
formattedCaption = null,
|
||||
source = aMediaSource(),
|
||||
info = null,
|
||||
details = null,
|
||||
|
||||
@@ -303,7 +303,7 @@ private fun MediaFileView(
|
||||
if (info != null) {
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
Text(
|
||||
text = info.name,
|
||||
text = info.filename,
|
||||
maxLines = 2,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
||||
@@ -13,43 +13,49 @@ import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class MediaInfo(
|
||||
val name: String,
|
||||
val filename: String,
|
||||
val caption: String?,
|
||||
val mimeType: String,
|
||||
val formattedFileSize: String,
|
||||
val fileExtension: String,
|
||||
) : Parcelable
|
||||
|
||||
fun anImageMediaInfo(): MediaInfo = MediaInfo(
|
||||
"an image file.jpg",
|
||||
MimeTypes.Jpeg,
|
||||
"4MB",
|
||||
"jpg"
|
||||
filename = "an image file.jpg",
|
||||
caption = null,
|
||||
mimeType = MimeTypes.Jpeg,
|
||||
formattedFileSize = "4MB",
|
||||
fileExtension = "jpg",
|
||||
)
|
||||
|
||||
fun aVideoMediaInfo(): MediaInfo = MediaInfo(
|
||||
"a video file.mp4",
|
||||
MimeTypes.Mp4,
|
||||
"14MB",
|
||||
"mp4"
|
||||
filename = "a video file.mp4",
|
||||
caption = null,
|
||||
mimeType = MimeTypes.Mp4,
|
||||
formattedFileSize = "14MB",
|
||||
fileExtension = "mp4",
|
||||
)
|
||||
|
||||
fun aPdfMediaInfo(): MediaInfo = MediaInfo(
|
||||
"a pdf file.pdf",
|
||||
MimeTypes.Pdf,
|
||||
"23MB",
|
||||
"pdf"
|
||||
filename = "a pdf file.pdf",
|
||||
caption = null,
|
||||
mimeType = MimeTypes.Pdf,
|
||||
formattedFileSize = "23MB",
|
||||
fileExtension = "pdf",
|
||||
)
|
||||
|
||||
fun anApkMediaInfo(): MediaInfo = MediaInfo(
|
||||
"an apk file.apk",
|
||||
MimeTypes.Apk,
|
||||
"50MB",
|
||||
"apk"
|
||||
filename = "an apk file.apk",
|
||||
caption = null,
|
||||
mimeType = MimeTypes.Apk,
|
||||
formattedFileSize = "50MB",
|
||||
fileExtension = "apk",
|
||||
)
|
||||
|
||||
fun anAudioMediaInfo(): MediaInfo = MediaInfo(
|
||||
"an audio file.mp3",
|
||||
MimeTypes.Mp3,
|
||||
"7MB",
|
||||
"mp3"
|
||||
filename = "an audio file.mp3",
|
||||
caption = null,
|
||||
mimeType = MimeTypes.Mp3,
|
||||
formattedFileSize = "7MB",
|
||||
fileExtension = "mp3",
|
||||
)
|
||||
|
||||
@@ -92,7 +92,7 @@ class MediaViewerPresenter @AssistedInject constructor(
|
||||
mediaLoader.downloadMediaFile(
|
||||
source = inputs.mediaSource,
|
||||
mimeType = inputs.mediaInfo.mimeType,
|
||||
body = inputs.mediaInfo.name
|
||||
body = inputs.mediaInfo.filename
|
||||
)
|
||||
.onSuccess {
|
||||
mediaFile.value = it
|
||||
|
||||
@@ -322,7 +322,7 @@ private fun ThumbnailView(
|
||||
if (isVisible) {
|
||||
val mediaRequestData = MediaRequestData(
|
||||
source = thumbnailSource,
|
||||
kind = MediaRequestData.Kind.File(mediaInfo.name, mediaInfo.mimeType)
|
||||
kind = MediaRequestData.Kind.File(mediaInfo.filename, mediaInfo.mimeType)
|
||||
)
|
||||
AsyncImage(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
||||
@@ -157,7 +157,7 @@ class AndroidLocalMediaActions @Inject constructor(
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
private fun saveOnDiskUsingMediaStore(localMedia: LocalMedia) {
|
||||
val contentValues = ContentValues().apply {
|
||||
put(MediaStore.MediaColumns.DISPLAY_NAME, localMedia.info.name)
|
||||
put(MediaStore.MediaColumns.DISPLAY_NAME, localMedia.info.filename)
|
||||
put(MediaStore.MediaColumns.MIME_TYPE, localMedia.info.mimeType)
|
||||
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
|
||||
}
|
||||
@@ -175,7 +175,7 @@ class AndroidLocalMediaActions @Inject constructor(
|
||||
private fun saveOnDiskUsingExternalStorageApi(localMedia: LocalMedia) {
|
||||
val target = File(
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||
localMedia.info.name
|
||||
localMedia.info.filename
|
||||
)
|
||||
localMedia.openStream()?.use { input ->
|
||||
FileOutputStream(target).use { output ->
|
||||
|
||||
@@ -32,21 +32,36 @@ class AndroidLocalMediaFactory @Inject constructor(
|
||||
private val fileSizeFormatter: FileSizeFormatter,
|
||||
private val fileExtensionExtractor: FileExtensionExtractor,
|
||||
) : LocalMediaFactory {
|
||||
override fun createFromMediaFile(mediaFile: MediaFile, mediaInfo: MediaInfo): LocalMedia {
|
||||
val uri = mediaFile.toFile().toUri()
|
||||
return createFromUri(
|
||||
uri = uri,
|
||||
mimeType = mediaInfo.mimeType,
|
||||
name = mediaInfo.name,
|
||||
formattedFileSize = mediaInfo.formattedFileSize,
|
||||
)
|
||||
}
|
||||
override fun createFromMediaFile(
|
||||
mediaFile: MediaFile,
|
||||
mediaInfo: MediaInfo,
|
||||
): LocalMedia = createFromUri(
|
||||
uri = mediaFile.toFile().toUri(),
|
||||
mimeType = mediaInfo.mimeType,
|
||||
name = mediaInfo.filename,
|
||||
caption = mediaInfo.caption,
|
||||
formattedFileSize = mediaInfo.formattedFileSize,
|
||||
)
|
||||
|
||||
override fun createFromUri(
|
||||
uri: Uri,
|
||||
mimeType: String?,
|
||||
name: String?,
|
||||
formattedFileSize: String?
|
||||
): LocalMedia = createFromUri(
|
||||
uri = uri,
|
||||
mimeType = mimeType,
|
||||
name = name,
|
||||
caption = null,
|
||||
formattedFileSize = formattedFileSize,
|
||||
)
|
||||
|
||||
private fun createFromUri(
|
||||
uri: Uri,
|
||||
mimeType: String?,
|
||||
name: String?,
|
||||
caption: String?,
|
||||
formattedFileSize: String?
|
||||
): LocalMedia {
|
||||
val resolvedMimeType = mimeType ?: context.getMimeType(uri) ?: MimeTypes.OctetStream
|
||||
val fileName = name ?: context.getFileName(uri) ?: ""
|
||||
@@ -56,7 +71,8 @@ class AndroidLocalMediaFactory @Inject constructor(
|
||||
uri = uri,
|
||||
info = MediaInfo(
|
||||
mimeType = resolvedMimeType,
|
||||
name = fileName,
|
||||
filename = fileName,
|
||||
caption = caption,
|
||||
formattedFileSize = fileSize,
|
||||
fileExtension = fileExtension
|
||||
)
|
||||
|
||||
@@ -29,7 +29,8 @@ class AndroidLocalMediaFactoryTest {
|
||||
assertThat(result.uri.toString()).endsWith("aPath")
|
||||
assertThat(result.info).isEqualTo(
|
||||
MediaInfo(
|
||||
name = "an image file.jpg",
|
||||
filename = "an image file.jpg",
|
||||
caption = null,
|
||||
mimeType = MimeTypes.Jpeg,
|
||||
formattedFileSize = "4MB",
|
||||
fileExtension = "jpg",
|
||||
|
||||
@@ -32,7 +32,8 @@ class FakeLocalMediaFactory(
|
||||
override fun createFromUri(uri: Uri, mimeType: String?, name: String?, formattedFileSize: String?): LocalMedia {
|
||||
val safeName = name ?: fallbackName
|
||||
val mediaInfo = MediaInfo(
|
||||
name = safeName,
|
||||
filename = safeName,
|
||||
caption = null,
|
||||
mimeType = mimeType ?: fallbackMimeType,
|
||||
formattedFileSize = formattedFileSize ?: fallbackFileSize,
|
||||
fileExtension = fileExtensionExtractor.extractFromName(safeName)
|
||||
|
||||
@@ -265,15 +265,15 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
||||
senderDisambiguatedDisplayName: String,
|
||||
): String {
|
||||
return when (val messageType = content.messageType) {
|
||||
is AudioMessageType -> messageType.body
|
||||
is AudioMessageType -> messageType.bestDescription
|
||||
is VoiceMessageType -> stringProvider.getString(CommonStrings.common_voice_message)
|
||||
is EmoteMessageType -> "* $senderDisambiguatedDisplayName ${messageType.body}"
|
||||
is FileMessageType -> messageType.body
|
||||
is ImageMessageType -> messageType.body
|
||||
is StickerMessageType -> messageType.body
|
||||
is FileMessageType -> messageType.bestDescription
|
||||
is ImageMessageType -> messageType.bestDescription
|
||||
is StickerMessageType -> messageType.bestDescription
|
||||
is NoticeMessageType -> messageType.body
|
||||
is TextMessageType -> messageType.toPlainText(permalinkParser = permalinkParser)
|
||||
is VideoMessageType -> messageType.body
|
||||
is VideoMessageType -> messageType.bestDescription
|
||||
is LocationMessageType -> messageType.body
|
||||
is OtherMessageType -> messageType.body
|
||||
}
|
||||
@@ -299,7 +299,7 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
||||
.getMediaFile(
|
||||
mediaSource = messageType.source,
|
||||
mimeType = messageType.info?.mimetype,
|
||||
body = messageType.body,
|
||||
body = messageType.filename,
|
||||
)
|
||||
is VideoMessageType -> null // Use the thumbnail here?
|
||||
else -> null
|
||||
|
||||
@@ -47,7 +47,7 @@ interface NotificationMediaRepo {
|
||||
*
|
||||
* @param mediaSource the media source of the media.
|
||||
* @param mimeType the mime type of the media.
|
||||
* @param body the body of the message.
|
||||
* @param body optional body which will be used to name the file.
|
||||
* @return A [Result] holding either the media [File] from the cache directory or an [Exception].
|
||||
*/
|
||||
suspend fun getMediaFile(
|
||||
|
||||
@@ -187,7 +187,7 @@ class DefaultNotifiableEventResolverTest {
|
||||
aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
senderId = A_USER_ID_2,
|
||||
messageType = AudioMessageType(body = "Audio", MediaSource("url"), null)
|
||||
messageType = AudioMessageType("Audio", null, null, MediaSource("url"), null)
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -206,7 +206,7 @@ class DefaultNotifiableEventResolverTest {
|
||||
aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
senderId = A_USER_ID_2,
|
||||
messageType = VideoMessageType(body = "Video", null, null, MediaSource("url"), null)
|
||||
messageType = VideoMessageType("Video", null, null, MediaSource("url"), null)
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -225,7 +225,7 @@ class DefaultNotifiableEventResolverTest {
|
||||
aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
senderId = A_USER_ID_2,
|
||||
messageType = VoiceMessageType(body = "Voice", MediaSource("url"), null, null)
|
||||
messageType = VoiceMessageType("Voice", null, null, MediaSource("url"), null, null)
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -263,7 +263,7 @@ class DefaultNotifiableEventResolverTest {
|
||||
aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
senderId = A_USER_ID_2,
|
||||
messageType = StickerMessageType("Sticker", MediaSource("url"), null),
|
||||
messageType = StickerMessageType("Sticker", null, null, MediaSource("url"), null),
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -282,7 +282,7 @@ class DefaultNotifiableEventResolverTest {
|
||||
aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
senderId = A_USER_ID_2,
|
||||
messageType = FileMessageType("File", MediaSource("url"), null),
|
||||
messageType = FileMessageType("File", null, null, MediaSource("url"), null),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user