Revert "[SES-2512] Rewrite ProfilePictureView (#1622)" (#1629)

This reverts commit 9919f716a7.

Co-authored-by: fanchao <git@fanchao.dev>
This commit is contained in:
Fanchao Liu 2024-08-21 09:30:02 +10:00 committed by GitHub
parent 9919f716a7
commit 8fc6679178
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 386 additions and 246 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
@ -195,7 +195,12 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
// set up the user avatar // set up the user avatar
TextSecurePreferences.getLocalNumber(this)?.let{ TextSecurePreferences.getLocalNumber(this)?.let{
binding.userAvatar.load(Address.fromSerialized(it)) val username = TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(it)
binding.userAvatar.apply {
publicKey = it
displayName = username
update()
}
} }
} }
@ -327,6 +332,8 @@ 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)
@ -334,7 +341,11 @@ 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.load(latestRecipient.recipient) binding.contactAvatar.apply {
publicKey = contactPublicKey
displayName = contactDisplayName
update()
}
} }
} }
} }

View File

@ -3,224 +3,164 @@ 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.widget.FrameLayout import android.view.View
import androidx.core.view.isVisible import android.widget.ImageView
import com.bumptech.glide.Glide import android.widget.RelativeLayout
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.TextSecurePreferences import org.session.libsession.utilities.AppTextSecurePreferences
import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.database.GroupDatabase import org.session.libsignal.utilities.Log
import javax.inject.Inject import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestManager
@AndroidEntryPoint class ProfilePictureView @JvmOverloads constructor(
class ProfilePictureView : FrameLayout { context: Context, attrs: AttributeSet? = null
constructor(context: Context) : super(context) ) : RelativeLayout(context, attrs) {
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) private val TAG = "ProfilePictureView"
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 var lastLoadJob: Job? = null private val glide: RequestManager = Glide.with(this)
private var lastLoadAddress: Address? = null private val prefs = AppTextSecurePreferences(context)
private val userPublicKey = prefs.getLocalNumber()
var publicKey: String? = null
var displayName: String? = null
var additionalPublicKey: String? = null
var additionalDisplayName: String? = null
private val unknownRecipientDrawable by lazy(LazyThreadSafetyMode.NONE) { private val profilePicturesCache = mutableMapOf<View, Recipient>()
ResourceContactPhoto(R.drawable.ic_profile_default) private val resourcePadding by lazy {
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false) context.resources.getDimensionPixelSize(R.dimen.normal_padding).toFloat()
}
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)
} }
private val unknownOpenGroupDrawable by lazy(LazyThreadSafetyMode.NONE) { fun update(recipient: Recipient) {
ResourceContactPhoto(R.drawable.ic_notification) recipient.run { update(address, isClosedGroupRecipient, isOpenGroupInboxRecipient) }
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false)
} }
private fun setShowAsDoubleMode(showAsDouble: Boolean) { fun update(
binding.doubleModeImageViewContainer.isVisible = showAsDouble address: Address,
binding.singleModeImageView.isVisible = !showAsDouble isClosedGroupRecipient: Boolean = false,
} isOpenGroupInboxRecipient: Boolean = false
) {
fun getUserDisplayName(publicKey: String): String = prefs.takeIf { userPublicKey == publicKey }?.getProfileName()
?: DatabaseComponent.get(context).sessionContactDatabase().getContactWithAccountID(publicKey)?.displayName(Contact.ContactContext.REGULAR)
?: publicKey
private fun cancelLastLoadJob() { if (isClosedGroupRecipient) {
lastLoadJob?.cancel() val members = DatabaseComponent.get(context).groupDatabase()
lastLoadJob = null .getGroupMemberAddresses(address.toGroupString(), true)
} .sorted()
.take(2)
@OptIn(DelicateCoroutinesApi::class) if (members.size <= 1) {
private fun loadAsDoubleImages(model: LoadModel) { publicKey = ""
cancelLastLoadJob() displayName = ""
additionalPublicKey = ""
// The use of GlobalScope is intentional here, as there is no better lifecycle scope that we can use additionalDisplayName = ""
// 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) {
data class GroupMemberInfo(
val contactPhoto: ContactPhoto?,
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()
)
)
}
}
when (groupAvatarOrMemberAvatars) {
is ContactPhoto -> {
setShowAsDoubleMode(false)
Glide.with(this@ProfilePictureView)
.load(groupAvatarOrMemberAvatars)
.error(unknownRecipientDrawable)
.circleCrop()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(binding.singleModeImageView)
}
is List<*> -> {
val first = groupAvatarOrMemberAvatars.getOrNull(0) as? GroupMemberInfo
val second = groupAvatarOrMemberAvatars.getOrNull(1) as? GroupMemberInfo
setShowAsDoubleMode(true)
Glide.with(binding.doubleModeImageView1)
.load(first?.let { it.contactPhoto ?: it.placeholderAvatarPhoto })
.error(first?.placeholderAvatarPhoto ?: unknownRecipientDrawable)
.circleCrop()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(binding.doubleModeImageView1)
Glide.with(binding.doubleModeImageView2)
.load(second?.let { it.contactPhoto ?: it.placeholderAvatarPhoto })
.error(second?.placeholderAvatarPhoto ?: unknownRecipientDrawable)
.circleCrop()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(binding.doubleModeImageView2)
}
else -> {
setShowAsDoubleMode(false)
binding.singleModeImageView.setImageDrawable(unknownRecipientDrawable)
}
}
}
}
@OptIn(DelicateCoroutinesApi::class)
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)
}
}
fun load(recipient: Recipient) {
if (recipient.address.isClosedGroup) {
loadAsDoubleImages(LoadModel.RecipientModel(recipient))
} else { } else {
loadAsSingleImage(LoadModel.RecipientModel(recipient)) val pk = members.getOrNull(0)?.serialize() ?: ""
publicKey = pk
displayName = getUserDisplayName(pk)
val apk = members.getOrNull(1)?.serialize() ?: ""
additionalPublicKey = apk
additionalDisplayName = getUserDisplayName(apk)
} }
} else if(isOpenGroupInboxRecipient) {
lastLoadAddress = recipient.address val publicKey = GroupUtil.getDecodedOpenGroupInboxAccountId(address.serialize())
} this.publicKey = publicKey
displayName = getUserDisplayName(publicKey)
fun load(address: Address) { additionalPublicKey = null
if (address.isClosedGroup) {
loadAsDoubleImages(LoadModel.AddressModel(address))
} else { } else {
loadAsSingleImage(LoadModel.AddressModel(address)) val publicKey = address.serialize()
this.publicKey = publicKey
displayName = getUserDisplayName(publicKey)
additionalPublicKey = null
}
update()
} }
lastLoadAddress = address fun update() {
val publicKey = publicKey ?: return Log.w(TAG, "Could not find public key to update profile picture")
val additionalPublicKey = additionalPublicKey
// if we have a multi avatar setup
if (additionalPublicKey != null) {
setProfilePictureIfNeeded(binding.doubleModeImageView1, publicKey, displayName)
setProfilePictureIfNeeded(binding.doubleModeImageView2, additionalPublicKey, additionalDisplayName)
binding.doubleModeImageViewContainer.visibility = View.VISIBLE
// clear single image
glide.clear(binding.singleModeImageView)
binding.singleModeImageView.visibility = View.INVISIBLE
} else { // single image mode
setProfilePictureIfNeeded(binding.singleModeImageView, publicKey, displayName)
binding.singleModeImageView.visibility = View.VISIBLE
// clear multi image
glide.clear(binding.doubleModeImageView1)
glide.clear(binding.doubleModeImageView2)
binding.doubleModeImageViewContainer.visibility = View.INVISIBLE
} }
private fun Recipient.displayName(): String { }
return if (isLocalNumber) {
TextSecurePreferences.getProfileName(context).orEmpty() private fun setProfilePictureIfNeeded(imageView: ImageView, publicKey: String, displayName: String?) {
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 { } else {
profileName ?: name ?: "" glide.load(placeholder)
.placeholder(unknownRecipientDrawable)
.centerCrop()
.circleCrop()
.diskCacheStrategy(DiskCacheStrategy.NONE).circleCrop().into(imageView)
}
} else {
glide.load(unknownRecipientDrawable)
.centerCrop()
.into(imageView)
} }
} }
private sealed interface LoadModel { fun recycle() {
val address: Address profilePicturesCache.clear()
/**
* 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
}
} }
// endregion
} }

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.load(user) binding.profilePictureView.update(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,6 +85,7 @@ 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.load(recipient) binding.profilePictureView.update(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,7 +27,6 @@ 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

@ -0,0 +1,87 @@
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

@ -0,0 +1,42 @@
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,11 +2,13 @@ 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.load(Address.fromSerialized(candidate.member.publicKey)) profilePictureView.publicKey = 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,6 +16,7 @@ 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
@ -25,8 +26,6 @@ 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
@ -53,6 +52,8 @@ 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
@ -178,7 +179,8 @@ class VisibleMessageView : FrameLayout {
if (isGroupThread && !message.isOutgoing) { if (isGroupThread && !message.isOutgoing) {
if (isEndOfMessageCluster) { if (isEndOfMessageCluster) {
binding.profilePictureView.load(message.individualRecipient) binding.profilePictureView.publicKey = senderAccountID
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)
@ -454,6 +456,7 @@ 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;
public class ContactPhotoFetcher implements DataFetcher<InputStream> { 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;
public ContactPhotoFetcher(@NonNull Context context, @NonNull ContactPhoto contactPhoto) { ContactPhotoFetcher(@NonNull Context context, @NonNull ContactPhoto contactPhoto) {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.contactPhoto = contactPhoto; this.contactPhoto = contactPhoto;
} }

View File

@ -9,15 +9,12 @@ 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( class PlaceholderAvatarFetcher(private val context: Context,
private val context: Context, private val photo: PlaceholderAvatarPhoto): DataFetcher<BitmapDrawable> {
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, hashString, displayName) val avatar = AvatarPlaceholderGenerator.generate(context, 128, photo.hashString, photo.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,9 +8,6 @@ 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> {
@ -20,15 +17,7 @@ class PlaceholderAvatarLoader(private val appContext: Context): ModelLoader<Plac
height: Int, height: Int,
options: Options options: Options
): LoadData<BitmapDrawable> { ): LoadData<BitmapDrawable> {
val displayName: String = when { return LoadData(model, PlaceholderAvatarFetcher(appContext, model))
!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,6 +4,7 @@ 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
@ -127,10 +128,11 @@ 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.load(thread.recipient) binding.profilePictureView.update(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,7 +347,8 @@ 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.load(Address.fromSerialized(publicKey)) binding.profileButton.recycle() // clear cached image before update tje profilePictureView
binding.profileButton.update()
if (textSecurePreferences.getHasViewedSeed()) { if (textSecurePreferences.getHasViewedSeed()) {
binding.seedReminderView.isVisible = false binding.seedReminderView.isVisible = false
} }
@ -387,7 +388,10 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
} }
private fun updateProfileButton() { private fun updateProfileButton() {
binding.profileButton.load(Address.fromSerialized(publicKey)) binding.profileButton.publicKey = publicKey
binding.profileButton.displayName = textSecurePreferences.getProfileName()
binding.profileButton.recycle()
binding.profileButton.update()
} }
// endregion // endregion

View File

@ -55,7 +55,8 @@ 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.load(recipient) profilePictureView.publicKey = publicKey
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,6 +11,7 @@ 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
@ -98,6 +99,12 @@ 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)
@ -107,6 +114,7 @@ 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.load(threadRecipient) binding.searchResultProfilePicture.update(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.load(recipient) searchResultProfilePicture.update(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.load(Address.fromSerialized(model.currentUserPublicKey)) binding.searchResultProfilePicture.update(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.load(model.messageResult.conversationRecipient) searchResultProfilePicture.update(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,11 +49,12 @@ class MessageRequestView : LinearLayout {
binding.snippetTextView.text = snippet binding.snippetTextView.text = snippet
post { post {
binding.profilePictureView.load(thread.recipient) binding.profilePictureView.update(thread.recipient)
} }
} }
fun recycle() { fun recycle() {
binding.profilePictureView.recycle()
} }
private fun getUserDisplayName(recipient: Recipient): String? { private fun getUserDisplayName(recipient: Recipient): String? {

View File

@ -56,7 +56,6 @@ 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();
@ -75,7 +74,6 @@ 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,6 +36,11 @@ 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)
@ -43,7 +48,9 @@ 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
binding.profilePictureView.load(selectable.item) with (binding.profilePictureView) {
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 {
loadProfilePicture(profilePictureView) setupProfilePictureView(profilePictureView)
profilePictureView.setOnClickListener { showEditProfilePictureUI() } profilePictureView.setOnClickListener { showEditProfilePictureUI() }
ctnGroupNameSection.setOnClickListener { startActionMode(DisplayNameEditActionModeCallback()) } ctnGroupNameSection.setOnClickListener { startActionMode(DisplayNameEditActionModeCallback()) }
btnGroupNameDisplay.text = getDisplayName() btnGroupNameDisplay.text = getDisplayName()
@ -185,9 +185,12 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
private fun getDisplayName(): String = private fun getDisplayName(): String =
TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(hexEncodedPublicKey) TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(hexEncodedPublicKey)
private fun loadProfilePicture(view: ProfilePictureView) { private fun setupProfilePictureView(view: ProfilePictureView) {
// Always reload the profile picture as it can change on this page. view.apply {
view.load(Address.fromSerialized(hexEncodedPublicKey)) publicKey = hexEncodedPublicKey
displayName = getDisplayName()
update()
}
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
@ -328,7 +331,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@SettingsActivity) ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@SettingsActivity)
loadProfilePicture(binding.profilePictureView) // Update our visuals
binding.profilePictureView.recycle()
binding.profilePictureView.update()
} }
// If the sync failed then inform the user // If the sync failed then inform the user
@ -403,7 +408,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(::loadProfilePicture) ?.also(::setupProfilePictureView)
val pictureIcon = findViewById<View>(R.id.ic_pictures) val pictureIcon = findViewById<View>(R.id.ic_pictures)

View File

@ -13,6 +13,7 @@ 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;
@ -153,7 +154,7 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecip
callback.onRemoveReaction(reaction.getBaseEmoji(), messageId, reaction.getTimestamp()); callback.onRemoveReaction(reaction.getBaseEmoji(), messageId, reaction.getTimestamp());
}); });
this.avatar.load(reaction.getSender()); this.avatar.update(reaction.getSender());
if (reaction.getSender().isLocalNumber()) { if (reaction.getSender().isLocalNumber()) {
this.recipient.setText(R.string.ReactionsRecipientAdapter_you); this.recipient.setText(R.string.ReactionsRecipientAdapter_you);
@ -169,6 +170,7 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecip
} }
void unbind() { void unbind() {
avatar.recycle();
} }
} }

View File

@ -11,6 +11,7 @@ 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
@ -360,7 +361,7 @@ fun RowScope.Avatar(recipient: Recipient) {
) { ) {
AndroidView( AndroidView(
factory = { factory = {
ProfilePictureView(it).apply { load(recipient) } ProfilePictureView(it).apply { update(recipient) }
}, },
modifier = Modifier modifier = Modifier
.width(46.dp) .width(46.dp)

View File

@ -0,0 +1,42 @@
<?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,12 +3,10 @@ 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
data class PlaceholderAvatarPhoto( class PlaceholderAvatarPhoto(val hashString: String,
val hashString: String, val displayName: String): Key {
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() ?: byteArrayOf()) messageDigest.update(displayName.encodeToByteArray())
} }
} }