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 df59a405..ae63ebfb 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java @@ -15,6 +15,7 @@ import org.joinmastodon.android.R; import org.joinmastodon.android.model.DisplayItemsParent; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem; +import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.PhotoStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.photoviewer.PhotoViewer; @@ -122,18 +123,18 @@ public abstract class BaseStatusListFragment exten public void openPhotoViewer(String parentID, Status _status, int attachmentIndex){ final Status status=_status.reblog!=null ? _status.reblog : _status; currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, new PhotoViewer.Listener(){ - private PhotoStatusDisplayItem.Holder transitioningHolder; + private ImageStatusDisplayItem.Holder transitioningHolder; @Override public void setPhotoViewVisibility(int index, boolean visible){ - PhotoStatusDisplayItem.Holder holder=findPhotoViewHolder(index); + ImageStatusDisplayItem.Holder holder=findPhotoViewHolder(index); if(holder!=null) holder.photo.setAlpha(visible ? 1f : 0f); } @Override public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){ - PhotoStatusDisplayItem.Holder holder=findPhotoViewHolder(index); + ImageStatusDisplayItem.Holder holder=findPhotoViewHolder(index); if(holder!=null){ transitioningHolder=holder; View view=transitioningHolder.photo; @@ -170,7 +171,7 @@ public abstract class BaseStatusListFragment exten @Override public Drawable getPhotoViewCurrentDrawable(int index){ - PhotoStatusDisplayItem.Holder holder=findPhotoViewHolder(index); + ImageStatusDisplayItem.Holder holder=findPhotoViewHolder(index); if(holder!=null) return holder.photo.getDrawable(); return null; @@ -181,14 +182,14 @@ public abstract class BaseStatusListFragment exten currentPhotoViewer=null; } - private PhotoStatusDisplayItem.Holder findPhotoViewHolder(int index){ + private ImageStatusDisplayItem.Holder findPhotoViewHolder(int index){ int offset=0; for(StatusDisplayItem item:displayItems){ if(item.parentID.equals(parentID)){ - if(item instanceof PhotoStatusDisplayItem){ + if(item instanceof ImageStatusDisplayItem){ RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(getMainAdapterOffset()+offset+index); - if(holder instanceof PhotoStatusDisplayItem.Holder){ - return (PhotoStatusDisplayItem.Holder) holder; + if(holder instanceof ImageStatusDisplayItem.Holder){ + return (ImageStatusDisplayItem.Holder) holder; } return null; } @@ -264,10 +265,10 @@ public abstract class BaseStatusListFragment exten position-=getMainAdapterOffset(); if(position>=0 && position1){ - int index=((PhotoStatusDisplayItem) item).index; + int index=((ImageStatusDisplayItem) item).index; 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 new file mode 100644 index 00000000..26bcb97b --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/GifVStatusDisplayItem.java @@ -0,0 +1,41 @@ +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 me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; + +public class GifVStatusDisplayItem extends ImageStatusDisplayItem{ + public GifVStatusDisplayItem(String parentID, Status status, Attachment photo, BaseStatusListFragment parentFragment, int index, int totalPhotos){ + super(parentID, parentFragment, photo, status, index, totalPhotos); + request=new UrlImageLoaderRequest(photo.previewUrl, 1000, 1000); + } + + @Override + public Type getType(){ + return Type.GIFV; + } + + public static class Holder extends ImageStatusDisplayItem.Holder{ + + public Holder(Activity activity, ViewGroup parent){ + super(activity, R.layout.display_item_gifv, parent); + View play=findViewById(R.id.play_button); + play.setOutlineProvider(new ViewOutlineProvider(){ + @Override + public void getOutline(View view, Outline outline){ + outline.setOval(0, 0, view.getWidth(), view.getHeight()); + outline.setAlpha(.99f); // fixes shadow rendering + } + }); + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ImageStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ImageStatusDisplayItem.java new file mode 100644 index 00000000..1d431296 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ImageStatusDisplayItem.java @@ -0,0 +1,79 @@ +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; +import android.widget.ImageView; + +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.photoviewer.PhotoViewerHost; + +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; + public final int totalPhotos; + protected Attachment attachment; + protected ImageLoaderRequest request; + protected Fragment parentFragment; + protected Status status; + + public ImageStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Attachment photo, Status status, int index, int totalPhotos){ + super(parentID, parentFragment); + this.attachment=photo; + this.parentFragment=parentFragment; + this.status=status; + this.index=index; + this.totalPhotos=totalPhotos; + } + + @Override + public int getImageCount(){ + return 1; + } + + @Override + public ImageLoaderRequest getImageRequest(int index){ + return request; + } + + public static abstract class Holder extends StatusDisplayItem.Holder implements ImageLoaderViewHolder{ + public final ImageView photo; + + public Holder(Activity activity, @LayoutRes int layout, ViewGroup parent){ + super(activity, layout, parent); + photo=findViewById(R.id.photo); + photo.setOnClickListener(this::onViewClick); + } + + @Override + public void onBind(ImageStatusDisplayItem item){ + + } + + @Override + public void setImage(int index, Drawable drawable){ + photo.setImageDrawable(drawable); + } + + @Override + public void clearImage(int index){ + photo.setImageDrawable(item.attachment.blurhashPlaceholder); + } + + private void onViewClick(View v){ + 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)); + } + } + } +} 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 84914b6c..a36a99bb 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 @@ -1,37 +1,19 @@ 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; -import android.widget.ImageView; 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.photoviewer.PhotoViewerHost; -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.BindableViewHolder; -public class PhotoStatusDisplayItem extends StatusDisplayItem{ - private Attachment attachment; - private ImageLoaderRequest request; - private Fragment parentFragment; - private Status status; - public final int index, totalPhotos; +public class PhotoStatusDisplayItem extends ImageStatusDisplayItem{ public PhotoStatusDisplayItem(String parentID, Status status, Attachment photo, BaseStatusListFragment parentFragment, int index, int totalPhotos){ - super(parentID, parentFragment); - this.status=status; - this.attachment=photo; + super(parentID, parentFragment, photo, status, index, totalPhotos); request=new UrlImageLoaderRequest(photo.url, 1000, 1000); - this.parentFragment=parentFragment; - this.index=index; - this.totalPhotos=totalPhotos; } @Override @@ -39,44 +21,10 @@ public class PhotoStatusDisplayItem extends StatusDisplayItem{ return Type.PHOTO; } - @Override - public int getImageCount(){ - return 1; - } + public static class Holder extends ImageStatusDisplayItem.Holder{ - @Override - public ImageLoaderRequest getImageRequest(int index){ - return request; - } - - public static class Holder extends StatusDisplayItem.Holder implements ImageLoaderViewHolder{ - public final ImageView photo; public Holder(Activity activity, ViewGroup parent){ super(activity, R.layout.display_item_photo, parent); - photo=findViewById(R.id.photo); - photo.setOnClickListener(this::onViewClick); - } - - @Override - public void onBind(PhotoStatusDisplayItem item){ - - } - - @Override - public void setImage(int index, Drawable drawable){ - photo.setImageDrawable(drawable); - } - - @Override - public void clearImage(int index){ - photo.setImageDrawable(item.attachment.blurhashPlaceholder); - } - - private void onViewClick(View v){ - 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)); - } } } } 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 bc296d1b..cb04733d 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 @@ -45,6 +45,7 @@ public abstract class StatusDisplayItem{ case REBLOG_OR_REPLY_LINE -> new ReblogOrReplyLineStatusDisplayItem.Holder(activity, parent); case TEXT -> new TextStatusDisplayItem.Holder(activity, parent); case PHOTO -> new PhotoStatusDisplayItem.Holder(activity, parent); + case GIFV -> new GifVStatusDisplayItem.Holder(activity, parent); case FOOTER -> new FooterStatusDisplayItem.Holder(activity, parent); default -> throw new UnsupportedOperationException(); }; @@ -63,7 +64,7 @@ public abstract class StatusDisplayItem{ int photoIndex=0; int totalPhotos=0; for(Attachment attachment:statusForContent.mediaAttachments){ - if(attachment.type==Attachment.Type.IMAGE){ + if(attachment.type==Attachment.Type.IMAGE || attachment.type==Attachment.Type.GIFV){ totalPhotos++; } } @@ -71,6 +72,9 @@ public abstract class StatusDisplayItem{ if(attachment.type==Attachment.Type.IMAGE){ items.add(new PhotoStatusDisplayItem(parentID, status, attachment, fragment, photoIndex, totalPhotos)); photoIndex++; + }else if(attachment.type==Attachment.Type.GIFV){ + items.add(new GifVStatusDisplayItem(parentID, status, attachment, fragment, photoIndex, totalPhotos)); + photoIndex++; } } items.add(new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID)); diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewer.java b/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewer.java index 35c258f9..e84cc3d9 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewer.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewer.java @@ -3,10 +3,16 @@ package org.joinmastodon.android.ui.photoviewer; import android.app.Activity; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.graphics.SurfaceTexture; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.media.MediaPlayer; +import android.net.Uri; +import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; +import android.view.Surface; +import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -16,6 +22,8 @@ import android.widget.ImageView; import org.joinmastodon.android.model.Attachment; +import java.io.IOException; +import java.util.ArrayList; import java.util.List; import androidx.annotation.NonNull; @@ -28,6 +36,8 @@ import me.grishka.appkit.utils.BindableViewHolder; import me.grishka.appkit.utils.CubicBezierInterpolator; public class PhotoViewer implements ZoomPanView.Listener{ + private static final String TAG="PhotoViewer"; + private Activity activity; private List attachments; private int currentIndex; @@ -37,6 +47,7 @@ public class PhotoViewer implements ZoomPanView.Listener{ private FrameLayout windowView; private ViewPager2 pager; private ColorDrawable background=new ColorDrawable(0xff000000); + private ArrayList players=new ArrayList<>(); public PhotoViewer(Activity activity, List attachments, int index, Listener listener){ this.activity=activity; @@ -79,7 +90,7 @@ public class PhotoViewer implements ZoomPanView.Listener{ int[] radius=new int[4]; if(listener.startPhotoViewTransition(index, rect, radius)){ RecyclerView rv=(RecyclerView) pager.getChildAt(0); - PhotoViewHolder holder=(PhotoViewHolder) rv.findViewHolderForAdapterPosition(index); + BaseHolder holder=(BaseHolder) rv.findViewHolderForAdapterPosition(index); holder.zoomPanView.animateIn(rect, radius); } @@ -121,7 +132,7 @@ public class PhotoViewer implements ZoomPanView.Listener{ int[] radius=new int[4]; if(listener.startPhotoViewTransition(index, rect, radius)){ RecyclerView rv=(RecyclerView) pager.getChildAt(0); - PhotoViewHolder holder=(PhotoViewHolder) rv.findViewHolderForAdapterPosition(index); + BaseHolder holder=(BaseHolder) rv.findViewHolderForAdapterPosition(index); holder.zoomPanView.animateOut(rect, radius, velocityY); }else{ windowView.animate() @@ -140,6 +151,8 @@ public class PhotoViewer implements ZoomPanView.Listener{ @Override public void onDismissed(){ + for(MediaPlayer player:players) + player.release(); listener.setPhotoViewVisibility(pager.getCurrentItem(), true); wm.removeView(windowView); listener.photoViewerDismissed(); @@ -193,16 +206,20 @@ public class PhotoViewer implements ZoomPanView.Listener{ void photoViewerDismissed(); } - private class PhotoViewAdapter extends RecyclerView.Adapter{ + private class PhotoViewAdapter extends RecyclerView.Adapter{ @NonNull @Override - public PhotoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ - return new PhotoViewHolder(); + public BaseHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ + return switch(viewType){ + case 0 -> new PhotoViewHolder(); + case 1 -> new GifVViewHolder(); + default -> throw new IllegalStateException("Unexpected value: "+viewType); + }; } @Override - public void onBindViewHolder(@NonNull PhotoViewHolder holder, int position){ + public void onBindViewHolder(@NonNull BaseHolder holder, int position){ holder.bind(attachments.get(position)); } @@ -210,27 +227,63 @@ public class PhotoViewer implements ZoomPanView.Listener{ public int getItemCount(){ return attachments.size(); } + + @Override + public int getItemViewType(int position){ + Attachment att=attachments.get(position); + return switch(att.type){ + case IMAGE -> 0; + case GIFV -> 1; + default -> throw new IllegalStateException("Unexpected value: "+att.type); + }; + } + + @Override + public void onViewDetachedFromWindow(@NonNull BaseHolder holder){ + super.onViewDetachedFromWindow(holder); + if(holder instanceof GifVViewHolder){ + ((GifVViewHolder) holder).reset(); + } + } + + @Override + public void onViewAttachedToWindow(@NonNull BaseHolder holder){ + super.onViewAttachedToWindow(holder); + if(holder instanceof GifVViewHolder){ + ((GifVViewHolder) holder).prepareAndStartPlayer(); + } + } } - private class PhotoViewHolder extends BindableViewHolder implements ViewImageLoader.Target{ - public ImageView imageView; + private abstract class BaseHolder extends BindableViewHolder{ public ZoomPanView zoomPanView; - - public PhotoViewHolder(){ + public BaseHolder(){ super(new ZoomPanView(activity)); zoomPanView=(ZoomPanView) itemView; zoomPanView.setListener(PhotoViewer.this); - itemView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - imageView=new ImageView(activity); - ((FrameLayout)itemView).addView(imageView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER)); + zoomPanView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } @Override public void onBind(Attachment item){ + zoomPanView.setScrollDirections(getAbsoluteAdapterPosition()>0, getAbsoluteAdapterPosition()0, getAbsoluteAdapterPosition() + + + diff --git a/mastodon/src/main/res/drawable/ic_play.xml b/mastodon/src/main/res/drawable/ic_play.xml new file mode 100644 index 00000000..d23f7015 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_play.xml @@ -0,0 +1,9 @@ + + + diff --git a/mastodon/src/main/res/drawable/play_button.xml b/mastodon/src/main/res/drawable/play_button.xml new file mode 100644 index 00000000..23b3b5f3 --- /dev/null +++ b/mastodon/src/main/res/drawable/play_button.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/layout/display_item_gifv.xml b/mastodon/src/main/res/layout/display_item_gifv.xml new file mode 100644 index 00000000..35b7b198 --- /dev/null +++ b/mastodon/src/main/res/layout/display_item_gifv.xml @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file