Update quote UI for story replies in chat.

This commit is contained in:
Rashad Sookram 2022-03-03 14:40:29 -05:00 committed by Alex Hart
parent ad57e62680
commit 0ccaad1462
8 changed files with 107 additions and 62 deletions

View file

@ -5,7 +5,6 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
@ -29,7 +28,6 @@ import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
import org.thoughtcrime.securesms.conversation.colors.ChatColors;
import org.thoughtcrime.securesms.database.model.Mention;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import org.thoughtcrime.securesms.mms.GlideRequest;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
@ -46,10 +44,29 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
private static final String TAG = Log.tag(QuoteView.class);
private static final int MESSAGE_TYPE_PREVIEW = 0;
private static final int MESSAGE_TYPE_OUTGOING = 1;
private static final int MESSAGE_TYPE_INCOMING = 2;
private static final int MESSAGE_TYPE_STORY_REPLY = 3;
public enum MessageType {
// These codes must match the values for the QuoteView_message_type XML attribute.
PREVIEW(0),
OUTGOING(1),
INCOMING(2),
STORY_REPLY(3);
private final int code;
MessageType(int code) {
this.code = code;
}
private static @NonNull MessageType fromCode(int code) {
for (MessageType value : values()) {
if (value.code == code) {
return value;
}
}
throw new IllegalArgumentException("Unsupported code " + code);
}
}
private ViewGroup mainView;
private ViewGroup footerView;
@ -68,7 +85,7 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
private TextView mediaDescriptionText;
private TextView missingLinkText;
private SlideDeck attachments;
private int messageType;
private MessageType messageType;
private int largeCornerRadius;
private int smallCornerRadius;
private CornerMask cornerMask;
@ -116,44 +133,24 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
this.smallCornerRadius = getResources().getDimensionPixelSize(R.dimen.quote_corner_radius_bottom);
cornerMask = new CornerMask(this);
cornerMask.setRadii(largeCornerRadius, largeCornerRadius, smallCornerRadius, smallCornerRadius);
if (attrs != null) {
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.QuoteView, 0, 0);
int primaryColor = typedArray.getColor(R.styleable.QuoteView_quote_colorPrimary, Color.BLACK);
int secondaryColor = typedArray.getColor(R.styleable.QuoteView_quote_colorSecondary, Color.BLACK);
messageType = typedArray.getInt(R.styleable.QuoteView_message_type, 0);
messageType = MessageType.fromCode(typedArray.getInt(R.styleable.QuoteView_message_type, 0));
typedArray.recycle();
dismissView.setVisibility(messageType == MESSAGE_TYPE_PREVIEW ? VISIBLE : GONE);
dismissView.setVisibility(messageType == MessageType.PREVIEW ? VISIBLE : GONE);
authorView.setTextColor(primaryColor);
bodyView.setTextColor(primaryColor);
attachmentNameView.setTextColor(primaryColor);
mediaDescriptionText.setTextColor(secondaryColor);
missingLinkText.setTextColor(primaryColor);
if (messageType == MESSAGE_TYPE_PREVIEW) {
int radius = getResources().getDimensionPixelOffset(R.dimen.quote_corner_radius_preview);
cornerMask.setTopLeftRadius(radius);
cornerMask.setTopRightRadius(radius);
}
}
if (messageType == MESSAGE_TYPE_STORY_REPLY) {
thumbWidth = getResources().getDimensionPixelOffset(R.dimen.quote_story_thumb_width);
thumbHeight = getResources().getDimensionPixelOffset(R.dimen.quote_story_thumb_height);
mainView.setMinimumHeight(thumbHeight);
ViewGroup.LayoutParams params = thumbnailView.getLayoutParams();
params.height = thumbHeight;
params.width = thumbWidth;
thumbnailView.setLayoutParams(params);
} else {
thumbWidth = thumbHeight = getResources().getDimensionPixelSize(R.dimen.quote_thumb_size);
}
setMessageType(messageType);
dismissView.setOnClickListener(view -> setVisibility(GONE));
}
@ -170,6 +167,30 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
if (author != null) author.removeForeverObserver(this);
}
public void setMessageType(@NonNull MessageType messageType) {
this.messageType = messageType;
cornerMask.setRadii(largeCornerRadius, largeCornerRadius, smallCornerRadius, smallCornerRadius);
thumbWidth = thumbHeight = getResources().getDimensionPixelSize(R.dimen.quote_thumb_size);
if (messageType == MessageType.PREVIEW) {
int radius = getResources().getDimensionPixelOffset(R.dimen.quote_corner_radius_preview);
cornerMask.setTopLeftRadius(radius);
cornerMask.setTopRightRadius(radius);
} else if (messageType == MessageType.STORY_REPLY) {
thumbWidth = getResources().getDimensionPixelOffset(R.dimen.quote_story_thumb_width);
thumbHeight = getResources().getDimensionPixelOffset(R.dimen.quote_story_thumb_height);
}
mainView.setMinimumHeight(thumbHeight);
ViewGroup.LayoutParams params = thumbnailView.getLayoutParams();
params.height = thumbHeight;
params.width = thumbWidth;
thumbnailView.setLayoutParams(params);
}
public void setQuote(GlideRequests glideRequests,
long id,
@NonNull Recipient author,
@ -191,7 +212,7 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
setQuoteAttachment(glideRequests, attachments);
setQuoteMissingFooter(originalMissing);
if (Build.VERSION.SDK_INT < 21 && messageType == MESSAGE_TYPE_INCOMING && chatColors != null) {
if (Build.VERSION.SDK_INT < 21 && messageType == MessageType.INCOMING && chatColors != null) {
this.setBackgroundColor(chatColors.asSingleColor());
} else {
this.setBackground(null);
@ -227,11 +248,12 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
}
private void setQuoteAuthor(@NonNull Recipient author) {
boolean outgoing = messageType != MESSAGE_TYPE_INCOMING;
boolean preview = messageType == MESSAGE_TYPE_PREVIEW || messageType == MESSAGE_TYPE_STORY_REPLY;
boolean outgoing = messageType != MessageType.INCOMING;
boolean preview = messageType == MessageType.PREVIEW || messageType == MessageType.STORY_REPLY;
if (messageType == MESSAGE_TYPE_STORY_REPLY && author.isGroup()) {
authorView.setText(getContext().getString(R.string.QuoteView_s_story, author.getDisplayName(getContext())));
if (messageType == MessageType.STORY_REPLY) {
authorView.setText(author.isSelf() ? getContext().getString(R.string.QuoteView_your_story)
: getContext().getString(R.string.QuoteView_s_story, author.getDisplayName(getContext())));
} else {
authorView.setText(author.isSelf() ? getContext().getString(R.string.QuoteView_you)
: author.getDisplayName(getContext()));

View file

@ -1400,6 +1400,13 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
throw new AssertionError();
}
Quote quote = ((MediaMmsMessageRecord)current).getQuote();
if (((MediaMmsMessageRecord) current).getParentStoryId() != null) {
quoteView.setMessageType(QuoteView.MessageType.STORY_REPLY);
} else {
quoteView.setMessageType(current.isOutgoing() ? QuoteView.MessageType.OUTGOING : QuoteView.MessageType.INCOMING);
}
//noinspection ConstantConditions
quoteView.setQuote(glideRequests, quote.getId(), Recipient.live(quote.getAuthor()).get(), quote.getDisplayText(), quote.isOriginalMissing(), quote.getAttachment(), chatColors);
quoteView.setVisibility(View.VISIBLE);

View file

@ -2270,7 +2270,8 @@ public class MmsDatabase extends MessageDatabase {
0,
-1,
null,
message.getStoryType());
message.getStoryType(),
message.getParentStoryId());
}
}
@ -2313,17 +2314,18 @@ public class MmsDatabase extends MessageDatabase {
int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID));
Recipient recipient = Recipient.live(RecipientId.from(recipientId)).get();
String contentLocation = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.CONTENT_LOCATION));
String transactionId = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.TRANSACTION_ID));
long messageSize = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_SIZE));
long expiry = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRY));
int status = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.STATUS));
int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.DELIVERY_RECEIPT_COUNT));
int readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.READ_RECEIPT_COUNT));
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.SUBSCRIPTION_ID));
int viewedReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsColumns.VIEWED_RECEIPT_COUNT));
long receiptTimestamp = CursorUtil.requireLong(cursor, MmsSmsColumns.RECEIPT_TIMESTAMP);
StoryType storyType = StoryType.fromCode(CursorUtil.requireInt(cursor, STORY_TYPE));
String contentLocation = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.CONTENT_LOCATION));
String transactionId = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.TRANSACTION_ID));
long messageSize = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_SIZE));
long expiry = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRY));
int status = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.STATUS));
int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.DELIVERY_RECEIPT_COUNT));
int readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.READ_RECEIPT_COUNT));
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.SUBSCRIPTION_ID));
int viewedReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsColumns.VIEWED_RECEIPT_COUNT));
long receiptTimestamp = CursorUtil.requireLong(cursor, MmsSmsColumns.RECEIPT_TIMESTAMP);
StoryType storyType = StoryType.fromCode(CursorUtil.requireInt(cursor, STORY_TYPE));
ParentStoryId parentStoryId = ParentStoryId.deserialize(CursorUtil.requireLong(cursor, PARENT_STORY_ID));
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
readReceiptCount = 0;
@ -2345,7 +2347,8 @@ public class MmsDatabase extends MessageDatabase {
addressDeviceId, dateSent, dateReceived, deliveryReceiptCount, threadId,
contentLocationBytes, messageSize, expiry, status,
transactionIdBytes, mailbox, subscriptionId, slideDeck,
readReceiptCount, viewedReceiptCount, receiptTimestamp, storyType);
readReceiptCount, viewedReceiptCount, receiptTimestamp, storyType,
parentStoryId);
}
private MediaMmsMessageRecord getMediaMmsMessageRecord(Cursor cursor) {
@ -2375,6 +2378,7 @@ public class MmsDatabase extends MessageDatabase {
long receiptTimestamp = CursorUtil.requireLong(cursor, MmsSmsColumns.RECEIPT_TIMESTAMP);
byte[] messageRangesData = CursorUtil.requireBlob(cursor, MESSAGE_RANGES);
StoryType storyType = StoryType.fromCode(CursorUtil.requireInt(cursor, STORY_TYPE));
ParentStoryId parentStoryId = ParentStoryId.deserialize(CursorUtil.requireLong(cursor, PARENT_STORY_ID));
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
readReceiptCount = 0;
@ -2410,7 +2414,7 @@ public class MmsDatabase extends MessageDatabase {
networkFailures, subscriptionId, expiresIn, expireStarted,
isViewOnce, readReceiptCount, quote, contacts, previews, unidentified, Collections.emptyList(),
remoteDelete, mentionsSelf, notifiedTimestamp, viewedReceiptCount, receiptTimestamp, messageRanges,
storyType);
storyType, parentStoryId);
}
private Set<IdentityKeyMismatch> getMismatchedIdentities(String document) {

View file

@ -535,7 +535,7 @@ public class MmsSmsDatabase extends Database {
public int getQuotedMessagePosition(long threadId, long quoteId, @NonNull RecipientId recipientId) {
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsDatabase.STORY_TYPE + " = 0" + " AND " + MmsDatabase.PARENT_STORY_ID + " <= 0";
try (Cursor cursor = queryTables(new String[]{ MmsSmsColumns.NORMALIZED_DATE_SENT, MmsSmsColumns.RECIPIENT_ID, MmsSmsColumns.REMOTE_DELETED}, selection, order, null)) {
boolean isOwnNumber = Recipient.resolved(recipientId).isSelf();
@ -558,7 +558,7 @@ public class MmsSmsDatabase extends Database {
public int getMessagePositionInConversation(long threadId, long receivedTimestamp, @NonNull RecipientId recipientId) {
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsDatabase.STORY_TYPE + " = 0" + " AND " + MmsDatabase.PARENT_STORY_ID + " <= 0";
try (Cursor cursor = queryTables(new String[]{ MmsSmsColumns.NORMALIZED_DATE_RECEIVED, MmsSmsColumns.RECIPIENT_ID, MmsSmsColumns.REMOTE_DELETED}, selection, order, null)) {
boolean isOwnNumber = Recipient.resolved(recipientId).isSelf();

View file

@ -92,13 +92,14 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
int viewedReceiptCount,
long receiptTimestamp,
@Nullable BodyRangeList messageRanges,
@NonNull StoryType storyType)
@NonNull StoryType storyType,
@Nullable ParentStoryId parentStoryId)
{
super(id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent,
dateReceived, dateServer, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures,
subscriptionId, expiresIn, expireStarted, viewOnce, slideDeck,
readReceiptCount, quote, contacts, linkPreviews, unidentified, reactions, remoteDelete, notifiedTimestamp, viewedReceiptCount, receiptTimestamp,
storyType);
storyType, parentStoryId);
this.partCount = partCount;
this.mentionsSelf = mentionsSelf;
this.messageRanges = messageRanges;
@ -152,7 +153,7 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
return new MediaMmsMessageRecord(getId(), getRecipient(), getIndividualRecipient(), getRecipientDeviceId(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), getSlideDeck(),
getPartCount(), getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getReadReceiptCount(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), reactions, isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType());
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId());
}
public @NonNull MediaMmsMessageRecord withAttachments(@NonNull Context context, @NonNull List<DatabaseAttachment> attachments) {
@ -173,7 +174,7 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
return new MediaMmsMessageRecord(getId(), getRecipient(), getIndividualRecipient(), getRecipientDeviceId(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), slideDeck,
getPartCount(), getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getReadReceiptCount(), quote, contacts, linkPreviews, isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType());
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId());
}
private static @NonNull List<Contact> updateContacts(@NonNull List<Contact> contacts, @NonNull Map<AttachmentId, DatabaseAttachment> attachmentIdMap) {

View file

@ -23,6 +23,7 @@ public abstract class MmsMessageRecord extends MessageRecord {
private final @NonNull List<Contact> contacts = new LinkedList<>();
private final @NonNull List<LinkPreview> linkPreviews = new LinkedList<>();
private final @NonNull StoryType storyType;
private final @Nullable ParentStoryId parentStoryId;
private final boolean viewOnce;
@ -36,17 +37,19 @@ public abstract class MmsMessageRecord extends MessageRecord {
@Nullable Quote quote, @NonNull List<Contact> contacts,
@NonNull List<LinkPreview> linkPreviews, boolean unidentified,
@NonNull List<ReactionRecord> reactions, boolean remoteDelete, long notifiedTimestamp,
int viewedReceiptCount, long receiptTimestamp, @NonNull StoryType storyType)
int viewedReceiptCount, long receiptTimestamp, @NonNull StoryType storyType,
@Nullable ParentStoryId parentStoryId)
{
super(id, body, conversationRecipient, individualRecipient, recipientDeviceId,
dateSent, dateReceived, dateServer, threadId, deliveryStatus, deliveryReceiptCount,
type, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, readReceiptCount,
unidentified, reactions, remoteDelete, notifiedTimestamp, viewedReceiptCount, receiptTimestamp);
this.slideDeck = slideDeck;
this.quote = quote;
this.viewOnce = viewOnce;
this.storyType = storyType;
this.slideDeck = slideDeck;
this.quote = quote;
this.viewOnce = viewOnce;
this.storyType = storyType;
this.parentStoryId = parentStoryId;
this.contacts.addAll(contacts);
this.linkPreviews.addAll(linkPreviews);
@ -82,6 +85,10 @@ public abstract class MmsMessageRecord extends MessageRecord {
return storyType;
}
public @Nullable ParentStoryId getParentStoryId() {
return parentStoryId;
}
public boolean containsMediaSlide() {
return slideDeck.containsMediaSlide();
}

View file

@ -20,6 +20,7 @@ import android.content.Context;
import android.text.SpannableString;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.MmsDatabase;
@ -53,13 +54,14 @@ public class NotificationMmsMessageRecord extends MmsMessageRecord {
long threadId, byte[] contentLocation, long messageSize,
long expiry, int status, byte[] transactionId, long mailbox,
int subscriptionId, SlideDeck slideDeck, int readReceiptCount,
int viewedReceiptCount, long receiptTimestamp, @NonNull StoryType storyType)
int viewedReceiptCount, long receiptTimestamp, @NonNull StoryType storyType,
@Nullable ParentStoryId parentStoryId)
{
super(id, "", conversationRecipient, individualRecipient, recipientDeviceId,
dateSent, dateReceived, -1, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox,
new HashSet<>(), new HashSet<>(), subscriptionId,
0, 0, false, slideDeck, readReceiptCount, null, Collections.emptyList(), Collections.emptyList(), false,
Collections.emptyList(), false, 0, viewedReceiptCount, receiptTimestamp, storyType);
Collections.emptyList(), false, 0, viewedReceiptCount, receiptTimestamp, storyType, parentStoryId);
this.contentLocation = contentLocation;
this.messageSize = messageSize;

View file

@ -2076,6 +2076,8 @@
<string name="QuoteView_original_missing">Original message not found</string>
<!-- Author formatting for group stories -->
<string name="QuoteView_s_story">%1$s Story</string>
<!-- Label indicating that a quote is for a reply to a story you created -->
<string name="QuoteView_your_story">Your Story</string>
<!-- conversation_fragment -->
<string name="conversation_fragment__scroll_to_the_bottom_content_description">Scroll to the bottom</string>