diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4b08467b7..b68621ffb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -495,10 +495,12 @@ android:windowSoftInputMode="stateHidden" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> - - - diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java deleted file mode 100644 index a1d6a2289..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ /dev/null @@ -1,869 +0,0 @@ -/* - * Copyright (C) 2014 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatDelegate; -import androidx.core.app.ShareCompat; -import androidx.core.util.Pair; -import androidx.core.view.ViewCompat; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentStatePagerAdapter; -import androidx.lifecycle.ViewModelProvider; -import androidx.loader.app.LoaderManager; -import androidx.loader.content.Loader; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.viewpager.widget.ViewPager; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.animation.DepthPageTransformer; -import org.thoughtcrime.securesms.attachments.DatabaseAttachment; -import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener; -import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController; -import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner; -import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment; -import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs; -import org.thoughtcrime.securesms.database.MediaDatabase; -import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord; -import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader; -import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity; -import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory; -import org.thoughtcrime.securesms.mediapreview.MediaPreviewFragment; -import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel; -import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter; -import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.mms.PartAuthority; -import org.thoughtcrime.securesms.permissions.Permissions; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.util.AttachmentUtil; -import org.thoughtcrime.securesms.util.DateUtils; -import org.thoughtcrime.securesms.util.FullscreenHelper; -import org.thoughtcrime.securesms.util.MediaUtil; -import org.thoughtcrime.securesms.util.SaveAttachmentTask; -import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment; -import org.thoughtcrime.securesms.util.StorageUtil; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; - -/** - * Activity for displaying media attachments in-app - */ -public final class MediaPreviewActivity extends PassphraseRequiredActivity - implements LoaderManager.LoaderCallbacks>, - MediaRailAdapter.RailItemListener, - MediaPreviewFragment.Events, - VoiceNoteMediaControllerOwner -{ - - private final static String TAG = Log.tag(MediaPreviewActivity.class); - - private ViewPager mediaPager; - private View detailsContainer; - private TextView caption; - private View captionContainer; - private RecyclerView albumRail; - private MediaRailAdapter albumRailAdapter; - private ViewGroup playbackControlsContainer; - private Uri initialMediaUri; - private String initialMediaType; - private long initialMediaSize; - private String initialCaption; - private boolean initialMediaIsVideoGif; - private boolean leftIsRecent; - private MediaPreviewViewModel viewModel; - private ViewPagerListener viewPagerListener; - - private int restartItem = -1; - private long threadId = MediaIntentFactory.NOT_IN_A_THREAD; - private boolean cameFromAllMedia; - private boolean showThread; - private MediaDatabase.Sorting sorting; - private FullscreenHelper fullscreenHelper; - - private VoiceNoteMediaController voiceNoteMediaController; - - private @Nullable Cursor cursor = null; - - public static @NonNull Intent intentFromMediaRecord(@NonNull Context context, - @NonNull MediaRecord mediaRecord, - boolean leftIsRecent) - { - DatabaseAttachment attachment = Objects.requireNonNull(mediaRecord.getAttachment()); - Intent intent = new Intent(context, MediaPreviewActivity.class); - intent.putExtra(MediaIntentFactory.THREAD_ID_EXTRA, mediaRecord.getThreadId()); - intent.putExtra(MediaIntentFactory.DATE_EXTRA, mediaRecord.getDate()); - intent.putExtra(MediaIntentFactory.SIZE_EXTRA, attachment.getSize()); - intent.putExtra(MediaIntentFactory.CAPTION_EXTRA, attachment.getCaption()); - intent.putExtra(MediaIntentFactory.LEFT_IS_RECENT_EXTRA, leftIsRecent); - intent.putExtra(MediaIntentFactory.IS_VIDEO_GIF, attachment.isVideoGif()); - intent.setDataAndType(attachment.getUri(), mediaRecord.getContentType()); - return intent; - } - - @Override - protected void attachBaseContext(@NonNull Context newBase) { - getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES); - super.attachBaseContext(newBase); - } - - @SuppressWarnings("ConstantConditions") - @Override - protected void onCreate(Bundle bundle, boolean ready) { - this.setTheme(R.style.TextSecure_MediaPreview); - setContentView(R.layout.media_preview_activity); - - setSupportActionBar(findViewById(R.id.toolbar)); - - voiceNoteMediaController = new VoiceNoteMediaController(this); - viewModel = new ViewModelProvider(this).get(MediaPreviewViewModel.class); - - fullscreenHelper = new FullscreenHelper(this); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - initializeViews(); - initializeResources(); - initializeObservers(); - } - - @SuppressLint("MissingSuperCall") - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults); - } - - @Override - public void onRailItemClicked(int distanceFromActive) { - mediaPager.setCurrentItem(mediaPager.getCurrentItem() + distanceFromActive); - } - - @Override - public void onRailItemDeleteClicked(int distanceFromActive) { - throw new UnsupportedOperationException("Callback unsupported."); - } - - @SuppressWarnings("ConstantConditions") - private void initializeActionBar() { - MediaItem mediaItem = getCurrentMediaItem(); - - if (mediaItem != null) { - getSupportActionBar().setTitle(getTitleText(mediaItem)); - getSupportActionBar().setSubtitle(getSubTitleText(mediaItem)); - } - } - - private @NonNull String getTitleText(@NonNull MediaItem mediaItem) { - String from; - if (mediaItem.outgoing) from = getString(R.string.MediaPreviewActivity_you); - else if (mediaItem.recipient != null) from = mediaItem.recipient.getDisplayName(this); - else from = ""; - - if (showThread) { - String titleText = null; - Recipient threadRecipient = mediaItem.threadRecipient; - - if (threadRecipient != null) { - if (mediaItem.outgoing) { - if (threadRecipient.isSelf()) { - titleText = getString(R.string.note_to_self); - } else { - titleText = getString(R.string.MediaPreviewActivity_you_to_s, threadRecipient.getDisplayName(this)); - } - } else { - if (threadRecipient.isGroup()) { - titleText = getString(R.string.MediaPreviewActivity_s_to_s, from, threadRecipient.getDisplayName(this)); - } else { - titleText = getString(R.string.MediaPreviewActivity_s_to_you, from); - } - } - } - return titleText != null ? titleText : from; - } else { - return from; - } - } - - private @NonNull String getSubTitleText(@NonNull MediaItem mediaItem) { - if (mediaItem.date > 0) { - return DateUtils.getExtendedRelativeTimeSpanString(this, Locale.getDefault(), mediaItem.date); - } else { - return getString(R.string.MediaPreviewActivity_draft); - } - } - - @Override - public void onResume() { - super.onResume(); - - initializeMedia(); - } - - @Override - public void onPause() { - super.onPause(); - restartItem = cleanupMedia(); - } - - @Override - protected void onDestroy() { - if (cursor != null) { - cursor.close(); - cursor = null; - } - super.onDestroy(); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - setIntent(intent); - initializeResources(); - } - - private void initializeViews() { - mediaPager = findViewById(R.id.media_pager); - mediaPager.setOffscreenPageLimit(1); - mediaPager.setPageTransformer(true, new DepthPageTransformer()); - - viewPagerListener = new ViewPagerListener(); - mediaPager.addOnPageChangeListener(viewPagerListener); - - albumRail = findViewById(R.id.media_preview_album_rail); - albumRailAdapter = new MediaRailAdapter(GlideApp.with(this), this, false); - - albumRail.setItemAnimator(null); // Or can crash when set to INVISIBLE while animating by FullscreenHelper https://issuetracker.google.com/issues/148720682 - albumRail.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)); - albumRail.setAdapter(albumRailAdapter); - - detailsContainer = findViewById(R.id.media_preview_details_container); - caption = findViewById(R.id.media_preview_caption); - captionContainer = findViewById(R.id.media_preview_caption_container); - playbackControlsContainer = findViewById(R.id.media_preview_playback_controls_container); - - View toolbarLayout = findViewById(R.id.toolbar_layout); - - anchorMarginsToBottomInsets(detailsContainer); - - fullscreenHelper.configureToolbarLayout(findViewById(R.id.toolbar_cutout_spacer), findViewById(R.id.toolbar)); - - fullscreenHelper.showAndHideWithSystemUI(getWindow(), detailsContainer, toolbarLayout); - } - - private void initializeResources() { - Intent intent = getIntent(); - - threadId = intent.getLongExtra(MediaIntentFactory.THREAD_ID_EXTRA, MediaIntentFactory.NOT_IN_A_THREAD); - cameFromAllMedia = intent.getBooleanExtra(MediaIntentFactory.HIDE_ALL_MEDIA_EXTRA, false); - showThread = intent.getBooleanExtra(MediaIntentFactory.SHOW_THREAD_EXTRA, false); - sorting = MediaDatabase.Sorting.values()[intent.getIntExtra(MediaIntentFactory.SORTING_EXTRA, 0)]; - - initialMediaUri = intent.getData(); - initialMediaType = intent.getType(); - initialMediaSize = intent.getLongExtra(MediaIntentFactory.SIZE_EXTRA, 0); - initialCaption = intent.getStringExtra(MediaIntentFactory.CAPTION_EXTRA); - leftIsRecent = intent.getBooleanExtra(MediaIntentFactory.LEFT_IS_RECENT_EXTRA, false); - initialMediaIsVideoGif = intent.getBooleanExtra(MediaIntentFactory.IS_VIDEO_GIF, false); - restartItem = -1; - } - - private void initializeObservers() { - viewModel.getPreviewData().observe(this, previewData -> { - if (previewData == null || mediaPager == null || mediaPager.getAdapter() == null) { - return; - } - - if (!((MediaItemAdapter) mediaPager.getAdapter()).hasFragmentFor(mediaPager.getCurrentItem())) { - Log.d(TAG, "MediaItemAdapter wasn't ready. Posting again..."); - viewModel.resubmitPreviewData(); - } - - View playbackControls = ((MediaItemAdapter) mediaPager.getAdapter()).getPlaybackControls(mediaPager.getCurrentItem()); - - if (previewData.getAlbumThumbnails().isEmpty() && previewData.getCaption() == null && playbackControls == null) { - detailsContainer.setVisibility(View.GONE); - } else { - detailsContainer.setVisibility(View.VISIBLE); - } - - albumRail.setVisibility(previewData.getAlbumThumbnails().isEmpty() ? View.GONE : View.VISIBLE); - albumRailAdapter.setMedia(previewData.getAlbumThumbnails(), previewData.getActivePosition()); - albumRail.smoothScrollToPosition(previewData.getActivePosition()); - - captionContainer.setVisibility(previewData.getCaption() == null ? View.GONE : View.VISIBLE); - caption.setText(previewData.getCaption()); - - if (playbackControls != null) { - ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - playbackControls.setLayoutParams(params); - - playbackControlsContainer.removeAllViews(); - playbackControlsContainer.addView(playbackControls); - } else { - playbackControlsContainer.removeAllViews(); - } - }); - } - - private void initializeMedia() { - if (!isContentTypeSupported(initialMediaType)) { - Log.w(TAG, "Unsupported media type sent to MediaPreviewActivity, finishing."); - Toast.makeText(getApplicationContext(), R.string.MediaPreviewActivity_unssuported_media_type, Toast.LENGTH_LONG).show(); - finish(); - } - - Log.i(TAG, "Loading Part URI: " + initialMediaUri); - - if (isMediaInDb()) { - LoaderManager.getInstance(this).restartLoader(0, null, this); - } else { - mediaPager.setAdapter(new SingleItemPagerAdapter(getSupportFragmentManager(), initialMediaUri, initialMediaType, initialMediaSize, initialMediaIsVideoGif)); - - if (initialCaption != null) { - detailsContainer.setVisibility(View.VISIBLE); - captionContainer.setVisibility(View.VISIBLE); - caption.setText(initialCaption); - } - } - } - - private int cleanupMedia() { - int restartItem = mediaPager.getCurrentItem(); - - mediaPager.removeAllViews(); - mediaPager.setAdapter(null); - viewModel.setCursor(this, null, leftIsRecent); - - return restartItem; - } - - private void showOverview() { - startActivity(MediaOverviewActivity.forThread(this, threadId)); - } - - private void forward() { - MediaItem mediaItem = getCurrentMediaItem(); - - if (mediaItem != null) { - MultiselectForwardFragmentArgs.create( - this, - threadId, - mediaItem.uri, - mediaItem.type, - args -> MultiselectForwardFragment.showBottomSheet(getSupportFragmentManager(), args) - ); - } - } - - private void share() { - MediaItem mediaItem = getCurrentMediaItem(); - - if (mediaItem != null) { - Uri publicUri = PartAuthority.getAttachmentPublicUri(mediaItem.uri); - String mimeType = Intent.normalizeMimeType(mediaItem.type); - Intent shareIntent = ShareCompat.IntentBuilder.from(this) - .setStream(publicUri) - .setType(mimeType) - .createChooserIntent() - .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - - try { - startActivity(shareIntent); - } catch (ActivityNotFoundException e) { - Log.w(TAG, "No activity existed to share the media.", e); - Toast.makeText(this, R.string.MediaPreviewActivity_cant_find_an_app_able_to_share_this_media, Toast.LENGTH_LONG).show(); - } - } - } - - @SuppressWarnings("CodeBlock2Expr") - @SuppressLint("InlinedApi") - private void saveToDisk() { - MediaItem mediaItem = getCurrentMediaItem(); - - if (mediaItem != null) { - SaveAttachmentTask.showWarningDialog(this, (dialogInterface, i) -> { - if (StorageUtil.canWriteToMediaStore()) { - performSavetoDisk(mediaItem); - return; - } - - Permissions.with(this) - .request(Manifest.permission.WRITE_EXTERNAL_STORAGE) - .ifNecessary() - .withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied)) - .onAnyDenied(() -> Toast.makeText(this, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show()) - .onAllGranted(() -> { - performSavetoDisk(mediaItem); - }) - .execute(); - }); - } - } - - private void performSavetoDisk(@NonNull MediaItem mediaItem) { - SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this); - long saveDate = (mediaItem.date > 0) ? mediaItem.date : System.currentTimeMillis(); - saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Attachment(mediaItem.uri, mediaItem.type, saveDate, null)); - } - - @SuppressLint("StaticFieldLeak") - private void deleteMedia() { - MediaItem mediaItem = getCurrentMediaItem(); - if (mediaItem == null || mediaItem.attachment == null) { - return; - } - - AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); - builder.setIcon(R.drawable.ic_warning); - builder.setTitle(R.string.MediaPreviewActivity_media_delete_confirmation_title); - builder.setMessage(R.string.MediaPreviewActivity_media_delete_confirmation_message); - builder.setCancelable(true); - - builder.setPositiveButton(R.string.delete, (dialogInterface, which) -> { - new AsyncTask() { - @Override - protected Void doInBackground(Void... voids) { - AttachmentUtil.deleteAttachment(MediaPreviewActivity.this.getApplicationContext(), - mediaItem.attachment); - return null; - } - }.execute(); - - finish(); - }); - builder.setNegativeButton(android.R.string.cancel, null); - builder.show(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.clear(); - MenuInflater inflater = this.getMenuInflater(); - inflater.inflate(R.menu.media_preview, menu); - - super.onCreateOptionsMenu(menu); - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - if (!isMediaInDb()) { - menu.findItem(R.id.delete).setVisible(false); - } - super.onPrepareOptionsMenu(menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - super.onOptionsItemSelected(item); - - int itemId = item.getItemId(); - - if (itemId == R.id.save) { saveToDisk(); return true; } - if (itemId == R.id.delete) { deleteMedia(); return true; } - if (itemId == android.R.id.home) { finish(); return true; } - - return false; - } - - private boolean isMediaInDb() { - return threadId != MediaIntentFactory.NOT_IN_A_THREAD; - } - - private @Nullable MediaItem getCurrentMediaItem() { - MediaItemAdapter adapter = (MediaItemAdapter)mediaPager.getAdapter(); - - if (adapter != null) { - return adapter.getMediaItemFor(mediaPager.getCurrentItem()); - } else { - return null; - } - } - - public static boolean isContentTypeSupported(final String contentType) { - return MediaUtil.isImageType(contentType) || MediaUtil.isVideoType(contentType); - } - - @Override - public @NonNull Loader> onCreateLoader(int id, Bundle args) { - return new PagingMediaLoader(this, threadId, initialMediaUri, leftIsRecent, sorting); - } - - @Override - public void onLoadFinished(@NonNull Loader> loader, @Nullable Pair data) { - if (data != null) { - if (data.first == cursor) { - return; - } - - if (cursor != null) { - cursor.close(); - } - cursor = Objects.requireNonNull(data.first); - - viewModel.setCursor(this, cursor, leftIsRecent); - - int mediaPosition = Objects.requireNonNull(data.second); - - CursorPagerAdapter oldAdapter = (CursorPagerAdapter) mediaPager.getAdapter(); - if (oldAdapter == null) { - CursorPagerAdapter adapter = new CursorPagerAdapter(getSupportFragmentManager(), this, cursor, mediaPosition, leftIsRecent); - mediaPager.setAdapter(adapter); - adapter.setActive(true); - } else { - oldAdapter.setCursor(cursor, mediaPosition); - oldAdapter.setActive(true); - } - - if (oldAdapter == null || restartItem >= 0) { - int item = restartItem >= 0 ? restartItem : mediaPosition; - mediaPager.setCurrentItem(item); - - if (item == 0) { - viewPagerListener.onPageSelected(0); - } - } - } else { - onMediaNotAvailable(); - } - } - - @Override - public void onLoaderReset(@NonNull Loader> loader) { - - } - - @Override - public boolean singleTapOnMedia() { - fullscreenHelper.toggleUiVisibility(); - return true; - } - - @Override - public void onMediaNotAvailable() { - Toast.makeText(this, R.string.MediaPreviewActivity_media_no_longer_available, Toast.LENGTH_LONG).show(); - finish(); - } - - @Override - public void onMediaReady() { - } - - @Override - public @NonNull VoiceNoteMediaController getVoiceNoteMediaController() { - return voiceNoteMediaController; - } - - private class ViewPagerListener extends ExtendedOnPageChangedListener { - - @Override - public void onPageSelected(int position) { - super.onPageSelected(position); - - MediaItemAdapter adapter = (MediaItemAdapter)mediaPager.getAdapter(); - - if (adapter != null) { - MediaItem item = adapter.getMediaItemFor(position); - if (item != null && item.recipient != null) { - item.recipient.live().observe(MediaPreviewActivity.this, r -> initializeActionBar()); - } - - viewModel.setActiveAlbumRailItem(MediaPreviewActivity.this, position); - initializeActionBar(); - } - } - - - @Override - public void onPageUnselected(int position) { - MediaItemAdapter adapter = (MediaItemAdapter)mediaPager.getAdapter(); - - if (adapter != null) { - MediaItem item = adapter.getMediaItemFor(position); - if (item != null && item.recipient != null) { - item.recipient.live().removeObservers(MediaPreviewActivity.this); - } - - adapter.pause(position); - } - } - } - - private static class SingleItemPagerAdapter extends FragmentStatePagerAdapter implements MediaItemAdapter { - - private final Uri uri; - private final String mediaType; - private final long size; - private final boolean isVideoGif; - - private MediaPreviewFragment mediaPreviewFragment; - - SingleItemPagerAdapter(@NonNull FragmentManager fragmentManager, - @NonNull Uri uri, - @NonNull String mediaType, - long size, - boolean isVideoGif) - { - super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); - this.uri = uri; - this.mediaType = mediaType; - this.size = size; - this.isVideoGif = isVideoGif; - } - - @Override - public int getCount() { - return 1; - } - - @NonNull - @Override - public Fragment getItem(int position) { - mediaPreviewFragment = MediaPreviewFragment.newInstance(uri, mediaType, size, true, isVideoGif); - return mediaPreviewFragment; - } - - @Override - public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { - if (mediaPreviewFragment != null) { - mediaPreviewFragment.cleanUp(); - mediaPreviewFragment = null; - } - } - - @Override - public @Nullable MediaItem getMediaItemFor(int position) { - return new MediaItem(null, null, null, uri, mediaType, -1, true); - } - - @Override - public void pause(int position) { - if (mediaPreviewFragment != null) { - mediaPreviewFragment.pause(); - } - } - - @Override - public @Nullable View getPlaybackControls(int position) { - if (mediaPreviewFragment != null) { - return mediaPreviewFragment.getBottomBarControls(); - } - return null; - } - - @Override - public boolean hasFragmentFor(int position) { - return mediaPreviewFragment != null; - } - } - - private static void anchorMarginsToBottomInsets(@NonNull View viewToAnchor) { - ViewCompat.setOnApplyWindowInsetsListener(viewToAnchor, (view, insets) -> { - ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); - - layoutParams.setMargins(insets.getSystemWindowInsetLeft(), - layoutParams.topMargin, - insets.getSystemWindowInsetRight(), - insets.getSystemWindowInsetBottom()); - - view.setLayoutParams(layoutParams); - - return insets; - }); - } - - private static class CursorPagerAdapter extends FragmentStatePagerAdapter implements MediaItemAdapter { - - @SuppressLint("UseSparseArrays") - private final Map mediaFragments = new HashMap<>(); - - private final Context context; - private final boolean leftIsRecent; - - private boolean active; - private Cursor cursor; - private int autoPlayPosition; - - CursorPagerAdapter(@NonNull FragmentManager fragmentManager, - @NonNull Context context, - @NonNull Cursor cursor, - int autoPlayPosition, - boolean leftIsRecent) - { - super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); - this.context = context.getApplicationContext(); - this.cursor = cursor; - this.autoPlayPosition = autoPlayPosition; - this.leftIsRecent = leftIsRecent; - } - - public void setActive(boolean active) { - this.active = active; - notifyDataSetChanged(); - } - - public void setCursor(@NonNull Cursor cursor, int autoPlayPosition) { - this.cursor = cursor; - this.autoPlayPosition = autoPlayPosition; - } - - @Override - public int getCount() { - if (!active) return 0; - else return cursor.getCount(); - } - - @NonNull - @Override - public Fragment getItem(int position) { - boolean autoPlay = autoPlayPosition == position; - int cursorPosition = getCursorPosition(position); - - autoPlayPosition = -1; - - cursor.moveToPosition(cursorPosition); - - MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(cursor); - DatabaseAttachment attachment = Objects.requireNonNull(mediaRecord.getAttachment()); - MediaPreviewFragment fragment = MediaPreviewFragment.newInstance(attachment, autoPlay); - - mediaFragments.put(position, fragment); - - return fragment; - } - - @Override - public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { - MediaPreviewFragment removed = mediaFragments.remove(position); - - if (removed != null) { - removed.cleanUp(); - } - - super.destroyItem(container, position, object); - } - - public @Nullable MediaItem getMediaItemFor(int position) { - int cursorPosition = getCursorPosition(position); - - if (cursor.isClosed() || cursorPosition < 0) { - Log.w(TAG, "Invalid cursor state! Closed: " + cursor.isClosed() + " Position: " + cursorPosition); - return null; - } - - cursor.moveToPosition(cursorPosition); - - MediaRecord mediaRecord = MediaRecord.from(cursor); - DatabaseAttachment attachment = Objects.requireNonNull(mediaRecord.getAttachment()); - RecipientId recipientId = mediaRecord.getRecipientId(); - RecipientId threadRecipientId = mediaRecord.getThreadRecipientId(); - - return new MediaItem(Recipient.live(recipientId).get(), - Recipient.live(threadRecipientId).get(), - attachment, - Objects.requireNonNull(attachment.getUri()), - mediaRecord.getContentType(), - mediaRecord.getDate(), - mediaRecord.isOutgoing()); - } - - @Override - public void pause(int position) { - MediaPreviewFragment mediaView = mediaFragments.get(position); - if (mediaView != null) mediaView.pause(); - } - - @Override - public @Nullable View getPlaybackControls(int position) { - MediaPreviewFragment mediaView = mediaFragments.get(position); - if (mediaView != null) return mediaView.getBottomBarControls(); - return null; - } - - @Override - public boolean hasFragmentFor(int position) { - return mediaFragments.containsKey(position); - } - - private int getCursorPosition(int position) { - if (leftIsRecent) return position; - else return cursor.getCount() - 1 - position; - } - } - - private static class MediaItem { - private final @Nullable Recipient recipient; - private final @Nullable Recipient threadRecipient; - private final @Nullable DatabaseAttachment attachment; - private final @NonNull Uri uri; - private final @NonNull String type; - private final long date; - private final boolean outgoing; - - private MediaItem(@Nullable Recipient recipient, - @Nullable Recipient threadRecipient, - @Nullable DatabaseAttachment attachment, - @NonNull Uri uri, - @NonNull String type, - long date, - boolean outgoing) - { - this.recipient = recipient; - this.threadRecipient = threadRecipient; - this.attachment = attachment; - this.uri = uri; - this.type = type; - this.date = date; - this.outgoing = outgoing; - } - } - - interface MediaItemAdapter { - @Nullable MediaItem getMediaItemFor(int position); - void pause(int position); - @Nullable View getPlaybackControls(int position); - boolean hasFragmentFor(int position); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt index 924240e1e..14e71ef18 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt @@ -28,7 +28,6 @@ import org.signal.core.util.DimensionUnit import org.thoughtcrime.securesms.AvatarPreviewActivity import org.thoughtcrime.securesms.BlockUnblockDialog import org.thoughtcrime.securesms.InviteActivity -import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.MuteDialog import org.thoughtcrime.securesms.PushContactSelectionActivity import org.thoughtcrime.securesms.R @@ -69,6 +68,7 @@ import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupInviteSentD import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupsLearnMoreBottomSheetDialogFragment import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationInitiationBottomSheetDialogFragment import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity +import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientExporter @@ -513,7 +513,7 @@ class ConversationSettingsFragment : DSLSettingsFragment( mediaIds = state.sharedMediaIds, onMediaRecordClick = { mediaRecord, isLtr -> startActivityForResult( - MediaPreviewActivity.intentFromMediaRecord(requireContext(), mediaRecord, isLtr), + MediaIntentFactory.intentFromMediaRecord(requireContext(), mediaRecord, isLtr), REQUEST_CODE_RETURN_FROM_MEDIA ) } 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 df9218c4d..f7f279941 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -71,7 +71,6 @@ import org.signal.core.util.DimensionUnit; import org.signal.core.util.StringUtil; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.BindableConversationItem; -import org.thoughtcrime.securesms.MediaPreviewActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.badges.BadgeImageView; @@ -114,6 +113,7 @@ import org.thoughtcrime.securesms.jobs.SmsSendJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory; +import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Fragment; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.ImageSlide; import org.thoughtcrime.securesms.mms.PartAuthority; @@ -2321,7 +2321,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo performClick(); } else if (!canPlayContent && mediaItem != null && eventListener != null) { eventListener.onPlayInlineContent(conversationMessage); - } else if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) { + } else if (MediaPreviewV2Fragment.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) { MediaIntentFactory.MediaPreviewArgs args = new MediaIntentFactory.MediaPreviewArgs( messageRecord.getThreadId(), messageRecord.getTimestamp(), @@ -2333,7 +2333,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo false, false, false, - MediaDatabase.Sorting.Newest.ordinal(), + MediaDatabase.Sorting.Newest, slide.isVideoGif()); context.startActivity(MediaIntentFactory.create(context, args)); } else if (slide.getUri() != null) { 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 e9c07a734..39e7e30f0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java @@ -248,7 +248,7 @@ public final class MediaOverviewPageFragment extends Fragment true, threadId == MediaDatabase.ALL_THREADS, true, - sorting.ordinal(), + sorting, attachment.isVideoGif()); context.startActivity(MediaIntentFactory.create(context, args)); } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaIntentFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaIntentFactory.kt index 2b53532b1..5593a45ab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaIntentFactory.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaIntentFactory.kt @@ -6,6 +6,9 @@ import android.net.Uri import android.os.Bundle import android.os.Parcelable import kotlinx.parcelize.Parcelize +import org.thoughtcrime.securesms.attachments.DatabaseAttachment +import org.thoughtcrime.securesms.database.MediaDatabase +import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord object MediaIntentFactory { private const val ARGS_KEY = "args" @@ -34,7 +37,7 @@ object MediaIntentFactory { val hideAllMedia: Boolean = false, val showThread: Boolean = false, val allMediaInRail: Boolean = false, - val sorting: Int, + val sorting: MediaDatabase.Sorting, val isVideoGif: Boolean ) : Parcelable @@ -45,4 +48,26 @@ object MediaIntentFactory { fun create(context: Context, args: MediaPreviewArgs): Intent { return Intent(context, MediaPreviewV2Activity::class.java).putExtra(ARGS_KEY, args) } + + fun intentFromMediaRecord( + context: Context, + mediaRecord: MediaRecord, + leftIsRecent: Boolean + ): Intent { + val attachment: DatabaseAttachment = mediaRecord.attachment!! + return create( + context, + MediaPreviewArgs( + mediaRecord.threadId, + mediaRecord.date, + attachment.uri!!, + attachment.contentType, + attachment.size, + attachment.caption, + leftIsRecent, + sorting = MediaDatabase.Sorting.Newest, + isVideoGif = attachment.isVideoGif + ) + ) + } } 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 04fb32561..a91ccc2e1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt @@ -92,7 +92,7 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med }.show() } viewModel.initialize(args.showThread, args.allMediaInRail, args.leftIsRecent) - val sorting = MediaDatabase.Sorting.deserialize(args.sorting) + val sorting = MediaDatabase.Sorting.deserialize(args.sorting.ordinal) viewModel.fetchAttachments(PartAuthority.requireAttachmentId(args.initialMediaUri), args.threadId, sorting) } @@ -442,5 +442,10 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med companion object { const val ARGS_KEY: String = "args" + + @JvmStatic + fun isContentTypeSupported(contentType: String?): Boolean { + return MediaUtil.isImageType(contentType) || MediaUtil.isVideoType(contentType) + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java index e54cb2179..d6a8239ee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -41,7 +41,6 @@ import androidx.fragment.app.Fragment; import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.MediaPreviewActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.components.AudioView; @@ -56,6 +55,7 @@ import org.thoughtcrime.securesms.database.MediaDatabase; import org.thoughtcrime.securesms.giph.ui.GiphyActivity; import org.thoughtcrime.securesms.maps.PlacePickerActivity; import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory; +import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Fragment; import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity; import org.thoughtcrime.securesms.payments.create.CreatePaymentFragmentArgs; import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity; @@ -470,7 +470,7 @@ public class AttachmentManager { } private void previewImageDraft(final @NonNull Slide slide) { - if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) { + if (MediaPreviewV2Fragment.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) { MediaIntentFactory.MediaPreviewArgs args = new MediaIntentFactory.MediaPreviewArgs( MediaIntentFactory.NOT_IN_A_THREAD, MediaIntentFactory.UNKNOWN_TIMESTAMP, @@ -482,7 +482,7 @@ public class AttachmentManager { false, false, false, - MediaDatabase.Sorting.Newest.ordinal(), + MediaDatabase.Sorting.Newest, slide.isVideoGif()); context.startActivity(MediaIntentFactory.create(context, args)); }