Skip to content

Commit

Permalink
Improve conversation update message stylings.
Browse files Browse the repository at this point in the history
  • Loading branch information
greyson-signal authored and cody-signal committed Oct 22, 2020
1 parent 90f20c3 commit 9c97cd8
Show file tree
Hide file tree
Showing 53 changed files with 757 additions and 369 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
package org.thoughtcrime.securesms.conversation;

import android.content.Context;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.text.Spannable;
import android.text.SpannableString;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.Transformations;

import org.thoughtcrime.securesms.BindableConversationItem;
import org.thoughtcrime.securesms.R;
Expand All @@ -30,10 +25,7 @@
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import org.whispersystems.libsignal.util.guava.Optional;
Expand All @@ -50,15 +42,12 @@ public final class ConversationUpdateItem extends LinearLayout

private Set<ConversationMessage> batchSelected;

private ImageView icon;
private TextView title;
private TextView body;
private TextView date;
private LiveRecipient sender;
private ConversationMessage conversationMessage;
private MessageRecord messageRecord;
private Locale locale;
private LiveData<SpannableString> displayBody;
private LiveData<Spannable> displayBody;

private final UpdateObserver updateObserver = new UpdateObserver();
private final SenderObserver senderObserver = new SenderObserver();
Expand All @@ -74,11 +63,7 @@ public ConversationUpdateItem(Context context, AttributeSet attrs) {
@Override
public void onFinishInflate() {
super.onFinishInflate();

this.icon = findViewById(R.id.conversation_update_icon);
this.title = findViewById(R.id.conversation_update_title);
this.body = findViewById(R.id.conversation_update_body);
this.date = findViewById(R.id.conversation_update_date);
this.body = findViewById(R.id.conversation_update_body);

this.setOnClickListener(new InternalClickListener(null));
}
Expand Down Expand Up @@ -117,22 +102,18 @@ private void bind(@NonNull LifecycleOwner lifecycleOwner, @NonNull ConversationM

observeSender(lifecycleOwner, messageRecord.getIndividualRecipient());

UpdateDescription updateDescription = Objects.requireNonNull(messageRecord.getUpdateDisplayBody(getContext()));
LiveData<String> liveUpdateMessage = LiveUpdateMessage.fromMessageDescription(updateDescription);
LiveData<SpannableString> spannableStringMessage = toSpannable(loading(liveUpdateMessage));
UpdateDescription updateDescription = Objects.requireNonNull(messageRecord.getUpdateDisplayBody(getContext()));
LiveData<Spannable> liveUpdateMessage = LiveUpdateMessage.fromMessageDescription(getContext(), updateDescription);
LiveData<Spannable> spannableMessage = loading(liveUpdateMessage);

present(conversationMessage);

observeDisplayBody(lifecycleOwner, spannableStringMessage);
observeDisplayBody(lifecycleOwner, spannableMessage);
}

/** After a short delay, if the main data hasn't shown yet, then a loading message is displayed. */
private @NonNull LiveData<String> loading(@NonNull LiveData<String> string) {
return LiveDataUtil.until(string, LiveDataUtil.delay(250, getContext().getString(R.string.ConversationUpdateItem_loading)));
}

private static LiveData<SpannableString> toSpannable(LiveData<String> loading) {
return Transformations.map(loading, source -> source == null ? null : new SpannableString(source));
private @NonNull LiveData<Spannable> loading(@NonNull LiveData<Spannable> string) {
return LiveDataUtil.until(string, LiveDataUtil.delay(250, new SpannableString(getContext().getString(R.string.ConversationUpdateItem_loading))));
}

@Override
Expand All @@ -152,7 +133,7 @@ private void observeSender(@NonNull LifecycleOwner lifecycleOwner, @Nullable Rec
}
}

private void observeDisplayBody(@NonNull LifecycleOwner lifecycleOwner, @Nullable LiveData<SpannableString> displayBody) {
private void observeDisplayBody(@NonNull LifecycleOwner lifecycleOwner, @Nullable LiveData<Spannable> displayBody) {
if (this.displayBody != displayBody) {
if (this.displayBody != null) {
this.displayBody.removeObserver(updateObserver);
Expand All @@ -176,98 +157,10 @@ private void setBodyText(@Nullable CharSequence text) {
}

private void present(ConversationMessage conversationMessage) {
MessageRecord messageRecord = conversationMessage.getMessageRecord();
if (messageRecord.isGroupAction()) setGroupRecord();
else if (messageRecord.isCallLog()) setCallRecord(messageRecord);
else if (messageRecord.isJoined()) setJoinedRecord();
else if (messageRecord.isExpirationTimerUpdate()) setTimerRecord(messageRecord);
else if (messageRecord.isEndSession()) setEndSessionRecord();
else if (messageRecord.isIdentityUpdate()) setIdentityRecord();
else if (messageRecord.isIdentityVerified() ||
messageRecord.isIdentityDefault()) setIdentityVerifyUpdate(messageRecord);
else if (messageRecord.isProfileChange()) setProfileNameChangeRecord();
else throw new AssertionError("Neither group nor log nor joined.");

if (batchSelected.contains(conversationMessage)) setSelected(true);
else setSelected(false);
}

private void setCallRecord(MessageRecord messageRecord) {
if (messageRecord.isIncomingCall()) icon.setImageResource(R.drawable.ic_call_received_grey600_24dp);
else if (messageRecord.isOutgoingCall()) icon.setImageResource(R.drawable.ic_call_made_grey600_24dp);
else icon.setImageResource(R.drawable.ic_call_missed_grey600_24dp);

date.setText(DateUtils.getExtendedRelativeTimeSpanString(getContext(), locale, messageRecord.getDateSent()));

title.setVisibility(GONE);
date.setVisibility(View.VISIBLE);
}

private void setTimerRecord(final MessageRecord messageRecord) {
if (messageRecord.getExpiresIn() > 0) {
icon.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.ic_timer_24));
} else {
icon.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.ic_timer_disabled_24));
}

icon.setColorFilter(getIconTintFilter());
title.setText(ExpirationUtil.getExpirationDisplayValue(getContext(), (int)(messageRecord.getExpiresIn() / 1000)));

title.setVisibility(VISIBLE);
date.setVisibility(GONE);
}

private ColorFilter getIconTintFilter() {
return new PorterDuffColorFilter(ThemeUtil.getThemedColor(getContext(), R.attr.icon_tint), PorterDuff.Mode.SRC_IN);
}

private void setIdentityRecord() {
icon.setImageDrawable(ThemeUtil.getThemedDrawable(getContext(), R.attr.safety_number_icon));
icon.setColorFilter(getIconTintFilter());

title.setVisibility(GONE);
date.setVisibility(GONE);
}

private void setIdentityVerifyUpdate(final MessageRecord messageRecord) {
if (messageRecord.isIdentityVerified()) icon.setImageResource(R.drawable.ic_check_white_24dp);
else icon.setImageResource(R.drawable.ic_info_outline_white_24);

icon.setColorFilter(getIconTintFilter());

title.setVisibility(GONE);
date.setVisibility(GONE);
}

private void setProfileNameChangeRecord() {
icon.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.ic_profile_outline_20));
icon.setColorFilter(getIconTintFilter());

title.setVisibility(GONE);
date.setVisibility(GONE);
}

private void setGroupRecord() {
icon.setImageDrawable(ThemeUtil.getThemedDrawable(getContext(), R.attr.menu_group_icon));
icon.clearColorFilter();

title.setVisibility(GONE);
date.setVisibility(GONE);
}

private void setJoinedRecord() {
icon.setImageResource(R.drawable.ic_favorite_grey600_24dp);
icon.clearColorFilter();

title.setVisibility(GONE);
date.setVisibility(GONE);
}

private void setEndSessionRecord() {
icon.setImageResource(R.drawable.ic_refresh_white_24dp);
icon.setColorFilter(getIconTintFilter());
}

@Override
public void setOnClickListener(View.OnClickListener l) {
super.setOnClickListener(new InternalClickListener(l));
Expand All @@ -281,10 +174,10 @@ public void onChanged(Recipient recipient) {
}
}

private final class UpdateObserver implements Observer<SpannableString> {
private final class UpdateObserver implements Observer<Spannable> {

@Override
public void onChanged(SpannableString update) {
public void onChanged(Spannable update) {
setBodyText(update);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import android.widget.RelativeLayout;
import android.widget.TextView;

import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
Expand Down Expand Up @@ -423,65 +424,65 @@ public void onRecipientChanged(@NonNull Recipient recipient) {

private static @NonNull LiveData<SpannableString> getThreadDisplayBody(@NonNull Context context, @NonNull ThreadRecord thread) {
if (!thread.isMessageRequestAccepted()) {
return emphasisAdded(context.getString(R.string.ThreadRecord_message_request));
return emphasisAdded(context, context.getString(R.string.ThreadRecord_message_request));
} else if (SmsDatabase.Types.isGroupUpdate(thread.getType())) {
if (thread.getRecipient().isPushV2Group()) {
return emphasisAdded(MessageRecord.getGv2ChangeDescription(context, thread.getBody()));
return emphasisAdded(context, MessageRecord.getGv2ChangeDescription(context, thread.getBody()));
} else {
return emphasisAdded(context.getString(R.string.ThreadRecord_group_updated));
return emphasisAdded(context, context.getString(R.string.ThreadRecord_group_updated));
}
} else if (SmsDatabase.Types.isGroupQuit(thread.getType())) {
return emphasisAdded(context.getString(R.string.ThreadRecord_left_the_group));
return emphasisAdded(context, context.getString(R.string.ThreadRecord_left_the_group));
} else if (SmsDatabase.Types.isKeyExchangeType(thread.getType())) {
return emphasisAdded(context.getString(R.string.ConversationListItem_key_exchange_message));
return emphasisAdded(context, context.getString(R.string.ConversationListItem_key_exchange_message));
} else if (SmsDatabase.Types.isFailedDecryptType(thread.getType())) {
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_bad_encrypted_message));
return emphasisAdded(context, context.getString(R.string.MessageDisplayHelper_bad_encrypted_message));
} else if (SmsDatabase.Types.isNoRemoteSessionType(thread.getType())) {
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
return emphasisAdded(context, context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
} else if (SmsDatabase.Types.isEndSessionType(thread.getType())) {
return emphasisAdded(context.getString(R.string.ThreadRecord_secure_session_reset));
return emphasisAdded(context, context.getString(R.string.ThreadRecord_secure_session_reset));
} else if (MmsSmsColumns.Types.isLegacyType(thread.getType())) {
return emphasisAdded(context.getString(R.string.MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported));
return emphasisAdded(context, context.getString(R.string.MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported));
} else if (MmsSmsColumns.Types.isDraftMessageType(thread.getType())) {
String draftText = context.getString(R.string.ThreadRecord_draft);
return emphasisAdded(draftText + " " + thread.getBody());
return emphasisAdded(context, draftText + " " + thread.getBody());
} else if (SmsDatabase.Types.isOutgoingCall(thread.getType())) {
return emphasisAdded(context.getString(R.string.ThreadRecord_called));
return emphasisAdded(context, context.getString(R.string.ThreadRecord_called));
} else if (SmsDatabase.Types.isIncomingCall(thread.getType())) {
return emphasisAdded(context.getString(R.string.ThreadRecord_called_you));
return emphasisAdded(context, context.getString(R.string.ThreadRecord_called_you));
} else if (SmsDatabase.Types.isMissedCall(thread.getType())) {
return emphasisAdded(context.getString(R.string.ThreadRecord_missed_call));
return emphasisAdded(context, context.getString(R.string.ThreadRecord_missed_call));
} else if (SmsDatabase.Types.isJoinedType(thread.getType())) {
return emphasisAdded(recipientToStringAsync(thread.getRecipient().getId(), r -> context.getString(R.string.ThreadRecord_s_is_on_signal, r.getDisplayName(context))));
return emphasisAdded(recipientToStringAsync(thread.getRecipient().getId(), r -> new SpannableString(context.getString(R.string.ThreadRecord_s_is_on_signal, r.getDisplayName(context)))));
} else if (SmsDatabase.Types.isExpirationTimerUpdate(thread.getType())) {
int seconds = (int)(thread.getExpiresIn() / 1000);
if (seconds <= 0) {
return emphasisAdded(context.getString(R.string.ThreadRecord_disappearing_messages_disabled));
return emphasisAdded(context, context.getString(R.string.ThreadRecord_disappearing_messages_disabled));
}
String time = ExpirationUtil.getExpirationDisplayValue(context, seconds);
return emphasisAdded(context.getString(R.string.ThreadRecord_disappearing_message_time_updated_to_s, time));
return emphasisAdded(context, context.getString(R.string.ThreadRecord_disappearing_message_time_updated_to_s, time));
} else if (SmsDatabase.Types.isIdentityUpdate(thread.getType())) {
return emphasisAdded(recipientToStringAsync(thread.getRecipient().getId(), r -> {
if (r.isGroup()) {
return context.getString(R.string.ThreadRecord_safety_number_changed);
return new SpannableString(context.getString(R.string.ThreadRecord_safety_number_changed));
} else {
return context.getString(R.string.ThreadRecord_your_safety_number_with_s_has_changed, r.getDisplayName(context));
return new SpannableString(context.getString(R.string.ThreadRecord_your_safety_number_with_s_has_changed, r.getDisplayName(context)));
}
}));
} else if (SmsDatabase.Types.isIdentityVerified(thread.getType())) {
return emphasisAdded(context.getString(R.string.ThreadRecord_you_marked_verified));
return emphasisAdded(context, context.getString(R.string.ThreadRecord_you_marked_verified));
} else if (SmsDatabase.Types.isIdentityDefault(thread.getType())) {
return emphasisAdded(context.getString(R.string.ThreadRecord_you_marked_unverified));
return emphasisAdded(context, context.getString(R.string.ThreadRecord_you_marked_unverified));
} else if (SmsDatabase.Types.isUnsupportedMessageType(thread.getType())) {
return emphasisAdded(context.getString(R.string.ThreadRecord_message_could_not_be_processed));
return emphasisAdded(context, context.getString(R.string.ThreadRecord_message_could_not_be_processed));
} else if (SmsDatabase.Types.isProfileChange(thread.getType())) {
return emphasisAdded("");
return emphasisAdded(context, "");
} else {
ThreadDatabase.Extra extra = thread.getExtra();
if (extra != null && extra.isViewOnce()) {
return emphasisAdded(getViewOnceDescription(context, thread.getContentType()));
return emphasisAdded(context, getViewOnceDescription(context, thread.getContentType()));
} else if (extra != null && extra.isRemoteDelete()) {
return emphasisAdded(context.getString(thread.isOutgoing() ? R.string.ThreadRecord_you_deleted_this_message : R.string.ThreadRecord_this_message_was_deleted));
return emphasisAdded(context, context.getString(thread.isOutgoing() ? R.string.ThreadRecord_you_deleted_this_message : R.string.ThreadRecord_this_message_was_deleted));
} else {
return LiveDataUtil.just(new SpannableString(removeNewlines(thread.getBody())));
}
Expand All @@ -500,15 +501,15 @@ public void onRecipientChanged(@NonNull Recipient recipient) {
}
}

private static @NonNull LiveData<SpannableString> emphasisAdded(@NonNull String string) {
return emphasisAdded(UpdateDescription.staticDescription(string));
private static @NonNull LiveData<SpannableString> emphasisAdded(@NonNull Context context, @NonNull String string) {
return emphasisAdded(context, UpdateDescription.staticDescription(string, 0, 0));
}

private static @NonNull LiveData<SpannableString> emphasisAdded(@NonNull UpdateDescription description) {
return emphasisAdded(LiveUpdateMessage.fromMessageDescription(description));
private static @NonNull LiveData<SpannableString> emphasisAdded(@NonNull Context context, @NonNull UpdateDescription description) {
return emphasisAdded(LiveUpdateMessage.fromMessageDescription(context, description));
}

private static @NonNull LiveData<SpannableString> emphasisAdded(@NonNull LiveData<String> description) {
private static @NonNull LiveData<SpannableString> emphasisAdded(@NonNull LiveData<Spannable> description) {
return Transformations.map(description, sequence -> {
SpannableString spannable = new SpannableString(sequence);
spannable.setSpan(new StyleSpan(Typeface.ITALIC),
Expand Down

0 comments on commit 9c97cd8

Please sign in to comment.