From 91fdef4428b198da92dba545c67a27e8dada54e1 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 19 May 2025 13:09:10 +0200 Subject: [PATCH] Showing files in square area like media when more than one in a single chat message --- .../chat/adapter/ConversationsFilesAdapter.kt | 13 +- .../chat/fragment/ConversationFragment.kt | 38 ++-- .../linphone/ui/main/chat/model/FileModel.kt | 1 - .../ui/main/chat/model/MessageModel.kt | 13 +- .../SendMessageInConversationViewModel.kt | 45 ++-- .../org/linphone/utils/DataBindingUtils.kt | 2 +- .../shape_squircle_file_background_top.xml | 5 + .../shape_squircle_file_bubble_background.xml | 5 + ...squircle_file_bubble_right_background.xml} | 2 +- ...shape_squircle_gray_200_r15_background.xml | 5 + .../layout/chat_bubble_content_grid_cell.xml | 203 ++---------------- .../res/layout/chat_bubble_file_grid_cell.xml | 155 +++++++++++++ .../main/res/layout/chat_bubble_incoming.xml | 12 +- .../layout/chat_bubble_media_grid_cell.xml | 79 +++++++ .../main/res/layout/chat_bubble_outgoing.xml | 12 +- .../chat_bubble_single_file_content.xml | 165 ++++++++++++++ .../chat_bubble_single_media_content.xml | 8 +- ...hat_conversation_attachments_area_cell.xml | 102 ++------- .../chat_document_content_list_cell.xml | 67 ------ .../layout/chat_media_content_grid_cell.xml | 114 +++++++--- app/src/main/res/values/dimen.xml | 6 +- 21 files changed, 616 insertions(+), 436 deletions(-) create mode 100644 app/src/main/res/drawable/shape_squircle_file_background_top.xml create mode 100644 app/src/main/res/drawable/shape_squircle_file_bubble_background.xml rename app/src/main/res/drawable/{shape_squircle_white_right.xml => shape_squircle_file_bubble_right_background.xml} (77%) create mode 100644 app/src/main/res/drawable/shape_squircle_gray_200_r15_background.xml create mode 100644 app/src/main/res/layout/chat_bubble_file_grid_cell.xml create mode 100644 app/src/main/res/layout/chat_bubble_media_grid_cell.xml create mode 100644 app/src/main/res/layout/chat_bubble_single_file_content.xml delete mode 100644 app/src/main/res/layout/chat_document_content_list_cell.xml diff --git a/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationsFilesAdapter.kt b/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationsFilesAdapter.kt index 298d7af90..de508a05c 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationsFilesAdapter.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationsFilesAdapter.kt @@ -30,10 +30,11 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import org.linphone.R -import org.linphone.databinding.ChatDocumentContentListCellBinding +import org.linphone.databinding.ChatBubbleSingleFileContentBinding import org.linphone.databinding.ChatMediaContentGridCellBinding import org.linphone.databinding.MeetingsListDecorationBinding import org.linphone.ui.main.chat.model.FileModel +import org.linphone.utils.AppUtils import org.linphone.utils.HeaderAdapter class ConversationsFilesAdapter : @@ -46,6 +47,9 @@ class ConversationsFilesAdapter : const val DOCUMENT_FILE = 2 } + private val topBottomPadding = AppUtils.getDimension(R.dimen.chat_documents_list_padding_top_bottom).toInt() + private val startEndPadding = AppUtils.getDimension(R.dimen.chat_documents_list_padding_start_end).toInt() + override fun displayHeaderForPosition(position: Int): Boolean { if (position == 0) return true @@ -89,15 +93,16 @@ class ConversationsFilesAdapter : } private fun createDocumentFileViewHolder(parent: ViewGroup): RecyclerView.ViewHolder { - val binding: ChatDocumentContentListCellBinding = DataBindingUtil.inflate( + val binding: ChatBubbleSingleFileContentBinding = DataBindingUtil.inflate( LayoutInflater.from(parent.context), - R.layout.chat_document_content_list_cell, + R.layout.chat_bubble_single_file_content, parent, false ) val viewHolder = DocumentFileViewHolder(binding) binding.apply { lifecycleOwner = parent.findViewTreeLifecycleOwner() + root.setPadding(startEndPadding, topBottomPadding, startEndPadding, topBottomPadding) } return viewHolder } @@ -123,7 +128,7 @@ class ConversationsFilesAdapter : } inner class DocumentFileViewHolder( - val binding: ChatDocumentContentListCellBinding + val binding: ChatBubbleSingleFileContentBinding ) : RecyclerView.ViewHolder(binding.root) { @UiThread fun bind(fileModel: FileModel) { diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt index 3e7033a92..0e082fd0b 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt @@ -128,22 +128,20 @@ open class ConversationFragment : SlidingPaneChildFragment() { ) ) { list -> sendMessageViewModel.closeFilePickerBottomSheet() - if (list.isNotEmpty()) { + val filesToAttach = arrayListOf() + lifecycleScope.launch { for (uri in list) { - lifecycleScope.launch { - withContext(Dispatchers.IO) { - val path = FileUtils.getFilePath(requireContext(), uri, false) - Log.i("$TAG Picked file [$uri] matching path is [$path]") - if (path != null) { - withContext(Dispatchers.Main) { - sendMessageViewModel.addAttachment(path) - } - } + withContext(Dispatchers.IO) { + val path = FileUtils.getFilePath(requireContext(), uri, false) + Log.i("$TAG Picked file [$uri] matching path is [$path]") + if (path != null) { + filesToAttach.add(path) } } } - } else { - Log.w("$TAG No file picked") + withContext(Dispatchers.Main) { + sendMessageViewModel.addAttachments(filesToAttach) + } } } @@ -153,16 +151,20 @@ open class ConversationFragment : SlidingPaneChildFragment() { ActivityResultContracts.OpenMultipleDocuments() ) { files -> sendMessageViewModel.closeFilePickerBottomSheet() - for (fileUri in files) { - lifecycleScope.launch { + val filesToAttach = arrayListOf() + lifecycleScope.launch { + for (fileUri in files) { val path = FileUtils.getFilePath(requireContext(), fileUri, false).orEmpty() if (path.isNotEmpty()) { Log.i("$TAG Picked file [$path]") - sendMessageViewModel.addAttachment(path) + filesToAttach.add(path) } else { Log.e("$TAG Failed to pick file [$fileUri]") } } + withContext(Dispatchers.Main) { + sendMessageViewModel.addAttachments(filesToAttach) + } } } @@ -174,7 +176,7 @@ open class ConversationFragment : SlidingPaneChildFragment() { if (path != null) { if (captured) { Log.i("$TAG Image was captured and saved in [$path]") - sendMessageViewModel.addAttachment(path) + sendMessageViewModel.addAttachments(arrayListOf(path)) } else { Log.w("$TAG Image capture was aborted") lifecycleScope.launch { @@ -892,7 +894,7 @@ open class ConversationFragment : SlidingPaneChildFragment() { Log.i("$TAG Rich content URI [$uri] matching path is [$path]") if (path != null) { withContext(Dispatchers.Main) { - sendMessageViewModel.addAttachment(path) + sendMessageViewModel.addAttachments(arrayListOf(path)) } } } @@ -920,7 +922,7 @@ open class ConversationFragment : SlidingPaneChildFragment() { if (files.isNotEmpty()) { Log.i("$TAG Found [${files.size}] files to share from intent") for (path in files) { - sendMessageViewModel.addAttachment(path) + sendMessageViewModel.addAttachments(arrayListOf(path)) } sharedViewModel.filesToShareFromIntent.value = arrayListOf() 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 101eecebe..44cae104f 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 @@ -91,7 +91,6 @@ class FileModel private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) init { - mediaPreviewAvailable.postValue(false) updateTransferProgress(-1) formattedFileSize.postValue(FileUtils.bytesToDisplayableSize(fileSize)) 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 904662abc..d1575dc08 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 @@ -409,16 +409,11 @@ class MessageModel filesList.postValue(arrayListOf()) var displayableContentFound = false - var filesContentCount = 0 + var contentIndex = 0 val filesPath = arrayListOf() val contents = chatMessage.contents allFilesDownloaded = true - - val notMediaContent = contents.find { - it.isIcalendar || it.isVoiceRecording || (it.isText && !it.isFile) || it.isFileTransfer || (it.isFile && !(it.type == "video" || it.type == "image")) - } - val allContentsAreMedia = notMediaContent == null val exactly4Contents = contents.size == 4 for (content in contents) { @@ -443,7 +438,7 @@ class MessageModel } else { if (content.isFile) { Log.d("$TAG Found file content with type [${content.type}/${content.subtype}]") - filesContentCount += 1 + contentIndex += 1 checkAndRepairFilePathIfNeeded(content) @@ -462,7 +457,7 @@ class MessageModel "$TAG Found file ready to be displayed [$path] with MIME [${content.type}/${content.subtype}] for message [${chatMessage.messageId}]" ) - val wrapBefore = allContentsAreMedia && exactly4Contents && filesContentCount == 3 + val wrapBefore = exactly4Contents && contentIndex == 3 val fileSize = content.fileSize.toLong() val timestamp = content.creationTimestamp val fileModel = FileModel( @@ -488,7 +483,7 @@ class MessageModel "$TAG Found file content (not downloaded yet) with type [${content.type}/${content.subtype}] and name [${content.name}]" ) allFilesDownloaded = false - filesContentCount += 1 + contentIndex += 1 val name = content.name ?: "" val timestamp = content.creationTimestamp if (name.isNotEmpty()) { diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt index ecae462b5..6a7ac45a2 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt @@ -379,35 +379,40 @@ class SendMessageInConversationViewModel } @UiThread - fun addAttachment(file: String) { - if (attachments.value.orEmpty().size >= MAX_FILES_TO_ATTACH) { - Log.w( - "$TAG Max number of attachments [$MAX_FILES_TO_ATTACH] reached, file [$file] won't be attached" - ) - showRedToast(R.string.conversation_maximum_number_of_attachments_reached, R.drawable.warning_circle) - viewModelScope.launch { - Log.i("$TAG Deleting temporary file [$file]") - FileUtils.deleteFile(file) - } - return - } - + fun addAttachments(files: ArrayList) { val list = arrayListOf() list.addAll(attachments.value.orEmpty()) - val fileName = FileUtils.getNameFromFilePath(file) - val timestamp = System.currentTimeMillis() / 1000 - val model = FileModel(file, fileName, 0, timestamp, false, file, false) { model -> - removeAttachment(model.path) - } + for (file in files) { + if (list.size >= MAX_FILES_TO_ATTACH) { + Log.w( + "$TAG Max number of attachments [$MAX_FILES_TO_ATTACH] reached, file [$file] won't be attached" + ) + showRedToast( + R.string.conversation_maximum_number_of_attachments_reached, + R.drawable.warning_circle + ) + viewModelScope.launch { + Log.i("$TAG Deleting temporary file [$file]") + FileUtils.deleteFile(file) + } + return + } - list.add(model) + val fileName = FileUtils.getNameFromFilePath(file) + val timestamp = System.currentTimeMillis() / 1000 + val model = FileModel(file, fileName, 0, timestamp, false, file, false) { model -> + removeAttachment(model.path) + } + + list.add(model) + } attachments.value = list maxNumberOfAttachmentsReached.value = list.size >= MAX_FILES_TO_ATTACH if (list.isNotEmpty()) { isFileAttachmentsListOpen.value = true - Log.i("$TAG [${list.size}] attachment(s) added") + Log.i("$TAG [${files.size}] attachment(s) added, in total ${list.size}] file(s) are attached") } else { Log.w("$TAG No attachment to display!") } diff --git a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt index fff752750..a299011b7 100644 --- a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt +++ b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt @@ -329,7 +329,7 @@ private fun loadImageForChatBubble( val isVideo = FileUtils.isExtensionVideo(file) if (isImage || isVideo) { val dimen = if (grid) { - imageView.resources.getDimension(R.dimen.chat_bubble_grid_image_size).toInt() + imageView.resources.getDimension(R.dimen.chat_bubble_grid_file_size).toInt() } else { imageView.resources.getDimension(R.dimen.chat_bubble_big_image_max_size).toInt() } diff --git a/app/src/main/res/drawable/shape_squircle_file_background_top.xml b/app/src/main/res/drawable/shape_squircle_file_background_top.xml new file mode 100644 index 000000000..f52aa40ba --- /dev/null +++ b/app/src/main/res/drawable/shape_squircle_file_background_top.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_squircle_file_bubble_background.xml b/app/src/main/res/drawable/shape_squircle_file_bubble_background.xml new file mode 100644 index 000000000..733afbda3 --- /dev/null +++ b/app/src/main/res/drawable/shape_squircle_file_bubble_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_squircle_white_right.xml b/app/src/main/res/drawable/shape_squircle_file_bubble_right_background.xml similarity index 77% rename from app/src/main/res/drawable/shape_squircle_white_right.xml rename to app/src/main/res/drawable/shape_squircle_file_bubble_right_background.xml index 4015ec80e..0f32552b6 100644 --- a/app/src/main/res/drawable/shape_squircle_white_right.xml +++ b/app/src/main/res/drawable/shape_squircle_file_bubble_right_background.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_squircle_gray_200_r15_background.xml b/app/src/main/res/drawable/shape_squircle_gray_200_r15_background.xml new file mode 100644 index 000000000..2b7fce250 --- /dev/null +++ b/app/src/main/res/drawable/shape_squircle_gray_200_r15_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file 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 6ab6fdfee..4378bfc62 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 @@ -1,7 +1,7 @@ + xmlns:bind="http://schemas.android.com/tools"> @@ -13,205 +13,32 @@ type="org.linphone.ui.main.chat.model.FileModel" /> - + app:layout_wrapBefore="@{model.flexboxLayoutWrapBefore}"> - - - - - - - - - - - - - - - - - - - - - - - - - - + layout="@layout/chat_bubble_file_grid_cell" + android:visibility="@{!model.isImage && !model.isVideoPreview ? View.VISIBLE : View.GONE}" + bind:model="@{model}" + bind:onLongClickListener="@{onLongClickListener}" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"/> diff --git a/app/src/main/res/layout/chat_bubble_file_grid_cell.xml b/app/src/main/res/layout/chat_bubble_file_grid_cell.xml new file mode 100644 index 000000000..99423ebf7 --- /dev/null +++ b/app/src/main/res/layout/chat_bubble_file_grid_cell.xml @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_bubble_incoming.xml b/app/src/main/res/layout/chat_bubble_incoming.xml index b87a4e8f1..765616b4f 100644 --- a/app/src/main/res/layout/chat_bubble_incoming.xml +++ b/app/src/main/res/layout/chat_bubble_incoming.xml @@ -175,7 +175,7 @@ android:onLongClick="@{onLongClickListener}" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:visibility="@{model.filesList.size() >= 2 || (model.filesList.size() == 1 && !model.firstFileModel.isMedia) ? View.VISIBLE : View.GONE, default=gone}" + android:visibility="@{model.filesList.size() >= 2 ? View.VISIBLE : View.GONE, default=gone}" app:alignItems="center" app:flexWrap="wrap" app:justifyContent="@{model.outgoing ? JustifyContent.FLEX_END : JustifyContent.FLEX_START}" @@ -193,6 +193,16 @@ bind:model="@{model.firstFileModel}" bind:onLongClickListener="@{onLongClickListener}"/> + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_bubble_outgoing.xml b/app/src/main/res/layout/chat_bubble_outgoing.xml index 01794927c..24bc86e16 100644 --- a/app/src/main/res/layout/chat_bubble_outgoing.xml +++ b/app/src/main/res/layout/chat_bubble_outgoing.xml @@ -147,7 +147,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:onLongClick="@{onLongClickListener}" - android:visibility="@{model.filesList.size() >= 2 || (model.filesList.size() == 1 && !model.firstFileModel.isMedia) ? View.VISIBLE : View.GONE, default=gone}" + android:visibility="@{model.filesList.size() >= 2 ? View.VISIBLE : View.GONE, default=gone}" app:alignItems="center" app:flexWrap="wrap" app:justifyContent="@{model.outgoing ? JustifyContent.FLEX_END : JustifyContent.FLEX_START}" @@ -165,6 +165,16 @@ bind:model="@{model.firstFileModel}" bind:onLongClickListener="@{onLongClickListener}"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_bubble_single_media_content.xml b/app/src/main/res/layout/chat_bubble_single_media_content.xml index a9ee9d23f..6fb6aa196 100644 --- a/app/src/main/res/layout/chat_bubble_single_media_content.xml +++ b/app/src/main/res/layout/chat_bubble_single_media_content.xml @@ -25,8 +25,8 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:bind="http://schemas.android.com/tools"> - - + android:layout_height="wrap_content"> - - - - - - - - - - + app:layout_constraintTop_toTopOf="parent"/> - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/chat_media_content_grid_cell.xml b/app/src/main/res/layout/chat_media_content_grid_cell.xml index 8c028699a..80b82a7d3 100644 --- a/app/src/main/res/layout/chat_media_content_grid_cell.xml +++ b/app/src/main/res/layout/chat_media_content_grid_cell.xml @@ -1,6 +1,7 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:bind="http://schemas.android.com/tools"> @@ -47,37 +48,6 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> - - - - + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml index 5acd61d93..8d99aa567 100644 --- a/app/src/main/res/values/dimen.xml +++ b/app/src/main/res/values/dimen.xml @@ -83,9 +83,9 @@ 2dp 8dp 25dp - 87dp 175dp - 178dp + 87dp + 178dp 271dp 230dp 271dp @@ -94,6 +94,8 @@ 5dp 15dp 24dp + 5dp + 10dp 300dp 300dp