Display profile name in when appropriate

Display in conversation list, conversation actionbar, group
messages, and group members list when address is not in system
contacts

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2017-08-15 19:23:42 -07:00
parent 77a216b705
commit e7c20499ec
14 changed files with 270 additions and 59 deletions

View File

@ -13,16 +13,6 @@
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView android:id="@+id/group_message_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="5dp"
android:fontFamily="sans-serif-light"
android:textSize="13sp"
android:textColor="?attr/conversation_group_member_name"
android:visibility="gone" />
<RelativeLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
@ -49,6 +39,42 @@
android:orientation="vertical"
tools:backgroundTint="@color/blue_900">
<LinearLayout android:id="@+id/group_sender_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone"
android:paddingRight="4dp"
android:paddingLeft="4dp"
android:layout_marginBottom="10dp"
tools:visibility="visible">
<TextView android:id="@+id/group_message_sender"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="4sp"
android:paddingEnd="4sp"
android:textSize="13sp"
android:textColor="?conversation_item_received_text_primary_color"
android:maxLines="1"
android:ellipsize="end"
tools:visibility="visible"
tools:text="+14152222222"/>
<TextView android:id="@+id/group_message_sender_profile"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="4sp"
android:paddingLeft="4sp"
android:fontFamily="sans-serif-light"
android:textColor="?conversation_item_received_text_secondary_color"
android:textSize="13sp"
android:maxLines="1"
android:ellipsize="end"
tools:text="~Clement Duval"/>
</LinearLayout>
<ViewStub
android:id="@+id/image_view_stub"
android:layout="@layout/conversation_item_received_thumbnail"
@ -76,7 +102,8 @@
android:textColor="?conversation_item_received_text_primary_color"
android:textColorLink="?conversation_item_received_text_primary_color"
android:textSize="@dimen/conversation_item_body_text_size"
app:scaleEmojis="true" />
app:scaleEmojis="true"
tools:text="boop"/>
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@ -75,7 +75,12 @@
android:paddingRight="4dp"
android:layout_gravity="right">
<TextView android:id="@+id/group_message_status"
<View android:id="@+id/group_sender_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"/>
<TextView android:id="@+id/group_message_sender"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:linksClickable="false"
@ -86,6 +91,18 @@
android:layout_marginRight="8dip"
android:paddingTop="1dip" />
<TextView android:id="@+id/group_message_sender_profile"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:linksClickable="false"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_gravity="right"
android:textColor="?conversation_item_sent_text_secondary_color"
android:visibility="gone"
android:layout_marginRight="8dip"
android:paddingTop="1dip" />
<TextView android:id="@+id/conversation_item_date"
android:autoLink="none"
android:layout_width="wrap_content"

View File

@ -73,6 +73,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.LongClickCopySpan;
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
@ -112,7 +113,9 @@ public class ConversationItem extends LinearLayout
private TextView dateText;
private TextView simInfoText;
private TextView indicatorText;
private TextView groupStatusText;
private TextView groupSender;
private TextView groupSenderProfileName;
private View groupSenderHolder;
private ImageView secureImage;
private AvatarImageView contactPhoto;
private DeliveryStatusView deliveryStatusIndicator;
@ -156,7 +159,8 @@ public class ConversationItem extends LinearLayout
this.dateText = (TextView) findViewById(R.id.conversation_item_date);
this.simInfoText = (TextView) findViewById(R.id.sim_info);
this.indicatorText = (TextView) findViewById(R.id.indicator_text);
this.groupStatusText = (TextView) findViewById(R.id.group_message_status);
this.groupSender = (TextView) findViewById(R.id.group_message_sender);
this.groupSenderProfileName = (TextView) findViewById(R.id.group_message_sender_profile);
this.secureImage = (ImageView) findViewById(R.id.secure_indicator);
this.deliveryStatusIndicator = (DeliveryStatusView) findViewById(R.id.delivery_status);
this.alertView = (AlertView) findViewById(R.id.indicators_parent);
@ -166,6 +170,7 @@ public class ConversationItem extends LinearLayout
this.audioViewStub = new Stub<>((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);
}
}

View File

@ -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);
}
}

View File

@ -111,7 +111,13 @@ public class GroupMembersDialog extends AsyncTask<Void, Void, List<Recipient>> {
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);
}
}

View File

@ -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();

View File

@ -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)

View File

@ -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);
}
}
}

View File

@ -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 {

View File

@ -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);

View File

@ -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<Recipient> getParticipants() {
public @NonNull List<Recipient> getParticipants() {
return participants;
}

View File

@ -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> groupRecord, Optional<RecipientsPreferences> preferences, boolean asynchronous) {
@ -198,6 +198,7 @@ class RecipientProvider {
public final boolean blocked;
public final int expireMessages;
@NonNull public final List<Recipient> 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<Recipient>() : participants;
this.profileName = preferences != null ? preferences.getProfileName() : null;
if (name == null && preferences != null) this.name = preferences.getSystemDisplayName();
else this.name = name;

View File

@ -162,17 +162,22 @@ public class DirectoryHelper {
List<Address> 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());

View File

@ -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;
}
}