package moe.kurumi.moegallery.fragment; import android.content.Context; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.support.v4.app.Fragment; import android.support.v4.view.ViewCompat; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ProgressBar; import com.almeros.android.multitouch.RotateGestureDetector; import com.bumptech.glide.GenericRequestBuilder; import com.bumptech.glide.RequestManager; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.model.GlideUrl; import com.bumptech.glide.load.model.LazyHeaders; import com.bumptech.glide.load.resource.drawable.GlideDrawable; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.target.SimpleTarget; import com.bumptech.glide.request.target.Target; import com.raizlabs.android.dbflow.sql.builder.Condition; import com.raizlabs.android.dbflow.sql.language.Select; import java.io.File; import java.io.InputStream; import java.util.List; import javax.inject.Inject; import moe.kurumi.moegallery.R; import moe.kurumi.moegallery.activity.MainActivity; import moe.kurumi.moegallery.data.ImageDataSource; import moe.kurumi.moegallery.data.Providers; import moe.kurumi.moegallery.di.DaggerMainComponent; import moe.kurumi.moegallery.di.modules.AppModule; import moe.kurumi.moegallery.di.modules.MainModule; import moe.kurumi.moegallery.model.AnimePictures; import moe.kurumi.moegallery.model.AnimePicturesImage; import moe.kurumi.moegallery.model.Image; import moe.kurumi.moegallery.model.database.HistoryImage; import moe.kurumi.moegallery.model.database.HistoryImage$Table; import moe.kurumi.moegallery.model.setting.Setting; import moe.kurumi.moegallery.utils.OkHttp; import moe.kurumi.moegallery.utils.Utils; import moe.kurumi.moegallery.view.DraggableRelativeLayout; import pl.droidsonroids.gif.GifDrawable; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import uk.co.senab.photoview.PhotoView; import uk.co.senab.photoview.PhotoViewAttacher; public class ImageFragment extends Fragment implements MainActivity.TouchEventListener { private static final String TAG = ImageFragment.class.getSimpleName(); @Inject RequestManager mRequestManager; @Inject Setting mSetting; @Inject GenericRequestBuilder<GlideUrl, InputStream, byte[], GifDrawable> mGifRequestBuilder; @Inject Retrofit.Builder mBuilder; @Inject ImageDataSource mImageDataSource; private boolean isVisibleToUser = false; private int width = -1; private int height = -1; private DraggableRelativeLayout mDraggableLayout; private PhotoView mPhotoView; private ProgressBar mProgressBar; private String mUri; private String mName; private Handler mHandler; private RotateGestureDetector mRotationDetector; private Runnable mUpdateGifRunnable = new Runnable() { @Override public void run() { if (mPhotoView != null && mPhotoView.getDrawable() instanceof Animatable) { Animatable animatable = (Animatable) mPhotoView.getDrawable(); if (isVisibleToUser) { animatable.start(); } else { animatable.stop(); } } } }; private Runnable mUpdateOrientationRunnable = new Runnable() { @Override public void run() { if (getActivity() != null && mSetting.autoRotate()) { ((MainActivity) getActivity()).updateOrientation(width, height); } } }; private Runnable mUpdateTransitionRunnable = new Runnable() { @Override public void run() { if (mPhotoView != null) { ViewCompat.setTransitionName(mPhotoView, isVisibleToUser ? mName : null); } } }; private DraggableRelativeLayout.DragListener mDragListener = new DraggableRelativeLayout.DragListener() { final float threshold = 0.85f; float releaseScale = 1f; float scale = 1f; @Override public void onDraggedVertical(int top, int height) { scale = (height - Math.abs(top)) / (float) height; if (scale >= threshold) { mPhotoView.setMinimumScale(threshold); mPhotoView.setScale(scale); mPhotoView.setAlpha(scale); } } @Override public void onViewReleased(float xvel, float yvel) { mPhotoView.setMinimumScale(1f); releaseScale = scale; } @Override public void onViewDragFinished() { if (getActivity() != null && releaseScale < threshold) { getActivity().onBackPressed(); } releaseScale = 1f; } }; private RotateGestureDetector.OnRotateGestureListener mOnRotateGestureListener = new RotateGestureDetector.OnRotateGestureListener() { int rotate = 0; @Override public boolean onRotate(RotateGestureDetector detector) { if (mPhotoView != null) { float degree = detector.getRotationDegreesDelta(); mPhotoView.setRotation(-degree + rotate); } return false; } @Override public boolean onRotateBegin(RotateGestureDetector detector) { mDraggableLayout.setDraggable(false); if (getActivity() != null) { ((MainActivity) getActivity()).setPagingEnabled(false); } //rotate = mDataSource.getRotate(mUri); return true; } @Override public void onRotateEnd(RotateGestureDetector detector) { if (mPhotoView != null) { // set rotation and save state float degree = -detector.getRotationDegreesDelta() + rotate; int n = Math.round(degree / 90); n = n % 4; if (n < 0) { n += 4; } switch (n) { case 0: mPhotoView.setRotation(0); break; case 1: mPhotoView.setRotation(90); break; case 2: mPhotoView.setRotation(180); break; case 3: mPhotoView.setRotation(270); break; } //mDataSource.setRotate(mUri, (int) mPhotoView.getRotation()); rotate = 0; } mDraggableLayout.setDraggable(true); if (getActivity() != null) { ((MainActivity) getActivity()).setPagingEnabled(true); } } }; private Image mImage; public ImageFragment() { mHandler = new Handler(Looper.getMainLooper()); } public static ImageFragment newInstance(Image image) { ImageFragment fragment = new ImageFragment(); fragment.setImage(image); return fragment; } private static LazyHeaders.Builder header() { return new LazyHeaders.Builder().addHeader("User-Agent", OkHttp.USER_AGENT); } @Override public void onAttach(Context context) { super.onAttach(context); DaggerMainComponent.builder() .appModule(new AppModule(getActivity().getApplication())) .mainModule(new MainModule((MainActivity) getActivity())) .build().inject(this); } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); this.isVisibleToUser = isVisibleToUser; if (isVisibleToUser) { updateOrientation(); if (getActivity() != null) { ((MainActivity) getActivity()).setTouchEventListener(this); } } mHandler.removeCallbacks(mUpdateGifRunnable); mHandler.postDelayed(mUpdateGifRunnable, 10); updateTransition(); } private void updateTransition() { mHandler.removeCallbacks(mUpdateTransitionRunnable); mHandler.postDelayed(mUpdateTransitionRunnable, 10); } private void updateOrientation() { mHandler.removeCallbacks(mUpdateOrientationRunnable); mHandler.postDelayed(mUpdateOrientationRunnable, 10); } @Override public void onDestroyView() { super.onDestroyView(); mHandler.removeCallbacks(mUpdateGifRunnable); mHandler.removeCallbacks(mUpdateOrientationRunnable); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.full_image, container, false); mRotationDetector = new RotateGestureDetector(getActivity(), mOnRotateGestureListener); mDraggableLayout = (DraggableRelativeLayout) view.findViewById(R.id.layout); mDraggableLayout.setDragListener(mDragListener); mDraggableLayout.setThreshold(0.15f); mPhotoView = (PhotoView) view.findViewById(R.id.image); mProgressBar = (ProgressBar) view.findViewById(R.id.progress); ViewCompat.setTransitionName(mPhotoView, mName); updateTransition(); String apiUri = mSetting.provider(); Image image = mImage; final LazyHeaders.Builder header = header(); mRequestManager.load(new GlideUrl(mImage.getPreviewUrl(), header.build())) .diskCacheStrategy(DiskCacheStrategy.SOURCE) .into(mPhotoView); switch (apiUri) { case Providers.BEHOIMI_URI: // add refer header header.addHeader("Referer", Providers.BEHOIMI_URI + "/"); cacheImage(image, header); break; case Providers.ANIME_PICTURES_URI: // check if the image have been visited List<HistoryImage> historyImages = new Select().from(HistoryImage.class) .where(Condition.column(HistoryImage$Table.PREVIEWURL) .eq(image.getPreviewUrl())).queryList(); if (historyImages.size() == 0) { Retrofit restAdapter = mBuilder .baseUrl(apiUri) .build(); AnimePictures animePictures = restAdapter.create(AnimePictures.class); String cookie = mSetting.animePicturesServer() + "=" + mSetting.animePicturesToken(); animePictures.post(image.getId(), "json", "en", cookie).enqueue( new Callback<AnimePicturesImage>() { @Override public void onResponse(Call<AnimePicturesImage> call, Response<AnimePicturesImage> response) { mProgressBar.setVisibility(View.VISIBLE); Image i = response.body(); cacheImage(i, header); } @Override public void onFailure(Call<AnimePicturesImage> call, Throwable t) { } }); } else { image = historyImages.get(0); mImageDataSource.cacheDetail(mImage.getFileUrl(), image); cacheImage(image, header); } break; default: cacheImage(image, header); break; } mPhotoView.setOnViewTapListener(new PhotoViewAttacher.OnViewTapListener() { @Override public void onViewTap(View view, float x, float y) { MainActivity out = (MainActivity) getActivity(); if (!out.isSystemUIVisible()) { out.setTitle(mName); out.showSystemUI(); out.hideSystemUIDelayed(3000); out.showFavorite(); } else { out.hideSystemUIDelayed(0); } } }); //mPhotoView.setRotation(mDataSource.getRotate(mUri)); return view; } private void cacheImage(final Image image, final LazyHeaders.Builder header) { mName = image.getName(); mUri = image.getFileUrl(); saveImage(image); mRequestManager.load(new GlideUrl(image.getFileUrl(), header.build())) .downloadOnly(new SimpleTarget<File>() { @Override public void onResourceReady(File resource, GlideAnimation<? super File> glideAnimation) { if (mSetting.autoDownload()) { File dir = new File( Environment.getExternalStorageDirectory().getPath(), "MoeGallery"); String fileName = image.getName().replace('/', '-'); Utils.copy(resource, dir, fileName); mImageDataSource.cacheImageUri(mImage.getFileUrl(), Uri.fromFile(new File(dir, fileName))); } loadImage(image, header); } }); mImageDataSource.cacheDetail(mImage.getFileUrl(), image); } // save to database private void saveImage(Image image) { List<HistoryImage> historyImages = new Select().from(HistoryImage.class) .where( Condition.column(HistoryImage$Table.PREVIEWURL).eq(image.getPreviewUrl())) .queryList(); if (historyImages.size() == 1) { historyImages.get(0).updateLast(); historyImages.get(0).save(); } else { HistoryImage historyImage = new HistoryImage(image); historyImage.save(); } } private void loadImage(Image image, LazyHeaders.Builder header) { if (mUri != null && mUri.toLowerCase().endsWith("gif")) { mGifRequestBuilder.load(new GlideUrl(image.getFileUrl(), header.build())) .diskCacheStrategy(DiskCacheStrategy.SOURCE) .listener(new MediaRequestListener<GlideUrl, GifDrawable>()) .into(mPhotoView); } else { mRequestManager.load(new GlideUrl(image.getFileUrl(), header.build())) .diskCacheStrategy(DiskCacheStrategy.SOURCE) .listener(new MediaRequestListener<GlideUrl, GlideDrawable>()) .into(mPhotoView); } } @Override public boolean onDispatchTouchEvent(MotionEvent motionEvent) { return mRotationDetector.onTouchEvent(motionEvent); } public void setImage(Image image) { mImage = image; } private class MediaRequestListener<T, V extends Drawable> implements RequestListener<T, V> { @Override public boolean onException(Exception e, T model, Target<V> target, boolean isFirstResource) { return false; } @Override public boolean onResourceReady(final V resource, T model, Target<V> target, boolean isFromMemoryCache, boolean isFirstResource) { width = resource.getIntrinsicWidth(); height = resource.getIntrinsicHeight(); if (isVisibleToUser) { updateOrientation(); if (getActivity() != null) { ((MainActivity) getActivity()).setTouchEventListener(ImageFragment.this); } } if (resource instanceof Animatable) { mHandler.removeCallbacks(mUpdateGifRunnable); mHandler.postDelayed(mUpdateGifRunnable, 10); } mProgressBar.setVisibility(View.GONE); return false; } } }