From 6cc6fe195bc0334bc14acbd669ac5db12bb6c635 Mon Sep 17 00:00:00 2001 From: Grishka Date: Sat, 19 Feb 2022 02:32:08 +0300 Subject: [PATCH] Media layout --- .../fragments/BaseStatusListFragment.java | 63 +++- .../fragments/NotificationsFragment.java | 15 +- .../joinmastodon/android/model/Account.java | 2 + .../android/model/Attachment.java | 21 +- .../android/ui/PhotoLayoutHelper.java | 338 ++++++++++++++++++ .../android/ui/TileGridLayoutManager.java | 27 ++ .../displayitems/GifVStatusDisplayItem.java | 5 +- .../displayitems/ImageStatusDisplayItem.java | 13 +- .../displayitems/PhotoStatusDisplayItem.java | 5 +- .../ui/displayitems/StatusDisplayItem.java | 32 +- .../displayitems/VideoStatusDisplayItem.java | 5 +- .../ui/views/ImageAttachmentFrameLayout.java | 52 +++ .../src/main/res/layout/display_item_gifv.xml | 6 +- .../main/res/layout/display_item_photo.xml | 6 +- .../main/res/layout/display_item_video.xml | 6 +- 15 files changed, 554 insertions(+), 42 deletions(-) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/PhotoLayoutHelper.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/TileGridLayoutManager.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/views/ImageAttachmentFrameLayout.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 88d43500b..47732dde3 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java @@ -22,6 +22,8 @@ import org.joinmastodon.android.model.DisplayItemsParent; import org.joinmastodon.android.model.Poll; 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.FooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem; @@ -32,6 +34,7 @@ import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem; import org.joinmastodon.android.ui.photoviewer.PhotoViewer; import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost; import org.joinmastodon.android.ui.utils.UiUtils; +import org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout; import java.util.ArrayList; import java.util.Collections; @@ -193,6 +196,11 @@ public abstract class BaseStatusListFragment exten @Override public void endPhotoViewTransition(){ + // fix drawable callback + Drawable d=transitioningHolder.photo.getDrawable(); + transitioningHolder.photo.setImageDrawable(null); + transitioningHolder.photo.setImageDrawable(d); + View view=transitioningHolder.photo; view.setTranslationX(0f); view.setTranslationY(0f); @@ -265,6 +273,46 @@ 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 width=Math.min(parent.getWidth(), 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*parent.getWidth()); + 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(parent.getWidth()>width){ + outRect.left+=(parent.getWidth()-V.dp(ImageAttachmentFrameLayout.MAX_WIDTH))/2; + if(tile.startCol>0){ + int spanOffset=0; + for(int i=0;i exten @Override protected RecyclerView.LayoutManager onCreateLayoutManager(){ - GridLayoutManager lm=new GridLayoutManager(getActivity(), 2); + GridLayoutManager lm=new TileGridLayoutManager(getActivity(), 1000); lm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup(){ @Override public int getSpanSize(int position){ @@ -303,14 +351,16 @@ public abstract class BaseStatusListFragment exten if(position>=0 && position1){ - int index=((ImageStatusDisplayItem) item).index; - return 1; + PhotoLayoutHelper.TiledLayoutResult layout=((ImageStatusDisplayItem) item).tiledLayout; + PhotoLayoutHelper.TiledLayoutResult.Tile tile=((ImageStatusDisplayItem) item).thisTile; + int spans=0; + for(int i=0;i exten public void onConfigurationChanged(Configuration newConfig){ super.onConfigurationChanged(newConfig); updateToolbar(); + list.invalidateItemDecorations(); } private void updateToolbar(){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsFragment.java index 08a448c2c..efe6d37a2 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsFragment.java @@ -1,18 +1,22 @@ package org.joinmastodon.android.fragments; import android.app.Activity; +import android.os.Bundle; import org.joinmastodon.android.R; import org.joinmastodon.android.api.requests.notifications.GetNotifications; import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.Poll; +import org.joinmastodon.android.model.Status; import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; +import org.parceler.Parcels; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import me.grishka.appkit.Nav; import me.grishka.appkit.api.SimpleCallback; public class NotificationsFragment extends BaseStatusListFragment{ @@ -71,7 +75,16 @@ public class NotificationsFragment extends BaseStatusListFragment{ @Override public void onItemClick(String id){ - + Notification n=getNotificationByID(id); + if(n.status!=null){ + Status status=n.status; + Bundle args=new Bundle(); + args.putString("account", accountID); + args.putParcelable("status", Parcels.wrap(status)); + if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId)) + args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(status.inReplyToAccountId))); + Nav.go(getActivity(), ThreadFragment.class, args); + } } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Account.java b/mastodon/src/main/java/org/joinmastodon/android/model/Account.java index 5c425b6d1..dc8d6ab01 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Account.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Account.java @@ -147,6 +147,8 @@ public class Account extends BaseModel{ } if(moved!=null) moved.postprocess(); + if(TextUtils.isEmpty(displayName)) + displayName=username; } public boolean isLocal(){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Attachment.java b/mastodon/src/main/java/org/joinmastodon/android/model/Attachment.java index ff64abf59..ceccfc3a5 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Attachment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Attachment.java @@ -11,6 +11,8 @@ import org.joinmastodon.android.api.RequiredField; import org.joinmastodon.android.ui.utils.BlurHashDecoder; import org.joinmastodon.android.ui.utils.BlurHashDrawable; import org.parceler.Parcel; +import org.parceler.ParcelConstructor; +import org.parceler.ParcelProperty; @Parcel public class Attachment extends BaseModel{ @@ -23,11 +25,24 @@ public class Attachment extends BaseModel{ public String previewUrl; public String remoteUrl; public String description; + @ParcelProperty("blurhash") public String blurhash; public Metadata meta; public transient Drawable blurhashPlaceholder; + public Attachment(){} + + @ParcelConstructor + public Attachment(@ParcelProperty("blurhash") String blurhash){ + this.blurhash=blurhash; + if(blurhash!=null){ + Bitmap placeholder=BlurHashDecoder.decode(blurhash, 16, 16); + if(placeholder!=null) + blurhashPlaceholder=new BlurHashDrawable(placeholder, getWidth(), getHeight()); + } + } + public int getWidth(){ if(meta==null) return 0; @@ -86,7 +101,11 @@ public class Attachment extends BaseModel{ @SerializedName("audio") AUDIO, @SerializedName("unknown") - UNKNOWN + UNKNOWN; + + public boolean isImage(){ + return this==IMAGE || this==GIFV || this==VIDEO; + } } @Parcel diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/PhotoLayoutHelper.java b/mastodon/src/main/java/org/joinmastodon/android/ui/PhotoLayoutHelper.java new file mode 100644 index 000000000..528c53c28 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/PhotoLayoutHelper.java @@ -0,0 +1,338 @@ +package org.joinmastodon.android.ui; + +import org.joinmastodon.android.model.Attachment; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +import androidx.annotation.NonNull; + +public class PhotoLayoutHelper{ + @NonNull + public static TiledLayoutResult processThumbs(int _maxW, int _maxH, List thumbs){ + TiledLayoutResult result=new TiledLayoutResult(); + if(thumbs.size()==1){ + Attachment att=thumbs.get(0); + result.rowSizes=result.columnSizes=new int[]{1}; + if(att.getWidth()>att.getHeight()){ + result.width=_maxW; + result.height=Math.round(att.getHeight()/(float)att.getWidth()*_maxW); + }else{ + result.height=_maxH; + result.width=Math.round(att.getWidth()/(float)att.getHeight()*_maxH); + } + result.tiles=new TiledLayoutResult.Tile[]{new TiledLayoutResult.Tile(1, 1, result.width, result.height, 0, 0)}; + }else if(thumbs.size()==0){ + throw new IllegalArgumentException("Empty thumbs array"); + } + + String orients=""; + ArrayList ratios=new ArrayList(); + int cnt=thumbs.size(); + + + for(Attachment thumb : thumbs){ +// float ratio=thumb.isSizeKnown() ? thumb.getWidth()/(float) thumb.getHeight() : 1f; + float ratio=thumb.getWidth()/(float) thumb.getHeight(); + char orient=ratio>1.2 ? 'w' : (ratio<0.8 ? 'n' : 'q'); + orients+=orient; + ratios.add(ratio); + } + + 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; + } + + float maxRatio=maxW/maxH; + + if(cnt==2){ + if(orients.equals("ww") && avgRatio>1.4*maxRatio && (ratios.get(1)-ratios.get(0))<0.2){ // two wide photos, one above the other + float h=Math.min(maxW/ratios.get(0), Math.min(maxW/ratios.get(1), (maxH-marginH)/2.0f)); + + result.width=Math.round(maxW); + result.height=Math.round(h*2+marginH); + result.columnSizes=new int[]{result.width}; + result.rowSizes=new int[]{Math.round(h), Math.round(h)}; + result.tiles=new TiledLayoutResult.Tile[]{ + new TiledLayoutResult.Tile(1, 1, maxW, h, 0, 0), + new TiledLayoutResult.Tile(1, 1, maxW, h, 0, 1) + }; + }else if(orients.equals("ww") || orients.equals("qq")){ // next to each other, same ratio + float w=((maxW-marginW)/2); + float h=Math.min(w/ratios.get(0), Math.min(w/ratios.get(1), maxH)); + + result.width=Math.round(maxW); + result.height=Math.round(h); + result.columnSizes=new int[]{Math.round(w), _maxW-Math.round(w)}; + result.rowSizes=new int[]{Math.round(h)}; + result.tiles=new TiledLayoutResult.Tile[]{ + new TiledLayoutResult.Tile(1, 1, w, h, 0, 0), + new TiledLayoutResult.Tile(1, 1, w, h, 1, 0) + }; + }else{ // next to each other, different ratios + float w0=((maxW-marginW)/ratios.get(1)/(1/ratios.get(0)+1/ratios.get(1))); + float w1=(maxW-w0-marginW); + float h=Math.min(maxH, Math.min(w0/ratios.get(0), w1/ratios.get(1))); + + result.columnSizes=new int[]{Math.round(w0), Math.round(w1)}; + result.rowSizes=new int[]{Math.round(h)}; + result.width=Math.round(w0+w1+marginW); + result.height=Math.round(h); + result.tiles=new TiledLayoutResult.Tile[]{ + new TiledLayoutResult.Tile(1, 1, w0, h, 0, 0), + new TiledLayoutResult.Tile(1, 1, w1, h, 1, 0) + }; + } + }else if(cnt==3){ + if(/*(ratios.get(0) > 1.2 * maxRatio || avgRatio > 1.5 * maxRatio) &&*/ orients.equals("www")){ // 2nd and 3rd photos are on the next line + float hCover=Math.min(maxW/ratios.get(0), (maxH-marginH)*0.66f); + float w2=((maxW-marginW)/2); + float h=Math.min(maxH-hCover-marginH, Math.min(w2/ratios.get(1), w2/ratios.get(2))); + result.width=Math.round(maxW); + result.height=Math.round(hCover+h+marginH); + result.columnSizes=new int[]{Math.round(w2), _maxW-Math.round(w2)}; + result.rowSizes=new int[]{Math.round(hCover), Math.round(h)}; + result.tiles=new TiledLayoutResult.Tile[]{ + new TiledLayoutResult.Tile(2, 1, maxW, hCover, 0, 0), + new TiledLayoutResult.Tile(1, 1, w2, h, 0, 1), + new TiledLayoutResult.Tile(1, 1, w2, h, 1, 1) + }; + }else{ // 2nd and 3rd photos are on the right part + float wCover=Math.min(maxH*ratios.get(0), (maxW-marginW)*0.75f); + float h1=(ratios.get(1)*(maxH-marginH)/(ratios.get(2)+ratios.get(1))); + float h0=(maxH-h1-marginH); + float w=Math.min(maxW-wCover-marginW, Math.min(h1*ratios.get(2), h0*ratios.get(1))); + result.width=Math.round(wCover+w+marginW); + result.height=Math.round(maxH); + result.columnSizes=new int[]{Math.round(wCover), Math.round(w)}; + result.rowSizes=new int[]{Math.round(h0), Math.round(h1)}; + result.tiles=new TiledLayoutResult.Tile[]{ + new TiledLayoutResult.Tile(1, 2, wCover, maxH, 0, 0), + new TiledLayoutResult.Tile(1, 1, w, h0, 1, 0), + new TiledLayoutResult.Tile(1, 1, w, h1, 1, 1) + }; + } + }else if(cnt==4){ + if(/*(ratios.get(0) > 1.2 * maxRatio || avgRatio > 1.5 * maxRatio) &&*/ orients.equals("wwww")){ // 2nd, 3rd and 4th photos are on the next line + float hCover=Math.min(maxW/ratios.get(0), (maxH-marginH)*0.66f); + float h=(maxW-2*marginW)/(ratios.get(1)+ratios.get(2)+ratios.get(3)); + float w0=h*ratios.get(1); + float w1=h*ratios.get(2); + float w2=h*ratios.get(3); + h=Math.min(maxH-hCover-marginH, h); + result.width=Math.round(maxW); + result.height=Math.round(hCover+h+marginH); + result.columnSizes=new int[]{Math.round(w0), Math.round(w1), _maxW-Math.round(w0)-Math.round(w1)}; + result.rowSizes=new int[]{Math.round(hCover), Math.round(h)}; + result.tiles=new TiledLayoutResult.Tile[]{ + new TiledLayoutResult.Tile(3, 1, maxW, hCover, 0, 0), + new TiledLayoutResult.Tile(1, 1, w0, h, 0, 1), + new TiledLayoutResult.Tile(1, 1, w1, h, 1, 1), + new TiledLayoutResult.Tile(1, 1, w2, h, 2, 1), + }; + }else{ // 2nd, 3rd and 4th photos are on the right part + float wCover= Math.min(maxH*ratios.get(0), (maxW-marginW)*0.66f); + float w=(maxH-2*marginH)/(1/ratios.get(1)+1/ratios.get(2)+1/ratios.get(3)); + float h0=w/ratios.get(1); + float h1=w/ratios.get(2); + float h2=w/ratios.get(3)+marginH; + w=Math.min(maxW-wCover-marginW, w); + result.width=Math.round(wCover+marginW+w); + result.height=Math.round(maxH); + result.columnSizes=new int[]{Math.round(wCover), Math.round(w)}; + result.rowSizes=new int[]{Math.round(h0), Math.round(h1), Math.round(h2)}; + result.tiles=new TiledLayoutResult.Tile[]{ + new TiledLayoutResult.Tile(1, 3, wCover, maxH, 0, 0), + new TiledLayoutResult.Tile(1, 1, w, h0, 1, 0), + new TiledLayoutResult.Tile(1, 1, w, h1, 1, 1), + new TiledLayoutResult.Tile(1, 1, w, h2, 1, 2), + }; + } + }else{ + ArrayList ratiosCropped=new ArrayList(); + if(avgRatio>1.1){ + for(float ratio : ratios){ + ratiosCropped.add(Math.max(1.0f, ratio)); + } + }else{ + for(float ratio : ratios){ + ratiosCropped.add(Math.min(1.0f, ratio)); + } + } + + HashMap tries=new HashMap<>(); + + // One line + int firstLine, secondLine, thirdLine; + tries.put(new int[]{firstLine=cnt}, new float[]{calculateMultiThumbsHeight(ratiosCropped, maxW, marginW)}); + + // Two lines + for(firstLine=1; firstLine<=cnt-1; firstLine++){ + tries.put(new int[]{firstLine, secondLine=cnt-firstLine}, new float[]{ + calculateMultiThumbsHeight(ratiosCropped.subList(0, firstLine), maxW, marginW), + calculateMultiThumbsHeight(ratiosCropped.subList(firstLine, ratiosCropped.size()), maxW, marginW) + } + ); + } + + // Three lines + for(firstLine=1; firstLine<=cnt-2; firstLine++){ + for(secondLine=1; secondLine<=cnt-firstLine-1; secondLine++){ + tries.put(new int[]{firstLine, secondLine, thirdLine=cnt-firstLine-secondLine}, new float[]{ + calculateMultiThumbsHeight(ratiosCropped.subList(0, firstLine), maxW, marginW), + calculateMultiThumbsHeight(ratiosCropped.subList(firstLine, firstLine+secondLine), maxW, marginW), + calculateMultiThumbsHeight(ratiosCropped.subList(firstLine+secondLine, ratiosCropped.size()), maxW, marginW) + } + ); + } + } + + // Looking for minimum difference between thumbs block height and maxH (may probably be little over) + int[] optConf=null; + float optDiff=0; + for(int[] conf : tries.keySet()){ + float[] heights=tries.get(conf); + float confH=marginH*(heights.length-1); + for(float h : heights) confH+=h; + float confDiff=Math.abs(confH-maxH); + if(conf.length>1){ + if(conf[0]>conf[1] || conf.length>2 && conf[1]>conf[2]){ + confDiff*=1.1; + } + } + if(optConf==null || confDiff thumbsRemain=new ArrayList<>(thumbs); + ArrayList ratiosRemain=new ArrayList<>(ratiosCropped); + float[] optHeights=tries.get(optConf); + int k=0; + + result.width=Math.round(maxW); + result.rowSizes=new int[optHeights.length]; + result.tiles=new TiledLayoutResult.Tile[thumbs.size()]; + float totalHeight=0f; + ArrayList gridLineOffsets=new ArrayList<>(); + ArrayList> rowTiles=new ArrayList<>(optHeights.length); + + for(int i=0; i lineThumbs=new ArrayList<>(); + for(int j=0; j row=new ArrayList<>(); + for(int j=0; j0; i--){ + result.columnSizes[i]=gridLineOffsets.get(i)-gridLineOffsets.get(i-1); + } + + for(ArrayList row : rowTiles){ + int columnOffset=0; + for(TiledLayoutResult.Tile tile : row){ + int startColumn=columnOffset; + tile.startCol=startColumn; + int width=0; + tile.colSpan=0; + for(int i=startColumn; i a){ + float sum=0; + for(float f:a) sum+=f; + return sum; + } + + private static float calculateMultiThumbsHeight(List ratios, float width, float margin){ + return (width-(ratios.size()-1)*margin)/sum(ratios); + } + + + public static class TiledLayoutResult{ + public int[] columnSizes, rowSizes; // sizes in grid fractions + public Tile[] tiles; + public int width, height; // in pixels (510x510 max) + + @Override + public String toString(){ + return "TiledLayoutResult{"+ + "columnSizes="+Arrays.toString(columnSizes)+ + ", rowSizes="+Arrays.toString(rowSizes)+ + ", tiles="+Arrays.toString(tiles)+ + ", width="+width+ + ", height="+height+ + '}'; + } + + public static class Tile{ + public int colSpan, rowSpan, width, height, startCol, startRow; + + public Tile(int colSpan, int rowSpan, int width, int height, int startCol, int startRow){ + this.colSpan=colSpan; + this.rowSpan=rowSpan; + this.width=width; + this.height=height; + this.startCol=startCol; + this.startRow=startRow; + } + + public Tile(int colSpan, int rowSpan, float width, float height, int startCol, int startRow){ + this(colSpan, rowSpan, Math.round(width), Math.round(height), startCol, startRow); + } + + @Override + public String toString(){ + return "Tile{"+ + "colSpan="+colSpan+ + ", rowSpan="+rowSpan+ + ", width="+width+ + ", height="+height+ + '}'; + } + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/TileGridLayoutManager.java b/mastodon/src/main/java/org/joinmastodon/android/ui/TileGridLayoutManager.java new file mode 100644 index 000000000..ceb58dd94 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/TileGridLayoutManager.java @@ -0,0 +1,27 @@ +package org.joinmastodon.android.ui; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +public class TileGridLayoutManager extends GridLayoutManager{ + private static final String TAG="TileGridLayoutManager"; + public TileGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ + super(context, attrs, defStyleAttr, defStyleRes); + } + + public TileGridLayoutManager(Context context, int spanCount){ + super(context, spanCount); + } + + public TileGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout){ + super(context, spanCount, orientation, reverseLayout); + } + + @Override + public int getColumnCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state){ + return 1; + } +} 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 index c2aa8edba..8461121b8 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/GifVStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/GifVStatusDisplayItem.java @@ -10,12 +10,13 @@ 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){ - super(parentID, parentFragment, attachment, status, index, totalPhotos); + 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); } 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 index 23e1de94b..d4061b363 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ImageStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ImageStatusDisplayItem.java @@ -1,7 +1,6 @@ package org.joinmastodon.android.ui.displayitems; import android.app.Activity; -import android.app.Fragment; import android.graphics.drawable.Drawable; import android.view.View; import android.view.ViewGroup; @@ -11,13 +10,14 @@ 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.imageloader.requests.UrlImageLoaderRequest; public abstract class ImageStatusDisplayItem extends StatusDisplayItem{ public final int index; @@ -25,13 +25,17 @@ public abstract class ImageStatusDisplayItem extends StatusDisplayItem{ protected Attachment attachment; protected ImageLoaderRequest request; public final Status status; + public final PhotoLayoutHelper.TiledLayoutResult tiledLayout; + public final PhotoLayoutHelper.TiledLayoutResult.Tile thisTile; - public ImageStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Attachment photo, Status status, int index, int totalPhotos){ + 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 @@ -46,6 +50,7 @@ public abstract class ImageStatusDisplayItem extends StatusDisplayItem{ 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; @@ -53,10 +58,12 @@ public abstract class ImageStatusDisplayItem extends StatusDisplayItem{ super(activity, layout, parent); photo=findViewById(R.id.photo); photo.setOnClickListener(this::onViewClick); + this.layout=(ImageAttachmentFrameLayout)itemView; } @Override public void onBind(ImageStatusDisplayItem item){ + layout.setLayout(item.tiledLayout, item.thisTile); crossfadeDrawable.setSize(item.attachment.getWidth(), item.attachment.getHeight()); crossfadeDrawable.setBlurhashDrawable(item.attachment.blurhashPlaceholder); crossfadeDrawable.setCrossfadeAlpha(item.status.spoilerRevealed ? 0f : 1f); 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 index a36a99bbb..964f57b4f 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PhotoStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PhotoStatusDisplayItem.java @@ -7,12 +7,13 @@ 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 PhotoStatusDisplayItem extends ImageStatusDisplayItem{ - public PhotoStatusDisplayItem(String parentID, Status status, Attachment photo, BaseStatusListFragment parentFragment, int index, int totalPhotos){ - super(parentID, parentFragment, photo, status, index, totalPhotos); + 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); } 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 aac26ad07..344e8ee85 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 @@ -13,12 +13,14 @@ import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.DisplayItemsParent; import org.joinmastodon.android.model.Poll; import org.joinmastodon.android.model.Status; +import org.joinmastodon.android.ui.PhotoLayoutHelper; import org.joinmastodon.android.ui.text.HtmlParser; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; import me.grishka.appkit.utils.BindableViewHolder; @@ -71,22 +73,20 @@ public abstract class StatusDisplayItem{ items.add(new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent)); if(!TextUtils.isEmpty(statusForContent.content)) items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, accountID), fragment, statusForContent)); - int photoIndex=0; - int totalPhotos=0; - for(Attachment attachment:statusForContent.mediaAttachments){ - if(attachment.type==Attachment.Type.IMAGE || attachment.type==Attachment.Type.GIFV || attachment.type==Attachment.Type.VIDEO){ - totalPhotos++; - } - } - for(Attachment attachment:statusForContent.mediaAttachments){ - if(attachment.type==Attachment.Type.IMAGE){ - items.add(new PhotoStatusDisplayItem(parentID, statusForContent, attachment, fragment, photoIndex, totalPhotos)); - photoIndex++; - }else if(attachment.type==Attachment.Type.GIFV){ - items.add(new GifVStatusDisplayItem(parentID, statusForContent, attachment, fragment, photoIndex, totalPhotos)); - photoIndex++; - }else if(attachment.type==Attachment.Type.VIDEO){ - items.add(new VideoStatusDisplayItem(parentID, statusForContent, attachment, fragment, photoIndex, totalPhotos)); + 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++; } } 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 index 86dd906ab..41c54aeef 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/VideoStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/VideoStatusDisplayItem.java @@ -10,12 +10,13 @@ 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){ - super(parentID, parentFragment, attachment, status, index, totalPhotos); + 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); } 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 new file mode 100644 index 000000000..654ba9845 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/ImageAttachmentFrameLayout.java @@ -0,0 +1,52 @@ +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; + + 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)); + 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.colSpan - @@ -25,4 +25,4 @@ android:layout_margin="8dp" 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 7c3146c4f..9c4c05ca8 100644 --- a/mastodon/src/main/res/layout/display_item_photo.xml +++ b/mastodon/src/main/res/layout/display_item_photo.xml @@ -1,13 +1,13 @@ - - \ 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 13937f215..83e8112d9 100644 --- a/mastodon/src/main/res/layout/display_item_video.xml +++ b/mastodon/src/main/res/layout/display_item_video.xml @@ -1,12 +1,12 @@ - @@ -18,4 +18,4 @@ android:elevation="3dp" android:background="@drawable/play_button"/> - \ No newline at end of file + \ No newline at end of file