Add support for an 'About' field on your profile.

This commit is contained in:
Greyson Parrelli
2021-01-21 12:35:00 -05:00
parent e80033c287
commit 7db16e6156
42 changed files with 709 additions and 119 deletions

View File

@@ -30,7 +30,7 @@ public class EmojiKeyboardProvider implements MediaKeyboardProvider,
{
private static final KeyEvent DELETE_KEY_EVENT = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
private static final String RECENT_STORAGE_KEY = "pref_recent_emoji2";
public static final String RECENT_STORAGE_KEY = "pref_recent_emoji2";
private final Context context;
private final List<EmojiPageModel> models;

View File

@@ -1,7 +1,14 @@
package org.thoughtcrime.securesms.components.emoji;
import androidx.annotation.NonNull;
import android.content.Context;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
import org.thoughtcrime.securesms.util.StringUtil;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.Pair;
import java.util.HashMap;
@@ -72,4 +79,15 @@ public final class EmojiUtil {
String canonical = VARIATION_MAP.get(emoji);
return canonical != null ? canonical : emoji;
}
/**
* Converts the provided emoji string into a single drawable, if possible.
*/
public static @Nullable Drawable convertToDrawable(@NonNull Context context, @Nullable String emoji) {
if (Util.isEmpty(emoji)) {
return null;
} else {
return EmojiProvider.getInstance(context).getEmojiDrawable(emoji);
}
}
}

View File

@@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.CursorUtil;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.Pair;
@@ -42,6 +43,7 @@ public class ContactRepository {
static final String NUMBER_TYPE_COLUMN = "number_type";
static final String LABEL_COLUMN = "label";
static final String CONTACT_TYPE_COLUMN = "contact_type";
static final String ABOUT_COLUMN = "about";
static final int NORMAL_TYPE = 0;
static final int PUSH_TYPE = 1;
@@ -52,18 +54,18 @@ public class ContactRepository {
/** Maps the recipient results to the legacy contact column names */
private static final List<Pair<String, ValueMapper>> SEARCH_CURSOR_MAPPERS = new ArrayList<Pair<String, ValueMapper>>() {{
add(new Pair<>(ID_COLUMN, cursor -> cursor.getLong(cursor.getColumnIndexOrThrow(RecipientDatabase.ID))));
add(new Pair<>(ID_COLUMN, cursor -> CursorUtil.requireLong(cursor, RecipientDatabase.ID)));
add(new Pair<>(NAME_COLUMN, cursor -> {
String system = cursor.getString(cursor.getColumnIndexOrThrow(RecipientDatabase.SYSTEM_DISPLAY_NAME));
String profile = cursor.getString(cursor.getColumnIndexOrThrow(RecipientDatabase.SEARCH_PROFILE_NAME));
String system = CursorUtil.requireString(cursor, RecipientDatabase.SYSTEM_DISPLAY_NAME);
String profile = CursorUtil.requireString(cursor, RecipientDatabase.SEARCH_PROFILE_NAME);
return Util.getFirstNonEmpty(system, profile);
}));
add(new Pair<>(NUMBER_COLUMN, cursor -> {
String phone = cursor.getString(cursor.getColumnIndexOrThrow(RecipientDatabase.PHONE));
String email = cursor.getString(cursor.getColumnIndexOrThrow(RecipientDatabase.EMAIL));
String phone = CursorUtil.requireString(cursor, RecipientDatabase.PHONE);
String email = CursorUtil.requireString(cursor, RecipientDatabase.EMAIL);
if (phone != null) {
phone = PhoneNumberFormatter.prettyPrint(phone);
@@ -72,14 +74,31 @@ public class ContactRepository {
return Util.getFirstNonEmpty(phone, email);
}));
add(new Pair<>(NUMBER_TYPE_COLUMN, cursor -> cursor.getInt(cursor.getColumnIndexOrThrow(RecipientDatabase.SYSTEM_PHONE_TYPE))));
add(new Pair<>(NUMBER_TYPE_COLUMN, cursor -> CursorUtil.requireInt(cursor, RecipientDatabase.SYSTEM_PHONE_TYPE)));
add(new Pair<>(LABEL_COLUMN, cursor -> cursor.getString(cursor.getColumnIndexOrThrow(RecipientDatabase.SYSTEM_PHONE_LABEL))));
add(new Pair<>(LABEL_COLUMN, cursor -> CursorUtil.requireString(cursor, RecipientDatabase.SYSTEM_PHONE_LABEL)));
add(new Pair<>(CONTACT_TYPE_COLUMN, cursor -> {
int registered = cursor.getInt(cursor.getColumnIndexOrThrow(RecipientDatabase.REGISTERED));
int registered = CursorUtil.requireInt(cursor, RecipientDatabase.REGISTERED);
return registered == RecipientDatabase.RegisteredState.REGISTERED.getId() ? PUSH_TYPE : NORMAL_TYPE;
}));
add(new Pair<>(ABOUT_COLUMN, cursor -> {
String aboutEmoji = CursorUtil.requireString(cursor, RecipientDatabase.ABOUT_EMOJI);
String about = CursorUtil.requireString(cursor, RecipientDatabase.ABOUT);
if (aboutEmoji != null) {
if (about != null) {
return aboutEmoji + " " + about;
} else {
return aboutEmoji;
}
} else if (about != null) {
return about;
} else {
return "";
}
}));
}};
public ContactRepository(@NonNull Context context) {
@@ -106,7 +125,7 @@ public class ContactRepository {
if (shouldAdd) {
MatrixCursor selfCursor = new MatrixCursor(RecipientDatabase.SEARCH_PROJECTION_NAMES);
selfCursor.addRow(new Object[]{ self.getId().serialize(), noteToSelfTitle, null, self.getE164().or(""), self.getEmail().orNull(), null, -1, RecipientDatabase.RegisteredState.REGISTERED.getId(), noteToSelfTitle });
selfCursor.addRow(new Object[]{ self.getId().serialize(), noteToSelfTitle, self.getE164().or(""), self.getEmail().orNull(), null, -1, RecipientDatabase.RegisteredState.REGISTERED.getId(), self.getAbout(), self.getAboutEmoji(), noteToSelfTitle, noteToSelfTitle });
cursor = cursor == null ? selfCursor : new MergeCursor(new Cursor[]{ cursor, selfCursor });
}

View File

@@ -40,6 +40,7 @@ import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter.ViewHolde
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.CursorUtil;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration.StickyHeaderAdapter;
import org.thoughtcrime.securesms.util.Util;
@@ -97,7 +98,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
super(itemView);
}
public abstract void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, int color, boolean checkboxVisible);
public abstract void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, int color, boolean checkboxVisible);
public abstract void unbind(@NonNull GlideRequests glideRequests);
public abstract void setChecked(boolean checked);
public abstract void setEnabled(boolean enabled);
@@ -117,8 +118,8 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
return (ContactSelectionListItem) itemView;
}
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, int color, boolean checkBoxVisible) {
getView().set(glideRequests, recipientId, type, name, number, label, color, checkBoxVisible);
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, int color, boolean checkBoxVisible) {
getView().set(glideRequests, recipientId, type, name, number, label, about, color, checkBoxVisible);
}
@Override
@@ -147,7 +148,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
}
@Override
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, int color, boolean checkboxVisible) {
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, int color, boolean checkboxVisible) {
this.label.setText(name);
}
@@ -204,13 +205,14 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
@Override
public void onBindItemViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) {
String rawId = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.ID_COLUMN));
String rawId = CursorUtil.requireString(cursor, ContactRepository.ID_COLUMN);
RecipientId id = rawId != null ? RecipientId.from(rawId) : null;
int contactType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactRepository.CONTACT_TYPE_COLUMN));
String name = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NAME_COLUMN ));
String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_COLUMN));
int numberType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_TYPE_COLUMN ));
String label = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.LABEL_COLUMN ));
int contactType = CursorUtil.requireInt(cursor, ContactRepository.CONTACT_TYPE_COLUMN);
String name = CursorUtil.requireString(cursor, ContactRepository.NAME_COLUMN);
String number = CursorUtil.requireString(cursor, ContactRepository.NUMBER_COLUMN);
int numberType = CursorUtil.requireInt(cursor, ContactRepository.NUMBER_TYPE_COLUMN);
String about = CursorUtil.requireString(cursor, ContactRepository.ABOUT_COLUMN);
String label = CursorUtil.requireString(cursor, ContactRepository.LABEL_COLUMN);
String labelText = ContactsContract.CommonDataKinds.Phone.getTypeLabel(getContext().getResources(),
numberType, label).toString();
@@ -220,7 +222,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
boolean currentContact = currentContacts.contains(id);
viewHolder.unbind(glideRequests);
viewHolder.bind(glideRequests, id, contactType, name, number, labelText, color, multiSelect || currentContact);
viewHolder.bind(glideRequests, id, contactType, name, number, labelText, about, color, multiSelect || currentContact);
viewHolder.setEnabled(true);
if (currentContact) {
@@ -239,10 +241,10 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
throw new AssertionError();
}
String rawId = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.ID_COLUMN));
String rawId = CursorUtil.requireString(cursor, ContactRepository.ID_COLUMN);
RecipientId id = rawId != null ? RecipientId.from(rawId) : null;
int numberType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_TYPE_COLUMN));
String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_COLUMN));
int numberType = CursorUtil.requireInt(cursor, ContactRepository.NUMBER_TYPE_COLUMN);
String number = CursorUtil.requireString(cursor, ContactRepository.NUMBER_COLUMN);
viewHolder.setEnabled(true);
@@ -258,7 +260,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
@Override
public int getItemViewType(@NonNull Cursor cursor) {
if (cursor.getInt(cursor.getColumnIndexOrThrow(ContactRepository.CONTACT_TYPE_COLUMN)) == ContactRepository.DIVIDER_TYPE) {
if (CursorUtil.requireInt(cursor, ContactRepository.CONTACT_TYPE_COLUMN) == ContactRepository.DIVIDER_TYPE) {
return VIEW_TYPE_DIVIDER;
} else {
return VIEW_TYPE_CONTACT;
@@ -317,7 +319,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
}
Cursor cursor = getCursorAtPositionOrThrow(position);
String letter = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NAME_COLUMN));
String letter = CursorUtil.requireString(cursor, ContactRepository.NAME_COLUMN);
if (letter != null) {
letter = letter.trim();

View File

@@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.whispersystems.libsignal.util.guava.Optional;
@@ -65,6 +66,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
String name,
String number,
String label,
String about,
int color,
boolean checkboxVisible)
{
@@ -87,7 +89,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
this.numberView.setTextColor(color);
this.contactPhotoImage.setAvatar(glideRequests, recipientSnapshot, false);
setText(recipientSnapshot, type, name, number, label);
setText(recipientSnapshot, type, name, number, label, about);
this.checkBox.setVisibility(checkboxVisible ? View.VISIBLE : View.GONE);
}
@@ -110,7 +112,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
}
@SuppressLint("SetTextI18n")
private void setText(@Nullable Recipient recipient, int type, String name, String number, String label) {
private void setText(@Nullable Recipient recipient, int type, String name, String number, String label, @Nullable String about) {
if (number == null || number.isEmpty()) {
this.nameView.setEnabled(false);
this.numberView.setText("");
@@ -120,7 +122,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
this.numberView.setText(getGroupMemberCount(recipient));
this.labelView.setVisibility(View.GONE);
} else if (type == ContactRepository.PUSH_TYPE) {
this.numberView.setText(number);
this.numberView.setText(!Util.isEmpty(about) ? about : number);
this.nameView.setEnabled(true);
this.labelView.setVisibility(View.GONE);
} else if (type == ContactRepository.NEW_USERNAME_TYPE) {
@@ -129,7 +131,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
this.labelView.setText(label);
this.labelView.setVisibility(View.VISIBLE);
} else {
this.numberView.setText(number);
this.numberView.setText(!Util.isEmpty(about) ? about : number);
this.nameView.setEnabled(true);
this.labelView.setText(label != null && !label.equals("null") ? label : "");
this.labelView.setVisibility(View.VISIBLE);

View File

@@ -71,7 +71,8 @@ public class ContactsCursorLoader extends CursorLoader {
ContactRepository.NUMBER_COLUMN,
ContactRepository.NUMBER_TYPE_COLUMN,
ContactRepository.LABEL_COLUMN,
ContactRepository.CONTACT_TYPE_COLUMN};
ContactRepository.CONTACT_TYPE_COLUMN,
ContactRepository.ABOUT_COLUMN};
private static final int RECENT_CONVERSATION_MAX = 25;
@@ -212,7 +213,8 @@ public class ContactsCursorLoader extends CursorLoader {
"",
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
"",
ContactRepository.DIVIDER_TYPE });
ContactRepository.DIVIDER_TYPE,
"" });
return recentsHeader;
}
@@ -223,7 +225,8 @@ public class ContactsCursorLoader extends CursorLoader {
"",
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
"",
ContactRepository.DIVIDER_TYPE });
ContactRepository.DIVIDER_TYPE,
"" });
return contactsHeader;
}
@@ -234,7 +237,8 @@ public class ContactsCursorLoader extends CursorLoader {
"",
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
"",
ContactRepository.DIVIDER_TYPE });
ContactRepository.DIVIDER_TYPE,
"" });
return groupHeader;
}
@@ -245,7 +249,8 @@ public class ContactsCursorLoader extends CursorLoader {
"",
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
"",
ContactRepository.DIVIDER_TYPE });
ContactRepository.DIVIDER_TYPE,
"" });
return contactsHeader;
}
@@ -256,7 +261,8 @@ public class ContactsCursorLoader extends CursorLoader {
"",
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
"",
ContactRepository.DIVIDER_TYPE });
ContactRepository.DIVIDER_TYPE,
"" });
return contactsHeader;
}
@@ -281,7 +287,8 @@ public class ContactsCursorLoader extends CursorLoader {
stringId,
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
"",
ContactRepository.RECENT_TYPE });
ContactRepository.RECENT_TYPE,
recipient.getCombinedAboutAndEmoji() });
}
}
return recentConversations;
@@ -316,7 +323,8 @@ public class ContactsCursorLoader extends CursorLoader {
groupRecord.getId(),
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
"",
ContactRepository.NORMAL_TYPE });
ContactRepository.NORMAL_TYPE,
"" });
}
}
return groupContacts;
@@ -329,7 +337,8 @@ public class ContactsCursorLoader extends CursorLoader {
filter,
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
"\u21e2",
ContactRepository.NEW_PHONE_TYPE});
ContactRepository.NEW_PHONE_TYPE,
"" });
return newNumberCursor;
}
@@ -340,7 +349,8 @@ public class ContactsCursorLoader extends CursorLoader {
filter,
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
"\u21e2",
ContactRepository.NEW_USERNAME_TYPE});
ContactRepository.NEW_USERNAME_TYPE,
"" });
return cursor;
}
@@ -368,7 +378,8 @@ public class ContactsCursorLoader extends CursorLoader {
cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_COLUMN)),
cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_TYPE_COLUMN)),
cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.LABEL_COLUMN)),
ContactRepository.NORMAL_TYPE});
ContactRepository.NORMAL_TYPE,
"" });
}
}
Log.i(TAG, "filterNonPushContacts() -> " + (System.currentTimeMillis() - startMillis) + "ms");

View File

@@ -395,8 +395,8 @@ public class ConversationActivity extends PassphraseRequiredActivity
private LiveRecipient recipient;
private long threadId;
private int distributionType;
private int reactWithAnyEmojiStartPage;
private boolean isSecureText;
private int reactWithAnyEmojiStartPage = -1;
private boolean isDefaultSms = true;
private boolean isMmsEnabled = true;
private boolean isSecurityInitialized = false;
@@ -488,7 +488,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
return;
}
reactWithAnyEmojiStartPage = 0;
reactWithAnyEmojiStartPage = -1;
if (!Util.isEmpty(composeText) || attachmentManager.isAttachmentPresent() || inputPanel.getQuote().isPresent()) {
saveDraft();
attachmentManager.clear(glideRequests, false);
@@ -745,7 +745,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
reactWithAnyEmojiStartPage = savedInstanceState.getInt(STATE_REACT_WITH_ANY_PAGE, 0);
reactWithAnyEmojiStartPage = savedInstanceState.getInt(STATE_REACT_WITH_ANY_PAGE, -1);
}
private void setVisibleThread(long threadId) {
@@ -2262,6 +2262,10 @@ public class ConversationActivity extends PassphraseRequiredActivity
reactWithAnyEmojiStartPage = page;
}
@Override
public void onReactWithAnyEmojiSelected(@NonNull String emoji) {
}
@Override
public void onSearchMoveUpPressed() {
searchViewModel.onMoveUp();

View File

@@ -21,6 +21,7 @@ public class ConversationBannerView extends ConstraintLayout {
private AvatarImageView contactAvatar;
private TextView contactTitle;
private TextView contactAbout;
private TextView contactSubtitle;
private TextView contactDescription;
@@ -39,6 +40,7 @@ public class ConversationBannerView extends ConstraintLayout {
contactAvatar = findViewById(R.id.message_request_avatar);
contactTitle = findViewById(R.id.message_request_title);
contactAbout = findViewById(R.id.message_request_about);
contactSubtitle = findViewById(R.id.message_request_subtitle);
contactDescription = findViewById(R.id.message_request_description);
@@ -53,6 +55,11 @@ public class ConversationBannerView extends ConstraintLayout {
contactTitle.setText(title);
}
public void setAbout(@Nullable String about) {
contactAbout.setText(about);
contactAbout.setVisibility(TextUtils.isEmpty(about) ? GONE : VISIBLE);
}
public void setSubtitle(@Nullable CharSequence subtitle) {
contactSubtitle.setText(subtitle);
contactSubtitle.setVisibility(TextUtils.isEmpty(subtitle) ? GONE : VISIBLE);

View File

@@ -126,6 +126,7 @@ import org.thoughtcrime.securesms.stickers.StickerLocator;
import org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity;
import org.thoughtcrime.securesms.util.CachedInflater;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.HtmlUtil;
import org.thoughtcrime.securesms.util.RemoteDeleteUtil;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
@@ -417,7 +418,6 @@ public class ConversationFragment extends LoggingFragment {
}
private static void presentMessageRequestProfileView(@NonNull Context context, @NonNull MessageRequestViewModel.RecipientInfo recipientInfo, @Nullable ConversationBannerView conversationBanner) {
if (conversationBanner == null) {
return;
}
@@ -434,6 +434,7 @@ public class ConversationFragment extends LoggingFragment {
String title = isSelf ? context.getString(R.string.note_to_self) : recipient.getDisplayNameOrUsername(context);
conversationBanner.setTitle(title);
conversationBanner.setAbout(recipient.getCombinedAboutAndEmoji());
if (recipient.isGroup()) {
if (pendingMemberCount > 0) {

View File

@@ -138,6 +138,8 @@ public class RecipientDatabase extends Database {
private static final String LAST_SESSION_RESET = "last_session_reset";
private static final String WALLPAPER = "wallpaper";
private static final String WALLPAPER_URI = "wallpaper_file";
public static final String ABOUT = "about";
public static final String ABOUT_EMOJI = "about_emoji";
public static final String SEARCH_PROFILE_NAME = "search_signal_profile";
private static final String SORT_NAME = "sort_name";
@@ -162,12 +164,14 @@ public class RecipientDatabase extends Database {
FORCE_SMS_SELECTION,
CAPABILITIES,
STORAGE_SERVICE_ID, DIRTY,
MENTION_SETTING, WALLPAPER, WALLPAPER_URI
MENTION_SETTING, WALLPAPER, WALLPAPER_URI,
MENTION_SETTING,
ABOUT, ABOUT_EMOJI
};
private static final String[] ID_PROJECTION = new String[]{ID};
private static final String[] SEARCH_PROJECTION = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, "COALESCE(" + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ") AS " + SEARCH_PROFILE_NAME, "COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ") AS " + SORT_NAME};
public static final String[] SEARCH_PROJECTION_NAMES = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, SEARCH_PROFILE_NAME, SORT_NAME};
private static final String[] SEARCH_PROJECTION = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, ABOUT, ABOUT_EMOJI, "COALESCE(" + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ") AS " + SEARCH_PROFILE_NAME, "COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ") AS " + SORT_NAME};
public static final String[] SEARCH_PROJECTION_NAMES = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, ABOUT, ABOUT_EMOJI, SEARCH_PROFILE_NAME, SORT_NAME};
private static final String[] TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION)
.map(columnName -> TABLE_NAME + "." + columnName)
.toList().toArray(new String[0]);
@@ -359,7 +363,9 @@ public class RecipientDatabase extends Database {
LAST_GV1_MIGRATE_REMINDER + " INTEGER DEFAULT 0, " +
LAST_SESSION_RESET + " BLOB DEFAULT NULL, " +
WALLPAPER + " BLOB DEFAULT NULL, " +
WALLPAPER_URI + " TEXT DEFAULT NULL);";
WALLPAPER_URI + " TEXT DEFAULT NULL, " +
ABOUT + " TEXT DEFAULT NULL, " +
ABOUT_EMOJI + " TEXT DEFAULT NULL);";
private static final String INSIGHTS_INVITEE_LIST = "SELECT " + TABLE_NAME + "." + ID +
" FROM " + TABLE_NAME +
@@ -1274,6 +1280,8 @@ public class RecipientDatabase extends Database {
String storageKeyRaw = CursorUtil.requireString(cursor, STORAGE_SERVICE_ID);
int mentionSettingId = CursorUtil.requireInt(cursor, MENTION_SETTING);
byte[] wallpaper = CursorUtil.requireBlob(cursor, WALLPAPER);
String about = CursorUtil.requireString(cursor, ABOUT);
String aboutEmoji = CursorUtil.requireString(cursor, ABOUT_EMOJI);
MaterialColor color;
byte[] profileKey = null;
@@ -1359,6 +1367,8 @@ public class RecipientDatabase extends Database {
storageKey,
MentionSetting.fromId(mentionSettingId),
chatWallpaper,
about,
aboutEmoji,
getSyncExtras(cursor));
}
@@ -1777,6 +1787,16 @@ public class RecipientDatabase extends Database {
}
}
public void setAbout(@NonNull RecipientId id, @Nullable String about, @Nullable String emoji) {
ContentValues contentValues = new ContentValues();
contentValues.put(ABOUT, about);
contentValues.put(ABOUT_EMOJI, emoji);
if (update(id, contentValues)) {
Recipient.live(id).refresh();
}
}
public void setProfileSharing(@NonNull RecipientId id, @SuppressWarnings("SameParameterValue") boolean enabled) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(PROFILE_SHARING, enabled ? 1 : 0);
@@ -2938,6 +2958,8 @@ public class RecipientDatabase extends Database {
private final byte[] storageId;
private final MentionSetting mentionSetting;
private final ChatWallpaper wallpaper;
private final String about;
private final String aboutEmoji;
private final SyncExtras syncExtras;
RecipientSettings(@NonNull RecipientId id,
@@ -2976,6 +2998,8 @@ public class RecipientDatabase extends Database {
@Nullable byte[] storageId,
@NonNull MentionSetting mentionSetting,
@Nullable ChatWallpaper wallpaper,
@Nullable String about,
@Nullable String aboutEmoji,
@NonNull SyncExtras syncExtras)
{
this.id = id;
@@ -3016,6 +3040,8 @@ public class RecipientDatabase extends Database {
this.storageId = storageId;
this.mentionSetting = mentionSetting;
this.wallpaper = wallpaper;
this.about = about;
this.aboutEmoji = aboutEmoji;
this.syncExtras = syncExtras;
}
@@ -3167,6 +3193,14 @@ public class RecipientDatabase extends Database {
return wallpaper;
}
public @Nullable String getAbout() {
return about;
}
public @Nullable String getAboutEmoji() {
return aboutEmoji;
}
public @NonNull SyncExtras getSyncExtras() {
return syncExtras;
}

View File

@@ -170,8 +170,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
private static final int CLEAR_PROFILE_KEY_CREDENTIALS = 86;
private static final int LAST_RESET_SESSION_TIME = 87;
private static final int WALLPAPER = 88;
private static final int ABOUT = 89;
private static final int DATABASE_VERSION = 88;
private static final int DATABASE_VERSION = 89;
private static final String DATABASE_NAME = "signal.db";
private final Context context;
@@ -1252,6 +1253,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
db.execSQL("ALTER TABLE recipient ADD COLUMN wallpaper_file TEXT DEFAULT NULL");
}
if (oldVersion < ABOUT) {
db.execSQL("ALTER TABLE recipient ADD COLUMN about TEXT DEFAULT NULL");
db.execSQL("ALTER TABLE recipient ADD COLUMN about_emoji TEXT DEFAULT NULL");
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();

View File

@@ -15,9 +15,12 @@ import androidx.recyclerview.widget.RecyclerView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.LifecycleRecyclerAdapter;
import org.thoughtcrime.securesms.util.LifecycleViewHolder;
import org.thoughtcrime.securesms.util.Util;
import java.util.ArrayList;
import java.util.HashSet;
@@ -166,6 +169,7 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
final Context context;
final AvatarImageView avatar;
final TextView recipient;
final EmojiTextView about;
final CheckBox selected;
final PopupMenuView popupMenu;
final View popupMenuContainer;
@@ -187,6 +191,7 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
this.context = itemView.getContext();
this.avatar = itemView.findViewById(R.id.recipient_avatar);
this.recipient = itemView.findViewById(R.id.recipient_name);
this.about = itemView.findViewById(R.id.recipient_about);
this.selected = itemView.findViewById(R.id.recipient_selected);
this.popupMenu = itemView.findViewById(R.id.popupMenu);
this.popupMenuContainer = itemView.findViewById(R.id.popupMenuProgressContainer);
@@ -201,12 +206,14 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
void bindRecipient(@NonNull Recipient recipient) {
String displayName = recipient.isSelf() ? context.getString(R.string.GroupMembersDialog_you)
: recipient.getDisplayName(itemView.getContext());
bindImageAndText(recipient, displayName);
bindImageAndText(recipient, displayName, recipient.getCombinedAboutAndEmoji());
}
void bindImageAndText(@NonNull Recipient recipient, @NonNull String displayText) {
void bindImageAndText(@NonNull Recipient recipient, @NonNull String displayText, @Nullable String about) {
this.recipient.setText(displayText);
this.avatar.setRecipient(recipient);
this.about.setText(about);
this.about.setVisibility(Util.isEmpty(about) ? View.GONE : View.VISIBLE);
}
void bindRecipientClick(@NonNull Recipient recipient) {
@@ -333,7 +340,7 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
GroupMemberEntry.PendingMember pendingMember = (GroupMemberEntry.PendingMember) memberEntry;
bindImageAndText(pendingMember.getInvitee(), pendingMember.getInvitee().getDisplayNameOrUsername(context));
bindImageAndText(pendingMember.getInvitee(), pendingMember.getInvitee().getDisplayNameOrUsername(context), pendingMember.getInvitee().getCombinedAboutAndEmoji());
bindRecipientClick(pendingMember.getInvitee());
if (pendingMember.isCancellable() && adminActionsListener != null) {
@@ -370,7 +377,7 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
pendingMembers.getInviteCount(),
displayName, pendingMembers.getInviteCount());
bindImageAndText(inviter, displayText);
bindImageAndText(inviter, displayText, inviter.getAbout());
if (pendingMembers.isCancellable() && adminActionsListener != null) {
popupMenu.setMenu(R.menu.others_invite_pending_menu,

View File

@@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.util.StreamDetails;
@@ -58,10 +59,12 @@ public final class ProfileUploadJob extends BaseJob {
ProfileKey profileKey = ProfileKeyUtil.getSelfProfileKey();
ProfileName profileName = Recipient.self().getProfileName();
String about = Optional.fromNullable(Recipient.self().getAbout()).or("");
String aboutEmoji = Optional.fromNullable(Recipient.self().getAboutEmoji()).or("");
String avatarPath;
try (StreamDetails avatar = AvatarHelper.getSelfProfileAvatarStream(context)) {
avatarPath = accountManager.setVersionedProfile(Recipient.self().getUuid().get(), profileKey, profileName.serialize(), avatar).orNull();
avatarPath = accountManager.setVersionedProfile(Recipient.self().getUuid().get(), profileKey, profileName.serialize(), about, aboutEmoji, avatar).orNull();
}
DatabaseFactory.getRecipientDatabase(context).setProfileAvatar(Recipient.self().getId(), avatarPath);

View File

@@ -75,6 +75,7 @@ public class RefreshOwnProfileJob extends BaseJob {
SignalServiceProfile profile = profileAndCredential.getProfile();
setProfileName(profile.getName());
setProfileAbout(profile.getAbout(), profile.getAboutEmoji());
setProfileAvatar(profile.getAvatar());
setProfileCapabilities(profile.getCapabilities());
Optional<ProfileKeyCredential> profileKeyCredential = profileAndCredential.getProfileKeyCredential();
@@ -117,6 +118,18 @@ public class RefreshOwnProfileJob extends BaseJob {
}
}
private void setProfileAbout(@Nullable String encryptedAbout, @Nullable String encryptedEmoji) {
try {
ProfileKey profileKey = ProfileKeyUtil.getSelfProfileKey();
String plaintextAbout = ProfileUtil.decryptName(profileKey, encryptedAbout);
String plaintextEmoji = ProfileUtil.decryptName(profileKey, encryptedEmoji);
DatabaseFactory.getRecipientDatabase(context).setAbout(Recipient.self().getId(), plaintextAbout, plaintextEmoji);
} catch (InvalidCiphertextException | IOException e) {
Log.w(TAG, e);
}
}
private static void setProfileAvatar(@Nullable String avatar) {
ApplicationDependencies.getJobManager().add(new RetrieveProfileAvatarJob(Recipient.self(), avatar));
}

View File

@@ -328,6 +328,7 @@ public class RetrieveProfileJob extends BaseJob {
ProfileKey recipientProfileKey = ProfileKeyUtil.profileKeyOrNull(recipient.getProfileKey());
setProfileName(recipient, profile.getName());
setProfileAbout(recipient, profile.getAbout(), profile.getAboutEmoji());
setProfileAvatar(recipient, profile.getAvatar());
clearUsername(recipient);
setProfileCapabilities(recipient, profile.getCapabilities());
@@ -454,6 +455,20 @@ public class RetrieveProfileJob extends BaseJob {
}
}
private void setProfileAbout(@NonNull Recipient recipient, @Nullable String encryptedAbout, @Nullable String encryptedEmoji) {
try {
ProfileKey profileKey = ProfileKeyUtil.profileKeyOrNull(recipient.getProfileKey());
if (profileKey == null) return;
String plaintextAbout = ProfileUtil.decryptName(profileKey, encryptedAbout);
String plaintextEmoji = ProfileUtil.decryptName(profileKey, encryptedEmoji);
DatabaseFactory.getRecipientDatabase(context).setAbout(recipient.getId(), plaintextAbout, plaintextEmoji);
} catch (InvalidCiphertextException | IOException e) {
Log.w(TAG, e);
}
}
private static void setProfileAvatar(Recipient recipient, String profileAvatar) {
if (recipient.getProfileKey() == null) return;

View File

@@ -0,0 +1,132 @@
package org.thoughtcrime.securesms.profiles.manage;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.Editable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.navigation.Navigation;
import org.signal.core.util.BreakIteratorCompat;
import org.signal.core.util.EditTextUtil;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.StringUtil;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
/**
* Let's you edit the 'About' section of your profile.
*/
public class EditAboutFragment extends Fragment implements ManageProfileActivity.EmojiController {
public static final int ABOUT_MAX_GLYPHS = 100;
public static final int ABOUT_LIMIT_DISPLAY_THRESHOLD = 75;
private static final String KEY_SELECTED_EMOJI = "selected_emoji";
private ImageView emojiView;
private EditText bodyView;
private TextView countView;
private String selectedEmoji;
@Override
public @NonNull View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.edit_about_fragment, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
this.emojiView = view.findViewById(R.id.edit_about_emoji);
this.bodyView = view.findViewById(R.id.edit_about_body);
this.countView = view.findViewById(R.id.edit_about_count);
view.<Toolbar>findViewById(R.id.toolbar)
.setNavigationOnClickListener(v -> Navigation.findNavController(view)
.popBackStack());
EditTextUtil.addGraphemeClusterLimitFilter(bodyView, ABOUT_MAX_GLYPHS);
this.bodyView.addTextChangedListener(new AfterTextChanged(editable -> {
trimFieldToMaxByteLength(editable);
presentCount(editable.toString());
}));
this.emojiView.setOnClickListener(v -> {
ReactWithAnyEmojiBottomSheetDialogFragment.createForAboutSelection()
.show(requireFragmentManager(), "BOTTOM");
});
view.findViewById(R.id.edit_about_save).setOnClickListener(this::onSaveClicked);
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_SELECTED_EMOJI)) {
onEmojiSelected(savedInstanceState.getString(KEY_SELECTED_EMOJI, ""));
} else {
this.bodyView.setText(Recipient.self().getAbout());
onEmojiSelected(Optional.fromNullable(Recipient.self().getAboutEmoji()).or(""));
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
outState.putString(KEY_SELECTED_EMOJI, selectedEmoji);
}
@Override
public void onEmojiSelected(@NonNull String emoji) {
Drawable drawable = EmojiUtil.convertToDrawable(requireContext(), emoji);
if (drawable != null) {
this.emojiView.setImageDrawable(drawable);
this.selectedEmoji = emoji;
}
}
private void presentCount(@NonNull String aboutBody) {
BreakIteratorCompat breakIterator = BreakIteratorCompat.getInstance();
breakIterator.setText(aboutBody);
int glyphCount = breakIterator.countBreaks();
if (glyphCount >= ABOUT_LIMIT_DISPLAY_THRESHOLD) {
this.countView.setVisibility(View.VISIBLE);
this.countView.setText(getResources().getString(R.string.EditAboutFragment_count, glyphCount, ABOUT_MAX_GLYPHS));
} else {
this.countView.setVisibility(View.GONE);
}
}
private void onSaveClicked(View view) {
SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> {
DatabaseFactory.getRecipientDatabase(requireContext()).setAbout(Recipient.self().getId(), bodyView.getText().toString(), selectedEmoji);
ApplicationDependencies.getJobManager().add(new ProfileUploadJob());
return null;
}, (nothing) -> {
Navigation.findNavController(view).popBackStack();
});
}
public static void trimFieldToMaxByteLength(Editable s) {
int trimmedLength = StringUtil.trimToFit(s.toString(), ProfileCipher.MAX_POSSIBLE_ABOUT_LENGTH).length();
if (s.length() > trimmedLength) {
s.delete(trimmedLength, s.length());
}
}
}

View File

@@ -5,21 +5,24 @@ import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavDirections;
import androidx.navigation.NavGraph;
import androidx.navigation.Navigation;
import androidx.navigation.fragment.NavHostFragment;
import org.thoughtcrime.securesms.BaseActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
import org.thoughtcrime.securesms.profiles.edit.EditProfileFragmentDirections;
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
/**
* Activity that manages the local user's profile, as accessed via the settings.
*/
public class ManageProfileActivity extends BaseActivity {
public class ManageProfileActivity extends BaseActivity implements ReactWithAnyEmojiBottomSheetDialogFragment.Callback {
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
@@ -61,4 +64,26 @@ public class ManageProfileActivity extends BaseActivity {
super.onResume();
dynamicTheme.onResume(this);
}
@Override
public void onReactWithAnyEmojiDialogDismissed() {
}
@Override
public void onReactWithAnyEmojiPageChanged(int page) {
}
@Override
public void onReactWithAnyEmojiSelected(@NonNull String emoji) {
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().getPrimaryNavigationFragment();
Fragment activeFragment = navHostFragment.getChildFragmentManager().getPrimaryNavigationFragment();
if (activeFragment instanceof EmojiController) {
((EmojiController) activeFragment).onEmojiSelected(emoji);
}
}
interface EmojiController {
void onEmojiSelected(@NonNull String emoji);
}
}

View File

@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.profiles.manage;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -13,6 +14,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.res.ResourcesCompat;
import androidx.lifecycle.ViewModelProviders;
import androidx.navigation.Navigation;
@@ -21,6 +23,7 @@ import com.bumptech.glide.Glide;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity;
import org.thoughtcrime.securesms.mediasend.AvatarSelectionBottomSheetDialogFragment;
import org.thoughtcrime.securesms.mediasend.Media;
@@ -44,7 +47,7 @@ public class ManageProfileFragment extends LoggingFragment {
private View usernameContainer;
private TextView aboutView;
private View aboutContainer;
private TextView aboutEmojiView;
private ImageView aboutEmojiView;
private AlertDialog avatarProgress;
private ManageProfileViewModel viewModel;
@@ -65,6 +68,7 @@ public class ManageProfileFragment extends LoggingFragment {
this.usernameContainer = view.findViewById(R.id.manage_profile_username_container);
this.aboutView = view.findViewById(R.id.manage_profile_about);
this.aboutContainer = view.findViewById(R.id.manage_profile_about_container);
this.aboutEmojiView = view.findViewById(R.id.manage_profile_about_icon);
initializeViewModel();
@@ -78,6 +82,10 @@ public class ManageProfileFragment extends LoggingFragment {
this.usernameContainer.setOnClickListener(v -> {
Navigation.findNavController(v).navigate(ManageProfileFragmentDirections.actionManageUsername());
});
this.aboutContainer.setOnClickListener(v -> {
Navigation.findNavController(v).navigate(ManageProfileFragmentDirections.actionManageAbout());
});
}
@Override
@@ -102,13 +110,8 @@ public class ManageProfileFragment extends LoggingFragment {
viewModel.getAvatar().observe(getViewLifecycleOwner(), this::presentAvatar);
viewModel.getProfileName().observe(getViewLifecycleOwner(), this::presentProfileName);
viewModel.getEvents().observe(getViewLifecycleOwner(), this::presentEvent);
if (viewModel.shouldShowAbout()) {
viewModel.getAbout().observe(getViewLifecycleOwner(), this::presentAbout);
viewModel.getAboutEmoji().observe(getViewLifecycleOwner(), this::presentAboutEmoji);
} else {
aboutContainer.setVisibility(View.GONE);
}
viewModel.getAbout().observe(getViewLifecycleOwner(), this::presentAbout);
viewModel.getAboutEmoji().observe(getViewLifecycleOwner(), this::presentAboutEmoji);
if (viewModel.shouldShowUsername()) {
viewModel.getUsername().observe(getViewLifecycleOwner(), this::presentUsername);
@@ -156,14 +159,26 @@ public class ManageProfileFragment extends LoggingFragment {
private void presentAbout(@Nullable String about) {
if (about == null || about.isEmpty()) {
aboutView.setHint(R.string.ManageProfileFragment_about);
aboutView.setText(R.string.ManageProfileFragment_about);
aboutView.setTextColor(requireContext().getResources().getColor(R.color.signal_text_secondary));
} else {
aboutView.setText(about);
aboutView.setTextColor(requireContext().getResources().getColor(R.color.signal_text_primary));
}
}
private void presentAboutEmoji(@NonNull String aboutEmoji) {
if (aboutEmoji == null || aboutEmoji.isEmpty()) {
aboutEmojiView.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_compose_24, null));
} else {
Drawable emoji = EmojiUtil.convertToDrawable(requireContext(), aboutEmoji);
if (emoji != null) {
aboutEmojiView.setImageDrawable(emoji);
} else {
aboutEmojiView.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_compose_24, null));
}
}
}
private void presentEvent(@NonNull ManageProfileViewModel.Event event) {

View File

@@ -100,10 +100,6 @@ class ManageProfileViewModel extends ViewModel {
return FeatureFlags.usernames();
}
public boolean shouldShowAbout() {
return FeatureFlags.about();
}
public void onAvatarSelected(@NonNull Context context, @Nullable Media media) {
if (media == null) {
SignalExecutors.BOUNDED.execute(() -> {
@@ -136,6 +132,8 @@ class ManageProfileViewModel extends ViewModel {
private void onRecipientChanged(@NonNull Recipient recipient) {
profileName.postValue(recipient.getProfileName());
username.postValue(recipient.getUsername().orNull());
about.postValue(recipient.getAbout());
aboutEmoji.postValue(recipient.getAboutEmoji());
}
@Override

View File

@@ -45,9 +45,14 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
EmojiPageViewGridAdapter.VariationSelectorListener
{
private static final String REACTION_STORAGE_KEY = "reactions_recent_emoji";
private static final String ABOUT_STORAGE_KEY = EmojiKeyboardProvider.RECENT_STORAGE_KEY;
private static final String ARG_MESSAGE_ID = "arg_message_id";
private static final String ARG_IS_MMS = "arg_is_mms";
private static final String ARG_START_PAGE = "arg_start_page";
private static final String ARG_SHADOWS = "arg_shadows";
private static final String ARG_RECENT_KEY = "arg_recent_key";
private ReactWithAnyEmojiViewModel viewModel;
private TextSwitcher categoryLabel;
@@ -65,6 +70,22 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
args.putLong(ARG_MESSAGE_ID, messageRecord.getId());
args.putBoolean(ARG_IS_MMS, messageRecord.isMms());
args.putInt(ARG_START_PAGE, startingPage);
args.putBoolean(ARG_SHADOWS, false);
args.putString(ARG_RECENT_KEY, REACTION_STORAGE_KEY);
fragment.setArguments(args);
return fragment;
}
public static DialogFragment createForAboutSelection() {
DialogFragment fragment = new ReactWithAnyEmojiBottomSheetDialogFragment();
Bundle args = new Bundle();
args.putLong(ARG_MESSAGE_ID, -1);
args.putBoolean(ARG_IS_MMS, false);
args.putInt(ARG_START_PAGE, -1);
args.putBoolean(ARG_SHADOWS, true);
args.putString(ARG_RECENT_KEY, ABOUT_STORAGE_KEY);
fragment.setArguments(args);
return fragment;
@@ -79,10 +100,13 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
boolean shadows = requireArguments().getBoolean(ARG_SHADOWS);
if (ThemeUtil.isDarkTheme(requireContext())) {
setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Signal_BottomSheetDialog_Fixed_ReactWithAny);
setStyle(DialogFragment.STYLE_NORMAL, shadows ? R.style.Theme_Signal_BottomSheetDialog_Fixed_ReactWithAny
: R.style.Theme_Signal_BottomSheetDialog_Fixed_ReactWithAny_Shadowless);
} else {
setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Signal_Light_BottomSheetDialog_Fixed_ReactWithAny);
setStyle(DialogFragment.STYLE_NORMAL, shadows ? R.style.Theme_Signal_Light_BottomSheetDialog_Fixed_ReactWithAny
: R.style.Theme_Signal_Light_BottomSheetDialog_Fixed_ReactWithAny_Shadowless);
}
super.onCreate(savedInstanceState);
@@ -168,15 +192,18 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
if (savedInstanceState == null) {
FrameLayout container = requireDialog().findViewById(R.id.container);
LayoutInflater layoutInflater = LayoutInflater.from(requireContext());
View statusBarShader = layoutInflater.inflate(R.layout.react_with_any_emoji_status_fade, container, false);
TabLayout categoryTabs = (TabLayout) layoutInflater.inflate(R.layout.react_with_any_emoji_tabs, container, false);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtil.getStatusBarHeight(container));
statusBarShader.setLayoutParams(params);
container.addView(statusBarShader, 0);
if (!requireArguments().getBoolean(ARG_SHADOWS)) {
View statusBarShader = layoutInflater.inflate(R.layout.react_with_any_emoji_status_fade, container, false);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtil.getStatusBarHeight(container));
statusBarShader.setLayoutParams(params);
container.addView(statusBarShader, 0);
}
container.addView(categoryTabs);
ViewCompat.setOnApplyWindowInsetsListener(container, (v, insets) -> insets.consumeSystemWindowInsets());
new TabLayoutMediator(categoryTabs, categoryPager, (tab, position) -> {
@@ -203,7 +230,7 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
private void initializeViewModel() {
Bundle args = requireArguments();
ReactWithAnyEmojiRepository repository = new ReactWithAnyEmojiRepository(requireContext());
ReactWithAnyEmojiRepository repository = new ReactWithAnyEmojiRepository(requireContext(), args.getString(ARG_RECENT_KEY));
ReactWithAnyEmojiViewModel.Factory factory = new ReactWithAnyEmojiViewModel.Factory(reactionsLoader, repository, args.getLong(ARG_MESSAGE_ID), args.getBoolean(ARG_IS_MMS));
viewModel = ViewModelProviders.of(this, factory).get(ReactWithAnyEmojiViewModel.class);
@@ -212,6 +239,7 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
@Override
public void onEmojiSelected(String emoji) {
viewModel.onEmojiSelected(emoji);
callback.onReactWithAnyEmojiSelected(emoji);
dismiss();
}
@@ -239,7 +267,8 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
}
private int getStartingPage(boolean firstPageHasContent) {
return requireArguments().getInt(ARG_START_PAGE, firstPageHasContent ? 0 : 1);
int startPage = requireArguments().getInt(ARG_START_PAGE);
return startPage >= 0 ? startPage : (firstPageHasContent ? 0 : 1);
}
private class OnPageChanged extends ViewPager2.OnPageChangeCallback {
@@ -253,5 +282,6 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
public interface Callback {
void onReactWithAnyEmojiDialogDismissed();
void onReactWithAnyEmojiPageChanged(int page);
void onReactWithAnyEmojiSelected(@NonNull String emoji);
}
}

View File

@@ -32,15 +32,13 @@ final class ReactWithAnyEmojiRepository {
private static final String TAG = Log.tag(ReactWithAnyEmojiRepository.class);
private static final String RECENT_STORAGE_KEY = "reactions_recent_emoji";
private final Context context;
private final RecentEmojiPageModel recentEmojiPageModel;
private final Context context;
private final RecentEmojiPageModel recentEmojiPageModel;
private final List<ReactWithAnyEmojiPage> emojiPages;
ReactWithAnyEmojiRepository(@NonNull Context context) {
ReactWithAnyEmojiRepository(@NonNull Context context, @NonNull String storageKey) {
this.context = context;
this.recentEmojiPageModel = new RecentEmojiPageModel(context, RECENT_STORAGE_KEY);
this.recentEmojiPageModel = new RecentEmojiPageModel(context, storageKey);
this.emojiPages = new LinkedList<>();
emojiPages.addAll(Stream.of(EmojiUtil.getDisplayPages())

View File

@@ -36,8 +36,10 @@ public final class ReactWithAnyEmojiViewModel extends ViewModel {
}
void onEmojiSelected(@NonNull String emoji) {
SignalStore.emojiValues().setPreferredVariation(emoji);
repository.addEmojiToMessage(emoji, messageId, isMms);
if (messageId > 0) {
SignalStore.emojiValues().setPreferredVariation(emoji);
repository.addEmojiToMessage(emoji, messageId, isMms);
}
}
static class Factory implements ViewModelProvider.Factory {

View File

@@ -108,6 +108,8 @@ public class Recipient {
private final byte[] storageId;
private final MentionSetting mentionSetting;
private final ChatWallpaper wallpaper;
private final String about;
private final String aboutEmoji;
/**
@@ -342,6 +344,8 @@ public class Recipient {
this.storageId = null;
this.mentionSetting = MentionSetting.ALWAYS_NOTIFY;
this.wallpaper = null;
this.about = null;
this.aboutEmoji = null;
}
public Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details, boolean resolved) {
@@ -385,6 +389,8 @@ public class Recipient {
this.storageId = details.storageId;
this.mentionSetting = details.mentionSetting;
this.wallpaper = details.wallpaper;
this.about = details.about;
this.aboutEmoji = details.aboutEmoji;
}
public @NonNull RecipientId getId() {
@@ -870,6 +876,28 @@ public class Recipient {
return contactUri != null;
}
public @Nullable String getAbout() {
return about;
}
public @Nullable String getAboutEmoji() {
return aboutEmoji;
}
public @Nullable String getCombinedAboutAndEmoji() {
if (aboutEmoji != null) {
if (about != null) {
return aboutEmoji + " " + about;
} else {
return aboutEmoji;
}
} else if (about != null) {
return about;
} else {
return null;
}
}
/**
* If this recipient is missing crucial data, this will return a populated copy. Otherwise it
* returns itself.
@@ -985,7 +1013,9 @@ public class Recipient {
insightsBannerTier == other.insightsBannerTier &&
Arrays.equals(storageId, other.storageId) &&
mentionSetting == other.mentionSetting &&
Objects.equals(wallpaper, other.wallpaper);
Objects.equals(wallpaper, other.wallpaper) &&
Objects.equals(about, other.about) &&
Objects.equals(aboutEmoji, other.aboutEmoji);
}
private static boolean allContentsAreTheSame(@NonNull List<Recipient> a, @NonNull List<Recipient> b) {

View File

@@ -67,6 +67,8 @@ public class RecipientDetails {
final byte[] storageId;
final MentionSetting mentionSetting;
final ChatWallpaper wallpaper;
final String about;
final String aboutEmoji;
public RecipientDetails(@Nullable String name,
@NonNull Optional<Long> groupAvatarId,
@@ -113,6 +115,8 @@ public class RecipientDetails {
this.storageId = settings.getStorageId();
this.mentionSetting = settings.getMentionSetting();
this.wallpaper = settings.getWallpaper();
this.about = settings.getAbout();
this.aboutEmoji = settings.getAboutEmoji();
if (name == null) this.name = settings.getSystemDisplayName();
else this.name = name;
@@ -161,6 +165,8 @@ public class RecipientDetails {
this.storageId = null;
this.mentionSetting = MentionSetting.ALWAYS_NOTIFY;
this.wallpaper = null;
this.about = null;
this.aboutEmoji = null;
}
public static @NonNull RecipientDetails forIndividual(@NonNull Context context, @NonNull RecipientSettings settings) {

View File

@@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.recipients.RecipientExporter;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.util.BottomSheetUtil;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.Util;
@@ -55,6 +56,7 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
private RecipientDialogViewModel viewModel;
private AvatarImageView avatar;
private TextView fullName;
private TextView about;
private TextView usernameNumber;
private Button messageButton;
private Button secureCallButton;
@@ -102,6 +104,7 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
avatar = view.findViewById(R.id.rbs_recipient_avatar);
fullName = view.findViewById(R.id.rbs_full_name);
about = view.findViewById(R.id.rbs_about);
usernameNumber = view.findViewById(R.id.rbs_username_number);
messageButton = view.findViewById(R.id.rbs_message_button);
secureCallButton = view.findViewById(R.id.rbs_secure_call_button);
@@ -158,6 +161,14 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
TextViewCompat.setCompoundDrawableTintList(fullName, ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.signal_text_primary)));
}
String aboutText = recipient.getCombinedAboutAndEmoji();
if (!Util.isEmpty(aboutText)) {
about.setText(aboutText);
about.setVisibility(View.VISIBLE);
} else {
about.setVisibility(View.GONE);
}
String usernameNumberString = recipient.hasAUserSetDisplayName(requireContext()) && !recipient.isSelf()
? recipient.getSmsAddress().transform(PhoneNumberFormatter::prettyPrint).or("").trim()
: "";

View File

@@ -51,6 +51,7 @@ import org.thoughtcrime.securesms.recipients.RecipientExporter;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.ui.notifications.CustomNotificationsDialogFragment;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.LifecycleCursorWrapper;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.Util;
@@ -71,6 +72,7 @@ public class ManageRecipientFragment extends LoggingFragment {
private GroupMemberListView sharedGroupList;
private Toolbar toolbar;
private TextView title;
private TextView about;
private TextView subtitle;
private ViewGroup internalDetails;
private TextView internalDetailsText;
@@ -132,6 +134,7 @@ public class ManageRecipientFragment extends LoggingFragment {
contactText = view.findViewById(R.id.recipient_contact_text);
contactIcon = view.findViewById(R.id.recipient_contact_icon);
title = view.findViewById(R.id.name);
about = view.findViewById(R.id.about);
subtitle = view.findViewById(R.id.username_number);
internalDetails = view.findViewById(R.id.recipient_internal_details);
internalDetailsText = view.findViewById(R.id.recipient_internal_details_text);
@@ -303,6 +306,10 @@ public class ManageRecipientFragment extends LoggingFragment {
});
}
String aboutText = recipient.getCombinedAboutAndEmoji();
about.setText(aboutText);
about.setVisibility(Util.isEmpty(aboutText) ? View.GONE : View.VISIBLE);
disappearingMessagesCard.setVisibility(recipient.isRegistered() ? View.VISIBLE : View.GONE);
addToAGroup.setVisibility(recipient.isRegistered() ? View.VISIBLE : View.GONE);

View File

@@ -71,7 +71,6 @@ public final class FeatureFlags {
private static final String AUTOMATIC_SESSION_INTERVAL = "android.automaticSessionResetInterval";
private static final String DEFAULT_MAX_BACKOFF = "android.defaultMaxBackoff";
private static final String OKHTTP_AUTOMATIC_RETRY = "android.okhttpAutomaticRetry";
private static final String ABOUT = "android.about";
private static final String SHARE_SELECTION_LIMIT = "android.share.limit";
/**
@@ -101,7 +100,6 @@ public final class FeatureFlags {
AUTOMATIC_SESSION_INTERVAL,
DEFAULT_MAX_BACKOFF,
OKHTTP_AUTOMATIC_RETRY,
ABOUT,
SHARE_SELECTION_LIMIT
);
@@ -141,7 +139,6 @@ public final class FeatureFlags {
AUTOMATIC_SESSION_INTERVAL,
DEFAULT_MAX_BACKOFF,
OKHTTP_AUTOMATIC_RETRY,
ABOUT,
SHARE_SELECTION_LIMIT
);
@@ -327,11 +324,6 @@ public final class FeatureFlags {
return getBoolean(OKHTTP_AUTOMATIC_RETRY, false);
}
/** Whether or not the 'About' section of the profile is enabled. */
public static boolean about() {
return getBoolean(ABOUT, false);
}
/** Only for rendering debug info. */
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
return new TreeMap<>(REMOTE_VALUES);