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 543a58f571..9a699ffdcf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MediaTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MediaTable.kt @@ -20,65 +20,67 @@ class MediaTable internal constructor(context: Context?, databaseHelper: SignalD private val TAG = Log.tag(MediaTable::class) const val ALL_THREADS = -1 private const val THREAD_RECIPIENT_ID = "THREAD_RECIPIENT_ID" + private const val MEDIA_MESSAGE_ID = "media_message_id" private val BASE_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}, + 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.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.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.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.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} + ${MessageTable.TABLE_NAME}.${MessageTable.LINK_PREVIEWS}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.MESSAGE_ID} as $MEDIA_MESSAGE_ID FROM ${AttachmentTable.TABLE_NAME} __INDEX_HINT__ LEFT JOIN ${MessageTable.TABLE_NAME} ON ${AttachmentTable.TABLE_NAME}.${AttachmentTable.MESSAGE_ID} = ${MessageTable.TABLE_NAME}.${MessageTable.ID} LEFT JOIN ${ThreadTable.TABLE_NAME} ON ${ThreadTable.TABLE_NAME}.${ThreadTable.ID} = ${MessageTable.TABLE_NAME}.${MessageTable.THREAD_ID} WHERE __THREAD_FILTER__ AND - (%s) AND - ${MessageTable.VIEW_ONCE} = 0 AND + (%s) AND + ${MessageTable.VIEW_ONCE} = 0 AND ${MessageTable.STORY_TYPE} = 0 AND - ${MessageTable.LATEST_REVISION_ID} IS NULL AND - ${AttachmentTable.QUOTE} = 0 AND - ${AttachmentTable.STICKER_PACK_ID} IS NULL AND - ${MessageTable.TABLE_NAME}.${MessageTable.FROM_RECIPIENT_ID} > 0 AND + ${MessageTable.LATEST_REVISION_ID} IS NULL AND + ${AttachmentTable.QUOTE} = 0 AND + ${AttachmentTable.STICKER_PACK_ID} IS NULL AND + ${MessageTable.TABLE_NAME}.${MessageTable.FROM_RECIPIENT_ID} > 0 AND $THREAD_RECIPIENT_ID > 0 """ @@ -179,7 +181,8 @@ class MediaTable internal constructor(context: Context?, databaseHelper: SignalD ${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} + ${MessageTable.TABLE_NAME}.${MessageTable.LINK_PREVIEWS}, + ${MessageTable.TABLE_NAME}.${MessageTable.ID} as $MEDIA_MESSAGE_ID FROM ${MessageTable.TABLE_NAME} LEFT JOIN ${AttachmentTable.TABLE_NAME} ON ${AttachmentTable.TABLE_NAME}.${AttachmentTable.MESSAGE_ID} = ${MessageTable.TABLE_NAME}.${MessageTable.ID} @@ -344,6 +347,7 @@ class MediaTable internal constructor(context: Context?, databaseHelper: SignalD val recipientId: RecipientId, val threadRecipientId: RecipientId, val threadId: Long, + val messageId: Long, val date: Long, val isOutgoing: Boolean, val linkPreviewJson: String? = null @@ -363,6 +367,7 @@ class MediaTable internal constructor(context: Context?, databaseHelper: SignalD recipientId = RecipientId.from(cursor.requireLong(MessageTable.FROM_RECIPIENT_ID)), threadId = cursor.requireLong(MessageTable.THREAD_ID), threadRecipientId = RecipientId.from(cursor.requireLong(THREAD_RECIPIENT_ID)), + messageId = cursor.requireLong(MEDIA_MESSAGE_ID), date = if (MessageTypes.isPushType(cursor.requireLong(MessageTable.TYPE))) { cursor.requireLong(MessageTable.DATE_SENT) } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/GroupedThreadMediaLoader.java b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/GroupedThreadMediaLoader.java index b8789c0ba3..a8373925d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/GroupedThreadMediaLoader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/GroupedThreadMediaLoader.java @@ -185,6 +185,7 @@ public final class GroupedThreadMediaLoader extends AsyncTaskLoader deletedMessageRecords = new HashSet<>(records.length); for (MediaTable.MediaRecord record : records) { - MessageRecord deleted = AttachmentUtil.deleteAttachment(record.getAttachment()); - if (deleted != null) { - deletedMessageRecords.add(deleted); + if (record.getAttachment() != null) { + MessageRecord deleted = AttachmentUtil.deleteAttachment(record.getAttachment()); + if (deleted != null) { + deletedMessageRecords.add(deleted); + } + } else { + MessageRecord deleted = SignalDatabase.messages().getMessageRecordOrNull(record.getMessageId()); + SignalDatabase.messages().deleteMessage(record.getMessageId()); + if (deleted != null) { + deletedMessageRecords.add(deleted); + } } } 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 1d5dfeb4d6..ef0ec32ed0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaGalleryAllAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaGalleryAllAdapter.java @@ -43,7 +43,7 @@ import com.codewaves.stickyheadergrid.StickyHeaderGridAdapter; import org.signal.core.util.ByteSize; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.attachments.AttachmentId; + import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.components.AudioView; import org.thoughtcrime.securesms.components.ThumbnailView; @@ -57,7 +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; @@ -79,12 +79,12 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { private static final long SELECTION_ANIMATION_DURATION = TimeUnit.MILLISECONDS.toMillis(150); - private final Context context; - private final boolean showThread; - private final RequestManager requestManager; - private final ItemClickListener itemClickListener; - private final Map selected = new HashMap<>(); - private final AudioItemListener audioItemListener; + private final Context context; + private final boolean showThread; + private final RequestManager requestManager; + private final ItemClickListener itemClickListener; + private final Map selected = new HashMap<>(); + private final AudioItemListener audioItemListener; private GroupedThreadMedia media; private boolean showFileSizes; @@ -221,12 +221,10 @@ 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); + MediaSelectionKey key = MediaSelectionKey.from(mediaRecord); + MediaTable.MediaRecord removed = selected.remove(key); if (removed == null) { - selected.put(attachmentId, mediaRecord); + selected.put(key, mediaRecord); } notifyItemRangeChanged(0, getItemCount(), PAYLOAD_SELECTED); @@ -237,9 +235,8 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { } public long getSelectedMediaTotalFileSize() { - //noinspection ConstantConditions attacment cannot be null if selected return Stream.of(selected.values()) - .collect(Collectors.summingLong(a -> a.getAttachment().size)); + .collect(Collectors.summingLong(a -> a.getAttachment() != null ? a.getAttachment().size : 0)); } @NonNull @@ -258,9 +255,7 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { int sectionItemCount = media.getSectionItemCount(section); for (int item = 0; item < sectionItemCount; item++) { MediaRecord mediaRecord = media.get(section, item); - if (mediaRecord.getAttachment() != null) { - selected.put(mediaRecord.getAttachment().attachmentId, mediaRecord); - } + selected.put(MediaSelectionKey.from(mediaRecord), mediaRecord); } } this.notifyItemRangeChanged(0, getItemCount(), PAYLOAD_SELECTED); @@ -274,6 +269,7 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { this.detailView = detailView; } + class SelectableViewHolder extends ItemViewHolder { protected final View selectedIndicator; @@ -304,7 +300,7 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { } protected boolean isSelected() { - return mediaRecord.getAttachment() != null && selected.containsKey(mediaRecord.getAttachment().attachmentId); + return selected.containsKey(MediaSelectionKey.from(mediaRecord)); } protected void updateSelectedView() { @@ -707,16 +703,9 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { thumbnailView.setVisibility(View.GONE); } + thumbnailView.setOnClickListener(view -> itemClickListener.onMediaClicked(getTransitionAnchor(), mediaRecord)); 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); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java index 1af5c36d48..4f9a874e26 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java @@ -52,10 +52,14 @@ import org.thoughtcrime.securesms.mms.PartAuthority; import org.signal.core.ui.permissions.Permissions; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.util.BottomOffsetDecoration; +import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.OffloadedMediaDialogUtil; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.ViewUtil; +import org.json.JSONArray; +import org.json.JSONException; + import java.util.Arrays; import java.util.Collection; import java.util.Objects; @@ -292,12 +296,20 @@ public final class MediaOverviewPageFragment extends LoggingFragment } private void handleMediaPreviewClick(@NonNull View view, @NonNull MediaTable.MediaRecord mediaRecord) { - if (mediaRecord.getAttachment().getDisplayUri() == null) { + Context context = getContext(); + if (context == null) { return; } - Context context = getContext(); - if (context == null) { + if (mediaRecord.getLinkPreviewJson() != null) { + String url = parseLinkUrl(mediaRecord.getLinkPreviewJson()); + if (url != null && !url.isEmpty()) { + CommunicationActions.openBrowserLink(context, url); + } + return; + } + + if (mediaRecord.getAttachment() == null || mediaRecord.getAttachment().getDisplayUri() == null) { return; } @@ -354,6 +366,18 @@ public final class MediaOverviewPageFragment extends LoggingFragment } } + private static @Nullable String parseLinkUrl(@NonNull String linkPreviewJson) { + try { + JSONArray json = new JSONArray(linkPreviewJson); + if (json.length() > 0) { + return json.getJSONObject(0).optString("url", ""); + } + } catch (JSONException e) { + // ignore + } + return null; + } + @Override public void onMediaLongClicked(MediaTable.MediaRecord mediaRecord) { if (actionMode == null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaSelectionKey.kt b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaSelectionKey.kt new file mode 100644 index 0000000000..e790e2715c --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaSelectionKey.kt @@ -0,0 +1,21 @@ +package org.thoughtcrime.securesms.mediaoverview + +import org.thoughtcrime.securesms.attachments.AttachmentId +import org.thoughtcrime.securesms.database.MediaTable.MediaRecord + +sealed class MediaSelectionKey { + data class Attachment(val attachmentId: AttachmentId) : MediaSelectionKey() + data class Message(val messageId: Long) : MediaSelectionKey() + + companion object { + @JvmStatic + fun from(mediaRecord: MediaRecord): MediaSelectionKey { + val attachment = mediaRecord.attachment + return if (attachment != null) { + Attachment(attachment.attachmentId) + } else { + Message(mediaRecord.messageId) + } + } + } +}