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 network.loki.messenger.databinding.ActivityWebrtcBinding
|
||||||
import org.apache.commons.lang3.time.DurationFormatUtils
|
import org.apache.commons.lang3.time.DurationFormatUtils
|
||||||
import org.session.libsession.messaging.contacts.Contact
|
import org.session.libsession.messaging.contacts.Contact
|
||||||
|
import org.session.libsession.utilities.Address
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsession.utilities.truncateIdForDisplay
|
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
@ -194,13 +194,8 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
clipFloatingInsets()
|
clipFloatingInsets()
|
||||||
|
|
||||||
// set up the user avatar
|
// set up the user avatar
|
||||||
TextSecurePreferences.getLocalNumber(this)?.let{
|
TextSecurePreferences.getLocalNumber(this)?.let {
|
||||||
val username = TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(it)
|
binding.userAvatar.load(Address.fromSerialized(it))
|
||||||
binding.userAvatar.apply {
|
|
||||||
publicKey = it
|
|
||||||
displayName = username
|
|
||||||
update()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,8 +327,6 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
|
|
||||||
launch {
|
launch {
|
||||||
viewModel.recipient.collect { latestRecipient ->
|
viewModel.recipient.collect { latestRecipient ->
|
||||||
binding.contactAvatar.recycle()
|
|
||||||
|
|
||||||
if (latestRecipient.recipient != null) {
|
if (latestRecipient.recipient != null) {
|
||||||
val contactPublicKey = latestRecipient.recipient.address.serialize()
|
val contactPublicKey = latestRecipient.recipient.address.serialize()
|
||||||
val contactDisplayName = getUserDisplayName(contactPublicKey)
|
val contactDisplayName = getUserDisplayName(contactPublicKey)
|
||||||
@ -341,11 +334,7 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
binding.remoteRecipientName.text = contactDisplayName
|
binding.remoteRecipientName.text = contactDisplayName
|
||||||
|
|
||||||
// sort out the contact's avatar
|
// sort out the contact's avatar
|
||||||
binding.contactAvatar.apply {
|
binding.contactAvatar.load(latestRecipient.recipient)
|
||||||
publicKey = contactPublicKey
|
|
||||||
displayName = contactDisplayName
|
|
||||||
update()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,164 +3,224 @@ package org.thoughtcrime.securesms.components
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.widget.FrameLayout
|
||||||
import android.widget.ImageView
|
import androidx.core.view.isVisible
|
||||||
import android.widget.RelativeLayout
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import network.loki.messenger.databinding.ViewProfilePictureBinding
|
import network.loki.messenger.databinding.ViewProfilePictureBinding
|
||||||
import org.session.libsession.avatars.ContactColors
|
import org.session.libsession.avatars.ContactColors
|
||||||
|
import org.session.libsession.avatars.ContactPhoto
|
||||||
import org.session.libsession.avatars.PlaceholderAvatarPhoto
|
import org.session.libsession.avatars.PlaceholderAvatarPhoto
|
||||||
import org.session.libsession.avatars.ProfileContactPhoto
|
|
||||||
import org.session.libsession.avatars.ResourceContactPhoto
|
import org.session.libsession.avatars.ResourceContactPhoto
|
||||||
import org.session.libsession.messaging.contacts.Contact
|
|
||||||
import org.session.libsession.utilities.Address
|
import org.session.libsession.utilities.Address
|
||||||
import org.session.libsession.utilities.AppTextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsession.utilities.GroupUtil
|
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
import org.session.libsignal.utilities.Log
|
import org.thoughtcrime.securesms.database.GroupDatabase
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
import javax.inject.Inject
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.RequestManager
|
|
||||||
|
|
||||||
class ProfilePictureView @JvmOverloads constructor(
|
@AndroidEntryPoint
|
||||||
context: Context, attrs: AttributeSet? = null
|
class ProfilePictureView : FrameLayout {
|
||||||
) : RelativeLayout(context, attrs) {
|
constructor(context: Context) : super(context)
|
||||||
private val TAG = "ProfilePictureView"
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||||
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
|
||||||
|
context,
|
||||||
|
attrs,
|
||||||
|
defStyleAttr
|
||||||
|
)
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var groupDatabase: GroupDatabase
|
||||||
|
|
||||||
private val binding = ViewProfilePictureBinding.inflate(LayoutInflater.from(context), this)
|
private val binding = ViewProfilePictureBinding.inflate(LayoutInflater.from(context), this)
|
||||||
private val glide: RequestManager = Glide.with(this)
|
private var lastLoadJob: Job? = null
|
||||||
private val prefs = AppTextSecurePreferences(context)
|
private var lastLoadAddress: Address? = null
|
||||||
private val userPublicKey = prefs.getLocalNumber()
|
|
||||||
var publicKey: String? = null
|
|
||||||
var displayName: String? = null
|
|
||||||
var additionalPublicKey: String? = null
|
|
||||||
var additionalDisplayName: String? = null
|
|
||||||
|
|
||||||
private val profilePicturesCache = mutableMapOf<View, Recipient>()
|
private val unknownRecipientDrawable by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
private val resourcePadding by lazy {
|
ResourceContactPhoto(R.drawable.ic_profile_default)
|
||||||
context.resources.getDimensionPixelSize(R.dimen.normal_padding).toFloat()
|
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false)
|
||||||
}
|
|
||||||
private val unknownRecipientDrawable by lazy { ResourceContactPhoto(R.drawable.ic_profile_default)
|
|
||||||
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false, resourcePadding) }
|
|
||||||
private val unknownOpenGroupDrawable by lazy { ResourceContactPhoto(R.drawable.ic_notification)
|
|
||||||
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false, resourcePadding) }
|
|
||||||
|
|
||||||
constructor(context: Context, sender: Recipient): this(context) {
|
|
||||||
update(sender)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(recipient: Recipient) {
|
private val unknownOpenGroupDrawable by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
recipient.run { update(address, isClosedGroupRecipient, isOpenGroupInboxRecipient) }
|
ResourceContactPhoto(R.drawable.ic_notification)
|
||||||
|
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(
|
private fun setShowAsDoubleMode(showAsDouble: Boolean) {
|
||||||
address: Address,
|
binding.doubleModeImageViewContainer.isVisible = showAsDouble
|
||||||
isClosedGroupRecipient: Boolean = false,
|
binding.singleModeImageView.isVisible = !showAsDouble
|
||||||
isOpenGroupInboxRecipient: Boolean = false
|
}
|
||||||
) {
|
|
||||||
fun getUserDisplayName(publicKey: String): String = prefs.takeIf { userPublicKey == publicKey }?.getProfileName()
|
|
||||||
?: DatabaseComponent.get(context).sessionContactDatabase().getContactWithAccountID(publicKey)?.displayName(Contact.ContactContext.REGULAR)
|
|
||||||
?: publicKey
|
|
||||||
|
|
||||||
if (isClosedGroupRecipient) {
|
private fun cancelLastLoadJob() {
|
||||||
val members = DatabaseComponent.get(context).groupDatabase()
|
lastLoadJob?.cancel()
|
||||||
.getGroupMemberAddresses(address.toGroupString(), true)
|
lastLoadJob = null
|
||||||
.sorted()
|
}
|
||||||
.take(2)
|
|
||||||
if (members.size <= 1) {
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
publicKey = ""
|
private fun loadAsDoubleImages(model: LoadModel) {
|
||||||
displayName = ""
|
cancelLastLoadJob()
|
||||||
additionalPublicKey = ""
|
|
||||||
additionalDisplayName = ""
|
// The use of GlobalScope is intentional here, as there is no better lifecycle scope that we can use
|
||||||
} else {
|
// to launch a coroutine from a view. The potential memory leak is not a concern here, as
|
||||||
val pk = members.getOrNull(0)?.serialize() ?: ""
|
// the coroutine is very short-lived. If you change the code here to be long live then you'll
|
||||||
publicKey = pk
|
// need to find a better scope to launch the coroutine from.
|
||||||
displayName = getUserDisplayName(pk)
|
lastLoadJob = GlobalScope.launch(Dispatchers.Main) {
|
||||||
val apk = members.getOrNull(1)?.serialize() ?: ""
|
data class GroupMemberInfo(
|
||||||
additionalPublicKey = apk
|
val contactPhoto: ContactPhoto?,
|
||||||
additionalDisplayName = getUserDisplayName(apk)
|
val placeholderAvatarPhoto: PlaceholderAvatarPhoto,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Load group avatar if available, otherwise load member avatars
|
||||||
|
val groupAvatarOrMemberAvatars = withContext(Dispatchers.Default) {
|
||||||
|
model.loadRecipient(context).contactPhoto
|
||||||
|
?: groupDatabase.getGroupMembers(model.address.toGroupString(), true)
|
||||||
|
.map {
|
||||||
|
GroupMemberInfo(
|
||||||
|
contactPhoto = it.contactPhoto,
|
||||||
|
placeholderAvatarPhoto = PlaceholderAvatarPhoto(
|
||||||
|
hashString = it.address.serialize(),
|
||||||
|
displayName = it.displayName()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if(isOpenGroupInboxRecipient) {
|
|
||||||
val publicKey = GroupUtil.getDecodedOpenGroupInboxAccountId(address.serialize())
|
|
||||||
this.publicKey = publicKey
|
|
||||||
displayName = getUserDisplayName(publicKey)
|
|
||||||
additionalPublicKey = null
|
|
||||||
} else {
|
|
||||||
val publicKey = address.serialize()
|
|
||||||
this.publicKey = publicKey
|
|
||||||
displayName = getUserDisplayName(publicKey)
|
|
||||||
additionalPublicKey = null
|
|
||||||
}
|
|
||||||
update()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun update() {
|
when (groupAvatarOrMemberAvatars) {
|
||||||
val publicKey = publicKey ?: return Log.w(TAG, "Could not find public key to update profile picture")
|
is ContactPhoto -> {
|
||||||
val additionalPublicKey = additionalPublicKey
|
setShowAsDoubleMode(false)
|
||||||
// if we have a multi avatar setup
|
Glide.with(this@ProfilePictureView)
|
||||||
if (additionalPublicKey != null) {
|
.load(groupAvatarOrMemberAvatars)
|
||||||
setProfilePictureIfNeeded(binding.doubleModeImageView1, publicKey, displayName)
|
.error(unknownRecipientDrawable)
|
||||||
setProfilePictureIfNeeded(binding.doubleModeImageView2, additionalPublicKey, additionalDisplayName)
|
.circleCrop()
|
||||||
binding.doubleModeImageViewContainer.visibility = View.VISIBLE
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.into(binding.singleModeImageView)
|
||||||
|
}
|
||||||
|
|
||||||
// clear single image
|
is List<*> -> {
|
||||||
glide.clear(binding.singleModeImageView)
|
val first = groupAvatarOrMemberAvatars.getOrNull(0) as? GroupMemberInfo
|
||||||
binding.singleModeImageView.visibility = View.INVISIBLE
|
val second = groupAvatarOrMemberAvatars.getOrNull(1) as? GroupMemberInfo
|
||||||
} else { // single image mode
|
setShowAsDoubleMode(true)
|
||||||
setProfilePictureIfNeeded(binding.singleModeImageView, publicKey, displayName)
|
Glide.with(binding.doubleModeImageView1)
|
||||||
binding.singleModeImageView.visibility = View.VISIBLE
|
.load(first?.let { it.contactPhoto ?: it.placeholderAvatarPhoto })
|
||||||
|
.error(first?.placeholderAvatarPhoto ?: unknownRecipientDrawable)
|
||||||
|
.circleCrop()
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.into(binding.doubleModeImageView1)
|
||||||
|
|
||||||
// clear multi image
|
Glide.with(binding.doubleModeImageView2)
|
||||||
glide.clear(binding.doubleModeImageView1)
|
.load(second?.let { it.contactPhoto ?: it.placeholderAvatarPhoto })
|
||||||
glide.clear(binding.doubleModeImageView2)
|
.error(second?.placeholderAvatarPhoto ?: unknownRecipientDrawable)
|
||||||
binding.doubleModeImageViewContainer.visibility = View.INVISIBLE
|
.circleCrop()
|
||||||
}
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.into(binding.doubleModeImageView2)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
else -> {
|
||||||
|
setShowAsDoubleMode(false)
|
||||||
private fun setProfilePictureIfNeeded(imageView: ImageView, publicKey: String, displayName: String?) {
|
binding.singleModeImageView.setImageDrawable(unknownRecipientDrawable)
|
||||||
if (publicKey.isNotEmpty()) {
|
}
|
||||||
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
|
|
||||||
if (profilePicturesCache[imageView] == recipient) return
|
|
||||||
profilePicturesCache[imageView] = recipient
|
|
||||||
val signalProfilePicture = recipient.contactPhoto
|
|
||||||
val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject
|
|
||||||
|
|
||||||
glide.clear(imageView)
|
|
||||||
|
|
||||||
val placeholder = PlaceholderAvatarPhoto(publicKey, displayName ?: "${publicKey.take(4)}...${publicKey.takeLast(4)}")
|
|
||||||
|
|
||||||
if (signalProfilePicture != null && avatar != "0" && avatar != "") {
|
|
||||||
glide.load(signalProfilePicture)
|
|
||||||
.placeholder(unknownRecipientDrawable)
|
|
||||||
.centerCrop()
|
|
||||||
.error(glide.load(placeholder))
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.circleCrop()
|
|
||||||
.into(imageView)
|
|
||||||
} else if (recipient.isCommunityRecipient && recipient.groupAvatarId == null) {
|
|
||||||
glide.clear(imageView)
|
|
||||||
glide.load(unknownOpenGroupDrawable)
|
|
||||||
.centerCrop()
|
|
||||||
.circleCrop()
|
|
||||||
.into(imageView)
|
|
||||||
} else {
|
|
||||||
glide.load(placeholder)
|
|
||||||
.placeholder(unknownRecipientDrawable)
|
|
||||||
.centerCrop()
|
|
||||||
.circleCrop()
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE).circleCrop().into(imageView)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
glide.load(unknownRecipientDrawable)
|
|
||||||
.centerCrop()
|
|
||||||
.into(imageView)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun recycle() {
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
profilePicturesCache.clear()
|
private fun loadAsSingleImage(model: LoadModel) {
|
||||||
|
cancelLastLoadJob()
|
||||||
|
|
||||||
|
setShowAsDoubleMode(false)
|
||||||
|
|
||||||
|
// Only clear the old image if the address has changed. This is important as we have a delay
|
||||||
|
// in loading the image, if this view is reused for another address before the image is loaded,
|
||||||
|
// the previous image could be displayed for a short period of time. We would want to avoid
|
||||||
|
// displaying the wrong image, even for a short time.
|
||||||
|
// However, if we are displaying the same user's image again, it's ok to show the old
|
||||||
|
// image until the new one is loaded. This is a trade-off between performance and correctness.
|
||||||
|
if (lastLoadAddress != model.address) {
|
||||||
|
Glide.with(this).clear(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The use of GlobalScope is intentional here, as there is no better lifecycle scope that we can use
|
||||||
|
// to launch a coroutine from a view. The potential memory leak is not a concern here, as
|
||||||
|
// the coroutine is very short-lived. If you change the code here to be long live then you'll
|
||||||
|
// need to find a better scope to launch the coroutine from.
|
||||||
|
lastLoadJob = GlobalScope.launch(Dispatchers.Main) {
|
||||||
|
val (contactPhoto, avatarPlaceholder) = withContext(Dispatchers.Default) {
|
||||||
|
model.loadRecipient(context).let {
|
||||||
|
it.contactPhoto to PlaceholderAvatarPhoto(it.address.serialize(), it.displayName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val address = model.address
|
||||||
|
|
||||||
|
val errorModel: Any = when {
|
||||||
|
address.isCommunity -> unknownOpenGroupDrawable
|
||||||
|
address.isContact -> avatarPlaceholder
|
||||||
|
else -> unknownRecipientDrawable
|
||||||
|
}
|
||||||
|
|
||||||
|
Glide.with(this@ProfilePictureView)
|
||||||
|
.load(contactPhoto)
|
||||||
|
.error(errorModel)
|
||||||
|
.circleCrop()
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.into(binding.singleModeImageView)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// endregion
|
|
||||||
}
|
fun load(recipient: Recipient) {
|
||||||
|
if (recipient.address.isClosedGroup) {
|
||||||
|
loadAsDoubleImages(LoadModel.RecipientModel(recipient))
|
||||||
|
} else {
|
||||||
|
loadAsSingleImage(LoadModel.RecipientModel(recipient))
|
||||||
|
}
|
||||||
|
|
||||||
|
lastLoadAddress = recipient.address
|
||||||
|
}
|
||||||
|
|
||||||
|
fun load(address: Address) {
|
||||||
|
if (address.isClosedGroup) {
|
||||||
|
loadAsDoubleImages(LoadModel.AddressModel(address))
|
||||||
|
} else {
|
||||||
|
loadAsSingleImage(LoadModel.AddressModel(address))
|
||||||
|
}
|
||||||
|
|
||||||
|
lastLoadAddress = address
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Recipient.displayName(): String {
|
||||||
|
return if (isLocalNumber) {
|
||||||
|
TextSecurePreferences.getProfileName(context).orEmpty()
|
||||||
|
} else {
|
||||||
|
profileName ?: name ?: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed interface LoadModel {
|
||||||
|
val address: Address
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the recipient if it's not already loaded.
|
||||||
|
*/
|
||||||
|
fun loadRecipient(context: Context): Recipient
|
||||||
|
|
||||||
|
data class AddressModel(override val address: Address) : LoadModel {
|
||||||
|
override fun loadRecipient(context: Context): Recipient {
|
||||||
|
return Recipient.from(context, address, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RecipientModel(val recipient: Recipient) : LoadModel {
|
||||||
|
override val address: Address
|
||||||
|
get() = recipient.address
|
||||||
|
|
||||||
|
override fun loadRecipient(context: Context): Recipient = recipient
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -53,7 +53,7 @@ class UserView : LinearLayout {
|
|||||||
return contact?.displayName(Contact.ContactContext.REGULAR) ?: publicKey
|
return contact?.displayName(Contact.ContactContext.REGULAR) ?: publicKey
|
||||||
}
|
}
|
||||||
val address = user.address.serialize()
|
val address = user.address.serialize()
|
||||||
binding.profilePictureView.update(user)
|
binding.profilePictureView.load(user)
|
||||||
binding.actionIndicatorImageView.setImageResource(R.drawable.ic_baseline_edit_24)
|
binding.actionIndicatorImageView.setImageResource(R.drawable.ic_baseline_edit_24)
|
||||||
binding.nameTextView.text = if (user.isGroupRecipient) user.name else getUserDisplayName(address)
|
binding.nameTextView.text = if (user.isGroupRecipient) user.name else getUserDisplayName(address)
|
||||||
when (actionIndicator) {
|
when (actionIndicator) {
|
||||||
@ -85,7 +85,6 @@ class UserView : LinearLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun unbind() {
|
fun unbind() {
|
||||||
binding.profilePictureView.recycle()
|
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ class ConversationActionBarView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun update(recipient: Recipient, openGroup: OpenGroup? = null, config: ExpirationConfiguration? = null) {
|
fun update(recipient: Recipient, openGroup: OpenGroup? = null, config: ExpirationConfiguration? = null) {
|
||||||
binding.profilePictureView.update(recipient)
|
binding.profilePictureView.load(recipient)
|
||||||
binding.conversationTitleView.text = recipient.takeUnless { it.isLocalNumber }?.toShortString() ?: context.getString(R.string.note_to_self)
|
binding.conversationTitleView.text = recipient.takeUnless { it.isLocalNumber }?.toShortString() ?: context.getString(R.string.note_to_self)
|
||||||
updateSubtitle(recipient, openGroup, config)
|
updateSubtitle(recipient, openGroup, config)
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
|
|||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
import com.bumptech.glide.RequestManager
|
import com.bumptech.glide.RequestManager
|
||||||
|
import org.thoughtcrime.securesms.database.getLong
|
||||||
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
|
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
|
||||||
import org.thoughtcrime.securesms.showSessionDialog
|
import org.thoughtcrime.securesms.showSessionDialog
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
@ -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 android.view.View
|
||||||
import network.loki.messenger.databinding.ViewMentionCandidateV2Binding
|
import network.loki.messenger.databinding.ViewMentionCandidateV2Binding
|
||||||
|
import org.session.libsession.utilities.Address
|
||||||
import org.thoughtcrime.securesms.conversation.v2.mention.MentionViewModel
|
import org.thoughtcrime.securesms.conversation.v2.mention.MentionViewModel
|
||||||
|
|
||||||
fun ViewMentionCandidateV2Binding.update(candidate: MentionViewModel.Candidate) {
|
fun ViewMentionCandidateV2Binding.update(candidate: MentionViewModel.Candidate) {
|
||||||
mentionCandidateNameTextView.text = candidate.nameHighlighted
|
mentionCandidateNameTextView.text = candidate.nameHighlighted
|
||||||
profilePictureView.publicKey = candidate.member.publicKey
|
profilePictureView.load(Address.fromSerialized(candidate.member.publicKey))
|
||||||
profilePictureView.displayName = candidate.member.name
|
|
||||||
profilePictureView.additionalPublicKey = null
|
|
||||||
profilePictureView.update()
|
|
||||||
moderatorIconImageView.visibility = if (candidate.member.isModerator) View.VISIBLE else View.GONE
|
moderatorIconImageView.visibility = if (candidate.member.isModerator) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@ import android.view.MotionEvent
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.LinearLayout
|
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
@ -26,6 +25,8 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.marginBottom
|
import androidx.core.view.marginBottom
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.RequestManager
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import network.loki.messenger.databinding.ViewEmojiReactionsBinding
|
import network.loki.messenger.databinding.ViewEmojiReactionsBinding
|
||||||
@ -52,8 +53,6 @@ import org.thoughtcrime.securesms.database.ThreadDatabase
|
|||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
import org.thoughtcrime.securesms.groups.OpenGroupManager
|
import org.thoughtcrime.securesms.groups.OpenGroupManager
|
||||||
import org.thoughtcrime.securesms.home.UserDetailsBottomSheet
|
import org.thoughtcrime.securesms.home.UserDetailsBottomSheet
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.RequestManager
|
|
||||||
import org.thoughtcrime.securesms.util.DateUtils
|
import org.thoughtcrime.securesms.util.DateUtils
|
||||||
import org.thoughtcrime.securesms.util.disableClipping
|
import org.thoughtcrime.securesms.util.disableClipping
|
||||||
import org.thoughtcrime.securesms.util.toDp
|
import org.thoughtcrime.securesms.util.toDp
|
||||||
@ -179,8 +178,7 @@ class VisibleMessageView : FrameLayout {
|
|||||||
|
|
||||||
if (isGroupThread && !message.isOutgoing) {
|
if (isGroupThread && !message.isOutgoing) {
|
||||||
if (isEndOfMessageCluster) {
|
if (isEndOfMessageCluster) {
|
||||||
binding.profilePictureView.publicKey = senderAccountID
|
binding.profilePictureView.load(message.individualRecipient)
|
||||||
binding.profilePictureView.update(message.individualRecipient)
|
|
||||||
binding.profilePictureView.setOnClickListener {
|
binding.profilePictureView.setOnClickListener {
|
||||||
if (thread.isCommunityRecipient) {
|
if (thread.isCommunityRecipient) {
|
||||||
val openGroup = lokiThreadDb.getOpenGroupChat(threadID)
|
val openGroup = lokiThreadDb.getOpenGroupChat(threadID)
|
||||||
@ -456,7 +454,6 @@ class VisibleMessageView : FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun recycle() {
|
fun recycle() {
|
||||||
binding.profilePictureView.recycle()
|
|
||||||
binding.messageContentView.root.recycle()
|
binding.messageContentView.root.recycle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,14 +13,14 @@ import org.session.libsession.avatars.ContactPhoto;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
class ContactPhotoFetcher implements DataFetcher<InputStream> {
|
public class ContactPhotoFetcher implements DataFetcher<InputStream> {
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final ContactPhoto contactPhoto;
|
private final ContactPhoto contactPhoto;
|
||||||
|
|
||||||
private InputStream inputStream;
|
private InputStream inputStream;
|
||||||
|
|
||||||
ContactPhotoFetcher(@NonNull Context context, @NonNull ContactPhoto contactPhoto) {
|
public ContactPhotoFetcher(@NonNull Context context, @NonNull ContactPhoto contactPhoto) {
|
||||||
this.context = context.getApplicationContext();
|
this.context = context.getApplicationContext();
|
||||||
this.contactPhoto = contactPhoto;
|
this.contactPhoto = contactPhoto;
|
||||||
}
|
}
|
||||||
|
@ -9,12 +9,15 @@ import org.session.libsession.avatars.PlaceholderAvatarPhoto
|
|||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator
|
import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator
|
||||||
|
|
||||||
class PlaceholderAvatarFetcher(private val context: Context,
|
class PlaceholderAvatarFetcher(
|
||||||
private val photo: PlaceholderAvatarPhoto): DataFetcher<BitmapDrawable> {
|
private val context: Context,
|
||||||
|
private val hashString: String,
|
||||||
|
private val displayName: String
|
||||||
|
): DataFetcher<BitmapDrawable> {
|
||||||
|
|
||||||
override fun loadData(priority: Priority,callback: DataFetcher.DataCallback<in BitmapDrawable>) {
|
override fun loadData(priority: Priority,callback: DataFetcher.DataCallback<in BitmapDrawable>) {
|
||||||
try {
|
try {
|
||||||
val avatar = AvatarPlaceholderGenerator.generate(context, 128, photo.hashString, photo.displayName)
|
val avatar = AvatarPlaceholderGenerator.generate(context, 128, hashString, displayName)
|
||||||
callback.onDataReady(avatar)
|
callback.onDataReady(avatar)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("Loki", "Error in fetching avatar")
|
Log.e("Loki", "Error in fetching avatar")
|
||||||
|
@ -8,6 +8,9 @@ import com.bumptech.glide.load.model.ModelLoader.LoadData
|
|||||||
import com.bumptech.glide.load.model.ModelLoaderFactory
|
import com.bumptech.glide.load.model.ModelLoaderFactory
|
||||||
import com.bumptech.glide.load.model.MultiModelLoaderFactory
|
import com.bumptech.glide.load.model.MultiModelLoaderFactory
|
||||||
import org.session.libsession.avatars.PlaceholderAvatarPhoto
|
import org.session.libsession.avatars.PlaceholderAvatarPhoto
|
||||||
|
import org.session.libsession.utilities.Address
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
|
|
||||||
class PlaceholderAvatarLoader(private val appContext: Context): ModelLoader<PlaceholderAvatarPhoto, BitmapDrawable> {
|
class PlaceholderAvatarLoader(private val appContext: Context): ModelLoader<PlaceholderAvatarPhoto, BitmapDrawable> {
|
||||||
|
|
||||||
@ -17,7 +20,15 @@ class PlaceholderAvatarLoader(private val appContext: Context): ModelLoader<Plac
|
|||||||
height: Int,
|
height: Int,
|
||||||
options: Options
|
options: Options
|
||||||
): LoadData<BitmapDrawable> {
|
): LoadData<BitmapDrawable> {
|
||||||
return LoadData(model, PlaceholderAvatarFetcher(appContext, model))
|
val displayName: String = when {
|
||||||
|
!model.displayName.isNullOrBlank() -> model.displayName.orEmpty()
|
||||||
|
model.hashString == TextSecurePreferences.getLocalNumber(appContext) -> TextSecurePreferences.getProfileName(appContext).orEmpty()
|
||||||
|
else -> Recipient.from(appContext, Address.fromSerialized(model.hashString), false).let {
|
||||||
|
it.profileName ?: it.name ?: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoadData(model, PlaceholderAvatarFetcher(appContext, model.hashString, displayName))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handles(model: PlaceholderAvatarPhoto): Boolean = true
|
override fun handles(model: PlaceholderAvatarPhoto): Boolean = true
|
||||||
|
@ -4,7 +4,6 @@ import android.content.Context
|
|||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.text.TextUtils
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@ -128,11 +127,10 @@ class ConversationView : LinearLayout {
|
|||||||
thread.isRead -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_filled_circle_check)
|
thread.isRead -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_filled_circle_check)
|
||||||
else -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_circle_check)
|
else -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_circle_check)
|
||||||
}
|
}
|
||||||
binding.profilePictureView.update(thread.recipient)
|
binding.profilePictureView.load(thread.recipient)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun recycle() {
|
fun recycle() {
|
||||||
binding.profilePictureView.recycle()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTitle(recipient: Recipient): String? = when {
|
private fun getTitle(recipient: Recipient): String? = when {
|
||||||
|
@ -347,8 +347,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
ApplicationContext.getInstance(this).messageNotifier.setHomeScreenVisible(true)
|
ApplicationContext.getInstance(this).messageNotifier.setHomeScreenVisible(true)
|
||||||
if (textSecurePreferences.getLocalNumber() == null) { return; } // This can be the case after a secondary device is auto-cleared
|
if (textSecurePreferences.getLocalNumber() == null) { return; } // This can be the case after a secondary device is auto-cleared
|
||||||
IdentityKeyUtil.checkUpdate(this)
|
IdentityKeyUtil.checkUpdate(this)
|
||||||
binding.profileButton.recycle() // clear cached image before update tje profilePictureView
|
binding.profileButton.load(Address.fromSerialized(publicKey))
|
||||||
binding.profileButton.update()
|
|
||||||
if (textSecurePreferences.getHasViewedSeed()) {
|
if (textSecurePreferences.getHasViewedSeed()) {
|
||||||
binding.seedReminderView.isVisible = false
|
binding.seedReminderView.isVisible = false
|
||||||
}
|
}
|
||||||
@ -388,10 +387,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateProfileButton() {
|
private fun updateProfileButton() {
|
||||||
binding.profileButton.publicKey = publicKey
|
binding.profileButton.load(Address.fromSerialized(publicKey))
|
||||||
binding.profileButton.displayName = textSecurePreferences.getProfileName()
|
|
||||||
binding.profileButton.recycle()
|
|
||||||
binding.profileButton.update()
|
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
@ -55,8 +55,7 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() {
|
|||||||
val recipient = Recipient.from(requireContext(), Address.fromSerialized(publicKey), false)
|
val recipient = Recipient.from(requireContext(), Address.fromSerialized(publicKey), false)
|
||||||
val threadRecipient = threadDb.getRecipientForThreadId(threadID) ?: return dismiss()
|
val threadRecipient = threadDb.getRecipientForThreadId(threadID) ?: return dismiss()
|
||||||
with(binding) {
|
with(binding) {
|
||||||
profilePictureView.publicKey = publicKey
|
profilePictureView.load(recipient)
|
||||||
profilePictureView.update(recipient)
|
|
||||||
nameTextViewContainer.visibility = View.VISIBLE
|
nameTextViewContainer.visibility = View.VISIBLE
|
||||||
nameTextViewContainer.setOnClickListener {
|
nameTextViewContainer.setOnClickListener {
|
||||||
if (recipient.isOpenGroupInboxRecipient || recipient.isOpenGroupOutboxRecipient) return@setOnClickListener
|
if (recipient.isOpenGroupInboxRecipient || recipient.isOpenGroupOutboxRecipient) return@setOnClickListener
|
||||||
|
@ -11,7 +11,6 @@ import network.loki.messenger.databinding.ViewGlobalSearchHeaderBinding
|
|||||||
import network.loki.messenger.databinding.ViewGlobalSearchResultBinding
|
import network.loki.messenger.databinding.ViewGlobalSearchResultBinding
|
||||||
import network.loki.messenger.databinding.ViewGlobalSearchSubheaderBinding
|
import network.loki.messenger.databinding.ViewGlobalSearchSubheaderBinding
|
||||||
import org.session.libsession.utilities.GroupRecord
|
import org.session.libsession.utilities.GroupRecord
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
|
||||||
import org.thoughtcrime.securesms.search.model.MessageResult
|
import org.thoughtcrime.securesms.search.model.MessageResult
|
||||||
import org.thoughtcrime.securesms.ui.GetString
|
import org.thoughtcrime.securesms.ui.GetString
|
||||||
import java.security.InvalidParameterException
|
import java.security.InvalidParameterException
|
||||||
@ -99,12 +98,6 @@ class GlobalSearchAdapter(private val modelCallback: (Model)->Unit): RecyclerVie
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
|
||||||
if (holder is ContentView) {
|
|
||||||
holder.binding.searchResultProfilePicture.recycle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ContentView(view: View, private val modelCallback: (Model) -> Unit) : RecyclerView.ViewHolder(view) {
|
class ContentView(view: View, private val modelCallback: (Model) -> Unit) : RecyclerView.ViewHolder(view) {
|
||||||
|
|
||||||
val binding = ViewGlobalSearchResultBinding.bind(view)
|
val binding = ViewGlobalSearchResultBinding.bind(view)
|
||||||
@ -114,7 +107,6 @@ class GlobalSearchAdapter(private val modelCallback: (Model)->Unit): RecyclerVie
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun bind(query: String, model: Model) {
|
fun bind(query: String, model: Model) {
|
||||||
binding.searchResultProfilePicture.recycle()
|
|
||||||
when (model) {
|
when (model) {
|
||||||
is Model.GroupConversation -> bindModel(query, model)
|
is Model.GroupConversation -> bindModel(query, model)
|
||||||
is Model.Contact -> bindModel(query, model)
|
is Model.Contact -> bindModel(query, model)
|
||||||
|
@ -93,7 +93,7 @@ fun ContentView.bindModel(query: String?, model: GroupConversation) {
|
|||||||
binding.searchResultSubtitle.isVisible = model.groupRecord.isClosedGroup
|
binding.searchResultSubtitle.isVisible = model.groupRecord.isClosedGroup
|
||||||
binding.searchResultTimestamp.isVisible = false
|
binding.searchResultTimestamp.isVisible = false
|
||||||
val threadRecipient = Recipient.from(binding.root.context, Address.fromSerialized(model.groupRecord.encodedId), false)
|
val threadRecipient = Recipient.from(binding.root.context, Address.fromSerialized(model.groupRecord.encodedId), false)
|
||||||
binding.searchResultProfilePicture.update(threadRecipient)
|
binding.searchResultProfilePicture.load(threadRecipient)
|
||||||
val nameString = model.groupRecord.title
|
val nameString = model.groupRecord.title
|
||||||
binding.searchResultTitle.text = getHighlight(query, nameString)
|
binding.searchResultTitle.text = getHighlight(query, nameString)
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ fun ContentView.bindModel(query: String?, model: ContactModel) = binding.run {
|
|||||||
searchResultTimestamp.isVisible = false
|
searchResultTimestamp.isVisible = false
|
||||||
searchResultSubtitle.text = null
|
searchResultSubtitle.text = null
|
||||||
val recipient = Recipient.from(root.context, Address.fromSerialized(model.contact.accountID), false)
|
val recipient = Recipient.from(root.context, Address.fromSerialized(model.contact.accountID), false)
|
||||||
searchResultProfilePicture.update(recipient)
|
searchResultProfilePicture.load(recipient)
|
||||||
val nameString = if (model.isSelf) root.context.getString(R.string.note_to_self)
|
val nameString = if (model.isSelf) root.context.getString(R.string.note_to_self)
|
||||||
else model.contact.getSearchName()
|
else model.contact.getSearchName()
|
||||||
searchResultTitle.text = getHighlight(query, nameString)
|
searchResultTitle.text = getHighlight(query, nameString)
|
||||||
@ -121,7 +121,7 @@ fun ContentView.bindModel(model: SavedMessages) {
|
|||||||
binding.searchResultSubtitle.isVisible = false
|
binding.searchResultSubtitle.isVisible = false
|
||||||
binding.searchResultTimestamp.isVisible = false
|
binding.searchResultTimestamp.isVisible = false
|
||||||
binding.searchResultTitle.setText(R.string.note_to_self)
|
binding.searchResultTitle.setText(R.string.note_to_self)
|
||||||
binding.searchResultProfilePicture.update(Address.fromSerialized(model.currentUserPublicKey))
|
binding.searchResultProfilePicture.load(Address.fromSerialized(model.currentUserPublicKey))
|
||||||
binding.searchResultProfilePicture.isVisible = true
|
binding.searchResultProfilePicture.isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ fun ContentView.bindModel(query: String?, model: Message) = binding.apply {
|
|||||||
// unreadCountTextView.text = model.unread.toString()
|
// unreadCountTextView.text = model.unread.toString()
|
||||||
// }
|
// }
|
||||||
searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(root.context, Locale.getDefault(), model.messageResult.sentTimestampMs)
|
searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(root.context, Locale.getDefault(), model.messageResult.sentTimestampMs)
|
||||||
searchResultProfilePicture.update(model.messageResult.conversationRecipient)
|
searchResultProfilePicture.load(model.messageResult.conversationRecipient)
|
||||||
val textSpannable = SpannableStringBuilder()
|
val textSpannable = SpannableStringBuilder()
|
||||||
if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) {
|
if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) {
|
||||||
// group chat, bind
|
// group chat, bind
|
||||||
|
@ -49,12 +49,11 @@ class MessageRequestView : LinearLayout {
|
|||||||
binding.snippetTextView.text = snippet
|
binding.snippetTextView.text = snippet
|
||||||
|
|
||||||
post {
|
post {
|
||||||
binding.profilePictureView.update(thread.recipient)
|
binding.profilePictureView.load(thread.recipient)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun recycle() {
|
fun recycle() {
|
||||||
binding.profilePictureView.recycle()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getUserDisplayName(recipient: Recipient): String? {
|
private fun getUserDisplayName(recipient: Recipient): String? {
|
||||||
|
@ -56,6 +56,7 @@ public class SignalGlideModule extends AppGlideModule {
|
|||||||
// builder.setDiskCache(new NoopDiskCacheFactory());
|
// builder.setDiskCache(new NoopDiskCacheFactory());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @noinspection unchecked*/
|
||||||
@Override
|
@Override
|
||||||
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
|
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
|
||||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||||
@ -74,6 +75,7 @@ public class SignalGlideModule extends AppGlideModule {
|
|||||||
registry.append(AttachmentModel.class, InputStream.class, new AttachmentStreamUriLoader.Factory());
|
registry.append(AttachmentModel.class, InputStream.class, new AttachmentStreamUriLoader.Factory());
|
||||||
registry.append(ChunkedImageUrl.class, InputStream.class, new ChunkedImageUrlLoader.Factory());
|
registry.append(ChunkedImageUrl.class, InputStream.class, new ChunkedImageUrlLoader.Factory());
|
||||||
registry.append(PlaceholderAvatarPhoto.class, BitmapDrawable.class, new PlaceholderAvatarLoader.Factory(context));
|
registry.append(PlaceholderAvatarPhoto.class, BitmapDrawable.class, new PlaceholderAvatarLoader.Factory(context));
|
||||||
|
|
||||||
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
|
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,11 +36,6 @@ class BlockedContactsAdapter(val viewModel: BlockedContactsViewModel) : ListAdap
|
|||||||
else holder.select(getItem(position).isSelected)
|
else holder.select(getItem(position).isSelected)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewRecycled(holder: ViewHolder) {
|
|
||||||
super.onViewRecycled(holder)
|
|
||||||
holder.binding.profilePictureView.recycle()
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
|
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
||||||
val glide = Glide.with(itemView)
|
val glide = Glide.with(itemView)
|
||||||
@ -48,9 +43,7 @@ class BlockedContactsAdapter(val viewModel: BlockedContactsViewModel) : ListAdap
|
|||||||
|
|
||||||
fun bind(selectable: SelectableRecipient, toggle: (SelectableRecipient) -> Unit) {
|
fun bind(selectable: SelectableRecipient, toggle: (SelectableRecipient) -> Unit) {
|
||||||
binding.recipientName.text = selectable.item.name
|
binding.recipientName.text = selectable.item.name
|
||||||
with (binding.profilePictureView) {
|
binding.profilePictureView.load(selectable.item)
|
||||||
update(selectable.item)
|
|
||||||
}
|
|
||||||
binding.root.setOnClickListener { toggle(selectable) }
|
binding.root.setOnClickListener { toggle(selectable) }
|
||||||
binding.selectButton.isSelected = selectable.isSelected
|
binding.selectButton.isSelected = selectable.isSelected
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
super.onStart()
|
super.onStart()
|
||||||
|
|
||||||
binding.run {
|
binding.run {
|
||||||
setupProfilePictureView(profilePictureView)
|
loadProfilePicture(profilePictureView)
|
||||||
profilePictureView.setOnClickListener { showEditProfilePictureUI() }
|
profilePictureView.setOnClickListener { showEditProfilePictureUI() }
|
||||||
ctnGroupNameSection.setOnClickListener { startActionMode(DisplayNameEditActionModeCallback()) }
|
ctnGroupNameSection.setOnClickListener { startActionMode(DisplayNameEditActionModeCallback()) }
|
||||||
btnGroupNameDisplay.text = getDisplayName()
|
btnGroupNameDisplay.text = getDisplayName()
|
||||||
@ -185,12 +185,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
private fun getDisplayName(): String =
|
private fun getDisplayName(): String =
|
||||||
TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(hexEncodedPublicKey)
|
TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(hexEncodedPublicKey)
|
||||||
|
|
||||||
private fun setupProfilePictureView(view: ProfilePictureView) {
|
private fun loadProfilePicture(view: ProfilePictureView) {
|
||||||
view.apply {
|
// Always reload the profile picture as it can change on this page.
|
||||||
publicKey = hexEncodedPublicKey
|
view.load(Address.fromSerialized(hexEncodedPublicKey))
|
||||||
displayName = getDisplayName()
|
|
||||||
update()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
@ -331,9 +328,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
|
|
||||||
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@SettingsActivity)
|
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@SettingsActivity)
|
||||||
|
|
||||||
// Update our visuals
|
loadProfilePicture(binding.profilePictureView)
|
||||||
binding.profilePictureView.recycle()
|
|
||||||
binding.profilePictureView.update()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the sync failed then inform the user
|
// If the sync failed then inform the user
|
||||||
@ -408,7 +403,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
cancelButton()
|
cancelButton()
|
||||||
}.apply {
|
}.apply {
|
||||||
val profilePic = findViewById<ProfilePictureView>(R.id.profile_picture_view)
|
val profilePic = findViewById<ProfilePictureView>(R.id.profile_picture_view)
|
||||||
?.also(::setupProfilePictureView)
|
?.also(::loadProfilePicture)
|
||||||
|
|
||||||
val pictureIcon = findViewById<View>(R.id.ic_pictures)
|
val pictureIcon = findViewById<View>(R.id.ic_pictures)
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@ import org.session.libsession.messaging.utilities.AccountId;
|
|||||||
import org.thoughtcrime.securesms.components.ProfilePictureView;
|
import org.thoughtcrime.securesms.components.ProfilePictureView;
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
|
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||||
import com.bumptech.glide.Glide;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -154,7 +153,7 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecip
|
|||||||
callback.onRemoveReaction(reaction.getBaseEmoji(), messageId, reaction.getTimestamp());
|
callback.onRemoveReaction(reaction.getBaseEmoji(), messageId, reaction.getTimestamp());
|
||||||
});
|
});
|
||||||
|
|
||||||
this.avatar.update(reaction.getSender());
|
this.avatar.load(reaction.getSender());
|
||||||
|
|
||||||
if (reaction.getSender().isLocalNumber()) {
|
if (reaction.getSender().isLocalNumber()) {
|
||||||
this.recipient.setText(R.string.ReactionsRecipientAdapter_you);
|
this.recipient.setText(R.string.ReactionsRecipientAdapter_you);
|
||||||
@ -170,7 +169,6 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecip
|
|||||||
}
|
}
|
||||||
|
|
||||||
void unbind() {
|
void unbind() {
|
||||||
avatar.recycle();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@ -361,7 +360,7 @@ fun RowScope.Avatar(recipient: Recipient) {
|
|||||||
) {
|
) {
|
||||||
AndroidView(
|
AndroidView(
|
||||||
factory = {
|
factory = {
|
||||||
ProfilePictureView(it).apply { update(recipient) }
|
ProfilePictureView(it).apply { load(recipient) }
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(46.dp)
|
.width(46.dp)
|
||||||
|
@ -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 com.bumptech.glide.load.Key
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
class PlaceholderAvatarPhoto(val hashString: String,
|
data class PlaceholderAvatarPhoto(
|
||||||
val displayName: String): Key {
|
val hashString: String,
|
||||||
|
val displayName: String?
|
||||||
|
) : Key {
|
||||||
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
|
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
|
||||||
messageDigest.update(hashString.encodeToByteArray())
|
messageDigest.update(hashString.encodeToByteArray())
|
||||||
messageDigest.update(displayName.encodeToByteArray())
|
messageDigest.update(displayName?.encodeToByteArray() ?: byteArrayOf())
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user