2017-01-20 09:09:10 +01:00
|
|
|
/* Copyright 2017 Andrew Dawson
|
|
|
|
*
|
2017-04-10 02:12:31 +02:00
|
|
|
* This file is a part of Tusky.
|
2017-01-20 09:09:10 +01:00
|
|
|
*
|
2017-04-10 02:12:31 +02:00
|
|
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
|
|
|
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
|
|
|
* License, or (at your option) any later version.
|
2017-01-20 09:09:10 +01:00
|
|
|
*
|
|
|
|
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
2017-04-10 02:12:31 +02:00
|
|
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
|
|
|
* Public License for more details.
|
2017-01-20 09:09:10 +01:00
|
|
|
*
|
2017-04-10 02:12:31 +02:00
|
|
|
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
|
|
|
* see <http://www.gnu.org/licenses>. */
|
2017-01-20 09:09:10 +01:00
|
|
|
|
2017-05-05 00:55:34 +02:00
|
|
|
package com.keylesspalace.tusky.fragment;
|
2017-01-10 07:14:27 +01:00
|
|
|
|
2018-05-10 20:13:25 +02:00
|
|
|
import android.animation.Animator;
|
|
|
|
import android.animation.AnimatorListenerAdapter;
|
2017-04-07 12:12:26 +02:00
|
|
|
import android.content.Context;
|
2017-01-10 07:14:27 +01:00
|
|
|
import android.os.Bundle;
|
2018-05-10 20:13:25 +02:00
|
|
|
import android.support.annotation.NonNull;
|
|
|
|
import android.support.annotation.Nullable;
|
2017-07-14 07:26:58 +02:00
|
|
|
import android.support.v4.view.ViewCompat;
|
2018-05-10 20:13:25 +02:00
|
|
|
import android.text.TextUtils;
|
2017-01-10 07:14:27 +01:00
|
|
|
import android.view.LayoutInflater;
|
|
|
|
import android.view.View;
|
|
|
|
import android.view.ViewGroup;
|
2017-05-04 16:16:24 +02:00
|
|
|
import android.widget.ImageView;
|
2018-05-10 20:13:25 +02:00
|
|
|
import android.widget.TextView;
|
2017-01-10 07:14:27 +01:00
|
|
|
|
2017-05-04 16:16:24 +02:00
|
|
|
import com.github.chrisbanes.photoview.PhotoView;
|
|
|
|
import com.github.chrisbanes.photoview.PhotoViewAttacher;
|
2017-05-10 15:24:45 +02:00
|
|
|
import com.keylesspalace.tusky.R;
|
2018-05-10 20:13:25 +02:00
|
|
|
import com.keylesspalace.tusky.ViewMediaActivity;
|
|
|
|
import com.keylesspalace.tusky.entity.Attachment;
|
2017-03-09 21:21:55 +01:00
|
|
|
import com.squareup.picasso.Callback;
|
2017-07-14 07:26:58 +02:00
|
|
|
import com.squareup.picasso.NetworkPolicy;
|
2017-03-09 21:21:55 +01:00
|
|
|
import com.squareup.picasso.Picasso;
|
|
|
|
|
2018-05-10 20:13:25 +02:00
|
|
|
import java.util.Objects;
|
|
|
|
|
|
|
|
import kotlin.jvm.functions.Function0;
|
|
|
|
|
|
|
|
public final class ViewMediaFragment extends BaseFragment {
|
2017-07-14 07:26:58 +02:00
|
|
|
public interface PhotoActionsListener {
|
2017-07-19 05:34:07 +02:00
|
|
|
void onBringUp();
|
2018-05-10 20:13:25 +02:00
|
|
|
|
2017-06-25 07:07:41 +02:00
|
|
|
void onDismiss();
|
2018-05-10 20:13:25 +02:00
|
|
|
|
2017-07-14 07:26:58 +02:00
|
|
|
void onPhotoTap();
|
2017-06-25 07:07:41 +02:00
|
|
|
}
|
2017-04-07 12:12:26 +02:00
|
|
|
|
2017-03-10 18:38:49 +01:00
|
|
|
private PhotoViewAttacher attacher;
|
2017-07-14 07:26:58 +02:00
|
|
|
private PhotoActionsListener photoActionsListener;
|
2017-10-18 00:20:26 +02:00
|
|
|
private View rootView;
|
|
|
|
private PhotoView photoView;
|
2018-05-10 20:13:25 +02:00
|
|
|
private TextView descriptionView;
|
|
|
|
|
|
|
|
private boolean showingDescription;
|
|
|
|
private boolean isDescriptionVisible;
|
|
|
|
private Function0 toolbarVisibiltyDisposable;
|
2017-07-14 07:26:58 +02:00
|
|
|
|
|
|
|
private static final String ARG_START_POSTPONED_TRANSITION = "startPostponedTransition";
|
2018-07-30 15:42:45 +02:00
|
|
|
private static final String ARG_ATTACHMENT = "attach";
|
|
|
|
private static final String ARG_AVATAR_URL = "avatarUrl";
|
2017-03-10 18:38:49 +01:00
|
|
|
|
2018-05-10 20:13:25 +02:00
|
|
|
public static ViewMediaFragment newInstance(@NonNull Attachment attachment,
|
|
|
|
boolean shouldStartPostponedTransition) {
|
2018-07-30 15:42:45 +02:00
|
|
|
Bundle arguments = new Bundle(2);
|
2017-01-10 07:14:27 +01:00
|
|
|
ViewMediaFragment fragment = new ViewMediaFragment();
|
2018-07-30 15:42:45 +02:00
|
|
|
arguments.putParcelable(ARG_ATTACHMENT, attachment);
|
2017-07-14 07:26:58 +02:00
|
|
|
arguments.putBoolean(ARG_START_POSTPONED_TRANSITION, shouldStartPostponedTransition);
|
|
|
|
|
2017-01-10 07:14:27 +01:00
|
|
|
fragment.setArguments(arguments);
|
|
|
|
return fragment;
|
|
|
|
}
|
|
|
|
|
2018-07-30 15:42:45 +02:00
|
|
|
public static ViewMediaFragment newAvatarInstance(@NonNull String avatarUrl) {
|
|
|
|
Bundle arguments = new Bundle(2);
|
|
|
|
ViewMediaFragment fragment = new ViewMediaFragment();
|
|
|
|
arguments.putString(ARG_AVATAR_URL, avatarUrl);
|
|
|
|
arguments.putBoolean(ARG_START_POSTPONED_TRANSITION, true);
|
|
|
|
|
|
|
|
fragment.setArguments(arguments);
|
|
|
|
return fragment;
|
|
|
|
}
|
|
|
|
|
2017-03-14 19:24:38 +01:00
|
|
|
@Override
|
2017-06-25 07:07:41 +02:00
|
|
|
public void onAttach(Context context) {
|
|
|
|
super.onAttach(context);
|
2017-07-14 07:26:58 +02:00
|
|
|
photoActionsListener = (PhotoActionsListener) context;
|
2017-03-14 19:24:38 +01:00
|
|
|
}
|
|
|
|
|
2017-01-10 07:14:27 +01:00
|
|
|
@Override
|
2018-05-10 20:13:25 +02:00
|
|
|
public View onCreateView(@NonNull LayoutInflater inflater, final ViewGroup container,
|
2017-07-14 07:26:58 +02:00
|
|
|
Bundle savedInstanceState) {
|
|
|
|
rootView = inflater.inflate(R.layout.fragment_view_media, container, false);
|
2017-10-18 00:20:26 +02:00
|
|
|
photoView = rootView.findViewById(R.id.view_media_image);
|
2018-05-10 20:13:25 +02:00
|
|
|
descriptionView = rootView.findViewById(R.id.tv_media_description);
|
|
|
|
|
|
|
|
final Bundle arguments = Objects.requireNonNull(getArguments(), "Empty arguments");
|
2018-07-30 15:42:45 +02:00
|
|
|
final Attachment attachment = arguments.getParcelable(ARG_ATTACHMENT);
|
|
|
|
final String url;
|
|
|
|
|
|
|
|
if(attachment != null) {
|
|
|
|
url = attachment.getUrl();
|
|
|
|
|
|
|
|
@Nullable final String description = attachment.getDescription();
|
|
|
|
|
|
|
|
descriptionView.setText(description);
|
|
|
|
showingDescription = !TextUtils.isEmpty(description);
|
|
|
|
isDescriptionVisible = showingDescription;
|
|
|
|
} else {
|
|
|
|
url = arguments.getString(ARG_AVATAR_URL);
|
|
|
|
if(url == null) {
|
|
|
|
throw new IllegalArgumentException("attachment or avatar url has to be set");
|
|
|
|
}
|
|
|
|
|
|
|
|
showingDescription = false;
|
|
|
|
isDescriptionVisible = false;
|
|
|
|
}
|
2018-05-10 20:13:25 +02:00
|
|
|
|
|
|
|
// Setting visibility without animations so it looks nice when you scroll images
|
|
|
|
//noinspection ConstantConditions
|
|
|
|
descriptionView.setVisibility(showingDescription
|
|
|
|
&& (((ViewMediaActivity) getActivity())).isToolbarVisible()
|
|
|
|
? View.VISIBLE : View.GONE);
|
2017-03-09 21:21:55 +01:00
|
|
|
|
2017-03-10 18:38:49 +01:00
|
|
|
attacher = new PhotoViewAttacher(photoView);
|
2017-01-10 07:14:27 +01:00
|
|
|
|
2017-03-20 08:30:31 +01:00
|
|
|
// Clicking outside the photo closes the viewer.
|
2018-05-10 20:13:25 +02:00
|
|
|
attacher.setOnOutsidePhotoTapListener(imageView -> photoActionsListener.onDismiss());
|
2017-07-14 07:26:58 +02:00
|
|
|
|
2018-05-10 20:13:25 +02:00
|
|
|
attacher.setOnClickListener(v -> onMediaTap());
|
2017-01-10 07:14:27 +01:00
|
|
|
|
2017-03-20 21:34:23 +01:00
|
|
|
/* A vertical swipe motion also closes the viewer. This is especially useful when the photo
|
2017-03-20 08:30:31 +01:00
|
|
|
* mostly fills the screen so clicking outside is difficult. */
|
2018-05-10 20:13:25 +02:00
|
|
|
attacher.setOnSingleFlingListener((e1, e2, velocityX, velocityY) -> {
|
|
|
|
if (Math.abs(velocityY) > Math.abs(velocityX)) {
|
|
|
|
photoActionsListener.onDismiss();
|
|
|
|
return true;
|
2017-03-20 08:30:31 +01:00
|
|
|
}
|
2018-05-10 20:13:25 +02:00
|
|
|
return false;
|
2017-03-20 08:30:31 +01:00
|
|
|
});
|
|
|
|
|
2017-07-14 07:26:58 +02:00
|
|
|
ViewCompat.setTransitionName(photoView, url);
|
|
|
|
|
|
|
|
// If we are the view to be shown initially...
|
|
|
|
if (arguments.getBoolean(ARG_START_POSTPONED_TRANSITION)) {
|
|
|
|
// Try to load image from disk.
|
|
|
|
Picasso.with(getContext())
|
|
|
|
.load(url)
|
|
|
|
.noFade()
|
|
|
|
.networkPolicy(NetworkPolicy.OFFLINE)
|
|
|
|
.into(photoView, new Callback() {
|
|
|
|
@Override
|
|
|
|
public void onSuccess() {
|
|
|
|
// if we loaded image from disk, we should check that view is attached.
|
|
|
|
if (ViewCompat.isAttachedToWindow(photoView)) {
|
|
|
|
finishLoadingSuccessfully();
|
|
|
|
} else {
|
|
|
|
// if view is not attached yet, wait for an attachment and
|
|
|
|
// start transition when it's finally ready.
|
|
|
|
photoView.addOnAttachStateChangeListener(
|
|
|
|
new View.OnAttachStateChangeListener() {
|
|
|
|
@Override
|
|
|
|
public void onViewAttachedToWindow(View v) {
|
|
|
|
finishLoadingSuccessfully();
|
|
|
|
photoView.removeOnAttachStateChangeListener(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onViewDetachedFromWindow(View v) {
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onError() {
|
2017-07-25 23:35:36 +02:00
|
|
|
// if there's no image in cache, load from network and start transition
|
2017-07-14 07:26:58 +02:00
|
|
|
// immediately.
|
2017-07-19 05:34:07 +02:00
|
|
|
photoActionsListener.onBringUp();
|
|
|
|
|
2017-07-14 07:26:58 +02:00
|
|
|
loadImageFromNetwork(url, photoView);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// if we're not initial page, don't bother.
|
|
|
|
loadImageFromNetwork(url, photoView);
|
|
|
|
}
|
|
|
|
|
2018-05-10 20:13:25 +02:00
|
|
|
toolbarVisibiltyDisposable = ((ViewMediaActivity) getActivity())
|
|
|
|
.addToolbarVisibilityListener(this::onToolbarVisibilityChange);
|
|
|
|
|
2017-07-14 07:26:58 +02:00
|
|
|
return rootView;
|
|
|
|
}
|
|
|
|
|
2018-05-10 20:13:25 +02:00
|
|
|
@Override
|
|
|
|
public void onDestroyView() {
|
|
|
|
if (toolbarVisibiltyDisposable != null) toolbarVisibiltyDisposable.invoke();
|
|
|
|
super.onDestroyView();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void onMediaTap() {
|
|
|
|
photoActionsListener.onPhotoTap();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void onToolbarVisibilityChange(boolean visible) {
|
|
|
|
isDescriptionVisible = showingDescription && visible;
|
|
|
|
final int visibility = isDescriptionVisible ? View.VISIBLE : View.INVISIBLE;
|
|
|
|
int alpha = isDescriptionVisible ? 1 : 0;
|
|
|
|
descriptionView.animate().alpha(alpha)
|
|
|
|
.setListener(new AnimatorListenerAdapter() {
|
|
|
|
@Override
|
|
|
|
public void onAnimationEnd(Animator animation) {
|
|
|
|
descriptionView.setVisibility(visibility);
|
|
|
|
animation.removeListener(this);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.start();
|
|
|
|
}
|
|
|
|
|
2017-07-25 23:35:36 +02:00
|
|
|
@Override
|
|
|
|
public void onDetach() {
|
|
|
|
super.onDetach();
|
|
|
|
Picasso.with(getContext())
|
|
|
|
.cancelRequest(photoView);
|
|
|
|
}
|
|
|
|
|
2017-07-14 07:26:58 +02:00
|
|
|
private void loadImageFromNetwork(String url, ImageView photoView) {
|
2017-03-09 21:21:55 +01:00
|
|
|
Picasso.with(getContext())
|
|
|
|
.load(url)
|
2017-07-14 07:26:58 +02:00
|
|
|
.noPlaceholder()
|
2018-07-14 17:12:57 +02:00
|
|
|
.networkPolicy(NetworkPolicy.NO_STORE)
|
2017-03-09 21:21:55 +01:00
|
|
|
.into(photoView, new Callback() {
|
|
|
|
@Override
|
|
|
|
public void onSuccess() {
|
2017-07-14 07:26:58 +02:00
|
|
|
finishLoadingSuccessfully();
|
2017-03-09 21:21:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2017-07-14 07:26:58 +02:00
|
|
|
public void onError() {
|
|
|
|
rootView.findViewById(R.id.view_media_progress).setVisibility(View.GONE);
|
|
|
|
}
|
2017-03-09 21:21:55 +01:00
|
|
|
});
|
2017-07-14 07:26:58 +02:00
|
|
|
}
|
2017-03-09 21:21:55 +01:00
|
|
|
|
2017-07-14 07:26:58 +02:00
|
|
|
private void finishLoadingSuccessfully() {
|
|
|
|
rootView.findViewById(R.id.view_media_progress).setVisibility(View.GONE);
|
|
|
|
attacher.update();
|
2017-07-19 05:34:07 +02:00
|
|
|
photoActionsListener.onBringUp();
|
2017-01-10 07:14:27 +01:00
|
|
|
}
|
|
|
|
}
|