mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-25 11:05:25 +00:00
Merge branch 'dev' into strings-squashed
This commit is contained in:
commit
2192c2c007
@ -280,8 +280,6 @@ dependencies {
|
|||||||
implementation 'pl.tajchert:waitingdots:0.1.0'
|
implementation 'pl.tajchert:waitingdots:0.1.0'
|
||||||
implementation 'com.vanniktech:android-image-cropper:4.5.0'
|
implementation 'com.vanniktech:android-image-cropper:4.5.0'
|
||||||
implementation 'com.melnykov:floatingactionbutton:1.3.0'
|
implementation 'com.melnykov:floatingactionbutton:1.3.0'
|
||||||
implementation 'com.google.zxing:android-integration:3.1.0'
|
|
||||||
implementation 'com.google.zxing:core:3.2.1'
|
|
||||||
implementation ('com.davemorrissey.labs:subsampling-scale-image-view:3.6.0') {
|
implementation ('com.davemorrissey.labs:subsampling-scale-image-view:3.6.0') {
|
||||||
exclude group: 'com.android.support', module: 'support-annotations'
|
exclude group: 'com.android.support', module: 'support-annotations'
|
||||||
}
|
}
|
||||||
@ -358,7 +356,6 @@ dependencies {
|
|||||||
testImplementation 'org.conscrypt:conscrypt-openjdk-uber:2.5.2' // For Robolectric
|
testImplementation 'org.conscrypt:conscrypt-openjdk-uber:2.5.2' // For Robolectric
|
||||||
testImplementation 'app.cash.turbine:turbine:1.1.0'
|
testImplementation 'app.cash.turbine:turbine:1.1.0'
|
||||||
|
|
||||||
|
|
||||||
implementation 'com.github.bumptech.glide:compose:1.0.0-alpha.5'
|
implementation 'com.github.bumptech.glide:compose:1.0.0-alpha.5'
|
||||||
implementation "androidx.compose.ui:ui:$composeVersion"
|
implementation "androidx.compose.ui:ui:$composeVersion"
|
||||||
implementation "androidx.compose.animation:animation:$composeVersion"
|
implementation "androidx.compose.animation:animation:$composeVersion"
|
||||||
@ -377,7 +374,8 @@ dependencies {
|
|||||||
implementation "androidx.camera:camera-lifecycle:1.3.2"
|
implementation "androidx.camera:camera-lifecycle:1.3.2"
|
||||||
implementation "androidx.camera:camera-view:1.3.2"
|
implementation "androidx.camera:camera-view:1.3.2"
|
||||||
|
|
||||||
implementation "com.google.mlkit:barcode-scanning:17.2.0"
|
// Note: ZXing 3.5.3 is the latest stable release as of 2024/08/21
|
||||||
|
implementation "com.google.zxing:core:$zxingVersion"
|
||||||
}
|
}
|
||||||
|
|
||||||
static def getLastCommitTimestamp() {
|
static def getLastCommitTimestamp() {
|
||||||
|
@ -18,7 +18,6 @@ import org.session.libsignal.utilities.ExternalStorageUtil.getImageDir
|
|||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.session.libsignal.utilities.NoExternalStorageException
|
import org.session.libsignal.utilities.NoExternalStorageException
|
||||||
import org.thoughtcrime.securesms.util.FileProviderUtil
|
import org.thoughtcrime.securesms.util.FileProviderUtil
|
||||||
import org.thoughtcrime.securesms.util.IntentUtils
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
@ -104,13 +103,8 @@ class AvatarSelection(
|
|||||||
includeClear: Boolean
|
includeClear: Boolean
|
||||||
): Intent {
|
): Intent {
|
||||||
val extraIntents: MutableList<Intent> = LinkedList()
|
val extraIntents: MutableList<Intent> = LinkedList()
|
||||||
var galleryIntent = Intent(Intent.ACTION_PICK)
|
val galleryIntent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||||
galleryIntent.setDataAndType(MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*")
|
|
||||||
|
|
||||||
if (!IntentUtils.isResolvable(context, galleryIntent)) {
|
|
||||||
galleryIntent = Intent(Intent.ACTION_GET_CONTENT)
|
|
||||||
galleryIntent.setType("image/*")
|
galleryIntent.setType("image/*")
|
||||||
}
|
|
||||||
|
|
||||||
if (tempCaptureFile != null) {
|
if (tempCaptureFile != null) {
|
||||||
val uri = FileProviderUtil.getUriFor(context, tempCaptureFile)
|
val uri = FileProviderUtil.getUriFor(context, tempCaptureFile)
|
||||||
|
@ -32,6 +32,7 @@ import org.session.libsession.messaging.contacts.Contact
|
|||||||
import org.session.libsession.utilities.Address
|
import org.session.libsession.utilities.Address
|
||||||
import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY
|
import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY
|
||||||
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
|
||||||
@ -197,8 +198,13 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
clipFloatingInsets()
|
clipFloatingInsets()
|
||||||
|
|
||||||
// set up the user avatar
|
// set up the user avatar
|
||||||
TextSecurePreferences.getLocalNumber(this)?.let {
|
TextSecurePreferences.getLocalNumber(this)?.let{
|
||||||
binding.userAvatar.load(Address.fromSerialized(it))
|
val username = TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(it)
|
||||||
|
binding.userAvatar.apply {
|
||||||
|
publicKey = it
|
||||||
|
displayName = username
|
||||||
|
update()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -335,6 +341,8 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
|
|
||||||
launch {
|
launch {
|
||||||
viewModel.recipient.collect { latestRecipient ->
|
viewModel.recipient.collect { latestRecipient ->
|
||||||
|
binding.contactAvatar.recycle()
|
||||||
|
|
||||||
if (latestRecipient.recipient != null) {
|
if (latestRecipient.recipient != null) {
|
||||||
val contactPublicKey = latestRecipient.recipient.address.serialize()
|
val contactPublicKey = latestRecipient.recipient.address.serialize()
|
||||||
val contactDisplayName = getUserDisplayName(contactPublicKey)
|
val contactDisplayName = getUserDisplayName(contactPublicKey)
|
||||||
@ -342,7 +350,11 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
binding.remoteRecipientName.text = contactDisplayName
|
binding.remoteRecipientName.text = contactDisplayName
|
||||||
|
|
||||||
// sort out the contact's avatar
|
// sort out the contact's avatar
|
||||||
binding.contactAvatar.load(latestRecipient.recipient)
|
binding.contactAvatar.apply {
|
||||||
|
publicKey = contactPublicKey
|
||||||
|
displayName = contactDisplayName
|
||||||
|
update()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,224 +3,164 @@ package org.thoughtcrime.securesms.components
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.FrameLayout
|
import android.view.View
|
||||||
import androidx.core.view.isVisible
|
import android.widget.ImageView
|
||||||
import com.bumptech.glide.Glide
|
import android.widget.RelativeLayout
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import network.loki.messenger.databinding.ViewProfilePictureBinding
|
import network.loki.messenger.databinding.ViewProfilePictureBinding
|
||||||
import org.session.libsession.avatars.ContactColors
|
import org.session.libsession.avatars.ContactColors
|
||||||
import org.session.libsession.avatars.ContactPhoto
|
|
||||||
import org.session.libsession.avatars.PlaceholderAvatarPhoto
|
import org.session.libsession.avatars.PlaceholderAvatarPhoto
|
||||||
|
import org.session.libsession.avatars.ProfileContactPhoto
|
||||||
import org.session.libsession.avatars.ResourceContactPhoto
|
import org.session.libsession.avatars.ResourceContactPhoto
|
||||||
|
import org.session.libsession.messaging.contacts.Contact
|
||||||
import org.session.libsession.utilities.Address
|
import org.session.libsession.utilities.Address
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.AppTextSecurePreferences
|
||||||
|
import org.session.libsession.utilities.GroupUtil
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.database.GroupDatabase
|
import org.session.libsignal.utilities.Log
|
||||||
import javax.inject.Inject
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.RequestManager
|
||||||
|
|
||||||
@AndroidEntryPoint
|
class ProfilePictureView @JvmOverloads constructor(
|
||||||
class ProfilePictureView : FrameLayout {
|
context: Context, attrs: AttributeSet? = null
|
||||||
constructor(context: Context) : super(context)
|
) : RelativeLayout(context, attrs) {
|
||||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
private val TAG = "ProfilePictureView"
|
||||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
|
|
||||||
context,
|
|
||||||
attrs,
|
|
||||||
defStyleAttr
|
|
||||||
)
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var groupDatabase: GroupDatabase
|
|
||||||
|
|
||||||
private val binding = ViewProfilePictureBinding.inflate(LayoutInflater.from(context), this)
|
private val binding = ViewProfilePictureBinding.inflate(LayoutInflater.from(context), this)
|
||||||
private var lastLoadJob: Job? = null
|
private val glide: RequestManager = Glide.with(this)
|
||||||
private var lastLoadAddress: Address? = null
|
private val prefs = AppTextSecurePreferences(context)
|
||||||
|
private val userPublicKey = prefs.getLocalNumber()
|
||||||
|
var publicKey: String? = null
|
||||||
|
var displayName: String? = null
|
||||||
|
var additionalPublicKey: String? = null
|
||||||
|
var additionalDisplayName: String? = null
|
||||||
|
|
||||||
private val unknownRecipientDrawable by lazy(LazyThreadSafetyMode.NONE) {
|
private val profilePicturesCache = mutableMapOf<View, Recipient>()
|
||||||
ResourceContactPhoto(R.drawable.ic_profile_default)
|
private val resourcePadding by lazy {
|
||||||
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false)
|
context.resources.getDimensionPixelSize(R.dimen.normal_padding).toFloat()
|
||||||
|
}
|
||||||
|
private val unknownRecipientDrawable by lazy { ResourceContactPhoto(R.drawable.ic_profile_default)
|
||||||
|
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false, resourcePadding) }
|
||||||
|
private val unknownOpenGroupDrawable by lazy { ResourceContactPhoto(R.drawable.ic_notification)
|
||||||
|
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false, resourcePadding) }
|
||||||
|
|
||||||
|
constructor(context: Context, sender: Recipient): this(context) {
|
||||||
|
update(sender)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val unknownOpenGroupDrawable by lazy(LazyThreadSafetyMode.NONE) {
|
fun update(recipient: Recipient) {
|
||||||
ResourceContactPhoto(R.drawable.ic_notification)
|
recipient.run { update(address, isClosedGroupRecipient, isOpenGroupInboxRecipient) }
|
||||||
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setShowAsDoubleMode(showAsDouble: Boolean) {
|
fun update(
|
||||||
binding.doubleModeImageViewContainer.isVisible = showAsDouble
|
address: Address,
|
||||||
binding.singleModeImageView.isVisible = !showAsDouble
|
isClosedGroupRecipient: Boolean = false,
|
||||||
}
|
isOpenGroupInboxRecipient: Boolean = false
|
||||||
|
) {
|
||||||
|
fun getUserDisplayName(publicKey: String): String = prefs.takeIf { userPublicKey == publicKey }?.getProfileName()
|
||||||
|
?: DatabaseComponent.get(context).sessionContactDatabase().getContactWithAccountID(publicKey)?.displayName(Contact.ContactContext.REGULAR)
|
||||||
|
?: publicKey
|
||||||
|
|
||||||
private fun cancelLastLoadJob() {
|
if (isClosedGroupRecipient) {
|
||||||
lastLoadJob?.cancel()
|
val members = DatabaseComponent.get(context).groupDatabase()
|
||||||
lastLoadJob = null
|
.getGroupMemberAddresses(address.toGroupString(), true)
|
||||||
}
|
.sorted()
|
||||||
|
.take(2)
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
if (members.size <= 1) {
|
||||||
private fun loadAsDoubleImages(model: LoadModel) {
|
publicKey = ""
|
||||||
cancelLastLoadJob()
|
displayName = ""
|
||||||
|
additionalPublicKey = ""
|
||||||
// The use of GlobalScope is intentional here, as there is no better lifecycle scope that we can use
|
additionalDisplayName = ""
|
||||||
// to launch a coroutine from a view. The potential memory leak is not a concern here, as
|
|
||||||
// the coroutine is very short-lived. If you change the code here to be long live then you'll
|
|
||||||
// need to find a better scope to launch the coroutine from.
|
|
||||||
lastLoadJob = GlobalScope.launch(Dispatchers.Main) {
|
|
||||||
data class GroupMemberInfo(
|
|
||||||
val contactPhoto: ContactPhoto?,
|
|
||||||
val placeholderAvatarPhoto: PlaceholderAvatarPhoto,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Load group avatar if available, otherwise load member avatars
|
|
||||||
val groupAvatarOrMemberAvatars = withContext(Dispatchers.Default) {
|
|
||||||
model.loadRecipient(context).contactPhoto
|
|
||||||
?: groupDatabase.getGroupMembers(model.address.toGroupString(), true)
|
|
||||||
.map {
|
|
||||||
GroupMemberInfo(
|
|
||||||
contactPhoto = it.contactPhoto,
|
|
||||||
placeholderAvatarPhoto = PlaceholderAvatarPhoto(
|
|
||||||
hashString = it.address.serialize(),
|
|
||||||
displayName = it.displayName()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
when (groupAvatarOrMemberAvatars) {
|
|
||||||
is ContactPhoto -> {
|
|
||||||
setShowAsDoubleMode(false)
|
|
||||||
Glide.with(this@ProfilePictureView)
|
|
||||||
.load(groupAvatarOrMemberAvatars)
|
|
||||||
.error(unknownRecipientDrawable)
|
|
||||||
.circleCrop()
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.into(binding.singleModeImageView)
|
|
||||||
}
|
|
||||||
|
|
||||||
is List<*> -> {
|
|
||||||
val first = groupAvatarOrMemberAvatars.getOrNull(0) as? GroupMemberInfo
|
|
||||||
val second = groupAvatarOrMemberAvatars.getOrNull(1) as? GroupMemberInfo
|
|
||||||
setShowAsDoubleMode(true)
|
|
||||||
Glide.with(binding.doubleModeImageView1)
|
|
||||||
.load(first?.let { it.contactPhoto ?: it.placeholderAvatarPhoto })
|
|
||||||
.error(first?.placeholderAvatarPhoto ?: unknownRecipientDrawable)
|
|
||||||
.circleCrop()
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.into(binding.doubleModeImageView1)
|
|
||||||
|
|
||||||
Glide.with(binding.doubleModeImageView2)
|
|
||||||
.load(second?.let { it.contactPhoto ?: it.placeholderAvatarPhoto })
|
|
||||||
.error(second?.placeholderAvatarPhoto ?: unknownRecipientDrawable)
|
|
||||||
.circleCrop()
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.into(binding.doubleModeImageView2)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
setShowAsDoubleMode(false)
|
|
||||||
binding.singleModeImageView.setImageDrawable(unknownRecipientDrawable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
private fun loadAsSingleImage(model: LoadModel) {
|
|
||||||
cancelLastLoadJob()
|
|
||||||
|
|
||||||
setShowAsDoubleMode(false)
|
|
||||||
|
|
||||||
// Only clear the old image if the address has changed. This is important as we have a delay
|
|
||||||
// in loading the image, if this view is reused for another address before the image is loaded,
|
|
||||||
// the previous image could be displayed for a short period of time. We would want to avoid
|
|
||||||
// displaying the wrong image, even for a short time.
|
|
||||||
// However, if we are displaying the same user's image again, it's ok to show the old
|
|
||||||
// image until the new one is loaded. This is a trade-off between performance and correctness.
|
|
||||||
if (lastLoadAddress != model.address) {
|
|
||||||
Glide.with(this).clear(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The use of GlobalScope is intentional here, as there is no better lifecycle scope that we can use
|
|
||||||
// to launch a coroutine from a view. The potential memory leak is not a concern here, as
|
|
||||||
// the coroutine is very short-lived. If you change the code here to be long live then you'll
|
|
||||||
// need to find a better scope to launch the coroutine from.
|
|
||||||
lastLoadJob = GlobalScope.launch(Dispatchers.Main) {
|
|
||||||
val (contactPhoto, avatarPlaceholder) = withContext(Dispatchers.Default) {
|
|
||||||
model.loadRecipient(context).let {
|
|
||||||
it.contactPhoto to PlaceholderAvatarPhoto(it.address.serialize(), it.displayName())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val address = model.address
|
|
||||||
|
|
||||||
val errorModel: Any = when {
|
|
||||||
address.isCommunity -> unknownOpenGroupDrawable
|
|
||||||
address.isContact -> avatarPlaceholder
|
|
||||||
else -> unknownRecipientDrawable
|
|
||||||
}
|
|
||||||
|
|
||||||
Glide.with(this@ProfilePictureView)
|
|
||||||
.load(contactPhoto)
|
|
||||||
.error(errorModel)
|
|
||||||
.circleCrop()
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.into(binding.singleModeImageView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun load(recipient: Recipient) {
|
|
||||||
if (recipient.address.isClosedGroup) {
|
|
||||||
loadAsDoubleImages(LoadModel.RecipientModel(recipient))
|
|
||||||
} else {
|
} else {
|
||||||
loadAsSingleImage(LoadModel.RecipientModel(recipient))
|
val pk = members.getOrNull(0)?.serialize() ?: ""
|
||||||
|
publicKey = pk
|
||||||
|
displayName = getUserDisplayName(pk)
|
||||||
|
val apk = members.getOrNull(1)?.serialize() ?: ""
|
||||||
|
additionalPublicKey = apk
|
||||||
|
additionalDisplayName = getUserDisplayName(apk)
|
||||||
}
|
}
|
||||||
|
} else if(isOpenGroupInboxRecipient) {
|
||||||
lastLoadAddress = recipient.address
|
val publicKey = GroupUtil.getDecodedOpenGroupInboxAccountId(address.serialize())
|
||||||
}
|
this.publicKey = publicKey
|
||||||
|
displayName = getUserDisplayName(publicKey)
|
||||||
fun load(address: Address) {
|
additionalPublicKey = null
|
||||||
if (address.isClosedGroup) {
|
|
||||||
loadAsDoubleImages(LoadModel.AddressModel(address))
|
|
||||||
} else {
|
} else {
|
||||||
loadAsSingleImage(LoadModel.AddressModel(address))
|
val publicKey = address.serialize()
|
||||||
|
this.publicKey = publicKey
|
||||||
|
displayName = getUserDisplayName(publicKey)
|
||||||
|
additionalPublicKey = null
|
||||||
|
}
|
||||||
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
lastLoadAddress = address
|
fun update() {
|
||||||
|
val publicKey = publicKey ?: return Log.w(TAG, "Could not find public key to update profile picture")
|
||||||
|
val additionalPublicKey = additionalPublicKey
|
||||||
|
// if we have a multi avatar setup
|
||||||
|
if (additionalPublicKey != null) {
|
||||||
|
setProfilePictureIfNeeded(binding.doubleModeImageView1, publicKey, displayName)
|
||||||
|
setProfilePictureIfNeeded(binding.doubleModeImageView2, additionalPublicKey, additionalDisplayName)
|
||||||
|
binding.doubleModeImageViewContainer.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
// clear single image
|
||||||
|
glide.clear(binding.singleModeImageView)
|
||||||
|
binding.singleModeImageView.visibility = View.INVISIBLE
|
||||||
|
} else { // single image mode
|
||||||
|
setProfilePictureIfNeeded(binding.singleModeImageView, publicKey, displayName)
|
||||||
|
binding.singleModeImageView.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
// clear multi image
|
||||||
|
glide.clear(binding.doubleModeImageView1)
|
||||||
|
glide.clear(binding.doubleModeImageView2)
|
||||||
|
binding.doubleModeImageViewContainer.visibility = View.INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Recipient.displayName(): String {
|
}
|
||||||
return if (isLocalNumber) {
|
|
||||||
TextSecurePreferences.getProfileName(context).orEmpty()
|
private fun setProfilePictureIfNeeded(imageView: ImageView, publicKey: String, displayName: String?) {
|
||||||
|
if (publicKey.isNotEmpty()) {
|
||||||
|
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
|
||||||
|
if (profilePicturesCache[imageView] == recipient) return
|
||||||
|
profilePicturesCache[imageView] = recipient
|
||||||
|
val signalProfilePicture = recipient.contactPhoto
|
||||||
|
val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject
|
||||||
|
|
||||||
|
glide.clear(imageView)
|
||||||
|
|
||||||
|
val placeholder = PlaceholderAvatarPhoto(publicKey, displayName ?: "${publicKey.take(4)}...${publicKey.takeLast(4)}")
|
||||||
|
|
||||||
|
if (signalProfilePicture != null && avatar != "0" && avatar != "") {
|
||||||
|
glide.load(signalProfilePicture)
|
||||||
|
.placeholder(unknownRecipientDrawable)
|
||||||
|
.centerCrop()
|
||||||
|
.error(glide.load(placeholder))
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.circleCrop()
|
||||||
|
.into(imageView)
|
||||||
|
} else if (recipient.isCommunityRecipient && recipient.groupAvatarId == null) {
|
||||||
|
glide.clear(imageView)
|
||||||
|
glide.load(unknownOpenGroupDrawable)
|
||||||
|
.centerCrop()
|
||||||
|
.circleCrop()
|
||||||
|
.into(imageView)
|
||||||
} else {
|
} else {
|
||||||
profileName ?: name ?: ""
|
glide.load(placeholder)
|
||||||
|
.placeholder(unknownRecipientDrawable)
|
||||||
|
.centerCrop()
|
||||||
|
.circleCrop()
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE).circleCrop().into(imageView)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
glide.load(unknownRecipientDrawable)
|
||||||
|
.centerCrop()
|
||||||
|
.into(imageView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed interface LoadModel {
|
fun recycle() {
|
||||||
val address: Address
|
profilePicturesCache.clear()
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the recipient if it's not already loaded.
|
|
||||||
*/
|
|
||||||
fun loadRecipient(context: Context): Recipient
|
|
||||||
|
|
||||||
data class AddressModel(override val address: Address) : LoadModel {
|
|
||||||
override fun loadRecipient(context: Context): Recipient {
|
|
||||||
return Recipient.from(context, address, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class RecipientModel(val recipient: Recipient) : LoadModel {
|
|
||||||
override val address: Address
|
|
||||||
get() = recipient.address
|
|
||||||
|
|
||||||
override fun loadRecipient(context: Context): Recipient = recipient
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// endregion
|
||||||
}
|
}
|
@ -55,7 +55,7 @@ class UserView : LinearLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val address = user.address.serialize()
|
val address = user.address.serialize()
|
||||||
binding.profilePictureView.load(user)
|
binding.profilePictureView.update(user)
|
||||||
binding.actionIndicatorImageView.setImageResource(R.drawable.ic_baseline_edit_24)
|
binding.actionIndicatorImageView.setImageResource(R.drawable.ic_baseline_edit_24)
|
||||||
binding.nameTextView.text = if (user.isGroupRecipient) user.name else getUserDisplayName(address)
|
binding.nameTextView.text = if (user.isGroupRecipient) user.name else getUserDisplayName(address)
|
||||||
when (actionIndicator) {
|
when (actionIndicator) {
|
||||||
@ -86,6 +86,8 @@ class UserView : LinearLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unbind() { /* Nothing to do */ }
|
fun unbind() {
|
||||||
|
binding.profilePictureView.recycle()
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
@ -85,9 +85,8 @@ class ConversationActionBarView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun update(recipient: Recipient, openGroup: OpenGroup? = null, config: ExpirationConfiguration? = null) {
|
fun update(recipient: Recipient, openGroup: OpenGroup? = null, config: ExpirationConfiguration? = null) {
|
||||||
binding.profilePictureView.load(recipient)
|
binding.profilePictureView.update(recipient)
|
||||||
binding.conversationTitleView.text = recipient.takeUnless { it.isLocalNumber }?.toShortString() ?: context.getString(R.string.noteToSelf)
|
binding.conversationTitleView.text = recipient.takeUnless { it.isLocalNumber }?.toShortString() ?: context.getString(R.string.noteToSelf)
|
||||||
|
|
||||||
updateSubtitle(recipient, openGroup, config)
|
updateSubtitle(recipient, openGroup, config)
|
||||||
|
|
||||||
binding.conversationTitleContainer.modifyLayoutParams<MarginLayoutParams> {
|
binding.conversationTitleContainer.modifyLayoutParams<MarginLayoutParams> {
|
||||||
|
@ -48,7 +48,7 @@ import org.thoughtcrime.securesms.ui.LoadingArcOr
|
|||||||
import org.thoughtcrime.securesms.ui.components.AppBarCloseIcon
|
import org.thoughtcrime.securesms.ui.components.AppBarCloseIcon
|
||||||
import org.thoughtcrime.securesms.ui.components.BackAppBar
|
import org.thoughtcrime.securesms.ui.components.BackAppBar
|
||||||
import org.thoughtcrime.securesms.ui.components.BorderlessButtonWithIcon
|
import org.thoughtcrime.securesms.ui.components.BorderlessButtonWithIcon
|
||||||
import org.thoughtcrime.securesms.ui.components.MaybeScanQrCode
|
import org.thoughtcrime.securesms.ui.components.QRScannerScreen
|
||||||
import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton
|
import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton
|
||||||
import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField
|
import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField
|
||||||
import org.thoughtcrime.securesms.ui.components.SessionTabRow
|
import org.thoughtcrime.securesms.ui.components.SessionTabRow
|
||||||
@ -89,7 +89,7 @@ internal fun NewMessage(
|
|||||||
HorizontalPager(pagerState) {
|
HorizontalPager(pagerState) {
|
||||||
when (TITLES[it]) {
|
when (TITLES[it]) {
|
||||||
R.string.accountIdEnter -> EnterAccountId(state, callbacks, onHelp)
|
R.string.accountIdEnter -> EnterAccountId(state, callbacks, onHelp)
|
||||||
R.string.qrScan -> MaybeScanQrCode(qrErrors, onScan = callbacks::onScanQrCode)
|
R.string.qrScan -> QRScannerScreen(qrErrors, onScan = callbacks::onScanQrCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,6 @@ import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
|
|||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
import com.bumptech.glide.RequestManager
|
import com.bumptech.glide.RequestManager
|
||||||
import org.thoughtcrime.securesms.database.getLong
|
|
||||||
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
|
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
|
||||||
import org.thoughtcrime.securesms.showSessionDialog
|
import org.thoughtcrime.securesms.showSessionDialog
|
||||||
|
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
package org.thoughtcrime.securesms.conversation.v2.components
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.BaseAdapter
|
||||||
|
import android.widget.ListView
|
||||||
|
import org.session.libsession.messaging.mentions.Mention
|
||||||
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
|
import com.bumptech.glide.RequestManager
|
||||||
|
import org.thoughtcrime.securesms.util.toPx
|
||||||
|
|
||||||
|
class MentionCandidateSelectionView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : ListView(context, attrs, defStyleAttr) {
|
||||||
|
private var mentionCandidates = listOf<Mention>()
|
||||||
|
set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.mentionCandidates = newValue }
|
||||||
|
var glide: RequestManager? = null
|
||||||
|
set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.glide = newValue }
|
||||||
|
var openGroupServer: String? = null
|
||||||
|
set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.openGroupServer = openGroupServer }
|
||||||
|
var openGroupRoom: String? = null
|
||||||
|
set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.openGroupRoom = openGroupRoom }
|
||||||
|
var onMentionCandidateSelected: ((Mention) -> Unit)? = null
|
||||||
|
|
||||||
|
private val mentionCandidateSelectionViewAdapter by lazy { Adapter(context) }
|
||||||
|
|
||||||
|
private class Adapter(private val context: Context) : BaseAdapter() {
|
||||||
|
var mentionCandidates = listOf<Mention>()
|
||||||
|
set(newValue) { field = newValue; notifyDataSetChanged() }
|
||||||
|
var glide: RequestManager? = null
|
||||||
|
var openGroupServer: String? = null
|
||||||
|
var openGroupRoom: String? = null
|
||||||
|
|
||||||
|
override fun getCount(): Int {
|
||||||
|
return mentionCandidates.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemId(position: Int): Long {
|
||||||
|
return position.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItem(position: Int): Mention {
|
||||||
|
return mentionCandidates[position]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getView(position: Int, cellToBeReused: View?, parent: ViewGroup): View {
|
||||||
|
val cell = cellToBeReused as MentionCandidateView? ?: MentionCandidateView(context)
|
||||||
|
val mentionCandidate = getItem(position)
|
||||||
|
cell.glide = glide
|
||||||
|
cell.mentionCandidate = mentionCandidate
|
||||||
|
cell.openGroupServer = openGroupServer
|
||||||
|
cell.openGroupRoom = openGroupRoom
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
||||||
|
constructor(context: Context) : this(context, null)
|
||||||
|
|
||||||
|
init {
|
||||||
|
clipToOutline = true
|
||||||
|
adapter = mentionCandidateSelectionViewAdapter
|
||||||
|
mentionCandidateSelectionViewAdapter.mentionCandidates = mentionCandidates
|
||||||
|
setOnItemClickListener { _, _, position, _ ->
|
||||||
|
onMentionCandidateSelected?.invoke(mentionCandidates[position])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun show(mentionCandidates: List<Mention>, threadID: Long) {
|
||||||
|
val openGroup = DatabaseComponent.get(context).lokiThreadDatabase().getOpenGroupChat(threadID)
|
||||||
|
if (openGroup != null) {
|
||||||
|
openGroupServer = openGroup.server
|
||||||
|
openGroupRoom = openGroup.room
|
||||||
|
}
|
||||||
|
this.mentionCandidates = mentionCandidates
|
||||||
|
val layoutParams = this.layoutParams as ViewGroup.LayoutParams
|
||||||
|
layoutParams.height = toPx(Math.min(mentionCandidates.count(), 4) * 44, resources)
|
||||||
|
this.layoutParams = layoutParams
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hide() {
|
||||||
|
val layoutParams = this.layoutParams as ViewGroup.LayoutParams
|
||||||
|
layoutParams.height = 0
|
||||||
|
this.layoutParams = layoutParams
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package org.thoughtcrime.securesms.conversation.v2.components
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import network.loki.messenger.databinding.ViewMentionCandidateBinding
|
||||||
|
import org.session.libsession.messaging.mentions.Mention
|
||||||
|
import org.thoughtcrime.securesms.groups.OpenGroupManager
|
||||||
|
import com.bumptech.glide.RequestManager
|
||||||
|
|
||||||
|
class MentionCandidateView : LinearLayout {
|
||||||
|
private lateinit var binding: ViewMentionCandidateBinding
|
||||||
|
var mentionCandidate = Mention("", "")
|
||||||
|
set(newValue) { field = newValue; update() }
|
||||||
|
var glide: RequestManager? = null
|
||||||
|
var openGroupServer: String? = null
|
||||||
|
var openGroupRoom: String? = null
|
||||||
|
|
||||||
|
constructor(context: Context) : this(context, null)
|
||||||
|
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
||||||
|
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() }
|
||||||
|
|
||||||
|
private fun initialize() {
|
||||||
|
binding = ViewMentionCandidateBinding.inflate(LayoutInflater.from(context), this, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun update() = with(binding) {
|
||||||
|
mentionCandidateNameTextView.text = mentionCandidate.displayName
|
||||||
|
profilePictureView.publicKey = mentionCandidate.publicKey
|
||||||
|
profilePictureView.displayName = mentionCandidate.displayName
|
||||||
|
profilePictureView.additionalPublicKey = null
|
||||||
|
profilePictureView.update()
|
||||||
|
if (openGroupServer != null && openGroupRoom != null) {
|
||||||
|
val isUserModerator = OpenGroupManager.isUserModerator(context, "$openGroupRoom.$openGroupServer", mentionCandidate.publicKey)
|
||||||
|
moderatorIconImageView.visibility = if (isUserModerator) View.VISIBLE else View.GONE
|
||||||
|
} else {
|
||||||
|
moderatorIconImageView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,11 +2,13 @@ package org.thoughtcrime.securesms.conversation.v2.input_bar.mentions
|
|||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import network.loki.messenger.databinding.ViewMentionCandidateV2Binding
|
import network.loki.messenger.databinding.ViewMentionCandidateV2Binding
|
||||||
import org.session.libsession.utilities.Address
|
|
||||||
import org.thoughtcrime.securesms.conversation.v2.mention.MentionViewModel
|
import org.thoughtcrime.securesms.conversation.v2.mention.MentionViewModel
|
||||||
|
|
||||||
fun ViewMentionCandidateV2Binding.update(candidate: MentionViewModel.Candidate) {
|
fun ViewMentionCandidateV2Binding.update(candidate: MentionViewModel.Candidate) {
|
||||||
mentionCandidateNameTextView.text = candidate.nameHighlighted
|
mentionCandidateNameTextView.text = candidate.nameHighlighted
|
||||||
profilePictureView.load(Address.fromSerialized(candidate.member.publicKey))
|
profilePictureView.publicKey = candidate.member.publicKey
|
||||||
|
profilePictureView.displayName = candidate.member.name
|
||||||
|
profilePictureView.additionalPublicKey = null
|
||||||
|
profilePictureView.update()
|
||||||
moderatorIconImageView.visibility = if (candidate.member.isModerator) View.VISIBLE else View.GONE
|
moderatorIconImageView.visibility = if (candidate.member.isModerator) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import android.view.MotionEvent
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
import android.widget.LinearLayout
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
@ -25,8 +26,6 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.marginBottom
|
import androidx.core.view.marginBottom
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.RequestManager
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@ -60,6 +59,8 @@ import org.thoughtcrime.securesms.database.ThreadDatabase
|
|||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
import org.thoughtcrime.securesms.groups.OpenGroupManager
|
import org.thoughtcrime.securesms.groups.OpenGroupManager
|
||||||
import org.thoughtcrime.securesms.home.UserDetailsBottomSheet
|
import org.thoughtcrime.securesms.home.UserDetailsBottomSheet
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.RequestManager
|
||||||
import org.thoughtcrime.securesms.util.DateUtils
|
import org.thoughtcrime.securesms.util.DateUtils
|
||||||
import org.thoughtcrime.securesms.util.disableClipping
|
import org.thoughtcrime.securesms.util.disableClipping
|
||||||
import org.thoughtcrime.securesms.util.toDp
|
import org.thoughtcrime.securesms.util.toDp
|
||||||
@ -178,7 +179,8 @@ class VisibleMessageView : FrameLayout {
|
|||||||
|
|
||||||
if (isGroupThread && !message.isOutgoing) {
|
if (isGroupThread && !message.isOutgoing) {
|
||||||
if (isEndOfMessageCluster) {
|
if (isEndOfMessageCluster) {
|
||||||
binding.profilePictureView.load(message.individualRecipient)
|
binding.profilePictureView.publicKey = senderAccountID
|
||||||
|
binding.profilePictureView.update(message.individualRecipient)
|
||||||
binding.profilePictureView.setOnClickListener {
|
binding.profilePictureView.setOnClickListener {
|
||||||
if (thread.isCommunityRecipient) {
|
if (thread.isCommunityRecipient) {
|
||||||
val openGroup = lokiThreadDb.getOpenGroupChat(threadID)
|
val openGroup = lokiThreadDb.getOpenGroupChat(threadID)
|
||||||
@ -463,6 +465,7 @@ 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;
|
||||||
|
|
||||||
public class ContactPhotoFetcher implements DataFetcher<InputStream> {
|
class ContactPhotoFetcher implements DataFetcher<InputStream> {
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final ContactPhoto contactPhoto;
|
private final ContactPhoto contactPhoto;
|
||||||
|
|
||||||
private InputStream inputStream;
|
private InputStream inputStream;
|
||||||
|
|
||||||
public ContactPhotoFetcher(@NonNull Context context, @NonNull ContactPhoto contactPhoto) {
|
ContactPhotoFetcher(@NonNull Context context, @NonNull ContactPhoto contactPhoto) {
|
||||||
this.context = context.getApplicationContext();
|
this.context = context.getApplicationContext();
|
||||||
this.contactPhoto = contactPhoto;
|
this.contactPhoto = contactPhoto;
|
||||||
}
|
}
|
||||||
|
@ -9,15 +9,12 @@ import org.session.libsession.avatars.PlaceholderAvatarPhoto
|
|||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator
|
import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator
|
||||||
|
|
||||||
class PlaceholderAvatarFetcher(
|
class PlaceholderAvatarFetcher(private val context: Context,
|
||||||
private val context: Context,
|
private val photo: PlaceholderAvatarPhoto): DataFetcher<BitmapDrawable> {
|
||||||
private val hashString: String,
|
|
||||||
private val displayName: String
|
|
||||||
): DataFetcher<BitmapDrawable> {
|
|
||||||
|
|
||||||
override fun loadData(priority: Priority,callback: DataFetcher.DataCallback<in BitmapDrawable>) {
|
override fun loadData(priority: Priority,callback: DataFetcher.DataCallback<in BitmapDrawable>) {
|
||||||
try {
|
try {
|
||||||
val avatar = AvatarPlaceholderGenerator.generate(context, 128, hashString, displayName)
|
val avatar = AvatarPlaceholderGenerator.generate(context, 128, photo.hashString, photo.displayName)
|
||||||
callback.onDataReady(avatar)
|
callback.onDataReady(avatar)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("Loki", "Error in fetching avatar")
|
Log.e("Loki", "Error in fetching avatar")
|
||||||
|
@ -8,9 +8,6 @@ import com.bumptech.glide.load.model.ModelLoader.LoadData
|
|||||||
import com.bumptech.glide.load.model.ModelLoaderFactory
|
import com.bumptech.glide.load.model.ModelLoaderFactory
|
||||||
import com.bumptech.glide.load.model.MultiModelLoaderFactory
|
import com.bumptech.glide.load.model.MultiModelLoaderFactory
|
||||||
import org.session.libsession.avatars.PlaceholderAvatarPhoto
|
import org.session.libsession.avatars.PlaceholderAvatarPhoto
|
||||||
import org.session.libsession.utilities.Address
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
|
||||||
|
|
||||||
class PlaceholderAvatarLoader(private val appContext: Context): ModelLoader<PlaceholderAvatarPhoto, BitmapDrawable> {
|
class PlaceholderAvatarLoader(private val appContext: Context): ModelLoader<PlaceholderAvatarPhoto, BitmapDrawable> {
|
||||||
|
|
||||||
@ -20,15 +17,7 @@ class PlaceholderAvatarLoader(private val appContext: Context): ModelLoader<Plac
|
|||||||
height: Int,
|
height: Int,
|
||||||
options: Options
|
options: Options
|
||||||
): LoadData<BitmapDrawable> {
|
): LoadData<BitmapDrawable> {
|
||||||
val displayName: String = when {
|
return LoadData(model, PlaceholderAvatarFetcher(appContext, model))
|
||||||
!model.displayName.isNullOrBlank() -> model.displayName.orEmpty()
|
|
||||||
model.hashString == TextSecurePreferences.getLocalNumber(appContext) -> TextSecurePreferences.getProfileName(appContext).orEmpty()
|
|
||||||
else -> Recipient.from(appContext, Address.fromSerialized(model.hashString), false).let {
|
|
||||||
it.profileName ?: it.name ?: ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return LoadData(model, PlaceholderAvatarFetcher(appContext, model.hashString, displayName))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handles(model: PlaceholderAvatarPhoto): Boolean = true
|
override fun handles(model: PlaceholderAvatarPhoto): Boolean = true
|
||||||
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.text.TextUtils
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@ -127,10 +128,11 @@ class ConversationView : LinearLayout {
|
|||||||
thread.isRead -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_filled_circle_check)
|
thread.isRead -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_filled_circle_check)
|
||||||
else -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_circle_check)
|
else -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_circle_check)
|
||||||
}
|
}
|
||||||
binding.profilePictureView.load(thread.recipient)
|
binding.profilePictureView.update(thread.recipient)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun recycle() {
|
fun recycle() {
|
||||||
|
binding.profilePictureView.recycle()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTitle(recipient: Recipient): String? = when {
|
private fun getTitle(recipient: Recipient): String? = when {
|
||||||
|
@ -478,7 +478,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
ApplicationContext.getInstance(this).messageNotifier.setHomeScreenVisible(true)
|
ApplicationContext.getInstance(this).messageNotifier.setHomeScreenVisible(true)
|
||||||
if (textSecurePreferences.getLocalNumber() == null) { return; } // This can be the case after a secondary device is auto-cleared
|
if (textSecurePreferences.getLocalNumber() == null) { return; } // This can be the case after a secondary device is auto-cleared
|
||||||
IdentityKeyUtil.checkUpdate(this)
|
IdentityKeyUtil.checkUpdate(this)
|
||||||
binding.profileButton.load(Address.fromSerialized(publicKey))
|
binding.profileButton.recycle() // clear cached image before update tje profilePictureView
|
||||||
|
binding.profileButton.update()
|
||||||
if (textSecurePreferences.getHasViewedSeed()) {
|
if (textSecurePreferences.getHasViewedSeed()) {
|
||||||
binding.seedReminderView.isVisible = false
|
binding.seedReminderView.isVisible = false
|
||||||
}
|
}
|
||||||
@ -518,7 +519,10 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateProfileButton() {
|
private fun updateProfileButton() {
|
||||||
binding.profileButton.load(Address.fromSerialized(publicKey))
|
binding.profileButton.publicKey = publicKey
|
||||||
|
binding.profileButton.displayName = textSecurePreferences.getProfileName()
|
||||||
|
binding.profileButton.recycle()
|
||||||
|
binding.profileButton.update()
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
@ -55,7 +55,8 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() {
|
|||||||
val recipient = Recipient.from(requireContext(), Address.fromSerialized(publicKey), false)
|
val recipient = Recipient.from(requireContext(), Address.fromSerialized(publicKey), false)
|
||||||
val threadRecipient = threadDb.getRecipientForThreadId(threadID) ?: return dismiss()
|
val threadRecipient = threadDb.getRecipientForThreadId(threadID) ?: return dismiss()
|
||||||
with(binding) {
|
with(binding) {
|
||||||
profilePictureView.load(recipient)
|
profilePictureView.publicKey = publicKey
|
||||||
|
profilePictureView.update(recipient)
|
||||||
nameTextViewContainer.visibility = View.VISIBLE
|
nameTextViewContainer.visibility = View.VISIBLE
|
||||||
nameTextViewContainer.setOnClickListener {
|
nameTextViewContainer.setOnClickListener {
|
||||||
if (recipient.isOpenGroupInboxRecipient || recipient.isOpenGroupOutboxRecipient) return@setOnClickListener
|
if (recipient.isOpenGroupInboxRecipient || recipient.isOpenGroupOutboxRecipient) return@setOnClickListener
|
||||||
|
@ -11,6 +11,7 @@ import network.loki.messenger.databinding.ViewGlobalSearchHeaderBinding
|
|||||||
import network.loki.messenger.databinding.ViewGlobalSearchResultBinding
|
import network.loki.messenger.databinding.ViewGlobalSearchResultBinding
|
||||||
import network.loki.messenger.databinding.ViewGlobalSearchSubheaderBinding
|
import network.loki.messenger.databinding.ViewGlobalSearchSubheaderBinding
|
||||||
import org.session.libsession.utilities.GroupRecord
|
import org.session.libsession.utilities.GroupRecord
|
||||||
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.search.model.MessageResult
|
import org.thoughtcrime.securesms.search.model.MessageResult
|
||||||
import org.thoughtcrime.securesms.ui.GetString
|
import org.thoughtcrime.securesms.ui.GetString
|
||||||
import java.security.InvalidParameterException
|
import java.security.InvalidParameterException
|
||||||
@ -98,6 +99,12 @@ class GlobalSearchAdapter(private val modelCallback: (Model)->Unit): RecyclerVie
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
||||||
|
if (holder is ContentView) {
|
||||||
|
holder.binding.searchResultProfilePicture.recycle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ContentView(view: View, private val modelCallback: (Model) -> Unit) : RecyclerView.ViewHolder(view) {
|
class ContentView(view: View, private val modelCallback: (Model) -> Unit) : RecyclerView.ViewHolder(view) {
|
||||||
|
|
||||||
val binding = ViewGlobalSearchResultBinding.bind(view)
|
val binding = ViewGlobalSearchResultBinding.bind(view)
|
||||||
@ -107,6 +114,7 @@ class GlobalSearchAdapter(private val modelCallback: (Model)->Unit): RecyclerVie
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun bind(query: String, model: Model) {
|
fun bind(query: String, model: Model) {
|
||||||
|
binding.searchResultProfilePicture.recycle()
|
||||||
when (model) {
|
when (model) {
|
||||||
is Model.GroupConversation -> bindModel(query, model)
|
is Model.GroupConversation -> bindModel(query, model)
|
||||||
is Model.Contact -> bindModel(query, model)
|
is Model.Contact -> bindModel(query, model)
|
||||||
|
@ -92,7 +92,7 @@ fun ContentView.bindModel(query: String?, model: GroupConversation) {
|
|||||||
binding.searchResultSubtitle.isVisible = model.groupRecord.isClosedGroup
|
binding.searchResultSubtitle.isVisible = model.groupRecord.isClosedGroup
|
||||||
binding.searchResultTimestamp.isVisible = false
|
binding.searchResultTimestamp.isVisible = false
|
||||||
val threadRecipient = Recipient.from(binding.root.context, Address.fromSerialized(model.groupRecord.encodedId), false)
|
val threadRecipient = Recipient.from(binding.root.context, Address.fromSerialized(model.groupRecord.encodedId), false)
|
||||||
binding.searchResultProfilePicture.load(threadRecipient)
|
binding.searchResultProfilePicture.update(threadRecipient)
|
||||||
val nameString = model.groupRecord.title
|
val nameString = model.groupRecord.title
|
||||||
binding.searchResultTitle.text = getHighlight(query, nameString)
|
binding.searchResultTitle.text = getHighlight(query, nameString)
|
||||||
|
|
||||||
@ -110,7 +110,7 @@ fun ContentView.bindModel(query: String?, model: ContactModel) = binding.run {
|
|||||||
searchResultTimestamp.isVisible = false
|
searchResultTimestamp.isVisible = false
|
||||||
searchResultSubtitle.text = null
|
searchResultSubtitle.text = null
|
||||||
val recipient = Recipient.from(root.context, Address.fromSerialized(model.contact.accountID), false)
|
val recipient = Recipient.from(root.context, Address.fromSerialized(model.contact.accountID), false)
|
||||||
searchResultProfilePicture.load(recipient)
|
searchResultProfilePicture.update(recipient)
|
||||||
val nameString = if (model.isSelf) root.context.getString(R.string.noteToSelf)
|
val nameString = if (model.isSelf) root.context.getString(R.string.noteToSelf)
|
||||||
else model.contact.getSearchName()
|
else model.contact.getSearchName()
|
||||||
searchResultTitle.text = getHighlight(query, nameString)
|
searchResultTitle.text = getHighlight(query, nameString)
|
||||||
@ -120,7 +120,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.noteToSelf)
|
binding.searchResultTitle.setText(R.string.noteToSelf)
|
||||||
binding.searchResultProfilePicture.load(Address.fromSerialized(model.currentUserPublicKey))
|
binding.searchResultProfilePicture.update(Address.fromSerialized(model.currentUserPublicKey))
|
||||||
binding.searchResultProfilePicture.isVisible = true
|
binding.searchResultProfilePicture.isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +135,7 @@ fun ContentView.bindModel(query: String?, model: Message) = binding.apply {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(root.context, Locale.getDefault(), model.messageResult.sentTimestampMs)
|
searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(root.context, Locale.getDefault(), model.messageResult.sentTimestampMs)
|
||||||
searchResultProfilePicture.load(model.messageResult.conversationRecipient)
|
searchResultProfilePicture.update(model.messageResult.conversationRecipient)
|
||||||
val textSpannable = SpannableStringBuilder()
|
val textSpannable = SpannableStringBuilder()
|
||||||
if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) {
|
if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) {
|
||||||
// group chat, bind
|
// group chat, bind
|
||||||
|
@ -131,6 +131,7 @@ fun MediaOverviewScreen(
|
|||||||
onSaveClicked = { showingSaveAttachmentWarning = true },
|
onSaveClicked = { showingSaveAttachmentWarning = true },
|
||||||
onDeleteClicked = { showingDeleteConfirmation = true },
|
onDeleteClicked = { showingDeleteConfirmation = true },
|
||||||
onSelectAllClicked = viewModel::onSelectAllClicked,
|
onSelectAllClicked = viewModel::onSelectAllClicked,
|
||||||
|
numSelected = selectedItems.size,
|
||||||
appBarScrollBehavior = appBarScrollBehavior
|
appBarScrollBehavior = appBarScrollBehavior
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.ui.theme.LocalColors
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
fun MediaOverviewTopAppBar(
|
fun MediaOverviewTopAppBar(
|
||||||
selectionMode: Boolean,
|
selectionMode: Boolean,
|
||||||
|
numSelected: Int,
|
||||||
title: String,
|
title: String,
|
||||||
onBackClicked: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
onSaveClicked: () -> Unit,
|
onSaveClicked: () -> Unit,
|
||||||
@ -25,7 +26,8 @@ fun MediaOverviewTopAppBar(
|
|||||||
) {
|
) {
|
||||||
ActionAppBar(
|
ActionAppBar(
|
||||||
title = title,
|
title = title,
|
||||||
navigationIcon = {AppBarBackIcon(onBack = onBackClicked)},
|
actionModeTitle = numSelected.toString(),
|
||||||
|
navigationIcon = { AppBarBackIcon(onBack = onBackClicked) },
|
||||||
scrollBehavior = appBarScrollBehavior,
|
scrollBehavior = appBarScrollBehavior,
|
||||||
actionMode = selectionMode,
|
actionMode = selectionMode,
|
||||||
actionModeActions = {
|
actionModeActions = {
|
||||||
|
@ -49,11 +49,12 @@ class MessageRequestView : LinearLayout {
|
|||||||
binding.snippetTextView.text = snippet
|
binding.snippetTextView.text = snippet
|
||||||
|
|
||||||
post {
|
post {
|
||||||
binding.profilePictureView.load(thread.recipient)
|
binding.profilePictureView.update(thread.recipient)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun recycle() {
|
fun recycle() {
|
||||||
|
binding.profilePictureView.recycle()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getUserDisplayName(recipient: Recipient): String? {
|
private fun getUserDisplayName(recipient: Recipient): String? {
|
||||||
|
@ -56,7 +56,6 @@ public class SignalGlideModule extends AppGlideModule {
|
|||||||
// builder.setDiskCache(new NoopDiskCacheFactory());
|
// builder.setDiskCache(new NoopDiskCacheFactory());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @noinspection unchecked*/
|
|
||||||
@Override
|
@Override
|
||||||
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
|
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
|
||||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||||
@ -75,7 +74,6 @@ public class SignalGlideModule extends AppGlideModule {
|
|||||||
registry.append(AttachmentModel.class, InputStream.class, new AttachmentStreamUriLoader.Factory());
|
registry.append(AttachmentModel.class, InputStream.class, new AttachmentStreamUriLoader.Factory());
|
||||||
registry.append(ChunkedImageUrl.class, InputStream.class, new ChunkedImageUrlLoader.Factory());
|
registry.append(ChunkedImageUrl.class, InputStream.class, new ChunkedImageUrlLoader.Factory());
|
||||||
registry.append(PlaceholderAvatarPhoto.class, BitmapDrawable.class, new PlaceholderAvatarLoader.Factory(context));
|
registry.append(PlaceholderAvatarPhoto.class, BitmapDrawable.class, new PlaceholderAvatarLoader.Factory(context));
|
||||||
|
|
||||||
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
|
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.thoughtcrime.securesms.onboarding.ui.ContinuePrimaryOutlineButton
|
import org.thoughtcrime.securesms.onboarding.ui.ContinuePrimaryOutlineButton
|
||||||
import org.thoughtcrime.securesms.ui.components.MaybeScanQrCode
|
import org.thoughtcrime.securesms.ui.components.QRScannerScreen
|
||||||
import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField
|
import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField
|
||||||
import org.thoughtcrime.securesms.ui.components.SessionTabRow
|
import org.thoughtcrime.securesms.ui.components.SessionTabRow
|
||||||
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
|
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
|
||||||
@ -52,7 +52,7 @@ internal fun LoadAccountScreen(
|
|||||||
) { page ->
|
) { page ->
|
||||||
when (TITLES[page]) {
|
when (TITLES[page]) {
|
||||||
R.string.sessionRecoveryPassword -> RecoveryPassword(state, onChange, onContinue)
|
R.string.sessionRecoveryPassword -> RecoveryPassword(state, onChange, onContinue)
|
||||||
R.string.qrScan -> MaybeScanQrCode(qrErrors, onScan = onScan)
|
R.string.qrScan -> QRScannerScreen(qrErrors, onScan = onScan)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
@ -14,7 +15,6 @@ import org.thoughtcrime.securesms.onboarding.manager.LoadAccountManager
|
|||||||
import org.thoughtcrime.securesms.onboarding.messagenotifications.MessageNotificationsActivity
|
import org.thoughtcrime.securesms.onboarding.messagenotifications.MessageNotificationsActivity
|
||||||
import org.thoughtcrime.securesms.ui.setComposeContent
|
import org.thoughtcrime.securesms.ui.setComposeContent
|
||||||
import org.thoughtcrime.securesms.util.start
|
import org.thoughtcrime.securesms.util.start
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class LoadAccountActivity : BaseActionBarActivity() {
|
class LoadAccountActivity : BaseActionBarActivity() {
|
||||||
|
@ -4,6 +4,7 @@ import android.app.Application
|
|||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@ -17,7 +18,6 @@ import org.session.libsignal.crypto.MnemonicCodec
|
|||||||
import org.session.libsignal.crypto.MnemonicCodec.DecodingError.InputTooShort
|
import org.session.libsignal.crypto.MnemonicCodec.DecodingError.InputTooShort
|
||||||
import org.session.libsignal.crypto.MnemonicCodec.DecodingError.InvalidWord
|
import org.session.libsignal.crypto.MnemonicCodec.DecodingError.InvalidWord
|
||||||
import org.thoughtcrime.securesms.crypto.MnemonicUtilities
|
import org.thoughtcrime.securesms.crypto.MnemonicUtilities
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class LoadAccountEvent(val mnemonic: ByteArray)
|
class LoadAccountEvent(val mnemonic: ByteArray)
|
||||||
|
|
||||||
@ -54,6 +54,7 @@ internal class LoadAccountViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onScanQrCode(string: String) {
|
fun onScanQrCode(string: String) {
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
codec.decodeMnemonicOrHexAsByteArray(string).let(::onSuccess)
|
codec.decodeMnemonicOrHexAsByteArray(string).let(::onSuccess)
|
||||||
|
@ -36,6 +36,11 @@ class BlockedContactsAdapter(val viewModel: BlockedContactsViewModel) : ListAdap
|
|||||||
else holder.select(getItem(position).isSelected)
|
else holder.select(getItem(position).isSelected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onViewRecycled(holder: ViewHolder) {
|
||||||
|
super.onViewRecycled(holder)
|
||||||
|
holder.binding.profilePictureView.recycle()
|
||||||
|
}
|
||||||
|
|
||||||
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
|
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
||||||
val glide = Glide.with(itemView)
|
val glide = Glide.with(itemView)
|
||||||
@ -43,7 +48,9 @@ class BlockedContactsAdapter(val viewModel: BlockedContactsViewModel) : ListAdap
|
|||||||
|
|
||||||
fun bind(selectable: SelectableRecipient, toggle: (SelectableRecipient) -> Unit) {
|
fun bind(selectable: SelectableRecipient, toggle: (SelectableRecipient) -> Unit) {
|
||||||
binding.recipientName.text = selectable.item.name
|
binding.recipientName.text = selectable.item.name
|
||||||
binding.profilePictureView.load(selectable.item)
|
with (binding.profilePictureView) {
|
||||||
|
update(selectable.item)
|
||||||
|
}
|
||||||
binding.root.setOnClickListener { toggle(selectable) }
|
binding.root.setOnClickListener { toggle(selectable) }
|
||||||
binding.selectButton.isSelected = selectable.isSelected
|
binding.selectButton.isSelected = selectable.isSelected
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
|||||||
import org.thoughtcrime.securesms.database.threadDatabase
|
import org.thoughtcrime.securesms.database.threadDatabase
|
||||||
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
|
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
|
||||||
import org.thoughtcrime.securesms.ui.theme.LocalColors
|
import org.thoughtcrime.securesms.ui.theme.LocalColors
|
||||||
import org.thoughtcrime.securesms.ui.components.MaybeScanQrCode
|
import org.thoughtcrime.securesms.ui.components.QRScannerScreen
|
||||||
import org.thoughtcrime.securesms.ui.components.QrImage
|
import org.thoughtcrime.securesms.ui.components.QrImage
|
||||||
import org.thoughtcrime.securesms.ui.components.SessionTabRow
|
import org.thoughtcrime.securesms.ui.components.SessionTabRow
|
||||||
import org.thoughtcrime.securesms.ui.contentDescription
|
import org.thoughtcrime.securesms.ui.contentDescription
|
||||||
@ -54,7 +54,7 @@ class QRCodeActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onScan(string: String) {
|
private fun onScan(string: String) {
|
||||||
if (!PublicKeyValidation.isValid(string)) {
|
if (!PublicKeyValidation.isValid(string)) {
|
||||||
errors.tryEmit(getString(R.string.qrNotAccountId))
|
errors.tryEmit(getString(R.string.qrNotAccountId))
|
||||||
} else if (!isFinishing) {
|
} else if (!isFinishing) {
|
||||||
@ -83,7 +83,7 @@ private fun Tabs(accountId: String, errors: Flow<String>, onScan: (String) -> Un
|
|||||||
) { page ->
|
) { page ->
|
||||||
when (TITLES[page]) {
|
when (TITLES[page]) {
|
||||||
R.string.view -> QrPage(accountId)
|
R.string.view -> QrPage(accountId)
|
||||||
R.string.scan -> MaybeScanQrCode(errors, onScan = onScan)
|
R.string.scan -> QRScannerScreen(errors, onScan = onScan)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,7 +170,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
super.onStart()
|
super.onStart()
|
||||||
|
|
||||||
binding.run {
|
binding.run {
|
||||||
loadProfilePicture(profilePictureView)
|
setupProfilePictureView(profilePictureView)
|
||||||
profilePictureView.setOnClickListener { showEditProfilePictureUI() }
|
profilePictureView.setOnClickListener { showEditProfilePictureUI() }
|
||||||
ctnGroupNameSection.setOnClickListener { startActionMode(DisplayNameEditActionModeCallback()) }
|
ctnGroupNameSection.setOnClickListener { startActionMode(DisplayNameEditActionModeCallback()) }
|
||||||
btnGroupNameDisplay.text = getDisplayName()
|
btnGroupNameDisplay.text = getDisplayName()
|
||||||
@ -190,9 +190,12 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
private fun getDisplayName(): String =
|
private fun getDisplayName(): String =
|
||||||
TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(hexEncodedPublicKey)
|
TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(hexEncodedPublicKey)
|
||||||
|
|
||||||
private fun loadProfilePicture(view: ProfilePictureView) {
|
private fun setupProfilePictureView(view: ProfilePictureView) {
|
||||||
// Always reload the profile picture as it can change on this page.
|
view.apply {
|
||||||
view.load(Address.fromSerialized(hexEncodedPublicKey))
|
publicKey = hexEncodedPublicKey
|
||||||
|
displayName = getDisplayName()
|
||||||
|
update()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
@ -333,7 +336,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
|
|
||||||
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@SettingsActivity)
|
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@SettingsActivity)
|
||||||
|
|
||||||
loadProfilePicture(binding.profilePictureView)
|
// Update our visuals
|
||||||
|
binding.profilePictureView.recycle()
|
||||||
|
binding.profilePictureView.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the sync failed then inform the user
|
// If the sync failed then inform the user
|
||||||
@ -412,7 +417,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
cancelButton()
|
cancelButton()
|
||||||
}.apply {
|
}.apply {
|
||||||
val profilePic = findViewById<ProfilePictureView>(R.id.profile_picture_view)
|
val profilePic = findViewById<ProfilePictureView>(R.id.profile_picture_view)
|
||||||
?.also(::loadProfilePicture)
|
?.also(::setupProfilePictureView)
|
||||||
|
|
||||||
val pictureIcon = findViewById<View>(R.id.ic_pictures)
|
val pictureIcon = findViewById<View>(R.id.ic_pictures)
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import org.session.libsession.messaging.utilities.AccountId;
|
|||||||
import org.thoughtcrime.securesms.components.ProfilePictureView;
|
import org.thoughtcrime.securesms.components.ProfilePictureView;
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
|
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
|
||||||
final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecipientsAdapter.ViewHolder> {
|
final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecipientsAdapter.ViewHolder> {
|
||||||
|
|
||||||
@ -152,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.load(reaction.getSender());
|
this.avatar.update(reaction.getSender());
|
||||||
|
|
||||||
if (reaction.getSender().isLocalNumber()) {
|
if (reaction.getSender().isLocalNumber()) {
|
||||||
this.recipient.setText(R.string.you);
|
this.recipient.setText(R.string.you);
|
||||||
@ -170,6 +171,7 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecip
|
|||||||
}
|
}
|
||||||
|
|
||||||
void unbind() {
|
void unbind() {
|
||||||
|
avatar.recycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@ -360,7 +361,7 @@ fun RowScope.Avatar(recipient: Recipient) {
|
|||||||
) {
|
) {
|
||||||
AndroidView(
|
AndroidView(
|
||||||
factory = {
|
factory = {
|
||||||
ProfilePictureView(it).apply { load(recipient) }
|
ProfilePictureView(it).apply { update(recipient) }
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(46.dp)
|
.width(46.dp)
|
||||||
|
@ -2,26 +2,27 @@ package org.thoughtcrime.securesms.ui.components
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarColors
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.thoughtcrime.securesms.ui.Divider
|
import org.thoughtcrime.securesms.ui.Divider
|
||||||
import org.thoughtcrime.securesms.ui.theme.LocalColors
|
import org.thoughtcrime.securesms.ui.theme.LocalColors
|
||||||
|
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
|
||||||
import org.thoughtcrime.securesms.ui.theme.LocalType
|
import org.thoughtcrime.securesms.ui.theme.LocalType
|
||||||
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
|
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
|
||||||
import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider
|
import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider
|
||||||
@ -37,7 +38,10 @@ fun AppBarPreview(
|
|||||||
Column() {
|
Column() {
|
||||||
BasicAppBar(title = "Basic App Bar")
|
BasicAppBar(title = "Basic App Bar")
|
||||||
Divider()
|
Divider()
|
||||||
BasicAppBar(title = "Basic App Bar With Color", backgroundColor = LocalColors.current.backgroundSecondary)
|
BasicAppBar(
|
||||||
|
title = "Basic App Bar With Color",
|
||||||
|
backgroundColor = LocalColors.current.backgroundSecondary
|
||||||
|
)
|
||||||
Divider()
|
Divider()
|
||||||
BackAppBar(title = "Back Bar", onBack = {})
|
BackAppBar(title = "Back Bar", onBack = {})
|
||||||
Divider()
|
Divider()
|
||||||
@ -69,7 +73,7 @@ fun BasicAppBar(
|
|||||||
backgroundColor: Color = LocalColors.current.background,
|
backgroundColor: Color = LocalColors.current.background,
|
||||||
navigationIcon: @Composable () -> Unit = {},
|
navigationIcon: @Composable () -> Unit = {},
|
||||||
actions: @Composable RowScope.() -> Unit = {},
|
actions: @Composable RowScope.() -> Unit = {},
|
||||||
){
|
) {
|
||||||
CenterAlignedTopAppBar(
|
CenterAlignedTopAppBar(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
title = {
|
title = {
|
||||||
@ -94,7 +98,7 @@ fun BackAppBar(
|
|||||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||||
backgroundColor: Color = LocalColors.current.background,
|
backgroundColor: Color = LocalColors.current.background,
|
||||||
actions: @Composable RowScope.() -> Unit = {},
|
actions: @Composable RowScope.() -> Unit = {},
|
||||||
){
|
) {
|
||||||
BasicAppBar(
|
BasicAppBar(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
title = title,
|
title = title,
|
||||||
@ -115,6 +119,7 @@ fun ActionAppBar(
|
|||||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||||
backgroundColor: Color = LocalColors.current.background,
|
backgroundColor: Color = LocalColors.current.background,
|
||||||
actionMode: Boolean = false,
|
actionMode: Boolean = false,
|
||||||
|
actionModeTitle: String = "",
|
||||||
navigationIcon: @Composable () -> Unit = {},
|
navigationIcon: @Composable () -> Unit = {},
|
||||||
actions: @Composable RowScope.() -> Unit = {},
|
actions: @Composable RowScope.() -> Unit = {},
|
||||||
actionModeActions: @Composable (RowScope.() -> Unit) = {},
|
actionModeActions: @Composable (RowScope.() -> Unit) = {},
|
||||||
@ -126,7 +131,19 @@ fun ActionAppBar(
|
|||||||
AppBarText(title = title)
|
AppBarText(title = title)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
navigationIcon =navigationIcon,
|
navigationIcon = {
|
||||||
|
if (actionMode) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.xxxsSpacing),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
navigationIcon()
|
||||||
|
AppBarText(title = actionModeTitle)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
navigationIcon()
|
||||||
|
}
|
||||||
|
},
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
colors = appBarColors(backgroundColor),
|
colors = appBarColors(backgroundColor),
|
||||||
actions = {
|
actions = {
|
||||||
|
@ -47,12 +47,16 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
|||||||
import com.google.accompanist.permissions.isGranted
|
import com.google.accompanist.permissions.isGranted
|
||||||
import com.google.accompanist.permissions.rememberPermissionState
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
import com.google.accompanist.permissions.shouldShowRationale
|
import com.google.accompanist.permissions.shouldShowRationale
|
||||||
import com.google.mlkit.vision.barcode.BarcodeScanner
|
import com.google.zxing.BinaryBitmap
|
||||||
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
import com.google.zxing.ChecksumException
|
||||||
import com.google.mlkit.vision.barcode.BarcodeScanning
|
import com.google.zxing.FormatException
|
||||||
import com.google.mlkit.vision.barcode.common.Barcode
|
import com.google.zxing.NotFoundException
|
||||||
import com.google.mlkit.vision.common.InputImage
|
import com.google.zxing.PlanarYUVLuminanceSource
|
||||||
|
import com.google.zxing.Result
|
||||||
|
import com.google.zxing.common.HybridBinarizer
|
||||||
|
import com.google.zxing.qrcode.QRCodeReader
|
||||||
import com.squareup.phrase.Phrase
|
import com.squareup.phrase.Phrase
|
||||||
|
import java.util.concurrent.Executors
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
@ -60,13 +64,12 @@ import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY
|
|||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
|
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
|
||||||
import org.thoughtcrime.securesms.ui.theme.LocalType
|
import org.thoughtcrime.securesms.ui.theme.LocalType
|
||||||
import java.util.concurrent.Executors
|
|
||||||
|
|
||||||
private const val TAG = "NewMessageFragment"
|
private const val TAG = "NewMessageFragment"
|
||||||
|
|
||||||
@OptIn(ExperimentalPermissionsApi::class)
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MaybeScanQrCode(
|
fun QRScannerScreen(
|
||||||
errors: Flow<String>,
|
errors: Flow<String>,
|
||||||
onClickSettings: () -> Unit = LocalContext.current.run { {
|
onClickSettings: () -> Unit = LocalContext.current.run { {
|
||||||
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||||
@ -147,17 +150,13 @@ fun ScanQrCode(errors: Flow<String>, onScan: (String) -> Unit) {
|
|||||||
runCatching {
|
runCatching {
|
||||||
cameraProvider.get().unbindAll()
|
cameraProvider.get().unbindAll()
|
||||||
|
|
||||||
val options = BarcodeScannerOptions.Builder()
|
|
||||||
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
|
|
||||||
.build()
|
|
||||||
val scanner = BarcodeScanning.getClient(options)
|
|
||||||
|
|
||||||
cameraProvider.get().bindToLifecycle(
|
cameraProvider.get().bindToLifecycle(
|
||||||
LocalLifecycleOwner.current,
|
LocalLifecycleOwner.current,
|
||||||
selector,
|
selector,
|
||||||
preview,
|
preview,
|
||||||
buildAnalysisUseCase(scanner, onScan)
|
buildAnalysisUseCase(QRCodeReader(), onScan)
|
||||||
)
|
)
|
||||||
|
|
||||||
}.onFailure { Log.e(TAG, "error binding camera", it) }
|
}.onFailure { Log.e(TAG, "error binding camera", it) }
|
||||||
|
|
||||||
DisposableEffect(cameraProvider) {
|
DisposableEffect(cameraProvider) {
|
||||||
@ -221,32 +220,51 @@ fun ScanQrCode(errors: Flow<String>, onScan: (String) -> Unit) {
|
|||||||
|
|
||||||
@SuppressLint("UnsafeOptInUsageError")
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
private fun buildAnalysisUseCase(
|
private fun buildAnalysisUseCase(
|
||||||
scanner: BarcodeScanner,
|
scanner: QRCodeReader,
|
||||||
onBarcodeScanned: (String) -> Unit
|
onBarcodeScanned: (String) -> Unit
|
||||||
): ImageAnalysis = ImageAnalysis.Builder()
|
): ImageAnalysis = ImageAnalysis.Builder()
|
||||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||||
.build().apply {
|
.build().apply {
|
||||||
setAnalyzer(Executors.newSingleThreadExecutor(), Analyzer(scanner, onBarcodeScanned))
|
setAnalyzer(Executors.newSingleThreadExecutor(), QRCodeAnalyzer(scanner, onBarcodeScanned))
|
||||||
}
|
}
|
||||||
|
|
||||||
class Analyzer(
|
class QRCodeAnalyzer(
|
||||||
private val scanner: BarcodeScanner,
|
private val qrCodeReader: QRCodeReader,
|
||||||
private val onBarcodeScanned: (String) -> Unit
|
private val onBarcodeScanned: (String) -> Unit
|
||||||
): ImageAnalysis.Analyzer {
|
): ImageAnalysis.Analyzer {
|
||||||
|
|
||||||
|
// Note: This analyze method is called once per frame of the camera feed.
|
||||||
@SuppressLint("UnsafeOptInUsageError")
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
override fun analyze(image: ImageProxy) {
|
override fun analyze(image: ImageProxy) {
|
||||||
InputImage.fromMediaImage(
|
// Grab the image data as a byte array so we can generate a PlanarYUVLuminanceSource from it
|
||||||
image.image!!,
|
val buffer = image.planes[0].buffer
|
||||||
image.imageInfo.rotationDegrees
|
buffer.rewind()
|
||||||
).let(scanner::process).apply {
|
val imageBytes = ByteArray(buffer.capacity())
|
||||||
addOnSuccessListener { barcodes ->
|
buffer.get(imageBytes) // IMPORTANT: This transfers data from the buffer INTO the imageBytes array, although it looks like it would go the other way around!
|
||||||
barcodes.forEach {
|
|
||||||
it.rawValue?.let(onBarcodeScanned)
|
// ZXing requires data as a BinaryBitmap to scan for QR codes, and to generate that we need to feed it a PlanarYUVLuminanceSource
|
||||||
|
val luminanceSource = PlanarYUVLuminanceSource(imageBytes, image.width, image.height, 0, 0, image.width, image.height, false)
|
||||||
|
val binaryBitmap = BinaryBitmap(HybridBinarizer(luminanceSource))
|
||||||
|
|
||||||
|
// Attempt to extract a QR code from the binary bitmap, and pass it through to our `onBarcodeScanned` method if we find one
|
||||||
|
try {
|
||||||
|
val result: Result = qrCodeReader.decode(binaryBitmap)
|
||||||
|
val resultTxt = result.text
|
||||||
|
// No need to close the image here - it'll always make it to the end, and calling `onBarcodeScanned`
|
||||||
|
// with a valid contact / recovery phrase / community code will stop calling this `analyze` method.
|
||||||
|
onBarcodeScanned(resultTxt)
|
||||||
}
|
}
|
||||||
|
catch (nfe: NotFoundException) { /* Hits if there is no QR code in the image */ }
|
||||||
|
catch (fe: FormatException) { /* Hits if we found a QR code but failed to decode it */ }
|
||||||
|
catch (ce: ChecksumException) { /* Hits if we found a QR code which is corrupted */ }
|
||||||
|
catch (e: Exception) {
|
||||||
|
// Hits if there's a genuine problem
|
||||||
|
Log.e("QR", "error", e)
|
||||||
}
|
}
|
||||||
addOnCompleteListener {
|
|
||||||
|
// Remember to close the image when we're done with it!
|
||||||
|
// IMPORTANT: It is CLOSING the image that allows this method to run again! If we don't
|
||||||
|
// close the image this method runs precisely ONCE and that's it, which is essentially useless.
|
||||||
image.close()
|
image.close()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import com.google.zxing.BarcodeFormat
|
|||||||
import com.google.zxing.EncodeHintType
|
import com.google.zxing.EncodeHintType
|
||||||
import com.google.zxing.qrcode.QRCodeWriter
|
import com.google.zxing.qrcode.QRCodeWriter
|
||||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
|
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
|
|
||||||
object QRCodeUtilities {
|
object QRCodeUtilities {
|
||||||
|
|
||||||
@ -34,5 +35,8 @@ object QRCodeUtilities {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.getOrNull()
|
}.getOrElse {
|
||||||
|
Log.e("QRCodeUtilities", "Failed to generate QR Code", it)
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
42
app/src/main/res/layout/view_mention_candidate.xml
Normal file
42
app/src/main/res/layout/view_mention_candidate.xml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<org.thoughtcrime.securesms.conversation.v2.components.MentionCandidateView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="@dimen/medium_spacing"
|
||||||
|
android:paddingEnd="@dimen/medium_spacing"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:background="@drawable/mention_candidate_view_background">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="26dp"
|
||||||
|
android:layout_height="32dp">
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.ProfilePictureView
|
||||||
|
android:id="@+id/profilePictureView"
|
||||||
|
android:layout_width="@dimen/very_small_profile_picture_size"
|
||||||
|
android:layout_height="@dimen/very_small_profile_picture_size"
|
||||||
|
android:layout_marginTop="3dp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/moderatorIconImageView"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:src="@drawable/ic_crown"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_alignParentBottom="true" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/mentionCandidateNameTextView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/medium_spacing"
|
||||||
|
android:textSize="@dimen/small_font_size"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end" />
|
||||||
|
|
||||||
|
</org.thoughtcrime.securesms.conversation.v2.components.MentionCandidateView>
|
@ -40,6 +40,7 @@ phraseVersion=1.2.0
|
|||||||
preferenceVersion=1.2.0
|
preferenceVersion=1.2.0
|
||||||
protobufVersion=2.5.0
|
protobufVersion=2.5.0
|
||||||
testCoreVersion=1.5.0
|
testCoreVersion=1.5.0
|
||||||
|
zxingVersion=3.5.3
|
||||||
android.defaults.buildfeatures.buildconfig=true
|
android.defaults.buildfeatures.buildconfig=true
|
||||||
android.nonTransitiveRClass=false
|
android.nonTransitiveRClass=false
|
||||||
android.nonFinalResIds=false
|
android.nonFinalResIds=false
|
||||||
|
@ -3,12 +3,10 @@ package org.session.libsession.avatars
|
|||||||
import com.bumptech.glide.load.Key
|
import com.bumptech.glide.load.Key
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
data class PlaceholderAvatarPhoto(
|
class PlaceholderAvatarPhoto(val hashString: String,
|
||||||
val hashString: String,
|
val displayName: String): Key {
|
||||||
val displayName: String?
|
|
||||||
) : Key {
|
|
||||||
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
|
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
|
||||||
messageDigest.update(hashString.encodeToByteArray())
|
messageDigest.update(hashString.encodeToByteArray())
|
||||||
messageDigest.update(displayName?.encodeToByteArray() ?: byteArrayOf())
|
messageDigest.update(displayName.encodeToByteArray())
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user