From 31a7aa9d406706460f0cacb6de1b4b6b00cc4a33 Mon Sep 17 00:00:00 2001 From: LucasGGamerM Date: Sat, 16 Sep 2023 16:14:48 -0300 Subject: [PATCH] refactor: start of refactor of previewless media status display item Its crashing a lot, and has a lot to be done --- .../fragments/BaseStatusListFragment.java | 85 +++++++++ ...PreviewlessMediaGridStatusDisplayItem.java | 176 ++++++++++++++++++ .../ui/displayitems/StatusDisplayItem.java | 7 +- ...viewlessMediaAttachmentViewController.java | 56 ++++++ .../ui/views/PreviewlessMediaGridLayout.java | 101 ++++++++++ 5 files changed, 422 insertions(+), 3 deletions(-) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PreviewlessMediaGridStatusDisplayItem.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/utils/PreviewlessMediaAttachmentViewController.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/views/PreviewlessMediaGridLayout.java diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java index f0cb17b66..21c81a0f0 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java @@ -44,6 +44,7 @@ import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem; +import org.joinmastodon.android.ui.displayitems.PreviewlessMediaGridStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.SpoilerStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem; @@ -51,6 +52,7 @@ import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem import org.joinmastodon.android.ui.photoviewer.PhotoViewer; import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost; import org.joinmastodon.android.ui.utils.MediaAttachmentViewController; +import org.joinmastodon.android.ui.utils.PreviewlessMediaAttachmentViewController; import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.utils.ProvidesAssistContent; import org.joinmastodon.android.utils.TypedObjectPool; @@ -90,6 +92,8 @@ public abstract class BaseStatusListFragment exten protected HashMap relationships=new HashMap<>(); protected Rect tmpRect=new Rect(); protected TypedObjectPool attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView); + protected TypedObjectPool previewlessAttachmentViewsPool=new TypedObjectPool<>(this::makeNewPreviewlessMediaAttachmentView); + protected boolean currentlyScrolling; public BaseStatusListFragment(){ @@ -280,6 +284,79 @@ public abstract class BaseStatusListFragment exten }); } + + public void openPreviewlessMediaPhotoViewer(String parentID, Status _status, int attachmentIndex, PreviewlessMediaGridStatusDisplayItem.Holder gridHolder){ + final Status status=_status.getContentStatus(); + currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, new PhotoViewer.Listener(){ + private PreviewlessMediaAttachmentViewController transitioningHolder; + + @Override + public void setPhotoViewVisibility(int index, boolean visible){ + + } + + @Override + public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){ + PreviewlessMediaAttachmentViewController holder=findPhotoViewHolder(index); + if(holder!=null && list!=null){ + transitioningHolder=holder; + View view=transitioningHolder.inner; + int[] pos={0, 0}; + view.getLocationOnScreen(pos); + outRect.set(pos[0], pos[1], pos[0]+view.getWidth(), pos[1]+view.getHeight()); + list.setClipChildren(false); + gridHolder.setClipChildren(false); + transitioningHolder.view.setElevation(1f); + return true; + } + return false; + } + + @Override + public void setTransitioningViewTransform(float translateX, float translateY, float scale){ + View view=transitioningHolder.inner; + view.setTranslationX(translateX); + view.setTranslationY(translateY); + view.setScaleX(scale); + view.setScaleY(scale); + } + + @Override + public void endPhotoViewTransition(){ + View view=transitioningHolder.inner; + view.setTranslationX(0f); + view.setTranslationY(0f); + view.setScaleX(1f); + view.setScaleY(1f); + transitioningHolder.view.setElevation(0f); + if(list!=null) + list.setClipChildren(true); + gridHolder.setClipChildren(true); + transitioningHolder=null; + } + + @Nullable + @Override + public Drawable getPhotoViewCurrentDrawable(int index){ + return null; + } + + @Override + public void photoViewerDismissed(){ + currentPhotoViewer=null; + } + + @Override + public void onRequestPermissions(String[] permissions){ + requestPermissions(permissions, PhotoViewer.PERMISSION_REQUEST); + } + + private PreviewlessMediaAttachmentViewController findPhotoViewHolder(int index){ + return gridHolder.getViewController(index); + } + }); + } + @Override public @Nullable View getFab() { if (getParentFragment() instanceof HasFab l) return l.getFab(); @@ -768,10 +845,18 @@ public abstract class BaseStatusListFragment exten return new MediaAttachmentViewController(getActivity(), type); } + private PreviewlessMediaAttachmentViewController makeNewPreviewlessMediaAttachmentView(MediaGridStatusDisplayItem.GridItemType type){ + return new PreviewlessMediaAttachmentViewController(getActivity(), type); + } + public TypedObjectPool getAttachmentViewsPool(){ return attachmentViewsPool; } + public TypedObjectPool getPreviewlessAttachmentViewsPool(){ + return previewlessAttachmentViewsPool; + } + @Override public void onProvideAssistContent(AssistContent assistContent) { assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon())); diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PreviewlessMediaGridStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PreviewlessMediaGridStatusDisplayItem.java new file mode 100644 index 000000000..198b962b8 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PreviewlessMediaGridStatusDisplayItem.java @@ -0,0 +1,176 @@ +package org.joinmastodon.android.ui.displayitems; + +import static org.joinmastodon.android.GlobalUserPreferences.*; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.app.Activity; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.text.Layout; +import android.text.TextUtils; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; +import android.view.ViewTreeObserver; +import android.widget.FrameLayout; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.fragments.BaseStatusListFragment; +import org.joinmastodon.android.model.Attachment; +import org.joinmastodon.android.model.Status; +import org.joinmastodon.android.ui.OutlineProviders; +import org.joinmastodon.android.ui.PhotoLayoutHelper; +import org.joinmastodon.android.ui.drawables.SpoilerStripesDrawable; +import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost; +import org.joinmastodon.android.ui.utils.MediaAttachmentViewController; +import org.joinmastodon.android.ui.utils.PreviewlessMediaAttachmentViewController; +import org.joinmastodon.android.ui.utils.UiUtils; +import org.joinmastodon.android.ui.views.FrameLayoutThatOnlyMeasuresFirstChild; +import org.joinmastodon.android.ui.views.MaxWidthFrameLayout; +import org.joinmastodon.android.ui.views.MediaGridLayout; +import org.joinmastodon.android.ui.views.PreviewlessMediaGridLayout; +import org.joinmastodon.android.utils.TypedObjectPool; + +import java.util.ArrayList; +import java.util.List; + +import me.grishka.appkit.imageloader.ImageLoaderViewHolder; +import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; +import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; +import me.grishka.appkit.utils.CubicBezierInterpolator; +import me.grishka.appkit.utils.V; + +public class PreviewlessMediaGridStatusDisplayItem extends StatusDisplayItem{ + private static final String TAG="PreviewlessMediaGridDisplayItem"; + + private PhotoLayoutHelper.TiledLayoutResult tiledLayout; + private final TypedObjectPool viewPool; + private final List attachments; + private final ArrayList requests=new ArrayList<>(); + public final Status status; + public String sensitiveTitle; + + public PreviewlessMediaGridStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, PhotoLayoutHelper.TiledLayoutResult tiledLayout, List attachments, Status status){ + super(parentID, parentFragment); + this.tiledLayout=tiledLayout; + this.viewPool=parentFragment.getPreviewlessAttachmentViewsPool(); + this.attachments=attachments; + this.status=status; +// for(Attachment att:attachments){ +// requests.add(new UrlImageLoaderRequest(switch(att.type){ +// case IMAGE -> att.url; +// case VIDEO, GIFV -> att.previewUrl == null ? att.url : att.previewUrl; +// default -> throw new IllegalStateException("Unexpected value: "+att.url); +// }, 1000, 1000)); +// } + } + + @Override + public Type getType(){ + return Type.PREVIEWLESS_MEDIA_GRID; + } + + @Override + public int getImageCount(){ + return attachments.size(); + } + + @Override + public ImageLoaderRequest getImageRequest(int index){ + return requests.get(index); + } + + public static class Holder extends StatusDisplayItem.Holder { + private final FrameLayout wrapper; + private final LinearLayout layout; + private final View.OnClickListener clickListener=this::onViewClick; + private final ArrayList controllers=new ArrayList<>(); + + // private final FrameLayout hideSensitiveButton; + + public Holder(Activity activity, ViewGroup parent){ + super(new FrameLayoutThatOnlyMeasuresFirstChild(activity)); + wrapper=(FrameLayout)itemView; + layout= new LinearLayout(activity); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + layout.setLayoutParams(params); + wrapper.addView(layout); + wrapper.setClipToPadding(false); + + // megalodon: no sensitive hide button because the visibility toggle looks prettier imo +// hideSensitiveButton=(FrameLayout) activity.getLayoutInflater().inflate(R.layout.alt_text_badge, overlays, false); +// ((TextView) hideSensitiveButton.findViewById(R.id.alt_button)).setText(R.string.hide); +// overlays.addView(hideSensitiveButton, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.END | Gravity.TOP)); + +// hideSensitiveButton.setOnClickListener(v->hideSensitive()); + } + + @Override + public void onBind(PreviewlessMediaGridStatusDisplayItem item){ + wrapper.setPadding(0, 0, 0, 0); // item.inset ? 0 : V.dp(8)); + +// if(altTextAnimator!=null) +// altTextAnimator.cancel(); + + for(PreviewlessMediaAttachmentViewController c:controllers){ + item.viewPool.reuse(c.type, c); + } + layout.removeAllViews(); + controllers.clear(); + + int i=0; +// if (!item.attachments.isEmpty()) updateBlurhashInSensitiveOverlay(); + for(Attachment att:item.attachments){ + PreviewlessMediaAttachmentViewController c=item.viewPool.obtain(switch(att.type){ + case IMAGE -> MediaGridStatusDisplayItem.GridItemType.PHOTO; + case VIDEO -> MediaGridStatusDisplayItem.GridItemType.VIDEO; + case GIFV -> MediaGridStatusDisplayItem.GridItemType.GIFV; + default -> throw new IllegalStateException("Unexpected value: "+att.type); + }); + if(c.view.getLayoutParams()==null) + c.view.setLayoutParams(new PreviewlessMediaGridLayout.LayoutParams(item.tiledLayout.tiles[i])); + else + ((MediaGridLayout.LayoutParams) c.view.getLayoutParams()).tile=item.tiledLayout.tiles[i]; + layout.addView(c.view); + c.view.setOnClickListener(clickListener); + c.view.setTag(i); + controllers.add(c); + c.bind(att, item.status); + i++; + } + + boolean insetAndLast=item.inset && isLastDisplayItemForStatus(); + wrapper.setClipToOutline(insetAndLast); + wrapper.setOutlineProvider(insetAndLast ? OutlineProviders.bottomRoundedRect(12) : null); + } + + private void onViewClick(View v){ + int index=(Integer)v.getTag(); + item.parentFragment.openPreviewlessMediaPhotoViewer(item.parentID, item.status, index, this); + } + + + public PreviewlessMediaAttachmentViewController getViewController(int index){ + return controllers.get(index); + } + + public void setClipChildren(boolean clip){ + layout.setClipChildren(clip); + wrapper.setClipChildren(clip); + } + + public LinearLayout getLayout(){ + return layout; + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java index 2fd32ac7b..21f9e7f5f 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java @@ -116,6 +116,7 @@ public abstract class StatusDisplayItem{ case GAP -> new GapStatusDisplayItem.Holder(activity, parent); case EXTENDED_FOOTER -> new ExtendedFooterStatusDisplayItem.Holder(activity, parent); case MEDIA_GRID -> new MediaGridStatusDisplayItem.Holder(activity, parent); + case PREVIEWLESS_MEDIA_GRID -> new PreviewlessMediaGridStatusDisplayItem.Holder(activity, parent); case WARNING -> new WarningFilteredStatusDisplayItem.Holder(activity, parent); case FILE -> new FileStatusDisplayItem.Holder(activity, parent); case SPOILER, FILTER_SPOILER -> new SpoilerStatusDisplayItem.Holder(activity, parent, type); @@ -269,9 +270,8 @@ public abstract class StatusDisplayItem{ contentItems.add(mediaGrid); } if((flags & FLAG_NO_MEDIA_PREVIEW)!=0){ - for(Attachment att:imageAttachments){ - contentItems.add(new FileStatusDisplayItem(parentID, fragment, att, statusForContent)); - } + contentItems.add(new PreviewlessMediaGridStatusDisplayItem(parentID, fragment, null, imageAttachments, statusForContent)); + } for(Attachment att:statusForContent.mediaAttachments){ if(att.type==Attachment.Type.AUDIO){ @@ -362,6 +362,7 @@ public abstract class StatusDisplayItem{ GAP, EXTENDED_FOOTER, MEDIA_GRID, + PREVIEWLESS_MEDIA_GRID, WARNING, FILE, SPOILER, diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/PreviewlessMediaAttachmentViewController.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/PreviewlessMediaAttachmentViewController.java new file mode 100644 index 000000000..3d27e30a0 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/PreviewlessMediaAttachmentViewController.java @@ -0,0 +1,56 @@ +package org.joinmastodon.android.ui.utils; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import org.joinmastodon.android.GlobalUserPreferences; +import org.joinmastodon.android.R; +import org.joinmastodon.android.model.Attachment; +import org.joinmastodon.android.model.Status; +import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem; +import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable; +import org.joinmastodon.android.ui.drawables.PlayIconDrawable; + +public class PreviewlessMediaAttachmentViewController{ + public final View view; + public final MediaGridStatusDisplayItem.GridItemType type; + private final TextView title, domain; + public final View inner; + private final ImageView icon; + private final Context context; + private Status status; + + public PreviewlessMediaAttachmentViewController(Context context, MediaGridStatusDisplayItem.GridItemType type){ + view=context.getSystemService(LayoutInflater.class).inflate(R.layout.display_item_file, null); + title=view.findViewById(R.id.title); + domain=view.findViewById(R.id.domain); + icon=view.findViewById(R.id.imageView); + inner=view.findViewById(R.id.inner); + this.context=context; + this.type=type; + } + + public void bind(Attachment attachment, Status status){ + this.status=status; + title.setText(attachment.description != null + ? attachment.description + : context.getString(R.string.sk_no_alt_text)); + title.setSingleLine(false); + + domain.setText(status.sensitive ? context.getString(R.string.sensitive_content_explain) : null); + domain.setVisibility(status.sensitive ? View.VISIBLE : View.GONE); + + if(attachment.type == Attachment.Type.IMAGE) + icon.setImageDrawable(context.getDrawable(R.drawable.ic_fluent_image_24_regular)); + if(attachment.type == Attachment.Type.VIDEO) + icon.setImageDrawable(context.getDrawable(R.drawable.ic_fluent_video_clip_24_regular)); + if(attachment.type == Attachment.Type.GIFV) + icon.setImageDrawable(context.getDrawable(R.drawable.ic_fluent_gif_24_regular)); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/PreviewlessMediaGridLayout.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/PreviewlessMediaGridLayout.java new file mode 100644 index 000000000..f81818989 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/PreviewlessMediaGridLayout.java @@ -0,0 +1,101 @@ +package org.joinmastodon.android.ui.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +import org.joinmastodon.android.ui.PhotoLayoutHelper; +import org.joinmastodon.android.ui.utils.UiUtils; + +import me.grishka.appkit.utils.V; + +public class PreviewlessMediaGridLayout extends LinearLayout{ + private static final String TAG="PreviewlessMediaGridLayout"; + + private static final int GAP=2; // dp + private PhotoLayoutHelper.TiledLayoutResult tiledLayout; + + public PreviewlessMediaGridLayout(Context context){ + this(context, null); + } + + public PreviewlessMediaGridLayout(Context context, AttributeSet attrs){ + this(context, attrs, 0); + } + + public PreviewlessMediaGridLayout(Context context, AttributeSet attrs, int defStyle){ + super(context, attrs, defStyle); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ +// if(tiledLayout==null){ +// setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), 0); +// return; +// } +// int width=Math.min(UiUtils.MAX_WIDTH, MeasureSpec.getSize(widthMeasureSpec)); +// int height=Math.round(width*(tiledLayout.height/(float)PhotoLayoutHelper.MAX_WIDTH)); +// if(tiledLayout.widthmaxWidth){ + xOffset=(r-l)/2-maxWidth/2; + } + + for(int i=0;i