/**
* Wire
* Copyright (C) 2016 Wire Swiss GmbH
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.waz.zclient.pages.main.conversation;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.waz.api.BitmapCallback;
import com.waz.api.ImageAsset;
import com.waz.api.LoadHandle;
import com.waz.api.User;
import com.waz.zclient.OnBackPressedListener;
import com.waz.zclient.R;
import com.waz.zclient.pages.BaseFragment;
import com.waz.zclient.pages.main.conversation.views.image.ImageDragViewContainer;
import com.waz.zclient.ui.animation.interpolators.penner.Expo;
import com.waz.zclient.ui.animation.interpolators.penner.Quart;
import com.waz.zclient.ui.text.GlyphTextView;
import com.waz.zclient.ui.utils.KeyboardUtils;
import com.waz.zclient.ui.utils.MathUtils;
import com.waz.zclient.utils.LayoutSpec;
import com.waz.zclient.utils.ViewUtils;
import com.waz.zclient.views.images.TouchImageView;
public class SingleImageUserFragment extends BaseFragment<SingleImageUserFragment.Container> implements ImageDragViewContainer.Callback,
OnBackPressedListener {
private static final Interpolator ALPHA_INTERPOLATOR = new Quart.EaseOut();
public static final float MIN_BACKGROUND_ALPHA = 0.64f;
public static final float MIN_DRAG_DISTANCE_FADE_CONTROL = 0.1f;
public static final String TAG = SingleImageUserFragment.class.getName();
private static final String ARG_USER = "ARG_USER";
private User user;
private TouchImageView animatingImageView;
private TouchImageView messageTouchImageView;
private LoadHandle bitmapLoadHandle;
private int clickedImageWidth;
private int clickedImageHeight;
private Point clickedImageLocation;
private View background;
private int openAnimationDuration;
private int openAnimationBackgroundDuration;
private int zoomOutAndRotateBackOnCloseDuration;
private int closeAnimationBackgroundDelay;
private View headerControls;
private ImageDragViewContainer dragViewContainer;
private OnClickListener imageViewOnClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
boolean fadeIn = !MathUtils.floatEqual(headerControls.getAlpha(), 1f);
fadeControls(fadeIn);
}
};
private float flingImageTop;
private float flingImageLeft;
private float flingRotation;
private float flingImagePivotX;
private float flingImagePivotY;
private boolean controlsVisibleOnStartDrag;
private boolean isFading;
private boolean isClosing;
private final OnClickListener actionButtonsOnClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
backToConversation(false);
}
};
public static SingleImageUserFragment newInstance(User user) {
SingleImageUserFragment fragment = new SingleImageUserFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_USER, user);
fragment.setArguments(args);
return fragment;
}
@Override
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
protected void onPostAttach(Activity activity) {
super.onPostAttach(activity);
if (LayoutSpec.isPhone(activity)) {
ViewUtils.unlockOrientation(activity);
}
}
@Override
protected void onPreDetach() {
super.onPreDetach();
Activity activity = getActivity();
if (activity != null && LayoutSpec.isPhone(activity)) {
ViewUtils.lockScreenOrientation(Configuration.ORIENTATION_PORTRAIT, activity);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_single_image, container, false);
user = (User) getArguments().get(ARG_USER);
dragViewContainer = ViewUtils.getView(view, R.id.dvc__single_image_message__drag_container);
dragViewContainer.setCallback(this);
messageTouchImageView = ViewUtils.getView(view, R.id.tiv__single_image_message__image);
messageTouchImageView.setOnClickListener(imageViewOnClickListener);
messageTouchImageView.setOnZoomLevelChangedListener(new TouchImageView.OnZoomLevelListener() {
@Override
public void onZoomLevelChanged(float scale) {
if (messageTouchImageView == null ||
dragViewContainer == null) {
return;
}
boolean draggingEnabled = !messageTouchImageView.isZoomed();
dragViewContainer.setDraggingEnabled(draggingEnabled);
}
});
animatingImageView = ViewUtils.getView(view, R.id.tiv__single_image_message__animating_image);
background = ViewUtils.getView(view, R.id.v__single_image_message__background);
openAnimationDuration = getResources().getInteger(R.integer.single_image_message__open_animation__duration);
openAnimationBackgroundDuration = getResources().getInteger(R.integer.framework_animation_duration_short);
zoomOutAndRotateBackOnCloseDuration = getResources().getInteger(R.integer.single_image_message__zoom_out_and_rotate_back_on_close_animation__duration);
closeAnimationBackgroundDelay = getResources().getInteger(R.integer.single_image_message__close_animation_background__delay);
headerControls = ViewUtils.getView(view, R.id.ll__single_image_message__header);
GlyphTextView closeGlyphText = ViewUtils.getView(view, R.id.gtv__single_image_message__close);
closeGlyphText.setOnClickListener(actionButtonsOnClickListener);
return view;
}
@Override
public void onStart() {
super.onStart();
KeyboardUtils.closeKeyboardIfShown(getActivity());
displayImage(getImage());
animatingImageView.setScaleType(getScaleType());
}
protected ImageAsset getImage() {
return user.getPicture();
}
protected ImageView.ScaleType getScaleType() {
return ImageView.ScaleType.CENTER_CROP;
}
@Override
public void onStop() {
if (bitmapLoadHandle != null) {
bitmapLoadHandle.cancel();
bitmapLoadHandle = null;
}
super.onStop();
}
@Override
public void onDestroyView() {
messageTouchImageView.setImageDrawable(null);
messageTouchImageView = null;
animatingImageView.setImageDrawable(null);
animatingImageView = null;
dragViewContainer.setCallback(null);
dragViewContainer = null;
user = null;
super.onDestroyView();
}
private void displayImage(ImageAsset imageAsset) {
if (bitmapLoadHandle != null) {
bitmapLoadHandle.cancel();
bitmapLoadHandle = null;
}
if (imageAsset == null) {
getFragmentManager().popBackStack();
return;
}
if (imageAsset.getWidth() > 0) {
fadeControls(true);
bitmapLoadHandle = imageAsset.getBitmap(ViewUtils.getOrientationIndependentDisplayWidth(getActivity()),
new BitmapCallback() {
@Override
public void onBitmapLoaded(Bitmap bitmap) {
if (getActivity() == null || messageTouchImageView == null) {
return;
}
if (messageTouchImageView.getDrawable() != null) {
// means we display the preview
showBitmap(bitmap);
return;
}
loadClickedImageSizeAndPosition();
positionViewAtAnimationStart();
showBitmap(bitmap);
animateOpeningTransition();
}
@Override
public void onBitmapLoadingFailed(BitmapLoadingFailed reason) {
// show error?
if (messageTouchImageView == null ||
// means we display nothing
messageTouchImageView.getDrawable() == null) {
getFragmentManager().popBackStack();
}
}
});
} else {
getFragmentManager().popBackStack();
}
}
private void showBitmap(Bitmap bitmap) {
messageTouchImageView.setImageDrawable(new BitmapDrawable(getResources(), bitmap));
animatingImageView.setImageDrawable(new BitmapDrawable(getResources(), bitmap));
}
private void loadClickedImageSizeAndPosition() {
final View clickedImage = getControllerFactory().getSingleImageController().getImageContainer();
clickedImageHeight = clickedImage.getMeasuredHeight();
clickedImageWidth = clickedImage.getMeasuredWidth();
if (clickedImageHeight == 0 || clickedImageWidth == 0) {
View parent = (View) clickedImage.getParent();
final int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getMeasuredWidth()
- parent.getPaddingLeft()
- parent.getPaddingRight(),
View.MeasureSpec.EXACTLY);
final int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getMeasuredHeight()
- parent.getPaddingTop()
- parent.getPaddingBottom(),
View.MeasureSpec.EXACTLY);
clickedImage.measure(widthSpec, heightSpec);
clickedImageHeight = clickedImage.getMeasuredHeight();
clickedImageWidth = clickedImage.getMeasuredWidth();
}
clickedImageLocation = ViewUtils.getLocationOnScreen(clickedImage);
int dx = 0;
int dy = -ViewUtils.getStatusBarHeight(getActivity());
clickedImageLocation.offset(dx, dy);
}
private void positionViewAtAnimationStart() {
ViewGroup.LayoutParams layoutParams = animatingImageView.getLayoutParams();
layoutParams.width = clickedImageWidth;
layoutParams.height = clickedImageHeight;
animatingImageView.setLayoutParams(layoutParams);
animatingImageView.setX(clickedImageLocation.x);
animatingImageView.setY(clickedImageLocation.y);
}
private void animateOpeningTransition() {
final int displayHeight = ViewUtils.getOrientationDependentDisplayHeight(getActivity()) - ViewUtils.getStatusBarHeight(
getActivity());
final int displayWidth = ViewUtils.getOrientationDependentDisplayWidth(getActivity());
int fullImageHeight = displayHeight;
int fullImageWidth = (int) (fullImageHeight * (float) clickedImageWidth / (float) clickedImageHeight);
if (fullImageWidth > displayWidth) {
fullImageWidth = displayWidth;
fullImageHeight = (int) (fullImageWidth * (float) clickedImageHeight / (float) clickedImageWidth);
}
final float scale = Math.min(fullImageWidth / (float) clickedImageWidth,
fullImageHeight / (float) clickedImageHeight);
final int finalTop = (displayHeight - clickedImageHeight) / 2;
final int finalLeft = (displayWidth - clickedImageWidth) / 2;
animatingImageView.animate()
.y(finalTop)
.x(finalLeft)
.scaleX(scale)
.scaleY(scale)
.setInterpolator(new Expo.EaseOut())
.setDuration(openAnimationDuration)
.withStartAction(new Runnable() {
@Override
public void run() {
getControllerFactory().getSingleImageController().getImageContainer().setVisibility(
View.INVISIBLE);
}
})
.withEndAction(new Runnable() {
@Override
public void run() {
if (messageTouchImageView == null || animatingImageView == null) {
return;
}
messageTouchImageView.setVisibility(View.VISIBLE);
animatingImageView.setVisibility(View.GONE);
}
})
.start();
background.animate()
.alpha(1f)
.setDuration(openAnimationBackgroundDuration)
.setInterpolator(new Quart.EaseOut())
.start();
}
private void backToConversation(boolean afterFling) {
if (isClosing) {
return;
}
isClosing = true;
loadClickedImageSizeAndPosition();
initAnimatingImageView(afterFling);
getControllerFactory().getSingleImageController().hideSingleImage();
fadeControls(false);
PointF currentFocusPoint = messageTouchImageView.getScrollPosition();
if (currentFocusPoint == null) {
getControllerFactory().getSingleImageController().clearReferences();
getFragmentManager().popBackStack();
return;
}
TouchImageView.FocusAndScale startFocusAndScale = new TouchImageView.FocusAndScale(currentFocusPoint.x,
currentFocusPoint.y,
messageTouchImageView.getCurrentZoom());
TouchImageView.FocusAndScale finishFocusAndScale = new TouchImageView.FocusAndScale(0.5f, 0.5f, 1f);
if ((MathUtils.floatEqual(currentFocusPoint.x, 0.5f) || MathUtils.floatEqual(currentFocusPoint.y, 0.5f)) &&
MathUtils.floatEqual(messageTouchImageView.getCurrentZoom(), 1f)) {
zoomOutAndRotateBackOnCloseDuration = 1;
}
ObjectAnimator.ofObject(messageTouchImageView,
"focusAndScale",
new TouchImageView.FocusAndScaleEvaluator(),
startFocusAndScale,
finishFocusAndScale)
.setDuration(zoomOutAndRotateBackOnCloseDuration)
.start();
final boolean imageOffScreenInList = getControllerFactory().getSingleImageController().isContainerOutOfScreen();
ViewPropertyAnimator exitAnimation = animatingImageView.animate();
if (imageOffScreenInList) {
exitAnimation.alpha(0);
} else {
exitAnimation.x(clickedImageLocation.x)
.y(clickedImageLocation.y)
.rotation(0f)
.scaleX(1f)
.scaleY(1f);
}
exitAnimation.setDuration(openAnimationDuration)
.setStartDelay(zoomOutAndRotateBackOnCloseDuration)
.setInterpolator(new Expo.EaseOut())
.withStartAction(new Runnable() {
@Override
public void run() {
animatingImageView.setVisibility(View.VISIBLE);
messageTouchImageView.setVisibility(View.GONE);
if (imageOffScreenInList) {
getControllerFactory().getSingleImageController().getImageContainer().setVisibility(
View.VISIBLE);
} else {
getControllerFactory().getSingleImageController().getImageContainer().setVisibility(
View.INVISIBLE);
}
}
})
.withEndAction(new Runnable() {
@Override
public void run() {
getControllerFactory().getSingleImageController().getImageContainer().setVisibility(View.VISIBLE);
getControllerFactory().getSingleImageController().clearReferences();
getFragmentManager().popBackStack();
}
});
exitAnimation.start();
background.animate()
.alpha(0f)
.setStartDelay(zoomOutAndRotateBackOnCloseDuration + closeAnimationBackgroundDelay)
.setDuration(openAnimationBackgroundDuration)
.setInterpolator(new Quart.EaseOut())
.start();
}
private void initAnimatingImageView(boolean afterFling) {
if (getView() == null) {
return;
}
ViewGroup parent = (ViewGroup) animatingImageView.getParent();
parent.removeView(animatingImageView);
final int displayHeight = ViewUtils.getOrientationDependentDisplayHeight(getActivity()) - ViewUtils.getStatusBarHeight(
getActivity());
final int displayWidth = ViewUtils.getOrientationDependentDisplayWidth(getActivity());
final int finalTop;
final int finalLeft;
if (afterFling) {
finalLeft = (int) flingImageLeft;
finalTop = (int) flingImageTop;
animatingImageView.setPivotX(flingImagePivotX);
animatingImageView.setPivotY(flingImagePivotY);
animatingImageView.setRotation(flingRotation);
} else {
finalTop = (displayHeight - clickedImageHeight) / 2;
finalLeft = (displayWidth - clickedImageWidth) / 2;
}
final float scale = Math.min(displayWidth / (float) clickedImageWidth,
displayHeight / (float) clickedImageHeight);
ViewGroup.LayoutParams layoutParams = new FrameLayout.LayoutParams(clickedImageWidth, clickedImageHeight);
animatingImageView.setLayoutParams(layoutParams);
animatingImageView.setX(finalLeft);
animatingImageView.setY(finalTop);
animatingImageView.setScaleX(scale);
animatingImageView.setScaleY(scale);
parent.addView(animatingImageView);
}
private void fadeControls(boolean fadeIn) {
fadeView(headerControls, fadeIn);
}
private void fadeView(final View view, final boolean fadeIn) {
isFading = true;
float toAlpha = fadeIn ? 1f : 0f;
view.setClickable(fadeIn);
view.animate()
.alpha(toAlpha)
.setInterpolator(new Quart.EaseOut())
.setDuration(getResources().getInteger(R.integer.single_image_message__overlay__fade_duration))
.withStartAction(new Runnable() {
@Override
public void run() {
if (fadeIn) {
view.setVisibility(View.VISIBLE);
}
}
})
.withEndAction(new Runnable() {
@Override
public void run() {
if (!fadeIn) {
view.setVisibility(View.GONE);
}
isFading = false;
}
})
.start();
}
@Override
public boolean onBackPressed() {
backToConversation(false);
return true;
}
@Override
public void onFlingRequested(float left, float top, float rotation, float pivotX, float pivotY) {
this.flingImageLeft = left;
this.flingImageTop = top;
this.flingImagePivotX = pivotX;
this.flingImagePivotY = pivotY;
this.flingRotation = rotation;
backToConversation(true);
}
@Override
public void onDragDistance(float distance) {
if (ViewUtils.isInPortrait(getActivity()) || LayoutSpec.isTablet(getActivity())) {
background.setAlpha(1f - (1f - MIN_BACKGROUND_ALPHA) * ALPHA_INTERPOLATOR.getInterpolation(distance));
}
if (distance >= MIN_DRAG_DISTANCE_FADE_CONTROL &&
headerControls.getVisibility() != View.GONE &&
!isFading) {
fadeControls(false);
}
}
@Override
public void onStartDrag() {
this.controlsVisibleOnStartDrag = MathUtils.floatEqual(headerControls.getAlpha(), 1f);
}
@Override
public void onEndDrag() {
if (controlsVisibleOnStartDrag) {
fadeControls(true);
}
}
public interface Container {
}
}