diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt index 7a59cafd7..b9716129b 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt @@ -1,10 +1,15 @@ package org.linphone.ui.main.chat.model +import android.media.MediaMetadataRetriever +import android.media.MediaMetadataRetriever.METADATA_KEY_DURATION +import android.net.Uri import androidx.annotation.AnyThread import androidx.annotation.UiThread import androidx.lifecycle.MutableLiveData +import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.core.tools.Log import org.linphone.utils.FileUtils +import org.linphone.utils.TimestampUtils class FileModel @AnyThread constructor( val file: String, @@ -19,22 +24,23 @@ class FileModel @AnyThread constructor( val formattedFileSize = MutableLiveData() - val path = MutableLiveData() - val downloadProgress = MutableLiveData() val mimeType: FileUtils.MimeType + val isMedia: Boolean + val isImage: Boolean val isVideoPreview: Boolean + val videoDuration = MutableLiveData() + val isPdf: Boolean val isAudio: Boolean init { - path.postValue(file) downloadProgress.postValue(-1) formattedFileSize.postValue(FileUtils.bytesToDisplayableSize(fileSize)) @@ -46,6 +52,9 @@ class FileModel @AnyThread constructor( mimeType = FileUtils.getMimeType(mime) isImage = mimeType == FileUtils.MimeType.Image isVideoPreview = mimeType == FileUtils.MimeType.Video + if (isVideoPreview) { + getDuration() + } isAudio = mimeType == FileUtils.MimeType.Audio Log.d( "$TAG File has already been downloaded, extension is [$extension], MIME is [$mime]" @@ -57,6 +66,8 @@ class FileModel @AnyThread constructor( isVideoPreview = false isAudio = false } + + isMedia = isVideoPreview || isImage } @UiThread @@ -69,4 +80,19 @@ class FileModel @AnyThread constructor( Log.i("$TAG Deleting file [$file]") FileUtils.deleteFile(file) } + + private fun getDuration() { + try { + val retriever = MediaMetadataRetriever() + retriever.setDataSource(coreContext.context, Uri.parse(file)) + val durationInMs = retriever.extractMetadata(METADATA_KEY_DURATION)?.toInt() ?: 0 + val seconds = durationInMs / 1000 + val duration = TimestampUtils.durationToString(seconds) + Log.d("$TAG Duration for file [$file] is $duration") + videoDuration.postValue(duration) + retriever.release() + } catch (e: Exception) { + Log.e("$TAG Failed to get duration for file [$file]: $e") + } + } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt index f3eb0534d..6ad3a41f7 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt @@ -24,6 +24,7 @@ import android.text.SpannableStringBuilder import android.text.Spanned import androidx.annotation.UiThread import androidx.annotation.WorkerThread +import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData import androidx.media.AudioFocusRequestCompat import java.text.SimpleDateFormat @@ -109,7 +110,7 @@ class MessageModel @WorkerThread constructor( val filesList = MutableLiveData>() - val firstImagePath = MutableLiveData() + val firstFileModel = MediatorLiveData() val isSelected = MutableLiveData() @@ -231,6 +232,12 @@ class MessageModel @WorkerThread constructor( updateReactionsList() computeContentsList() + + coreContext.postOnMainThread { + firstFileModel.addSource(filesList) { + firstFileModel.value = it.firstOrNull() + } + } } @WorkerThread @@ -296,7 +303,6 @@ class MessageModel @WorkerThread constructor( Log.d("$TAG Computing message contents list") text.postValue(Spannable.Factory.getInstance().newSpannable("")) filesList.postValue(arrayListOf()) - firstImagePath.postValue("") var displayableContentFound = false var filesContentCount = 0 @@ -349,10 +355,6 @@ class MessageModel @WorkerThread constructor( } filesPath.add(fileModel) - if (filesContentCount == 1) { - firstImagePath.postValue(path) - } - displayableContentFound = true } else -> { @@ -376,9 +378,6 @@ class MessageModel @WorkerThread constructor( if (name.isNotEmpty()) { val fileModel = if (isOutgoing && chatMessage.isFileTransferInProgress) { val path = content.filePath ?: "" - if (filesContentCount == 1) { - firstImagePath.postValue(path) - } FileModel(path, name, content.fileSize.toLong(), false) { model -> onContentClicked?.invoke(model.file) } diff --git a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt index 70ce3e396..7e9af23b4 100644 --- a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt +++ b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt @@ -83,6 +83,17 @@ fun setEntries( viewGroup: ViewGroup, entries: List?, layoutId: Int +) { + setEntries(viewGroup, entries, layoutId, null) +} + +@UiThread +@BindingAdapter("entries", "layout", "onLongClick") +fun setEntries( + viewGroup: ViewGroup, + entries: List?, + layoutId: Int, + onLongClick: View.OnLongClickListener? ) { viewGroup.removeAllViews() if (!entries.isNullOrEmpty()) { @@ -96,6 +107,7 @@ fun setEntries( ) binding.setVariable(BR.model, entry) + binding.setVariable(BR.onLongClickListener, onLongClick) // This is a bit hacky... if (viewGroup.context as? LifecycleOwner != null) { diff --git a/app/src/main/res/layout/chat_bubble_content_grid_cell.xml b/app/src/main/res/layout/chat_bubble_content_grid_cell.xml index b072775a4..a47d0a783 100644 --- a/app/src/main/res/layout/chat_bubble_content_grid_cell.xml +++ b/app/src/main/res/layout/chat_bubble_content_grid_cell.xml @@ -5,6 +5,9 @@ + @@ -12,7 +15,6 @@ @@ -25,16 +27,31 @@ + + + app:tint="@color/white" /> + layout="@{@layout/chat_bubble_content_grid_cell}" + onLongClick="@{onLongClickListener}"/> - + android:layout_height="wrap_content" + android:layout="@layout/chat_bubble_single_media_content" + android:visibility="@{model.filesList.size() == 1 && model.firstFileModel.isMedia ? View.VISIBLE : View.GONE, default=gone}" + bind:inflatedVisibility="@{model.filesList.size() == 1 && model.firstFileModel.isMedia? View.VISIBLE : View.GONE}" + bind:model="@{model.firstFileModel}" + bind:onLongClickListener="@{onLongClickListener}"/> + bind:model="@{model}" + bind:onLongClickListener="@{onLongClickListener}"/> + bind:model="@{model}" + bind:onLongClickListener="@{onLongClickListener}" /> + @@ -15,6 +18,7 @@ diff --git a/app/src/main/res/layout/chat_bubble_outgoing.xml b/app/src/main/res/layout/chat_bubble_outgoing.xml index c733b3057..2904808b2 100644 --- a/app/src/main/res/layout/chat_bubble_outgoing.xml +++ b/app/src/main/res/layout/chat_bubble_outgoing.xml @@ -139,23 +139,23 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:onLongClick="@{onLongClickListener}" - android:visibility="@{model.filesList.size() >= 2 || (model.filesList.size() >= 1 && model.firstImagePath.length() == 0) ? View.VISIBLE : View.GONE, default=gone}" + android:visibility="@{model.filesList.size() >= 2 || (model.filesList.size() == 1 && !model.firstFileModel.isMedia) ? View.VISIBLE : View.GONE, default=gone}" app:alignItems="center" app:flexWrap="wrap" app:justifyContent="@{model.outgoing ? JustifyContent.FLEX_END : JustifyContent.FLEX_START}" entries="@{model.filesList}" - layout="@{@layout/chat_bubble_content_grid_cell}"/> + layout="@{@layout/chat_bubble_content_grid_cell}" + onLongClick="@{onLongClickListener}"/> - + android:layout_height="wrap_content" + android:layout="@layout/chat_bubble_single_media_content" + android:visibility="@{model.filesList.size() == 1 && model.firstFileModel.isMedia ? View.VISIBLE : View.GONE, default=gone}" + bind:inflatedVisibility="@{model.filesList.size() == 1 && model.firstFileModel.isMedia? View.VISIBLE : View.GONE}" + bind:model="@{model.firstFileModel}" + bind:onLongClickListener="@{onLongClickListener}"/> + bind:model="@{model}" + bind:onLongClickListener="@{onLongClickListener}"/> + bind:model="@{model}" + bind:onLongClickListener="@{onLongClickListener}" /> + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_bubble_voice_record_content.xml b/app/src/main/res/layout/chat_bubble_voice_record_content.xml index cb38f018a..a808a5e41 100644 --- a/app/src/main/res/layout/chat_bubble_voice_record_content.xml +++ b/app/src/main/res/layout/chat_bubble_voice_record_content.xml @@ -5,6 +5,9 @@ + @@ -16,6 +19,7 @@