Allow opening avatars and cover images in photo viewer

closes #24
This commit is contained in:
Grishka 2022-05-03 22:14:56 +03:00
parent bbedf46b21
commit 9823537474
4 changed files with 174 additions and 25 deletions

View File

@ -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<Attachment> 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)));
}
}

View File

@ -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<Drawable> currentDrawableSupplier;
private final Runnable onStart, onEnd;
private float origAlpha;
public SingleImagePhotoViewerListener(View sourceView, View transformView, int[] cornerRadius, Fragment parentFragment, Runnable onDismissed, Supplier<Drawable> 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);
}
}

View File

@ -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));

View File

@ -52,7 +52,7 @@
tools:visibility="visible"
android:text="@string/follows_you"/>
<View
<FrameLayout
android:id="@+id/avatar_border"
android:layout_width="102dp"
android:layout_height="102dp"
@ -60,19 +60,19 @@
android:layout_alignParentStart="true"
android:layout_marginTop="-40dp"
android:layout_marginStart="14dp"
android:background="@drawable/profile_ava_bg"/>
android:outlineProvider="@null"
android:background="@drawable/profile_ava_bg">
<ImageView
android:id="@+id/avatar"
android:layout_width="98dp"
android:layout_height="98dp"
android:layout_below="@id/cover"
android:layout_alignParentStart="true"
android:layout_marginStart="16dp"
android:layout_marginTop="-38dp"
android:scaleType="centerCrop"
android:contentDescription="@string/profile_picture"
tools:src="#0f0" />
<ImageView
android:id="@+id/avatar"
android:layout_width="98dp"
android:layout_height="98dp"
android:layout_gravity="center"
android:scaleType="centerCrop"
android:contentDescription="@string/profile_picture"
tools:src="#0f0" />
</FrameLayout>
<LinearLayout
android:id="@+id/profile_counters"
@ -196,10 +196,10 @@
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/avatar"
android:layout_below="@id/avatar_border"
android:layout_alignParentStart="true"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginTop="12dp"
android:layout_toStartOf="@id/profile_action_btn_wrap"
android:textAppearance="@style/m3_headline_small"
android:textAlignment="viewStart"
@ -232,10 +232,10 @@
android:id="@+id/name_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/avatar"
android:layout_below="@id/avatar_border"
android:layout_alignParentStart="true"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginTop="12dp"
android:layout_toStartOf="@id/profile_action_btn_wrap"
android:textAppearance="@style/m3_body_large"
android:textSize="16sp"