Merge remote-tracking branch 'mastodon/master'
# Conflicts: # mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java # mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ImageStatusDisplayItem.java # mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PhotoStatusDisplayItem.java # mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java # mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java # mastodon/src/main/res/layout/display_item_gifv.xml # mastodon/src/main/res/layout/display_item_photo.xml # mastodon/src/main/res/layout/display_item_video.xml
This commit is contained in:
@@ -32,12 +32,10 @@ import org.joinmastodon.android.model.Poll;
|
|||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
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.ExtendedFooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
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.PollFooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
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.displayitems.WarningFilteredStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
||||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
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.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.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -57,7 +57,6 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
@@ -81,6 +80,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
protected HashMap<String, Account> knownAccounts=new HashMap<>();
|
protected HashMap<String, Account> knownAccounts=new HashMap<>();
|
||||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||||
protected Rect tmpRect=new Rect();
|
protected Rect tmpRect=new Rect();
|
||||||
|
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
|
||||||
|
|
||||||
private final int THRESHOLD = 800;
|
private final int THRESHOLD = 800;
|
||||||
|
|
||||||
@@ -192,21 +192,21 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void openPhotoViewer(String parentID, Status _status, int attachmentIndex){
|
public void openPhotoViewer(String parentID, Status _status, int attachmentIndex, MediaGridStatusDisplayItem.Holder gridHolder){
|
||||||
final Status status=_status.reblog!=null ? _status.reblog : _status;
|
final Status status=_status.getContentStatus();
|
||||||
currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, new PhotoViewer.Listener(){
|
currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, new PhotoViewer.Listener(){
|
||||||
private ImageStatusDisplayItem.Holder<?> transitioningHolder;
|
private MediaAttachmentViewController transitioningHolder;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPhotoViewVisibility(int index, boolean visible){
|
public void setPhotoViewVisibility(int index, boolean visible){
|
||||||
ImageStatusDisplayItem.Holder<?> holder=findPhotoViewHolder(index);
|
MediaAttachmentViewController holder=findPhotoViewHolder(index);
|
||||||
if(holder!=null)
|
if(holder!=null)
|
||||||
holder.photo.setAlpha(visible ? 1f : 0f);
|
holder.photo.setAlpha(visible ? 1f : 0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
|
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
|
||||||
ImageStatusDisplayItem.Holder<?> holder=findPhotoViewHolder(index);
|
MediaAttachmentViewController holder=findPhotoViewHolder(index);
|
||||||
if(holder!=null){
|
if(holder!=null){
|
||||||
transitioningHolder=holder;
|
transitioningHolder=holder;
|
||||||
View view=transitioningHolder.photo;
|
View view=transitioningHolder.photo;
|
||||||
@@ -214,7 +214,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
view.getLocationOnScreen(pos);
|
view.getLocationOnScreen(pos);
|
||||||
outRect.set(pos[0], pos[1], pos[0]+view.getWidth(), pos[1]+view.getHeight());
|
outRect.set(pos[0], pos[1], pos[0]+view.getWidth(), pos[1]+view.getHeight());
|
||||||
list.setClipChildren(false);
|
list.setClipChildren(false);
|
||||||
transitioningHolder.itemView.setElevation(1f);
|
gridHolder.setClipChildren(false);
|
||||||
|
transitioningHolder.view.setElevation(1f);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -241,15 +242,16 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
view.setTranslationY(0f);
|
view.setTranslationY(0f);
|
||||||
view.setScaleX(1f);
|
view.setScaleX(1f);
|
||||||
view.setScaleY(1f);
|
view.setScaleY(1f);
|
||||||
transitioningHolder.itemView.setElevation(0f);
|
transitioningHolder.view.setElevation(0f);
|
||||||
if(list!=null)
|
if(list!=null)
|
||||||
list.setClipChildren(true);
|
list.setClipChildren(true);
|
||||||
|
gridHolder.setClipChildren(true);
|
||||||
transitioningHolder=null;
|
transitioningHolder=null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Drawable getPhotoViewCurrentDrawable(int index){
|
public Drawable getPhotoViewCurrentDrawable(int index){
|
||||||
ImageStatusDisplayItem.Holder<?> holder=findPhotoViewHolder(index);
|
MediaAttachmentViewController holder=findPhotoViewHolder(index);
|
||||||
if(holder!=null)
|
if(holder!=null)
|
||||||
return holder.photo.getDrawable();
|
return holder.photo.getDrawable();
|
||||||
return null;
|
return null;
|
||||||
@@ -265,23 +267,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
requestPermissions(permissions, PhotoViewer.PERMISSION_REQUEST);
|
requestPermissions(permissions, PhotoViewer.PERMISSION_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImageStatusDisplayItem.Holder<?> findPhotoViewHolder(int index){
|
private MediaAttachmentViewController findPhotoViewHolder(int index){
|
||||||
if(list==null)
|
return gridHolder.getViewController(index);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -380,31 +367,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> 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<displayItems.size()){
|
|
||||||
StatusDisplayItem item=displayItems.get(position);
|
|
||||||
if(item instanceof ImageStatusDisplayItem imgItem){
|
|
||||||
PhotoLayoutHelper.TiledLayoutResult layout=imgItem.tiledLayout;
|
|
||||||
PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgItem.thisTile;
|
|
||||||
int spans=0;
|
|
||||||
for(int i=0;i<tile.colSpan;i++){
|
|
||||||
spans+=layout.columnSizes[tile.startCol+i];
|
|
||||||
}
|
|
||||||
return spans;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 1000;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return lm;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigurationChanged(Configuration newConfig){
|
public void onConfigurationChanged(Configuration newConfig){
|
||||||
super.onConfigurationChanged(newConfig);
|
super.onConfigurationChanged(newConfig);
|
||||||
@@ -523,7 +485,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
revealSpoiler(status, holder.getItemID());
|
revealSpoiler(status, holder.getItemID());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onRevealSpoilerClick(ImageStatusDisplayItem.Holder<?> holder){
|
public void onRevealSpoilerClick(MediaGridStatusDisplayItem.Holder holder){
|
||||||
Status status=holder.getItem().status;
|
Status status=holder.getItem().status;
|
||||||
revealSpoiler(status, holder.getItemID());
|
revealSpoiler(status, holder.getItemID());
|
||||||
}
|
}
|
||||||
@@ -571,13 +533,14 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
|
|
||||||
protected void updateImagesSpoilerState(Status status, String itemID){
|
protected void updateImagesSpoilerState(Status status, String itemID){
|
||||||
ArrayList<Integer> updatedPositions=new ArrayList<>();
|
ArrayList<Integer> updatedPositions=new ArrayList<>();
|
||||||
for(ImageStatusDisplayItem.Holder photo:(List<ImageStatusDisplayItem.Holder>)findAllHoldersOfType(itemID, ImageStatusDisplayItem.Holder.class)){
|
MediaGridStatusDisplayItem.Holder mediaGrid=findHolderOfType(itemID, MediaGridStatusDisplayItem.Holder.class);
|
||||||
photo.setRevealed(status.spoilerRevealed);
|
if(mediaGrid!=null){
|
||||||
updatedPositions.add(photo.getAbsoluteAdapterPosition()-getMainAdapterOffset());
|
mediaGrid.setRevealed(status.spoilerRevealed);
|
||||||
|
updatedPositions.add(mediaGrid.getAbsoluteAdapterPosition()-getMainAdapterOffset());
|
||||||
}
|
}
|
||||||
int i=0;
|
int i=0;
|
||||||
for(StatusDisplayItem item:displayItems){
|
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);
|
adapter.notifyItemChanged(i);
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
@@ -720,6 +683,14 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
return UiUtils.pickAccountForCompose(getActivity(), accountID);
|
return UiUtils.pickAccountForCompose(getActivity(), accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MediaAttachmentViewController makeNewMediaAttachmentView(MediaGridStatusDisplayItem.GridItemType type){
|
||||||
|
return new MediaAttachmentViewController(getActivity(), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> getAttachmentViewsPool(){
|
||||||
|
return attachmentViewsPool;
|
||||||
|
}
|
||||||
|
|
||||||
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
|
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
|
||||||
|
|
||||||
public DisplayItemsAdapter(){
|
public DisplayItemsAdapter(){
|
||||||
@@ -757,16 +728,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||||
return displayItems.get(position).getImageRequest(image);
|
return displayItems.get(position).getImageRequest(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public void onViewDetachedFromWindow(@NonNull BindableViewHolder<StatusDisplayItem> holder){
|
|
||||||
// if(holder instanceof ImageLoaderViewHolder){
|
|
||||||
// int count=holder.getItem().getImageCount();
|
|
||||||
// for(int i=0;i<count;i++){
|
|
||||||
// ((ImageLoaderViewHolder) holder).clearImage(i);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class StatusListItemDecoration extends RecyclerView.ItemDecoration{
|
private class StatusListItemDecoration extends RecyclerView.ItemDecoration{
|
||||||
@@ -800,25 +761,21 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
for(int i=0;i<parent.getChildCount();i++){
|
for(int i=0;i<parent.getChildCount();i++){
|
||||||
View child=parent.getChildAt(i);
|
View child=parent.getChildAt(i);
|
||||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> imgHolder){
|
if(holder instanceof MediaGridStatusDisplayItem.Holder imgHolder){
|
||||||
if(!imgHolder.getItem().status.spoilerRevealed && TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
|
if(!imgHolder.getItem().status.spoilerRevealed && TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
|
||||||
hiddenMediaPaint.setColor(0x80000000);
|
hiddenMediaPaint.setColor(0x80000000);
|
||||||
PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgHolder.getItem().thisTile;
|
c.drawRect(child.getX(), child.getY(), child.getX()+child.getWidth(), child.getY()+child.getHeight(), hiddenMediaPaint);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(int i=0;i<parent.getChildCount();i++){
|
for(int i=0;i<parent.getChildCount();i++){
|
||||||
View child=parent.getChildAt(i);
|
View child=parent.getChildAt(i);
|
||||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> imgHolder){
|
if(holder instanceof MediaGridStatusDisplayItem.Holder imgHolder){
|
||||||
if(!imgHolder.getItem().status.spoilerRevealed){
|
if(!imgHolder.getItem().status.spoilerRevealed){
|
||||||
PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgHolder.getItem().thisTile;
|
if(TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
|
||||||
if(tile.startCol==0 && tile.startRow==0 && TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
|
|
||||||
int listWidth=getListWidthForMediaLayout();
|
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)
|
if(currentMediaHiddenLayoutsWidth!=width)
|
||||||
rebuildMediaHiddenLayouts(width-V.dp(32));
|
rebuildMediaHiddenLayouts(width-V.dp(32));
|
||||||
c.save();
|
c.save();
|
||||||
@@ -843,47 +800,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> 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.colSpan<layout.columnSizes.length){
|
|
||||||
outRect.right=V.dp(1);
|
|
||||||
}
|
|
||||||
if(tile.startRow+tile.rowSpan<layout.rowSizes.length){
|
|
||||||
outRect.bottom=V.dp(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For a view that spans rows, compensate its additional height so the row it's in stays the right height
|
|
||||||
if(tile.rowSpan>1){
|
|
||||||
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;i<tile.startCol;i++){
|
|
||||||
spanOffset+=layout.columnSizes[i];
|
|
||||||
}
|
|
||||||
outRect.left-=Math.round(spanOffset/1000f*listWidth);
|
|
||||||
outRect.left+=Math.round(spanOffset/1000f*width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void rebuildMediaHiddenLayouts(int width){
|
private void rebuildMediaHiddenLayouts(int width){
|
||||||
currentMediaHiddenLayoutsWidth=width;
|
currentMediaHiddenLayoutsWidth=width;
|
||||||
String title=getString(R.string.sensitive_content);
|
String title=getString(R.string.sensitive_content);
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ import org.joinmastodon.android.ui.text.ComposeAutocompleteSpan;
|
|||||||
import org.joinmastodon.android.ui.text.ComposeHashtagOrMentionSpan;
|
import org.joinmastodon.android.ui.text.ComposeHashtagOrMentionSpan;
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||||
import org.joinmastodon.android.ui.utils.TransferSpeedTracker;
|
import org.joinmastodon.android.utils.TransferSpeedTracker;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.ComposeEditText;
|
import org.joinmastodon.android.ui.views.ComposeEditText;
|
||||||
import org.joinmastodon.android.ui.views.ComposeMediaLayout;
|
import org.joinmastodon.android.ui.views.ComposeMediaLayout;
|
||||||
@@ -1013,9 +1013,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onCustomEmojiClick(Emoji emoji){
|
private void onCustomEmojiClick(Emoji emoji){
|
||||||
int start=mainEditText.getSelectionStart();
|
if(getActivity().getCurrentFocus() instanceof EditText edit){
|
||||||
String prefix=start>0 && !Character.isWhitespace(mainEditText.getText().charAt(start-1)) ? " :" : ":";
|
int start=edit.getSelectionStart();
|
||||||
mainEditText.getText().replace(start, mainEditText.getSelectionEnd(), prefix+emoji.shortcode+':');
|
String prefix=start>0 && !Character.isWhitespace(edit.getText().charAt(start-1)) ? " :" : ":";
|
||||||
|
edit.getText().replace(start, edit.getSelectionEnd(), prefix+emoji.shortcode+':');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import org.joinmastodon.android.model.Status;
|
|||||||
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
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.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
@@ -97,14 +96,9 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
};
|
};
|
||||||
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, n.status, extraText, n, null) : null;
|
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, n.status, extraText, n, null) : null;
|
||||||
if(n.status!=null){
|
if(n.status!=null){
|
||||||
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n, false, Filter.FilterContext.NOTIFICATIONS, titleItem);
|
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n, false, Filter.FilterContext.NOTIFICATIONS);
|
||||||
if(titleItem!=null){
|
if(titleItem!=null)
|
||||||
for(StatusDisplayItem item:items){
|
items.add(0, titleItem);
|
||||||
if(item instanceof ImageStatusDisplayItem imgItem){
|
|
||||||
imgItem.horizontalInset=V.dp(32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return items;
|
return items;
|
||||||
}else if(titleItem!=null){
|
}else if(titleItem!=null){
|
||||||
AccountCardStatusDisplayItem card=new AccountCardStatusDisplayItem(n.id, this,
|
AccountCardStatusDisplayItem card=new AccountCardStatusDisplayItem(n.id, this,
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import org.joinmastodon.android.model.Status;
|
|||||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||||
import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
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.LinkCardStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
@@ -133,22 +132,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
|||||||
if(holder.getAbsoluteAdapterPosition()==0)
|
if(holder.getAbsoluteAdapterPosition()==0)
|
||||||
return;
|
return;
|
||||||
outRect.left=V.dp(40);
|
outRect.left=V.dp(40);
|
||||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> imgHolder){
|
if(holder instanceof AudioStatusDisplayItem.Holder){
|
||||||
PhotoLayoutHelper.TiledLayoutResult layout=imgHolder.getItem().tiledLayout;
|
|
||||||
PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgHolder.getItem().thisTile;
|
|
||||||
String siblingID;
|
|
||||||
if(holder.getAbsoluteAdapterPosition()<parent.getAdapter().getItemCount()-1){
|
|
||||||
siblingID=displayItems.get(holder.getAbsoluteAdapterPosition()-getMainAdapterOffset()+1).parentID;
|
|
||||||
}else{
|
|
||||||
siblingID=null;
|
|
||||||
}
|
|
||||||
if(tile.startCol>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){
|
|
||||||
outRect.bottom=V.dp(16);
|
outRect.bottom=V.dp(16);
|
||||||
}else if(holder instanceof LinkCardStatusDisplayItem.Holder){
|
}else if(holder instanceof LinkCardStatusDisplayItem.Holder){
|
||||||
outRect.bottom=V.dp(16);
|
outRect.bottom=V.dp(16);
|
||||||
@@ -167,10 +151,6 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
|||||||
parent.getDecoratedBoundsWithMargins(child, tmpRect);
|
parent.getDecoratedBoundsWithMargins(child, tmpRect);
|
||||||
String id=sdiHolder.getItemID();
|
String id=sdiHolder.getItemID();
|
||||||
int height=tmpRect.height();
|
int height=tmpRect.height();
|
||||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> imgHolder){
|
|
||||||
if(imgHolder.getItem().thisTile.startCol+imgHolder.getItem().thisTile.colSpan<imgHolder.getItem().tiledLayout.columnSizes.length)
|
|
||||||
height=0;
|
|
||||||
}
|
|
||||||
if(!(holder instanceof HeaderStatusDisplayItem.Holder) && !(holder instanceof ReblogOrReplyLineStatusDisplayItem.Holder))
|
if(!(holder instanceof HeaderStatusDisplayItem.Holder) && !(holder instanceof ReblogOrReplyLineStatusDisplayItem.Holder))
|
||||||
postsWithKnownNonHeaderHeights.add(id);
|
postsWithKnownNonHeaderHeights.add(id);
|
||||||
knownDisplayItemHeights.put(holder.getAbsoluteAdapterPosition(), height);
|
knownDisplayItemHeights.put(holder.getAbsoluteAdapterPosition(), height);
|
||||||
@@ -237,17 +217,6 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
|||||||
return adapter;
|
return adapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
|
||||||
List<StatusDisplayItem> 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){
|
protected void drawDivider(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder, RecyclerView parent, Canvas c, Paint paint){
|
||||||
parent.getDecoratedBoundsWithMargins(child, tmpRect);
|
parent.getDecoratedBoundsWithMargins(child, tmpRect);
|
||||||
tmpRect.offset(0, Math.round(child.getTranslationY()));
|
tmpRect.offset(0, Math.round(child.getTranslationY()));
|
||||||
|
|||||||
@@ -11,8 +11,14 @@ import java.util.List;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
public class PhotoLayoutHelper{
|
public class PhotoLayoutHelper{
|
||||||
|
public static final int MAX_WIDTH=1000;
|
||||||
|
public static final int MAX_HEIGHT=1910;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static TiledLayoutResult processThumbs(int _maxW, int _maxH, List<Attachment> thumbs){
|
public static TiledLayoutResult processThumbs(List<Attachment> thumbs){
|
||||||
|
int _maxW=MAX_WIDTH;
|
||||||
|
int _maxH=MAX_HEIGHT;
|
||||||
|
|
||||||
TiledLayoutResult result=new TiledLayoutResult();
|
TiledLayoutResult result=new TiledLayoutResult();
|
||||||
if(thumbs.size()==1){
|
if(thumbs.size()==1){
|
||||||
Attachment att=thumbs.get(0);
|
Attachment att=thumbs.get(0);
|
||||||
@@ -45,13 +51,8 @@ public class PhotoLayoutHelper{
|
|||||||
float avgRatio=!ratios.isEmpty() ? sum(ratios)/ratios.size() : 1.0f;
|
float avgRatio=!ratios.isEmpty() ? sum(ratios)/ratios.size() : 1.0f;
|
||||||
|
|
||||||
float maxW, maxH, marginW=0, marginH=0;
|
float maxW, maxH, marginW=0, marginH=0;
|
||||||
if(_maxW>0){
|
|
||||||
maxW=_maxW;
|
maxW=_maxW;
|
||||||
maxH=_maxH;
|
maxH=_maxH;
|
||||||
}else{
|
|
||||||
maxW=510;
|
|
||||||
maxH=510;
|
|
||||||
}
|
|
||||||
|
|
||||||
float maxRatio=maxW/maxH;
|
float maxRatio=maxW/maxH;
|
||||||
|
|
||||||
|
|||||||
@@ -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<GifVStatusDisplayItem>{
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<T extends ImageStatusDisplayItem> extends StatusDisplayItem.Holder<T> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<GridItemType, MediaAttachmentViewController> viewPool;
|
||||||
|
private final List<Attachment> attachments;
|
||||||
|
private final ArrayList<ImageLoaderRequest> requests=new ArrayList<>();
|
||||||
|
public final Status status;
|
||||||
|
|
||||||
|
public MediaGridStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, PhotoLayoutHelper.TiledLayoutResult tiledLayout, List<Attachment> 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<MediaGridStatusDisplayItem> implements ImageLoaderViewHolder{
|
||||||
|
private final FrameLayout wrapper;
|
||||||
|
private final MediaGridLayout layout;
|
||||||
|
private final View.OnClickListener clickListener=this::onViewClick, altTextClickListener=this::onAltTextClick;
|
||||||
|
private final ArrayList<MediaAttachmentViewController> 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<Animator> 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<Animator> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<PhotoStatusDisplayItem> {
|
|
||||||
public Holder(Activity activity, ViewGroup parent) {
|
|
||||||
super(activity, R.layout.display_item_photo, parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -70,10 +70,7 @@ public abstract class StatusDisplayItem{
|
|||||||
case HEADER -> new HeaderStatusDisplayItem.Holder(activity, parent);
|
case HEADER -> new HeaderStatusDisplayItem.Holder(activity, parent);
|
||||||
case REBLOG_OR_REPLY_LINE -> new ReblogOrReplyLineStatusDisplayItem.Holder(activity, parent);
|
case REBLOG_OR_REPLY_LINE -> new ReblogOrReplyLineStatusDisplayItem.Holder(activity, parent);
|
||||||
case TEXT -> new TextStatusDisplayItem.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 AUDIO -> new AudioStatusDisplayItem.Holder(activity, parent);
|
||||||
case VIDEO -> new VideoStatusDisplayItem.Holder(activity, parent);
|
|
||||||
case POLL_OPTION -> new PollOptionStatusDisplayItem.Holder(activity, parent);
|
case POLL_OPTION -> new PollOptionStatusDisplayItem.Holder(activity, parent);
|
||||||
case POLL_FOOTER -> new PollFooterStatusDisplayItem.Holder(activity, parent);
|
case POLL_FOOTER -> new PollFooterStatusDisplayItem.Holder(activity, parent);
|
||||||
case CARD -> new LinkCardStatusDisplayItem.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 HASHTAG -> new HashtagStatusDisplayItem.Holder(activity, parent);
|
||||||
case GAP -> new GapStatusDisplayItem.Holder(activity, parent);
|
case GAP -> new GapStatusDisplayItem.Holder(activity, parent);
|
||||||
case EXTENDED_FOOTER -> new ExtendedFooterStatusDisplayItem.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);
|
case WARNING -> new WarningFilteredStatusDisplayItem.Holder(activity, parent);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -161,20 +159,8 @@ public abstract class StatusDisplayItem{
|
|||||||
header.needBottomPadding=true;
|
header.needBottomPadding=true;
|
||||||
List<Attachment> imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList());
|
List<Attachment> imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList());
|
||||||
if(!imageAttachments.isEmpty()){
|
if(!imageAttachments.isEmpty()){
|
||||||
int photoIndex=0;
|
PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(imageAttachments);
|
||||||
PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(1000, 1910, imageAttachments);
|
items.add(new MediaGridStatusDisplayItem(parentID, fragment, layout, imageAttachments, statusForContent));
|
||||||
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++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for(Attachment att:statusForContent.mediaAttachments){
|
for(Attachment att:statusForContent.mediaAttachments){
|
||||||
if(att.type==Attachment.Type.AUDIO){
|
if(att.type==Attachment.Type.AUDIO){
|
||||||
@@ -222,9 +208,6 @@ public abstract class StatusDisplayItem{
|
|||||||
HEADER,
|
HEADER,
|
||||||
REBLOG_OR_REPLY_LINE,
|
REBLOG_OR_REPLY_LINE,
|
||||||
TEXT,
|
TEXT,
|
||||||
PHOTO,
|
|
||||||
VIDEO,
|
|
||||||
GIFV,
|
|
||||||
AUDIO,
|
AUDIO,
|
||||||
POLL_OPTION,
|
POLL_OPTION,
|
||||||
POLL_FOOTER,
|
POLL_FOOTER,
|
||||||
@@ -236,6 +219,7 @@ public abstract class StatusDisplayItem{
|
|||||||
GAP,
|
GAP,
|
||||||
WARNING,
|
WARNING,
|
||||||
EXTENDED_FOOTER
|
EXTENDED_FOOTER
|
||||||
|
MEDIA_GRID
|
||||||
}
|
}
|
||||||
|
|
||||||
public static abstract class Holder<T extends StatusDisplayItem> extends BindableViewHolder<T> implements UsableRecyclerView.DisableableClickable{
|
public static abstract class Holder<T extends StatusDisplayItem> extends BindableViewHolder<T> implements UsableRecyclerView.DisableableClickable{
|
||||||
|
|||||||
@@ -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<VideoStatusDisplayItem>{
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
package org.joinmastodon.android.ui.photoviewer;
|
package org.joinmastodon.android.ui.photoviewer;
|
||||||
|
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
||||||
|
|
||||||
public interface PhotoViewerHost{
|
public interface PhotoViewerHost{
|
||||||
void openPhotoViewer(String parentID, Status status, int attachmentIndex);
|
void openPhotoViewer(String parentID, Status status, int attachmentIndex, MediaGridStatusDisplayItem.Holder gridHolder);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,9 @@ import android.view.View;
|
|||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
import org.joinmastodon.android.fragments.NotificationsListFragment;
|
|
||||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
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.LinkCardStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -87,21 +86,11 @@ public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
|
|||||||
boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
|
boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
|
||||||
boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
|
boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
|
||||||
int pad;
|
int pad;
|
||||||
if(holder instanceof ImageStatusDisplayItem.Holder || holder instanceof LinkCardStatusDisplayItem.Holder)
|
if(holder instanceof MediaGridStatusDisplayItem.Holder || holder instanceof LinkCardStatusDisplayItem.Holder)
|
||||||
pad=V.dp(16);
|
pad=V.dp(16);
|
||||||
else
|
else
|
||||||
pad=V.dp(12);
|
pad=V.dp(12);
|
||||||
boolean insetLeft=true, insetRight=true;
|
boolean insetLeft=true, insetRight=true;
|
||||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> 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)
|
if(insetLeft)
|
||||||
outRect.left=pad;
|
outRect.left=pad;
|
||||||
if(insetRight)
|
if(insetRight)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.colSpan<tileLayout.columnSizes.length)
|
|
||||||
actualWidth-=V.dp(1);
|
|
||||||
heightMeasureSpec=actualHeight | MeasureSpec.EXACTLY;
|
|
||||||
widthMeasureSpec=actualWidth | MeasureSpec.EXACTLY;
|
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLayout(PhotoLayoutHelper.TiledLayoutResult layout, PhotoLayoutHelper.TiledLayoutResult.Tile tile, int horizontalInset){
|
|
||||||
tileLayout=layout;
|
|
||||||
this.tile=tile;
|
|
||||||
this.horizontalInset=horizontalInset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package org.joinmastodon.android.ui.views;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
|
public class MediaGridLayout extends ViewGroup{
|
||||||
|
private static final String TAG="MediaGridLayout";
|
||||||
|
|
||||||
|
public static final int MAX_WIDTH=400; // dp
|
||||||
|
private static final int GAP=1; // dp
|
||||||
|
private PhotoLayoutHelper.TiledLayoutResult tiledLayout;
|
||||||
|
private int[] columnStarts=new int[10], columnEnds=new int[10], rowStarts=new int[10], rowEnds=new int[10];
|
||||||
|
|
||||||
|
public MediaGridLayout(Context context){
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MediaGridLayout(Context context, AttributeSet attrs){
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MediaGridLayout(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(V.dp(MAX_WIDTH), MeasureSpec.getSize(widthMeasureSpec));
|
||||||
|
int height=Math.round(width*(tiledLayout.height/(float)PhotoLayoutHelper.MAX_WIDTH));
|
||||||
|
|
||||||
|
int offset=0;
|
||||||
|
for(int i=0;i<tiledLayout.columnSizes.length;i++){
|
||||||
|
columnStarts[i]=offset;
|
||||||
|
offset+=Math.round(tiledLayout.columnSizes[i]/(float)tiledLayout.width*width);
|
||||||
|
columnEnds[i]=offset;
|
||||||
|
offset+=V.dp(GAP);
|
||||||
|
}
|
||||||
|
columnEnds[tiledLayout.columnSizes.length-1]=width;
|
||||||
|
offset=0;
|
||||||
|
for(int i=0;i<tiledLayout.rowSizes.length;i++){
|
||||||
|
rowStarts[i]=offset;
|
||||||
|
offset+=Math.round(tiledLayout.rowSizes[i]/(float)tiledLayout.height*height);
|
||||||
|
rowEnds[i]=offset;
|
||||||
|
offset+=V.dp(GAP);
|
||||||
|
}
|
||||||
|
rowEnds[tiledLayout.rowSizes.length-1]=height;
|
||||||
|
|
||||||
|
for(int i=0;i<getChildCount();i++){
|
||||||
|
View child=getChildAt(i);
|
||||||
|
LayoutParams lp=(LayoutParams) child.getLayoutParams();
|
||||||
|
int colSpan=Math.max(1, lp.tile.colSpan)-1;
|
||||||
|
int rowSpan=Math.max(1, lp.tile.rowSpan)-1;
|
||||||
|
int w=columnEnds[lp.tile.startCol+colSpan]-columnStarts[lp.tile.startCol];
|
||||||
|
int h=rowEnds[lp.tile.startRow+rowSpan]-rowStarts[lp.tile.startRow];
|
||||||
|
child.measure(w | MeasureSpec.EXACTLY, h | MeasureSpec.EXACTLY);
|
||||||
|
}
|
||||||
|
|
||||||
|
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onLayout(boolean changed, int l, int t, int r, int b){
|
||||||
|
if(tiledLayout==null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int maxWidth=V.dp(MAX_WIDTH);
|
||||||
|
int xOffset=0;
|
||||||
|
if(r-l>maxWidth){
|
||||||
|
xOffset=(r-l)/2-maxWidth/2;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i=0;i<getChildCount();i++){
|
||||||
|
View child=getChildAt(i);
|
||||||
|
LayoutParams lp=(LayoutParams) child.getLayoutParams();
|
||||||
|
int colSpan=Math.max(1, lp.tile.colSpan)-1;
|
||||||
|
int rowSpan=Math.max(1, lp.tile.rowSpan)-1;
|
||||||
|
child.layout(columnStarts[lp.tile.startCol]+xOffset, rowStarts[lp.tile.startRow], columnEnds[lp.tile.startCol+colSpan]+xOffset, rowEnds[lp.tile.startRow+rowSpan]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTiledLayout(PhotoLayoutHelper.TiledLayoutResult tiledLayout){
|
||||||
|
this.tiledLayout=tiledLayout;
|
||||||
|
requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LayoutParams extends ViewGroup.LayoutParams{
|
||||||
|
public PhotoLayoutHelper.TiledLayoutResult.Tile tile;
|
||||||
|
|
||||||
|
public LayoutParams(PhotoLayoutHelper.TiledLayoutResult.Tile tile){
|
||||||
|
super(WRAP_CONTENT, WRAP_CONTENT);
|
||||||
|
this.tile=tile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.joinmastodon.android.ui.utils;
|
package org.joinmastodon.android.utils;
|
||||||
|
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.joinmastodon.android.utils;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class TypedObjectPool<K, V>{
|
||||||
|
private final Function<K, V> producer;
|
||||||
|
private final HashMap<K, LinkedList<V>> pool=new HashMap<>();
|
||||||
|
|
||||||
|
public TypedObjectPool(Function<K, V> producer){
|
||||||
|
this.producer=producer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public V obtain(K type){
|
||||||
|
LinkedList<V> 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<V> tp=pool.get(type);
|
||||||
|
if(tp==null)
|
||||||
|
pool.put(type, tp=new LinkedList<>());
|
||||||
|
tp.add(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
@@ -26,5 +26,4 @@
|
|||||||
android:background="@drawable/ic_gif"/>
|
android:background="@drawable/ic_gif"/>
|
||||||
|
|
||||||
<include layout="@layout/alt_badge" />
|
<include layout="@layout/alt_badge" />
|
||||||
|
</FrameLayout>
|
||||||
</org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout>
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
@@ -10,6 +11,22 @@
|
|||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:scaleType="centerCrop"/>
|
android:scaleType="centerCrop"/>
|
||||||
|
|
||||||
<include layout="@layout/alt_badge" />
|
<!-- This is hidden from screenreaders because that same alt text is set as content description on the ImageView -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/alt_button"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="22dp"
|
||||||
|
android:layout_gravity="start|bottom"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:textAppearance="@style/m3_label_large"
|
||||||
|
android:textColor="#FFF"
|
||||||
|
android:gravity="center"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:background="@drawable/bg_image_alt_overlay"
|
||||||
|
android:text="ALT"/>
|
||||||
|
|
||||||
</org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout>
|
</FrameLayout>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
@@ -18,6 +18,4 @@
|
|||||||
android:elevation="3dp"
|
android:elevation="3dp"
|
||||||
android:background="@drawable/play_button"/>
|
android:background="@drawable/play_button"/>
|
||||||
|
|
||||||
<include layout="@layout/alt_badge" />
|
</FrameLayout>
|
||||||
|
|
||||||
</org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout>
|
|
||||||
58
mastodon/src/main/res/layout/overlay_image_alt_text.xml
Normal file
58
mastodon/src/main/res/layout/overlay_image_alt_text.xml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/alt_text_wrapper"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|bottom"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:importantForAccessibility="noHideDescendants"
|
||||||
|
android:background="@drawable/bg_image_alt_overlay">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/alt_button"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="22dp"
|
||||||
|
android:textAppearance="@style/m3_label_large"
|
||||||
|
android:textColor="#FFF"
|
||||||
|
android:gravity="center"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="ALT"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/alt_text_close"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_gravity="end|top"
|
||||||
|
android:src="@drawable/ic_baseline_close_24"
|
||||||
|
android:tint="#FFF"
|
||||||
|
android:background="?android:selectableItemBackgroundBorderless"/>
|
||||||
|
|
||||||
|
<org.joinmastodon.android.ui.views.NestableScrollView
|
||||||
|
android:id="@+id/alt_text_scroller"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="40dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/alt_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:textAppearance="@style/m3_body_medium"
|
||||||
|
android:textColor="#FFF"
|
||||||
|
tools:text="Alt text goes here"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</org.joinmastodon.android.ui.views.NestableScrollView>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
Reference in New Issue
Block a user