Showing files in square area like media when more than one in a single chat message

This commit is contained in:
Sylvain Berfini 2025-05-19 13:09:10 +02:00
parent b2c5b9dd28
commit 91fdef4428
21 changed files with 616 additions and 436 deletions

View file

@ -30,10 +30,11 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.linphone.R import org.linphone.R
import org.linphone.databinding.ChatDocumentContentListCellBinding import org.linphone.databinding.ChatBubbleSingleFileContentBinding
import org.linphone.databinding.ChatMediaContentGridCellBinding import org.linphone.databinding.ChatMediaContentGridCellBinding
import org.linphone.databinding.MeetingsListDecorationBinding import org.linphone.databinding.MeetingsListDecorationBinding
import org.linphone.ui.main.chat.model.FileModel import org.linphone.ui.main.chat.model.FileModel
import org.linphone.utils.AppUtils
import org.linphone.utils.HeaderAdapter import org.linphone.utils.HeaderAdapter
class ConversationsFilesAdapter : class ConversationsFilesAdapter :
@ -46,6 +47,9 @@ class ConversationsFilesAdapter :
const val DOCUMENT_FILE = 2 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 { override fun displayHeaderForPosition(position: Int): Boolean {
if (position == 0) return true if (position == 0) return true
@ -89,15 +93,16 @@ class ConversationsFilesAdapter :
} }
private fun createDocumentFileViewHolder(parent: ViewGroup): RecyclerView.ViewHolder { private fun createDocumentFileViewHolder(parent: ViewGroup): RecyclerView.ViewHolder {
val binding: ChatDocumentContentListCellBinding = DataBindingUtil.inflate( val binding: ChatBubbleSingleFileContentBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
R.layout.chat_document_content_list_cell, R.layout.chat_bubble_single_file_content,
parent, parent,
false false
) )
val viewHolder = DocumentFileViewHolder(binding) val viewHolder = DocumentFileViewHolder(binding)
binding.apply { binding.apply {
lifecycleOwner = parent.findViewTreeLifecycleOwner() lifecycleOwner = parent.findViewTreeLifecycleOwner()
root.setPadding(startEndPadding, topBottomPadding, startEndPadding, topBottomPadding)
} }
return viewHolder return viewHolder
} }
@ -123,7 +128,7 @@ class ConversationsFilesAdapter :
} }
inner class DocumentFileViewHolder( inner class DocumentFileViewHolder(
val binding: ChatDocumentContentListCellBinding val binding: ChatBubbleSingleFileContentBinding
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
@UiThread @UiThread
fun bind(fileModel: FileModel) { fun bind(fileModel: FileModel) {

View file

@ -128,22 +128,20 @@ open class ConversationFragment : SlidingPaneChildFragment() {
) )
) { list -> ) { list ->
sendMessageViewModel.closeFilePickerBottomSheet() sendMessageViewModel.closeFilePickerBottomSheet()
if (list.isNotEmpty()) { val filesToAttach = arrayListOf<String>()
lifecycleScope.launch {
for (uri in list) { for (uri in list) {
lifecycleScope.launch { withContext(Dispatchers.IO) {
withContext(Dispatchers.IO) { val path = FileUtils.getFilePath(requireContext(), uri, false)
val path = FileUtils.getFilePath(requireContext(), uri, false) Log.i("$TAG Picked file [$uri] matching path is [$path]")
Log.i("$TAG Picked file [$uri] matching path is [$path]") if (path != null) {
if (path != null) { filesToAttach.add(path)
withContext(Dispatchers.Main) {
sendMessageViewModel.addAttachment(path)
}
}
} }
} }
} }
} else { withContext(Dispatchers.Main) {
Log.w("$TAG No file picked") sendMessageViewModel.addAttachments(filesToAttach)
}
} }
} }
@ -153,16 +151,20 @@ open class ConversationFragment : SlidingPaneChildFragment() {
ActivityResultContracts.OpenMultipleDocuments() ActivityResultContracts.OpenMultipleDocuments()
) { files -> ) { files ->
sendMessageViewModel.closeFilePickerBottomSheet() sendMessageViewModel.closeFilePickerBottomSheet()
for (fileUri in files) { val filesToAttach = arrayListOf<String>()
lifecycleScope.launch { lifecycleScope.launch {
for (fileUri in files) {
val path = FileUtils.getFilePath(requireContext(), fileUri, false).orEmpty() val path = FileUtils.getFilePath(requireContext(), fileUri, false).orEmpty()
if (path.isNotEmpty()) { if (path.isNotEmpty()) {
Log.i("$TAG Picked file [$path]") Log.i("$TAG Picked file [$path]")
sendMessageViewModel.addAttachment(path) filesToAttach.add(path)
} else { } else {
Log.e("$TAG Failed to pick file [$fileUri]") 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 (path != null) {
if (captured) { if (captured) {
Log.i("$TAG Image was captured and saved in [$path]") Log.i("$TAG Image was captured and saved in [$path]")
sendMessageViewModel.addAttachment(path) sendMessageViewModel.addAttachments(arrayListOf(path))
} else { } else {
Log.w("$TAG Image capture was aborted") Log.w("$TAG Image capture was aborted")
lifecycleScope.launch { lifecycleScope.launch {
@ -892,7 +894,7 @@ open class ConversationFragment : SlidingPaneChildFragment() {
Log.i("$TAG Rich content URI [$uri] matching path is [$path]") Log.i("$TAG Rich content URI [$uri] matching path is [$path]")
if (path != null) { if (path != null) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
sendMessageViewModel.addAttachment(path) sendMessageViewModel.addAttachments(arrayListOf(path))
} }
} }
} }
@ -920,7 +922,7 @@ open class ConversationFragment : SlidingPaneChildFragment() {
if (files.isNotEmpty()) { if (files.isNotEmpty()) {
Log.i("$TAG Found [${files.size}] files to share from intent") Log.i("$TAG Found [${files.size}] files to share from intent")
for (path in files) { for (path in files) {
sendMessageViewModel.addAttachment(path) sendMessageViewModel.addAttachments(arrayListOf(path))
} }
sharedViewModel.filesToShareFromIntent.value = arrayListOf() sharedViewModel.filesToShareFromIntent.value = arrayListOf()

View file

@ -91,7 +91,6 @@ class FileModel
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
init { init {
mediaPreviewAvailable.postValue(false)
updateTransferProgress(-1) updateTransferProgress(-1)
formattedFileSize.postValue(FileUtils.bytesToDisplayableSize(fileSize)) formattedFileSize.postValue(FileUtils.bytesToDisplayableSize(fileSize))

View file

@ -409,16 +409,11 @@ class MessageModel
filesList.postValue(arrayListOf()) filesList.postValue(arrayListOf())
var displayableContentFound = false var displayableContentFound = false
var filesContentCount = 0 var contentIndex = 0
val filesPath = arrayListOf<FileModel>() val filesPath = arrayListOf<FileModel>()
val contents = chatMessage.contents val contents = chatMessage.contents
allFilesDownloaded = true 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 val exactly4Contents = contents.size == 4
for (content in contents) { for (content in contents) {
@ -443,7 +438,7 @@ class MessageModel
} else { } else {
if (content.isFile) { if (content.isFile) {
Log.d("$TAG Found file content with type [${content.type}/${content.subtype}]") Log.d("$TAG Found file content with type [${content.type}/${content.subtype}]")
filesContentCount += 1 contentIndex += 1
checkAndRepairFilePathIfNeeded(content) 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}]" "$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 fileSize = content.fileSize.toLong()
val timestamp = content.creationTimestamp val timestamp = content.creationTimestamp
val fileModel = FileModel( 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}]" "$TAG Found file content (not downloaded yet) with type [${content.type}/${content.subtype}] and name [${content.name}]"
) )
allFilesDownloaded = false allFilesDownloaded = false
filesContentCount += 1 contentIndex += 1
val name = content.name ?: "" val name = content.name ?: ""
val timestamp = content.creationTimestamp val timestamp = content.creationTimestamp
if (name.isNotEmpty()) { if (name.isNotEmpty()) {

View file

@ -379,35 +379,40 @@ class SendMessageInConversationViewModel
} }
@UiThread @UiThread
fun addAttachment(file: String) { fun addAttachments(files: ArrayList<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
}
val list = arrayListOf<FileModel>() val list = arrayListOf<FileModel>()
list.addAll(attachments.value.orEmpty()) list.addAll(attachments.value.orEmpty())
val fileName = FileUtils.getNameFromFilePath(file) for (file in files) {
val timestamp = System.currentTimeMillis() / 1000 if (list.size >= MAX_FILES_TO_ATTACH) {
val model = FileModel(file, fileName, 0, timestamp, false, file, false) { model -> Log.w(
removeAttachment(model.path) "$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 attachments.value = list
maxNumberOfAttachmentsReached.value = list.size >= MAX_FILES_TO_ATTACH maxNumberOfAttachmentsReached.value = list.size >= MAX_FILES_TO_ATTACH
if (list.isNotEmpty()) { if (list.isNotEmpty()) {
isFileAttachmentsListOpen.value = true 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 { } else {
Log.w("$TAG No attachment to display!") Log.w("$TAG No attachment to display!")
} }

View file

@ -329,7 +329,7 @@ private fun loadImageForChatBubble(
val isVideo = FileUtils.isExtensionVideo(file) val isVideo = FileUtils.isExtensionVideo(file)
if (isImage || isVideo) { if (isImage || isVideo) {
val dimen = if (grid) { 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 { } else {
imageView.resources.getDimension(R.dimen.chat_bubble_big_image_max_size).toInt() imageView.resources.getDimension(R.dimen.chat_bubble_big_image_max_size).toInt()
} }

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:topLeftRadius="5dp" android:topRightRadius="5dp" />
<solid android:color="?attr/color_chat_bubble_file" />
</shape>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:radius="5dp" />
<solid android:color="?attr/color_main2_000"/>
</shape>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:topRightRadius="10dp" android:bottomRightRadius="10dp" /> <corners android:topRightRadius="10dp" android:bottomRightRadius="10dp" />
<solid android:color="?attr/color_main2_000" /> <solid android:color="?attr/color_main2_000"/>
</shape> </shape>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<corners android:radius="15dp" />
<solid android:color="?attr/color_grey_100"/>
</shape>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"> xmlns:bind="http://schemas.android.com/tools">
<data> <data>
<import type="android.view.View" /> <import type="android.view.View" />
@ -13,205 +13,32 @@
type="org.linphone.ui.main.chat.model.FileModel" /> type="org.linphone.ui.main.chat.model.FileModel" />
</data> </data>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_wrapBefore="@{model.flexboxLayoutWrapBefore}" app:layout_wrapBefore="@{model.flexboxLayoutWrapBefore}">
android:padding="1dp">
<androidx.constraintlayout.widget.Group <include
android:id="@+id/file_group" android:id="@+id/media_layout"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="@{model.isImage || model.isVideoPreview ? View.GONE : View.VISIBLE}" layout="@layout/chat_bubble_media_grid_cell"
app:constraint_referenced_ids="file_name, file_background" />
<androidx.constraintlayout.widget.Group
android:id="@+id/download_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{model.transferProgress == -1 || model.transferProgress >= 100 ? View.GONE : View.VISIBLE}"
app:constraint_referenced_ids="transfer_progress, transfer_progress_label" />
<ImageView
android:id="@+id/broken_media_icon"
android:onClick="@{() -> model.onClick()}"
android:layout_width="@dimen/chat_bubble_grid_image_size"
android:layout_height="@dimen/chat_bubble_grid_image_size"
android:adjustViewBounds="true"
android:padding="18dp"
android:background="@drawable/shape_squircle_file_background_5dp"
android:visibility="@{model.mediaPreviewAvailable ? View.GONE : View.VISIBLE, default=gone}"
android:contentDescription="@{model.isVideoPreview ? @string/content_description_chat_bubble_video : @string/content_description_chat_bubble_image}"
android:src="@{model.isVideoPreview ? @drawable/file_video : @drawable/file_image, default=@drawable/file_video}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:tint="?attr/color_main2_600" />
<ImageView
android:id="@+id/image"
android:onClick="@{() -> model.onClick()}"
android:onLongClick="@{onLongClickListener}"
android:layout_width="@dimen/chat_bubble_grid_image_size"
android:layout_height="@dimen/chat_bubble_grid_image_size"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:contentDescription="@{model.isVideoPreview ? @string/content_description_chat_bubble_video : @string/content_description_chat_bubble_image}"
android:visibility="@{model.isImage || model.isVideoPreview ? View.VISIBLE : View.GONE}" android:visibility="@{model.isImage || model.isVideoPreview ? View.VISIBLE : View.GONE}"
coilBubbleGrid="@{model.mediaPreview}" bind:model="@{model}"
coilBubbleFallback="@{brokenMediaIcon}" bind:onLongClickListener="@{onLongClickListener}"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/> app:layout_constraintTop_toTopOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView <include
style="@style/default_text_style_600" android:id="@+id/file_layout"
android:id="@+id/video_duration"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="5dp" layout="@layout/chat_bubble_file_grid_cell"
android:text="@{model.audioVideoDuration, default=`00:42`}" android:visibility="@{!model.isImage &amp;&amp; !model.isVideoPreview ? View.VISIBLE : View.GONE}"
android:textColor="@{model.isVideoPreview ? @color/bc_white : @color/main2_600}" bind:model="@{model}"
android:textSize="12sp" bind:onLongClickListener="@{onLongClickListener}"
android:visibility="@{model.isVideoPreview &amp;&amp; model.audioVideoDuration.length() > 0 ? View.VISIBLE : View.GONE, default=gone}" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toTopOf="parent"/>
app:layout_constraintStart_toStartOf="parent"/>
<ImageView
android:id="@+id/video_preview"
android:onClick="@{() -> model.onClick()}"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:src="@drawable/play_fill"
android:contentDescription="@null"
android:visibility="@{model.isVideoPreview &amp;&amp; model.mediaPreviewAvailable ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toTopOf="@id/image"
app:layout_constraintBottom_toBottomOf="@id/image"
app:layout_constraintStart_toStartOf="@id/image"
app:layout_constraintEnd_toEndOf="@id/image"
app:tint="@color/bc_white" />
<View
android:id="@+id/left_background"
android:layout_width="@dimen/chat_bubble_grid_image_size"
android:layout_height="@dimen/chat_bubble_grid_image_size"
android:background="@drawable/shape_squircle_file_background_left"
android:visibility="@{model.isImage || model.isVideoPreview ? View.INVISIBLE : View.VISIBLE}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/file_icon"
android:layout_width="0dp"
android:layout_height="0dp"
android:onClick="@{() -> model.onClick()}"
android:onLongClick="@{onLongClickListener}"
android:adjustViewBounds="true"
android:padding="18dp"
android:contentDescription="@string/content_description_chat_bubble_file"
android:src="@{model.isWaitingToBeDownloaded ? @drawable/download_simple : model.isPdf ? @drawable/file_pdf : model.isAudio ? @drawable/file_audio : @drawable/file, default=@drawable/file_pdf}"
android:visibility="@{model.isImage || model.isVideoPreview || (model.transferProgress >= 0 &amp;&amp; model.transferProgress &lt; 100) ? View.GONE : View.VISIBLE}"
app:layout_constraintTop_toTopOf="@id/left_background"
app:layout_constraintBottom_toBottomOf="@id/left_background"
app:layout_constraintStart_toStartOf="@id/left_background"
app:layout_constraintEnd_toEndOf="@id/left_background"
app:tint="?attr/color_main2_600" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_600"
android:id="@+id/audio_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@{model.audioVideoDuration, default=`00:42`}"
android:textColor="?attr/color_main2_600"
android:textSize="12sp"
android:visibility="@{model.isAudio &amp;&amp; model.audioVideoDuration.length() > 0 ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintBottom_toBottomOf="@id/left_background"
app:layout_constraintStart_toStartOf="@id/left_background"/>
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/transfer_progress"
android:layout_width="@dimen/chat_bubble_grid_image_size"
android:layout_height="@dimen/chat_bubble_grid_image_size"
android:indeterminate="false"
android:progress="@{model.transferProgress}"
android:max="100"
app:indicatorSize="50dp"
app:trackColor="?attr/color_main1_100"
app:indicatorColor="?attr/color_main1_500"
app:layout_constraintTop_toTopOf="@id/left_background"
app:layout_constraintBottom_toBottomOf="@id/left_background"
app:layout_constraintStart_toStartOf="@id/left_background"
app:layout_constraintEnd_toEndOf="@id/left_background"
tools:progress="40" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/main_page_title_style"
android:id="@+id/transfer_progress_label"
android:layout_width="0dp"
android:layout_height="0dp"
android:onClick="@{() -> model.onClick()}"
android:onLongClick="@{onLongClickListener}"
android:text="@{model.transferProgressLabel, default=`40%`}"
android:textSize="16sp"
android:textAlignment="center"
android:textColor="?attr/color_main1_500"
app:layout_constraintTop_toTopOf="@id/transfer_progress"
app:layout_constraintBottom_toBottomOf="@id/transfer_progress"
app:layout_constraintStart_toStartOf="@id/transfer_progress"
app:layout_constraintEnd_toEndOf="@id/transfer_progress" />
<View
android:id="@+id/file_background"
android:onClick="@{() -> model.onClick()}"
android:onLongClick="@{onLongClickListener}"
android:layout_width="@dimen/chat_bubble_grid_file_width"
android:layout_height="0dp"
android:background="@drawable/shape_squircle_white_right"
app:layout_constraintTop_toTopOf="@id/left_background"
app:layout_constraintBottom_toBottomOf="@id/left_background"
app:layout_constraintStart_toEndOf="@id/left_background"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_700"
android:id="@+id/file_name"
android:onClick="@{() -> model.onClick()}"
android:onLongClick="@{onLongClickListener}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@{model.fileName, default=`Lorem_ipsum.pdf`}"
android:textColor="?attr/color_main2_600"
android:textSize="13sp"
android:maxLines="1"
android:ellipsize="middle"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/left_background"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/file_background"
app:layout_constraintBottom_toTopOf="@id/file_size"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_300"
android:id="@+id/file_size"
android:onClick="@{() -> model.onClick()}"
android:onLongClick="@{onLongClickListener}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@{model.formattedFileSize, default=`42 kb`}"
android:textColor="?attr/color_main2_600"
android:textSize="12sp"
android:maxLines="1"
android:ellipsize="end"
android:visibility="@{model.isImage || model.isVideoPreview || model.fileSize == 0 ? View.GONE : View.VISIBLE, default=gone}"
app:layout_constraintStart_toStartOf="@id/file_name"
app:layout_constraintEnd_toEndOf="@id/file_name"
app:layout_constraintTop_toBottomOf="@id/file_name"
app:layout_constraintBottom_toBottomOf="@id/file_background"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,155 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="onLongClickListener"
type="View.OnLongClickListener" />
<variable
name="model"
type="org.linphone.ui.main.chat.model.FileModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="1dp">
<View
android:id="@+id/background"
android:layout_width="@dimen/chat_bubble_grid_file_size"
android:layout_height="@dimen/chat_bubble_grid_file_size"
android:background="@drawable/shape_squircle_file_bubble_background"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<androidx.constraintlayout.widget.Group
android:id="@+id/download_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{model.transferProgress == -1 || model.transferProgress >= 100 ? View.GONE : View.VISIBLE, default=gone}"
app:constraint_referenced_ids="transfer_progress, transfer_progress_label" />
<View
android:id="@+id/top_background"
android:layout_width="0dp"
android:layout_height="24dp"
android:background="@drawable/shape_squircle_file_background_top"
app:layout_constraintStart_toStartOf="@id/background"
app:layout_constraintEnd_toEndOf="@id/background"
app:layout_constraintTop_toTopOf="@id/background"
app:layout_constraintBottom_toBottomOf="@id/file_icon"/>
<ImageView
android:id="@+id/file_icon"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:adjustViewBounds="true"
android:padding="2dp"
android:onClick="@{() -> model.onClick()}"
android:onLongClick="@{onLongClickListener}"
android:contentDescription="@string/content_description_chat_bubble_file"
android:src="@{model.isWaitingToBeDownloaded ? @drawable/download_simple : model.isPdf ? @drawable/file_pdf : model.isAudio ? @drawable/file_audio : @drawable/file, default=@drawable/file_pdf}"
app:layout_constraintStart_toStartOf="@id/background"
app:layout_constraintEnd_toEndOf="@id/background"
app:layout_constraintTop_toTopOf="@id/top_background"
app:layout_constraintBottom_toBottomOf="@id/top_background"
app:tint="?attr/color_main2_600" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_700"
android:id="@+id/file_name"
android:onClick="@{() -> model.onClick()}"
android:onLongClick="@{onLongClickListener}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:text="@{model.fileName, default=`Lorem_ipsum.pdf`}"
android:textColor="?attr/color_main2_600"
android:textAlignment="center"
android:textSize="12sp"
android:maxLines="2"
android:ellipsize="end"
android:visibility="@{model.transferProgress >= 0 &amp;&amp; model.transferProgress &lt; 100 ? View.INVISIBLE : View.VISIBLE}"
app:layout_constraintStart_toStartOf="@id/background"
app:layout_constraintEnd_toEndOf="@id/background"
app:layout_constraintTop_toBottomOf="@id/file_icon"
app:layout_constraintBottom_toTopOf="@id/bottom_barrier"/>
<androidx.constraintlayout.widget.Barrier
android:id="@+id/bottom_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="top"
app:constraint_referenced_ids="file_size, audio_duration" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_300"
android:id="@+id/file_size"
android:onClick="@{() -> model.onClick()}"
android:onLongClick="@{onLongClickListener}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:text="@{model.formattedFileSize, default=`42 kb`}"
android:textColor="?attr/color_main2_600"
android:textSize="12sp"
android:maxLines="1"
android:ellipsize="end"
android:visibility="@{(model.transferProgress >= 0 &amp;&amp; model.transferProgress &lt; 100) || model.fileSize == 0 ? View.GONE : View.VISIBLE, default=gone}"
app:layout_constraintEnd_toEndOf="@id/background"
app:layout_constraintBottom_toBottomOf="@id/background"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_600"
android:id="@+id/audio_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@{model.audioVideoDuration, default=`00:42`}"
android:textColor="?attr/color_main2_600"
android:textSize="11sp"
android:visibility="@{model.isAudio &amp;&amp; model.audioVideoDuration.length() > 0 ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintBottom_toBottomOf="@id/background"
app:layout_constraintStart_toStartOf="parent"/>
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/transfer_progress"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:indeterminate="false"
android:progress="@{model.transferProgress}"
android:max="100"
app:indicatorSize="50dp"
app:trackColor="?attr/color_main1_100"
app:indicatorColor="?attr/color_main1_500"
app:layout_constraintTop_toBottomOf="@id/top_background"
app:layout_constraintBottom_toBottomOf="@id/background"
app:layout_constraintStart_toStartOf="@id/background"
app:layout_constraintEnd_toEndOf="@id/background"
tools:progress="40" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/main_page_title_style"
android:id="@+id/transfer_progress_label"
android:layout_width="0dp"
android:layout_height="0dp"
android:onClick="@{() -> model.onClick()}"
android:onLongClick="@{onLongClickListener}"
android:text="@{model.transferProgressLabel, default=`40%`}"
android:textSize="16sp"
android:textAlignment="center"
android:textColor="?attr/color_main1_500"
app:layout_constraintTop_toTopOf="@id/transfer_progress"
app:layout_constraintBottom_toBottomOf="@id/transfer_progress"
app:layout_constraintStart_toStartOf="@id/transfer_progress"
app:layout_constraintEnd_toEndOf="@id/transfer_progress" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -175,7 +175,7 @@
android:onLongClick="@{onLongClickListener}" android:onLongClick="@{onLongClickListener}"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="@{model.filesList.size() >= 2 || (model.filesList.size() == 1 &amp;&amp; !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:alignItems="center"
app:flexWrap="wrap" app:flexWrap="wrap"
app:justifyContent="@{model.outgoing ? JustifyContent.FLEX_END : JustifyContent.FLEX_START}" app:justifyContent="@{model.outgoing ? JustifyContent.FLEX_END : JustifyContent.FLEX_START}"
@ -193,6 +193,16 @@
bind:model="@{model.firstFileModel}" bind:model="@{model.firstFileModel}"
bind:onLongClickListener="@{onLongClickListener}"/> bind:onLongClickListener="@{onLongClickListener}"/>
<ViewStub
android:id="@+id/single_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout="@layout/chat_bubble_single_file_content"
android:visibility="@{model.filesList.size() == 1 &amp;&amp; !model.firstFileModel.isMedia ? View.VISIBLE : View.GONE, default=gone}"
bind:inflatedVisibility="@{model.filesList.size() == 1 &amp;&amp; !model.firstFileModel.isMedia? View.VISIBLE : View.GONE}"
bind:model="@{model.firstFileModel}"
bind:onLongClickListener="@{onLongClickListener}"/>
<ViewStub <ViewStub
android:id="@+id/meeting_info" android:id="@+id/meeting_info"
android:layout_width="@dimen/chat_bubble_meeting_invite_width" android:layout_width="@dimen/chat_bubble_meeting_invite_width"

View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<variable
name="onLongClickListener"
type="View.OnLongClickListener" />
<variable
name="model"
type="org.linphone.ui.main.chat.model.FileModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="1dp">
<ImageView
android:id="@+id/broken_media_icon"
android:onClick="@{() -> model.onClick()}"
android:layout_width="@dimen/chat_bubble_grid_file_size"
android:layout_height="@dimen/chat_bubble_grid_file_size"
android:adjustViewBounds="true"
android:padding="18dp"
android:background="@drawable/shape_squircle_file_background_5dp"
android:visibility="@{!model.isMedia || model.mediaPreviewAvailable ? View.GONE : View.VISIBLE, default=gone}"
android:contentDescription="@{model.isVideoPreview ? @string/content_description_chat_bubble_video : @string/content_description_chat_bubble_image}"
android:src="@{model.isVideoPreview ? @drawable/file_video : @drawable/file_image, default=@drawable/file_video}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:tint="?attr/color_main2_600" />
<ImageView
android:id="@+id/image"
android:onClick="@{() -> model.onClick()}"
android:onLongClick="@{onLongClickListener}"
android:layout_width="@dimen/chat_bubble_grid_file_size"
android:layout_height="@dimen/chat_bubble_grid_file_size"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:contentDescription="@{model.isVideoPreview ? @string/content_description_chat_bubble_video : @string/content_description_chat_bubble_image}"
android:visibility="@{model.isImage || model.isVideoPreview ? View.VISIBLE : View.GONE}"
coilBubbleGrid="@{model.mediaPreview}"
coilBubbleFallback="@{brokenMediaIcon}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_600"
android:id="@+id/video_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@{model.audioVideoDuration, default=`00:42`}"
android:textColor="@{model.isVideoPreview ? @color/bc_white : @color/main2_600}"
android:textSize="12sp"
android:visibility="@{model.isVideoPreview &amp;&amp; model.audioVideoDuration.length() > 0 ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<ImageView
android:id="@+id/video_preview"
android:onClick="@{() -> model.onClick()}"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:src="@drawable/play_fill"
android:contentDescription="@null"
android:visibility="@{model.isVideoPreview &amp;&amp; model.mediaPreviewAvailable ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toTopOf="@id/image"
app:layout_constraintBottom_toBottomOf="@id/image"
app:layout_constraintStart_toStartOf="@id/image"
app:layout_constraintEnd_toEndOf="@id/image"
app:tint="@color/bc_white" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -147,7 +147,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:onLongClick="@{onLongClickListener}" android:onLongClick="@{onLongClickListener}"
android:visibility="@{model.filesList.size() >= 2 || (model.filesList.size() == 1 &amp;&amp; !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:alignItems="center"
app:flexWrap="wrap" app:flexWrap="wrap"
app:justifyContent="@{model.outgoing ? JustifyContent.FLEX_END : JustifyContent.FLEX_START}" app:justifyContent="@{model.outgoing ? JustifyContent.FLEX_END : JustifyContent.FLEX_START}"
@ -165,6 +165,16 @@
bind:model="@{model.firstFileModel}" bind:model="@{model.firstFileModel}"
bind:onLongClickListener="@{onLongClickListener}"/> bind:onLongClickListener="@{onLongClickListener}"/>
<ViewStub
android:id="@+id/single_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout="@layout/chat_bubble_single_file_content"
android:visibility="@{model.filesList.size() == 1 &amp;&amp; !model.firstFileModel.isMedia ? View.VISIBLE : View.GONE, default=gone}"
bind:inflatedVisibility="@{model.filesList.size() == 1 &amp;&amp; !model.firstFileModel.isMedia? View.VISIBLE : View.GONE}"
bind:model="@{model.firstFileModel}"
bind:onLongClickListener="@{onLongClickListener}"/>
<ViewStub <ViewStub
android:id="@+id/meeting_info" android:id="@+id/meeting_info"
android:layout_width="@dimen/chat_bubble_meeting_invite_width" android:layout_width="@dimen/chat_bubble_meeting_invite_width"

View file

@ -0,0 +1,165 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="onLongClickListener"
type="View.OnLongClickListener" />
<variable
name="model"
type="org.linphone.ui.main.chat.model.FileModel" />
<variable
name="inflatedVisibility"
type="Integer" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="1dp"
android:visibility="@{inflatedVisibility == View.VISIBLE ? View.VISIBLE : View.GONE}"
inflatedLifecycleOwner="@{true}">
<androidx.constraintlayout.widget.Group
android:id="@+id/file_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{model.isImage || model.isVideoPreview ? View.GONE : View.VISIBLE}"
app:constraint_referenced_ids="file_name, file_background" />
<androidx.constraintlayout.widget.Group
android:id="@+id/download_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{model.transferProgress == -1 || model.transferProgress >= 100 ? View.GONE : View.VISIBLE, default=gone}"
app:constraint_referenced_ids="transfer_progress, transfer_progress_label" />
<View
android:id="@+id/left_background"
android:layout_width="@dimen/chat_bubble_grid_file_size"
android:layout_height="@dimen/chat_bubble_grid_file_size"
android:background="@drawable/shape_squircle_file_background_left"
android:visibility="@{model.isImage || model.isVideoPreview ? View.INVISIBLE : View.VISIBLE}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/file_icon"
android:layout_width="0dp"
android:layout_height="0dp"
android:onClick="@{() -> model.onClick()}"
android:onLongClick="@{onLongClickListener}"
android:adjustViewBounds="true"
android:padding="18dp"
android:contentDescription="@string/content_description_chat_bubble_file"
android:src="@{model.isWaitingToBeDownloaded ? @drawable/download_simple : model.isPdf ? @drawable/file_pdf : model.isAudio ? @drawable/file_audio : @drawable/file, default=@drawable/file_pdf}"
android:visibility="@{model.isImage || model.isVideoPreview || (model.transferProgress >= 0 &amp;&amp; model.transferProgress &lt; 100) ? View.GONE : View.VISIBLE}"
app:layout_constraintTop_toTopOf="@id/left_background"
app:layout_constraintBottom_toBottomOf="@id/left_background"
app:layout_constraintStart_toStartOf="@id/left_background"
app:layout_constraintEnd_toEndOf="@id/left_background"
app:tint="?attr/color_main2_600" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_600"
android:id="@+id/audio_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@{model.audioVideoDuration, default=`00:42`}"
android:textColor="?attr/color_main2_600"
android:textSize="12sp"
android:visibility="@{model.isAudio &amp;&amp; model.audioVideoDuration.length() > 0 ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintBottom_toBottomOf="@id/left_background"
app:layout_constraintStart_toStartOf="@id/left_background"/>
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/transfer_progress"
android:layout_width="@dimen/chat_bubble_grid_file_size"
android:layout_height="@dimen/chat_bubble_grid_file_size"
android:indeterminate="false"
android:progress="@{model.transferProgress}"
android:max="100"
app:indicatorSize="50dp"
app:trackColor="?attr/color_main1_100"
app:indicatorColor="?attr/color_main1_500"
app:layout_constraintTop_toTopOf="@id/left_background"
app:layout_constraintBottom_toBottomOf="@id/left_background"
app:layout_constraintStart_toStartOf="@id/left_background"
app:layout_constraintEnd_toEndOf="@id/left_background"
tools:progress="40" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/main_page_title_style"
android:id="@+id/transfer_progress_label"
android:layout_width="0dp"
android:layout_height="0dp"
android:onClick="@{() -> model.onClick()}"
android:onLongClick="@{onLongClickListener}"
android:text="@{model.transferProgressLabel, default=`40%`}"
android:textSize="16sp"
android:textAlignment="center"
android:textColor="?attr/color_main1_500"
app:layout_constraintTop_toTopOf="@id/transfer_progress"
app:layout_constraintBottom_toBottomOf="@id/transfer_progress"
app:layout_constraintStart_toStartOf="@id/transfer_progress"
app:layout_constraintEnd_toEndOf="@id/transfer_progress" />
<View
android:id="@+id/file_background"
android:onClick="@{() -> model.onClick()}"
android:onLongClick="@{onLongClickListener}"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/shape_squircle_file_bubble_right_background"
app:layout_constraintWidth_min="@dimen/chat_bubble_single_file_width"
app:layout_constraintTop_toTopOf="@id/left_background"
app:layout_constraintBottom_toBottomOf="@id/left_background"
app:layout_constraintStart_toEndOf="@id/left_background"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_700"
android:id="@+id/file_name"
android:onClick="@{() -> model.onClick()}"
android:onLongClick="@{onLongClickListener}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@{model.fileName, default=`Lorem_ipsum.pdf`}"
android:textColor="?attr/color_main2_600"
android:textSize="13sp"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/left_background"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/file_background"
app:layout_constraintBottom_toTopOf="@id/file_size"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_300"
android:id="@+id/file_size"
android:onClick="@{() -> model.onClick()}"
android:onLongClick="@{onLongClickListener}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@{model.formattedFileSize, default=`42 kb`}"
android:textColor="?attr/color_main2_600"
android:textSize="12sp"
android:maxLines="1"
android:ellipsize="end"
android:visibility="@{model.isImage || model.isVideoPreview || model.fileSize == 0 ? View.GONE : View.VISIBLE, default=gone}"
app:layout_constraintStart_toStartOf="@id/file_name"
app:layout_constraintEnd_toEndOf="@id/file_name"
app:layout_constraintTop_toBottomOf="@id/file_name"
app:layout_constraintBottom_toBottomOf="@id/file_background"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -25,8 +25,8 @@
<ImageView <ImageView
android:id="@+id/broken_media_icon" android:id="@+id/broken_media_icon"
android:onClick="@{() -> model.onClick()}" android:onClick="@{() -> model.onClick()}"
android:layout_width="@dimen/chat_bubble_grid_image_size" android:layout_width="@dimen/chat_bubble_grid_file_size"
android:layout_height="@dimen/chat_bubble_grid_image_size" android:layout_height="@dimen/chat_bubble_grid_file_size"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:padding="18dp" android:padding="18dp"
android:background="@drawable/shape_squircle_file_background_5dp" android:background="@drawable/shape_squircle_file_background_5dp"
@ -85,8 +85,8 @@
<com.google.android.material.progressindicator.CircularProgressIndicator <com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/transfer_progress" android:id="@+id/transfer_progress"
android:layout_width="@dimen/chat_bubble_grid_image_size" android:layout_width="@dimen/chat_bubble_grid_file_size"
android:layout_height="@dimen/chat_bubble_grid_image_size" android:layout_height="@dimen/chat_bubble_grid_file_size"
android:indeterminate="false" android:indeterminate="false"
android:progress="@{model.transferProgress}" android:progress="@{model.transferProgress}"
android:max="100" android:max="100"

View file

@ -1,110 +1,38 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:bind="http://schemas.android.com/tools">
<data> <data>
<import type="android.view.View" /> <import type="android.view.View" />
<variable
name="onLongClickListener"
type="View.OnLongClickListener" />
<variable <variable
name="model" name="model"
type="org.linphone.ui.main.chat.model.FileModel" /> type="org.linphone.ui.main.chat.model.FileModel" />
</data> </data>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:layout_margin="1dp">
<ImageView <include
android:id="@+id/broken_media_icon" android:id="@+id/media_layout"
android:onClick="@{() -> model.onClick()}" android:layout_width="wrap_content"
android:layout_width="@dimen/chat_bubble_grid_image_size" android:layout_height="wrap_content"
android:layout_height="@dimen/chat_bubble_grid_image_size" layout="@layout/chat_bubble_media_grid_cell"
android:adjustViewBounds="true"
android:padding="18dp"
android:background="@drawable/shape_squircle_file_background_5dp"
android:visibility="@{model.mediaPreviewAvailable ? View.GONE : View.VISIBLE, default=gone}"
android:contentDescription="@{model.isVideoPreview ? @string/content_description_chat_bubble_video : @string/content_description_chat_bubble_image}"
android:src="@{model.isVideoPreview ? @drawable/file_video : @drawable/file_image, default=@drawable/file_video}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:tint="?attr/color_main2_600" />
<ImageView
android:id="@+id/image"
android:layout_width="@dimen/chat_bubble_grid_image_size"
android:layout_height="@dimen/chat_bubble_grid_image_size"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:contentDescription="@{model.isVideoPreview ? @string/content_description_chat_bubble_video : @string/content_description_chat_bubble_image}"
android:visibility="@{model.isImage || model.isVideoPreview ? View.VISIBLE : View.GONE}" android:visibility="@{model.isImage || model.isVideoPreview ? View.VISIBLE : View.GONE}"
coilBubbleGrid="@{model.mediaPreview}" bind:model="@{model}"
coilBubbleFallback="@{brokenMediaIcon}"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/> app:layout_constraintTop_toTopOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView <include
style="@style/default_text_style_600" android:id="@+id/file_layout"
android:id="@+id/video_duration"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="5dp" layout="@layout/chat_bubble_file_grid_cell"
android:text="@{model.audioVideoDuration, default=`00:42`}" android:visibility="@{!model.isImage &amp;&amp; !model.isVideoPreview ? View.VISIBLE : View.GONE}"
android:textColor="@{model.isVideoPreview ? @color/bc_white : @color/main2_600}" bind:model="@{model}"
android:textSize="12sp"
android:visibility="@{model.isVideoPreview &amp;&amp; model.audioVideoDuration.length() > 0 ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<ImageView
android:id="@+id/video_preview"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:src="@drawable/play_fill"
android:contentDescription="@null"
android:visibility="@{model.isVideoPreview &amp;&amp; model.mediaPreviewAvailable ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toTopOf="@id/image"
app:layout_constraintBottom_toBottomOf="@id/image"
app:layout_constraintStart_toStartOf="@id/image"
app:layout_constraintEnd_toEndOf="@id/image"
app:tint="@color/bc_white" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_700"
android:id="@+id/file_name"
android:layout_width="@dimen/chat_bubble_grid_image_size"
android:layout_height="@dimen/chat_bubble_grid_image_size"
android:paddingStart="3dp"
android:paddingEnd="3dp"
android:text="@{model.fileName, default=`Lorem_ipsum.pdf`}"
android:textColor="?attr/color_main2_600"
android:textSize="13sp"
android:maxLines="2"
android:ellipsize="middle"
android:gravity="center"
android:background="@drawable/shape_squircle_main2_200"
android:visibility="@{model.isImage || model.isVideoPreview ? View.GONE : View.VISIBLE}"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"/>
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_600"
android:id="@+id/audio_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@{model.audioVideoDuration, default=`00:42`}"
android:textColor="?attr/color_main2_600"
android:textSize="12sp"
android:visibility="@{model.isAudio &amp;&amp; model.audioVideoDuration.length() > 0 ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintBottom_toBottomOf="@id/file_name"
app:layout_constraintStart_toStartOf="@id/file_name"/>
<ImageView <ImageView
android:id="@+id/remove_attachment" android:id="@+id/remove_attachment"

View file

@ -1,67 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<variable
name="model"
type="org.linphone.ui.main.chat.model.FileModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<ImageView
android:id="@+id/file_icon"
android:layout_width="@dimen/chat_bubble_grid_image_size"
android:layout_height="@dimen/chat_bubble_grid_image_size"
android:onClick="@{() -> model.onClick()}"
android:adjustViewBounds="true"
android:padding="18dp"
android:contentDescription="@string/content_description_chat_bubble_file"
android:src="@{model.isWaitingToBeDownloaded ? @drawable/download_simple : model.isPdf ? @drawable/file_pdf : model.isAudio ? @drawable/file_audio : @drawable/file, default=@drawable/file_pdf}"
android:background="@drawable/shape_squircle_file_background_left"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:tint="?attr/color_main2_600" />
<View
android:id="@+id/file_background"
android:onClick="@{() -> model.onClick()}"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/shape_squircle_white_right"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/file_icon"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_700"
android:id="@+id/file_name"
android:onClick="@{() -> model.onClick()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@{model.fileName, default=`Lorem_ipsum.pdf`}"
android:textColor="?attr/color_main2_600"
android:textSize="13sp"
android:maxLines="1"
android:ellipsize="middle"
app:layout_constraintStart_toEndOf="@id/file_icon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:bind="http://schemas.android.com/tools">
<data> <data>
<import type="android.view.View" /> <import type="android.view.View" />
@ -47,37 +48,6 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/> app:layout_constraintBottom_toBottomOf="parent"/>
<ImageView
android:id="@+id/audio_file"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="1dp"
android:adjustViewBounds="true"
android:padding="18dp"
android:background="@drawable/shape_squircle_main2_200"
android:src="@drawable/file_audio"
android:contentDescription="@null"
android:visibility="@{model.isAudio ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:tint="?attr/color_main2_600" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_600"
android:id="@+id/audio_video_duration"
android:onClick="@{() -> model.onClick()}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@{model.audioVideoDuration, default=`00:42`}"
android:textColor="@{model.isVideoPreview ? @color/bc_white : @color/main2_600}"
android:textSize="12sp"
android:visibility="@{(model.isVideoPreview || model.isAudio) &amp;&amp; model.audioVideoDuration.length() > 0 ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<ImageView <ImageView
android:id="@+id/video_preview" android:id="@+id/video_preview"
android:onClick="@{() -> model.onClick()}" android:onClick="@{() -> model.onClick()}"
@ -92,6 +62,86 @@
app:layout_constraintEnd_toEndOf="@id/image" app:layout_constraintEnd_toEndOf="@id/image"
app:tint="@color/bc_white" /> app:tint="@color/bc_white" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/audio_file"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="1dp"
android:visibility="@{model.isAudio ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<View
android:id="@+id/background"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/shape_squircle_file_bubble_background"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<View
android:id="@+id/top_background"
android:layout_width="0dp"
android:layout_height="24dp"
android:background="@drawable/shape_squircle_file_background_top"
app:layout_constraintStart_toStartOf="@id/background"
app:layout_constraintEnd_toEndOf="@id/background"
app:layout_constraintTop_toTopOf="@id/background"
app:layout_constraintBottom_toBottomOf="@id/file_icon"/>
<ImageView
android:id="@+id/file_icon"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:adjustViewBounds="true"
android:padding="2dp"
android:onClick="@{() -> model.onClick()}"
android:contentDescription="@string/content_description_chat_bubble_file"
android:src="@drawable/file_audio"
app:layout_constraintStart_toStartOf="@id/background"
app:layout_constraintEnd_toEndOf="@id/background"
app:layout_constraintTop_toTopOf="@id/top_background"
app:layout_constraintBottom_toBottomOf="@id/top_background"
app:tint="?attr/color_main2_600" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_700"
android:id="@+id/file_name"
android:onClick="@{() -> model.onClick()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:text="@{model.fileName, default=`Lorem_ipsum.pdf`}"
android:textColor="?attr/color_main2_600"
android:textAlignment="center"
android:textSize="12sp"
android:maxLines="2"
android:ellipsize="end"
android:visibility="@{model.transferProgress >= 0 &amp;&amp; model.transferProgress &lt; 100 ? View.INVISIBLE : View.VISIBLE}"
app:layout_constraintStart_toStartOf="@id/background"
app:layout_constraintEnd_toEndOf="@id/background"
app:layout_constraintTop_toBottomOf="@id/file_icon"
app:layout_constraintBottom_toTopOf="@id/audio_duration"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_600"
android:id="@+id/audio_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@{model.audioVideoDuration, default=`00:42`}"
android:textColor="?attr/color_main2_600"
android:textSize="11sp"
android:visibility="@{model.audioVideoDuration.length() > 0 ? View.VISIBLE : View.GONE}"
app:layout_constraintBottom_toBottomOf="@id/background"
app:layout_constraintStart_toStartOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</layout> </layout>

View file

@ -83,9 +83,9 @@
<dimen name="chat_bubble_grouped_bottom_margin">2dp</dimen> <dimen name="chat_bubble_grouped_bottom_margin">2dp</dimen>
<dimen name="chat_bubble_bottom_margin">8dp</dimen> <dimen name="chat_bubble_bottom_margin">8dp</dimen>
<dimen name="chat_bubble_long_press_emoji_reaction_size">25dp</dimen> <dimen name="chat_bubble_long_press_emoji_reaction_size">25dp</dimen>
<dimen name="chat_bubble_grid_image_size">87dp</dimen>
<dimen name="chat_bubble_big_image_max_size">175dp</dimen> <dimen name="chat_bubble_big_image_max_size">175dp</dimen>
<dimen name="chat_bubble_grid_file_width">178dp</dimen> <!-- (87 + 2) * 2 --> <dimen name="chat_bubble_grid_file_size">87dp</dimen>
<dimen name="chat_bubble_single_file_width">178dp</dimen> <!-- (87 + 2) * 2 -->
<dimen name="chat_bubble_meeting_invite_width">271dp</dimen> <dimen name="chat_bubble_meeting_invite_width">271dp</dimen>
<dimen name="chat_bubble_voice_record_width">230dp</dimen> <dimen name="chat_bubble_voice_record_width">230dp</dimen>
<dimen name="chat_bubble_max_reply_width">271dp</dimen> <dimen name="chat_bubble_max_reply_width">271dp</dimen>
@ -94,6 +94,8 @@
<dimen name="chat_conversation_header_avatar_start_margin_if_back_button">5dp</dimen> <dimen name="chat_conversation_header_avatar_start_margin_if_back_button">5dp</dimen>
<dimen name="chat_conversation_header_avatar_start_margin_if_no_back_button">15dp</dimen> <dimen name="chat_conversation_header_avatar_start_margin_if_no_back_button">15dp</dimen>
<dimen name="unread_count_indicator_size">24dp</dimen> <dimen name="unread_count_indicator_size">24dp</dimen>
<dimen name="chat_documents_list_padding_top_bottom">5dp</dimen>
<dimen name="chat_documents_list_padding_start_end">10dp</dimen>
<dimen name="chat_room_emoji_picker_height">300dp</dimen> <dimen name="chat_room_emoji_picker_height">300dp</dimen>
<dimen name="chat_room_participants_list_max_height">300dp</dimen> <dimen name="chat_room_participants_list_max_height">300dp</dimen>