Refactor media preview to use fragments.

This commit is contained in:
Alan Evans 2019-07-19 11:43:55 -04:00 committed by Greyson Parrelli
parent dd66e22443
commit 79a142c1be
9 changed files with 304 additions and 255 deletions

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.components.ZoomingImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="false"
android:contentDescription="@string/media_preview_activity__media_content_description" />

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<org.thoughtcrime.securesms.video.VideoPlayer
android:id="@+id/video_player"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.thoughtcrime.securesms.components.MediaView
android:id="@+id/media_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>

View File

@ -18,70 +18,67 @@ package org.thoughtcrime.securesms;
import android.Manifest; import android.Manifest;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import androidx.lifecycle.ViewModelProviders;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle; 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.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; 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.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.components.MediaView;
import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener; import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.MediaDatabase;
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord; import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader; import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mediapreview.MediaPreviewFragment;
import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel; import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel;
import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter; import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.AttachmentUtil; import org.thoughtcrime.securesms.util.AttachmentUtil;
import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.SaveAttachmentTask; import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment; import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import java.io.IOException; import java.util.HashMap;
import java.util.WeakHashMap; import java.util.Locale;
import java.util.Map;
/** /**
* Activity for displaying media attachments in-app * Activity for displaying media attachments in-app
*/ */
public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity implements RecipientModifiedListener, public final class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
implements RecipientModifiedListener,
LoaderManager.LoaderCallbacks<Pair<Cursor, Integer>>, LoaderManager.LoaderCallbacks<Pair<Cursor, Integer>>,
MediaRailAdapter.RailItemListener MediaRailAdapter.RailItemListener,
MediaPreviewFragment.Events
{ {
private final static String TAG = MediaPreviewActivity.class.getSimpleName(); 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 OUTGOING_EXTRA = "outgoing";
public static final String LEFT_IS_RECENT_EXTRA = "left_is_recent"; public static final String LEFT_IS_RECENT_EXTRA = "left_is_recent";
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private ViewPager mediaPager; private ViewPager mediaPager;
private View detailsContainer; private View detailsContainer;
private TextView caption; private TextView caption;
@ -108,7 +103,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
private String initialCaption; private String initialCaption;
private Recipient conversationRecipient; private Recipient conversationRecipient;
private boolean leftIsRecent; private boolean leftIsRecent;
private GestureDetector clickDetector;
private MediaPreviewViewModel viewModel; private MediaPreviewViewModel viewModel;
private ViewPagerListener viewPagerListener; private ViewPagerListener viewPagerListener;
@ -119,11 +113,10 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
@Override @Override
protected void onCreate(Bundle bundle, boolean ready) { protected void onCreate(Bundle bundle, boolean ready) {
this.setTheme(R.style.TextSecure_DarkTheme); this.setTheme(R.style.TextSecure_DarkTheme);
dynamicLanguage.onCreate(this);
viewModel = ViewModelProviders.of(this).get(MediaPreviewViewModel.class); viewModel = ViewModelProviders.of(this).get(MediaPreviewViewModel.class);
setFullscreenIfPossible(); getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN); WindowManager.LayoutParams.FLAG_FULLSCREEN);
@ -135,24 +128,11 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
initializeObservers(); initializeObservers();
} }
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
clickDetector.onTouchEvent(ev);
return super.dispatchTouchEvent(ev);
}
@Override @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, 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 @Override
public void onModified(Recipient recipient) { public void onModified(Recipient recipient) {
Util.runOnMain(this::initializeActionBar); Util.runOnMain(this::initializeActionBar);
@ -176,7 +156,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
CharSequence relativeTimeSpan; CharSequence relativeTimeSpan;
if (mediaItem.date > 0) { if (mediaItem.date > 0) {
relativeTimeSpan = DateUtils.getExtendedRelativeTimeSpanString(this,dynamicLanguage.getCurrentLocale(), mediaItem.date); relativeTimeSpan = DateUtils.getExtendedRelativeTimeSpanString(this, Locale.getDefault(), mediaItem.date);
} else { } else {
relativeTimeSpan = getString(R.string.MediaPreviewActivity_draft); relativeTimeSpan = getString(R.string.MediaPreviewActivity_draft);
} }
@ -193,7 +173,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
dynamicLanguage.onResume(this);
initializeMedia(); initializeMedia();
} }
@ -277,16 +256,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
playbackControlsContainer.removeAllViews(); 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() { private void initializeMedia() {
@ -299,9 +268,9 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
Log.i(TAG, "Loading Part URI: " + initialMediaUri); Log.i(TAG, "Loading Part URI: " + initialMediaUri);
if (conversationRecipient != null) { if (conversationRecipient != null) {
getSupportLoaderManager().restartLoader(0, null, this); LoaderManager.getInstance(this).restartLoader(0, null, this);
} else { } else {
mediaPager.setAdapter(new SingleItemPagerAdapter(this, GlideApp.with(this), getWindow(), initialMediaUri, initialMediaType, initialMediaSize)); mediaPager.setAdapter(new SingleItemPagerAdapter(getSupportFragmentManager(), initialMediaUri, initialMediaType, initialMediaSize));
if (initialCaption != null) { if (initialCaption != null) {
detailsContainer.setVisibility(View.VISIBLE); detailsContainer.setVisibility(View.VISIBLE);
@ -376,9 +345,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
new AsyncTask<Void, Void, Void>() { new AsyncTask<Void, Void, Void>() {
@Override @Override
protected Void doInBackground(Void... voids) { protected Void doInBackground(Void... voids) {
if (mediaItem.attachment == null) {
return null;
}
AttachmentUtil.deleteAttachment(MediaPreviewActivity.this.getApplicationContext(), AttachmentUtil.deleteAttachment(MediaPreviewActivity.this.getApplicationContext(),
mediaItem.attachment); mediaItem.attachment);
return null; return null;
@ -449,7 +415,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
public void onLoadFinished(@NonNull Loader<Pair<Cursor, Integer>> loader, @Nullable Pair<Cursor, Integer> data) { public void onLoadFinished(@NonNull Loader<Pair<Cursor, Integer>> loader, @Nullable Pair<Cursor, Integer> data) {
if (data != null) { if (data != null) {
@SuppressWarnings("ConstantConditions") @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); mediaPager.setAdapter(adapter);
adapter.setActive(true); 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 { private class ViewPagerListener extends ExtendedOnPageChangedListener {
@Override @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 Uri uri;
private final String mediaType; private final String mediaType;
private final long size; private final long size;
private final LayoutInflater inflater; private MediaPreviewFragment mediaPreviewFragment;
SingleItemPagerAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests, SingleItemPagerAdapter(@NonNull FragmentManager fragmentManager,
@NonNull Window window, @NonNull Uri uri, @NonNull String mediaType, @NonNull Uri uri,
@NonNull String mediaType,
long size) long size)
{ {
this.glideRequests = glideRequests; super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
this.window = window;
this.uri = uri; this.uri = uri;
this.mediaType = mediaType; this.mediaType = mediaType;
this.size = size; this.size = size;
this.inflater = LayoutInflater.from(context);
} }
@Override @Override
@ -526,33 +496,19 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
return 1; return 1;
} }
@NonNull
@Override @Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { public Fragment getItem(int position) {
return view == object; mediaPreviewFragment = MediaPreviewFragment.newInstance(uri, mediaType, size, true);
} return mediaPreviewFragment;
@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;
} }
@Override @Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
MediaView mediaView = ((FrameLayout)object).findViewById(R.id.media_view); if (mediaPreviewFragment != null) {
mediaView.cleanup(); mediaPreviewFragment.cleanUp();
mediaPreviewFragment = null;
container.removeView((FrameLayout)object); }
} }
@Override @Override
@ -562,35 +518,40 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
@Override @Override
public void pause(int position) { public void pause(int position) {
if (mediaPreviewFragment != null) {
mediaPreviewFragment.pause();
}
} }
@Override @Override
public @Nullable View getPlaybackControls(int position) { public @Nullable View getPlaybackControls(int position) {
if (mediaPreviewFragment != null) {
return mediaPreviewFragment.getPlaybackControls();
}
return null; return null;
} }
} }
private static class CursorPagerAdapter extends PagerAdapter implements MediaItemAdapter { private static class CursorPagerAdapter extends FragmentStatePagerAdapter implements MediaItemAdapter {
private final WeakHashMap<Integer, MediaView> mediaViews = new WeakHashMap<>(); @SuppressLint("UseSparseArrays")
private final Map<Integer, MediaPreviewFragment> mediaFragments = new HashMap<>();
private final Context context; private final Context context;
private final GlideRequests glideRequests;
private final Window window;
private final Cursor cursor; private final Cursor cursor;
private final boolean leftIsRecent; private final boolean leftIsRecent;
private boolean active; private boolean active;
private int autoPlayPosition; private int autoPlayPosition;
CursorPagerAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests, CursorPagerAdapter(@NonNull FragmentManager fragmentManager,
@NonNull Window window, @NonNull Cursor cursor, int autoPlayPosition, @NonNull Context context,
@NonNull Cursor cursor,
int autoPlayPosition,
boolean leftIsRecent) boolean leftIsRecent)
{ {
super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.glideRequests = glideRequests;
this.window = window;
this.cursor = cursor; this.cursor = cursor;
this.autoPlayPosition = autoPlayPosition; this.autoPlayPosition = autoPlayPosition;
this.leftIsRecent = leftIsRecent; this.leftIsRecent = leftIsRecent;
@ -607,45 +568,34 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
else return cursor.getCount(); else return cursor.getCount();
} }
@NonNull
@Override @Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { public Fragment getItem(int position) {
return view == object; boolean autoPlay = autoPlayPosition == position;
}
@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); int cursorPosition = getCursorPosition(position);
autoPlayPosition = -1; autoPlayPosition = -1;
cursor.moveToPosition(cursorPosition); 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 { mediaFragments.put(position, fragment);
//noinspection ConstantConditions
mediaView.set(glideRequests, window, mediaRecord.getAttachment().getDataUri(),
mediaRecord.getAttachment().getContentType(), mediaRecord.getAttachment().getSize(), autoplay);
} catch (IOException e) {
Log.w(TAG, e);
}
mediaViews.put(position, mediaView); return fragment;
container.addView(itemView);
return itemView;
} }
@Override @Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
MediaView mediaView = ((FrameLayout)object).findViewById(R.id.media_view); MediaPreviewFragment removed = mediaFragments.remove(position);
mediaView.cleanup();
mediaViews.remove(position); if (removed != null) {
container.removeView((FrameLayout)object); removed.cleanUp();
}
super.destroyItem(container, position, object);
} }
public MediaItem getMediaItemFor(int position) { public MediaItem getMediaItemFor(int position) {
@ -665,13 +615,13 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
@Override @Override
public void pause(int position) { public void pause(int position) {
MediaView mediaView = mediaViews.get(position); MediaPreviewFragment mediaView = mediaFragments.get(position);
if (mediaView != null) mediaView.pause(); if (mediaView != null) mediaView.pause();
} }
@Override @Override
public @Nullable View getPlaybackControls(int position) { public @Nullable View getPlaybackControls(int position) {
MediaView mediaView = mediaViews.get(position); MediaPreviewFragment mediaView = mediaFragments.get(position);
if (mediaView != null) return mediaView.getPlaybackControls(); if (mediaView != null) return mediaView.getPlaybackControls();
return null; return null;
} }

View File

@ -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<VideoPlayer> 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();
}
}
}

View File

@ -57,6 +57,9 @@ public class ZoomingImageView extends FrameLayout {
this.subsamplingImageView = findViewById(R.id.subsampling_image_view); this.subsamplingImageView = findViewById(R.id.subsampling_image_view);
this.subsamplingImageView.setOrientation(SubsamplingScaleImageView.ORIENTATION_USE_EXIF); this.subsamplingImageView.setOrientation(SubsamplingScaleImageView.ORIENTATION_USE_EXIF);
this.photoView.setOnClickListener(v -> ZoomingImageView.this.callOnClick());
this.subsamplingImageView.setOnClickListener(v -> ZoomingImageView.this.callOnClick());
} }
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}