From 03ee116ed4ce3d0ca22d08f702ad18187e1a0e93 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 18 Dec 2023 15:41:58 +0100 Subject: [PATCH] Updated ImageUtils.getBitmapFromMultipleAvatars() to make it synchronous, fixing avatar in conversations list when quickly scrolling + shortcuts for group conversation --- .../chat/adapter/ConversationEventAdapter.kt | 9 +++-- .../chat/adapter/ConversationsListAdapter.kt | 5 ++- .../ui/main/chat/model/ConversationModel.kt | 32 +++++++++++++++-- .../org/linphone/utils/DataBindingUtils.kt | 8 ++--- .../main/java/org/linphone/utils/FileUtils.kt | 19 ++++++++--- .../java/org/linphone/utils/ImageUtils.kt | 34 +++++++++++-------- .../java/org/linphone/utils/ShortcutUtils.kt | 6 ++-- app/src/main/res/layout/chat_list_cell.xml | 2 +- 8 files changed, 79 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt b/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt index efa70fbc9..5f8e13472 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt @@ -21,6 +21,7 @@ package org.linphone.ui.main.chat.adapter import android.view.LayoutInflater import android.view.ViewGroup +import androidx.core.view.doOnPreDraw import androidx.databinding.DataBindingUtil import androidx.lifecycle.MutableLiveData import androidx.lifecycle.findViewTreeLifecycleOwner @@ -180,7 +181,9 @@ class ConversationEventAdapter : ListAdapter() + val composingLabel = MutableLiveData() + val isMuted = MutableLiveData() val isEphemeral = MutableLiveData() - val composingLabel = MutableLiveData() - val lastMessageText = MutableLiveData() val lastMessageIcon = MutableLiveData() @@ -341,6 +341,32 @@ class ConversationModel @WorkerThread constructor(val chatRoom: ChatRoom) { @WorkerThread private fun computeComposingLabel() { - // TODO + val composing = chatRoom.isRemoteComposing + isComposing.postValue(composing) + if (!composing) { + composingLabel.postValue("") + return + } + + val composingFriends = arrayListOf() + var label = "" + for (address in chatRoom.composingAddresses) { + val avatar = coreContext.contactsManager.getContactAvatarModelForAddress(address) + val name = avatar.name.value ?: LinphoneUtils.getDisplayName(address) + composingFriends.add(name) + label += "$name, " + } + if (composingFriends.size > 0) { + label = label.dropLast(2) + + val format = AppUtils.getStringWithPlural( + R.plurals.conversation_composing_label, + composingFriends.size, + label + ) + composingLabel.postValue(format) + } else { + composingLabel.postValue("") + } } } diff --git a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt index caea2b615..70ce3e396 100644 --- a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt +++ b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt @@ -48,7 +48,6 @@ import androidx.databinding.ViewDataBinding import androidx.emoji2.emojipicker.EmojiPickerView import androidx.emoji2.emojipicker.EmojiViewItem import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.lifecycleScope import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import coil.dispose import coil.load @@ -56,7 +55,6 @@ import coil.request.videoFrameMillis import coil.size.Dimension import coil.transform.RoundedCornersTransformation import com.google.android.material.imageview.ShapeableImageView -import kotlinx.coroutines.launch import org.linphone.BR import org.linphone.R import org.linphone.contacts.AbstractAvatarModel @@ -412,10 +410,8 @@ private fun loadContactPictureWithCoil( } else { AppUtils.getDimension(R.dimen.avatar_list_cell_size).toInt() } - (context as AppCompatActivity).lifecycleScope.launch { - val bitmap = ImageUtils.getBitmapFromMultipleAvatars(imageView.context, w, images) - imageView.load(bitmap) - } + val bitmap = ImageUtils.getBitmapFromMultipleAvatars(imageView.context, w, images) + imageView.load(bitmap) } } else { imageView.load(R.drawable.smiley) diff --git a/app/src/main/java/org/linphone/utils/FileUtils.kt b/app/src/main/java/org/linphone/utils/FileUtils.kt index a71665c95..715ab2c35 100644 --- a/app/src/main/java/org/linphone/utils/FileUtils.kt +++ b/app/src/main/java/org/linphone/utils/FileUtils.kt @@ -207,13 +207,19 @@ class FileUtils { return contentUri } - suspend fun getFilePath(context: Context, uri: Uri, overrideExisting: Boolean): String? { + suspend fun getFilePath( + context: Context, + uri: Uri, + overrideExisting: Boolean, + copyToCache: Boolean = false + ): String? { return withContext(Dispatchers.IO) { - val name: String = getNameFromUri(uri, context) try { + val path = uri.path + if (path.isNullOrEmpty()) return@withContext null if (Os.fstat( ParcelFileDescriptor.open( - File(uri.path), + File(path), ParcelFileDescriptor.MODE_READ_ONLY ).fileDescriptor ).st_uid != Process.myUid() @@ -225,12 +231,17 @@ class FileUtils { Log.e("$TAG Can't check file ownership: ", e) } + val name: String = getNameFromUri(uri, context) val extension = getExtensionFromFileName(name) val type = getMimeTypeFromExtension(extension) val isImage = getMimeType(type) == MimeType.Image try { - val localFile: File = getFileStoragePath(name, isImage, overrideExisting) + val localFile: File = if (copyToCache) { + getFileStorageCacheDir(name, overrideExisting) + } else { + getFileStoragePath(name, isImage, overrideExisting) + } copyFile(uri, localFile) return@withContext localFile.absolutePath } catch (e: Exception) { diff --git a/app/src/main/java/org/linphone/utils/ImageUtils.kt b/app/src/main/java/org/linphone/utils/ImageUtils.kt index 7a0c630b1..45b499e6c 100644 --- a/app/src/main/java/org/linphone/utils/ImageUtils.kt +++ b/app/src/main/java/org/linphone/utils/ImageUtils.kt @@ -21,6 +21,7 @@ package org.linphone.utils import android.content.Context import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.graphics.Canvas import android.graphics.ImageDecoder import android.graphics.Rect @@ -28,9 +29,6 @@ import android.graphics.drawable.BitmapDrawable import android.net.Uri import androidx.annotation.AnyThread import androidx.annotation.WorkerThread -import androidx.core.graphics.drawable.toBitmap -import coil.imageLoader -import coil.request.ImageRequest import java.io.FileNotFoundException import org.linphone.contacts.AvatarGenerator import org.linphone.core.tools.Log @@ -39,7 +37,7 @@ class ImageUtils { companion object { private const val TAG = "[Image Utils]" - @WorkerThread + @AnyThread fun getGeneratedAvatar(context: Context, size: Int = 0, textSize: Int = 0, initials: String): BitmapDrawable { val builder = AvatarGenerator(context) builder.setInitials(initials) @@ -89,17 +87,20 @@ class ImageUtils { } @AnyThread - suspend fun getBitmapFromMultipleAvatars(context: Context, size: Int, images: List): Bitmap { - val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888) - val canvas = Canvas(bitmap) - + fun getBitmapFromMultipleAvatars(context: Context, size: Int, images: List): Bitmap { val drawables = images.mapNotNull { - val request = ImageRequest.Builder(context) - .data(it) - .size(size / 2) - .allowHardware(false) - .build() - context.imageLoader.execute(request).drawable + try { + val uri = Uri.parse(it) + val stream = context.contentResolver.openInputStream(uri) + val bm = BitmapFactory.decodeStream(stream) + if (bm != null) { + Bitmap.createScaledBitmap(bm, size, size, false) + } else { + null + } + } catch (e: Exception) { + null + } } val rectangles = if (drawables.size == 2) { @@ -124,6 +125,9 @@ class ImageUtils { arrayListOf(Rect(0, 0, size, size)) } + val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + for (i in 0 until rectangles.size) { val src = if (drawables.size == 3 && i == 2) { // To prevent deformation for the bottom image when merging 3 of them @@ -139,7 +143,7 @@ class ImageUtils { try { canvas.drawBitmap( - drawables[i].toBitmap(size, size, Bitmap.Config.ARGB_8888), + drawables[i]/*.toBitmap(size, size, Bitmap.Config.ARGB_8888)*/, src, rectangles[i], null diff --git a/app/src/main/java/org/linphone/utils/ShortcutUtils.kt b/app/src/main/java/org/linphone/utils/ShortcutUtils.kt index 204d4f13c..559761925 100644 --- a/app/src/main/java/org/linphone/utils/ShortcutUtils.kt +++ b/app/src/main/java/org/linphone/utils/ShortcutUtils.kt @@ -145,9 +145,7 @@ class ShortcutUtils { ).buildIcon() } else { subject = chatRoom.subject.orEmpty() - IconCompat.createWithResource(context, R.drawable.users_three) - // TODO FIXME: use generated chat room avatar - /*val list = arrayListOf() + val list = arrayListOf() for (participant in chatRoom.participants) { val contact = coreContext.contactsManager.findContactByAddress(participant.address) @@ -169,7 +167,7 @@ class ShortcutUtils { ) } else { AvatarGenerator(context).setInitials(subject).buildIcon() - }*/ + } } val persons = arrayOfNulls(personsList.size) diff --git a/app/src/main/res/layout/chat_list_cell.xml b/app/src/main/res/layout/chat_list_cell.xml index 40b869544..e7a751fb1 100644 --- a/app/src/main/res/layout/chat_list_cell.xml +++ b/app/src/main/res/layout/chat_list_cell.xml @@ -120,7 +120,7 @@ android:text="@{model.isBeingDeleted ? @string/conversations_list_is_being_removed_label : model.isComposing ? model.composingLabel : model.lastMessageText, default=`Hello there!`}" android:textSize="14sp" android:textColor="?attr/color_main2_400" - android:textStyle="@{model.isBeingDeleted || model.unreadMessageCount > 0 ? Typeface.BOLD : Typeface.NORMAL}" + android:textStyle="@{model.isBeingDeleted || model.unreadMessageCount > 0 || model.isComposing ? Typeface.BOLD : Typeface.NORMAL}" app:layout_constraintStart_toStartOf="@id/name" app:layout_constraintEnd_toStartOf="@id/right_border" app:layout_constraintTop_toBottomOf="@id/name"