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?