[SES-2512] Rewrite ProfilePictureView (#1622)

This commit is contained in:
Fanchao Liu 2024-08-19 17:32:30 +10:00 committed by GitHub
parent 6701cb1dc1
commit 9919f716a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 251 additions and 391 deletions

View File

@ -27,8 +27,8 @@ import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityWebrtcBinding import network.loki.messenger.databinding.ActivityWebrtcBinding
import org.apache.commons.lang3.time.DurationFormatUtils import org.apache.commons.lang3.time.DurationFormatUtils
import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.truncateIdForDisplay
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.dependencies.DatabaseComponent
@ -194,13 +194,8 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
clipFloatingInsets() clipFloatingInsets()
// set up the user avatar // set up the user avatar
TextSecurePreferences.getLocalNumber(this)?.let{ TextSecurePreferences.getLocalNumber(this)?.let {
val username = TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(it) binding.userAvatar.load(Address.fromSerialized(it))
binding.userAvatar.apply {
publicKey = it
displayName = username
update()
}
} }
} }
@ -332,8 +327,6 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
launch { launch {
viewModel.recipient.collect { latestRecipient -> viewModel.recipient.collect { latestRecipient ->
binding.contactAvatar.recycle()
if (latestRecipient.recipient != null) { if (latestRecipient.recipient != null) {
val contactPublicKey = latestRecipient.recipient.address.serialize() val contactPublicKey = latestRecipient.recipient.address.serialize()
val contactDisplayName = getUserDisplayName(contactPublicKey) val contactDisplayName = getUserDisplayName(contactPublicKey)
@ -341,11 +334,7 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
binding.remoteRecipientName.text = contactDisplayName binding.remoteRecipientName.text = contactDisplayName
// sort out the contact's avatar // sort out the contact's avatar
binding.contactAvatar.apply { binding.contactAvatar.load(latestRecipient.recipient)
publicKey = contactPublicKey
displayName = contactDisplayName
update()
}
} }
} }
} }

View File

@ -3,164 +3,224 @@ package org.thoughtcrime.securesms.components
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.widget.FrameLayout
import android.widget.ImageView import androidx.core.view.isVisible
import android.widget.RelativeLayout import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ViewProfilePictureBinding import network.loki.messenger.databinding.ViewProfilePictureBinding
import org.session.libsession.avatars.ContactColors import org.session.libsession.avatars.ContactColors
import org.session.libsession.avatars.ContactPhoto
import org.session.libsession.avatars.PlaceholderAvatarPhoto import org.session.libsession.avatars.PlaceholderAvatarPhoto
import org.session.libsession.avatars.ProfileContactPhoto
import org.session.libsession.avatars.ResourceContactPhoto import org.session.libsession.avatars.ResourceContactPhoto
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.AppTextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.database.GroupDatabase
import org.thoughtcrime.securesms.dependencies.DatabaseComponent import javax.inject.Inject
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestManager
class ProfilePictureView @JvmOverloads constructor( @AndroidEntryPoint
context: Context, attrs: AttributeSet? = null class ProfilePictureView : FrameLayout {
) : RelativeLayout(context, attrs) { constructor(context: Context) : super(context)
private val TAG = "ProfilePictureView" constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
@Inject
lateinit var groupDatabase: GroupDatabase
private val binding = ViewProfilePictureBinding.inflate(LayoutInflater.from(context), this) private val binding = ViewProfilePictureBinding.inflate(LayoutInflater.from(context), this)
private val glide: RequestManager = Glide.with(this) private var lastLoadJob: Job? = null
private val prefs = AppTextSecurePreferences(context) private var lastLoadAddress: Address? = null
private val userPublicKey = prefs.getLocalNumber()
var publicKey: String? = null
var displayName: String? = null
var additionalPublicKey: String? = null
var additionalDisplayName: String? = null
private val profilePicturesCache = mutableMapOf<View, Recipient>() private val unknownRecipientDrawable by lazy(LazyThreadSafetyMode.NONE) {
private val resourcePadding by lazy { ResourceContactPhoto(R.drawable.ic_profile_default)
context.resources.getDimensionPixelSize(R.dimen.normal_padding).toFloat() .asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false)
}
private val unknownRecipientDrawable by lazy { ResourceContactPhoto(R.drawable.ic_profile_default)
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false, resourcePadding) }
private val unknownOpenGroupDrawable by lazy { ResourceContactPhoto(R.drawable.ic_notification)
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false, resourcePadding) }
constructor(context: Context, sender: Recipient): this(context) {
update(sender)
} }
fun update(recipient: Recipient) { private val unknownOpenGroupDrawable by lazy(LazyThreadSafetyMode.NONE) {
recipient.run { update(address, isClosedGroupRecipient, isOpenGroupInboxRecipient) } ResourceContactPhoto(R.drawable.ic_notification)
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false)
} }
fun update( private fun setShowAsDoubleMode(showAsDouble: Boolean) {
address: Address, binding.doubleModeImageViewContainer.isVisible = showAsDouble
isClosedGroupRecipient: Boolean = false, binding.singleModeImageView.isVisible = !showAsDouble
isOpenGroupInboxRecipient: Boolean = false }
) {
fun getUserDisplayName(publicKey: String): String = prefs.takeIf { userPublicKey == publicKey }?.getProfileName()
?: DatabaseComponent.get(context).sessionContactDatabase().getContactWithAccountID(publicKey)?.displayName(Contact.ContactContext.REGULAR)
?: publicKey
if (isClosedGroupRecipient) { private fun cancelLastLoadJob() {
val members = DatabaseComponent.get(context).groupDatabase() lastLoadJob?.cancel()
.getGroupMemberAddresses(address.toGroupString(), true) lastLoadJob = null
.sorted() }
.take(2)
if (members.size <= 1) { @OptIn(DelicateCoroutinesApi::class)
publicKey = "" private fun loadAsDoubleImages(model: LoadModel) {
displayName = "" cancelLastLoadJob()
additionalPublicKey = ""
additionalDisplayName = "" // The use of GlobalScope is intentional here, as there is no better lifecycle scope that we can use
} else { // to launch a coroutine from a view. The potential memory leak is not a concern here, as
val pk = members.getOrNull(0)?.serialize() ?: "" // the coroutine is very short-lived. If you change the code here to be long live then you'll
publicKey = pk // need to find a better scope to launch the coroutine from.
displayName = getUserDisplayName(pk) lastLoadJob = GlobalScope.launch(Dispatchers.Main) {
val apk = members.getOrNull(1)?.serialize() ?: "" data class GroupMemberInfo(
additionalPublicKey = apk val contactPhoto: ContactPhoto?,
additionalDisplayName = getUserDisplayName(apk) val placeholderAvatarPhoto: PlaceholderAvatarPhoto,
)
// Load group avatar if available, otherwise load member avatars
val groupAvatarOrMemberAvatars = withContext(Dispatchers.Default) {
model.loadRecipient(context).contactPhoto
?: groupDatabase.getGroupMembers(model.address.toGroupString(), true)
.map {
GroupMemberInfo(
contactPhoto = it.contactPhoto,
placeholderAvatarPhoto = PlaceholderAvatarPhoto(
hashString = it.address.serialize(),
displayName = it.displayName()
)
)
}
} }
} else if(isOpenGroupInboxRecipient) {
val publicKey = GroupUtil.getDecodedOpenGroupInboxAccountId(address.serialize())
this.publicKey = publicKey
displayName = getUserDisplayName(publicKey)
additionalPublicKey = null
} else {
val publicKey = address.serialize()
this.publicKey = publicKey
displayName = getUserDisplayName(publicKey)
additionalPublicKey = null
}
update()
}
fun update() { when (groupAvatarOrMemberAvatars) {
val publicKey = publicKey ?: return Log.w(TAG, "Could not find public key to update profile picture") is ContactPhoto -> {
val additionalPublicKey = additionalPublicKey setShowAsDoubleMode(false)
// if we have a multi avatar setup Glide.with(this@ProfilePictureView)
if (additionalPublicKey != null) { .load(groupAvatarOrMemberAvatars)
setProfilePictureIfNeeded(binding.doubleModeImageView1, publicKey, displayName) .error(unknownRecipientDrawable)
setProfilePictureIfNeeded(binding.doubleModeImageView2, additionalPublicKey, additionalDisplayName) .circleCrop()
binding.doubleModeImageViewContainer.visibility = View.VISIBLE .diskCacheStrategy(DiskCacheStrategy.NONE)
.into(binding.singleModeImageView)
}
// clear single image is List<*> -> {
glide.clear(binding.singleModeImageView) val first = groupAvatarOrMemberAvatars.getOrNull(0) as? GroupMemberInfo
binding.singleModeImageView.visibility = View.INVISIBLE val second = groupAvatarOrMemberAvatars.getOrNull(1) as? GroupMemberInfo
} else { // single image mode setShowAsDoubleMode(true)
setProfilePictureIfNeeded(binding.singleModeImageView, publicKey, displayName) Glide.with(binding.doubleModeImageView1)
binding.singleModeImageView.visibility = View.VISIBLE .load(first?.let { it.contactPhoto ?: it.placeholderAvatarPhoto })
.error(first?.placeholderAvatarPhoto ?: unknownRecipientDrawable)
.circleCrop()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(binding.doubleModeImageView1)
// clear multi image Glide.with(binding.doubleModeImageView2)
glide.clear(binding.doubleModeImageView1) .load(second?.let { it.contactPhoto ?: it.placeholderAvatarPhoto })
glide.clear(binding.doubleModeImageView2) .error(second?.placeholderAvatarPhoto ?: unknownRecipientDrawable)
binding.doubleModeImageViewContainer.visibility = View.INVISIBLE .circleCrop()
} .diskCacheStrategy(DiskCacheStrategy.NONE)
.into(binding.doubleModeImageView2)
}
} else -> {
setShowAsDoubleMode(false)
private fun setProfilePictureIfNeeded(imageView: ImageView, publicKey: String, displayName: String?) { binding.singleModeImageView.setImageDrawable(unknownRecipientDrawable)
if (publicKey.isNotEmpty()) { }
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
if (profilePicturesCache[imageView] == recipient) return
profilePicturesCache[imageView] = recipient
val signalProfilePicture = recipient.contactPhoto
val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject
glide.clear(imageView)
val placeholder = PlaceholderAvatarPhoto(publicKey, displayName ?: "${publicKey.take(4)}...${publicKey.takeLast(4)}")
if (signalProfilePicture != null && avatar != "0" && avatar != "") {
glide.load(signalProfilePicture)
.placeholder(unknownRecipientDrawable)
.centerCrop()
.error(glide.load(placeholder))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.circleCrop()
.into(imageView)
} else if (recipient.isCommunityRecipient && recipient.groupAvatarId == null) {
glide.clear(imageView)
glide.load(unknownOpenGroupDrawable)
.centerCrop()
.circleCrop()
.into(imageView)
} else {
glide.load(placeholder)
.placeholder(unknownRecipientDrawable)
.centerCrop()
.circleCrop()
.diskCacheStrategy(DiskCacheStrategy.NONE).circleCrop().into(imageView)
} }
} else {
glide.load(unknownRecipientDrawable)
.centerCrop()
.into(imageView)
} }
} }
fun recycle() { @OptIn(DelicateCoroutinesApi::class)
profilePicturesCache.clear() private fun loadAsSingleImage(model: LoadModel) {
cancelLastLoadJob()
setShowAsDoubleMode(false)
// Only clear the old image if the address has changed. This is important as we have a delay
// in loading the image, if this view is reused for another address before the image is loaded,
// the previous image could be displayed for a short period of time. We would want to avoid
// displaying the wrong image, even for a short time.
// However, if we are displaying the same user's image again, it's ok to show the old
// image until the new one is loaded. This is a trade-off between performance and correctness.
if (lastLoadAddress != model.address) {
Glide.with(this).clear(this)
}
// The use of GlobalScope is intentional here, as there is no better lifecycle scope that we can use
// to launch a coroutine from a view. The potential memory leak is not a concern here, as
// the coroutine is very short-lived. If you change the code here to be long live then you'll
// need to find a better scope to launch the coroutine from.
lastLoadJob = GlobalScope.launch(Dispatchers.Main) {
val (contactPhoto, avatarPlaceholder) = withContext(Dispatchers.Default) {
model.loadRecipient(context).let {
it.contactPhoto to PlaceholderAvatarPhoto(it.address.serialize(), it.displayName())
}
}
val address = model.address
val errorModel: Any = when {
address.isCommunity -> unknownOpenGroupDrawable
address.isContact -> avatarPlaceholder
else -> unknownRecipientDrawable
}
Glide.with(this@ProfilePictureView)
.load(contactPhoto)
.error(errorModel)
.circleCrop()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(binding.singleModeImageView)
}
} }
// endregion
} fun load(recipient: Recipient) {
if (recipient.address.isClosedGroup) {
loadAsDoubleImages(LoadModel.RecipientModel(recipient))
} else {
loadAsSingleImage(LoadModel.RecipientModel(recipient))
}
lastLoadAddress = recipient.address
}
fun load(address: Address) {
if (address.isClosedGroup) {
loadAsDoubleImages(LoadModel.AddressModel(address))
} else {
loadAsSingleImage(LoadModel.AddressModel(address))
}
lastLoadAddress = address
}
private fun Recipient.displayName(): String {
return if (isLocalNumber) {
TextSecurePreferences.getProfileName(context).orEmpty()
} else {
profileName ?: name ?: ""
}
}
private sealed interface LoadModel {
val address: Address
/**
* Load the recipient if it's not already loaded.
*/
fun loadRecipient(context: Context): Recipient
data class AddressModel(override val address: Address) : LoadModel {
override fun loadRecipient(context: Context): Recipient {
return Recipient.from(context, address, false)
}
}
data class RecipientModel(val recipient: Recipient) : LoadModel {
override val address: Address
get() = recipient.address
override fun loadRecipient(context: Context): Recipient = recipient
}
}
}

View File

@ -53,7 +53,7 @@ class UserView : LinearLayout {
return contact?.displayName(Contact.ContactContext.REGULAR) ?: publicKey return contact?.displayName(Contact.ContactContext.REGULAR) ?: publicKey
} }
val address = user.address.serialize() val address = user.address.serialize()
binding.profilePictureView.update(user) binding.profilePictureView.load(user)
binding.actionIndicatorImageView.setImageResource(R.drawable.ic_baseline_edit_24) binding.actionIndicatorImageView.setImageResource(R.drawable.ic_baseline_edit_24)
binding.nameTextView.text = if (user.isGroupRecipient) user.name else getUserDisplayName(address) binding.nameTextView.text = if (user.isGroupRecipient) user.name else getUserDisplayName(address)
when (actionIndicator) { when (actionIndicator) {
@ -85,7 +85,6 @@ class UserView : LinearLayout {
} }
fun unbind() { fun unbind() {
binding.profilePictureView.recycle()
} }
// endregion // endregion
} }

View File

@ -81,7 +81,7 @@ class ConversationActionBarView @JvmOverloads constructor(
} }
fun update(recipient: Recipient, openGroup: OpenGroup? = null, config: ExpirationConfiguration? = null) { fun update(recipient: Recipient, openGroup: OpenGroup? = null, config: ExpirationConfiguration? = null) {
binding.profilePictureView.update(recipient) binding.profilePictureView.load(recipient)
binding.conversationTitleView.text = recipient.takeUnless { it.isLocalNumber }?.toShortString() ?: context.getString(R.string.note_to_self) binding.conversationTitleView.text = recipient.takeUnless { it.isLocalNumber }?.toShortString() ?: context.getString(R.string.note_to_self)
updateSubtitle(recipient, openGroup, config) updateSubtitle(recipient, openGroup, config)

View File

@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import com.bumptech.glide.RequestManager import com.bumptech.glide.RequestManager
import org.thoughtcrime.securesms.database.getLong
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.showSessionDialog
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong

View File

@ -1,87 +0,0 @@
package org.thoughtcrime.securesms.conversation.v2.components
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ListView
import org.session.libsession.messaging.mentions.Mention
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import com.bumptech.glide.RequestManager
import org.thoughtcrime.securesms.util.toPx
class MentionCandidateSelectionView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : ListView(context, attrs, defStyleAttr) {
private var mentionCandidates = listOf<Mention>()
set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.mentionCandidates = newValue }
var glide: RequestManager? = null
set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.glide = newValue }
var openGroupServer: String? = null
set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.openGroupServer = openGroupServer }
var openGroupRoom: String? = null
set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.openGroupRoom = openGroupRoom }
var onMentionCandidateSelected: ((Mention) -> Unit)? = null
private val mentionCandidateSelectionViewAdapter by lazy { Adapter(context) }
private class Adapter(private val context: Context) : BaseAdapter() {
var mentionCandidates = listOf<Mention>()
set(newValue) { field = newValue; notifyDataSetChanged() }
var glide: RequestManager? = null
var openGroupServer: String? = null
var openGroupRoom: String? = null
override fun getCount(): Int {
return mentionCandidates.count()
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItem(position: Int): Mention {
return mentionCandidates[position]
}
override fun getView(position: Int, cellToBeReused: View?, parent: ViewGroup): View {
val cell = cellToBeReused as MentionCandidateView? ?: MentionCandidateView(context)
val mentionCandidate = getItem(position)
cell.glide = glide
cell.mentionCandidate = mentionCandidate
cell.openGroupServer = openGroupServer
cell.openGroupRoom = openGroupRoom
return cell
}
}
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context) : this(context, null)
init {
clipToOutline = true
adapter = mentionCandidateSelectionViewAdapter
mentionCandidateSelectionViewAdapter.mentionCandidates = mentionCandidates
setOnItemClickListener { _, _, position, _ ->
onMentionCandidateSelected?.invoke(mentionCandidates[position])
}
}
fun show(mentionCandidates: List<Mention>, threadID: Long) {
val openGroup = DatabaseComponent.get(context).lokiThreadDatabase().getOpenGroupChat(threadID)
if (openGroup != null) {
openGroupServer = openGroup.server
openGroupRoom = openGroup.room
}
this.mentionCandidates = mentionCandidates
val layoutParams = this.layoutParams as ViewGroup.LayoutParams
layoutParams.height = toPx(Math.min(mentionCandidates.count(), 4) * 44, resources)
this.layoutParams = layoutParams
}
fun hide() {
val layoutParams = this.layoutParams as ViewGroup.LayoutParams
layoutParams.height = 0
this.layoutParams = layoutParams
}
}

View File

@ -1,42 +0,0 @@
package org.thoughtcrime.securesms.conversation.v2.components
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import network.loki.messenger.databinding.ViewMentionCandidateBinding
import org.session.libsession.messaging.mentions.Mention
import org.thoughtcrime.securesms.groups.OpenGroupManager
import com.bumptech.glide.RequestManager
class MentionCandidateView : LinearLayout {
private lateinit var binding: ViewMentionCandidateBinding
var mentionCandidate = Mention("", "")
set(newValue) { field = newValue; update() }
var glide: RequestManager? = null
var openGroupServer: String? = null
var openGroupRoom: String? = null
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() }
private fun initialize() {
binding = ViewMentionCandidateBinding.inflate(LayoutInflater.from(context), this, true)
}
private fun update() = with(binding) {
mentionCandidateNameTextView.text = mentionCandidate.displayName
profilePictureView.publicKey = mentionCandidate.publicKey
profilePictureView.displayName = mentionCandidate.displayName
profilePictureView.additionalPublicKey = null
profilePictureView.update()
if (openGroupServer != null && openGroupRoom != null) {
val isUserModerator = OpenGroupManager.isUserModerator(context, "$openGroupRoom.$openGroupServer", mentionCandidate.publicKey)
moderatorIconImageView.visibility = if (isUserModerator) View.VISIBLE else View.GONE
} else {
moderatorIconImageView.visibility = View.GONE
}
}
}

View File

@ -2,13 +2,11 @@ package org.thoughtcrime.securesms.conversation.v2.input_bar.mentions
import android.view.View import android.view.View
import network.loki.messenger.databinding.ViewMentionCandidateV2Binding import network.loki.messenger.databinding.ViewMentionCandidateV2Binding
import org.session.libsession.utilities.Address
import org.thoughtcrime.securesms.conversation.v2.mention.MentionViewModel import org.thoughtcrime.securesms.conversation.v2.mention.MentionViewModel
fun ViewMentionCandidateV2Binding.update(candidate: MentionViewModel.Candidate) { fun ViewMentionCandidateV2Binding.update(candidate: MentionViewModel.Candidate) {
mentionCandidateNameTextView.text = candidate.nameHighlighted mentionCandidateNameTextView.text = candidate.nameHighlighted
profilePictureView.publicKey = candidate.member.publicKey profilePictureView.load(Address.fromSerialized(candidate.member.publicKey))
profilePictureView.displayName = candidate.member.name
profilePictureView.additionalPublicKey = null
profilePictureView.update()
moderatorIconImageView.visibility = if (candidate.member.isModerator) View.VISIBLE else View.GONE moderatorIconImageView.visibility = if (candidate.member.isModerator) View.VISIBLE else View.GONE
} }

View File

@ -16,7 +16,6 @@ import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
@ -26,6 +25,8 @@ import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.marginBottom import androidx.core.view.marginBottom
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ViewEmojiReactionsBinding import network.loki.messenger.databinding.ViewEmojiReactionsBinding
@ -52,8 +53,6 @@ import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.groups.OpenGroupManager
import org.thoughtcrime.securesms.home.UserDetailsBottomSheet import org.thoughtcrime.securesms.home.UserDetailsBottomSheet
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestManager
import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.disableClipping import org.thoughtcrime.securesms.util.disableClipping
import org.thoughtcrime.securesms.util.toDp import org.thoughtcrime.securesms.util.toDp
@ -179,8 +178,7 @@ class VisibleMessageView : FrameLayout {
if (isGroupThread && !message.isOutgoing) { if (isGroupThread && !message.isOutgoing) {
if (isEndOfMessageCluster) { if (isEndOfMessageCluster) {
binding.profilePictureView.publicKey = senderAccountID binding.profilePictureView.load(message.individualRecipient)
binding.profilePictureView.update(message.individualRecipient)
binding.profilePictureView.setOnClickListener { binding.profilePictureView.setOnClickListener {
if (thread.isCommunityRecipient) { if (thread.isCommunityRecipient) {
val openGroup = lokiThreadDb.getOpenGroupChat(threadID) val openGroup = lokiThreadDb.getOpenGroupChat(threadID)
@ -456,7 +454,6 @@ class VisibleMessageView : FrameLayout {
} }
fun recycle() { fun recycle() {
binding.profilePictureView.recycle()
binding.messageContentView.root.recycle() binding.messageContentView.root.recycle()
} }

View File

@ -13,14 +13,14 @@ import org.session.libsession.avatars.ContactPhoto;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
class ContactPhotoFetcher implements DataFetcher<InputStream> { public class ContactPhotoFetcher implements DataFetcher<InputStream> {
private final Context context; private final Context context;
private final ContactPhoto contactPhoto; private final ContactPhoto contactPhoto;
private InputStream inputStream; private InputStream inputStream;
ContactPhotoFetcher(@NonNull Context context, @NonNull ContactPhoto contactPhoto) { public ContactPhotoFetcher(@NonNull Context context, @NonNull ContactPhoto contactPhoto) {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.contactPhoto = contactPhoto; this.contactPhoto = contactPhoto;
} }

View File

@ -9,12 +9,15 @@ import org.session.libsession.avatars.PlaceholderAvatarPhoto
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator
class PlaceholderAvatarFetcher(private val context: Context, class PlaceholderAvatarFetcher(
private val photo: PlaceholderAvatarPhoto): DataFetcher<BitmapDrawable> { private val context: Context,
private val hashString: String,
private val displayName: String
): DataFetcher<BitmapDrawable> {
override fun loadData(priority: Priority,callback: DataFetcher.DataCallback<in BitmapDrawable>) { override fun loadData(priority: Priority,callback: DataFetcher.DataCallback<in BitmapDrawable>) {
try { try {
val avatar = AvatarPlaceholderGenerator.generate(context, 128, photo.hashString, photo.displayName) val avatar = AvatarPlaceholderGenerator.generate(context, 128, hashString, displayName)
callback.onDataReady(avatar) callback.onDataReady(avatar)
} catch (e: Exception) { } catch (e: Exception) {
Log.e("Loki", "Error in fetching avatar") Log.e("Loki", "Error in fetching avatar")

View File

@ -8,6 +8,9 @@ import com.bumptech.glide.load.model.ModelLoader.LoadData
import com.bumptech.glide.load.model.ModelLoaderFactory import com.bumptech.glide.load.model.ModelLoaderFactory
import com.bumptech.glide.load.model.MultiModelLoaderFactory import com.bumptech.glide.load.model.MultiModelLoaderFactory
import org.session.libsession.avatars.PlaceholderAvatarPhoto import org.session.libsession.avatars.PlaceholderAvatarPhoto
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient
class PlaceholderAvatarLoader(private val appContext: Context): ModelLoader<PlaceholderAvatarPhoto, BitmapDrawable> { class PlaceholderAvatarLoader(private val appContext: Context): ModelLoader<PlaceholderAvatarPhoto, BitmapDrawable> {
@ -17,7 +20,15 @@ class PlaceholderAvatarLoader(private val appContext: Context): ModelLoader<Plac
height: Int, height: Int,
options: Options options: Options
): LoadData<BitmapDrawable> { ): LoadData<BitmapDrawable> {
return LoadData(model, PlaceholderAvatarFetcher(appContext, model)) val displayName: String = when {
!model.displayName.isNullOrBlank() -> model.displayName.orEmpty()
model.hashString == TextSecurePreferences.getLocalNumber(appContext) -> TextSecurePreferences.getProfileName(appContext).orEmpty()
else -> Recipient.from(appContext, Address.fromSerialized(model.hashString), false).let {
it.profileName ?: it.name ?: ""
}
}
return LoadData(model, PlaceholderAvatarFetcher(appContext, model.hashString, displayName))
} }
override fun handles(model: PlaceholderAvatarPhoto): Boolean = true override fun handles(model: PlaceholderAvatarPhoto): Boolean = true

View File

@ -4,7 +4,6 @@ import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.text.TextUtils
import android.util.AttributeSet import android.util.AttributeSet
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
@ -128,11 +127,10 @@ class ConversationView : LinearLayout {
thread.isRead -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_filled_circle_check) thread.isRead -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_filled_circle_check)
else -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_circle_check) else -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_circle_check)
} }
binding.profilePictureView.update(thread.recipient) binding.profilePictureView.load(thread.recipient)
} }
fun recycle() { fun recycle() {
binding.profilePictureView.recycle()
} }
private fun getTitle(recipient: Recipient): String? = when { private fun getTitle(recipient: Recipient): String? = when {

View File

@ -347,8 +347,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
ApplicationContext.getInstance(this).messageNotifier.setHomeScreenVisible(true) ApplicationContext.getInstance(this).messageNotifier.setHomeScreenVisible(true)
if (textSecurePreferences.getLocalNumber() == null) { return; } // This can be the case after a secondary device is auto-cleared if (textSecurePreferences.getLocalNumber() == null) { return; } // This can be the case after a secondary device is auto-cleared
IdentityKeyUtil.checkUpdate(this) IdentityKeyUtil.checkUpdate(this)
binding.profileButton.recycle() // clear cached image before update tje profilePictureView binding.profileButton.load(Address.fromSerialized(publicKey))
binding.profileButton.update()
if (textSecurePreferences.getHasViewedSeed()) { if (textSecurePreferences.getHasViewedSeed()) {
binding.seedReminderView.isVisible = false binding.seedReminderView.isVisible = false
} }
@ -388,10 +387,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
} }
private fun updateProfileButton() { private fun updateProfileButton() {
binding.profileButton.publicKey = publicKey binding.profileButton.load(Address.fromSerialized(publicKey))
binding.profileButton.displayName = textSecurePreferences.getProfileName()
binding.profileButton.recycle()
binding.profileButton.update()
} }
// endregion // endregion

View File

@ -55,8 +55,7 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() {
val recipient = Recipient.from(requireContext(), Address.fromSerialized(publicKey), false) val recipient = Recipient.from(requireContext(), Address.fromSerialized(publicKey), false)
val threadRecipient = threadDb.getRecipientForThreadId(threadID) ?: return dismiss() val threadRecipient = threadDb.getRecipientForThreadId(threadID) ?: return dismiss()
with(binding) { with(binding) {
profilePictureView.publicKey = publicKey profilePictureView.load(recipient)
profilePictureView.update(recipient)
nameTextViewContainer.visibility = View.VISIBLE nameTextViewContainer.visibility = View.VISIBLE
nameTextViewContainer.setOnClickListener { nameTextViewContainer.setOnClickListener {
if (recipient.isOpenGroupInboxRecipient || recipient.isOpenGroupOutboxRecipient) return@setOnClickListener if (recipient.isOpenGroupInboxRecipient || recipient.isOpenGroupOutboxRecipient) return@setOnClickListener

View File

@ -11,7 +11,6 @@ import network.loki.messenger.databinding.ViewGlobalSearchHeaderBinding
import network.loki.messenger.databinding.ViewGlobalSearchResultBinding import network.loki.messenger.databinding.ViewGlobalSearchResultBinding
import network.loki.messenger.databinding.ViewGlobalSearchSubheaderBinding import network.loki.messenger.databinding.ViewGlobalSearchSubheaderBinding
import org.session.libsession.utilities.GroupRecord import org.session.libsession.utilities.GroupRecord
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.search.model.MessageResult import org.thoughtcrime.securesms.search.model.MessageResult
import org.thoughtcrime.securesms.ui.GetString import org.thoughtcrime.securesms.ui.GetString
import java.security.InvalidParameterException import java.security.InvalidParameterException
@ -99,12 +98,6 @@ class GlobalSearchAdapter(private val modelCallback: (Model)->Unit): RecyclerVie
} }
} }
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
if (holder is ContentView) {
holder.binding.searchResultProfilePicture.recycle()
}
}
class ContentView(view: View, private val modelCallback: (Model) -> Unit) : RecyclerView.ViewHolder(view) { class ContentView(view: View, private val modelCallback: (Model) -> Unit) : RecyclerView.ViewHolder(view) {
val binding = ViewGlobalSearchResultBinding.bind(view) val binding = ViewGlobalSearchResultBinding.bind(view)
@ -114,7 +107,6 @@ class GlobalSearchAdapter(private val modelCallback: (Model)->Unit): RecyclerVie
} }
fun bind(query: String, model: Model) { fun bind(query: String, model: Model) {
binding.searchResultProfilePicture.recycle()
when (model) { when (model) {
is Model.GroupConversation -> bindModel(query, model) is Model.GroupConversation -> bindModel(query, model)
is Model.Contact -> bindModel(query, model) is Model.Contact -> bindModel(query, model)

View File

@ -93,7 +93,7 @@ fun ContentView.bindModel(query: String?, model: GroupConversation) {
binding.searchResultSubtitle.isVisible = model.groupRecord.isClosedGroup binding.searchResultSubtitle.isVisible = model.groupRecord.isClosedGroup
binding.searchResultTimestamp.isVisible = false binding.searchResultTimestamp.isVisible = false
val threadRecipient = Recipient.from(binding.root.context, Address.fromSerialized(model.groupRecord.encodedId), false) val threadRecipient = Recipient.from(binding.root.context, Address.fromSerialized(model.groupRecord.encodedId), false)
binding.searchResultProfilePicture.update(threadRecipient) binding.searchResultProfilePicture.load(threadRecipient)
val nameString = model.groupRecord.title val nameString = model.groupRecord.title
binding.searchResultTitle.text = getHighlight(query, nameString) binding.searchResultTitle.text = getHighlight(query, nameString)
@ -111,7 +111,7 @@ fun ContentView.bindModel(query: String?, model: ContactModel) = binding.run {
searchResultTimestamp.isVisible = false searchResultTimestamp.isVisible = false
searchResultSubtitle.text = null searchResultSubtitle.text = null
val recipient = Recipient.from(root.context, Address.fromSerialized(model.contact.accountID), false) val recipient = Recipient.from(root.context, Address.fromSerialized(model.contact.accountID), false)
searchResultProfilePicture.update(recipient) searchResultProfilePicture.load(recipient)
val nameString = if (model.isSelf) root.context.getString(R.string.note_to_self) val nameString = if (model.isSelf) root.context.getString(R.string.note_to_self)
else model.contact.getSearchName() else model.contact.getSearchName()
searchResultTitle.text = getHighlight(query, nameString) searchResultTitle.text = getHighlight(query, nameString)
@ -121,7 +121,7 @@ fun ContentView.bindModel(model: SavedMessages) {
binding.searchResultSubtitle.isVisible = false binding.searchResultSubtitle.isVisible = false
binding.searchResultTimestamp.isVisible = false binding.searchResultTimestamp.isVisible = false
binding.searchResultTitle.setText(R.string.note_to_self) binding.searchResultTitle.setText(R.string.note_to_self)
binding.searchResultProfilePicture.update(Address.fromSerialized(model.currentUserPublicKey)) binding.searchResultProfilePicture.load(Address.fromSerialized(model.currentUserPublicKey))
binding.searchResultProfilePicture.isVisible = true binding.searchResultProfilePicture.isVisible = true
} }
@ -134,7 +134,7 @@ fun ContentView.bindModel(query: String?, model: Message) = binding.apply {
// unreadCountTextView.text = model.unread.toString() // unreadCountTextView.text = model.unread.toString()
// } // }
searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(root.context, Locale.getDefault(), model.messageResult.sentTimestampMs) searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(root.context, Locale.getDefault(), model.messageResult.sentTimestampMs)
searchResultProfilePicture.update(model.messageResult.conversationRecipient) searchResultProfilePicture.load(model.messageResult.conversationRecipient)
val textSpannable = SpannableStringBuilder() val textSpannable = SpannableStringBuilder()
if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) { if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) {
// group chat, bind // group chat, bind

View File

@ -49,12 +49,11 @@ class MessageRequestView : LinearLayout {
binding.snippetTextView.text = snippet binding.snippetTextView.text = snippet
post { post {
binding.profilePictureView.update(thread.recipient) binding.profilePictureView.load(thread.recipient)
} }
} }
fun recycle() { fun recycle() {
binding.profilePictureView.recycle()
} }
private fun getUserDisplayName(recipient: Recipient): String? { private fun getUserDisplayName(recipient: Recipient): String? {

View File

@ -56,6 +56,7 @@ public class SignalGlideModule extends AppGlideModule {
// builder.setDiskCache(new NoopDiskCacheFactory()); // builder.setDiskCache(new NoopDiskCacheFactory());
} }
/** @noinspection unchecked*/
@Override @Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) { public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(); AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
@ -74,6 +75,7 @@ public class SignalGlideModule extends AppGlideModule {
registry.append(AttachmentModel.class, InputStream.class, new AttachmentStreamUriLoader.Factory()); registry.append(AttachmentModel.class, InputStream.class, new AttachmentStreamUriLoader.Factory());
registry.append(ChunkedImageUrl.class, InputStream.class, new ChunkedImageUrlLoader.Factory()); registry.append(ChunkedImageUrl.class, InputStream.class, new ChunkedImageUrlLoader.Factory());
registry.append(PlaceholderAvatarPhoto.class, BitmapDrawable.class, new PlaceholderAvatarLoader.Factory(context)); registry.append(PlaceholderAvatarPhoto.class, BitmapDrawable.class, new PlaceholderAvatarLoader.Factory(context));
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory()); registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
} }

View File

@ -36,11 +36,6 @@ class BlockedContactsAdapter(val viewModel: BlockedContactsViewModel) : ListAdap
else holder.select(getItem(position).isSelected) else holder.select(getItem(position).isSelected)
} }
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
holder.binding.profilePictureView.recycle()
}
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
val glide = Glide.with(itemView) val glide = Glide.with(itemView)
@ -48,9 +43,7 @@ class BlockedContactsAdapter(val viewModel: BlockedContactsViewModel) : ListAdap
fun bind(selectable: SelectableRecipient, toggle: (SelectableRecipient) -> Unit) { fun bind(selectable: SelectableRecipient, toggle: (SelectableRecipient) -> Unit) {
binding.recipientName.text = selectable.item.name binding.recipientName.text = selectable.item.name
with (binding.profilePictureView) { binding.profilePictureView.load(selectable.item)
update(selectable.item)
}
binding.root.setOnClickListener { toggle(selectable) } binding.root.setOnClickListener { toggle(selectable) }
binding.selectButton.isSelected = selectable.isSelected binding.selectButton.isSelected = selectable.isSelected
} }

View File

@ -168,7 +168,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
super.onStart() super.onStart()
binding.run { binding.run {
setupProfilePictureView(profilePictureView) loadProfilePicture(profilePictureView)
profilePictureView.setOnClickListener { showEditProfilePictureUI() } profilePictureView.setOnClickListener { showEditProfilePictureUI() }
ctnGroupNameSection.setOnClickListener { startActionMode(DisplayNameEditActionModeCallback()) } ctnGroupNameSection.setOnClickListener { startActionMode(DisplayNameEditActionModeCallback()) }
btnGroupNameDisplay.text = getDisplayName() btnGroupNameDisplay.text = getDisplayName()
@ -185,12 +185,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
private fun getDisplayName(): String = private fun getDisplayName(): String =
TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(hexEncodedPublicKey) TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(hexEncodedPublicKey)
private fun setupProfilePictureView(view: ProfilePictureView) { private fun loadProfilePicture(view: ProfilePictureView) {
view.apply { // Always reload the profile picture as it can change on this page.
publicKey = hexEncodedPublicKey view.load(Address.fromSerialized(hexEncodedPublicKey))
displayName = getDisplayName()
update()
}
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
@ -331,9 +328,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@SettingsActivity) ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@SettingsActivity)
// Update our visuals loadProfilePicture(binding.profilePictureView)
binding.profilePictureView.recycle()
binding.profilePictureView.update()
} }
// If the sync failed then inform the user // If the sync failed then inform the user
@ -408,7 +403,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
cancelButton() cancelButton()
}.apply { }.apply {
val profilePic = findViewById<ProfilePictureView>(R.id.profile_picture_view) val profilePic = findViewById<ProfilePictureView>(R.id.profile_picture_view)
?.also(::setupProfilePictureView) ?.also(::loadProfilePicture)
val pictureIcon = findViewById<View>(R.id.ic_pictures) val pictureIcon = findViewById<View>(R.id.ic_pictures)

View File

@ -13,7 +13,6 @@ import org.session.libsession.messaging.utilities.AccountId;
import org.thoughtcrime.securesms.components.ProfilePictureView; import org.thoughtcrime.securesms.components.ProfilePictureView;
import org.thoughtcrime.securesms.components.emoji.EmojiImageView; import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
import org.thoughtcrime.securesms.database.model.MessageId; import org.thoughtcrime.securesms.database.model.MessageId;
import com.bumptech.glide.Glide;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -154,7 +153,7 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecip
callback.onRemoveReaction(reaction.getBaseEmoji(), messageId, reaction.getTimestamp()); callback.onRemoveReaction(reaction.getBaseEmoji(), messageId, reaction.getTimestamp());
}); });
this.avatar.update(reaction.getSender()); this.avatar.load(reaction.getSender());
if (reaction.getSender().isLocalNumber()) { if (reaction.getSender().isLocalNumber()) {
this.recipient.setText(R.string.ReactionsRecipientAdapter_you); this.recipient.setText(R.string.ReactionsRecipientAdapter_you);
@ -170,7 +169,6 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecip
} }
void unbind() { void unbind() {
avatar.recycle();
} }
} }

View File

@ -11,7 +11,6 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@ -361,7 +360,7 @@ fun RowScope.Avatar(recipient: Recipient) {
) { ) {
AndroidView( AndroidView(
factory = { factory = {
ProfilePictureView(it).apply { update(recipient) } ProfilePictureView(it).apply { load(recipient) }
}, },
modifier = Modifier modifier = Modifier
.width(46.dp) .width(46.dp)

View File

@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.conversation.v2.components.MentionCandidateView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="44dp"
android:orientation="horizontal"
android:paddingStart="@dimen/medium_spacing"
android:paddingEnd="@dimen/medium_spacing"
android:gravity="center_vertical"
android:background="@drawable/mention_candidate_view_background">
<RelativeLayout
android:layout_width="26dp"
android:layout_height="32dp">
<org.thoughtcrime.securesms.components.ProfilePictureView
android:id="@+id/profilePictureView"
android:layout_width="@dimen/very_small_profile_picture_size"
android:layout_height="@dimen/very_small_profile_picture_size"
android:layout_marginTop="3dp" />
<ImageView
android:id="@+id/moderatorIconImageView"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_crown"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true" />
</RelativeLayout>
<TextView
android:id="@+id/mentionCandidateNameTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/medium_spacing"
android:textSize="@dimen/small_font_size"
android:textColor="?android:textColorPrimary"
android:maxLines="1"
android:ellipsize="end" />
</org.thoughtcrime.securesms.conversation.v2.components.MentionCandidateView>

View File

@ -3,10 +3,12 @@ package org.session.libsession.avatars
import com.bumptech.glide.load.Key import com.bumptech.glide.load.Key
import java.security.MessageDigest import java.security.MessageDigest
class PlaceholderAvatarPhoto(val hashString: String, data class PlaceholderAvatarPhoto(
val displayName: String): Key { val hashString: String,
val displayName: String?
) : Key {
override fun updateDiskCacheKey(messageDigest: MessageDigest) { override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(hashString.encodeToByteArray()) messageDigest.update(hashString.encodeToByteArray())
messageDigest.update(displayName.encodeToByteArray()) messageDigest.update(displayName?.encodeToByteArray() ?: byteArrayOf())
} }
} }