mirror of
https://github.com/oxen-io/session-android.git
synced 2025-06-09 15:58:34 +00:00
Refactor media preview to use fragments.
This commit is contained in:
parent
dd66e22443
commit
79a142c1be
6
res/layout/media_preview_image_fragment.xml
Normal file
6
res/layout/media_preview_image_fragment.xml
Normal 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" />
|
12
res/layout/media_preview_video_fragment.xml
Normal file
12
res/layout/media_preview_video_fragment.xml
Normal 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>
|
@ -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>
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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")
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user