Merge pull request #332 from loki-project/profile-pictures

New Profile Picture Placeholder
This commit is contained in:
Niels Andriesse 2020-09-09 13:59:14 +10:00 committed by GitHub
commit 2ba98bb8c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 215 additions and 62 deletions

View File

@ -3,7 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<solid android:color="@color/unimportant" /> <solid android:color="@color/profile_picture_background" />
<corners android:radius="38dp" /> <corners android:radius="38dp" />

View File

@ -7,5 +7,5 @@
<corners android:radius="38dp" /> <corners android:radius="38dp" />
<stroke android:width="@dimen/profile_picture_border_thickness" android:color="@color/border" /> <stroke android:width="@dimen/profile_picture_border_thickness" android:color="@color/profile_picture_border" />
</shape> </shape>

View File

@ -3,9 +3,9 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<solid android:color="@color/unimportant" /> <solid android:color="@color/profile_picture_background" />
<corners android:radius="23dp" /> <corners android:radius="23dp" />
<stroke android:width="@dimen/profile_picture_border_thickness" android:color="@color/border" /> <stroke android:width="@dimen/profile_picture_border_thickness" android:color="@color/profile_picture_border" />
</shape> </shape>

View File

@ -7,5 +7,5 @@
<corners android:radius="23dp" /> <corners android:radius="23dp" />
<stroke android:width="@dimen/profile_picture_border_thickness" android:color="@color/border" /> <stroke android:width="@dimen/profile_picture_border_thickness" android:color="@color/profile_picture_border" />
</shape> </shape>

View File

@ -3,9 +3,9 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<solid android:color="#353535" /> <solid android:color="@color/profile_picture_background" />
<corners android:radius="23dp" /> <corners android:radius="23dp" />
<stroke android:width="@dimen/profile_picture_border_thickness" android:color="@color/border" /> <stroke android:width="@dimen/profile_picture_border_thickness" android:color="@color/profile_picture_border" />
</shape> </shape>

View File

@ -3,9 +3,9 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<solid android:color="@color/unimportant" /> <solid android:color="@color/profile_picture_background" />
<corners android:radius="18dp" /> <corners android:radius="18dp" />
<stroke android:width="@dimen/profile_picture_border_thickness" android:color="@color/border" /> <stroke android:width="@dimen/profile_picture_border_thickness" android:color="@color/profile_picture_border" />
</shape> </shape>

View File

@ -7,5 +7,5 @@
<corners android:radius="18dp" /> <corners android:radius="18dp" />
<stroke android:width="@dimen/profile_picture_border_thickness" android:color="@color/border" /> <stroke android:width="@dimen/profile_picture_border_thickness" android:color="@color/profile_picture_border" />
</shape> </shape>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<RelativeLayout <RelativeLayout
android:id="@+id/doubleModeImageViewContainer" android:id="@+id/doubleModeImageViewContainer"

View File

@ -7,6 +7,8 @@
<color name="text">#000000</color> <color name="text">#000000</color>
<color name="destructive">#f26f55</color> <color name="destructive">#f26f55</color>
<color name="unimportant">#606060</color> <color name="unimportant">#606060</color>
<color name="profile_picture_border">#23000000</color>
<color name="profile_picture_background">#B0B0B0</color>
<color name="cell_background">#FCFCFC</color> <color name="cell_background">#FCFCFC</color>
<color name="cell_selected">#DFDFDF</color> <color name="cell_selected">#DFDFDF</color>
<color name="navigation_bar_background">#FCFCFC</color> <color name="navigation_bar_background">#FCFCFC</color>

View File

@ -7,6 +7,8 @@
<color name="text">#FFFFFF</color> <color name="text">#FFFFFF</color>
<color name="destructive">#FF453A</color> <color name="destructive">#FF453A</color>
<color name="unimportant">#D8D8D8</color> <color name="unimportant">#D8D8D8</color>
<color name="profile_picture_border">#23FFFFFF</color>
<color name="profile_picture_background">#353535</color>
<color name="border">#979797</color> <color name="border">#979797</color>
<color name="cell_background">#1B1B1B</color> <color name="cell_background">#1B1B1B</color>
<color name="cell_selected">#0C0C0C</color> <color name="cell_selected">#0C0C0C</color>
@ -29,6 +31,13 @@
<color name="pn_option_background">#1B1B1B</color> <color name="pn_option_background">#1B1B1B</color>
<color name="pn_option_border">#212121</color> <color name="pn_option_border">#212121</color>
<color name="paths_building">#FFCE3A</color> <color name="paths_building">#FFCE3A</color>
<array name="profile_picture_placeholder_colors">
<item>#5ff8b0</item>
<item>#26cdb9</item>
<item>#f3c615</item>
<item>#fcac5a</item>
</array>
<!-- Session --> <!-- Session -->
<!-- Loki --> <!-- Loki -->

View File

@ -804,6 +804,7 @@ public class ConversationItem extends LinearLayout
bodyBubble.setLayoutParams(layoutParams); bodyBubble.setLayoutParams(layoutParams);
if (profilePictureView == null) return; if (profilePictureView == null) return;
profilePictureView.setPublicKey(recipient.getAddress().toString()); profilePictureView.setPublicKey(recipient.getAddress().toString());
profilePictureView.setDisplayName(recipient.getName());
profilePictureView.setAdditionalPublicKey(null); profilePictureView.setAdditionalPublicKey(null);
profilePictureView.setRSSFeed(false); profilePictureView.setRSSFeed(false);
profilePictureView.setGlide(glideRequests); profilePictureView.setGlide(glideRequests);

View File

@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.loki.activities package org.thoughtcrime.securesms.loki.activities
import android.app.AlertDialog import android.app.AlertDialog
import androidx.lifecycle.Observer
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -11,10 +10,6 @@ import android.net.Uri
import android.os.AsyncTask import android.os.AsyncTask
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.recyclerview.widget.LinearLayoutManager
import android.text.Spannable import android.text.Spannable
import android.text.SpannableString import android.text.SpannableString
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
@ -22,6 +17,11 @@ import android.util.DisplayMetrics
import android.view.View import android.view.View
import android.widget.RelativeLayout import android.widget.RelativeLayout
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.Observer
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_home.* import kotlinx.android.synthetic.main.activity_home.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
@ -43,14 +43,13 @@ import org.thoughtcrime.securesms.loki.views.NewConversationButtonSetViewDelegat
import org.thoughtcrime.securesms.loki.views.SeedReminderViewDelegate import org.thoughtcrime.securesms.loki.views.SeedReminderViewDelegate
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.util.GroupUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol
import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocol import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocol
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol
import org.whispersystems.signalservice.loki.protocol.shelved.syncmessages.SyncMessagesProtocol import org.whispersystems.signalservice.loki.protocol.shelved.syncmessages.SyncMessagesProtocol
import org.whispersystems.signalservice.loki.utilities.toHexString import org.whispersystems.signalservice.loki.utilities.toHexString
@ -99,6 +98,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
// Set up toolbar buttons // Set up toolbar buttons
profileButton.glide = glide profileButton.glide = glide
profileButton.publicKey = publicKey profileButton.publicKey = publicKey
profileButton.displayName = TextSecurePreferences.getProfileName(this)
profileButton.update() profileButton.update()
profileButton.setOnClickListener { openSettings() } profileButton.setOnClickListener { openSettings() }
pathStatusViewContainer.disableClipping() pathStatusViewContainer.disableClipping()

View File

@ -52,10 +52,8 @@ import java.security.SecureRandom
import java.util.* import java.util.*
class SettingsActivity : PassphraseRequiredActionBarActivity() { class SettingsActivity : PassphraseRequiredActionBarActivity() {
private var displayNameEditActionMode: ActionMode? = null private var displayNameEditActionMode: ActionMode? = null
set(value) { field = value; handleDisplayNameEditActionModeChanged() } set(value) { field = value; handleDisplayNameEditActionModeChanged() }
private lateinit var glide: GlideRequests private lateinit var glide: GlideRequests
private var displayNameToBeUploaded: String? = null private var displayNameToBeUploaded: String? = null
private var profilePictureToBeUploaded: ByteArray? = null private var profilePictureToBeUploaded: ByteArray? = null
@ -71,17 +69,17 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
// region Lifecycle // region Lifecycle
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady) super.onCreate(savedInstanceState, isReady)
setContentView(R.layout.activity_settings) setContentView(R.layout.activity_settings)
val displayName = DatabaseFactory.getLokiUserDatabase(this).getDisplayName(hexEncodedPublicKey)
glide = GlideApp.with(this) glide = GlideApp.with(this)
profilePictureView.glide = glide profilePictureView.glide = glide
profilePictureView.publicKey = hexEncodedPublicKey profilePictureView.publicKey = hexEncodedPublicKey
profilePictureView.displayName = displayName
profilePictureView.isLarge = true profilePictureView.isLarge = true
profilePictureView.update() profilePictureView.update()
profilePictureView.setOnClickListener { showEditProfilePictureUI() } profilePictureView.setOnClickListener { showEditProfilePictureUI() }
ctnGroupNameSection.setOnClickListener { startActionMode(DisplayNameEditActionModeCallback()) } ctnGroupNameSection.setOnClickListener { startActionMode(DisplayNameEditActionModeCallback()) }
btnGroupNameDisplay.text = DatabaseFactory.getLokiUserDatabase(this).getDisplayName(hexEncodedPublicKey) btnGroupNameDisplay.text = displayName
publicKeyTextView.text = hexEncodedPublicKey publicKeyTextView.text = hexEncodedPublicKey
copyButton.setOnClickListener { copyPublicKey() } copyButton.setOnClickListener { copyPublicKey() }
shareButton.setOnClickListener { sharePublicKey() } shareButton.setOnClickListener { sharePublicKey() }
@ -103,12 +101,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.settings_general, menu) menuInflater.inflate(R.menu.settings_general, menu)
// Update UI mode menu icon
// Update UI mode menu icon.
// It uses three-level selector where each level corresponds to the related UiMode ordinal value.
val uiMode = UiModeUtilities.getUserSelectedUiMode(this) val uiMode = UiModeUtilities.getUserSelectedUiMode(this)
menu.findItem(R.id.action_change_theme).icon!!.level = uiMode.ordinal menu.findItem(R.id.action_change_theme).icon!!.level = uiMode.ordinal
return true return true
} }

View File

@ -0,0 +1,79 @@
package org.thoughtcrime.securesms.loki.todo
import android.content.Context
import android.graphics.*
import android.graphics.drawable.BitmapDrawable
import android.text.TextPaint
import android.text.TextUtils
import androidx.annotation.ColorInt
import androidx.core.graphics.ColorUtils
import network.loki.messenger.R
import java.util.*
object AvatarPlaceholderGenerator {
private val tmpFloatArray = FloatArray(3)
private const val EMPTY_LABEL = "0";
fun generate(context: Context, pixelSize: Int, hashString: String, displayName: String?): BitmapDrawable {
//TODO That should be replaced with a proper hash extraction code.
val hash: Long
if (hashString.length >= 12 && hashString.matches(Regex("^[0-9A-Fa-f]+\$"))) {
hash = hashString.substring(0 until 12).toLong(16)
} else {
hash = 0
}
// Do not cache color array, it may be different depends on the current theme.
val colorArray = context.resources.getIntArray(R.array.profile_picture_placeholder_colors)
val colorPrimary = colorArray[(hash % colorArray.size).toInt()]
val colorSecondary = changeColorHueBy(colorPrimary, 12f)
val labelText = when {
!TextUtils.isEmpty(displayName) -> extractLabel(displayName!!)
!TextUtils.isEmpty(hashString) -> extractLabel(hashString)
else -> EMPTY_LABEL
}
val bitmap = Bitmap.createBitmap(pixelSize, pixelSize, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
// Draw background/frame
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.shader = LinearGradient(0f, 0f, 0f, pixelSize.toFloat(),
colorPrimary,
colorPrimary,
Shader.TileMode.REPEAT)
canvas.drawCircle(pixelSize.toFloat() / 2, pixelSize.toFloat() / 2, pixelSize.toFloat() / 2, paint)
// Draw text
val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
textPaint.typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
textPaint.textSize = pixelSize * 0.5f
textPaint.color = Color.WHITE
val areaRect = Rect(0, 0, pixelSize, pixelSize)
val textBounds = RectF(areaRect)
textBounds.right = textPaint.measureText(labelText)
textBounds.bottom = textPaint.descent() - textPaint.ascent()
textBounds.left += (areaRect.width() - textBounds.right) * 0.5f
textBounds.top += (areaRect.height() - textBounds.bottom) * 0.5f
canvas.drawText(labelText, textBounds.left, textBounds.top - textPaint.ascent(), textPaint)
return BitmapDrawable(context.resources, bitmap)
}
@ColorInt
private fun changeColorHueBy(@ColorInt color: Int, hueDelta: Float): Int {
val hslColor = tmpFloatArray
ColorUtils.colorToHSL(color, hslColor)
hslColor[0] = (hslColor[0] + hueDelta) % 360f
return ColorUtils.HSLToColor(hslColor)
}
private fun extractLabel(content: String): String {
var content = content.trim()
if (content.isEmpty()) return EMPTY_LABEL
return content.first().toString().toUpperCase(Locale.ROOT)
}
}

View File

@ -2,12 +2,14 @@ package org.thoughtcrime.securesms.loki.views
import android.content.Context import android.content.Context
import android.graphics.Typeface import android.graphics.Typeface
import android.text.TextUtils
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import kotlinx.android.synthetic.main.view_conversation.view.* import kotlinx.android.synthetic.main.view_conversation.view.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities.populateUserPublicKeyCacheIfNeeded import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities.populateUserPublicKeyCacheIfNeeded
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities.highlightMentions import org.thoughtcrime.securesms.loki.utilities.MentionUtilities.highlightMentions
@ -81,5 +83,10 @@ class ConversationView : LinearLayout {
else -> statusIndicatorImageView.setImageResource(R.drawable.ic_circle_check) else -> statusIndicatorImageView.setImageResource(R.drawable.ic_circle_check)
} }
} }
private fun getUserDisplayName(publicKey: String?): String? {
if (TextUtils.isEmpty(publicKey)) return null
return DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey!!)
}
// endregion // endregion
} }

View File

@ -32,6 +32,7 @@ class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr:
private fun update() { private fun update() {
btnGroupNameDisplay.text = mentionCandidate.displayName btnGroupNameDisplay.text = mentionCandidate.displayName
profilePictureView.publicKey = mentionCandidate.publicKey profilePictureView.publicKey = mentionCandidate.publicKey
profilePictureView.displayName = mentionCandidate.displayName
profilePictureView.additionalPublicKey = null profilePictureView.additionalPublicKey = null
profilePictureView.isRSSFeed = false profilePictureView.isRSSFeed = false
profilePictureView.glide = glide!! profilePictureView.glide = glide!!

View File

@ -1,19 +1,19 @@
package org.thoughtcrime.securesms.loki.views package org.thoughtcrime.securesms.loki.views
import android.content.Context import android.content.Context
import androidx.annotation.DimenRes
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
import android.widget.RelativeLayout import android.widget.RelativeLayout
import androidx.annotation.DimenRes
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import kotlinx.android.synthetic.main.view_conversation.view.*
import kotlinx.android.synthetic.main.view_profile_picture.view.* import kotlinx.android.synthetic.main.view_profile_picture.view.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto
import org.thoughtcrime.securesms.database.Address import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.loki.todo.JazzIdenticonDrawable import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.loki.todo.AvatarPlaceholderGenerator
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
@ -24,7 +24,9 @@ import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager
class ProfilePictureView : RelativeLayout { class ProfilePictureView : RelativeLayout {
lateinit var glide: GlideRequests lateinit var glide: GlideRequests
var publicKey: String? = null var publicKey: String? = null
var displayName: String? = null
var additionalPublicKey: String? = null var additionalPublicKey: String? = null
var additionalDisplayName: String? = null
var isRSSFeed = false var isRSSFeed = false
var isLarge = false var isLarge = false
@ -54,9 +56,17 @@ class ProfilePictureView : RelativeLayout {
// region Updating // region Updating
fun update(recipient: Recipient, threadID: Long) { fun update(recipient: Recipient, threadID: Long) {
fun getUserDisplayName(publicKey: String?): String? {
if (publicKey == null || publicKey.isBlank()) {
return null
} else {
return DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey!!)
}
}
if (recipient.isGroupRecipient) { if (recipient.isGroupRecipient) {
if ("Session Public Chat" == recipient.name) { if ("Session Public Chat" == recipient.name) {
publicKey = "" publicKey = ""
displayName = ""
additionalPublicKey = null additionalPublicKey = null
isRSSFeed = true isRSSFeed = true
} else { } else {
@ -67,12 +77,17 @@ class ProfilePictureView : RelativeLayout {
users.remove(masterPublicKey) users.remove(masterPublicKey)
} }
val randomUsers = users.sorted() // Sort to provide a level of stability val randomUsers = users.sorted() // Sort to provide a level of stability
publicKey = randomUsers.getOrNull(0) ?: "" val pk = randomUsers.getOrNull(0) ?: ""
additionalPublicKey = randomUsers.getOrNull(1) ?: "" publicKey = pk
displayName = getUserDisplayName(pk)
val apk = randomUsers.getOrNull(1) ?: ""
additionalPublicKey = apk
additionalDisplayName = getUserDisplayName(apk)
isRSSFeed = recipient.name == "Loki News" || recipient.name == "Session Updates" isRSSFeed = recipient.name == "Loki News" || recipient.name == "Session Updates"
} }
} else { } else {
publicKey = recipient.address.toString() publicKey = recipient.address.toString()
displayName = recipient.name
additionalPublicKey = null additionalPublicKey = null
isRSSFeed = false isRSSFeed = false
} }
@ -86,28 +101,50 @@ class ProfilePictureView : RelativeLayout {
singleModeImageViewContainer.visibility = if (additionalPublicKey == null && !isRSSFeed && !isLarge) View.VISIBLE else View.INVISIBLE singleModeImageViewContainer.visibility = if (additionalPublicKey == null && !isRSSFeed && !isLarge) View.VISIBLE else View.INVISIBLE
largeSingleModeImageViewContainer.visibility = if (additionalPublicKey == null && !isRSSFeed && isLarge) View.VISIBLE else View.INVISIBLE largeSingleModeImageViewContainer.visibility = if (additionalPublicKey == null && !isRSSFeed && isLarge) View.VISIBLE else View.INVISIBLE
rssImageView.visibility = if (isRSSFeed) View.VISIBLE else View.INVISIBLE rssImageView.visibility = if (isRSSFeed) View.VISIBLE else View.INVISIBLE
fun setProfilePictureIfNeeded(imageView: ImageView, hexEncodedPublicKey: String, @DimenRes sizeID: Int) { setProfilePictureIfNeeded(
doubleModeImageView1,
publicKey,
displayName,
R.dimen.small_profile_picture_size)
setProfilePictureIfNeeded(
doubleModeImageView2,
additionalPublicKey ?: "",
additionalDisplayName,
R.dimen.small_profile_picture_size)
setProfilePictureIfNeeded(
singleModeImageView,
publicKey,
displayName,
R.dimen.medium_profile_picture_size)
setProfilePictureIfNeeded(
largeSingleModeImageView,
publicKey,
displayName,
R.dimen.large_profile_picture_size)
}
private fun setProfilePictureIfNeeded(imageView: ImageView, publicKey: String, displayName: String?, @DimenRes sizeResId: Int) {
glide.clear(imageView) glide.clear(imageView)
if (hexEncodedPublicKey.isNotEmpty()) { if (publicKey.isNotEmpty()) {
val recipient = Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false); val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false);
val signalProfilePicture = recipient.contactPhoto val signalProfilePicture = recipient.contactPhoto
if (signalProfilePicture != null && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "0" && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "") { if (signalProfilePicture != null && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "0"
&& (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "") {
glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView) glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView)
} else { } else {
val size = resources.getDimensionPixelSize(sizeID) val sizeInPX = resources.getDimensionPixelSize(sizeResId)
val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context) val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
val hepk = if (recipient.isLocalNumber && masterHexEncodedPublicKey != null) masterHexEncodedPublicKey else hexEncodedPublicKey val hepk = if (recipient.isLocalNumber && masterPublicKey != null) masterPublicKey else publicKey
val jazzIcon = JazzIdenticonDrawable(size, size, hepk) glide.load(AvatarPlaceholderGenerator.generate(
glide.load(jazzIcon).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView) context,
sizeInPX,
hepk,
displayName
)).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView)
} }
} else { } else {
imageView.setImageDrawable(null) imageView.setImageDrawable(null)
} }
} }
setProfilePictureIfNeeded(doubleModeImageView1, publicKey, R.dimen.small_profile_picture_size)
setProfilePictureIfNeeded(doubleModeImageView2, additionalPublicKey ?: "", R.dimen.small_profile_picture_size)
setProfilePictureIfNeeded(singleModeImageView, publicKey, R.dimen.medium_profile_picture_size)
setProfilePictureIfNeeded(largeSingleModeImageView, publicKey, R.dimen.large_profile_picture_size)
}
// endregion // endregion
} }

View File

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.loki.views package org.thoughtcrime.securesms.loki.views
import android.content.Context import android.content.Context
import android.text.TextUtils
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -8,6 +9,7 @@ import android.widget.LinearLayout
import kotlinx.android.synthetic.main.view_conversation.view.profilePictureView import kotlinx.android.synthetic.main.view_conversation.view.profilePictureView
import kotlinx.android.synthetic.main.view_user.view.* import kotlinx.android.synthetic.main.view_user.view.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.groups.GroupManager import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
@ -47,22 +49,35 @@ class UserView : LinearLayout {
// region Updating // region Updating
fun bind(user: Recipient, glide: GlideRequests, actionIndicator: ActionIndicator, isSelected: Boolean = false) { fun bind(user: Recipient, glide: GlideRequests, actionIndicator: ActionIndicator, isSelected: Boolean = false) {
fun getUserDisplayName(publicKey: String?): String? {
if (publicKey == null || publicKey.isBlank()) {
return null
} else {
return DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey!!)
}
}
val address = user.address.serialize() val address = user.address.serialize()
if (user.isGroupRecipient) { if (user.isGroupRecipient) {
if ("Session Public Chat" == user.name || user.address.isRSSFeed) { if ("Session Public Chat" == user.name || user.address.isRSSFeed) {
profilePictureView.publicKey = "" profilePictureView.publicKey = ""
profilePictureView.displayName = null
profilePictureView.additionalPublicKey = null profilePictureView.additionalPublicKey = null
profilePictureView.isRSSFeed = true profilePictureView.isRSSFeed = true
} else { } else {
val threadID = GroupManager.getThreadIDFromGroupID(address, context) val threadID = GroupManager.getThreadIDFromGroupID(address, context)
val users = MentionsManager.shared.userPublicKeyCache[threadID]?.toList() ?: listOf() val users = MentionsManager.shared.userPublicKeyCache[threadID]?.toList() ?: listOf()
val randomUsers = users.sorted() // Sort to provide a level of stability val randomUsers = users.sorted() // Sort to provide a level of stability
profilePictureView.publicKey = randomUsers.getOrNull(0) ?: "" val pk = randomUsers.getOrNull(0) ?: ""
profilePictureView.additionalPublicKey = randomUsers.getOrNull(1) ?: "" profilePictureView.publicKey = pk
profilePictureView.displayName = getUserDisplayName(pk)
val apk = randomUsers.getOrNull(1) ?: ""
profilePictureView.additionalPublicKey = apk
profilePictureView.additionalDisplayName = getUserDisplayName(apk)
profilePictureView.isRSSFeed = false profilePictureView.isRSSFeed = false
} }
} else { } else {
profilePictureView.publicKey = address profilePictureView.publicKey = address
profilePictureView.displayName = getUserDisplayName(address)
profilePictureView.additionalPublicKey = null profilePictureView.additionalPublicKey = null
profilePictureView.isRSSFeed = false profilePictureView.isRSSFeed = false
} }

View File

@ -14,21 +14,22 @@ import android.graphics.RectF;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.text.SpannableStringBuilder;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationCompat.Action; import androidx.core.app.NotificationCompat.Action;
import androidx.core.app.RemoteInput; import androidx.core.app.RemoteInput;
import android.text.SpannableStringBuilder;
import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.thoughtcrime.securesms.contacts.avatars.ContactColors; import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.todo.AvatarPlaceholderGenerator;
import org.thoughtcrime.securesms.loki.utilities.NotificationUtilities; import org.thoughtcrime.securesms.loki.utilities.NotificationUtilities;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
@ -81,8 +82,6 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
} }
ContactPhoto contactPhoto = recipient.getContactPhoto(); ContactPhoto contactPhoto = recipient.getContactPhoto();
FallbackContactPhoto fallbackContactPhoto = recipient.getFallbackContactPhoto();
if (contactPhoto != null) { if (contactPhoto != null) {
try { try {
setLargeIcon(GlideApp.with(context.getApplicationContext()) setLargeIcon(GlideApp.with(context.getApplicationContext())
@ -94,10 +93,10 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
.get()); .get());
} catch (InterruptedException | ExecutionException e) { } catch (InterruptedException | ExecutionException e) {
Log.w(TAG, e); Log.w(TAG, e);
setLargeIcon(fallbackContactPhoto.asDrawable(context, recipient.getColor().toConversationColor(context))); setLargeIcon(getPlaceholderDrawable(context, recipient));
} }
} else { } else {
setLargeIcon(fallbackContactPhoto.asDrawable(context, recipient.getColor().toConversationColor(context))); setLargeIcon(getPlaceholderDrawable(context, recipient));
} }
} else { } else {
@ -321,4 +320,12 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
return content; return content;
} }
private static Drawable getPlaceholderDrawable(Context context, Recipient recipient) {
String publicKey = recipient.getAddress().serialize();
String hepk = (recipient.isLocalNumber() && publicKey != null)
? TextSecurePreferences.getMasterHexEncodedPublicKey(context)
: publicKey;
String displayName = recipient.getName();
return AvatarPlaceholderGenerator.INSTANCE.generate(context, 128, hepk, displayName);
}
} }