diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java index ffb3c432da..0dfb5a7059 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -579,7 +579,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity } } } else { - mediaNotAvailable(); + onMediaNotAvailable(); } } @@ -595,7 +595,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity } @Override - public void mediaNotAvailable() { + public void onMediaNotAvailable() { Toast.makeText(this, R.string.MediaPreviewActivity_media_no_longer_available, Toast.LENGTH_LONG).show(); finish(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java index 21222545f1..53be5f8c4b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java @@ -93,10 +93,6 @@ public class MediaDatabase extends Database { } public @NonNull Cursor getGalleryMediaForThread(long threadId, @NonNull Sorting sorting) { - return getGalleryMediaForThread(threadId, sorting, false); - } - - public @NonNull Cursor getGalleryMediaForThread(long threadId, @NonNull Sorting sorting, boolean listenToAllThreads) { SQLiteDatabase database = databaseHelper.getSignalReadableDatabase(); String query = sorting.applyToQuery(applyEqualityOperator(threadId, GALLERY_MEDIA_QUERY)); String[] args = {threadId + ""}; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java index 48cf0a39c0..3362dae53f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java @@ -46,7 +46,7 @@ public final class PagingMediaLoader extends AsyncLoader> public @Nullable Pair loadInBackground() { ApplicationDependencies.getDatabaseObserver().registerAttachmentObserver(observer); - Cursor cursor = SignalDatabase.media().getGalleryMediaForThread(threadId, sorting, threadId == MediaDatabase.ALL_THREADS); + Cursor cursor = SignalDatabase.media().getGalleryMediaForThread(threadId, sorting); while (cursor.moveToNext()) { AttachmentId attachmentId = new AttachmentId(cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.ROW_ID)), cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.UNIQUE_ID))); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java index 52dc23ecac..e24b69dec0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java @@ -96,12 +96,12 @@ public abstract class MediaPreviewFragment extends Fragment { SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> SignalDatabase.attachments().hasAttachment(attachmentId), - hasAttachment -> { if (!hasAttachment) events.mediaNotAvailable(); }); + hasAttachment -> { if (!hasAttachment) events.onMediaNotAvailable(); }); } public interface Events { boolean singleTapOnMedia(); - void mediaNotAvailable(); + void onMediaNotAvailable(); void onMediaReady(); default @Nullable VideoControlsDelegate getVideoControlsDelegate() { return null; diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewRepository.kt index 3eacc7b459..2607fe27c4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewRepository.kt @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.mediapreview -import android.net.Uri import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.schedulers.Schedulers @@ -11,7 +10,6 @@ import org.thoughtcrime.securesms.database.AttachmentDatabase import org.thoughtcrime.securesms.database.MediaDatabase import org.thoughtcrime.securesms.database.MediaDatabase.Sorting import org.thoughtcrime.securesms.database.SignalDatabase.Companion.media -import org.thoughtcrime.securesms.mms.PartAuthority /** * Repository for accessing the attachments in the encrypted database. @@ -28,45 +26,43 @@ class MediaPreviewRepository { * @param sorting the ordering of the results * @param limit the maximum quantity of the results */ - fun getAttachments(startingUri: Uri, threadId: Long, sorting: Sorting, limit: Int = 500): Flowable { + fun getAttachments(startingAttachmentId: AttachmentId, threadId: Long, sorting: Sorting, limit: Int = 500): Flowable { return Single.fromCallable { - val cursor = media.getGalleryMediaForThread(threadId, sorting) - - val acc = mutableListOf() - var initialPosition = 0 - var attachmentUri: Uri? = null - while (cursor.moveToNext()) { - val attachmentId = AttachmentId(cursor.requireLong(AttachmentDatabase.ROW_ID), cursor.requireLong(AttachmentDatabase.UNIQUE_ID)) - attachmentUri = PartAuthority.getAttachmentDataUri(attachmentId) - if (attachmentUri == startingUri) { - initialPosition = cursor.position - break - } - } - - if (attachmentUri == startingUri) { - val frontLimit: Int = limit / 2 - if (initialPosition < frontLimit) { - cursor.moveToFirst() - } else { - cursor.move(-frontLimit) - } - for (i in 0..limit) { - val element = MediaDatabase.MediaRecord.from(cursor) - if (element != null) { - acc.add(element) - } - if (!cursor.isLast) { - cursor.moveToNext() - } else { + media.getGalleryMediaForThread(threadId, sorting).use { cursor -> + val mediaRecords = mutableListOf() + var startingRow = -1 + while (cursor.moveToNext()) { + if (startingAttachmentId.rowId == cursor.requireLong(AttachmentDatabase.ROW_ID) && + startingAttachmentId.uniqueId == cursor.requireLong(AttachmentDatabase.UNIQUE_ID) + ) { + startingRow = cursor.position break } } - } else { - Log.e(TAG, "Could not find $startingUri in thread $threadId") + + var itemPosition = -1 + if (startingRow >= 0) { + val frontLimit: Int = limit / 2 + val windowStart = if (startingRow >= frontLimit) startingRow - frontLimit else 0 + + itemPosition = startingRow - windowStart + + cursor.moveToPosition(windowStart) + + for (i in 0..limit) { + val element = MediaDatabase.MediaRecord.from(cursor) + if (element != null) { + mediaRecords.add(element) + } + if (!cursor.moveToNext()) { + break + } + } + } + Result(itemPosition, mediaRecords.toList()) } - Result(initialPosition, acc.toList()) }.subscribeOn(Schedulers.io()).toFlowable() } + data class Result(val initialPosition: Int, val records: List) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt index 470c6eb60c..57a2d23b45 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt @@ -142,10 +142,14 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med } viewModel.setShowThread(args.showThread) val sorting = MediaDatabase.Sorting.values()[args.sorting] - viewModel.fetchAttachments(args.initialMediaUri, args.threadId, sorting) + viewModel.fetchAttachments(PartAuthority.requireAttachmentId(args.initialMediaUri), args.threadId, sorting) } private fun bindCurrentState(currentState: MediaPreviewV2State) { + if (currentState.position == -1 && currentState.mediaRecords.isEmpty()) { + onMediaNotAvailable() + return + } when (currentState.loadState) { MediaPreviewV2State.LoadState.READY -> bindReadyState(currentState) MediaPreviewV2State.LoadState.LOADED -> { @@ -159,7 +163,7 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med private fun bindReadyState(currentState: MediaPreviewV2State) { (binding.mediaPager.adapter as MediaPreviewV2Adapter).updateBackingItems(currentState.mediaRecords.mapNotNull { it.attachment }) if (binding.mediaPager.currentItem != currentState.position) { - binding.mediaPager.currentItem = currentState.position + binding.mediaPager.setCurrentItem(currentState.position, false) } val currentItem: MediaDatabase.MediaRecord = currentState.mediaRecords[currentState.position] binding.toolbar.title = getTitleText(currentItem, currentState.showThread) @@ -295,11 +299,9 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med return true } - override fun mediaNotAvailable() { - Snackbar.make(binding.root, R.string.MediaPreviewActivity_media_no_longer_available, Snackbar.LENGTH_LONG) - .setAction(R.string.MediaPreviewActivity_dismiss_due_to_error) { - requireActivity().finish() - }.show() + override fun onMediaNotAvailable() { + Toast.makeText(requireContext(), R.string.MediaPreviewActivity_media_no_longer_available, Toast.LENGTH_LONG).show() + requireActivity().finish() } override fun onMediaReady() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt index cf34ef1e03..0ab965bc69 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.mediapreview import android.content.Context -import android.net.Uri import androidx.lifecycle.ViewModel import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Flowable @@ -11,6 +10,7 @@ import io.reactivex.rxjava3.functions.Consumer import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.schedulers.Schedulers import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.attachments.AttachmentId import org.thoughtcrime.securesms.attachments.DatabaseAttachment import org.thoughtcrime.securesms.database.MediaDatabase import org.thoughtcrime.securesms.util.AttachmentUtil @@ -24,8 +24,8 @@ class MediaPreviewV2ViewModel : ViewModel() { val state: Flowable = store.stateFlowable.observeOn(AndroidSchedulers.mainThread()) - fun fetchAttachments(startingUri: Uri, threadId: Long, sorting: MediaDatabase.Sorting) { - disposables += store.update(repository.getAttachments(startingUri, threadId, sorting)) { + fun fetchAttachments(startingAttachmentId: AttachmentId, threadId: Long, sorting: MediaDatabase.Sorting) { + disposables += store.update(repository.getAttachments(startingAttachmentId, threadId, sorting)) { result: MediaPreviewRepository.Result, oldState: MediaPreviewV2State -> oldState.copy( position = result.initialPosition, diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java index be180259cf..cd178e0f39 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java @@ -75,7 +75,7 @@ public final class VideoMediaPreviewFragment extends MediaPreviewFragment { @Override public void onError() { - events.mediaNotAvailable(); + events.onMediaNotAvailable(); } });