Merge pull request #3816 from element-hq/feature/bma/moreCaptionWork

Iteration on caption
This commit is contained in:
Benoit Marty 2024-11-06 16:01:14 +01:00 committed by GitHub
commit b23ec7e0bd
11 changed files with 84 additions and 29 deletions

View file

@ -70,14 +70,14 @@ fun TimelineItemVideoView(
onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit, onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val description = stringResource(CommonStrings.common_image) val description = stringResource(CommonStrings.common_video)
Column( Column(
modifier = modifier.semantics { contentDescription = description } modifier = modifier.semantics { contentDescription = description }
) { ) {
val containerModifier = if (content.showCaption) { val containerModifier = if (content.showCaption) {
Modifier Modifier
.padding(top = 6.dp) .padding(top = 6.dp)
.clip(RoundedCornerShape(6.dp)) .clip(RoundedCornerShape(6.dp))
} else { } else {
Modifier Modifier
} }
@ -93,8 +93,8 @@ fun TimelineItemVideoView(
var isLoaded by remember { mutableStateOf(false) } var isLoaded by remember { mutableStateOf(false) }
AsyncImage( AsyncImage(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.then(if (isLoaded) Modifier.background(Color.White) else Modifier), .then(if (isLoaded) Modifier.background(Color.White) else Modifier),
model = MediaRequestData( model = MediaRequestData(
source = content.thumbnailSource, source = content.thumbnailSource,
kind = MediaRequestData.Kind.File( kind = MediaRequestData.Kind.File(

View file

@ -95,7 +95,9 @@ class DefaultPinnedMessagesBannerFormatter @Inject constructor(
messageType.bestDescription.prefixWith(CommonStrings.common_audio) messageType.bestDescription.prefixWith(CommonStrings.common_audio)
} }
is VoiceMessageType -> { is VoiceMessageType -> {
messageType.bestDescription.prefixWith(CommonStrings.common_voice_message) // In this case, do not use bestDescription, because the filename is useless, only use the caption if available.
messageType.caption?.prefixWith(sp.getString(CommonStrings.common_voice_message))
?: sp.getString(CommonStrings.common_voice_message)
} }
is OtherMessageType -> { is OtherMessageType -> {
messageType.body messageType.body

View file

@ -110,25 +110,27 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
messageType.toPlainText(permalinkParser) messageType.toPlainText(permalinkParser)
} }
is VideoMessageType -> { is VideoMessageType -> {
sp.getString(CommonStrings.common_video) messageType.bestDescription.prefixWith(sp.getString(CommonStrings.common_video))
} }
is ImageMessageType -> { is ImageMessageType -> {
sp.getString(CommonStrings.common_image) messageType.bestDescription.prefixWith(sp.getString(CommonStrings.common_image))
} }
is StickerMessageType -> { is StickerMessageType -> {
sp.getString(CommonStrings.common_sticker) messageType.bestDescription.prefixWith(sp.getString(CommonStrings.common_sticker))
} }
is LocationMessageType -> { is LocationMessageType -> {
sp.getString(CommonStrings.common_shared_location) sp.getString(CommonStrings.common_shared_location)
} }
is FileMessageType -> { is FileMessageType -> {
sp.getString(CommonStrings.common_file) messageType.bestDescription.prefixWith(sp.getString(CommonStrings.common_file))
} }
is AudioMessageType -> { is AudioMessageType -> {
sp.getString(CommonStrings.common_audio) messageType.bestDescription.prefixWith(sp.getString(CommonStrings.common_audio))
} }
is VoiceMessageType -> { is VoiceMessageType -> {
sp.getString(CommonStrings.common_voice_message) // In this case, do not use bestDescription, because the filename is useless, only use the caption if available.
messageType.caption?.prefixWith(sp.getString(CommonStrings.common_voice_message))
?: sp.getString(CommonStrings.common_voice_message)
} }
is OtherMessageType -> { is OtherMessageType -> {
messageType.body messageType.body
@ -140,7 +142,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
return message.prefixIfNeeded(senderDisambiguatedDisplayName, isDmRoom, isOutgoing) return message.prefixIfNeeded(senderDisambiguatedDisplayName, isDmRoom, isOutgoing)
} }
private fun String.prefixIfNeeded( private fun CharSequence.prefixIfNeeded(
senderDisambiguatedDisplayName: String, senderDisambiguatedDisplayName: String,
isDmRoom: Boolean, isDmRoom: Boolean,
isOutgoing: Boolean, isOutgoing: Boolean,

View file

@ -159,11 +159,11 @@ class DefaultPinnedMessagesBannerFormatterTest {
val expectedResult = when (type) { val expectedResult = when (type) {
is VideoMessageType, is VideoMessageType,
is AudioMessageType, is AudioMessageType,
is VoiceMessageType,
is ImageMessageType, is ImageMessageType,
is StickerMessageType, is StickerMessageType,
is FileMessageType, is FileMessageType,
is LocationMessageType -> AnnotatedString::class.java is LocationMessageType -> AnnotatedString::class.java
is VoiceMessageType,
is EmoteMessageType, is EmoteMessageType,
is TextMessageType, is TextMessageType,
is NoticeMessageType, is NoticeMessageType,
@ -176,7 +176,7 @@ class DefaultPinnedMessagesBannerFormatterTest {
val expectedResult = when (type) { val expectedResult = when (type) {
is VideoMessageType -> "Video: Shared body" is VideoMessageType -> "Video: Shared body"
is AudioMessageType -> "Audio: Shared body" is AudioMessageType -> "Audio: Shared body"
is VoiceMessageType -> "Voice message: Shared body" is VoiceMessageType -> "Voice message"
is ImageMessageType -> "Image: Shared body" is ImageMessageType -> "Image: Shared body"
is StickerMessageType -> "Sticker: Shared body" is StickerMessageType -> "Sticker: Shared body"
is FileMessageType -> "File: Shared body" is FileMessageType -> "File: Shared body"

View file

@ -208,32 +208,51 @@ class DefaultRoomLastMessageFormatterTest {
// Verify results of DM mode // Verify results of DM mode
for ((type, result) in resultsInDm) { for ((type, result) in resultsInDm) {
val string = result.toString()
val expectedResult = when (type) { val expectedResult = when (type) {
is VideoMessageType -> "Video" is VideoMessageType -> "Video: Shared body"
is AudioMessageType -> "Audio" is AudioMessageType -> "Audio: Shared body"
is VoiceMessageType -> "Voice message" is VoiceMessageType -> "Voice message"
is ImageMessageType -> "Image" is ImageMessageType -> "Image: Shared body"
is StickerMessageType -> "Sticker" is StickerMessageType -> "Sticker: Shared body"
is FileMessageType -> "File" is FileMessageType -> "File: Shared body"
is LocationMessageType -> "Shared location" is LocationMessageType -> "Shared location"
is EmoteMessageType -> "* $senderName ${type.body}" is EmoteMessageType -> "* $senderName ${type.body}"
is TextMessageType, is TextMessageType,
is NoticeMessageType, is NoticeMessageType,
is OtherMessageType -> body is OtherMessageType -> body
} }
assertWithMessage("$type was not properly handled for DM").that(result).isEqualTo(expectedResult) val shouldCreateAnnotatedString = when (type) {
is VideoMessageType -> true
is AudioMessageType -> true
is VoiceMessageType -> false
is ImageMessageType -> true
is StickerMessageType -> true
is FileMessageType -> true
is LocationMessageType -> false
is EmoteMessageType -> false
is TextMessageType -> false
is NoticeMessageType -> false
is OtherMessageType -> false
}
if (shouldCreateAnnotatedString) {
assertWithMessage("$type doesn't produce an AnnotatedString")
.that(result)
.isInstanceOf(AnnotatedString::class.java)
}
assertWithMessage("$type was not properly handled for DM").that(string).isEqualTo(expectedResult)
} }
// Verify results of Room mode // Verify results of Room mode
for ((type, result) in resultsInRoom) { for ((type, result) in resultsInRoom) {
val string = result.toString() val string = result.toString()
val expectedResult = when (type) { val expectedResult = when (type) {
is VideoMessageType -> "$expectedPrefix: Video" is VideoMessageType -> "$expectedPrefix: Video: Shared body"
is AudioMessageType -> "$expectedPrefix: Audio" is AudioMessageType -> "$expectedPrefix: Audio: Shared body"
is VoiceMessageType -> "$expectedPrefix: Voice message" is VoiceMessageType -> "$expectedPrefix: Voice message"
is ImageMessageType -> "$expectedPrefix: Image" is ImageMessageType -> "$expectedPrefix: Image: Shared body"
is StickerMessageType -> "$expectedPrefix: Sticker" is StickerMessageType -> "$expectedPrefix: Sticker: Shared body"
is FileMessageType -> "$expectedPrefix: File" is FileMessageType -> "$expectedPrefix: File: Shared body"
is LocationMessageType -> "$expectedPrefix: Shared location" is LocationMessageType -> "$expectedPrefix: Shared location"
is TextMessageType, is TextMessageType,
is NoticeMessageType, is NoticeMessageType,
@ -249,7 +268,8 @@ class DefaultRoomLastMessageFormatterTest {
is FileMessageType -> true is FileMessageType -> true
is LocationMessageType -> false is LocationMessageType -> false
is EmoteMessageType -> false is EmoteMessageType -> false
is TextMessageType, is NoticeMessageType -> true is TextMessageType -> true
is NoticeMessageType -> true
is OtherMessageType -> true is OtherMessageType -> true
} }
if (shouldCreateAnnotatedString) { if (shouldCreateAnnotatedString) {

View file

@ -107,6 +107,7 @@ class DefaultNotifiableEventResolver @Inject constructor(
senderDisambiguatedDisplayName = senderDisambiguatedDisplayName, senderDisambiguatedDisplayName = senderDisambiguatedDisplayName,
body = messageBody, body = messageBody,
imageUriString = content.fetchImageIfPresent(client)?.toString(), imageUriString = content.fetchImageIfPresent(client)?.toString(),
imageMimeType = content.getImageMimetype(),
roomName = roomDisplayName, roomName = roomDisplayName,
roomIsDm = isDm, roomIsDm = isDm,
roomAvatarPath = roomAvatarUrl, roomAvatarPath = roomAvatarUrl,
@ -316,6 +317,17 @@ class DefaultNotifiableEventResolver @Inject constructor(
} }
.getOrNull() .getOrNull()
} }
private suspend fun NotificationContent.MessageLike.RoomMessage.getImageMimetype(): String? {
if (appPreferencesStore.doesHideImagesAndVideosFlow().first()) {
return null
}
return when (val messageType = messageType) {
is ImageMessageType -> messageType.info?.mimetype
is VideoMessageType -> null // Use the thumbnail here?
else -> null
}
}
} }
@Suppress("LongParameterList") @Suppress("LongParameterList")
@ -333,6 +345,7 @@ internal fun buildNotifiableMessageEvent(
// We cannot use Uri? type here, as that could trigger a // We cannot use Uri? type here, as that could trigger a
// NotSerializableException when persisting this to storage // NotSerializableException when persisting this to storage
imageUriString: String? = null, imageUriString: String? = null,
imageMimeType: String? = null,
threadId: ThreadId? = null, threadId: ThreadId? = null,
roomName: String? = null, roomName: String? = null,
roomIsDm: Boolean = false, roomIsDm: Boolean = false,
@ -358,6 +371,7 @@ internal fun buildNotifiableMessageEvent(
senderDisambiguatedDisplayName = senderDisambiguatedDisplayName, senderDisambiguatedDisplayName = senderDisambiguatedDisplayName,
body = body, body = body,
imageUriString = imageUriString, imageUriString = imageUriString,
imageMimeType = imageMimeType,
threadId = threadId, threadId = threadId,
roomName = roomName, roomName = roomName,
roomIsDm = roomIsDm, roomIsDm = roomIsDm,

View file

@ -150,6 +150,7 @@ class NotificationBroadcastReceiverHandler @Inject constructor(
?: stringProvider.getString(R.string.notification_sender_me), ?: stringProvider.getString(R.string.notification_sender_me),
body = message, body = message,
imageUriString = null, imageUriString = null,
imageMimeType = null,
threadId = threadId, threadId = threadId,
roomName = room.displayName, roomName = room.displayName,
roomIsDm = room.isDm, roomIsDm = room.isDm,

View file

@ -419,11 +419,22 @@ class DefaultNotificationCreator @Inject constructor(
senderPerson senderPerson
).also { message -> ).also { message ->
event.imageUri?.let { event.imageUri?.let {
message.setData("image/", it) message.setData(event.imageMimeType ?: "image/", it)
} }
message.extras.putString(MESSAGE_EVENT_ID, event.eventId.value) message.extras.putString(MESSAGE_EVENT_ID, event.eventId.value)
} }
addMessage(message) addMessage(message)
// Add additional message for captions
if (event.imageUri != null && event.body != null) {
addMessage(
MessagingStyle.Message(
event.body,
event.timestamp,
senderPerson,
)
)
}
} }
} }
} }

View file

@ -31,7 +31,8 @@ data class NotifiableMessageEvent(
val body: String?, val body: String?,
// We cannot use Uri? type here, as that could trigger a // We cannot use Uri? type here, as that could trigger a
// NotSerializableException when persisting this to storage // NotSerializableException when persisting this to storage
val imageUriString: String?, private val imageUriString: String?,
val imageMimeType: String?,
val threadId: ThreadId?, val threadId: ThreadId?,
val roomName: String?, val roomName: String?,
val roomIsDm: Boolean = false, val roomIsDm: Boolean = false,

View file

@ -590,6 +590,7 @@ class DefaultNotifiableEventResolverTest {
senderDisambiguatedDisplayName = A_USER_NAME_2, senderDisambiguatedDisplayName = A_USER_NAME_2,
body = "Call in progress (unsupported)", body = "Call in progress (unsupported)",
imageUriString = null, imageUriString = null,
imageMimeType = null,
threadId = null, threadId = null,
roomName = A_ROOM_NAME, roomName = A_ROOM_NAME,
roomAvatarPath = null, roomAvatarPath = null,
@ -669,6 +670,7 @@ class DefaultNotifiableEventResolverTest {
canBeReplaced = false, canBeReplaced = false,
isRedacted = false, isRedacted = false,
imageUriString = null, imageUriString = null,
imageMimeType = null,
type = EventType.CALL_NOTIFY, type = EventType.CALL_NOTIFY,
) )
) )
@ -704,6 +706,7 @@ class DefaultNotifiableEventResolverTest {
canBeReplaced = false, canBeReplaced = false,
isRedacted = false, isRedacted = false,
imageUriString = null, imageUriString = null,
imageMimeType = null,
type = EventType.CALL_NOTIFY, type = EventType.CALL_NOTIFY,
) )
) )

View file

@ -100,6 +100,7 @@ fun aNotifiableMessageEvent(
canBeReplaced = false, canBeReplaced = false,
isRedacted = isRedacted, isRedacted = isRedacted,
imageUriString = null, imageUriString = null,
imageMimeType = null,
roomAvatarPath = null, roomAvatarPath = null,
senderAvatarPath = null, senderAvatarPath = null,
soundName = null, soundName = null,