package org.wikipedia.gallery;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.Animatable;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
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.widget.MediaController;
import android.widget.ProgressBar;
import android.widget.VideoView;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView;
import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.samples.zoomable.DoubleTapGestureListener;
import com.facebook.samples.zoomable.ZoomableDraweeView;
import org.wikipedia.Constants;
import org.wikipedia.R;
import org.wikipedia.WikipediaApp;
import org.wikipedia.dataclient.WikiSite;
import org.wikipedia.page.PageTitle;
import org.wikipedia.util.FeedbackUtil;
import org.wikipedia.util.FileUtil;
import org.wikipedia.util.PermissionUtil;
import org.wikipedia.util.ShareUtil;
import org.wikipedia.util.log.L;
import java.io.File;
import java.util.Map;
import static org.wikipedia.util.PermissionUtil.hasWriteExternalStoragePermission;
import static org.wikipedia.util.PermissionUtil.requestWriteStorageRuntimePermissions;
public class GalleryItemFragment extends Fragment {
public static final String ARG_PAGETITLE = "pageTitle";
public static final String ARG_MEDIATITLE = "imageTitle";
public static final String ARG_MIMETYPE = "mimeType";
public static final String ARG_FEED_FEATURED_IMAGE = "feedFeaturedImage";
public static final String ARG_AGE = "age";
private ProgressBar progressBar;
private ZoomableDraweeView imageView;
private View videoContainer;
private VideoView videoView;
private SimpleDraweeView videoThumbnail;
private View videoPlayButton;
private MediaController mediaController;
@NonNull private WikipediaApp app = WikipediaApp.getInstance();
@Nullable private GalleryActivity parentActivity;
@Nullable private PageTitle pageTitle;
@SuppressWarnings("NullableProblems") @NonNull private PageTitle imageTitle;
@Nullable private String mimeType;
private int age;
@Nullable private GalleryItem galleryItem;
@Nullable public GalleryItem getGalleryItem() {
return galleryItem;
}
public static GalleryItemFragment newInstance(@Nullable PageTitle pageTitle, @NonNull WikiSite wiki,
@NonNull GalleryItem galleryItemProto) {
GalleryItemFragment f = new GalleryItemFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_PAGETITLE, pageTitle);
args.putParcelable(ARG_MEDIATITLE, new PageTitle(galleryItemProto.getName(), wiki));
args.putString(ARG_MIMETYPE, galleryItemProto.getMimeType());
if (galleryItemProto instanceof FeaturedImageGalleryItem) {
args.putBoolean(ARG_FEED_FEATURED_IMAGE, true);
args.putInt(ARG_AGE, ((FeaturedImageGalleryItem) galleryItemProto).getAge());
}
f.setArguments(args);
return f;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
pageTitle = getArguments().getParcelable(ARG_PAGETITLE);
//noinspection ConstantConditions
imageTitle = getArguments().getParcelable(ARG_MEDIATITLE);
mimeType = getArguments().getString(ARG_MIMETYPE);
if (getArguments().getBoolean(ARG_FEED_FEATURED_IMAGE)) {
age = getArguments().getInt(ARG_AGE);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_gallery_item, container, false);
progressBar = (ProgressBar) rootView.findViewById(R.id.gallery_item_progress_bar);
videoContainer = rootView.findViewById(R.id.gallery_video_container);
videoView = (VideoView) rootView.findViewById(R.id.gallery_video);
videoThumbnail = (SimpleDraweeView) rootView.findViewById(R.id.gallery_video_thumbnail);
videoPlayButton = rootView.findViewById(R.id.gallery_video_play_button);
imageView = (ZoomableDraweeView) rootView.findViewById(R.id.gallery_image);
imageView.setTapListener(new DoubleTapGestureListener(imageView) {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
parentActivity.toggleControls();
return true;
}
});
GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources())
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
.build();
imageView.setHierarchy(hierarchy);
return rootView;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
parentActivity = (GalleryActivity) getActivity();
// do we already have a prepopulated item in the gallery cache?
galleryItem = parentActivity.getGalleryCache().get(imageTitle);
if (galleryItem == null) {
loadGalleryItem();
} else {
loadMedia();
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
imageView.setController(null);
imageView.setOnClickListener(null);
videoThumbnail.setController(null);
videoThumbnail.setOnClickListener(null);
}
private void updateProgressBar(boolean visible, boolean indeterminate, int value) {
progressBar.setIndeterminate(indeterminate);
if (!indeterminate) {
progressBar.setProgress(value);
}
progressBar.setVisibility(visible ? View.VISIBLE : View.GONE);
}
@Override
public void onDestroy() {
super.onDestroy();
app.getRefWatcher().watch(this);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (!isAdded()) {
return;
}
inflater.inflate(R.menu.menu_gallery, menu);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
if (!isAdded()) {
return;
}
menu.findItem(R.id.menu_gallery_visit_page).setEnabled(galleryItem != null);
menu.findItem(R.id.menu_gallery_share).setEnabled(galleryItem != null
&& imageView.getDrawable() != null);
menu.findItem(R.id.menu_gallery_save).setEnabled(galleryItem != null
&& imageView.getDrawable() != null);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_gallery_visit_page:
if (galleryItem != null) {
parentActivity.finishWithPageResult(imageTitle);
}
return true;
case R.id.menu_gallery_save:
handleImageSaveRequest();
return true;
case R.id.menu_gallery_share:
shareImage();
return true;
default:
break;
}
return super.onOptionsItemSelected(item);
}
/**
* Notifies this fragment that the current position of its containing ViewPager has changed.
*
* @param fragmentPosition This fragment's position in the ViewPager.
* @param pagerPosition The pager's current position that is displayed to the user.
*/
public void onUpdatePosition(int fragmentPosition, int pagerPosition) {
if (fragmentPosition != pagerPosition) {
// update stuff if our position is not "current" within the ViewPager...
if (mediaController != null) {
if (videoView.isPlaying()) {
videoView.pause();
}
mediaController.hide();
}
} else {
// update stuff if our position is "current"
if (mediaController != null) {
if (!videoView.isPlaying()) {
videoView.start();
}
}
}
}
private void handleImageSaveRequest() {
if (!(hasWriteExternalStoragePermission(this.getActivity()))) {
requestWriteExternalStoragePermission();
} else {
saveImage();
}
}
private void requestWriteExternalStoragePermission() {
requestWriteStorageRuntimePermissions(this,
Constants.ACTIVITY_REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION);
}
/**
* Perform a network request to load information and metadata for our gallery item.
*/
private void loadGalleryItem() {
updateProgressBar(true, true, 0);
new GalleryItemFetchTask(app.getAPIForSite(imageTitle.getWikiSite()),
imageTitle.getWikiSite(), imageTitle, FileUtil.isVideo(mimeType)) {
@Override public void onFinish(Map<PageTitle, GalleryItem> result) {
if (!isAdded()) {
return;
}
if (result.size() > 0) {
galleryItem = (GalleryItem) result.values().toArray()[0];
parentActivity.getGalleryCache().put((PageTitle) result.keySet().toArray()[0],
galleryItem);
loadMedia();
} else {
updateProgressBar(false, true, 0);
parentActivity.showError(null, true);
}
}
@Override public void onCatch(Throwable caught) {
if (!isAdded()) {
return;
}
updateProgressBar(false, true, 0);
parentActivity.showError(caught, true);
L.e(caught);
}
}.execute();
}
/**
* Load the actual media associated with our gallery item into the UI.
*/
private void loadMedia() {
if (FileUtil.isVideo(mimeType)) {
loadVideo();
} else {
// it's actually OK to use the thumbUrl in all cases, and here's why:
// - in the case of a JPG or PNG image:
// - if the image is bigger than the requested thumbnail size, then the
// thumbnail will correctly be a scaled-down version of the image, so
// that it won't overload the device's bitmap buffer.
// - if the image is smaller than the requested thumbnail size, then the
// thumbnail url will be the same as the actual image url.
// - in the case of an SVG file:
// - we need the thumbnail image anyway, since the ImageView can't
// display SVGs.
loadImage(galleryItem.getThumbUrl());
}
parentActivity.supportInvalidateOptionsMenu();
parentActivity.layOutGalleryDescription();
}
private View.OnClickListener videoThumbnailClickListener = new View.OnClickListener() {
private boolean loading = false;
@Override
public void onClick(View v) {
if (loading) {
return;
}
loading = true;
L.d("Loading video from url: " + galleryItem.getUrl());
videoView.setVisibility(View.VISIBLE);
mediaController = new MediaController(parentActivity);
updateProgressBar(true, true, 0);
videoView.setMediaController(mediaController);
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
updateProgressBar(false, true, 0);
// ...update the parent activity, which will trigger us to start playing!
parentActivity.layOutGalleryDescription();
// hide the video thumbnail, since we're about to start playback
videoThumbnail.setVisibility(View.GONE);
videoPlayButton.setVisibility(View.GONE);
// and start!
videoView.start();
loading = false;
}
});
videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
updateProgressBar(false, true, 0);
FeedbackUtil.showMessage(getActivity(),
R.string.gallery_error_video_failed);
videoView.setVisibility(View.GONE);
videoThumbnail.setVisibility(View.VISIBLE);
videoPlayButton.setVisibility(View.VISIBLE);
loading = false;
return true;
}
});
videoView.setVideoURI(Uri.parse(galleryItem.getUrl()));
}
};
private void loadVideo() {
videoContainer.setVisibility(View.VISIBLE);
videoPlayButton.setVisibility(View.VISIBLE);
videoView.setVisibility(View.GONE);
if (TextUtils.isEmpty(galleryItem.getThumbUrl())) {
videoThumbnail.setVisibility(View.GONE);
} else {
// show the video thumbnail while the video loads...
videoThumbnail.setVisibility(View.VISIBLE);
videoThumbnail.setController(Fresco.newDraweeControllerBuilder()
.setUri(galleryItem.getThumbUrl())
.setAutoPlayAnimations(true)
.setControllerListener(new BaseControllerListener<ImageInfo>() {
@Override
public void onFinalImageSet(String id, ImageInfo imageInfo, Animatable animatable) {
updateProgressBar(false, true, 0);
}
@Override
public void onFailure(String id, Throwable throwable) {
updateProgressBar(false, true, 0);
}
})
.build());
}
videoThumbnail.setOnClickListener(videoThumbnailClickListener);
}
private void loadImage(String url) {
imageView.setVisibility(View.VISIBLE);
L.v("Loading image from url: " + url);
imageView.setController(Fresco.newDraweeControllerBuilder()
.setUri(url)
.setAutoPlayAnimations(true)
.setControllerListener(new BaseControllerListener<ImageInfo>() {
@Override
public void onFinalImageSet(String id, ImageInfo imageInfo, Animatable animatable) {
if (!isAdded()) {
return;
}
updateProgressBar(false, true, 0);
if (shouldHaveWhiteBackground(galleryItem.getMimeType())) {
imageView.setBackgroundColor(Color.WHITE);
}
parentActivity.supportInvalidateOptionsMenu();
}
@Override
public void onFailure(String id, Throwable throwable) {
if (!isAdded()) {
return;
}
updateProgressBar(false, true, 0);
FeedbackUtil.showMessage(getActivity(), R.string.gallery_error_draw_failed);
}
})
.build());
}
/**
* Share the current image using an activity chooser, so that the user can choose the
* app with which to share the content.
* This is done by saving the image to a temporary file in external storage, then specifying
* that file in the share intent. The name of the temporary file is kept constant, so that
* it's overwritten every time an image is shared from the app, so that it takes up a
* constant amount of space.
*/
private void shareImage() {
if (galleryItem == null) {
return;
}
parentActivity.getFunnel().logGalleryShare(pageTitle, galleryItem.getName());
new ImagePipelineBitmapGetter(getActivity(), galleryItem.getThumbUrl()){
@Override
public void onSuccess(@Nullable Bitmap bitmap) {
if (!isAdded()) {
return;
}
if (bitmap != null) {
ShareUtil.shareImage(parentActivity,
bitmap,
new File(galleryItem.getUrl()).getName(),
getShareSubject(),
imageTitle.getCanonicalUri());
} else {
ShareUtil.shareText(parentActivity, imageTitle);
}
}
}.get();
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
switch (requestCode) {
case Constants.ACTIVITY_REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION:
if (PermissionUtil.isPermitted(grantResults)) {
saveImage();
} else {
L.e("Write permission was denied by user");
FeedbackUtil.showMessage(getActivity(),
R.string.gallery_save_image_write_permission_rationale);
}
break;
default:
throw new RuntimeException("unexpected permission request code " + requestCode);
}
}
@Nullable
private String getShareSubject() {
if (getArguments().getBoolean(ARG_FEED_FEATURED_IMAGE)) {
return ShareUtil.getFeaturedImageShareSubject(getContext(), age);
}
return pageTitle != null ? pageTitle.getDisplayText() : null;
}
private void saveImage() {
if (galleryItem == null) {
return;
}
parentActivity.getFunnel().logGallerySave(pageTitle, galleryItem.getName());
parentActivity.getDownloadReceiver().download(galleryItem);
}
private boolean shouldHaveWhiteBackground(String mimeType) {
return mimeType.contains("svg") || mimeType.contains("png") || mimeType.contains("gif");
}
}