diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java index 4d050596..e8a69432 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java @@ -9,6 +9,7 @@ import android.app.Fragment; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Outline; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; @@ -47,9 +48,12 @@ import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment; import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.AccountField; +import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.ui.SimpleViewHolder; +import org.joinmastodon.android.ui.SingleImagePhotoViewerListener; import org.joinmastodon.android.ui.drawables.CoverOverlayGradientDrawable; +import org.joinmastodon.android.ui.photoviewer.PhotoViewer; import org.joinmastodon.android.ui.tabs.TabLayout; import org.joinmastodon.android.ui.tabs.TabLayoutMediator; import org.joinmastodon.android.ui.text.CustomEmojiSpan; @@ -121,6 +125,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList private boolean refreshing; private View fab; private WindowInsets childInsets; + private PhotoViewer currentPhotoViewer; public ProfileFragment(){ super(R.layout.loader_fragment_overlay_toolbar); @@ -595,12 +600,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){ int topBarsH=getToolbar().getHeight()+statusBarHeight; - if(scrollY>avatar.getTop()-topBarsH){ - float avaAlpha=Math.max(1f-((scrollY-(avatar.getTop()-topBarsH))/(float)V.dp(38)), 0f); - avatar.setAlpha(avaAlpha); + if(scrollY>avatarBorder.getTop()-topBarsH){ + float avaAlpha=Math.max(1f-((scrollY-(avatarBorder.getTop()-topBarsH))/(float)V.dp(38)), 0f); avatarBorder.setAlpha(avaAlpha); }else{ - avatar.setAlpha(1f); avatarBorder.setAlpha(1f); } if(scrollY>cover.getHeight()-topBarsH){ @@ -622,6 +625,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList toolbarTitleView.setTranslationY(titleTransY); toolbarSubtitleView.setTranslationY(titleTransY); } + if(currentPhotoViewer!=null){ + currentPhotoViewer.offsetView(0, oldScrollY-scrollY); + } } private Fragment getFragmentForPage(int page){ @@ -804,15 +810,38 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList return false; } + private List createFakeAttachments(String url, Drawable drawable){ + Attachment att=new Attachment(); + att.type=Attachment.Type.IMAGE; + att.url=url; + att.meta=new Attachment.Metadata(); + att.meta.width=drawable.getIntrinsicWidth(); + att.meta.height=drawable.getIntrinsicHeight(); + return Collections.singletonList(att); + } + private void onAvatarClick(View v){ if(isInEditMode){ startImagePicker(AVATAR_RESULT); + }else{ + Drawable ava=avatar.getDrawable(); + if(ava==null) + return; + int radius=V.dp(25); + currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.avatar, ava), 0, + new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->currentPhotoViewer=null, ()->ava, null, null)); } } private void onCoverClick(View v){ if(isInEditMode){ startImagePicker(COVER_RESULT); + }else{ + Drawable drawable=cover.getDrawable(); + if(drawable==null || drawable instanceof ColorDrawable) + return; + currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.header, drawable), 0, + new SingleImagePhotoViewerListener(cover, cover, null, this, ()->currentPhotoViewer=null, ()->drawable, ()->avatarBorder.setTranslationZ(2), ()->avatarBorder.setTranslationZ(0))); } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/SingleImagePhotoViewerListener.java b/mastodon/src/main/java/org/joinmastodon/android/ui/SingleImagePhotoViewerListener.java new file mode 100644 index 00000000..3645a652 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/SingleImagePhotoViewerListener.java @@ -0,0 +1,87 @@ +package org.joinmastodon.android.ui; + +import android.app.Fragment; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.view.View; + +import org.joinmastodon.android.ui.photoviewer.PhotoViewer; + +import java.util.function.Supplier; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class SingleImagePhotoViewerListener implements PhotoViewer.Listener{ + private final View sourceView, transformView; + private final int[] cornerRadius; + private final Runnable onDismissed; + private final Fragment parentFragment; + private final Supplier currentDrawableSupplier; + private final Runnable onStart, onEnd; + + private float origAlpha; + + public SingleImagePhotoViewerListener(View sourceView, View transformView, int[] cornerRadius, Fragment parentFragment, Runnable onDismissed, Supplier currentDrawableSupplier, Runnable onStart, Runnable onEnd){ + this.sourceView=sourceView; + this.transformView=transformView; + this.cornerRadius=cornerRadius; + this.onDismissed=onDismissed; + this.parentFragment=parentFragment; + this.currentDrawableSupplier=currentDrawableSupplier; + this.onStart=onStart; + this.onEnd=onEnd; + if(cornerRadius!=null && cornerRadius.length!=4) + throw new IllegalArgumentException("Corner radius must be null or have length of 4"); + } + + @Override + public void setPhotoViewVisibility(int index, boolean visible){ + transformView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); + } + + @Override + public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){ + int[] loc={0, 0}; + sourceView.getLocationOnScreen(loc); + outRect.set(loc[0], loc[1], loc[0]+sourceView.getWidth(), loc[1]+sourceView.getHeight()); + if(cornerRadius!=null) + System.arraycopy(cornerRadius, 0, outCornerRadius, 0, 4); + transformView.setTranslationZ(1); + if(onStart!=null) + onStart.run(); + return true; + } + + @Override + public void setTransitioningViewTransform(float translateX, float translateY, float scale){ + transformView.setTranslationX(translateX); + transformView.setTranslationY(translateY); + transformView.setScaleX(scale); + transformView.setScaleY(scale); + } + + @Override + public void endPhotoViewTransition(){ + setTransitioningViewTransform(0f, 0f, 1f); + transformView.setTranslationZ(0); + if(onEnd!=null) + onEnd.run(); + } + + @Nullable + @Override + public Drawable getPhotoViewCurrentDrawable(int index){ + return currentDrawableSupplier.get(); + } + + @Override + public void photoViewerDismissed(){ + onDismissed.run(); + } + + @Override + public void onRequestPermissions(String[] permissions){ + parentFragment.requestPermissions(permissions, PhotoViewer.PERMISSION_REQUEST); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/ZoomPanView.java b/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/ZoomPanView.java index 86665294..58a4dfe2 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/ZoomPanView.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/ZoomPanView.java @@ -4,6 +4,7 @@ import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; import android.graphics.Matrix; +import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; import android.util.AttributeSet; @@ -54,6 +55,9 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS private float lastFlingVelocityY; private float backgroundAlphaForTransition=1f; private boolean forceUpdateLayout; + private int[] transitionCornerRadius; + private Path transitionClipPath=new Path(); + private float[] tmpFloatArray=new float[8]; private static final String TAG="ZoomPanView"; @@ -148,10 +152,25 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS child.getMatrix().mapRect(tmpRect2); tmpRect2.offset(child.getLeft(), child.getTop()); canvas.save(); - canvas.clipRect(interpolate(tmpRect2.left, tmpRect.left, cropAnimationValue), - interpolate(tmpRect2.top, tmpRect.top, cropAnimationValue), - interpolate(tmpRect2.right, tmpRect.right, cropAnimationValue), - interpolate(tmpRect2.bottom, tmpRect.bottom, cropAnimationValue)); + if(transitionCornerRadius!=null){ + float radiusScale=child.getScaleX(); + tmpFloatArray[0]=tmpFloatArray[1]=(float)transitionCornerRadius[0]*radiusScale*(1f-cropAnimationValue); + tmpFloatArray[2]=tmpFloatArray[3]=(float)transitionCornerRadius[1]*radiusScale*(1f-cropAnimationValue); + tmpFloatArray[4]=tmpFloatArray[5]=(float)transitionCornerRadius[2]*radiusScale*(1f-cropAnimationValue); + tmpFloatArray[6]=tmpFloatArray[7]=(float)transitionCornerRadius[3]*radiusScale*(1f-cropAnimationValue); + transitionClipPath.rewind(); + transitionClipPath.addRoundRect(interpolate(tmpRect2.left, tmpRect.left, cropAnimationValue), + interpolate(tmpRect2.top, tmpRect.top, cropAnimationValue), + interpolate(tmpRect2.right, tmpRect.right, cropAnimationValue), + interpolate(tmpRect2.bottom, tmpRect.bottom, cropAnimationValue), + tmpFloatArray, Path.Direction.CW); + canvas.clipPath(transitionClipPath); + }else{ + canvas.clipRect(interpolate(tmpRect2.left, tmpRect.left, cropAnimationValue), + interpolate(tmpRect2.top, tmpRect.top, cropAnimationValue), + interpolate(tmpRect2.right, tmpRect.right, cropAnimationValue), + interpolate(tmpRect2.bottom, tmpRect.bottom, cropAnimationValue)); + } boolean res=super.drawChild(canvas, child, drawingTime); canvas.restore(); return res; @@ -189,6 +208,18 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS return initialScale; } + private void validateAndSetCornerRadius(int[] cornerRadius){ + transitionCornerRadius=null; + if(cornerRadius!=null && cornerRadius.length==4){ + for(int corner:cornerRadius){ + if(corner>0){ + transitionCornerRadius=cornerRadius; + break; + } + } + } + } + public void animateIn(Rect rect, int[] cornerRadius){ int[] loc={0, 0}; getLocationOnScreen(loc); @@ -204,6 +235,7 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS animatingTransition=true; matrix.getValues(matrixValues); + validateAndSetCornerRadius(cornerRadius); child.setAlpha(0f); setupAndStartTransitionAnim(new SpringAnimation(this, CROP_AND_FADE, 1f).setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE)); @@ -233,6 +265,7 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS animatingTransition=true; dismissAfterTransition=true; rawCropAndFadeValue=1f; + validateAndSetCornerRadius(cornerRadius); setupAndStartTransitionAnim(new SpringAnimation(this, CROP_AND_FADE, 0f).setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE)); setupAndStartTransitionAnim(new SpringAnimation(child, DynamicAnimation.SCALE_X, initialScale)); diff --git a/mastodon/src/main/res/layout/fragment_profile.xml b/mastodon/src/main/res/layout/fragment_profile.xml index 6cc49dfc..8c31d928 100644 --- a/mastodon/src/main/res/layout/fragment_profile.xml +++ b/mastodon/src/main/res/layout/fragment_profile.xml @@ -52,7 +52,7 @@ tools:visibility="visible" android:text="@string/follows_you"/> - + android:outlineProvider="@null" + android:background="@drawable/profile_ava_bg"> - + + +