From 08491579ddba335f53a3374112b3692a3a2a7d61 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Sat, 21 Mar 2026 09:34:23 -0400 Subject: [PATCH] Add links to the all media view. --- .../securesms/database/MediaTable.kt | 90 ++++++++++- .../database/loaders/MediaLoader.java | 1 + .../database/loaders/ThreadMediaLoader.java | 1 + .../mediaoverview/MediaGalleryAllAdapter.java | 146 ++++++++++++++++-- .../mediaoverview/MediaOverviewActivity.java | 9 +- .../media_overview_link_fallback_bg.xml | 6 + .../media_overview_detail_item_link.xml | 88 +++++++++++ app/src/main/res/values/strings.xml | 2 + 8 files changed, 323 insertions(+), 20 deletions(-) create mode 100644 app/src/main/res/drawable/media_overview_link_fallback_bg.xml create mode 100644 app/src/main/res/layout/media_overview_detail_item_link.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MediaTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MediaTable.kt index acbed7a198..aff3ff633c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MediaTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MediaTable.kt @@ -61,8 +61,9 @@ class MediaTable internal constructor(context: Context?, databaseHelper: SignalD ${MessageTable.TABLE_NAME}.${MessageTable.DATE_RECEIVED}, ${MessageTable.TABLE_NAME}.${MessageTable.DATE_SERVER}, ${MessageTable.TABLE_NAME}.${MessageTable.THREAD_ID}, - ${MessageTable.TABLE_NAME}.${MessageTable.FROM_RECIPIENT_ID}, - ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} as $THREAD_RECIPIENT_ID + ${MessageTable.TABLE_NAME}.${MessageTable.FROM_RECIPIENT_ID}, + ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} as $THREAD_RECIPIENT_ID, + ${MessageTable.TABLE_NAME}.${MessageTable.LINK_PREVIEWS} FROM ${AttachmentTable.TABLE_NAME} LEFT JOIN ${MessageTable.TABLE_NAME} ON ${AttachmentTable.TABLE_NAME}.${AttachmentTable.MESSAGE_ID} = ${MessageTable.TABLE_NAME}.${MessageTable.ID} @@ -135,6 +136,69 @@ class MediaTable internal constructor(context: Context?, databaseHelper: SignalD """ ) + private val LINK_MEDIA_QUERY = """ + SELECT + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.ID} AS ${AttachmentTable.ID}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.CONTENT_TYPE}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.MESSAGE_ID}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.TRANSFER_STATE}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.DATA_SIZE}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.FILE_NAME}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.DATA_FILE}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.THUMBNAIL_FILE}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.CDN_NUMBER}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.REMOTE_LOCATION}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.REMOTE_KEY}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.REMOTE_DIGEST}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.FAST_PREFLIGHT_ID}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.VOICE_NOTE}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.BORDERLESS}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.VIDEO_GIF}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.WIDTH}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.HEIGHT}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.QUOTE}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.QUOTE_TARGET_CONTENT_TYPE}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.STICKER_PACK_ID}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.STICKER_PACK_KEY}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.STICKER_ID}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.STICKER_EMOJI}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.BLUR_HASH}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.TRANSFORM_PROPERTIES}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.DISPLAY_ORDER}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.CAPTION}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.UPLOAD_TIMESTAMP}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.REMOTE_INCREMENTAL_DIGEST}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.REMOTE_INCREMENTAL_DIGEST_CHUNK_SIZE}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.DATA_HASH_END}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.ARCHIVE_CDN}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.THUMBNAIL_RESTORE_STATE}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.ARCHIVE_TRANSFER_STATE}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.ATTACHMENT_UUID}, + ${MessageTable.TABLE_NAME}.${MessageTable.TYPE}, + ${MessageTable.TABLE_NAME}.${MessageTable.DATE_SENT}, + ${MessageTable.TABLE_NAME}.${MessageTable.DATE_RECEIVED}, + ${MessageTable.TABLE_NAME}.${MessageTable.DATE_SERVER}, + ${MessageTable.TABLE_NAME}.${MessageTable.THREAD_ID}, + ${MessageTable.TABLE_NAME}.${MessageTable.FROM_RECIPIENT_ID}, + ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} as $THREAD_RECIPIENT_ID, + ${MessageTable.TABLE_NAME}.${MessageTable.LINK_PREVIEWS} + FROM + ${MessageTable.TABLE_NAME} + LEFT JOIN ${AttachmentTable.TABLE_NAME} ON ${AttachmentTable.TABLE_NAME}.${AttachmentTable.MESSAGE_ID} = ${MessageTable.TABLE_NAME}.${MessageTable.ID} + AND ${AttachmentTable.TABLE_NAME}.${AttachmentTable.QUOTE} = 0 + AND ${AttachmentTable.TABLE_NAME}.${AttachmentTable.STICKER_PACK_ID} IS NULL + LEFT JOIN ${ThreadTable.TABLE_NAME} ON ${ThreadTable.TABLE_NAME}.${ThreadTable.ID} = ${MessageTable.TABLE_NAME}.${MessageTable.THREAD_ID} + WHERE + ${MessageTable.TABLE_NAME}.${MessageTable.THREAD_ID} __EQUALITY__ ? AND + ${MessageTable.TABLE_NAME}.${MessageTable.LINK_PREVIEWS} IS NOT NULL AND + ${MessageTable.TABLE_NAME}.${MessageTable.VIEW_ONCE} = 0 AND + ${MessageTable.TABLE_NAME}.${MessageTable.STORY_TYPE} = 0 AND + ${MessageTable.TABLE_NAME}.${MessageTable.LATEST_REVISION_ID} IS NULL AND + ${MessageTable.TABLE_NAME}.${MessageTable.FROM_RECIPIENT_ID} > 0 AND + $THREAD_RECIPIENT_ID > 0 AND + ${MessageTable.TABLE_NAME}.${MessageTable.SCHEDULED_DATE} < 0 + """ + private val DOCUMENT_MEDIA_QUERY = String.format( BASE_MEDIA_QUERY, """ @@ -180,6 +244,17 @@ class MediaTable internal constructor(context: Context?, databaseHelper: SignalD return readableDatabase.rawQuery(query, args) } + fun getLinkMediaForThread(threadId: Long, sorting: Sorting): Cursor { + val orderBy = when (sorting) { + Sorting.Newest -> " ORDER BY ${MessageTable.TABLE_NAME}.${MessageTable.DATE_SENT} DESC" + Sorting.Oldest -> " ORDER BY ${MessageTable.TABLE_NAME}.${MessageTable.DATE_SENT} ASC" + Sorting.Largest -> " ORDER BY ${AttachmentTable.TABLE_NAME}.${AttachmentTable.DATA_SIZE} DESC" + } + val query = applyEqualityOperator(threadId, LINK_MEDIA_QUERY) + orderBy + val args = arrayOf(threadId.toString()) + return readableDatabase.rawQuery(query, args) + } + fun getAllMediaForThread(threadId: Long, sorting: Sorting): Cursor { val query = sorting.applyToQuery(applyEqualityOperator(threadId, ALL_MEDIA_QUERY)) val args = arrayOf(threadId.toString() + "") @@ -236,7 +311,8 @@ class MediaTable internal constructor(context: Context?, databaseHelper: SignalD val threadRecipientId: RecipientId, val threadId: Long, val date: Long, - val isOutgoing: Boolean + val isOutgoing: Boolean, + val linkPreviewJson: String? = null ) { val contentType: String? @@ -245,8 +321,11 @@ class MediaTable internal constructor(context: Context?, databaseHelper: SignalD companion object { @JvmStatic fun from(cursor: Cursor): MediaRecord { + val linkPreviewIdx = cursor.getColumnIndex(MessageTable.LINK_PREVIEWS) + val attachmentIdIdx = cursor.getColumnIndex(AttachmentTable.ID) + val hasAttachment = attachmentIdIdx != -1 && !cursor.isNull(attachmentIdIdx) return MediaRecord( - attachment = SignalDatabase.attachments.getAttachment(cursor), + attachment = if (hasAttachment) SignalDatabase.attachments.getAttachment(cursor) else null, recipientId = RecipientId.from(cursor.requireLong(MessageTable.FROM_RECIPIENT_ID)), threadId = cursor.requireLong(MessageTable.THREAD_ID), threadRecipientId = RecipientId.from(cursor.requireLong(THREAD_RECIPIENT_ID)), @@ -255,7 +334,8 @@ class MediaTable internal constructor(context: Context?, databaseHelper: SignalD } else { cursor.requireLong(MessageTable.DATE_RECEIVED) }, - isOutgoing = MessageTypes.isOutgoingMessageType(cursor.requireLong(MessageTable.TYPE)) + isOutgoing = MessageTypes.isOutgoingMessageType(cursor.requireLong(MessageTable.TYPE)), + linkPreviewJson = if (linkPreviewIdx != -1) cursor.getString(linkPreviewIdx) else null ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/MediaLoader.java b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/MediaLoader.java index 1ac9ded38b..022173a5a3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/MediaLoader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/MediaLoader.java @@ -14,6 +14,7 @@ public abstract class MediaLoader extends AbstractCursorLoader { GALLERY, DOCUMENT, AUDIO, + LINK, ALL } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/ThreadMediaLoader.java b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/ThreadMediaLoader.java index 7e35a4be33..24076d8f26 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/ThreadMediaLoader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/ThreadMediaLoader.java @@ -40,6 +40,7 @@ public final class ThreadMediaLoader extends MediaLoader { case GALLERY : return mediaDatabase.getGalleryMediaForThread(threadId, sorting); case DOCUMENT: return mediaDatabase.getDocumentMediaForThread(threadId, sorting); case AUDIO : return mediaDatabase.getAudioMediaForThread(threadId, sorting); + case LINK : return mediaDatabase.getLinkMediaForThread(threadId, sorting); case ALL : return mediaDatabase.getAllMediaForThread(threadId, sorting); default : throw new AssertionError(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaGalleryAllAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaGalleryAllAdapter.java index f8a74c9ac8..1d5dfeb4d6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaGalleryAllAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaGalleryAllAdapter.java @@ -32,6 +32,10 @@ import androidx.annotation.Nullable; import androidx.lifecycle.Observer; import androidx.recyclerview.widget.RecyclerView; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + import com.annimon.stream.Collectors; import com.annimon.stream.Stream; import com.bumptech.glide.RequestManager; @@ -53,6 +57,7 @@ import org.thoughtcrime.securesms.mms.AudioSlide; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.MediaUtil; import org.signal.core.util.Util; @@ -89,6 +94,7 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { public static final int GALLERY = 2; private static final int GALLERY_DETAIL = 3; private static final int DOCUMENT_DETAIL = 4; + private static final int LINK_DETAIL = 5; private static final int PAYLOAD_SELECTED = 1; @@ -142,6 +148,8 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { return new GalleryDetailViewHolder(LayoutInflater.from(context).inflate(R.layout.media_overview_detail_item_media, parent, false)); case AUDIO_DETAIL: return new AudioDetailViewHolder(LayoutInflater.from(context).inflate(R.layout.media_overview_detail_item_audio, parent, false)); + case LINK_DETAIL: + return new LinkDetailViewHolder(LayoutInflater.from(context).inflate(R.layout.media_overview_detail_item_link, parent, false)); default: return new DocumentDetailViewHolder(LayoutInflater.from(context).inflate(R.layout.media_overview_detail_item_document, parent, false)); } @@ -150,7 +158,11 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { @Override public int getSectionItemViewType(int section, int offset) { MediaTable.MediaRecord mediaRecord = media.get(section, offset); - Slide slide = MediaUtil.getSlideForAttachment(mediaRecord.getAttachment()); + + if (mediaRecord.getLinkPreviewJson() != null) return LINK_DETAIL; + if (mediaRecord.getAttachment() == null) return 0; + + Slide slide = MediaUtil.getSlideForAttachment(mediaRecord.getAttachment()); if (slide.hasAudio()) return AUDIO_DETAIL; if (slide.hasImage() || slide.hasVideo()) return detailView ? GALLERY_DETAIL : GALLERY; @@ -177,7 +189,7 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { @Override public void onBindItemViewHolder(ItemViewHolder viewHolder, int section, int offset) { MediaTable.MediaRecord mediaRecord = media.get(section, offset); - Slide slide = MediaUtil.getSlideForAttachment(mediaRecord.getAttachment()); + Slide slide = mediaRecord.getAttachment() != null ? MediaUtil.getSlideForAttachment(mediaRecord.getAttachment()) : null; ((SelectableViewHolder) viewHolder).bind(context, mediaRecord, slide); } @@ -209,6 +221,8 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { } public void toggleSelection(@NonNull MediaRecord mediaRecord) { + if (mediaRecord.getAttachment() == null) return; + AttachmentId attachmentId = mediaRecord.getAttachment().attachmentId; MediaTable.MediaRecord removed = selected.remove(attachmentId); if (removed == null) { @@ -244,7 +258,9 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { int sectionItemCount = media.getSectionItemCount(section); for (int item = 0; item < sectionItemCount; item++) { MediaRecord mediaRecord = media.get(section, item); - selected.put(mediaRecord.getAttachment().attachmentId, mediaRecord); + if (mediaRecord.getAttachment() != null) { + selected.put(mediaRecord.getAttachment().attachmentId, mediaRecord); + } } } this.notifyItemRangeChanged(0, getItemCount(), PAYLOAD_SELECTED); @@ -270,7 +286,7 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { this.selectedIndicator = itemView.findViewById(R.id.selected_indicator); } - public void bind(@NonNull Context context, @NonNull MediaTable.MediaRecord mediaRecord, @NonNull Slide slide) { + public void bind(@NonNull Context context, @NonNull MediaTable.MediaRecord mediaRecord, @Nullable Slide slide) { if (bound) { unbind(); } @@ -288,7 +304,7 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { } protected boolean isSelected() { - return selected.containsKey(mediaRecord.getAttachment().attachmentId); + return mediaRecord.getAttachment() != null && selected.containsKey(mediaRecord.getAttachment().attachmentId); } protected void updateSelectedView() { @@ -413,10 +429,10 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { } @Override - public void bind(@NonNull Context context, @NonNull MediaTable.MediaRecord mediaRecord, @NonNull Slide slide) { + public void bind(@NonNull Context context, @NonNull MediaTable.MediaRecord mediaRecord, @Nullable Slide slide) { super.bind(context, mediaRecord, slide); - fileName = slide.getFileName(); + fileName = slide != null ? slide.getFileName() : Optional.empty(); fileTypeDescription = getFileTypeDescription(context, slide); line1.setText(fileName.orElse(fileTypeDescription)); @@ -449,14 +465,17 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { super.unbind(); } - private String getLine2(@NonNull Context context, @NonNull MediaTable.MediaRecord mediaRecord, @NonNull Slide slide) { + private String getLine2(@NonNull Context context, @NonNull MediaTable.MediaRecord mediaRecord, @Nullable Slide slide) { + if (slide == null) { + return DateUtils.formatDateWithoutDayOfWeek(Locale.getDefault(), mediaRecord.getDate()); + } return context.getString(R.string.MediaOverviewActivity_detail_line_3_part, new ByteSize(slide.getFileSize()).toUnitString(2), getFileTypeDescription(context, slide), DateUtils.formatDateWithoutDayOfWeek(Locale.getDefault(), mediaRecord.getDate())); } - protected String getFileTypeDescription(@NonNull Context context, @NonNull Slide slide) { + protected String getFileTypeDescription(@NonNull Context context, @Nullable Slide slide) { return context.getString(R.string.MediaOverviewActivity_file); } @@ -609,7 +628,7 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { } @Override - protected String getFileTypeDescription(@NonNull Context context, @NonNull Slide slide) { + protected String getFileTypeDescription(@NonNull Context context, @Nullable Slide slide) { return context.getString(R.string.MediaOverviewActivity_audio); } } @@ -641,9 +660,9 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { } @Override - protected String getFileTypeDescription(@NonNull Context context, @NonNull Slide slide) { - if (slide.hasVideo()) return context.getString(R.string.MediaOverviewActivity_video); - if (slide.hasImage()) return context.getString(R.string.MediaOverviewActivity_image); + protected String getFileTypeDescription(@NonNull Context context, @Nullable Slide slide) { + if (slide != null && slide.hasVideo()) return context.getString(R.string.MediaOverviewActivity_video); + if (slide != null && slide.hasImage()) return context.getString(R.string.MediaOverviewActivity_image); return super.getFileTypeDescription(context, slide); } @@ -660,6 +679,107 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { } } + private class LinkDetailViewHolder extends DetailViewHolder { + + private final ThumbnailView thumbnailView; + private final TextView linkUrlView; + + private Slide slide; + private String linkUrl; + private String linkTitle; + + LinkDetailViewHolder(@NonNull View itemView) { + super(itemView); + this.thumbnailView = itemView.findViewById(R.id.image); + this.linkUrlView = itemView.findViewById(R.id.link_url); + } + + @Override + public void bind(@NonNull Context context, @NonNull MediaTable.MediaRecord mediaRecord, @Nullable Slide slide) { + parseLinkPreview(mediaRecord); + super.bind(context, mediaRecord, slide); + this.slide = slide; + + if (slide != null) { + thumbnailView.setVisibility(View.VISIBLE); + thumbnailView.setImageResource(requestManager, slide, false, false); + } else { + thumbnailView.setVisibility(View.GONE); + } + + thumbnailView.setOnLongClickListener(view -> onLongClick()); + + View.OnClickListener openLink = view -> { + if (linkUrl != null && !linkUrl.isEmpty()) { + CommunicationActions.openBrowserLink(context, linkUrl); + } + }; + thumbnailView.setOnClickListener(openLink); + itemView.setOnClickListener(openLink); + + if (linkUrl != null && !linkUrl.isEmpty()) { + linkUrlView.setText(linkUrl); + linkUrlView.setVisibility(View.VISIBLE); + } else { + linkUrlView.setVisibility(View.GONE); + } + + TextView line2View = itemView.findViewById(R.id.line2); + line2View.setText(DateUtils.formatDateWithoutDayOfWeek(Locale.getDefault(), mediaRecord.getDate())); + } + + @Override + protected @Nullable String getMediaTitle() { + if (linkTitle != null && !linkTitle.isEmpty()) { + return linkTitle; + } + return null; + } + + @Override + protected @NonNull View getTransitionAnchor() { + return itemView; + } + + @Override + protected String getFileTypeDescription(@NonNull Context context, @Nullable Slide slide) { + return context.getString(R.string.MediaOverviewActivity_link); + } + + @Override + void rebind() { + if (slide != null) { + thumbnailView.setImageResource(requestManager, slide, false, false); + } + super.rebind(); + } + + @Override + void unbind() { + if (slide != null) { + thumbnailView.clear(requestManager); + } + super.unbind(); + } + + private void parseLinkPreview(@NonNull MediaTable.MediaRecord mediaRecord) { + linkUrl = ""; + linkTitle = ""; + if (mediaRecord.getLinkPreviewJson() != null) { + try { + JSONArray json = new JSONArray(mediaRecord.getLinkPreviewJson()); + if (json.length() > 0) { + JSONObject preview = json.getJSONObject(0); + linkUrl = preview.optString("url", ""); + linkTitle = preview.optString("title", ""); + } + } catch (JSONException e) { + // ignore + } + } + } + } + private static final class AudioViewCallbacksAdapter implements AudioView.Callbacks { private final AudioItemListener audioItemListener; diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewActivity.java index 5bd6623d8a..9a04dc42ec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewActivity.java @@ -128,7 +128,7 @@ public final class MediaOverviewActivity extends PassphraseRequiredActivity { } }); - viewPager.setCurrentItem(allThreads ? 3 : 0); + viewPager.setCurrentItem(allThreads ? viewPager.getAdapter().getCount() - 1 : 0); } private static boolean allowGridSelectionOnPage(int page) { @@ -264,10 +264,15 @@ public final class MediaOverviewActivity extends PassphraseRequiredActivity { MediaOverviewPagerAdapter(FragmentManager fragmentManager) { super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); - pages = new ArrayList<>(4); + boolean allThreads = threadId == MediaTable.ALL_THREADS; + + pages = new ArrayList<>(allThreads ? 4 : 5); pages.add(new Pair<>(MediaLoader.MediaType.GALLERY, getString(R.string.MediaOverviewActivity_Media))); pages.add(new Pair<>(MediaLoader.MediaType.DOCUMENT, getString(R.string.MediaOverviewActivity_Files))); pages.add(new Pair<>(MediaLoader.MediaType.AUDIO, getString(R.string.MediaOverviewActivity_Audio))); + if (!allThreads) { + pages.add(new Pair<>(MediaLoader.MediaType.LINK, getString(R.string.MediaOverviewActivity_Links))); + } pages.add(new Pair<>(MediaLoader.MediaType.ALL, getString(R.string.MediaOverviewActivity_All))); } diff --git a/app/src/main/res/drawable/media_overview_link_fallback_bg.xml b/app/src/main/res/drawable/media_overview_link_fallback_bg.xml new file mode 100644 index 0000000000..47866a1641 --- /dev/null +++ b/app/src/main/res/drawable/media_overview_link_fallback_bg.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/layout/media_overview_detail_item_link.xml b/app/src/main/res/layout/media_overview_detail_item_link.xml new file mode 100644 index 0000000000..d11b379814 --- /dev/null +++ b/app/src/main/res/layout/media_overview_detail_item_link.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0dc8a17b7e..f35126202f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1780,6 +1780,8 @@ Files Audio All + Links + Link Delete selected item? Delete selected items?