package org.wordpress.android.ui.media;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.MediaController;
import android.widget.TextView;
import android.widget.VideoView;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.wordpress.android.R;
import org.wordpress.android.WordPress;
import org.wordpress.android.fluxc.Dispatcher;
import org.wordpress.android.fluxc.model.MediaModel;
import org.wordpress.android.fluxc.model.SiteModel;
import org.wordpress.android.fluxc.store.MediaStore;
import org.wordpress.android.fluxc.tools.FluxCImageLoader;
import org.wordpress.android.util.ActivityUtils;
import org.wordpress.android.util.AniUtils;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.DateTimeUtils;
import org.wordpress.android.util.DisplayUtils;
import org.wordpress.android.util.ImageUtils;
import org.wordpress.android.util.MediaUtils;
import org.wordpress.android.util.PhotonUtils;
import org.wordpress.android.util.SiteUtils;
import org.wordpress.android.util.ToastUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.inject.Inject;
import uk.co.senab.photoview.PhotoViewAttacher;
public class MediaPreviewActivity extends AppCompatActivity {
private static final String ARG_MEDIA_CONTENT_URI = "content_uri";
private static final String ARG_MEDIA_LOCAL_ID = "media_local_id";
private static final String ARG_IS_VIDEO = "is_video";
private String mContentUri;
private int mMediaId;
private boolean mIsVideo;
private boolean mEnableMetadata;
private boolean mShowEditMenuItem;
private SiteModel mSite;
private ImageView mImageView;
private VideoView mVideoView;
private ViewGroup mMetadataView;
private Toolbar mToolbar;
@Inject MediaStore mMediaStore;
@Inject FluxCImageLoader mImageLoader;
@Inject Dispatcher mDispatcher;
private static final long FADE_DELAY_MS = 4000;
private final Handler mFadeHandler = new Handler();
/**
* @param context self explanatory
* @param sourceView optional imageView on calling activity which shows thumbnail of same media
* @param contentUri local content:// uri of media
* @param isVideo whether the passed media is a video - assumed to be an image otherwise
*/
public static void showPreview(Context context,
View sourceView,
String contentUri,
boolean isVideo) {
Intent intent = new Intent(context, MediaPreviewActivity.class);
intent.putExtra(ARG_MEDIA_CONTENT_URI, contentUri);
intent.putExtra(ARG_IS_VIDEO, isVideo);
showPreviewIntent(context, sourceView, intent);
}
/**
* @param context self explanatory
* @param sourceView optional imageView on calling activity which shows thumbnail of same media
* @param site site which contains this media item
* @param mediaId local ID in site's media library
*/
public static void showPreview(Context context,
View sourceView,
SiteModel site,
int mediaId) {
Intent intent = new Intent(context, MediaPreviewActivity.class);
intent.putExtra(ARG_MEDIA_LOCAL_ID, mediaId);
intent.putExtra(WordPress.SITE, site);
showPreviewIntent(context, sourceView, intent);
}
private static void showPreviewIntent(Context context, View sourceView, Intent intent) {
ActivityOptionsCompat options;
if (sourceView != null) {
int startWidth = sourceView.getWidth();
int startHeight = sourceView.getHeight();
int startX = startWidth / 2;
int startY = startHeight / 2;
options = ActivityOptionsCompat.makeScaleUpAnimation(
sourceView,
startX,
startY,
startWidth,
startHeight);
} else {
options = ActivityOptionsCompat.makeBasic();
}
ActivityCompat.startActivity(context, intent, options.toBundle());
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((WordPress) getApplication()).component().inject(this);
setContentView(R.layout.media_preview_activity);
mImageView = (ImageView) findViewById(R.id.image_preview);
mVideoView = (VideoView) findViewById(R.id.video_preview);
mMetadataView = (ViewGroup) findViewById(R.id.layout_metadata);
mToolbar = (Toolbar) findViewById(R.id.toolbar);
if (savedInstanceState != null) {
mSite = (SiteModel) savedInstanceState.getSerializable(WordPress.SITE);
mContentUri = savedInstanceState.getString(ARG_MEDIA_CONTENT_URI);
mMediaId = savedInstanceState.getInt(ARG_MEDIA_LOCAL_ID);
mIsVideo = savedInstanceState.getBoolean(ARG_IS_VIDEO);
} else {
mSite = (SiteModel) getIntent().getSerializableExtra(WordPress.SITE);
mContentUri = getIntent().getStringExtra(ARG_MEDIA_CONTENT_URI);
mMediaId = getIntent().getIntExtra(ARG_MEDIA_LOCAL_ID, 0);
mIsVideo = getIntent().getBooleanExtra(ARG_IS_VIDEO, false);
}
boolean hasEditFragment = hasEditFragment();
setLookClosable(hasEditFragment);
String mediaUri = null;
if (!TextUtils.isEmpty(mContentUri)) {
mediaUri = mContentUri;
} else if (mMediaId != 0) {
MediaModel media = mMediaStore.getMediaWithLocalId(mMediaId);
if (media == null) {
delayedFinish(true);
return;
}
mIsVideo = media.isVideo();
mEnableMetadata = true;
mediaUri = media.getUrl();
loadMetaData(media);
if (!hasEditFragment) {
fadeInMetadata();
}
}
if (TextUtils.isEmpty(mediaUri)) {
delayedFinish(true);
return;
}
if (mEnableMetadata) {
setSupportActionBar(mToolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.media);
}
} else {
mToolbar.setVisibility(View.GONE);
}
mImageView.setVisibility(mIsVideo ? View.GONE : View.VISIBLE);
mVideoView.setVisibility(mIsVideo ? View.VISIBLE : View.GONE);
mShowEditMenuItem = mEnableMetadata && mSite != null && !hasEditFragment;
if (mIsVideo) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
playVideo(mediaUri);
} else {
loadImage(mediaUri);
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(ARG_MEDIA_CONTENT_URI, mContentUri);
outState.putInt(ARG_MEDIA_LOCAL_ID, mMediaId);
outState.putBoolean(ARG_IS_VIDEO, mIsVideo);
if (mSite != null) {
outState.putSerializable(WordPress.SITE, mSite);
}
}
@Override
public void onStart() {
super.onStart();
mDispatcher.register(this);
}
@Override
public void onStop() {
mDispatcher.unregister(this);
super.onStop();
}
private void delayedFinish(boolean showError) {
if (showError) {
ToastUtils.showToast(this, R.string.error_media_not_found);
}
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
finish();
}
}, 1500);
}
private void showProgress(boolean show) {
findViewById(R.id.progress).setVisibility(show ? View.VISIBLE : View.GONE);
}
@Override
public void onBackPressed() {
MediaEditFragment fragment = getEditFragment();
if (fragment != null) {
ActivityUtils.hideKeyboard(this);
fragment.saveChanges();
}
setLookClosable(false);
showEditMenuItem(true);
super.onBackPressed();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.media_preview, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem mnuEdit = menu.findItem(R.id.menu_edit);
mnuEdit.setVisible(mShowEditMenuItem);
MenuItem mnuShare = menu.findItem(R.id.menu_share);
mnuShare.setVisible(mMediaId != 0);
return super.onPrepareOptionsMenu(menu);
}
private void showEditMenuItem(boolean show) {
mShowEditMenuItem = show;
invalidateOptionsMenu();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
} else if (item.getItemId() == R.id.menu_edit) {
showEditFragment(mMediaId);
return true;
} else if (item.getItemId() == R.id.menu_share) {
shareMedia();
}
return super.onOptionsItemSelected(item);
}
/*
* loads and displays a remote or local image
*/
private void loadImage(@NonNull String mediaUri) {
int width = DisplayUtils.getDisplayPixelWidth(this);
int height = DisplayUtils.getDisplayPixelHeight(this);
int size = Math.max(width, height);
if (mediaUri.startsWith("http")) {
showProgress(true);
String imageUrl = mediaUri;
if (SiteUtils.isPhotonCapable(mSite)) {
imageUrl = PhotonUtils.getPhotonImageUrl(mediaUri, size, 0);
}
mImageLoader.get(imageUrl, new ImageLoader.ImageListener() {
@Override
public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
if (!isFinishing() && response.getBitmap() != null) {
showProgress(false);
setBitmap(response.getBitmap());
}
}
@Override
public void onErrorResponse(VolleyError error) {
AppLog.e(AppLog.T.MEDIA, error);
if (!isFinishing()) {
showProgress(false);
delayedFinish(true);
}
}
}, size, 0);
} else {
new LocalImageTask(mediaUri, size).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
private class LocalImageTask extends AsyncTask<Void, Void, Bitmap> {
private final String mMediaUri;
private final int mSize;
LocalImageTask(@NonNull String mediaUri, int size) {
mMediaUri = mediaUri;
mSize = size;
}
@Override
protected Bitmap doInBackground(Void... params) {
byte[] bytes = ImageUtils.createThumbnailFromUri(
MediaPreviewActivity.this, Uri.parse(mMediaUri), mSize, null, 0);
if (bytes != null) {
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
}
return null;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (isFinishing()) {
return;
}
if (bitmap != null) {
setBitmap(bitmap);
} else {
delayedFinish(true);
}
}
}
private void setBitmap(@NonNull Bitmap bmp) {
// assign the photo attacher to enable pinch/zoom - must come before setImageBitmap
// for it to be correctly resized upon loading
PhotoViewAttacher attacher = new PhotoViewAttacher(mImageView);
// fade in metadata when tapped
if (mEnableMetadata) {
attacher.setOnViewTapListener(new PhotoViewAttacher.OnViewTapListener() {
@Override
public void onViewTap(View view, float x, float y) {
if (!hasEditFragment()) {
fadeInMetadata();
}
}
});
}
mImageView.setImageBitmap(bmp);
}
/*
* loads and plays a remote or local video
*/
private void playVideo(@NonNull String mediaUri) {
final MediaController controls = new MediaController(this);
mVideoView.setMediaController(controls);
mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
delayedFinish(false);
return false;
}
});
mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
controls.show();
mp.start();
}
});
mVideoView.setVideoURI(Uri.parse(mediaUri));
mVideoView.requestFocus();
}
private void loadMetaData(@NonNull final MediaModel media) {
boolean isLocal = MediaUtils.isLocalFile(media.getUploadState());
TextView titleView = (TextView) mMetadataView.findViewById(R.id.media_details_file_name_or_title);
TextView captionView = (TextView) mMetadataView.findViewById(R.id.media_details_caption);
TextView descriptionView = (TextView) mMetadataView.findViewById(R.id.media_details_description);
TextView dateView = (TextView) mMetadataView.findViewById(R.id.media_details_date);
TextView fileTypeView = (TextView) mMetadataView.findViewById(R.id.media_details_file_type);
if (TextUtils.isEmpty(media.getCaption())) {
captionView.setVisibility(View.GONE);
} else {
captionView.setText(media.getCaption());
captionView.setVisibility(View.VISIBLE);
}
if (TextUtils.isEmpty(media.getDescription())) {
descriptionView.setVisibility(View.GONE);
} else {
descriptionView.setText(media.getDescription());
descriptionView.setVisibility(View.VISIBLE);
}
String datePrefix = isLocal ?
getString(R.string.media_details_label_date_added) :
getString(R.string.media_details_label_date_uploaded);
dateView.setText(datePrefix + " " + getDisplayDate(media.getUploadDate()));
String fileURL = media.getUrl();
String fileName = media.getFileName();
titleView.setText(TextUtils.isEmpty(media.getTitle()) ? fileName : media.getTitle());
float mediaWidth = media.getWidth();
float mediaHeight = media.getHeight();
// show dimens & file ext together
String dimens =
(mediaWidth > 0 && mediaHeight > 0) ? (int) mediaWidth + " x " + (int) mediaHeight : null;
String fileExt =
TextUtils.isEmpty(fileURL) ? null : fileURL.replaceAll(".*\\.(\\w+)$", "$1").toUpperCase();
boolean hasDimens = !TextUtils.isEmpty(dimens);
boolean hasExt = !TextUtils.isEmpty(fileExt);
if (hasDimens & hasExt) {
fileTypeView.setText(fileExt + ", " + dimens);
fileTypeView.setVisibility(View.VISIBLE);
} else if (hasExt) {
fileTypeView.setText(fileExt);
fileTypeView.setVisibility(View.VISIBLE);
} else {
fileTypeView.setVisibility(View.GONE);
}
}
private final Runnable fadeOutRunnable = new Runnable() {
@Override
public void run() {
fadeOutMetadata();
}
};
private void fadeOutMetadata() {
if (!isFinishing() && mMetadataView.getVisibility() == View.VISIBLE) {
AniUtils.fadeOut(mMetadataView, AniUtils.Duration.LONG);
}
}
private void fadeInMetadata() {
if (!isFinishing()) {
mFadeHandler.removeCallbacks(fadeOutRunnable);
if (mMetadataView.getVisibility() != View.VISIBLE) {
AniUtils.fadeIn(mMetadataView, AniUtils.Duration.LONG);
}
mFadeHandler.postDelayed(fadeOutRunnable, FADE_DELAY_MS);
}
}
/*
* returns the passed string formatted as a short date if it's valid ISO 8601 date,
* otherwise returns the passed string
*/
private String getDisplayDate(String dateString) {
if (dateString != null) {
Date date = DateTimeUtils.dateFromIso8601(dateString);
if (date != null) {
return SimpleDateFormat.getDateInstance().format(date);
}
}
return dateString;
}
private boolean hasEditFragment() {
return getEditFragment() != null;
}
private MediaEditFragment getEditFragment() {
FragmentManager fm = getFragmentManager();
Fragment fragment = fm.findFragmentByTag(MediaEditFragment.TAG);
if (fragment != null) {
return (MediaEditFragment) fragment;
}
return null;
}
private void showEditFragment(int localMediaId) {
MediaEditFragment fragment = getEditFragment();
if (fragment == null) {
fragment = MediaEditFragment.newInstance(mSite, localMediaId);
FragmentManager fm = getFragmentManager();
fm.beginTransaction()
.replace(R.id.fragment_container, fragment, MediaEditFragment.TAG)
.addToBackStack(null)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commitAllowingStateLoss();
} else {
fragment.loadMedia(localMediaId);
}
setLookClosable(true);
showEditMenuItem(false);
fadeOutMetadata();
}
private void setLookClosable(boolean lookClosable) {
if (mToolbar != null) {
mToolbar.setNavigationIcon(lookClosable ? R.drawable.ic_close_white_24dp : R.drawable.ic_arrow_left_white_24dp);
}
}
private void shareMedia() {
MediaModel media = mMediaStore.getMediaWithLocalId(mMediaId);
if (media == null) {
ToastUtils.showToast(this, R.string.error_media_not_found);
return;
}
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, media.getUrl());
if (!TextUtils.isEmpty(media.getTitle())) {
intent.putExtra(Intent.EXTRA_SUBJECT, media.getTitle());
} else if (!TextUtils.isEmpty(media.getDescription())) {
intent.putExtra(Intent.EXTRA_SUBJECT, media.getDescription());
}
try {
startActivity(Intent.createChooser(intent, getString(R.string.share_link)));
} catch (android.content.ActivityNotFoundException ex) {
ToastUtils.showToast(this, R.string.reader_toast_err_share_intent);
}
}
@SuppressWarnings("unused")
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMediaChanged(MediaStore.OnMediaChanged event) {
if (!event.isError() && mMediaId != 0) {
MediaModel media = mMediaStore.getMediaWithLocalId(mMediaId);
if (media != null) {
loadMetaData(media);
fadeInMetadata();
}
}
}
}