From 54a74a8586960be1ca2e49dc574ca499fdc0862b Mon Sep 17 00:00:00 2001 From: adbenitez Date: Mon, 11 May 2026 17:48:19 +0200 Subject: [PATCH] migrate to ViewPager2 in MediaPreviewActivity --- .../securesms/MediaPreviewActivity.java | 113 +++++++++--------- .../components/ControllableViewPager.java | 31 ----- .../ExtendedOnPageChangedListener.java | 22 ---- .../components/viewpager/HackyViewPager.java | 41 ------- .../res/layout/media_preview_activity.xml | 2 +- 5 files changed, 60 insertions(+), 149 deletions(-) delete mode 100644 src/main/java/org/thoughtcrime/securesms/components/ControllableViewPager.java delete mode 100644 src/main/java/org/thoughtcrime/securesms/components/viewpager/ExtendedOnPageChangedListener.java delete mode 100644 src/main/java/org/thoughtcrime/securesms/components/viewpager/HackyViewPager.java diff --git a/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java b/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java index d9663f3c9..cec946b1d 100644 --- a/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -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 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 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 implements MediaItemAdapter { private final WeakHashMap 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); diff --git a/src/main/java/org/thoughtcrime/securesms/components/ControllableViewPager.java b/src/main/java/org/thoughtcrime/securesms/components/ControllableViewPager.java deleted file mode 100644 index be7cb48ac..000000000 --- a/src/main/java/org/thoughtcrime/securesms/components/ControllableViewPager.java +++ /dev/null @@ -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); - } -} diff --git a/src/main/java/org/thoughtcrime/securesms/components/viewpager/ExtendedOnPageChangedListener.java b/src/main/java/org/thoughtcrime/securesms/components/viewpager/ExtendedOnPageChangedListener.java deleted file mode 100644 index 8c7222047..000000000 --- a/src/main/java/org/thoughtcrime/securesms/components/viewpager/ExtendedOnPageChangedListener.java +++ /dev/null @@ -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) {} -} diff --git a/src/main/java/org/thoughtcrime/securesms/components/viewpager/HackyViewPager.java b/src/main/java/org/thoughtcrime/securesms/components/viewpager/HackyViewPager.java deleted file mode 100644 index 4d0504728..000000000 --- a/src/main/java/org/thoughtcrime/securesms/components/viewpager/HackyViewPager.java +++ /dev/null @@ -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 - * - *

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. - * - *

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; - } - } -} diff --git a/src/main/res/layout/media_preview_activity.xml b/src/main/res/layout/media_preview_activity.xml index e3c79688b..b0f50199f 100644 --- a/src/main/res/layout/media_preview_activity.xml +++ b/src/main/res/layout/media_preview_activity.xml @@ -7,7 +7,7 @@ android:fitsSystemWindows="true" android:background="@color/gray95"> -