diff --git a/res/layout/media_preview_image_fragment.xml b/res/layout/media_preview_image_fragment.xml
new file mode 100644
index 0000000000..8ebf575390
--- /dev/null
+++ b/res/layout/media_preview_image_fragment.xml
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/res/layout/media_preview_video_fragment.xml b/res/layout/media_preview_video_fragment.xml
new file mode 100644
index 0000000000..5224e6a71f
--- /dev/null
+++ b/res/layout/media_preview_video_fragment.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/media_view_page.xml b/res/layout/media_view_page.xml
deleted file mode 100644
index 58e1386a8d..0000000000
--- a/res/layout/media_view_page.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/MediaPreviewActivity.java b/src/org/thoughtcrime/securesms/MediaPreviewActivity.java
index 8fcb5d88a1..809f8f3e3a 100644
--- a/src/org/thoughtcrime/securesms/MediaPreviewActivity.java
+++ b/src/org/thoughtcrime/securesms/MediaPreviewActivity.java
@@ -18,70 +18,67 @@ package org.thoughtcrime.securesms;
import android.Manifest;
import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
-import androidx.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
import android.os.Bundle;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.loader.app.LoaderManager;
-import androidx.loader.content.Loader;
-import androidx.core.util.Pair;
-import androidx.viewpager.widget.PagerAdapter;
-import androidx.viewpager.widget.ViewPager;
-import androidx.appcompat.app.AlertDialog;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import android.view.GestureDetector;
-import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
-import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.Window;
import android.view.WindowManager;
-import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.core.util.Pair;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentStatePagerAdapter;
+import androidx.lifecycle.ViewModelProviders;
+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 org.thoughtcrime.securesms.attachments.DatabaseAttachment;
-import org.thoughtcrime.securesms.components.MediaView;
import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener;
import org.thoughtcrime.securesms.database.Address;
+import org.thoughtcrime.securesms.database.MediaDatabase;
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader;
import org.thoughtcrime.securesms.logging.Log;
+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.GlideRequests;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.AttachmentUtil;
import org.thoughtcrime.securesms.util.DateUtils;
-import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
import org.thoughtcrime.securesms.util.Util;
-import java.io.IOException;
-import java.util.WeakHashMap;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
/**
* Activity for displaying media attachments in-app
*/
-public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity implements RecipientModifiedListener,
- LoaderManager.LoaderCallbacks>,
- MediaRailAdapter.RailItemListener
+public final class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
+ implements RecipientModifiedListener,
+ LoaderManager.LoaderCallbacks>,
+ MediaRailAdapter.RailItemListener,
+ MediaPreviewFragment.Events
{
private final static String TAG = MediaPreviewActivity.class.getSimpleName();
@@ -93,8 +90,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
public static final String OUTGOING_EXTRA = "outgoing";
public static final String LEFT_IS_RECENT_EXTRA = "left_is_recent";
- private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
-
private ViewPager mediaPager;
private View detailsContainer;
private TextView caption;
@@ -108,7 +103,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
private String initialCaption;
private Recipient conversationRecipient;
private boolean leftIsRecent;
- private GestureDetector clickDetector;
private MediaPreviewViewModel viewModel;
private ViewPagerListener viewPagerListener;
@@ -119,11 +113,10 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
@Override
protected void onCreate(Bundle bundle, boolean ready) {
this.setTheme(R.style.TextSecure_DarkTheme);
- dynamicLanguage.onCreate(this);
viewModel = ViewModelProviders.of(this).get(MediaPreviewViewModel.class);
- setFullscreenIfPossible();
+ getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
@@ -135,24 +128,11 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
initializeObservers();
}
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- clickDetector.onTouchEvent(ev);
- return super.dispatchTouchEvent(ev);
- }
-
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
- @TargetApi(VERSION_CODES.JELLY_BEAN)
- private void setFullscreenIfPossible() {
- if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
- getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN);
- }
- }
-
@Override
public void onModified(Recipient recipient) {
Util.runOnMain(this::initializeActionBar);
@@ -176,7 +156,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
CharSequence relativeTimeSpan;
if (mediaItem.date > 0) {
- relativeTimeSpan = DateUtils.getExtendedRelativeTimeSpanString(this,dynamicLanguage.getCurrentLocale(), mediaItem.date);
+ relativeTimeSpan = DateUtils.getExtendedRelativeTimeSpanString(this, Locale.getDefault(), mediaItem.date);
} else {
relativeTimeSpan = getString(R.string.MediaPreviewActivity_draft);
}
@@ -193,7 +173,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
public void onResume() {
super.onResume();
- dynamicLanguage.onResume(this);
initializeMedia();
}
@@ -277,16 +256,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
playbackControlsContainer.removeAllViews();
}
});
-
- clickDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
- @Override
- public boolean onSingleTapUp(MotionEvent e) {
- if (e.getY() < detailsContainer.getTop()) {
- detailsContainer.setVisibility(detailsContainer.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE);
- }
- return super.onSingleTapUp(e);
- }
- });
}
private void initializeMedia() {
@@ -299,9 +268,9 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
Log.i(TAG, "Loading Part URI: " + initialMediaUri);
if (conversationRecipient != null) {
- getSupportLoaderManager().restartLoader(0, null, this);
+ LoaderManager.getInstance(this).restartLoader(0, null, this);
} else {
- mediaPager.setAdapter(new SingleItemPagerAdapter(this, GlideApp.with(this), getWindow(), initialMediaUri, initialMediaType, initialMediaSize));
+ mediaPager.setAdapter(new SingleItemPagerAdapter(getSupportFragmentManager(), initialMediaUri, initialMediaType, initialMediaSize));
if (initialCaption != null) {
detailsContainer.setVisibility(View.VISIBLE);
@@ -376,9 +345,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
new AsyncTask() {
@Override
protected Void doInBackground(Void... voids) {
- if (mediaItem.attachment == null) {
- return null;
- }
AttachmentUtil.deleteAttachment(MediaPreviewActivity.this.getApplicationContext(),
mediaItem.attachment);
return null;
@@ -449,7 +415,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
public void onLoadFinished(@NonNull Loader> loader, @Nullable Pair data) {
if (data != null) {
@SuppressWarnings("ConstantConditions")
- CursorPagerAdapter adapter = new CursorPagerAdapter(this, GlideApp.with(this), getWindow(), data.first, data.second, leftIsRecent);
+ CursorPagerAdapter adapter = new CursorPagerAdapter(getSupportFragmentManager(),this, data.first, data.second, leftIsRecent);
mediaPager.setAdapter(adapter);
adapter.setActive(true);
@@ -469,6 +435,13 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
}
+ @Override
+ public boolean singleTapOnMedia() {
+ detailsContainer.setVisibility(detailsContainer.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE);
+
+ return true;
+ }
+
private class ViewPagerListener extends ExtendedOnPageChangedListener {
@Override
@@ -499,26 +472,23 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
}
}
- private static class SingleItemPagerAdapter extends PagerAdapter implements MediaItemAdapter {
+ private static class SingleItemPagerAdapter extends FragmentStatePagerAdapter implements MediaItemAdapter {
- private final GlideRequests glideRequests;
- private final Window window;
- private final Uri uri;
- private final String mediaType;
- private final long size;
+ private final Uri uri;
+ private final String mediaType;
+ private final long size;
- private final LayoutInflater inflater;
+ private MediaPreviewFragment mediaPreviewFragment;
- SingleItemPagerAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests,
- @NonNull Window window, @NonNull Uri uri, @NonNull String mediaType,
+ SingleItemPagerAdapter(@NonNull FragmentManager fragmentManager,
+ @NonNull Uri uri,
+ @NonNull String mediaType,
long size)
{
- this.glideRequests = glideRequests;
- this.window = window;
- this.uri = uri;
- this.mediaType = mediaType;
- this.size = size;
- this.inflater = LayoutInflater.from(context);
+ super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
+ this.uri = uri;
+ this.mediaType = mediaType;
+ this.size = size;
}
@Override
@@ -526,33 +496,19 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
return 1;
}
+ @NonNull
@Override
- public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
- return view == object;
- }
-
- @Override
- public @NonNull Object instantiateItem(@NonNull ViewGroup container, int position) {
- View itemView = inflater.inflate(R.layout.media_view_page, container, false);
- MediaView mediaView = itemView.findViewById(R.id.media_view);
-
- try {
- mediaView.set(glideRequests, window, uri, mediaType, size, true);
- } catch (IOException e) {
- Log.w(TAG, e);
- }
-
- container.addView(itemView);
-
- return itemView;
+ public Fragment getItem(int position) {
+ mediaPreviewFragment = MediaPreviewFragment.newInstance(uri, mediaType, size, true);
+ return mediaPreviewFragment;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
- MediaView mediaView = ((FrameLayout)object).findViewById(R.id.media_view);
- mediaView.cleanup();
-
- container.removeView((FrameLayout)object);
+ if (mediaPreviewFragment != null) {
+ mediaPreviewFragment.cleanUp();
+ mediaPreviewFragment = null;
+ }
}
@Override
@@ -562,35 +518,40 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
@Override
public void pause(int position) {
-
+ if (mediaPreviewFragment != null) {
+ mediaPreviewFragment.pause();
+ }
}
@Override
public @Nullable View getPlaybackControls(int position) {
+ if (mediaPreviewFragment != null) {
+ return mediaPreviewFragment.getPlaybackControls();
+ }
return null;
}
}
- private static class CursorPagerAdapter extends PagerAdapter implements MediaItemAdapter {
+ private static class CursorPagerAdapter extends FragmentStatePagerAdapter implements MediaItemAdapter {
- private final WeakHashMap mediaViews = new WeakHashMap<>();
+ @SuppressLint("UseSparseArrays")
+ private final Map mediaFragments = new HashMap<>();
- private final Context context;
- private final GlideRequests glideRequests;
- private final Window window;
- private final Cursor cursor;
- private final boolean leftIsRecent;
+ private final Context context;
+ private final Cursor cursor;
+ private final boolean leftIsRecent;
private boolean active;
private int autoPlayPosition;
- CursorPagerAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests,
- @NonNull Window window, @NonNull Cursor cursor, 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.glideRequests = glideRequests;
- this.window = window;
this.cursor = cursor;
this.autoPlayPosition = autoPlayPosition;
this.leftIsRecent = leftIsRecent;
@@ -607,45 +568,34 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
else return cursor.getCount();
}
+ @NonNull
@Override
- public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
- return view == object;
- }
-
- @Override
- public @NonNull Object instantiateItem(@NonNull ViewGroup container, int position) {
- View itemView = LayoutInflater.from(context).inflate(R.layout.media_view_page, container, false);
- MediaView mediaView = itemView.findViewById(R.id.media_view);
- boolean autoplay = position == autoPlayPosition;
- int cursorPosition = getCursorPosition(position);
+ public Fragment getItem(int position) {
+ boolean autoPlay = autoPlayPosition == position;
+ int cursorPosition = getCursorPosition(position);
autoPlayPosition = -1;
cursor.moveToPosition(cursorPosition);
- MediaRecord mediaRecord = MediaRecord.from(context, cursor);
+ MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(context, cursor);
+ DatabaseAttachment attachment = mediaRecord.getAttachment();
+ MediaPreviewFragment fragment = MediaPreviewFragment.newInstance(attachment, autoPlay);
- try {
- //noinspection ConstantConditions
- mediaView.set(glideRequests, window, mediaRecord.getAttachment().getDataUri(),
- mediaRecord.getAttachment().getContentType(), mediaRecord.getAttachment().getSize(), autoplay);
- } catch (IOException e) {
- Log.w(TAG, e);
- }
+ mediaFragments.put(position, fragment);
- mediaViews.put(position, mediaView);
- container.addView(itemView);
-
- return itemView;
+ return fragment;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
- MediaView mediaView = ((FrameLayout)object).findViewById(R.id.media_view);
- mediaView.cleanup();
+ MediaPreviewFragment removed = mediaFragments.remove(position);
- mediaViews.remove(position);
- container.removeView((FrameLayout)object);
+ if (removed != null) {
+ removed.cleanUp();
+ }
+
+ super.destroyItem(container, position, object);
}
public MediaItem getMediaItemFor(int position) {
@@ -665,13 +615,13 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
@Override
public void pause(int position) {
- MediaView mediaView = mediaViews.get(position);
+ MediaPreviewFragment mediaView = mediaFragments.get(position);
if (mediaView != null) mediaView.pause();
}
@Override
public @Nullable View getPlaybackControls(int position) {
- MediaView mediaView = mediaViews.get(position);
+ MediaPreviewFragment mediaView = mediaFragments.get(position);
if (mediaView != null) return mediaView.getPlaybackControls();
return null;
}
diff --git a/src/org/thoughtcrime/securesms/components/MediaView.java b/src/org/thoughtcrime/securesms/components/MediaView.java
deleted file mode 100644
index a42c341c56..0000000000
--- a/src/org/thoughtcrime/securesms/components/MediaView.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package org.thoughtcrime.securesms.components;
-
-
-import android.content.Context;
-import android.net.Uri;
-import android.os.Build;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.Window;
-import android.widget.FrameLayout;
-
-import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.mms.GlideRequests;
-import org.thoughtcrime.securesms.mms.VideoSlide;
-import org.thoughtcrime.securesms.util.views.Stub;
-import org.thoughtcrime.securesms.video.VideoPlayer;
-
-import java.io.IOException;
-
-public class MediaView extends FrameLayout {
-
- private ZoomingImageView imageView;
- private Stub videoView;
-
- public MediaView(@NonNull Context context) {
- super(context);
- initialize();
- }
-
- public MediaView(@NonNull Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- initialize();
- }
-
- public MediaView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- initialize();
- }
-
- @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
- public MediaView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- initialize();
- }
-
- private void initialize() {
- inflate(getContext(), R.layout.media_view, this);
-
- this.imageView = findViewById(R.id.image);
- this.videoView = new Stub<>(findViewById(R.id.video_player_stub));
- }
-
- public void set(@NonNull GlideRequests glideRequests,
- @NonNull Window window,
- @NonNull Uri source,
- @NonNull String mediaType,
- long size,
- boolean autoplay)
- throws IOException
- {
- if (mediaType.startsWith("image/")) {
- imageView.setVisibility(View.VISIBLE);
- if (videoView.resolved()) videoView.get().setVisibility(View.GONE);
- imageView.setImageUri(glideRequests, source, mediaType);
- } else if (mediaType.startsWith("video/")) {
- imageView.setVisibility(View.GONE);
- videoView.get().setVisibility(View.VISIBLE);
- videoView.get().setWindow(window);
- videoView.get().setVideoSource(new VideoSlide(getContext(), source, size), autoplay);
- } else {
- throw new IOException("Unsupported media type: " + mediaType);
- }
- }
-
- public void pause() {
- if (this.videoView.resolved()){
- this.videoView.get().pause();
- }
- }
-
- public void hideControls() {
- if (this.videoView.resolved()){
- this.videoView.get().hideControls();
- }
- }
-
- public @Nullable View getPlaybackControls() {
- if (this.videoView.resolved()){
- return this.videoView.get().getControlView();
- }
- return null;
- }
-
- public void cleanup() {
- this.imageView.cleanup();
- if (this.videoView.resolved()) {
- this.videoView.get().cleanup();
- }
- }
-}
diff --git a/src/org/thoughtcrime/securesms/components/ZoomingImageView.java b/src/org/thoughtcrime/securesms/components/ZoomingImageView.java
index c1b4f42db4..8634a04833 100644
--- a/src/org/thoughtcrime/securesms/components/ZoomingImageView.java
+++ b/src/org/thoughtcrime/securesms/components/ZoomingImageView.java
@@ -57,6 +57,9 @@ public class ZoomingImageView extends FrameLayout {
this.subsamplingImageView = findViewById(R.id.subsampling_image_view);
this.subsamplingImageView.setOrientation(SubsamplingScaleImageView.ORIENTATION_USE_EXIF);
+
+ this.photoView.setOnClickListener(v -> ZoomingImageView.this.callOnClick());
+ this.subsamplingImageView.setOnClickListener(v -> ZoomingImageView.this.callOnClick());
}
@SuppressLint("StaticFieldLeak")
diff --git a/src/org/thoughtcrime/securesms/mediapreview/ImageMediaPreviewFragment.java b/src/org/thoughtcrime/securesms/mediapreview/ImageMediaPreviewFragment.java
new file mode 100644
index 0000000000..1c0dc59751
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/mediapreview/ImageMediaPreviewFragment.java
@@ -0,0 +1,44 @@
+package org.thoughtcrime.securesms.mediapreview;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.components.ZoomingImageView;
+import org.thoughtcrime.securesms.mms.GlideApp;
+import org.thoughtcrime.securesms.mms.GlideRequests;
+import org.thoughtcrime.securesms.util.MediaUtil;
+
+final class ImageMediaPreviewFragment extends MediaPreviewFragment {
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ ZoomingImageView zoomingImageView = (ZoomingImageView) inflater.inflate(R.layout.media_preview_image_fragment, container, false);
+ GlideRequests glideRequests = GlideApp.with(requireActivity());
+ Bundle arguments = requireArguments();
+ Uri uri = arguments.getParcelable(DATA_URI);
+ String contentType = arguments.getString(DATA_CONTENT_TYPE);
+
+ if (!MediaUtil.isImageType(contentType)) {
+ throw new AssertionError("This fragment can only display images");
+ }
+
+ //noinspection ConstantConditions
+ zoomingImageView.setImageUri(glideRequests, uri, contentType);
+
+ zoomingImageView.setOnClickListener(v -> events.singleTapOnMedia());
+
+ return zoomingImageView;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java b/src/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java
new file mode 100644
index 0000000000..867bb8cb6f
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java
@@ -0,0 +1,77 @@
+package org.thoughtcrime.securesms.mediapreview;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import org.thoughtcrime.securesms.attachments.Attachment;
+import org.thoughtcrime.securesms.util.MediaUtil;
+
+public abstract class MediaPreviewFragment extends Fragment {
+
+ static final String DATA_URI = "DATA_URI";
+ static final String DATA_SIZE = "DATA_SIZE";
+ static final String DATA_CONTENT_TYPE = "DATA_CONTENT_TYPE";
+ static final String AUTO_PLAY = "AUTO_PLAY";
+
+ protected Events events;
+
+ public static MediaPreviewFragment newInstance(@NonNull Attachment attachment, boolean autoPlay) {
+ return newInstance(attachment.getDataUri(), attachment.getContentType(), attachment.getSize(), autoPlay);
+ }
+
+ public static MediaPreviewFragment newInstance(@NonNull Uri dataUri, @NonNull String contentType, long size, boolean autoPlay) {
+ Bundle args = new Bundle();
+
+ args.putParcelable(MediaPreviewFragment.DATA_URI, dataUri);
+ args.putString(MediaPreviewFragment.DATA_CONTENT_TYPE, contentType);
+ args.putLong(MediaPreviewFragment.DATA_SIZE, size);
+ args.putBoolean(MediaPreviewFragment.AUTO_PLAY, autoPlay);
+
+ MediaPreviewFragment fragment = createCorrectFragmentType(contentType);
+
+ fragment.setArguments(args);
+
+ return fragment;
+ }
+
+ private static MediaPreviewFragment createCorrectFragmentType(@NonNull String contentType) {
+ if (MediaUtil.isVideo(contentType)) {
+ return new VideoMediaPreviewFragment();
+ } else if (MediaUtil.isImageType(contentType)) {
+ return new ImageMediaPreviewFragment();
+ } else {
+ throw new AssertionError("Unexpected media type: " + contentType);
+ }
+ }
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+ if (!(context instanceof Events)) {
+ throw new AssertionError("Activity must support " + Events.class);
+ }
+
+ events = (Events) context;
+ }
+
+ public void cleanUp() {
+ }
+
+ public void pause() {
+ }
+
+ public @Nullable View getPlaybackControls() {
+ return null;
+ }
+
+ public interface Events {
+
+ boolean singleTapOnMedia();
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java b/src/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java
new file mode 100644
index 0000000000..bfebc6266c
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java
@@ -0,0 +1,72 @@
+package org.thoughtcrime.securesms.mediapreview;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.logging.Log;
+import org.thoughtcrime.securesms.mms.VideoSlide;
+import org.thoughtcrime.securesms.util.MediaUtil;
+import org.thoughtcrime.securesms.video.VideoPlayer;
+
+import java.io.IOException;
+
+final class VideoMediaPreviewFragment extends MediaPreviewFragment {
+
+ private static final String TAG = Log.tag(VideoMediaPreviewFragment.class);
+
+ private VideoPlayer videoView;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View itemView = inflater.inflate(R.layout.media_preview_video_fragment, container, false);
+ Bundle arguments = requireArguments();
+ Uri uri = arguments.getParcelable(DATA_URI);
+ String contentType = arguments.getString(DATA_CONTENT_TYPE);
+ long size = arguments.getLong(DATA_SIZE);
+ boolean autoPlay = arguments.getBoolean(AUTO_PLAY);
+
+ if (!MediaUtil.isVideo(contentType)) {
+ throw new AssertionError("This fragment can only display video");
+ }
+
+ videoView = itemView.findViewById(R.id.video_player);
+
+ videoView.setWindow(requireActivity().getWindow());
+ videoView.setVideoSource(new VideoSlide(getContext(), uri, size), autoPlay);
+
+ videoView.setOnClickListener(v -> events.singleTapOnMedia());
+
+ return itemView;
+ }
+
+ @Override
+ public void cleanUp() {
+ if (videoView != null) {
+ videoView.cleanup();
+ }
+ }
+
+ @Override
+ public void pause() {
+ if (videoView != null) {
+ videoView.pause();
+ }
+ }
+
+ @Override
+ public View getPlaybackControls() {
+ return videoView != null ? videoView.getControlView() : null;
+ }
+}