migrate to ViewPager2 in MediaPreviewActivity

This commit is contained in:
adbenitez
2026-05-11 17:48:19 +02:00
parent f25474947b
commit 54a74a8586
5 changed files with 60 additions and 149 deletions
@@ -33,14 +33,13 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;
import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcMediaGalleryElement;
@@ -48,7 +47,6 @@ import com.b44t.messenger.DcMsg;
import java.io.IOException;
import java.util.WeakHashMap;
import org.thoughtcrime.securesms.components.MediaView;
import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader;
@@ -88,7 +86,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
@Nullable private DcMsg messageRecord;
private DcContext dcContext;
private MediaItem initialMedia;
private ViewPager mediaPager;
private ViewPager2 mediaPager;
private Recipient conversationRecipient;
private boolean leftIsRecent;
@@ -190,7 +188,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
private void initializeViews() {
mediaPager = findViewById(R.id.media_pager);
mediaPager.setOffscreenPageLimit(1);
mediaPager.addOnPageChangeListener(new ViewPagerListener());
mediaPager.registerOnPageChangeCallback(new ViewPagerListener());
}
private void initializeResources() {
@@ -260,10 +258,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
private int cleanupMedia() {
int restartItem = mediaPager.getCurrentItem();
mediaPager.removeAllViews();
mediaPager.setAdapter(null);
return restartItem;
}
@@ -483,22 +478,25 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
@SuppressWarnings("ConstantConditions")
DcMediaPagerAdapter adapter =
new DcMediaPagerAdapter(this, GlideApp.with(this), getWindow(), data, leftIsRecent);
mediaPager.setAdapter(adapter);
adapter.setActive(true);
mediaPager.setAdapter(adapter);
if (restartItem < 0) mediaPager.setCurrentItem(data.getPosition());
else mediaPager.setCurrentItem(restartItem);
if (restartItem < 0) mediaPager.setCurrentItem(data.getPosition(), false);
else mediaPager.setCurrentItem(restartItem, false);
}
}
@Override
public void onLoaderReset(Loader<DcMediaGalleryElement> loader) {}
private class ViewPagerListener extends ExtendedOnPageChangedListener {
private class ViewPagerListener extends ViewPager2.OnPageChangeCallback {
private Integer currentPage = null;
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
if (currentPage != null && currentPage != position) onPageUnselected(currentPage);
currentPage = position;
MediaItemAdapter adapter = (MediaItemAdapter) mediaPager.getAdapter();
@@ -510,8 +508,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
}
}
@Override
public void onPageUnselected(int position) {
private void onPageUnselected(int position) {
MediaItemAdapter adapter = (MediaItemAdapter) mediaPager.getAdapter();
if (adapter != null) {
@@ -526,7 +523,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
}
}
private static class SingleItemPagerAdapter extends PagerAdapter implements MediaItemAdapter {
private static class SingleItemPagerAdapter extends RecyclerView.Adapter<SingleItemPagerAdapter.MediaViewHolder> implements MediaItemAdapter {
private final GlideRequests glideRequests;
private final Window window;
@@ -555,37 +552,29 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
}
@Override
public int getCount() {
public int getItemCount() {
return 1;
}
@NonNull
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
public MediaViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new MediaViewHolder(inflater.inflate(R.layout.media_view_page, parent, false));
}
@Override
public @NonNull Object instantiateItem(@NonNull ViewGroup container, int position) {
View itemView = inflater.inflate(R.layout.media_view_page, container, false);
MediaView mediaView = itemView.findViewById(R.id.media_view);
public void onBindViewHolder(@NonNull MediaViewHolder holder, int position) {
try {
mediaView.set(glideRequests, window, uri, name, mediaType, size, true);
holder.mediaView.set(glideRequests, window, uri, name, mediaType, size, true);
} catch (IOException e) {
Log.w(TAG, e);
}
container.addView(itemView);
return itemView;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
MediaView mediaView = ((FrameLayout) object).findViewById(R.id.media_view);
mediaView.cleanup();
container.removeView((FrameLayout) object);
public void onViewRecycled(@NonNull MediaViewHolder holder) {
super.onViewRecycled(holder);
holder.mediaView.cleanup();
}
@Override
@@ -595,9 +584,18 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
@Override
public void pause(int position) {}
static class MediaViewHolder extends RecyclerView.ViewHolder {
final MediaView mediaView;
MediaViewHolder(@NonNull View itemView) {
super(itemView);
mediaView = itemView.findViewById(R.id.media_view);
}
}
}
private static class DcMediaPagerAdapter extends PagerAdapter implements MediaItemAdapter {
private static class DcMediaPagerAdapter extends RecyclerView.Adapter<DcMediaPagerAdapter.MediaViewHolder> implements MediaItemAdapter {
private final WeakHashMap<Integer, MediaView> mediaViews = new WeakHashMap<>();
@@ -630,21 +628,21 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
}
@Override
public int getCount() {
public int getItemCount() {
if (!active) return 0;
else return gallery.getCount();
}
@NonNull
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
public MediaViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView =
LayoutInflater.from(context).inflate(R.layout.media_view_page, parent, false);
return new MediaViewHolder(itemView);
}
@Override
public @NonNull Object instantiateItem(@NonNull ViewGroup container, int position) {
View itemView =
LayoutInflater.from(context).inflate(R.layout.media_view_page, container, false);
MediaView mediaView = itemView.findViewById(R.id.media_view);
public void onBindViewHolder(@NonNull MediaViewHolder holder, int position) {
boolean autoplay = position == autoPlayPosition;
int cursorPosition = getCursorPosition(position);
@@ -656,7 +654,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
try {
//noinspection ConstantConditions
mediaView.set(
holder.mediaView.set(
glideRequests,
window,
Uri.fromFile(msg.getFileAsFile()),
@@ -668,19 +666,17 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
Log.w(TAG, e);
}
mediaViews.put(position, mediaView);
container.addView(itemView);
return itemView;
mediaViews.put(position, holder.mediaView);
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
MediaView mediaView = ((FrameLayout) object).findViewById(R.id.media_view);
mediaView.cleanup();
mediaViews.remove(position);
container.removeView((FrameLayout) object);
public void onViewRecycled(@NonNull MediaViewHolder holder) {
super.onViewRecycled(holder);
int pos = holder.getBindingAdapterPosition();
if (pos != RecyclerView.NO_POSITION) {
mediaViews.remove(pos);
}
holder.mediaView.cleanup();
}
public MediaItem getMediaItemFor(int position) {
@@ -710,6 +706,15 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
if (leftIsRecent) return position;
else return gallery.getCount() - 1 - position;
}
static class MediaViewHolder extends RecyclerView.ViewHolder {
final MediaView mediaView;
MediaViewHolder(@NonNull View itemView) {
super(itemView);
mediaView = itemView.findViewById(R.id.media_view);
}
}
}
private static class MediaItem {
@@ -742,7 +747,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
}
}
interface MediaItemAdapter {
private interface MediaItemAdapter {
MediaItem getMediaItemFor(int position);
void pause(int position);
@@ -1,31 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.ViewPager;
import org.thoughtcrime.securesms.components.viewpager.HackyViewPager;
/** An implementation of {@link ViewPager} that disables swiping when the view is disabled. */
public class ControllableViewPager extends HackyViewPager {
public ControllableViewPager(@NonNull Context context) {
super(context);
}
public ControllableViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return isEnabled() && super.onTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return isEnabled() && super.onInterceptTouchEvent(ev);
}
}
@@ -1,22 +0,0 @@
package org.thoughtcrime.securesms.components.viewpager;
import androidx.viewpager.widget.ViewPager;
public abstract class ExtendedOnPageChangedListener implements ViewPager.OnPageChangeListener {
private Integer currentPage = null;
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
@Override
public void onPageSelected(int position) {
if (currentPage != null && currentPage != position) onPageUnselected(currentPage);
currentPage = position;
}
public abstract void onPageUnselected(int position);
@Override
public void onPageScrollStateChanged(int state) {}
}
@@ -1,41 +0,0 @@
package org.thoughtcrime.securesms.components.viewpager;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import androidx.viewpager.widget.ViewPager;
/**
* Hacky fix for http://code.google.com/p/android/issues/detail?id=18990
*
* <p>ScaleGestureDetector seems to mess up the touch events, which means that ViewGroups which make
* use of onInterceptTouchEvent throw a lot of IllegalArgumentException: pointerIndex out of range.
*
* <p>There's not much I can do in my code for now, but we can mask the result by just catching the
* problem and ignoring it.
*
* @author Chris Banes
*/
public class HackyViewPager extends ViewPager {
private static final String TAG = "HackyViewPager";
public HackyViewPager(Context context) {
super(context);
}
public HackyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
try {
return super.onInterceptTouchEvent(ev);
} catch (IllegalArgumentException e) {
Log.w(TAG, e);
return false;
}
}
}