diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java b/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java index 806b64f61..6340fe001 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java @@ -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())); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index 377a683f4..f4a1a7fc3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -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); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index c3af11243..bf9ea8fbf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -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 getMismatchedIdentities(String document) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index da5a91705..39c40a3e1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -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(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java index 452be5c51..a60df1f6f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java @@ -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 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 updateContacts(@NonNull List contacts, @NonNull Map attachmentIdMap) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java index 3e45d8eaf..bc59ea295 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java @@ -23,6 +23,7 @@ public abstract class MmsMessageRecord extends MessageRecord { private final @NonNull List contacts = new LinkedList<>(); private final @NonNull List 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 contacts, @NonNull List linkPreviews, boolean unidentified, @NonNull List 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(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java index 1513b7cdc..a1512f1fb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java @@ -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; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2c661df0d..aab3131a4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2076,6 +2076,8 @@ Original message not found %1$s Story + + Your Story Scroll to the bottom