mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-21 14:28:26 +00:00
Generate placeholder avatars from two characters, re-fetch missed avatars (#856)
* feat: splitting names in the avatar generation * fix: re-fetch avatars if initial downloads fail * fix: remove shadowed name, add tests for common labels
This commit is contained in:
parent
11d49426d3
commit
6649a9a745
@ -254,7 +254,7 @@ public class JobManager implements ConstraintObserver.Notifier {
|
|||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
private ExecutorFactory executorFactory = new DefaultExecutorFactory();
|
private ExecutorFactory executorFactory = new DefaultExecutorFactory();
|
||||||
private int jobThreadCount = Math.max(2, Math.min(Runtime.getRuntime().availableProcessors() - 1, 4));
|
private int jobThreadCount = 1;
|
||||||
private Map<String, Job.Factory> jobFactories = new HashMap<>();
|
private Map<String, Job.Factory> jobFactories = new HashMap<>();
|
||||||
private Map<String, Constraint.Factory> constraintFactories = new HashMap<>();
|
private Map<String, Constraint.Factory> constraintFactories = new HashMap<>();
|
||||||
private List<ConstraintObserver> constraintObservers = new ArrayList<>();
|
private List<ConstraintObserver> constraintObservers = new ArrayList<>();
|
||||||
|
@ -84,7 +84,7 @@ public class RetrieveProfileAvatarJob extends BaseJob {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Util.equals(profileAvatar, recipient.resolve().getProfileAvatar())) {
|
if (AvatarHelper.avatarFileExists(context, recipient.resolve().getAddress()) && Util.equals(profileAvatar, recipient.resolve().getProfileAvatar())) {
|
||||||
Log.w(TAG, "Already retrieved profile avatar: " + profileAvatar);
|
Log.w(TAG, "Already retrieved profile avatar: " + profileAvatar);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
package org.thoughtcrime.securesms.util
|
package org.thoughtcrime.securesms.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.*
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.graphics.RectF
|
||||||
|
import android.graphics.Typeface
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import android.text.TextPaint
|
import android.text.TextPaint
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
|
|
||||||
object AvatarPlaceholderGenerator {
|
object AvatarPlaceholderGenerator {
|
||||||
|
|
||||||
@ -28,7 +34,7 @@ object AvatarPlaceholderGenerator {
|
|||||||
val colorPrimary = colorArray[(hash % colorArray.size).toInt()]
|
val colorPrimary = colorArray[(hash % colorArray.size).toInt()]
|
||||||
|
|
||||||
val labelText = when {
|
val labelText = when {
|
||||||
!TextUtils.isEmpty(displayName) -> extractLabel(displayName!!.capitalize())
|
!TextUtils.isEmpty(displayName) -> extractLabel(displayName!!.capitalize(Locale.ROOT))
|
||||||
!TextUtils.isEmpty(hashString) -> extractLabel(hashString)
|
!TextUtils.isEmpty(hashString) -> extractLabel(hashString)
|
||||||
else -> EMPTY_LABEL
|
else -> EMPTY_LABEL
|
||||||
}
|
}
|
||||||
@ -57,14 +63,19 @@ object AvatarPlaceholderGenerator {
|
|||||||
return BitmapDrawable(context.resources, bitmap)
|
return BitmapDrawable(context.resources, bitmap)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractLabel(content: String): String {
|
fun extractLabel(content: String): String {
|
||||||
var content = content.trim()
|
val trimmedContent = content.trim()
|
||||||
if (content.isEmpty()) return EMPTY_LABEL
|
if (trimmedContent.isEmpty()) return EMPTY_LABEL
|
||||||
return if (content.length > 2 && content.startsWith("05")) {
|
return if (trimmedContent.length > 2 && trimmedContent.startsWith("05")) {
|
||||||
content[2].toString().toUpperCase(Locale.ROOT)
|
trimmedContent[2].toString()
|
||||||
} else {
|
} else {
|
||||||
content.first().toString().toUpperCase(Locale.ROOT)
|
val splitWords = trimmedContent.split(Regex("\\W"))
|
||||||
|
if (splitWords.size < 2) {
|
||||||
|
trimmedContent.take(2)
|
||||||
|
} else {
|
||||||
|
splitWords.filter { word -> word.isNotEmpty() }.take(2).map { it.first() }.joinToString("")
|
||||||
}
|
}
|
||||||
|
}.uppercase()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSha512(input: String): String {
|
private fun getSha512(input: String): String {
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
package org.thoughtcrime.securesms.recipients
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator
|
||||||
|
|
||||||
|
class AvatarGeneratorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCommonAvatarFormats() {
|
||||||
|
val testNamesAndResults = mapOf(
|
||||||
|
"H " to "H",
|
||||||
|
"Test Name" to "TN",
|
||||||
|
"test name" to "TN",
|
||||||
|
"howdy partner" to "HP",
|
||||||
|
"testname" to "TE", //
|
||||||
|
"05aaapubkey" to "A", // pubkey values only return first non-05 character
|
||||||
|
"Test" to "TE"
|
||||||
|
)
|
||||||
|
testNamesAndResults.forEach { (test, expected) ->
|
||||||
|
val processed = AvatarPlaceholderGenerator.extractLabel(test)
|
||||||
|
assertEquals(expected, processed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -46,6 +46,11 @@ public class AvatarHelper {
|
|||||||
return new File(avatarDirectory, new File(address.serialize()).getName());
|
return new File(avatarDirectory, new File(address.serialize()).getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean avatarFileExists(@NonNull Context context , @NonNull Address address) {
|
||||||
|
File avatarFile = getAvatarFile(context, address);
|
||||||
|
return avatarFile.exists();
|
||||||
|
}
|
||||||
|
|
||||||
public static void setAvatar(@NonNull Context context, @NonNull Address address, @Nullable byte[] data)
|
public static void setAvatar(@NonNull Context context, @NonNull Address address, @Nullable byte[] data)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package org.session.libsession.messaging.sending_receiving
|
package org.session.libsession.messaging.sending_receiving
|
||||||
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
|
import org.session.libsession.avatars.AvatarHelper
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
import org.session.libsession.messaging.jobs.AttachmentDownloadJob
|
import org.session.libsession.messaging.jobs.AttachmentDownloadJob
|
||||||
import org.session.libsession.messaging.jobs.JobQueue
|
import org.session.libsession.messaging.jobs.JobQueue
|
||||||
@ -188,28 +189,33 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS
|
|||||||
val storage = MessagingModuleConfiguration.shared.storage
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
val context = MessagingModuleConfiguration.shared.context
|
val context = MessagingModuleConfiguration.shared.context
|
||||||
val userPublicKey = storage.getUserPublicKey()
|
val userPublicKey = storage.getUserPublicKey()
|
||||||
|
val messageSender: String? = message.sender
|
||||||
// Get or create thread
|
// Get or create thread
|
||||||
// FIXME: In case this is an open group this actually * doesn't * create the thread if it doesn't yet
|
// FIXME: In case this is an open group this actually * doesn't * create the thread if it doesn't yet
|
||||||
// exist. This is intentional, but it's very non-obvious.
|
// exist. This is intentional, but it's very non-obvious.
|
||||||
val threadID = storage.getOrCreateThreadIdFor(message.syncTarget
|
val threadID = storage.getOrCreateThreadIdFor(message.syncTarget
|
||||||
?: message.sender!!, message.groupPublicKey, openGroupID)
|
?: messageSender!!, message.groupPublicKey, openGroupID)
|
||||||
if (threadID < 0) {
|
if (threadID < 0) {
|
||||||
// Thread doesn't exist; should only be reached in a case where we are processing open group messages for a no longer existent thread
|
// Thread doesn't exist; should only be reached in a case where we are processing open group messages for a no longer existent thread
|
||||||
throw MessageReceiver.Error.NoThread
|
throw MessageReceiver.Error.NoThread
|
||||||
}
|
}
|
||||||
// Update profile if needed
|
// Update profile if needed
|
||||||
val recipient = Recipient.from(context, Address.fromSerialized(message.sender!!), false)
|
val recipient = Recipient.from(context, Address.fromSerialized(messageSender!!), false)
|
||||||
val profile = message.profile
|
val profile = message.profile
|
||||||
if (profile != null && userPublicKey != message.sender) {
|
if (profile != null && userPublicKey != messageSender) {
|
||||||
val profileManager = SSKEnvironment.shared.profileManager
|
val profileManager = SSKEnvironment.shared.profileManager
|
||||||
val name = profile.displayName!!
|
val name = profile.displayName!!
|
||||||
if (name.isNotEmpty()) {
|
if (name.isNotEmpty()) {
|
||||||
profileManager.setName(context, recipient, name)
|
profileManager.setName(context, recipient, name)
|
||||||
}
|
}
|
||||||
val newProfileKey = profile.profileKey
|
val newProfileKey = profile.profileKey
|
||||||
if (newProfileKey?.isNotEmpty() == true && (newProfileKey.size == 16 || newProfileKey.size == 32) && profile.profilePictureURL?.isNotEmpty() == true
|
|
||||||
&& (recipient.profileKey == null || !MessageDigest.isEqual(recipient.profileKey, newProfileKey))) {
|
val needsProfilePicture = !AvatarHelper.avatarFileExists(context, Address.fromSerialized(messageSender))
|
||||||
profileManager.setProfileKey(context, recipient, newProfileKey)
|
val profileKeyValid = newProfileKey?.isNotEmpty() == true && (newProfileKey.size == 16 || newProfileKey.size == 32) && profile.profilePictureURL?.isNotEmpty() == true
|
||||||
|
val profileKeyChanged = (recipient.profileKey == null || !MessageDigest.isEqual(recipient.profileKey, newProfileKey))
|
||||||
|
|
||||||
|
if ((profileKeyValid && profileKeyChanged) || (profileKeyValid && needsProfilePicture)) {
|
||||||
|
profileManager.setProfileKey(context, recipient, newProfileKey!!)
|
||||||
profileManager.setUnidentifiedAccessMode(context, recipient, Recipient.UnidentifiedAccessMode.UNKNOWN)
|
profileManager.setUnidentifiedAccessMode(context, recipient, Recipient.UnidentifiedAccessMode.UNKNOWN)
|
||||||
profileManager.setProfilePictureURL(context, recipient, profile.profilePictureURL!!)
|
profileManager.setProfilePictureURL(context, recipient, profile.profilePictureURL!!)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user