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 a558ba43a..affcdafc0 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java @@ -32,12 +32,10 @@ import org.joinmastodon.android.model.Poll; import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.ui.BetterItemAnimator; -import org.joinmastodon.android.ui.PhotoLayoutHelper; -import org.joinmastodon.android.ui.TileGridLayoutManager; import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem; -import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem; +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.StatusDisplayItem; @@ -45,8 +43,10 @@ import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem; 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.UiUtils; -import org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout; +import org.joinmastodon.android.ui.views.MediaGridLayout; +import org.joinmastodon.android.utils.TypedObjectPool; import java.util.ArrayList; import java.util.Collections; @@ -57,7 +57,6 @@ import java.util.stream.Collectors; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import me.grishka.appkit.Nav; @@ -81,6 +80,7 @@ public abstract class BaseStatusListFragment exten protected HashMap knownAccounts=new HashMap<>(); protected HashMap relationships=new HashMap<>(); protected Rect tmpRect=new Rect(); + protected TypedObjectPool attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView); private final int THRESHOLD = 800; @@ -192,21 +192,21 @@ public abstract class BaseStatusListFragment exten } @Override - public void openPhotoViewer(String parentID, Status _status, int attachmentIndex){ - final Status status=_status.reblog!=null ? _status.reblog : _status; + public void openPhotoViewer(String parentID, Status _status, int attachmentIndex, MediaGridStatusDisplayItem.Holder gridHolder){ + final Status status=_status.getContentStatus(); currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, new PhotoViewer.Listener(){ - private ImageStatusDisplayItem.Holder transitioningHolder; + private MediaAttachmentViewController transitioningHolder; @Override public void setPhotoViewVisibility(int index, boolean visible){ - ImageStatusDisplayItem.Holder holder=findPhotoViewHolder(index); + MediaAttachmentViewController holder=findPhotoViewHolder(index); if(holder!=null) holder.photo.setAlpha(visible ? 1f : 0f); } @Override public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){ - ImageStatusDisplayItem.Holder holder=findPhotoViewHolder(index); + MediaAttachmentViewController holder=findPhotoViewHolder(index); if(holder!=null){ transitioningHolder=holder; View view=transitioningHolder.photo; @@ -214,7 +214,8 @@ public abstract class BaseStatusListFragment exten view.getLocationOnScreen(pos); outRect.set(pos[0], pos[1], pos[0]+view.getWidth(), pos[1]+view.getHeight()); list.setClipChildren(false); - transitioningHolder.itemView.setElevation(1f); + gridHolder.setClipChildren(false); + transitioningHolder.view.setElevation(1f); return true; } return false; @@ -241,15 +242,16 @@ public abstract class BaseStatusListFragment exten view.setTranslationY(0f); view.setScaleX(1f); view.setScaleY(1f); - transitioningHolder.itemView.setElevation(0f); + transitioningHolder.view.setElevation(0f); if(list!=null) list.setClipChildren(true); + gridHolder.setClipChildren(true); transitioningHolder=null; } @Override public Drawable getPhotoViewCurrentDrawable(int index){ - ImageStatusDisplayItem.Holder holder=findPhotoViewHolder(index); + MediaAttachmentViewController holder=findPhotoViewHolder(index); if(holder!=null) return holder.photo.getDrawable(); return null; @@ -265,23 +267,8 @@ public abstract class BaseStatusListFragment exten requestPermissions(permissions, PhotoViewer.PERMISSION_REQUEST); } - private ImageStatusDisplayItem.Holder findPhotoViewHolder(int index){ - if(list==null) - return null; - int offset=0; - for(StatusDisplayItem item:displayItems){ - if(item.parentID.equals(parentID)){ - if(item instanceof ImageStatusDisplayItem){ - RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(getMainAdapterOffset()+offset+index); - if(holder instanceof ImageStatusDisplayItem.Holder imgHolder){ - return imgHolder; - } - return null; - } - } - offset++; - } - return null; + private MediaAttachmentViewController findPhotoViewHolder(int index){ + return gridHolder.getViewController(index); } }); } @@ -380,31 +367,6 @@ public abstract class BaseStatusListFragment exten } } - @Override - protected RecyclerView.LayoutManager onCreateLayoutManager(){ - GridLayoutManager lm=new TileGridLayoutManager(getActivity(), 1000); - lm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup(){ - @Override - public int getSpanSize(int position){ - position-=getMainAdapterOffset(); - if(position>=0 && position exten revealSpoiler(status, holder.getItemID()); } - public void onRevealSpoilerClick(ImageStatusDisplayItem.Holder holder){ + public void onRevealSpoilerClick(MediaGridStatusDisplayItem.Holder holder){ Status status=holder.getItem().status; revealSpoiler(status, holder.getItemID()); } @@ -571,13 +533,14 @@ public abstract class BaseStatusListFragment exten protected void updateImagesSpoilerState(Status status, String itemID){ ArrayList updatedPositions=new ArrayList<>(); - for(ImageStatusDisplayItem.Holder photo:(List)findAllHoldersOfType(itemID, ImageStatusDisplayItem.Holder.class)){ - photo.setRevealed(status.spoilerRevealed); - updatedPositions.add(photo.getAbsoluteAdapterPosition()-getMainAdapterOffset()); + MediaGridStatusDisplayItem.Holder mediaGrid=findHolderOfType(itemID, MediaGridStatusDisplayItem.Holder.class); + if(mediaGrid!=null){ + mediaGrid.setRevealed(status.spoilerRevealed); + updatedPositions.add(mediaGrid.getAbsoluteAdapterPosition()-getMainAdapterOffset()); } int i=0; for(StatusDisplayItem item:displayItems){ - if(itemID.equals(item.parentID) && item instanceof ImageStatusDisplayItem && !updatedPositions.contains(i)){ + if(itemID.equals(item.parentID) && item instanceof MediaGridStatusDisplayItem && !updatedPositions.contains(i)){ adapter.notifyItemChanged(i); } i++; @@ -720,6 +683,14 @@ public abstract class BaseStatusListFragment exten return UiUtils.pickAccountForCompose(getActivity(), accountID); } + private MediaAttachmentViewController makeNewMediaAttachmentView(MediaGridStatusDisplayItem.GridItemType type){ + return new MediaAttachmentViewController(getActivity(), type); + } + + public TypedObjectPool getAttachmentViewsPool(){ + return attachmentViewsPool; + } + protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter> implements ImageLoaderRecyclerAdapter{ public DisplayItemsAdapter(){ @@ -757,16 +728,6 @@ public abstract class BaseStatusListFragment exten public ImageLoaderRequest getImageRequest(int position, int image){ return displayItems.get(position).getImageRequest(image); } - -// @Override -// public void onViewDetachedFromWindow(@NonNull BindableViewHolder holder){ -// if(holder instanceof ImageLoaderViewHolder){ -// int count=holder.getItem().getImageCount(); -// for(int i=0;i exten for(int i=0;i imgHolder){ + if(holder instanceof MediaGridStatusDisplayItem.Holder imgHolder){ if(!imgHolder.getItem().status.spoilerRevealed && TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){ hiddenMediaPaint.setColor(0x80000000); - PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgHolder.getItem().thisTile; - float hGap=tile.startCol>0 ? V.dp(1) : 0; - float vGap=tile.startRow>0 ? V.dp(1) : 0; - c.drawRect(child.getX()-hGap, child.getY()-vGap, child.getX()+child.getWidth(), child.getY()+child.getHeight(), hiddenMediaPaint); + c.drawRect(child.getX(), child.getY(), child.getX()+child.getWidth(), child.getY()+child.getHeight(), hiddenMediaPaint); } } } for(int i=0;i imgHolder){ + if(holder instanceof MediaGridStatusDisplayItem.Holder imgHolder){ if(!imgHolder.getItem().status.spoilerRevealed){ - PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgHolder.getItem().thisTile; - if(tile.startCol==0 && tile.startRow==0 && TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){ + if(TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){ int listWidth=getListWidthForMediaLayout(); - int width=Math.min(listWidth, V.dp(ImageAttachmentFrameLayout.MAX_WIDTH)); + int width=Math.min(listWidth, V.dp(MediaGridLayout.MAX_WIDTH)); if(currentMediaHiddenLayoutsWidth!=width) rebuildMediaHiddenLayouts(width-V.dp(32)); c.save(); @@ -843,47 +800,6 @@ public abstract class BaseStatusListFragment exten } } - @Override - public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){ - RecyclerView.ViewHolder holder=parent.getChildViewHolder(view); - if(holder instanceof ImageStatusDisplayItem.Holder){ - int listWidth=getListWidthForMediaLayout(); - int width=Math.min(listWidth, V.dp(ImageAttachmentFrameLayout.MAX_WIDTH)); - PhotoLayoutHelper.TiledLayoutResult layout=((ImageStatusDisplayItem.Holder) holder).getItem().tiledLayout; - PhotoLayoutHelper.TiledLayoutResult.Tile tile=((ImageStatusDisplayItem.Holder) holder).getItem().thisTile; - if(tile.startCol+tile.colSpan1){ - outRect.bottom=-(Math.round(tile.height/1000f*width)-Math.round(layout.rowSizes[tile.startRow]/1000f*width)); - } - // ...and for its siblings, offset those on rows below first to the right where they belong - if(tile.startCol>0 && layout.tiles[0].rowSpan>1 && tile.startRow>layout.tiles[0].startRow){ - int xOffset=Math.round(layout.tiles[0].width/1000f*listWidth); - outRect.left=xOffset; - outRect.right=-xOffset; - } - - // If the width of the media block is smaller than that of the RecyclerView, offset the views horizontally to center them - if(listWidth>width){ - outRect.left+=(listWidth-V.dp(ImageAttachmentFrameLayout.MAX_WIDTH))/2; - if(tile.startCol>0){ - int spanOffset=0; - for(int i=0;i0 && !Character.isWhitespace(mainEditText.getText().charAt(start-1)) ? " :" : ":"; - mainEditText.getText().replace(start, mainEditText.getSelectionEnd(), prefix+emoji.shortcode+':'); + if(getActivity().getCurrentFocus() instanceof EditText edit){ + int start=edit.getSelectionStart(); + String prefix=start>0 && !Character.isWhitespace(edit.getText().charAt(start-1)) ? " :" : ":"; + edit.getText().replace(start, edit.getSelectionEnd(), prefix+emoji.shortcode+':'); + } } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java index b9597275d..eaf5f4f1b 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java @@ -21,7 +21,6 @@ import org.joinmastodon.android.model.Status; import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem; -import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem; import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper; @@ -97,14 +96,9 @@ public class NotificationsListFragment extends BaseStatusListFragment items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n, false, Filter.FilterContext.NOTIFICATIONS, titleItem); - if(titleItem!=null){ - for(StatusDisplayItem item:items){ - if(item instanceof ImageStatusDisplayItem imgItem){ - imgItem.horizontalInset=V.dp(32); - } - } - } + ArrayList items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n, false, Filter.FilterContext.NOTIFICATIONS); + if(titleItem!=null) + items.add(0, titleItem); return items; }else if(titleItem!=null){ AccountCardStatusDisplayItem card=new AccountCardStatusDisplayItem(n.id, this, diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java index e8b08b6d9..2f0ebfb07 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java @@ -26,7 +26,6 @@ import org.joinmastodon.android.model.Status; import org.joinmastodon.android.ui.PhotoLayoutHelper; import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem; -import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; @@ -133,22 +132,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{ if(holder.getAbsoluteAdapterPosition()==0) return; outRect.left=V.dp(40); - if(holder instanceof ImageStatusDisplayItem.Holder imgHolder){ - PhotoLayoutHelper.TiledLayoutResult layout=imgHolder.getItem().tiledLayout; - PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgHolder.getItem().thisTile; - String siblingID; - if(holder.getAbsoluteAdapterPosition()0) - outRect.left=0; - outRect.left+=V.dp(16); - outRect.right=V.dp(16); - if(!imgHolder.getItemID().equals(siblingID) || tile.startRow+tile.rowSpan==layout.rowSizes.length) - outRect.bottom=V.dp(16); - }else if(holder instanceof AudioStatusDisplayItem.Holder){ + if(holder instanceof AudioStatusDisplayItem.Holder){ outRect.bottom=V.dp(16); }else if(holder instanceof LinkCardStatusDisplayItem.Holder){ outRect.bottom=V.dp(16); @@ -167,10 +151,6 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{ parent.getDecoratedBoundsWithMargins(child, tmpRect); String id=sdiHolder.getItemID(); int height=tmpRect.height(); - if(holder instanceof ImageStatusDisplayItem.Holder imgHolder){ - if(imgHolder.getItem().thisTile.startCol+imgHolder.getItem().thisTile.colSpan buildDisplayItems(Status s){ - List items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null, Filter.FilterContext.HOME); - for(StatusDisplayItem item:items){ - if(item instanceof ImageStatusDisplayItem isdi){ - isdi.horizontalInset=V.dp(40+32); - } - } - return items; - } - protected void drawDivider(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder, RecyclerView parent, Canvas c, Paint paint){ parent.getDecoratedBoundsWithMargins(child, tmpRect); tmpRect.offset(0, Math.round(child.getTranslationY())); diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/PhotoLayoutHelper.java b/mastodon/src/main/java/org/joinmastodon/android/ui/PhotoLayoutHelper.java index 9f1a653a5..fe3accc4f 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/PhotoLayoutHelper.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/PhotoLayoutHelper.java @@ -11,8 +11,14 @@ import java.util.List; import androidx.annotation.NonNull; public class PhotoLayoutHelper{ + public static final int MAX_WIDTH=1000; + public static final int MAX_HEIGHT=1910; + @NonNull - public static TiledLayoutResult processThumbs(int _maxW, int _maxH, List thumbs){ + public static TiledLayoutResult processThumbs(List thumbs){ + int _maxW=MAX_WIDTH; + int _maxH=MAX_HEIGHT; + TiledLayoutResult result=new TiledLayoutResult(); if(thumbs.size()==1){ Attachment att=thumbs.get(0); @@ -45,13 +51,8 @@ public class PhotoLayoutHelper{ float avgRatio=!ratios.isEmpty() ? sum(ratios)/ratios.size() : 1.0f; float maxW, maxH, marginW=0, marginH=0; - if(_maxW>0){ - maxW=_maxW; - maxH=_maxH; - }else{ - maxW=510; - maxH=510; - } + maxW=_maxW; + maxH=_maxH; float maxRatio=maxW/maxH; diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/GifVStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/GifVStatusDisplayItem.java deleted file mode 100644 index 8461121b8..000000000 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/GifVStatusDisplayItem.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.joinmastodon.android.ui.displayitems; - -import android.app.Activity; -import android.graphics.Outline; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewOutlineProvider; - -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.PhotoLayoutHelper; - -import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; - -public class GifVStatusDisplayItem extends ImageStatusDisplayItem{ - public GifVStatusDisplayItem(String parentID, Status status, Attachment attachment, BaseStatusListFragment parentFragment, int index, int totalPhotos, PhotoLayoutHelper.TiledLayoutResult tiledLayout, PhotoLayoutHelper.TiledLayoutResult.Tile thisTile){ - super(parentID, parentFragment, attachment, status, index, totalPhotos, tiledLayout, thisTile); - request=new UrlImageLoaderRequest(attachment.previewUrl, 1000, 1000); - } - - @Override - public Type getType(){ - return Type.GIFV; - } - - public static class Holder extends ImageStatusDisplayItem.Holder{ - - public Holder(Activity activity, ViewGroup parent){ - super(activity, R.layout.display_item_gifv, parent); - View play=findViewById(R.id.play_button); - play.setOutlineProvider(new ViewOutlineProvider(){ - @Override - public void getOutline(View view, Outline outline){ - outline.setOval(0, 0, view.getWidth(), view.getHeight()); - outline.setAlpha(.99f); // fixes shadow rendering - } - }); - } - } -} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ImageStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ImageStatusDisplayItem.java deleted file mode 100644 index b76cb997b..000000000 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ImageStatusDisplayItem.java +++ /dev/null @@ -1,244 +0,0 @@ -package org.joinmastodon.android.ui.displayitems; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.app.Activity; -import android.graphics.drawable.Drawable; -import android.text.TextUtils; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.widget.FrameLayout; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.TextView; - -import org.joinmastodon.android.GlobalUserPreferences; -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.PhotoLayoutHelper; -import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable; -import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost; -import org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout; - -import androidx.annotation.LayoutRes; -import me.grishka.appkit.imageloader.ImageLoaderViewHolder; -import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; -import me.grishka.appkit.utils.CubicBezierInterpolator; - -public abstract class ImageStatusDisplayItem extends StatusDisplayItem{ - public final int index; - public final int totalPhotos; - protected Attachment attachment; - protected ImageLoaderRequest request; - public final Status status; - public final PhotoLayoutHelper.TiledLayoutResult tiledLayout; - public final PhotoLayoutHelper.TiledLayoutResult.Tile thisTile; - public int horizontalInset; - - public ImageStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Attachment photo, Status status, int index, int totalPhotos, PhotoLayoutHelper.TiledLayoutResult tiledLayout, PhotoLayoutHelper.TiledLayoutResult.Tile thisTile){ - super(parentID, parentFragment); - this.attachment=photo; - this.status=status; - this.index=index; - this.totalPhotos=totalPhotos; - this.tiledLayout=tiledLayout; - this.thisTile=thisTile; - } - - @Override - public int getImageCount(){ - return 1; - } - - @Override - public ImageLoaderRequest getImageRequest(int index){ - return request; - } - - public static abstract class Holder extends StatusDisplayItem.Holder implements ImageLoaderViewHolder{ - public final ImageView photo; - private ImageAttachmentFrameLayout layout; - private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable(); - private boolean didClear; - - private AnimatorSet currentAnim; - private final FrameLayout altTextWrapper; - private final TextView altTextButton; - private final ImageView noAltTextButton; - private final View altTextScroller; - private final ImageButton altTextClose; - private final TextView altText, noAltText; - - private View altOrNoAltButton; - private boolean altTextShown; - - public Holder(Activity activity, @LayoutRes int layout, ViewGroup parent){ - super(activity, layout, parent); - photo=findViewById(R.id.photo); - photo.setOnClickListener(this::onViewClick); - this.layout=(ImageAttachmentFrameLayout)itemView; - - altTextWrapper=findViewById(R.id.alt_text_wrapper); - altTextButton=findViewById(R.id.alt_button); - noAltTextButton=findViewById(R.id.no_alt_button); - altTextScroller=findViewById(R.id.alt_text_scroller); - altTextClose=findViewById(R.id.alt_text_close); - altText=findViewById(R.id.alt_text); - noAltText=findViewById(R.id.no_alt_text); - - altTextButton.setOnClickListener(this::onShowHideClick); - noAltTextButton.setOnClickListener(this::onShowHideClick); - altTextClose.setOnClickListener(this::onShowHideClick); -// altTextScroller.setNestedScrollingEnabled(true); - } - - @Override - public void onBind(ImageStatusDisplayItem item){ - layout.setLayout(item.tiledLayout, item.thisTile, item.horizontalInset); - crossfadeDrawable.setSize(item.attachment.getWidth(), item.attachment.getHeight()); - crossfadeDrawable.setBlurhashDrawable(item.attachment.blurhashPlaceholder); - crossfadeDrawable.setCrossfadeAlpha(item.status.spoilerRevealed ? 0f : 1f); - photo.setImageDrawable(null); - photo.setImageDrawable(crossfadeDrawable); - photo.setContentDescription(TextUtils.isEmpty(item.attachment.description) ? item.parentFragment.getString(R.string.media_no_description) : item.attachment.description); - didClear=false; - - if (currentAnim != null) currentAnim.cancel(); - - boolean altTextMissing = TextUtils.isEmpty(item.attachment.description); - altOrNoAltButton = altTextMissing ? noAltTextButton : altTextButton; - altTextShown=false; - - altTextScroller.setVisibility(View.GONE); - altTextClose.setVisibility(View.GONE); - altTextButton.setVisibility(View.VISIBLE); - noAltTextButton.setVisibility(View.VISIBLE); - altTextButton.setAlpha(1f); - noAltTextButton.setAlpha(1f); - altTextWrapper.setVisibility(View.VISIBLE); - - if (altTextMissing){ - if (GlobalUserPreferences.showNoAltIndicator) { - noAltTextButton.setVisibility(View.VISIBLE); - noAltText.setVisibility(View.VISIBLE); - altTextWrapper.setBackgroundResource(R.drawable.bg_image_no_alt_overlay); - altTextButton.setVisibility(View.GONE); - altText.setVisibility(View.GONE); - } else { - altTextWrapper.setVisibility(View.GONE); - } - }else{ - if (GlobalUserPreferences.showAltIndicator) { - noAltTextButton.setVisibility(View.GONE); - noAltText.setVisibility(View.GONE); - altTextWrapper.setBackgroundResource(R.drawable.bg_image_alt_overlay); - altTextButton.setVisibility(View.VISIBLE); - altTextButton.setText(R.string.sk_alt_button); - altText.setVisibility(View.VISIBLE); - altText.setText(item.attachment.description); - altText.setPadding(0, 0, 0, 0); - } else { - altTextWrapper.setVisibility(View.GONE); - } - } - } - - private void onShowHideClick(View v){ - boolean show=v.getId()==R.id.alt_button || v.getId()==R.id.no_alt_button; - - if(altTextShown==show) - return; - if(currentAnim!=null) - currentAnim.cancel(); - - altTextShown=show; - if(show){ - altTextScroller.setVisibility(View.VISIBLE); - altTextClose.setVisibility(View.VISIBLE); - }else{ - altOrNoAltButton.setVisibility(View.VISIBLE); - // Hide these views temporarily so FrameLayout measures correctly - altTextScroller.setVisibility(View.GONE); - altTextClose.setVisibility(View.GONE); - } - - // This is the current size... - int prevLeft=altTextWrapper.getLeft(); - int prevRight=altTextWrapper.getRight(); - int prevTop=altTextWrapper.getTop(); - altTextWrapper.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){ - @Override - public boolean onPreDraw(){ - altTextWrapper.getViewTreeObserver().removeOnPreDrawListener(this); - - // ...and this is after the layout pass, right now the FrameLayout has its final size, but we animate that change - if(!show){ - // Show these views again so they're visible for the duration of the animation. - // No one would notice they were missing during measure/layout. - altTextScroller.setVisibility(View.VISIBLE); - altTextClose.setVisibility(View.VISIBLE); - } - AnimatorSet set=new AnimatorSet(); - set.playTogether( - ObjectAnimator.ofInt(altTextWrapper, "left", prevLeft, altTextWrapper.getLeft()), - ObjectAnimator.ofInt(altTextWrapper, "right", prevRight, altTextWrapper.getRight()), - ObjectAnimator.ofInt(altTextWrapper, "top", prevTop, altTextWrapper.getTop()), - ObjectAnimator.ofFloat(altOrNoAltButton, View.ALPHA, show ? 1f : 0f, show ? 0f : 1f), - ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, show ? 0f : 1f, show ? 1f : 0f), - ObjectAnimator.ofFloat(altTextClose, View.ALPHA, show ? 0f : 1f, show ? 1f : 0f) - ); - set.setDuration(300); - set.setInterpolator(CubicBezierInterpolator.DEFAULT); - set.addListener(new AnimatorListenerAdapter(){ - @Override - public void onAnimationEnd(Animator animation){ - if(show){ - altOrNoAltButton.setVisibility(View.GONE); - }else{ - altTextScroller.setVisibility(View.GONE); - altTextClose.setVisibility(View.GONE); - } - currentAnim=null; - } - }); - set.start(); - currentAnim=set; - - return true; - } - }); - } - - @Override - public void setImage(int index, Drawable drawable){ - crossfadeDrawable.setImageDrawable(drawable); - if(didClear && item.status.spoilerRevealed) - crossfadeDrawable.animateAlpha(0f); - } - - @Override - public void clearImage(int index){ - crossfadeDrawable.setCrossfadeAlpha(1f); - crossfadeDrawable.setImageDrawable(null); - didClear=true; - } - - private void onViewClick(View v){ - if(!item.status.spoilerRevealed){ - item.parentFragment.onRevealSpoilerClick(this); - }else if(item.parentFragment instanceof PhotoViewerHost){ - Status contentStatus=item.status.reblog!=null ? item.status.reblog : item.status; - ((PhotoViewerHost) item.parentFragment).openPhotoViewer(item.parentID, item.status, contentStatus.mediaAttachments.indexOf(item.attachment)); - } - } - - public void setRevealed(boolean revealed){ - crossfadeDrawable.animateAlpha(revealed ? 0f : 1f); - } - } -} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/MediaGridStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/MediaGridStatusDisplayItem.java new file mode 100644 index 000000000..5f0daeb3e --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/MediaGridStatusDisplayItem.java @@ -0,0 +1,292 @@ +package org.joinmastodon.android.ui.displayitems; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.app.Activity; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.FrameLayout; +import android.widget.ImageButton; +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.PhotoLayoutHelper; +import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost; +import org.joinmastodon.android.ui.utils.MediaAttachmentViewController; +import org.joinmastodon.android.ui.views.FrameLayoutThatOnlyMeasuresFirstChild; +import org.joinmastodon.android.ui.views.MediaGridLayout; +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; + +public class MediaGridStatusDisplayItem extends StatusDisplayItem{ + private static final String TAG="MediaGridDisplayItem"; + + private final PhotoLayoutHelper.TiledLayoutResult tiledLayout; + private final TypedObjectPool viewPool; + private final List attachments; + private final ArrayList requests=new ArrayList<>(); + public final Status status; + + public MediaGridStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, PhotoLayoutHelper.TiledLayoutResult tiledLayout, List attachments, Status status){ + super(parentID, parentFragment); + this.tiledLayout=tiledLayout; + this.viewPool=parentFragment.getAttachmentViewsPool(); + 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; + default -> throw new IllegalStateException("Unexpected value: "+att.type); + }, 1000, 1000)); + } + } + + @Override + public Type getType(){ + return Type.MEDIA_GRID; + } + + @Override + public int getImageCount(){ + return requests.size(); + } + + @Override + public ImageLoaderRequest getImageRequest(int index){ + return requests.get(index); + } + + public enum GridItemType{ + PHOTO, + VIDEO, + GIFV + } + + public static class Holder extends StatusDisplayItem.Holder implements ImageLoaderViewHolder{ + private final FrameLayout wrapper; + private final MediaGridLayout layout; + private final View.OnClickListener clickListener=this::onViewClick, altTextClickListener=this::onAltTextClick; + private final ArrayList controllers=new ArrayList<>(); + + private final FrameLayout altTextWrapper; + private final TextView altTextButton; + private final View altTextScroller; + private final ImageButton altTextClose; + private final TextView altText; + + private int altTextIndex=-1; + private Animator altTextAnimator; + + public Holder(Activity activity, ViewGroup parent){ + super(new FrameLayoutThatOnlyMeasuresFirstChild(activity)); + wrapper=(FrameLayout)itemView; + layout=new MediaGridLayout(activity); + wrapper.addView(layout); + + activity.getLayoutInflater().inflate(R.layout.overlay_image_alt_text, wrapper); + altTextWrapper=findViewById(R.id.alt_text_wrapper); + altTextButton=findViewById(R.id.alt_button); + altTextScroller=findViewById(R.id.alt_text_scroller); + altTextClose=findViewById(R.id.alt_text_close); + altText=findViewById(R.id.alt_text); + altTextClose.setOnClickListener(this::onAltTextCloseClick); + } + + @Override + public void onBind(MediaGridStatusDisplayItem item){ + if(altTextAnimator!=null) + altTextAnimator.cancel(); + + layout.setTiledLayout(item.tiledLayout); + for(MediaAttachmentViewController c:controllers){ + item.viewPool.reuse(c.type, c); + } + layout.removeAllViews(); + controllers.clear(); + int i=0; + for(Attachment att:item.attachments){ + MediaAttachmentViewController c=item.viewPool.obtain(switch(att.type){ + case IMAGE -> GridItemType.PHOTO; + case VIDEO -> GridItemType.VIDEO; + case GIFV -> GridItemType.GIFV; + default -> throw new IllegalStateException("Unexpected value: "+att.type); + }); + if(c.view.getLayoutParams()==null) + c.view.setLayoutParams(new MediaGridLayout.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); + if(c.altButton!=null){ + c.altButton.setOnClickListener(altTextClickListener); + c.altButton.setTag(i); + c.altButton.setAlpha(1f); + } + controllers.add(c); + c.bind(att, item.status); + i++; + } + altTextWrapper.setVisibility(View.GONE); + altTextIndex=-1; + } + + @Override + public void setImage(int index, Drawable drawable){ + controllers.get(index).setImage(drawable); + } + + @Override + public void clearImage(int index){ + controllers.get(index).clearImage(); + } + + private void onViewClick(View v){ + int index=(Integer)v.getTag(); + if(!item.status.spoilerRevealed){ + item.parentFragment.onRevealSpoilerClick(this); + }else if(item.parentFragment instanceof PhotoViewerHost){ + ((PhotoViewerHost) item.parentFragment).openPhotoViewer(item.parentID, item.status, index, this); + } + } + + private void onAltTextClick(View v){ + if(altTextAnimator!=null) + altTextAnimator.cancel(); + v.setVisibility(View.INVISIBLE); + int index=(Integer)v.getTag(); + altTextIndex=index; + Attachment att=item.attachments.get(index); + altText.setText(att.description); + altTextWrapper.setVisibility(View.VISIBLE); + altTextWrapper.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){ + @Override + public boolean onPreDraw(){ + altTextWrapper.getViewTreeObserver().removeOnPreDrawListener(this); + + int[] loc={0, 0}; + v.getLocationInWindow(loc); + int btnL=loc[0], btnT=loc[1]; + wrapper.getLocationInWindow(loc); + btnL-=loc[0]; + btnT-=loc[1]; + + ArrayList anims=new ArrayList<>(); + anims.add(ObjectAnimator.ofFloat(altTextButton, View.ALPHA, 1, 0)); + anims.add(ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, 0, 1)); + anims.add(ObjectAnimator.ofFloat(altTextClose, View.ALPHA, 0, 1)); + anims.add(ObjectAnimator.ofInt(altTextWrapper, "left", btnL, altTextWrapper.getLeft())); + anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT, altTextWrapper.getTop())); + anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+v.getWidth(), altTextWrapper.getRight())); + anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+v.getHeight(), altTextWrapper.getBottom())); + for(Animator a:anims) + a.setDuration(300); + + for(MediaAttachmentViewController c:controllers){ + if(c.altButton!=null && c.altButton!=v){ + anims.add(ObjectAnimator.ofFloat(c.altButton, View.ALPHA, 1, 0).setDuration(150)); + } + } + + AnimatorSet set=new AnimatorSet(); + set.playTogether(anims); + set.setInterpolator(CubicBezierInterpolator.DEFAULT); + set.addListener(new AnimatorListenerAdapter(){ + @Override + public void onAnimationEnd(Animator animation){ + altTextAnimator=null; + for(MediaAttachmentViewController c:controllers){ + if(c.altButton!=null){ + c.altButton.setVisibility(View.INVISIBLE); + } + } + } + }); + altTextAnimator=set; + set.start(); + + return true; + } + }); + } + + private void onAltTextCloseClick(View v){ + if(altTextAnimator!=null) + altTextAnimator.cancel(); + + View btn=controllers.get(altTextIndex).altButton; + for(MediaAttachmentViewController c:controllers){ + if(c.altButton!=null && c.altButton!=btn) + c.altButton.setVisibility(View.VISIBLE); + } + + int[] loc={0, 0}; + btn.getLocationInWindow(loc); + int btnL=loc[0], btnT=loc[1]; + wrapper.getLocationInWindow(loc); + btnL-=loc[0]; + btnT-=loc[1]; + + ArrayList anims=new ArrayList<>(); + anims.add(ObjectAnimator.ofFloat(altTextButton, View.ALPHA, 1)); + anims.add(ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, 0)); + anims.add(ObjectAnimator.ofFloat(altTextClose, View.ALPHA, 0)); + anims.add(ObjectAnimator.ofInt(altTextWrapper, "left", btnL)); + anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT)); + anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+btn.getWidth())); + anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+btn.getHeight())); + for(Animator a:anims) + a.setDuration(300); + + for(MediaAttachmentViewController c:controllers){ + if(c.altButton!=null && c.altButton!=btn){ + anims.add(ObjectAnimator.ofFloat(c.altButton, View.ALPHA, 1).setDuration(150)); + } + } + + AnimatorSet set=new AnimatorSet(); + set.playTogether(anims); + set.setInterpolator(CubicBezierInterpolator.DEFAULT); + set.addListener(new AnimatorListenerAdapter(){ + @Override + public void onAnimationEnd(Animator animation){ + altTextAnimator=null; + altTextWrapper.setVisibility(View.GONE); + btn.setVisibility(View.VISIBLE); + } + }); + altTextAnimator=set; + set.start(); + } + + public void setRevealed(boolean revealed){ + for(MediaAttachmentViewController c:controllers){ + c.setRevealed(revealed); + } + } + + public MediaAttachmentViewController getViewController(int index){ + return controllers.get(index); + } + + public void setClipChildren(boolean clip){ + layout.setClipChildren(clip); + wrapper.setClipChildren(clip); + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PhotoStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PhotoStatusDisplayItem.java deleted file mode 100644 index a0609d10c..000000000 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PhotoStatusDisplayItem.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.joinmastodon.android.ui.displayitems; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.app.Activity; -import android.text.TextUtils; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.widget.FrameLayout; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.ScrollView; -import android.widget.TextView; - -import org.joinmastodon.android.GlobalUserPreferences; -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.PhotoLayoutHelper; - -import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; -import me.grishka.appkit.utils.CubicBezierInterpolator; -import me.grishka.appkit.utils.V; - -public class PhotoStatusDisplayItem extends ImageStatusDisplayItem{ - public PhotoStatusDisplayItem(String parentID, Status status, Attachment photo, BaseStatusListFragment parentFragment, int index, int totalPhotos, PhotoLayoutHelper.TiledLayoutResult tiledLayout, PhotoLayoutHelper.TiledLayoutResult.Tile thisTile){ - super(parentID, parentFragment, photo, status, index, totalPhotos, tiledLayout, thisTile); - request=new UrlImageLoaderRequest(photo.url, 1000, 1000); - } - - @Override - public Type getType(){ - return Type.PHOTO; - } - - public static class Holder extends ImageStatusDisplayItem.Holder { - public Holder(Activity activity, ViewGroup parent) { - super(activity, R.layout.display_item_photo, parent); - } - } -} 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 c95fb4b1d..22c5eed75 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 @@ -70,10 +70,7 @@ public abstract class StatusDisplayItem{ case HEADER -> new HeaderStatusDisplayItem.Holder(activity, parent); case REBLOG_OR_REPLY_LINE -> new ReblogOrReplyLineStatusDisplayItem.Holder(activity, parent); case TEXT -> new TextStatusDisplayItem.Holder(activity, parent); - case PHOTO -> new PhotoStatusDisplayItem.Holder(activity, parent); - case GIFV -> new GifVStatusDisplayItem.Holder(activity, parent); case AUDIO -> new AudioStatusDisplayItem.Holder(activity, parent); - case VIDEO -> new VideoStatusDisplayItem.Holder(activity, parent); case POLL_OPTION -> new PollOptionStatusDisplayItem.Holder(activity, parent); case POLL_FOOTER -> new PollFooterStatusDisplayItem.Holder(activity, parent); case CARD -> new LinkCardStatusDisplayItem.Holder(activity, parent); @@ -83,6 +80,7 @@ public abstract class StatusDisplayItem{ case HASHTAG -> new HashtagStatusDisplayItem.Holder(activity, parent); case GAP -> new GapStatusDisplayItem.Holder(activity, parent); case EXTENDED_FOOTER -> new ExtendedFooterStatusDisplayItem.Holder(activity, parent); + case MEDIA_GRID -> new MediaGridStatusDisplayItem.Holder(activity, parent); case WARNING -> new WarningFilteredStatusDisplayItem.Holder(activity, parent); }; } @@ -161,20 +159,8 @@ public abstract class StatusDisplayItem{ header.needBottomPadding=true; List imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList()); if(!imageAttachments.isEmpty()){ - int photoIndex=0; - PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(1000, 1910, imageAttachments); - for(Attachment attachment:imageAttachments){ - if(attachment.type==Attachment.Type.IMAGE){ - items.add(new PhotoStatusDisplayItem(parentID, statusForContent, attachment, fragment, photoIndex, imageAttachments.size(), layout, layout.tiles[photoIndex])); - }else if(attachment.type==Attachment.Type.GIFV){ - items.add(new GifVStatusDisplayItem(parentID, statusForContent, attachment, fragment, photoIndex, imageAttachments.size(), layout, layout.tiles[photoIndex])); - }else if(attachment.type==Attachment.Type.VIDEO){ - items.add(new VideoStatusDisplayItem(parentID, statusForContent, attachment, fragment, photoIndex, imageAttachments.size(), layout, layout.tiles[photoIndex])); - }else{ - throw new IllegalStateException("This isn't supposed to happen, type is "+attachment.type); - } - photoIndex++; - } + PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(imageAttachments); + items.add(new MediaGridStatusDisplayItem(parentID, fragment, layout, imageAttachments, statusForContent)); } for(Attachment att:statusForContent.mediaAttachments){ if(att.type==Attachment.Type.AUDIO){ @@ -222,9 +208,6 @@ public abstract class StatusDisplayItem{ HEADER, REBLOG_OR_REPLY_LINE, TEXT, - PHOTO, - VIDEO, - GIFV, AUDIO, POLL_OPTION, POLL_FOOTER, @@ -236,6 +219,7 @@ public abstract class StatusDisplayItem{ GAP, WARNING, EXTENDED_FOOTER + MEDIA_GRID } public static abstract class Holder extends BindableViewHolder implements UsableRecyclerView.DisableableClickable{ diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/VideoStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/VideoStatusDisplayItem.java deleted file mode 100644 index 41c54aeef..000000000 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/VideoStatusDisplayItem.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.joinmastodon.android.ui.displayitems; - -import android.app.Activity; -import android.graphics.Outline; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewOutlineProvider; - -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.PhotoLayoutHelper; - -import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; - -public class VideoStatusDisplayItem extends ImageStatusDisplayItem{ - public VideoStatusDisplayItem(String parentID, Status status, Attachment attachment, BaseStatusListFragment parentFragment, int index, int totalPhotos, PhotoLayoutHelper.TiledLayoutResult tiledLayout, PhotoLayoutHelper.TiledLayoutResult.Tile thisTile){ - super(parentID, parentFragment, attachment, status, index, totalPhotos, tiledLayout, thisTile); - request=new UrlImageLoaderRequest(attachment.previewUrl, 1000, 1000); - } - - @Override - public Type getType(){ - return Type.VIDEO; - } - - public static class Holder extends ImageStatusDisplayItem.Holder{ - - public Holder(Activity activity, ViewGroup parent){ - super(activity, R.layout.display_item_video, parent); - View play=findViewById(R.id.play_button); - play.setOutlineProvider(new ViewOutlineProvider(){ - @Override - public void getOutline(View view, Outline outline){ - outline.setOval(0, 0, view.getWidth(), view.getHeight()); - outline.setAlpha(.99f); // fixes shadow rendering - } - }); - } - } -} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewerHost.java b/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewerHost.java index 72dcb47ce..fc168087b 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewerHost.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewerHost.java @@ -1,7 +1,8 @@ package org.joinmastodon.android.ui.photoviewer; import org.joinmastodon.android.model.Status; +import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem; public interface PhotoViewerHost{ - void openPhotoViewer(String parentID, Status status, int attachmentIndex); + void openPhotoViewer(String parentID, Status status, int attachmentIndex, MediaGridStatusDisplayItem.Holder gridHolder); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/InsetStatusItemDecoration.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/InsetStatusItemDecoration.java index 73ca6f2f9..d16639917 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/InsetStatusItemDecoration.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/InsetStatusItemDecoration.java @@ -8,10 +8,9 @@ import android.view.View; import org.joinmastodon.android.R; import org.joinmastodon.android.fragments.BaseStatusListFragment; -import org.joinmastodon.android.fragments.NotificationsListFragment; import org.joinmastodon.android.ui.PhotoLayoutHelper; -import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem; +import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import java.util.List; @@ -87,21 +86,11 @@ public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{ boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset; boolean bottomSiblingInset=pos img){ - PhotoLayoutHelper.TiledLayoutResult layout=img.getItem().tiledLayout; - PhotoLayoutHelper.TiledLayoutResult.Tile tile=img.getItem().thisTile; - // only inset those items that are on the edges of the layout - insetLeft=tile.startCol==0; - insetRight=tile.startCol+tile.colSpan==layout.columnSizes.length; - // inset all items in the bottom row - if(tile.startRow+tile.rowSpan==layout.rowSizes.length) - bottomSiblingInset=false; - } if(insetLeft) outRect.left=pad; if(insetRight) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/MediaAttachmentViewController.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/MediaAttachmentViewController.java new file mode 100644 index 000000000..9f269a2c8 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/MediaAttachmentViewController.java @@ -0,0 +1,67 @@ +package org.joinmastodon.android.ui.utils; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; + +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; + +public class MediaAttachmentViewController{ + public final View view; + public final MediaGridStatusDisplayItem.GridItemType type; + public final ImageView photo; + public final View altButton; + private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable(); + private final Context context; + private boolean didClear; + private Status status; + + public MediaAttachmentViewController(Context context, MediaGridStatusDisplayItem.GridItemType type){ + view=context.getSystemService(LayoutInflater.class).inflate(switch(type){ + case PHOTO -> R.layout.display_item_photo; + case VIDEO -> R.layout.display_item_video; + case GIFV -> R.layout.display_item_gifv; + }, null); + photo=view.findViewById(R.id.photo); + altButton=view.findViewById(R.id.alt_button); + this.type=type; + this.context=context; + } + + public void bind(Attachment attachment, Status status){ + this.status=status; + crossfadeDrawable.setSize(attachment.getWidth(), attachment.getHeight()); + crossfadeDrawable.setBlurhashDrawable(attachment.blurhashPlaceholder); + crossfadeDrawable.setCrossfadeAlpha(status.spoilerRevealed ? 0f : 1f); + photo.setImageDrawable(null); + photo.setImageDrawable(crossfadeDrawable); + photo.setContentDescription(TextUtils.isEmpty(attachment.description) ? context.getString(R.string.media_no_description) : attachment.description); + if(altButton!=null){ + altButton.setVisibility(TextUtils.isEmpty(attachment.description) ? View.GONE : View.VISIBLE); + } + didClear=false; + } + + public void setImage(Drawable drawable){ + crossfadeDrawable.setImageDrawable(drawable); + if(didClear && status.spoilerRevealed) + crossfadeDrawable.animateAlpha(0f); + } + + public void clearImage(){ + crossfadeDrawable.setCrossfadeAlpha(1f); + crossfadeDrawable.setImageDrawable(null); + didClear=true; + } + + public void setRevealed(boolean revealed){ + crossfadeDrawable.animateAlpha(revealed ? 0f : 1f); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/FrameLayoutThatOnlyMeasuresFirstChild.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/FrameLayoutThatOnlyMeasuresFirstChild.java new file mode 100644 index 000000000..f195ac633 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/FrameLayoutThatOnlyMeasuresFirstChild.java @@ -0,0 +1,29 @@ +package org.joinmastodon.android.ui.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; + +public class FrameLayoutThatOnlyMeasuresFirstChild extends FrameLayout{ + public FrameLayoutThatOnlyMeasuresFirstChild(Context context){ + this(context, null); + } + + public FrameLayoutThatOnlyMeasuresFirstChild(Context context, AttributeSet attrs){ + this(context, attrs, 0); + } + + public FrameLayoutThatOnlyMeasuresFirstChild(Context context, AttributeSet attrs, int defStyle){ + super(context, attrs, defStyle); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ + if(getChildCount()==0) + return; + View child0=getChildAt(0); + measureChild(child0, widthMeasureSpec, heightMeasureSpec); + super.onMeasure(child0.getMeasuredWidth() | MeasureSpec.EXACTLY, child0.getMeasuredHeight() | MeasureSpec.EXACTLY); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/ImageAttachmentFrameLayout.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/ImageAttachmentFrameLayout.java deleted file mode 100644 index 0bfcbdb16..000000000 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/views/ImageAttachmentFrameLayout.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.joinmastodon.android.ui.views; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.widget.FrameLayout; - -import org.joinmastodon.android.ui.PhotoLayoutHelper; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import me.grishka.appkit.utils.V; - -public class ImageAttachmentFrameLayout extends FrameLayout{ - public static final int MAX_WIDTH=400; // dp - - private PhotoLayoutHelper.TiledLayoutResult tileLayout; - private PhotoLayoutHelper.TiledLayoutResult.Tile tile; - private int horizontalInset; - - public ImageAttachmentFrameLayout(@NonNull Context context){ - super(context); - } - - public ImageAttachmentFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs){ - super(context, attrs); - } - - public ImageAttachmentFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr){ - super(context, attrs, defStyleAttr); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ - if(isInEditMode()){ - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - return; - } - int w=Math.min(((View)getParent()).getMeasuredWidth(), V.dp(MAX_WIDTH))-horizontalInset; - int actualHeight=Math.round(tile.height/1000f*w)+V.dp(1)*(tile.rowSpan-1); - int actualWidth=Math.round(tile.width/1000f*w); - if(tile.startCol+tile.colSpanmaxWidth){ + xOffset=(r-l)/2-maxWidth/2; + } + + for(int i=0;i{ + private final Function producer; + private final HashMap> pool=new HashMap<>(); + + public TypedObjectPool(Function producer){ + this.producer=producer; + } + + public V obtain(K type){ + LinkedList tp=pool.get(type); + if(tp==null) + pool.put(type, tp=new LinkedList<>()); + + V value=tp.poll(); + if(value==null) + value=producer.apply(type); + return value; + } + + public void reuse(K type, V obj){ + Objects.requireNonNull(obj); + Objects.requireNonNull(type); + + LinkedList tp=pool.get(type); + if(tp==null) + pool.put(type, tp=new LinkedList<>()); + tp.add(obj); + } +} diff --git a/mastodon/src/main/res/layout/display_item_gifv.xml b/mastodon/src/main/res/layout/display_item_gifv.xml index a3575ab06..ad9fb13f2 100644 --- a/mastodon/src/main/res/layout/display_item_gifv.xml +++ b/mastodon/src/main/res/layout/display_item_gifv.xml @@ -1,5 +1,5 @@ - @@ -26,5 +26,4 @@ android:background="@drawable/ic_gif"/> - - \ No newline at end of file + \ No newline at end of file diff --git a/mastodon/src/main/res/layout/display_item_photo.xml b/mastodon/src/main/res/layout/display_item_photo.xml index 462dab84a..2ba0d2860 100644 --- a/mastodon/src/main/res/layout/display_item_photo.xml +++ b/mastodon/src/main/res/layout/display_item_photo.xml @@ -1,5 +1,6 @@ - @@ -10,6 +11,22 @@ android:layout_gravity="center" android:scaleType="centerCrop"/> - + + - \ No newline at end of file + \ No newline at end of file diff --git a/mastodon/src/main/res/layout/display_item_video.xml b/mastodon/src/main/res/layout/display_item_video.xml index 1f1283fab..5ecaa5782 100644 --- a/mastodon/src/main/res/layout/display_item_video.xml +++ b/mastodon/src/main/res/layout/display_item_video.xml @@ -1,5 +1,5 @@ - @@ -18,6 +18,4 @@ android:elevation="3dp" android:background="@drawable/play_button"/> - - - \ No newline at end of file + \ No newline at end of file diff --git a/mastodon/src/main/res/layout/overlay_image_alt_text.xml b/mastodon/src/main/res/layout/overlay_image_alt_text.xml new file mode 100644 index 000000000..53f4e5ba6 --- /dev/null +++ b/mastodon/src/main/res/layout/overlay_image_alt_text.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + +