Fixed text message description not being italic for some parts + improved description: added duration to voice message & replaced file name by image emoji for pictures

This commit is contained in:
Sylvain Berfini 2024-08-26 16:41:45 +02:00
parent cbaf7673f5
commit 94f2c1cc98
9 changed files with 99 additions and 44 deletions

View file

@ -778,7 +778,7 @@ class NotificationsManager @MainThread constructor(private val context: Context)
coreContext.contactsManager.findContactByAddress(address) coreContext.contactsManager.findContactByAddress(address)
val displayName = contact?.name ?: LinphoneUtils.getDisplayName(address) val displayName = contact?.name ?: LinphoneUtils.getDisplayName(address)
val originalMessage = LinphoneUtils.getTextDescribingMessage(message) val originalMessage = LinphoneUtils.getPlainTextDescribingMessage(message)
val text = AppUtils.getString(R.string.notification_chat_message_reaction_received).format( val text = AppUtils.getString(R.string.notification_chat_message_reaction_received).format(
reaction, reaction,
originalMessage originalMessage
@ -874,7 +874,7 @@ class NotificationsManager @MainThread constructor(private val context: Context)
coreContext.contactsManager.findContactByAddress(message.fromAddress) coreContext.contactsManager.findContactByAddress(message.fromAddress)
val displayName = contact?.name ?: LinphoneUtils.getDisplayName(message.fromAddress) val displayName = contact?.name ?: LinphoneUtils.getDisplayName(message.fromAddress)
val text = LinphoneUtils.getTextDescribingMessage(message) val text = LinphoneUtils.getPlainTextDescribingMessage(message)
val notifiableMessage = NotifiableMessage( val notifiableMessage = NotifiableMessage(
text, text,
contact, contact,

View file

@ -19,6 +19,7 @@
*/ */
package org.linphone.ui.main.chat.model package org.linphone.ui.main.chat.model
import android.text.Spannable
import androidx.annotation.UiThread import androidx.annotation.UiThread
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
@ -70,7 +71,9 @@ class ConversationModel @WorkerThread constructor(val chatRoom: ChatRoom) {
val isEphemeral = MutableLiveData<Boolean>() val isEphemeral = MutableLiveData<Boolean>()
val lastMessageText = MutableLiveData<String>() val lastMessageTextSender = MutableLiveData<String>()
val lastMessageText = MutableLiveData<Spannable>()
val lastMessageIcon = MutableLiveData<Int>() val lastMessageIcon = MutableLiveData<Int>()
@ -254,7 +257,6 @@ class ConversationModel @WorkerThread constructor(val chatRoom: ChatRoom) {
private fun updateLastMessageStatus(message: ChatMessage) { private fun updateLastMessageStatus(message: ChatMessage) {
val isOutgoing = message.isOutgoing val isOutgoing = message.isOutgoing
val text = LinphoneUtils.getTextDescribingMessage(message)
if (isGroup && !isOutgoing) { if (isGroup && !isOutgoing) {
val fromAddress = message.fromAddress val fromAddress = message.fromAddress
val sender = coreContext.contactsManager.findContactByAddress(fromAddress) val sender = coreContext.contactsManager.findContactByAddress(fromAddress)
@ -263,10 +265,11 @@ class ConversationModel @WorkerThread constructor(val chatRoom: ChatRoom) {
R.string.conversations_last_message_format, R.string.conversations_last_message_format,
name name
) )
lastMessageText.postValue("$senderName $text") lastMessageTextSender.postValue(senderName)
} else { } else {
lastMessageText.postValue(text) lastMessageTextSender.postValue("")
} }
lastMessageText.postValue(LinphoneUtils.getFormattedTextDescribingMessage(message))
isLastMessageOutgoing.postValue(isOutgoing) isLastMessageOutgoing.postValue(isOutgoing)
if (isOutgoing) { if (isOutgoing) {

View file

@ -58,7 +58,7 @@ class EventLogModel @WorkerThread constructor(
val avatarModel = coreContext.contactsManager.getContactAvatarModelForAddress(from) val avatarModel = coreContext.contactsManager.getContactAvatarModelForAddress(from)
replyTo = avatarModel.contactName ?: LinphoneUtils.getDisplayName(from) replyTo = avatarModel.contactName ?: LinphoneUtils.getDisplayName(from)
LinphoneUtils.getTextDescribingMessage(replyMessage) LinphoneUtils.getPlainTextDescribingMessage(replyMessage)
} else { } else {
Log.e( Log.e(
"$TAG Failed to find the reply message from ID [${chatMessage.replyMessageId}]" "$TAG Failed to find the reply message from ID [${chatMessage.replyMessageId}]"

View file

@ -510,12 +510,11 @@ class MessageModel @WorkerThread constructor(
filesList.postValue(filesPath) filesList.postValue(filesPath)
if (!displayableContentFound) { // Temporary workaround to prevent empty bubbles if (!displayableContentFound) { // Temporary workaround to prevent empty bubbles
val describe = LinphoneUtils.getTextDescribingMessage(chatMessage) val describe = LinphoneUtils.getFormattedTextDescribingMessage(chatMessage)
Log.w( Log.w(
"$TAG No displayable content found, generating text based description [$describe]" "$TAG No displayable content found, generating text based description [$describe]"
) )
val spannable = Spannable.Factory.getInstance().newSpannable(describe) text.postValue(describe)
text.postValue(spannable)
} }
} }

View file

@ -21,6 +21,7 @@ package org.linphone.ui.main.chat.viewmodel
import android.Manifest import android.Manifest
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.text.Spannable
import androidx.annotation.UiThread import androidx.annotation.UiThread
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
@ -82,7 +83,7 @@ class SendMessageInConversationViewModel @UiThread constructor() : GenericViewMo
val isReplyingTo = MutableLiveData<String>() val isReplyingTo = MutableLiveData<String>()
val isReplyingToMessage = MutableLiveData<String>() val isReplyingToMessage = MutableLiveData<Spannable>()
val isKeyboardOpen = MutableLiveData<Boolean>() val isKeyboardOpen = MutableLiveData<Boolean>()
@ -211,7 +212,7 @@ class SendMessageInConversationViewModel @UiThread constructor() : GenericViewMo
Log.i("$TAG Pending reply to message [${message.messageId}]") Log.i("$TAG Pending reply to message [${message.messageId}]")
chatMessageToReplyTo = message chatMessageToReplyTo = message
isReplyingTo.postValue(model.avatarModel.value?.friend?.name) isReplyingTo.postValue(model.avatarModel.value?.friend?.name)
isReplyingToMessage.postValue(LinphoneUtils.getTextDescribingMessage(message)) isReplyingToMessage.postValue(LinphoneUtils.getFormattedTextDescribingMessage(message))
isReplying.postValue(true) isReplying.postValue(true)
} }
} }

View file

@ -19,10 +19,17 @@
*/ */
package org.linphone.utils package org.linphone.utils
import android.graphics.Typeface
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.StyleSpan
import androidx.annotation.AnyThread import androidx.annotation.AnyThread
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.IntegerRes import androidx.annotation.IntegerRes
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.core.text.toSpannable
import java.text.SimpleDateFormat
import java.util.Locale
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R import org.linphone.R
import org.linphone.contacts.getListOfSipAddresses import org.linphone.contacts.getListOfSipAddresses
@ -391,9 +398,31 @@ class LinphoneUtils {
} }
@WorkerThread @WorkerThread
fun getTextDescribingMessage(message: ChatMessage): String { fun getFormattedTextDescribingMessage(message: ChatMessage): Spannable {
val pair = getTextDescribingMessage(message)
val builder = SpannableStringBuilder(
"${pair.first} ${pair.second}".trim()
)
builder.setSpan(
StyleSpan(Typeface.ITALIC),
0,
pair.first.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
return builder.toSpannable()
}
@WorkerThread
fun getPlainTextDescribingMessage(message: ChatMessage): String {
val pair = getTextDescribingMessage(message)
return "${pair.first} ${pair.second}".trim()
}
@WorkerThread
private fun getTextDescribingMessage(message: ChatMessage): Pair<String, String> {
// If message contains text, then use that // If message contains text, then use that
var text = message.contents.find { content -> content.isText }?.utf8Text ?: "" var text = message.contents.find { content -> content.isText }?.utf8Text ?: ""
var contentDescription = ""
if (text.isEmpty()) { if (text.isEmpty()) {
val firstContent = message.contents.firstOrNull() val firstContent = message.contents.firstOrNull()
@ -402,26 +431,21 @@ class LinphoneUtils {
firstContent firstContent
) )
if (conferenceInfo != null) { if (conferenceInfo != null) {
val subject = conferenceInfo.subject.orEmpty() text = conferenceInfo.subject.orEmpty()
text = when (conferenceInfo.state) { contentDescription = when (conferenceInfo.state) {
ConferenceInfo.State.Cancelled -> { ConferenceInfo.State.Cancelled -> {
AppUtils.getFormattedString( AppUtils.getString(
R.string.message_meeting_invitation_cancelled_content_description, R.string.message_meeting_invitation_cancelled_content_description
subject
) )
} }
ConferenceInfo.State.Updated -> { ConferenceInfo.State.Updated -> {
AppUtils.getFormattedString( AppUtils.getString(
R.string.message_meeting_invitation_updated_content_description, R.string.message_meeting_invitation_updated_content_description
subject
) )
} }
else -> { else -> {
AppUtils.getFormattedString( AppUtils.getString(
R.string.message_meeting_invitation_content_description, R.string.message_meeting_invitation_content_description
subject
) )
} }
} }
@ -432,18 +456,31 @@ class LinphoneUtils {
text = firstContent.name.orEmpty() text = firstContent.name.orEmpty()
} }
} else if (firstContent?.isVoiceRecording == true) { } else if (firstContent?.isVoiceRecording == true) {
text = AppUtils.getString(R.string.message_voice_message_content_description) val label = AppUtils.getString(
R.string.message_voice_message_content_description
)
val formattedDuration = SimpleDateFormat(
"mm:ss",
Locale.getDefault()
).format(firstContent.fileDuration) // duration is in ms
contentDescription = "$label ($formattedDuration)"
} else { } else {
for (content in message.contents) { for (content in message.contents) {
if (text.isNotEmpty()) { if (text.isNotEmpty()) {
text += ", " text += ", "
} }
text += content.name val contentType = "${content.type}/${content.subtype}"
text += when (FileUtils.getMimeType(contentType)) {
FileUtils.MimeType.Image -> "\uD83D\uDDBC\uFE0F"
// FileUtils.MimeType.Video -> "\uD83C\uDF9E\uFE0F"
// FileUtils.MimeType.Audio -> "\uD83C\uDFB5"
else -> content.name
}
} }
} }
} }
return text return Pair(contentDescription, text)
} }
@WorkerThread @WorkerThread

View file

@ -80,6 +80,23 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/last_message_or_composing"/> app:layout_constraintBottom_toTopOf="@id/last_message_or_composing"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/last_message_sender"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="3sp"
android:gravity="center_vertical|start"
android:text="@{model.lastMessageTextSender, default=`John Doe:`}"
android:textSize="14sp"
android:textColor="?attr/color_main2_400"
android:visibility="@{model.lastMessageTextSender.length() > 0 ? View.VISIBLE : View.GONE}"
textFont="@{model.isBeingDeleted || model.unreadMessageCount > 0 || model.isComposing ? NotoSansFont.NotoSansBold : NotoSansFont.NotoSansRegular}"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toStartOf="@id/last_message_or_composing"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintBottom_toTopOf="@id/separator"/>
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style" style="@style/default_text_style"
android:id="@+id/last_message_or_composing" android:id="@+id/last_message_or_composing"
@ -92,10 +109,10 @@
android:textSize="14sp" android:textSize="14sp"
android:textColor="?attr/color_main2_400" android:textColor="?attr/color_main2_400"
textFont="@{model.isBeingDeleted || model.unreadMessageCount > 0 || model.isComposing ? NotoSansFont.NotoSansBold : NotoSansFont.NotoSansRegular}" textFont="@{model.isBeingDeleted || model.unreadMessageCount > 0 || model.isComposing ? NotoSansFont.NotoSansBold : NotoSansFont.NotoSansRegular}"
app:layout_constraintStart_toStartOf="@id/title" app:layout_constraintStart_toEndOf="@id/last_message_sender"
app:layout_constraintEnd_toStartOf="@id/right_border" app:layout_constraintEnd_toStartOf="@id/right_border"
app:layout_constraintTop_toBottomOf="@id/title" app:layout_constraintTop_toTopOf="@id/last_message_sender"
app:layout_constraintBottom_toTopOf="@id/separator" /> app:layout_constraintBottom_toBottomOf="@id/last_message_sender" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_300" style="@style/default_text_style_300"

View file

@ -502,6 +502,11 @@
<string name="message_reaction_click_to_remove_label">Cliquez pour supprimer</string> <string name="message_reaction_click_to_remove_label">Cliquez pour supprimer</string>
<string name="message_forwarded_label">Transféré</string> <string name="message_forwarded_label">Transféré</string>
<string name="message_meeting_invitation_content_description">invitation à une réunion :</string>
<string name="message_meeting_invitation_updated_content_description">réunion mise à jour :</string>
<string name="message_meeting_invitation_cancelled_content_description">réunion annulée :</string>
<string name="message_voice_message_content_description">message vocal</string>
<!-- Scheduled conferences --> <!-- Scheduled conferences -->
<string name="meetings_list_empty">Aucune réunion pour le moment…</string> <string name="meetings_list_empty">Aucune réunion pour le moment…</string>
<string name="meetings_list_no_meeting_for_today">Aucune réunion aujourd\'hui</string> <string name="meetings_list_no_meeting_for_today">Aucune réunion aujourd\'hui</string>
@ -714,12 +719,6 @@
<string name="assistant_forgotten_password"><u>Mot de passe oublié ?</u></string> <string name="assistant_forgotten_password"><u>Mot de passe oublié ?</u></string>
<string name="call_zrtp_sas_validation_skip"><u>Passer</u></string> <string name="call_zrtp_sas_validation_skip"><u>Passer</u></string>
<!-- Keep <i></i> in the following strings translations! -->
<string name="message_meeting_invitation_content_description"><i>invitation à une réunion : </i>%s</string>
<string name="message_meeting_invitation_updated_content_description"><i>réunion mise à jour : </i>%s</string>
<string name="message_meeting_invitation_cancelled_content_description"><i>réunion annulée : </i>%s</string>
<string name="message_voice_message_content_description"><i>message vocal</i></string>
<!-- Keep <b></b> in the following strings translations! --> <!-- Keep <b></b> in the following strings translations! -->
<string name="conversation_message_meeting_updated_label"><b>La réunion a été mise à jour</b></string> <string name="conversation_message_meeting_updated_label"><b>La réunion a été mise à jour</b></string>
<string name="conversation_message_meeting_cancelled_label"><b>La réunion a été annulée</b></string> <string name="conversation_message_meeting_cancelled_label"><b>La réunion a été annulée</b></string>

View file

@ -540,6 +540,11 @@
<string name="message_reaction_click_to_remove_label">Click to remove</string> <string name="message_reaction_click_to_remove_label">Click to remove</string>
<string name="message_forwarded_label">Forwarded</string> <string name="message_forwarded_label">Forwarded</string>
<string name="message_meeting_invitation_content_description">meeting invite:</string>
<string name="message_meeting_invitation_updated_content_description">meeting updated:</string>
<string name="message_meeting_invitation_cancelled_content_description">meeting cancelled:</string>
<string name="message_voice_message_content_description">voice message</string>
<!-- Scheduled conferences --> <!-- Scheduled conferences -->
<string name="meetings_list_empty">No meeting for the moment…</string> <string name="meetings_list_empty">No meeting for the moment…</string>
<string name="meetings_list_no_meeting_for_today">No meeting scheduled for today</string> <string name="meetings_list_no_meeting_for_today">No meeting scheduled for today</string>
@ -752,12 +757,6 @@
<string name="assistant_forgotten_password"><u>Forgotten password?</u></string> <string name="assistant_forgotten_password"><u>Forgotten password?</u></string>
<string name="call_zrtp_sas_validation_skip"><u>Skip</u></string> <string name="call_zrtp_sas_validation_skip"><u>Skip</u></string>
<!-- Keep <i></i> in the following strings translations! -->
<string name="message_meeting_invitation_content_description"><i>meeting invite: </i>%s</string>
<string name="message_meeting_invitation_updated_content_description"><i>meeting updated: </i>%s</string>
<string name="message_meeting_invitation_cancelled_content_description"><i>meeting cancelled: </i>%s</string>
<string name="message_voice_message_content_description"><i>voice message</i></string>
<!-- Keep <b></b> in the following strings translations! --> <!-- Keep <b></b> in the following strings translations! -->
<string name="conversation_message_meeting_updated_label"><b>Meeting has been updated</b></string> <string name="conversation_message_meeting_updated_label"><b>Meeting has been updated</b></string>
<string name="conversation_message_meeting_cancelled_label"><b>Meeting has been cancelled!</b></string> <string name="conversation_message_meeting_cancelled_label"><b>Meeting has been cancelled!</b></string>