mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-21 15:05:19 +00:00
[SES-2512] Rewrite ProfilePictureView (#1622)
This commit is contained in:
parent
6701cb1dc1
commit
9919f716a7
@ -27,8 +27,8 @@ import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ActivityWebrtcBinding
|
||||
import org.apache.commons.lang3.time.DurationFormatUtils
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.truncateIdForDisplay
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||
@ -194,13 +194,8 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
||||
clipFloatingInsets()
|
||||
|
||||
// set up the user avatar
|
||||
TextSecurePreferences.getLocalNumber(this)?.let{
|
||||
val username = TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(it)
|
||||
binding.userAvatar.apply {
|
||||
publicKey = it
|
||||
displayName = username
|
||||
update()
|
||||
}
|
||||
TextSecurePreferences.getLocalNumber(this)?.let {
|
||||
binding.userAvatar.load(Address.fromSerialized(it))
|
||||
}
|
||||
}
|
||||
|
||||
@ -332,8 +327,6 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
||||
|
||||
launch {
|
||||
viewModel.recipient.collect { latestRecipient ->
|
||||
binding.contactAvatar.recycle()
|
||||
|
||||
if (latestRecipient.recipient != null) {
|
||||
val contactPublicKey = latestRecipient.recipient.address.serialize()
|
||||
val contactDisplayName = getUserDisplayName(contactPublicKey)
|
||||
@ -341,11 +334,7 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
||||
binding.remoteRecipientName.text = contactDisplayName
|
||||
|
||||
// sort out the contact's avatar
|
||||
binding.contactAvatar.apply {
|
||||
publicKey = contactPublicKey
|
||||
displayName = contactDisplayName
|
||||
update()
|
||||
}
|
||||
binding.contactAvatar.load(latestRecipient.recipient)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,164 +3,224 @@ package org.thoughtcrime.securesms.components
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.isVisible
|
||||
import com.bumptech.glide.Glide
|
||||
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.databinding.ViewProfilePictureBinding
|
||||
import org.session.libsession.avatars.ContactColors
|
||||
import org.session.libsession.avatars.ContactPhoto
|
||||
import org.session.libsession.avatars.PlaceholderAvatarPhoto
|
||||
import org.session.libsession.avatars.ProfileContactPhoto
|
||||
import org.session.libsession.avatars.ResourceContactPhoto
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.AppTextSecurePreferences
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.RequestManager
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase
|
||||
import javax.inject.Inject
|
||||
|
||||
class ProfilePictureView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : RelativeLayout(context, attrs) {
|
||||
private val TAG = "ProfilePictureView"
|
||||
@AndroidEntryPoint
|
||||
class ProfilePictureView : FrameLayout {
|
||||
constructor(context: Context) : super(context)
|
||||
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 glide: RequestManager = Glide.with(this)
|
||||
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 var lastLoadJob: Job? = null
|
||||
private var lastLoadAddress: Address? = null
|
||||
|
||||
private val profilePicturesCache = mutableMapOf<View, Recipient>()
|
||||
private val resourcePadding by lazy {
|
||||
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 unknownRecipientDrawable by lazy(LazyThreadSafetyMode.NONE) {
|
||||
ResourceContactPhoto(R.drawable.ic_profile_default)
|
||||
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false)
|
||||
}
|
||||
|
||||
fun update(recipient: Recipient) {
|
||||
recipient.run { update(address, isClosedGroupRecipient, isOpenGroupInboxRecipient) }
|
||||
private val unknownOpenGroupDrawable by lazy(LazyThreadSafetyMode.NONE) {
|
||||
ResourceContactPhoto(R.drawable.ic_notification)
|
||||
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false)
|
||||
}
|
||||
|
||||
fun update(
|
||||
address: Address,
|
||||
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
|
||||
|
||||
if (isClosedGroupRecipient) {
|
||||
val members = DatabaseComponent.get(context).groupDatabase()
|
||||
.getGroupMemberAddresses(address.toGroupString(), true)
|
||||
.sorted()
|
||||
.take(2)
|
||||
if (members.size <= 1) {
|
||||
publicKey = ""
|
||||
displayName = ""
|
||||
additionalPublicKey = ""
|
||||
additionalDisplayName = ""
|
||||
} else {
|
||||
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) {
|
||||
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()
|
||||
private fun setShowAsDoubleMode(showAsDouble: Boolean) {
|
||||
binding.doubleModeImageViewContainer.isVisible = showAsDouble
|
||||
binding.singleModeImageView.isVisible = !showAsDouble
|
||||
}
|
||||
|
||||
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 cancelLastLoadJob() {
|
||||
lastLoadJob?.cancel()
|
||||
lastLoadJob = null
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
private fun loadAsDoubleImages(model: LoadModel) {
|
||||
cancelLastLoadJob()
|
||||
|
||||
// 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) {
|
||||
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()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
when (groupAvatarOrMemberAvatars) {
|
||||
is ContactPhoto -> {
|
||||
setShowAsDoubleMode(false)
|
||||
Glide.with(this@ProfilePictureView)
|
||||
.load(groupAvatarOrMemberAvatars)
|
||||
.error(unknownRecipientDrawable)
|
||||
.circleCrop()
|
||||
.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)
|
||||
.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)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
glide.load(unknownRecipientDrawable)
|
||||
.centerCrop()
|
||||
.into(imageView)
|
||||
}
|
||||
}
|
||||
|
||||
fun recycle() {
|
||||
profilePicturesCache.clear()
|
||||
@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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
}
|
@ -53,7 +53,7 @@ class UserView : LinearLayout {
|
||||
return contact?.displayName(Contact.ContactContext.REGULAR) ?: publicKey
|
||||
}
|
||||
val address = user.address.serialize()
|
||||
binding.profilePictureView.update(user)
|
||||
binding.profilePictureView.load(user)
|
||||
binding.actionIndicatorImageView.setImageResource(R.drawable.ic_baseline_edit_24)
|
||||
binding.nameTextView.text = if (user.isGroupRecipient) user.name else getUserDisplayName(address)
|
||||
when (actionIndicator) {
|
||||
@ -85,7 +85,6 @@ class UserView : LinearLayout {
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
binding.profilePictureView.recycle()
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ class ConversationActionBarView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
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)
|
||||
updateSubtitle(recipient, openGroup, config)
|
||||
|
||||
|
@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||
import com.bumptech.glide.RequestManager
|
||||
import org.thoughtcrime.securesms.database.getLong
|
||||
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
|
||||
import org.thoughtcrime.securesms.showSessionDialog
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -2,13 +2,11 @@ package org.thoughtcrime.securesms.conversation.v2.input_bar.mentions
|
||||
|
||||
import android.view.View
|
||||
import network.loki.messenger.databinding.ViewMentionCandidateV2Binding
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.thoughtcrime.securesms.conversation.v2.mention.MentionViewModel
|
||||
|
||||
fun ViewMentionCandidateV2Binding.update(candidate: MentionViewModel.Candidate) {
|
||||
mentionCandidateNameTextView.text = candidate.nameHighlighted
|
||||
profilePictureView.publicKey = candidate.member.publicKey
|
||||
profilePictureView.displayName = candidate.member.name
|
||||
profilePictureView.additionalPublicKey = null
|
||||
profilePictureView.update()
|
||||
profilePictureView.load(Address.fromSerialized(candidate.member.publicKey))
|
||||
moderatorIconImageView.visibility = if (candidate.member.isModerator) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
@ -26,6 +25,8 @@ import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.marginBottom
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.RequestManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import network.loki.messenger.R
|
||||
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.groups.OpenGroupManager
|
||||
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.disableClipping
|
||||
import org.thoughtcrime.securesms.util.toDp
|
||||
@ -179,8 +178,7 @@ class VisibleMessageView : FrameLayout {
|
||||
|
||||
if (isGroupThread && !message.isOutgoing) {
|
||||
if (isEndOfMessageCluster) {
|
||||
binding.profilePictureView.publicKey = senderAccountID
|
||||
binding.profilePictureView.update(message.individualRecipient)
|
||||
binding.profilePictureView.load(message.individualRecipient)
|
||||
binding.profilePictureView.setOnClickListener {
|
||||
if (thread.isCommunityRecipient) {
|
||||
val openGroup = lokiThreadDb.getOpenGroupChat(threadID)
|
||||
@ -456,7 +454,6 @@ class VisibleMessageView : FrameLayout {
|
||||
}
|
||||
|
||||
fun recycle() {
|
||||
binding.profilePictureView.recycle()
|
||||
binding.messageContentView.root.recycle()
|
||||
}
|
||||
|
||||
|
@ -13,14 +13,14 @@ import org.session.libsession.avatars.ContactPhoto;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
class ContactPhotoFetcher implements DataFetcher<InputStream> {
|
||||
public class ContactPhotoFetcher implements DataFetcher<InputStream> {
|
||||
|
||||
private final Context context;
|
||||
private final ContactPhoto contactPhoto;
|
||||
|
||||
private InputStream inputStream;
|
||||
|
||||
ContactPhotoFetcher(@NonNull Context context, @NonNull ContactPhoto contactPhoto) {
|
||||
public ContactPhotoFetcher(@NonNull Context context, @NonNull ContactPhoto contactPhoto) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.contactPhoto = contactPhoto;
|
||||
}
|
||||
|
@ -9,12 +9,15 @@ import org.session.libsession.avatars.PlaceholderAvatarPhoto
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator
|
||||
|
||||
class PlaceholderAvatarFetcher(private val context: Context,
|
||||
private val photo: PlaceholderAvatarPhoto): DataFetcher<BitmapDrawable> {
|
||||
class PlaceholderAvatarFetcher(
|
||||
private val context: Context,
|
||||
private val hashString: String,
|
||||
private val displayName: String
|
||||
): DataFetcher<BitmapDrawable> {
|
||||
|
||||
override fun loadData(priority: Priority,callback: DataFetcher.DataCallback<in BitmapDrawable>) {
|
||||
try {
|
||||
val avatar = AvatarPlaceholderGenerator.generate(context, 128, photo.hashString, photo.displayName)
|
||||
val avatar = AvatarPlaceholderGenerator.generate(context, 128, hashString, displayName)
|
||||
callback.onDataReady(avatar)
|
||||
} catch (e: Exception) {
|
||||
Log.e("Loki", "Error in fetching avatar")
|
||||
|
@ -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.MultiModelLoaderFactory
|
||||
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> {
|
||||
|
||||
@ -17,7 +20,15 @@ class PlaceholderAvatarLoader(private val appContext: Context): ModelLoader<Plac
|
||||
height: Int,
|
||||
options: Options
|
||||
): 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
|
||||
|
@ -4,7 +4,6 @@ import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.text.TextUtils
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
@ -128,11 +127,10 @@ class ConversationView : LinearLayout {
|
||||
thread.isRead -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_filled_circle_check)
|
||||
else -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_circle_check)
|
||||
}
|
||||
binding.profilePictureView.update(thread.recipient)
|
||||
binding.profilePictureView.load(thread.recipient)
|
||||
}
|
||||
|
||||
fun recycle() {
|
||||
binding.profilePictureView.recycle()
|
||||
}
|
||||
|
||||
private fun getTitle(recipient: Recipient): String? = when {
|
||||
|
@ -347,8 +347,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
ApplicationContext.getInstance(this).messageNotifier.setHomeScreenVisible(true)
|
||||
if (textSecurePreferences.getLocalNumber() == null) { return; } // This can be the case after a secondary device is auto-cleared
|
||||
IdentityKeyUtil.checkUpdate(this)
|
||||
binding.profileButton.recycle() // clear cached image before update tje profilePictureView
|
||||
binding.profileButton.update()
|
||||
binding.profileButton.load(Address.fromSerialized(publicKey))
|
||||
if (textSecurePreferences.getHasViewedSeed()) {
|
||||
binding.seedReminderView.isVisible = false
|
||||
}
|
||||
@ -388,10 +387,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
}
|
||||
|
||||
private fun updateProfileButton() {
|
||||
binding.profileButton.publicKey = publicKey
|
||||
binding.profileButton.displayName = textSecurePreferences.getProfileName()
|
||||
binding.profileButton.recycle()
|
||||
binding.profileButton.update()
|
||||
binding.profileButton.load(Address.fromSerialized(publicKey))
|
||||
}
|
||||
// endregion
|
||||
|
||||
|
@ -55,8 +55,7 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() {
|
||||
val recipient = Recipient.from(requireContext(), Address.fromSerialized(publicKey), false)
|
||||
val threadRecipient = threadDb.getRecipientForThreadId(threadID) ?: return dismiss()
|
||||
with(binding) {
|
||||
profilePictureView.publicKey = publicKey
|
||||
profilePictureView.update(recipient)
|
||||
profilePictureView.load(recipient)
|
||||
nameTextViewContainer.visibility = View.VISIBLE
|
||||
nameTextViewContainer.setOnClickListener {
|
||||
if (recipient.isOpenGroupInboxRecipient || recipient.isOpenGroupOutboxRecipient) return@setOnClickListener
|
||||
|
@ -11,7 +11,6 @@ import network.loki.messenger.databinding.ViewGlobalSearchHeaderBinding
|
||||
import network.loki.messenger.databinding.ViewGlobalSearchResultBinding
|
||||
import network.loki.messenger.databinding.ViewGlobalSearchSubheaderBinding
|
||||
import org.session.libsession.utilities.GroupRecord
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.search.model.MessageResult
|
||||
import org.thoughtcrime.securesms.ui.GetString
|
||||
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) {
|
||||
|
||||
val binding = ViewGlobalSearchResultBinding.bind(view)
|
||||
@ -114,7 +107,6 @@ class GlobalSearchAdapter(private val modelCallback: (Model)->Unit): RecyclerVie
|
||||
}
|
||||
|
||||
fun bind(query: String, model: Model) {
|
||||
binding.searchResultProfilePicture.recycle()
|
||||
when (model) {
|
||||
is Model.GroupConversation -> bindModel(query, model)
|
||||
is Model.Contact -> bindModel(query, model)
|
||||
|
@ -93,7 +93,7 @@ fun ContentView.bindModel(query: String?, model: GroupConversation) {
|
||||
binding.searchResultSubtitle.isVisible = model.groupRecord.isClosedGroup
|
||||
binding.searchResultTimestamp.isVisible = 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
|
||||
binding.searchResultTitle.text = getHighlight(query, nameString)
|
||||
|
||||
@ -111,7 +111,7 @@ fun ContentView.bindModel(query: String?, model: ContactModel) = binding.run {
|
||||
searchResultTimestamp.isVisible = false
|
||||
searchResultSubtitle.text = null
|
||||
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)
|
||||
else model.contact.getSearchName()
|
||||
searchResultTitle.text = getHighlight(query, nameString)
|
||||
@ -121,7 +121,7 @@ fun ContentView.bindModel(model: SavedMessages) {
|
||||
binding.searchResultSubtitle.isVisible = false
|
||||
binding.searchResultTimestamp.isVisible = false
|
||||
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
|
||||
}
|
||||
|
||||
@ -134,7 +134,7 @@ fun ContentView.bindModel(query: String?, model: Message) = binding.apply {
|
||||
// unreadCountTextView.text = model.unread.toString()
|
||||
// }
|
||||
searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(root.context, Locale.getDefault(), model.messageResult.sentTimestampMs)
|
||||
searchResultProfilePicture.update(model.messageResult.conversationRecipient)
|
||||
searchResultProfilePicture.load(model.messageResult.conversationRecipient)
|
||||
val textSpannable = SpannableStringBuilder()
|
||||
if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) {
|
||||
// group chat, bind
|
||||
|
@ -49,12 +49,11 @@ class MessageRequestView : LinearLayout {
|
||||
binding.snippetTextView.text = snippet
|
||||
|
||||
post {
|
||||
binding.profilePictureView.update(thread.recipient)
|
||||
binding.profilePictureView.load(thread.recipient)
|
||||
}
|
||||
}
|
||||
|
||||
fun recycle() {
|
||||
binding.profilePictureView.recycle()
|
||||
}
|
||||
|
||||
private fun getUserDisplayName(recipient: Recipient): String? {
|
||||
|
@ -56,6 +56,7 @@ public class SignalGlideModule extends AppGlideModule {
|
||||
// builder.setDiskCache(new NoopDiskCacheFactory());
|
||||
}
|
||||
|
||||
/** @noinspection unchecked*/
|
||||
@Override
|
||||
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
|
||||
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(ChunkedImageUrl.class, InputStream.class, new ChunkedImageUrlLoader.Factory());
|
||||
registry.append(PlaceholderAvatarPhoto.class, BitmapDrawable.class, new PlaceholderAvatarLoader.Factory(context));
|
||||
|
||||
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
|
||||
}
|
||||
|
||||
|
@ -36,11 +36,6 @@ class BlockedContactsAdapter(val viewModel: BlockedContactsViewModel) : ListAdap
|
||||
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) {
|
||||
|
||||
val glide = Glide.with(itemView)
|
||||
@ -48,9 +43,7 @@ class BlockedContactsAdapter(val viewModel: BlockedContactsViewModel) : ListAdap
|
||||
|
||||
fun bind(selectable: SelectableRecipient, toggle: (SelectableRecipient) -> Unit) {
|
||||
binding.recipientName.text = selectable.item.name
|
||||
with (binding.profilePictureView) {
|
||||
update(selectable.item)
|
||||
}
|
||||
binding.profilePictureView.load(selectable.item)
|
||||
binding.root.setOnClickListener { toggle(selectable) }
|
||||
binding.selectButton.isSelected = selectable.isSelected
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
super.onStart()
|
||||
|
||||
binding.run {
|
||||
setupProfilePictureView(profilePictureView)
|
||||
loadProfilePicture(profilePictureView)
|
||||
profilePictureView.setOnClickListener { showEditProfilePictureUI() }
|
||||
ctnGroupNameSection.setOnClickListener { startActionMode(DisplayNameEditActionModeCallback()) }
|
||||
btnGroupNameDisplay.text = getDisplayName()
|
||||
@ -185,12 +185,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
private fun getDisplayName(): String =
|
||||
TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(hexEncodedPublicKey)
|
||||
|
||||
private fun setupProfilePictureView(view: ProfilePictureView) {
|
||||
view.apply {
|
||||
publicKey = hexEncodedPublicKey
|
||||
displayName = getDisplayName()
|
||||
update()
|
||||
}
|
||||
private fun loadProfilePicture(view: ProfilePictureView) {
|
||||
// Always reload the profile picture as it can change on this page.
|
||||
view.load(Address.fromSerialized(hexEncodedPublicKey))
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
@ -331,9 +328,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
|
||||
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@SettingsActivity)
|
||||
|
||||
// Update our visuals
|
||||
binding.profilePictureView.recycle()
|
||||
binding.profilePictureView.update()
|
||||
loadProfilePicture(binding.profilePictureView)
|
||||
}
|
||||
|
||||
// If the sync failed then inform the user
|
||||
@ -408,7 +403,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
cancelButton()
|
||||
}.apply {
|
||||
val profilePic = findViewById<ProfilePictureView>(R.id.profile_picture_view)
|
||||
?.also(::setupProfilePictureView)
|
||||
?.also(::loadProfilePicture)
|
||||
|
||||
val pictureIcon = findViewById<View>(R.id.ic_pictures)
|
||||
|
||||
|
@ -13,7 +13,6 @@ import org.session.libsession.messaging.utilities.AccountId;
|
||||
import org.thoughtcrime.securesms.components.ProfilePictureView;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import com.bumptech.glide.Glide;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -154,7 +153,7 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecip
|
||||
callback.onRemoveReaction(reaction.getBaseEmoji(), messageId, reaction.getTimestamp());
|
||||
});
|
||||
|
||||
this.avatar.update(reaction.getSender());
|
||||
this.avatar.load(reaction.getSender());
|
||||
|
||||
if (reaction.getSender().isLocalNumber()) {
|
||||
this.recipient.setText(R.string.ReactionsRecipientAdapter_you);
|
||||
@ -170,7 +169,6 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecip
|
||||
}
|
||||
|
||||
void unbind() {
|
||||
avatar.recycle();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@ -361,7 +360,7 @@ fun RowScope.Avatar(recipient: Recipient) {
|
||||
) {
|
||||
AndroidView(
|
||||
factory = {
|
||||
ProfilePictureView(it).apply { update(recipient) }
|
||||
ProfilePictureView(it).apply { load(recipient) }
|
||||
},
|
||||
modifier = Modifier
|
||||
.width(46.dp)
|
||||
|
@ -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>
|
@ -3,10 +3,12 @@ package org.session.libsession.avatars
|
||||
import com.bumptech.glide.load.Key
|
||||
import java.security.MessageDigest
|
||||
|
||||
class PlaceholderAvatarPhoto(val hashString: String,
|
||||
val displayName: String): Key {
|
||||
data class PlaceholderAvatarPhoto(
|
||||
val hashString: String,
|
||||
val displayName: String?
|
||||
) : Key {
|
||||
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
|
||||
messageDigest.update(hashString.encodeToByteArray())
|
||||
messageDigest.update(displayName.encodeToByteArray())
|
||||
messageDigest.update(displayName?.encodeToByteArray() ?: byteArrayOf())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user