diff --git a/res/layout/conversation_item_received.xml b/res/layout/conversation_item_received.xml
index 5169b4eb01..491e69ca21 100644
--- a/res/layout/conversation_item_received.xml
+++ b/res/layout/conversation_item_received.xml
@@ -13,16 +13,6 @@
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
-
-
+
+
+
+
+
+
+
+
+ app:scaleEmojis="true"
+ tools:text="boop"/>
-
+
+
+
+
+
((ViewStub) findViewById(R.id.audio_view_stub));
this.documentViewStub = new Stub<>((ViewStub) findViewById(R.id.document_view_stub));
this.expirationTimer = (ExpirationTimerView) findViewById(R.id.expiration_indicator);
+ this.groupSenderHolder = findViewById(R.id.group_sender_holder);
setOnClickListener(new ClickListener(null));
@@ -205,6 +210,32 @@ public class ConversationItem extends LinearLayout
setExpiration(messageRecord);
}
+ @Override
+ public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ if (groupSenderHolder != null && groupSenderHolder.getVisibility() == View.VISIBLE) {
+ View content = (View) groupSenderHolder.getParent();
+
+ groupSenderHolder.layout(content.getPaddingLeft(), content.getPaddingTop(),
+ content.getWidth() - content.getPaddingRight(),
+ content.getPaddingTop() + groupSenderHolder.getMeasuredHeight());
+
+
+ if (DynamicLanguage.getLayoutDirection(context) == LAYOUT_DIRECTION_RTL) {
+ groupSenderProfileName.layout(groupSenderHolder.getPaddingLeft(),
+ groupSenderHolder.getPaddingTop(),
+ groupSenderHolder.getPaddingLeft() + groupSenderProfileName.getWidth(),
+ groupSenderHolder.getPaddingTop() + groupSenderProfileName.getHeight());
+ } else {
+ groupSenderProfileName.layout(groupSenderHolder.getWidth() - groupSenderHolder.getPaddingRight() - groupSenderProfileName.getWidth(),
+ groupSenderHolder.getPaddingTop(),
+ groupSenderHolder.getWidth() - groupSenderProfileName.getPaddingRight(),
+ groupSenderHolder.getPaddingTop() + groupSenderProfileName.getHeight());
+ }
+ }
+ }
+
private void initializeAttributes() {
final int[] attributes = new int[] {R.attr.conversation_item_bubble_background,
R.attr.conversation_list_item_background_selected,
@@ -499,10 +530,19 @@ public class ConversationItem extends LinearLayout
private void setGroupMessageStatus(MessageRecord messageRecord, Recipient recipient) {
if (groupThread && !messageRecord.isOutgoing()) {
- this.groupStatusText.setText(recipient.toShortString());
- this.groupStatusText.setVisibility(View.VISIBLE);
+ this.groupSender.setText(recipient.toShortString());
+
+ if (recipient.getName() == null && recipient.getProfileName() != null) {
+ this.groupSenderProfileName.setText("~" + recipient.getProfileName());
+ this.groupSenderProfileName.setVisibility(View.VISIBLE);
+ } else {
+ this.groupSenderProfileName.setText(null);
+ this.groupSenderProfileName.setVisibility(View.GONE);
+ }
+
+ this.groupSenderHolder.setVisibility(View.VISIBLE);
} else {
- this.groupStatusText.setVisibility(View.GONE);
+ this.groupSenderHolder.setVisibility(View.GONE);
}
}
diff --git a/src/org/thoughtcrime/securesms/ConversationTitleView.java b/src/org/thoughtcrime/securesms/ConversationTitleView.java
index ce7740a163..69be4d432c 100644
--- a/src/org/thoughtcrime/securesms/ConversationTitleView.java
+++ b/src/org/thoughtcrime/securesms/ConversationTitleView.java
@@ -65,23 +65,35 @@ public class ConversationTitleView extends LinearLayout {
}
private void setRecipientTitle(Recipient recipient) {
- if (!recipient.isGroupRecipient()) {
- if (TextUtils.isEmpty(recipient.getName())) {
- this.title.setText(recipient.getAddress().serialize());
- this.subtitle.setText(null);
- this.subtitle.setVisibility(View.GONE);
- } else {
- this.title.setText(recipient.getName());
+ if (recipient.isGroupRecipient()) setGroupRecipientTitle(recipient);
+ else if (TextUtils.isEmpty(recipient.getName())) setNonContactRecipientTitle(recipient);
+ else setContactRecipientTitle(recipient);
+ }
- if (recipient.getCustomLabel() != null) this.subtitle.setText(recipient.getCustomLabel());
- else this.subtitle.setText(recipient.getAddress().serialize());
+ private void setGroupRecipientTitle(Recipient recipient) {
+ this.title.setText(recipient.getName());
+ this.subtitle.setText(null);
+ this.subtitle.setVisibility(View.GONE);
+ }
- this.subtitle.setVisibility(View.VISIBLE);
- }
- } else {
- this.title.setText(recipient.getName());
+ private void setNonContactRecipientTitle(Recipient recipient) {
+ this.title.setText(recipient.getAddress().serialize());
+
+ if (TextUtils.isEmpty(recipient.getProfileName())) {
this.subtitle.setText(null);
this.subtitle.setVisibility(View.GONE);
+ } else {
+ this.subtitle.setText("~" + recipient.getProfileName());
+ this.subtitle.setVisibility(View.VISIBLE);
}
}
+
+ private void setContactRecipientTitle(Recipient recipient) {
+ this.title.setText(recipient.getName());
+
+ if (recipient.getCustomLabel() != null) this.subtitle.setText(recipient.getCustomLabel());
+ else this.subtitle.setText(recipient.getAddress().serialize());
+
+ this.subtitle.setVisibility(View.VISIBLE);
+ }
}
diff --git a/src/org/thoughtcrime/securesms/GroupMembersDialog.java b/src/org/thoughtcrime/securesms/GroupMembersDialog.java
index acf675aecd..fcdd6e4576 100644
--- a/src/org/thoughtcrime/securesms/GroupMembersDialog.java
+++ b/src/org/thoughtcrime/securesms/GroupMembersDialog.java
@@ -111,7 +111,13 @@ public class GroupMembersDialog extends AsyncTask> {
if (isLocalNumber(recipient)) {
recipientStrings.add(context.getString(R.string.GroupMembersDialog_me));
} else {
- recipientStrings.add(recipient.toShortString());
+ String name = recipient.toShortString();
+
+ if (recipient.getName() == null && recipient.getProfileName() != null) {
+ name += " ~" + recipient.getProfileName();
+ }
+
+ recipientStrings.add(name);
}
}
diff --git a/src/org/thoughtcrime/securesms/components/FromTextView.java b/src/org/thoughtcrime/securesms/components/FromTextView.java
index 804fc736a8..1ac461af6d 100644
--- a/src/org/thoughtcrime/securesms/components/FromTextView.java
+++ b/src/org/thoughtcrime/securesms/components/FromTextView.java
@@ -4,13 +4,21 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.text.Spannable;
+import android.text.SpannableString;
import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.AbsoluteSizeSpan;
+import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
+import android.text.style.TypefaceSpan;
import android.util.AttributeSet;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.util.DynamicLanguage;
+import org.thoughtcrime.securesms.util.ResUtil;
+import org.thoughtcrime.securesms.util.spans.CenterAlignedRelativeSizeSpan;
public class FromTextView extends EmojiTextView {
@@ -41,9 +49,28 @@ public class FromTextView extends EmojiTextView {
typeface = Typeface.NORMAL;
}
- SpannableStringBuilder builder = new SpannableStringBuilder(fromString);
- builder.setSpan(new StyleSpan(typeface), 0, builder.length(),
- Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+ SpannableStringBuilder builder = new SpannableStringBuilder();
+
+ SpannableString fromSpan = new SpannableString(fromString);
+ fromSpan.setSpan(new StyleSpan(typeface), 0, builder.length(),
+ Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ if (recipient.getName() == null && recipient.getProfileName() != null) {
+ SpannableString profileName = new SpannableString(" (~" + recipient.getProfileName() + ") ");
+ profileName.setSpan(new CenterAlignedRelativeSizeSpan(0.75f), 0, profileName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ profileName.setSpan(new TypefaceSpan("sans-serif-light"), 0, profileName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ profileName.setSpan(new ForegroundColorSpan(ResUtil.getColor(getContext(), R.attr.conversation_list_item_subject_color)), 0, profileName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ if (DynamicLanguage.getLayoutDirection(getContext()) == LAYOUT_DIRECTION_RTL){
+ builder.append(profileName);
+ builder.append(fromSpan);
+ } else {
+ builder.append(fromSpan);
+ builder.append(profileName);
+ }
+ } else {
+ builder.append(fromSpan);
+ }
colors.recycle();
diff --git a/src/org/thoughtcrime/securesms/contacts/avatars/ContactPhotoFactory.java b/src/org/thoughtcrime/securesms/contacts/avatars/ContactPhotoFactory.java
index 94cc521bc5..7a9ad21601 100644
--- a/src/org/thoughtcrime/securesms/contacts/avatars/ContactPhotoFactory.java
+++ b/src/org/thoughtcrime/securesms/contacts/avatars/ContactPhotoFactory.java
@@ -73,10 +73,10 @@ public class ContactPhotoFactory {
return new BitmapContactPhoto(BitmapFactory.decodeByteArray(avatar, 0, avatar.length));
}
- private static ContactPhoto getSignalAvatarContactPhoto(@NonNull Context context,
- @NonNull Address address,
- @Nullable String name,
- int targetSize)
+ public static ContactPhoto getSignalAvatarContactPhoto(@NonNull Context context,
+ @NonNull Address address,
+ @Nullable String name,
+ int targetSize)
{
try {
Bitmap bitmap = Glide.with(context)
diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
index b1075bd3d4..b7da217334 100644
--- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
+++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
@@ -1288,9 +1288,12 @@ public class DatabaseFactory {
String address = new NumberMigrator(TextSecurePreferences.getLocalNumber(context)).migrate(cursor.getString(0));
ContentValues contentValues = new ContentValues(1);
- contentValues.put("recipient_ids", address);
contentValues.put("registered", cursor.getInt(1) == 1);
- db.replace("recipient_preferences", null, contentValues);
+
+ if (db.update("recipient_preferences", contentValues, "recipient_ids = ?", new String[] {address}) < 1) {
+ contentValues.put("recipient_ids", address);
+ db.insert("recipient_preferences", null, contentValues);
+ }
}
}
diff --git a/src/org/thoughtcrime/securesms/database/RecipientPreferenceDatabase.java b/src/org/thoughtcrime/securesms/database/RecipientPreferenceDatabase.java
index b1b28c2b67..23371d0383 100644
--- a/src/org/thoughtcrime/securesms/database/RecipientPreferenceDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/RecipientPreferenceDatabase.java
@@ -171,6 +171,18 @@ public class RecipientPreferenceDatabase extends Database {
signalProfileAvatar));
}
+ public BulkOperationsHandle resetAllDisplayNames() {
+ SQLiteDatabase database = databaseHelper.getWritableDatabase();
+ database.beginTransaction();
+
+ ContentValues contentValues = new ContentValues(1);
+ contentValues.put(SYSTEM_DISPLAY_NAME, (String)null);
+
+ database.update(TABLE_NAME, contentValues, null, null);
+
+ return new BulkOperationsHandle(database);
+ }
+
public void setColor(Recipient recipient, MaterialColor color) {
ContentValues values = new ContentValues();
values.put(COLOR, color.serialize());
@@ -266,19 +278,17 @@ public class RecipientPreferenceDatabase extends Database {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
for (Address activeAddress : activeAddresses) {
- ContentValues contentValues = new ContentValues(2);
- contentValues.put(ADDRESS, activeAddress.serialize());
+ ContentValues contentValues = new ContentValues(1);
contentValues.put(REGISTERED, 1);
- db.replace(TABLE_NAME, null, contentValues);
+ updateOrInsert(activeAddress, contentValues);
}
for (Address inactiveAddress : inactiveAddresses) {
- ContentValues contentValues = new ContentValues(2);
- contentValues.put(ADDRESS, inactiveAddress.serialize());
+ ContentValues contentValues = new ContentValues(1);
contentValues.put(REGISTERED, 0);
- db.replace(TABLE_NAME, null, contentValues);
+ updateOrInsert(inactiveAddress, contentValues);
}
context.getContentResolver().notifyChange(Uri.parse(RECIPIENT_PREFERENCES_URI), null);
@@ -302,6 +312,15 @@ public class RecipientPreferenceDatabase extends Database {
database.beginTransaction();
+ updateOrInsert(database, address, contentValues);
+
+ database.setTransactionSuccessful();
+ database.endTransaction();
+
+ context.getContentResolver().notifyChange(Uri.parse(RECIPIENT_PREFERENCES_URI), null);
+ }
+
+ private void updateOrInsert(SQLiteDatabase database, Address address, ContentValues contentValues) {
int updated = database.update(TABLE_NAME, contentValues, ADDRESS + " = ?",
new String[] {address.serialize()});
@@ -309,11 +328,28 @@ public class RecipientPreferenceDatabase extends Database {
contentValues.put(ADDRESS, address.serialize());
database.insert(TABLE_NAME, null, contentValues);
}
+ }
- database.setTransactionSuccessful();
- database.endTransaction();
+ public class BulkOperationsHandle {
- context.getContentResolver().notifyChange(Uri.parse(RECIPIENT_PREFERENCES_URI), null);
+ private final SQLiteDatabase database;
+
+ public BulkOperationsHandle(SQLiteDatabase database) {
+ this.database = database;
+ }
+
+ public void setDisplayName(@NonNull Address address, @Nullable String displayName) {
+ ContentValues contentValues = new ContentValues(1);
+ contentValues.put(SYSTEM_DISPLAY_NAME, displayName);
+ updateOrInsert(address, contentValues);
+ }
+
+ public void finish() {
+ database.setTransactionSuccessful();
+ database.endTransaction();
+ RecipientFactory.clearCache(context);
+ context.getContentResolver().notifyChange(Uri.parse(RECIPIENT_PREFERENCES_URI), null);
+ }
}
public static class RecipientsPreferences {
diff --git a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java
index 48817c41c7..1e43ebd486 100644
--- a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java
+++ b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java
@@ -65,7 +65,7 @@ public class GroupMessageProcessor {
if (record.isPresent() && group.getType() == Type.UPDATE) {
return handleGroupUpdate(context, masterSecret, envelope, group, record.get(), outgoing);
- } else if (record.isPresent() && group.getType() == Type.UPDATE) {
+ } else if (!record.isPresent() && group.getType() == Type.UPDATE) {
return handleGroupCreate(context, masterSecret, envelope, group, outgoing);
} else if (record.isPresent() && group.getType() == Type.QUIT) {
return handleGroupLeave(context, masterSecret, envelope, group, record.get(), outgoing);
diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java
index d0590efa9f..cd525cf347 100644
--- a/src/org/thoughtcrime/securesms/recipients/Recipient.java
+++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java
@@ -64,6 +64,7 @@ public class Recipient implements RecipientModifiedListener {
private boolean blocked = false;
private VibrateState vibrate = VibrateState.DEFAULT;
private int expireMessages = 0;
+ private String profileName = null;
@Nullable private MaterialColor color;
@@ -88,6 +89,9 @@ public class Recipient implements RecipientModifiedListener {
this.blocked = stale.blocked;
this.vibrate = stale.vibrate;
this.expireMessages = stale.expireMessages;
+ this.profileName = stale.profileName;
+ this.participants.clear();
+ this.participants.addAll(stale.participants);
}
if (details.isPresent()) {
@@ -99,6 +103,7 @@ public class Recipient implements RecipientModifiedListener {
this.blocked = details.get().blocked;
this.vibrate = details.get().vibrateState;
this.expireMessages = details.get().expireMessages;
+ this.profileName = details.get().profileName;
this.participants.clear();
this.participants.addAll(details.get().participants);
}
@@ -118,6 +123,7 @@ public class Recipient implements RecipientModifiedListener {
Recipient.this.blocked = result.blocked;
Recipient.this.vibrate = result.vibrateState;
Recipient.this.expireMessages = result.expireMessages;
+ Recipient.this.profileName = result.profileName;
Recipient.this.participants.clear();
Recipient.this.participants.addAll(result.participants);
@@ -151,6 +157,7 @@ public class Recipient implements RecipientModifiedListener {
this.blocked = details.blocked;
this.vibrate = details.vibrateState;
this.expireMessages = details.expireMessages;
+ this.profileName = details.profileName;
this.participants.addAll(details.participants);
this.resolving = false;
}
@@ -196,6 +203,10 @@ public class Recipient implements RecipientModifiedListener {
return customLabel;
}
+ public @Nullable String getProfileName() {
+ return profileName;
+ }
+
public boolean isGroupRecipient() {
return address.isGroup();
}
@@ -208,7 +219,7 @@ public class Recipient implements RecipientModifiedListener {
return address.isGroup() && !address.isMmsGroup();
}
- public List getParticipants() {
+ public @NonNull List getParticipants() {
return participants;
}
diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java b/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java
index 5747209e30..ea4196207b 100644
--- a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java
+++ b/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java
@@ -154,7 +154,7 @@ class RecipientProvider {
}
if (STATIC_DETAILS.containsKey(address.serialize())) return STATIC_DETAILS.get(address.serialize());
- else return new RecipientDetails(null, null, null, ContactPhotoFactory.getDefaultContactPhoto(null), preferences.orNull(), null);
+ else return new RecipientDetails(null, null, null, ContactPhotoFactory.getSignalAvatarContactPhoto(context, address, null, context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size)), preferences.orNull(), null);
}
private @NonNull RecipientDetails getGroupRecipientDetails(Context context, Address groupId, Optional groupRecord, Optional preferences, boolean asynchronous) {
@@ -198,6 +198,7 @@ class RecipientProvider {
public final boolean blocked;
public final int expireMessages;
@NonNull public final List participants;
+ @Nullable public final String profileName;
public RecipientDetails(@Nullable String name, @Nullable String customLabel,
@Nullable Uri contactUri, @NonNull ContactPhoto avatar,
@@ -214,6 +215,7 @@ class RecipientProvider {
this.blocked = preferences != null && preferences.isBlocked();
this.expireMessages = preferences != null ? preferences.getExpireMessages() : 0;
this.participants = participants == null ? new LinkedList() : participants;
+ this.profileName = preferences != null ? preferences.getProfileName() : null;
if (name == null && preferences != null) this.name = preferences.getSystemDisplayName();
else this.name = name;
diff --git a/src/org/thoughtcrime/securesms/util/DirectoryHelper.java b/src/org/thoughtcrime/securesms/util/DirectoryHelper.java
index b178a1176d..22d022bdd2 100644
--- a/src/org/thoughtcrime/securesms/util/DirectoryHelper.java
+++ b/src/org/thoughtcrime/securesms/util/DirectoryHelper.java
@@ -162,17 +162,22 @@ public class DirectoryHelper {
List newUsers = DatabaseFactory.getContactsDatabase(context)
.setRegisteredUsers(account.get().getAccount(), activeAddresses, removeMissing);
- Cursor cursor = ContactAccessor.getInstance().getAllSystemContacts(context);
+ Cursor cursor = ContactAccessor.getInstance().getAllSystemContacts(context);
+ RecipientPreferenceDatabase.BulkOperationsHandle handle = DatabaseFactory.getRecipientPreferenceDatabase(context).resetAllDisplayNames();
- while (cursor != null && cursor.moveToNext()) {
- String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER));
+ try {
+ while (cursor != null && cursor.moveToNext()) {
+ String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER));
- if (!TextUtils.isEmpty(number)) {
- Address address = Address.fromExternal(context, number);
- String displayName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
+ if (!TextUtils.isEmpty(number)) {
+ Address address = Address.fromExternal(context, number);
+ String displayName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
- DatabaseFactory.getRecipientPreferenceDatabase(context).setSystemDisplayName(address, displayName);
+ handle.setDisplayName(address, displayName);
+ }
}
+ } finally {
+ handle.finish();
}
return new RefreshResult(newUsers, account.get().isFresh());
diff --git a/src/org/thoughtcrime/securesms/util/spans/CenterAlignedRelativeSizeSpan.java b/src/org/thoughtcrime/securesms/util/spans/CenterAlignedRelativeSizeSpan.java
new file mode 100644
index 0000000000..b15c96137d
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/util/spans/CenterAlignedRelativeSizeSpan.java
@@ -0,0 +1,25 @@
+package org.thoughtcrime.securesms.util.spans;
+
+
+import android.text.TextPaint;
+import android.text.style.MetricAffectingSpan;
+
+public class CenterAlignedRelativeSizeSpan extends MetricAffectingSpan {
+
+ private final float relativeSize;
+
+ public CenterAlignedRelativeSizeSpan(float relativeSize) {
+ this.relativeSize = relativeSize;
+ }
+
+ @Override
+ public void updateMeasureState(TextPaint p) {
+ updateDrawState(p);
+ }
+
+ @Override
+ public void updateDrawState(TextPaint tp) {
+ tp.setTextSize(tp.getTextSize() * relativeSize);
+ tp.baselineShift += (int) (tp.ascent() * relativeSize) / 4;
+ }
+}