Updated ImageUtils.getBitmapFromMultipleAvatars() to make it synchronous, fixing avatar in conversations list when quickly scrolling + shortcuts for group conversation

This commit is contained in:
Sylvain Berfini 2023-12-18 15:41:58 +01:00
parent f702054ac4
commit 03ee116ed4
8 changed files with 79 additions and 36 deletions

View file

@ -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<EventLogModel, RecyclerView.ViewHol
model = message
executePendingBindings()
binding.deliveryStatus.startAnimatedDrawable()
binding.root.doOnPreDraw {
binding.deliveryStatus.startAnimatedDrawable()
}
}
}
}
@ -193,7 +196,9 @@ class ConversationEventAdapter : ListAdapter<EventLogModel, RecyclerView.ViewHol
model = message
executePendingBindings()
binding.deliveryStatus.startAnimatedDrawable()
binding.root.doOnPreDraw {
binding.deliveryStatus.startAnimatedDrawable()
}
}
}
}

View file

@ -3,6 +3,7 @@ package org.linphone.ui.main.chat.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.core.view.doOnPreDraw
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.findViewTreeLifecycleOwner
@ -74,7 +75,9 @@ class ConversationsListAdapter : ListAdapter<ConversationModel, RecyclerView.Vie
executePendingBindings()
binding.lastSentMessageStatus.startAnimatedDrawable()
binding.root.doOnPreDraw {
binding.lastSentMessageStatus.startAnimatedDrawable()
}
}
}
}

View file

@ -60,12 +60,12 @@ class ConversationModel @WorkerThread constructor(val chatRoom: ChatRoom) {
val isComposing = MutableLiveData<Boolean>()
val composingLabel = MutableLiveData<String>()
val isMuted = MutableLiveData<Boolean>()
val isEphemeral = MutableLiveData<Boolean>()
val composingLabel = MutableLiveData<Boolean>()
val lastMessageText = MutableLiveData<String>()
val lastMessageIcon = MutableLiveData<Int>()
@ -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<String>()
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("")
}
}
}

View file

@ -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)

View file

@ -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) {

View file

@ -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<String>): Bitmap {
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
fun getBitmapFromMultipleAvatars(context: Context, size: Int, images: List<String>): 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

View file

@ -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<String>()
val list = arrayListOf<String>()
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<Person>(personsList.size)

View file

@ -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"