浏览代码

Merge pull request #13016 from nextcloud/bugfix/media-item-delete-crash

Bugfix Media Item Delete Crash
Alper Öztürk 1 年之前
父节点
当前提交
4ecce019a4

+ 0 - 47
app/src/main/java/com/owncloud/android/ui/components/CustomViewPager.java

@@ -1,47 +0,0 @@
-/*
- * Nextcloud - Android Client
- *
- * SPDX-FileCopyrightText: 2018 Tobias Kaminsky <tobias@kaminsky.me>
- * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
- */
-package com.owncloud.android.ui.components;
-
-import android.annotation.SuppressLint;
-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;
-
-public class CustomViewPager extends ViewPager {
-    public CustomViewPager(@NonNull Context context) {
-        super(context);
-    }
-
-    public CustomViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @SuppressLint("ClickableViewAccessibility")
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        try {
-            return super.onTouchEvent(ev);
-        } catch (IllegalArgumentException ex) {
-            // no logging as this might flood log and we cannot do anything
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        try {
-            return super.onInterceptTouchEvent(ev);
-        } catch (IllegalArgumentException ex) {
-            // no logging as this might flood log and we cannot do anything
-        }
-        return false;
-    }
-}

+ 42 - 67
app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java

@@ -59,7 +59,7 @@ import androidx.annotation.NonNull;
 import androidx.appcompat.app.ActionBar;
 import androidx.drawerlayout.widget.DrawerLayout;
 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
-import androidx.viewpager.widget.ViewPager;
+import androidx.viewpager2.widget.ViewPager2;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 
 /**
@@ -68,7 +68,6 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 @SuppressWarnings("PMD.AvoidDuplicateLiterals")
 public class PreviewImageActivity extends FileActivity implements
     FileFragment.ContainerActivity,
-    ViewPager.OnPageChangeListener,
     OnRemoteOperationListener,
     Injectable {
 
@@ -78,13 +77,12 @@ public class PreviewImageActivity extends FileActivity implements
     private static final String KEY_SYSTEM_VISIBLE = "TRUE";
 
     private OCFile livePhotoFile;
-    private ViewPager viewPager;
+    private ViewPager2 viewPager;
     private PreviewImagePagerAdapter previewImagePagerAdapter;
     private int savedPosition;
     private boolean hasSavedPosition;
     private boolean requestWaitingForBinder;
     private DownloadFinishReceiver downloadFinishReceiver;
-    private UploadFinishReceiver uploadFinishReceiver;
     private View fullScreenAnchorView;
     private boolean isDownloadWorkStarted = false;
 
@@ -158,7 +156,7 @@ public class PreviewImageActivity extends FileActivity implements
         if (virtualFolderType != null && virtualFolderType != VirtualFolderType.NONE) {
             VirtualFolderType type = (VirtualFolderType) virtualFolderType;
 
-            previewImagePagerAdapter = new PreviewImagePagerAdapter(getSupportFragmentManager(),
+            previewImagePagerAdapter = new PreviewImagePagerAdapter(this,
                                                                     type,
                                                                     user,
                                                                     getStorageManager());
@@ -168,11 +166,11 @@ public class PreviewImageActivity extends FileActivity implements
 
             if (parentFolder == null) {
                 // should not be necessary
-                parentFolder = getStorageManager().getFileByPath(OCFile.ROOT_PATH);
+                parentFolder = getStorageManager().getFileByEncryptedRemotePath(OCFile.ROOT_PATH);
             }
 
             previewImagePagerAdapter = new PreviewImagePagerAdapter(
-                getSupportFragmentManager(),
+                this,
                 livePhotoFile,
                 parentFolder,
                 user,
@@ -185,11 +183,16 @@ public class PreviewImageActivity extends FileActivity implements
         viewPager = findViewById(R.id.fragmentPager);
 
         int position = hasSavedPosition ? savedPosition : previewImagePagerAdapter.getFilePosition(getFile());
-        position = position >= 0 ? position : 0;
+        position = Math.max(position, 0);
 
         viewPager.setAdapter(previewImagePagerAdapter);
-        viewPager.addOnPageChangeListener(this);
-        viewPager.setCurrentItem(position);
+        viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
+            @Override
+            public void onPageSelected(int position) {
+                selectPage(position);
+            }
+        });
+        viewPager.setCurrentItem(position, false);
 
         if (position == 0 && !getFile().isDown()) {
             // this is necessary because mViewPager.setCurrentItem(0) just after setting the
@@ -247,7 +250,7 @@ public class PreviewImageActivity extends FileActivity implements
             if (file != null) {
                 /// Refresh the activity according to the Account and OCFile set
                 setFile(file);  // reset after getting it fresh from storageManager
-                getSupportActionBar().setTitle(getFile().getFileName());
+                updateActionBarTitle(getFile().getFileName());
                 //if (!stateWasRecovered) {
                 initViewPager(optionalUser.get());
                 //}
@@ -271,15 +274,16 @@ public class PreviewImageActivity extends FileActivity implements
         super.onRemoteOperationFinish(operation, result);
 
         if (operation instanceof RemoveFileOperation) {
-            // initialize the pager with the new file list
-            initViewPager(getUser().get());
-            if (viewPager.getAdapter().getCount() > 0) {
-                // Trigger page reselection, to update the title
-                onPageSelected(viewPager.getCurrentItem());
-            } else {
-                // Last file has been deleted, so finish the activity
+            int deletePosition = viewPager.getCurrentItem();
+            int nextPosition = deletePosition > 0 ? deletePosition - 1 : 0;
+
+            if (previewImagePagerAdapter.getItemCount() <= 1) {
                 finish();
+                return;
             }
+
+            viewPager.setCurrentItem(nextPosition, true);
+            previewImagePagerAdapter.delete(deletePosition);
         } else if (operation instanceof SynchronizeFileOperation) {
             onSynchronizeFileOperationFinish(result);
         }
@@ -301,7 +305,7 @@ public class PreviewImageActivity extends FileActivity implements
                     requestWaitingForBinder = false;
                     Log_OC.d(TAG, "Simulating reselection of current page after connection " +
                         "of download binder");
-                    onPageSelected(viewPager.getCurrentItem());
+                    selectPage(viewPager.getCurrentItem());
                 }
             } else {
                 Log_OC.d(TAG, "Download worker stopped");
@@ -328,7 +332,7 @@ public class PreviewImageActivity extends FileActivity implements
         IntentFilter downloadIntentFilter = new IntentFilter(FileDownloadWorker.Companion.getDownloadFinishMessage());
         localBroadcastManager.registerReceiver(downloadFinishReceiver, downloadIntentFilter);
 
-        uploadFinishReceiver = new UploadFinishReceiver();
+        UploadFinishReceiver uploadFinishReceiver = new UploadFinishReceiver();
         IntentFilter uploadIntentFilter = new IntentFilter(FileUploadWorker.Companion.getUploadFinishMessage());
         localBroadcastManager.registerReceiver(uploadFinishReceiver, uploadIntentFilter);
     }
@@ -384,8 +388,7 @@ public class PreviewImageActivity extends FileActivity implements
      *
      *  @param  position        Position index of the new selected page
      */
-    @Override
-    public void onPageSelected(int position) {
+    public void selectPage(int position) {
         savedPosition = position;
         hasSavedPosition = true;
 
@@ -405,45 +408,21 @@ public class PreviewImageActivity extends FileActivity implements
             }
         }
 
-        // Update ActionBar title
         if (currentFile != null) {
-            if (getSupportActionBar() != null) {
-                getSupportActionBar().setTitle(currentFile.getFileName());
-            }
+            updateActionBarTitle(currentFile.getFileName());
             setDrawerIndicatorEnabled(false);
         }
     }
 
-    /**
-     * Called when the scroll state changes. Useful for discovering when the user begins dragging,
-     * when the pager is automatically settling to the current page. when it is fully stopped/idle.
-     *
-     * @param   state       The new scroll state (SCROLL_STATE_IDLE, _DRAGGING, _SETTLING
-     */
-    @Override
-    public void onPageScrollStateChanged(int state) {
-        // not used at the moment
-    }
-
-    /**
-     * This method will be invoked when the current page is scrolled, either as part of a
-     * programmatically initiated smooth scroll or a user initiated touch scroll.
-     *
-     * @param   position                Position index of the first page currently being displayed.
-     *                                  Page position+1 will be visible if positionOffset is
-     *                                  nonzero.
-     * @param   positionOffset          Value from [0, 1) indicating the offset from the page
-     *                                  at position.
-     * @param   positionOffsetPixels    Value in pixels indicating the offset from position.
-     */
-    @Override
-    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
-        // not used at the moment
+    public void updateActionBarTitle(String title) {
+        if (getSupportActionBar() != null) {
+            getSupportActionBar().setTitle(title);
+        }
     }
 
     /**
      * Class waiting for broadcast events from the {@link FileDownloadWorker} service.
-     *
+     * <p>
      * Updates the UI when a download is started or finished, provided that it is relevant for the
      * folder displayed in the gallery.
      */
@@ -465,8 +444,9 @@ public class PreviewImageActivity extends FileActivity implements
         String accountName = intent.getStringExtra(FileDownloadWorker.EXTRA_ACCOUNT_NAME);
         String downloadedRemotePath = intent.getStringExtra(FileDownloadWorker.EXTRA_REMOTE_PATH);
         String downloadBehaviour = intent.getStringExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR);
+
         if (getAccount().name.equals(accountName) && downloadedRemotePath != null) {
-            OCFile file = getStorageManager().getFileByPath(downloadedRemotePath);
+            OCFile file = getStorageManager().getFileByEncryptedRemotePath(downloadedRemotePath);
             boolean downloadWasFine = intent.getBooleanExtra(FileDownloadWorker.EXTRA_DOWNLOAD_RESULT, false);
 
             if (EditImageActivity.OPEN_IMAGE_EDITOR.equals(downloadBehaviour)) {
@@ -476,16 +456,19 @@ public class PreviewImageActivity extends FileActivity implements
                 if (position >= 0) {
                     if (downloadWasFine) {
                         previewImagePagerAdapter.updateFile(position, file);
-
                     } else {
                         previewImagePagerAdapter.updateWithDownloadError(position);
                     }
-                    previewImagePagerAdapter.notifyDataSetChanged();   // will trigger the creation of new fragments
+                    previewImagePagerAdapter.notifyItemChanged(position);
                 } else if (downloadWasFine) {
-                    initViewPager(getUser().get());
-                    int newPosition = previewImagePagerAdapter.getFilePosition(file);
-                    if (newPosition >= 0) {
-                        viewPager.setCurrentItem(newPosition);
+                    Optional<User> user = getUser();
+
+                    if (user.isPresent()) {
+                        initViewPager(user.get());
+                        int newPosition = previewImagePagerAdapter.getFilePosition(file);
+                        if (newPosition >= 0) {
+                            viewPager.setCurrentItem(newPosition);
+                        }
                     }
                 }
             }
@@ -502,19 +485,11 @@ public class PreviewImageActivity extends FileActivity implements
 
         if (visible) {
             hideSystemUI(fullScreenAnchorView);
-            // actionBar.hide(); // propagated through
-            // OnSystemUiVisibilityChangeListener()
         } else {
             showSystemUI(fullScreenAnchorView);
-            // actionBar.show(); // propagated through
-            // OnSystemUiVisibilityChangeListener()
         }
     }
 
-    public void switchToFullScreen() {
-        hideSystemUI(fullScreenAnchorView);
-    }
-
     public void startImageEditor(OCFile file) {
         if (file.isDown()) {
             Intent editImageIntent = new Intent(this, EditImageActivity.class);

+ 12 - 2
app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java

@@ -81,6 +81,7 @@ import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentManager;
 import androidx.fragment.app.FragmentStatePagerAdapter;
 import androidx.fragment.app.FragmentTransaction;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import pl.droidsonroids.gif.GifDrawable;
 
@@ -127,7 +128,7 @@ public class PreviewImageFragment extends FileFragment implements Injectable {
      * This method hides to client objects the need of doing the construction in two steps.
      *
      * @param imageFile             An {@link OCFile} to preview as an image in the fragment
-     * @param ignoreFirstSavedState Flag to work around an unexpected behaviour of {@link FragmentStatePagerAdapter} ;
+     * @param ignoreFirstSavedState Flag to work around an unexpected behaviour of {@link FragmentStateAdapter} ;
      *                                                           TODO better solution
      */
     public static PreviewImageFragment newInstance(@NonNull OCFile imageFile,
@@ -232,8 +233,17 @@ public class PreviewImageFragment extends FileFragment implements Injectable {
         if (savedInstanceState != null) {
             if (!ignoreFirstSavedState) {
                 OCFile file = BundleExtensionsKt.getParcelableArgument(savedInstanceState, EXTRA_FILE, OCFile.class);
+                if (file == null) {
+                    return;
+                }
+
                 setFile(file);
-                binding.image.setScale(Math.min(binding.image.getMaximumScale(), savedInstanceState.getFloat(EXTRA_ZOOM)));
+
+                try {
+                    binding.image.setScale(Math.min(binding.image.getMaximumScale(), savedInstanceState.getFloat(EXTRA_ZOOM)));
+                } catch (IllegalArgumentException e) {
+                    Log_OC.d(TAG, "Error caught at setScale: " + e);
+                }
             } else {
                 ignoreFirstSavedState = false;
             }

+ 0 - 246
app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java

@@ -1,246 +0,0 @@
-/*
- * Nextcloud - Android Client
- *
- * SPDX-FileCopyrightText: 2023 Alper Ozturk <alper_ozturk@proton.me>
- * SPDX-FileCopyrightText: 2018 Tobias Kaminsky <tobias@kaminsky.me>
- * SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
- * SPDX-FileCopyrightText: 2015 ownCloud Inc.
- * SPDX-FileCopyrightText: 2013 David A. Velasco <dvelasco@solidgear.es>
- * SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
- */
-package com.owncloud.android.ui.preview;
-
-import android.util.SparseArray;
-import android.view.ViewGroup;
-
-import com.nextcloud.client.account.User;
-import com.nextcloud.client.preferences.AppPreferences;
-import com.owncloud.android.datamodel.FileDataStorageManager;
-import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.datamodel.VirtualFolderType;
-import com.owncloud.android.ui.fragment.FileFragment;
-import com.owncloud.android.utils.FileSortOrder;
-import com.owncloud.android.utils.FileStorageUtils;
-
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import javax.annotation.Nullable;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.OptIn;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentStatePagerAdapter;
-import androidx.media3.common.util.UnstableApi;
-
-/**
- * Adapter class that provides Fragment instances
- */
-public class PreviewImagePagerAdapter extends FragmentStatePagerAdapter {
-
-    private OCFile selectedFile;
-    private List<OCFile> mImageFiles;
-    private User user;
-    private Set<Object> mObsoleteFragments;
-    private Set<Integer> mObsoletePositions;
-    private Set<Integer> mDownloadErrors;
-    private FileDataStorageManager mStorageManager;
-    private SparseArray<FileFragment> mCachedFragments;
-
-    /**
-     * Constructor
-     *
-     * @param fragmentManager {@link FragmentManager} instance that will handle the {@link Fragment}s provided by the
-     *                        adapter.
-     * @param parentFolder    Folder where images will be searched for.
-     * @param storageManager  Bridge to database.
-     */
-    public PreviewImagePagerAdapter(FragmentManager fragmentManager,
-                                    OCFile selectedFile,
-                                    OCFile parentFolder,
-                                    User user,
-                                    FileDataStorageManager storageManager,
-                                    boolean onlyOnDevice,
-                                    AppPreferences preferences) {
-        super(fragmentManager);
-        if (parentFolder == null) {
-            throw new IllegalArgumentException("NULL parent folder");
-        }
-        if (storageManager == null) {
-            throw new IllegalArgumentException("NULL storage manager");
-        }
-
-        this.user = user;
-        this.selectedFile = selectedFile;
-        mStorageManager = storageManager;
-        mImageFiles = mStorageManager.getFolderImages(parentFolder, onlyOnDevice);
-
-        FileSortOrder sortOrder = preferences.getSortOrderByFolder(parentFolder);
-        mImageFiles = sortOrder.sortCloudFiles(mImageFiles);
-
-        mObsoleteFragments = new HashSet<>();
-        mObsoletePositions = new HashSet<>();
-        mDownloadErrors = new HashSet<>();
-        mCachedFragments = new SparseArray<>();
-    }
-
-    /**
-     * Constructor
-     *
-     * @param fragmentManager {@link FragmentManager} instance that will handle the {@link Fragment}s provided by the
-     *                        adapter.
-     * @param type            Type of virtual folder, e.g. favorite or photos
-     * @param storageManager  Bridge to database.
-     */
-    public PreviewImagePagerAdapter(FragmentManager fragmentManager,
-                                    VirtualFolderType type,
-                                    User user,
-                                    FileDataStorageManager storageManager) {
-        super(fragmentManager);
-
-        if (fragmentManager == null) {
-            throw new IllegalArgumentException("NULL FragmentManager instance");
-        }
-        if (type == null) {
-            throw new IllegalArgumentException("NULL parent folder");
-        }
-        if (type == VirtualFolderType.NONE) {
-            throw new IllegalArgumentException("NONE virtual folder type");
-        }
-        if (storageManager == null) {
-            throw new IllegalArgumentException("NULL storage manager");
-        }
-
-        this.user = user;
-        mStorageManager = storageManager;
-
-        if (type == VirtualFolderType.GALLERY) {
-            mImageFiles = mStorageManager.getAllGalleryItems();
-            mImageFiles = FileStorageUtils.sortOcFolderDescDateModifiedWithoutFavoritesFirst(mImageFiles);
-        } else {
-            mImageFiles = mStorageManager.getVirtualFolderContent(type, true);
-        }
-
-        mObsoleteFragments = new HashSet<>();
-        mObsoletePositions = new HashSet<>();
-        mDownloadErrors = new HashSet<>();
-        mCachedFragments = new SparseArray<>();
-    }
-
-    /**
-     * Returns the image files handled by the adapter.
-     *
-     * @return OCFile desired image or null if position is not in adapter
-     */
-    @Nullable
-    public OCFile getFileAt(int position) {
-        try {
-            return mImageFiles.get(position);
-        } catch (IndexOutOfBoundsException exception) {
-            return null;
-        }
-    }
-
-    private void addVideoOfLivePhoto(OCFile file) {
-        file.livePhotoVideo = selectedFile;
-    }
-
-    @NonNull
-    @OptIn(markerClass = UnstableApi.class)
-    public Fragment getItem(int i) {
-        OCFile file = getFileAt(i);
-        Fragment fragment;
-
-        if (file == null) {
-            fragment = PreviewImageErrorFragment.newInstance();
-        } else if (file.isDown()) {
-            fragment = PreviewImageFragment.newInstance(file, mObsoletePositions.contains(i), false);
-        } else {
-            addVideoOfLivePhoto(file);
-
-            if (mDownloadErrors.remove(i)) {
-                fragment = FileDownloadFragment.newInstance(file, user, true);
-                ((FileDownloadFragment) fragment).setError(true);
-            } else {
-                if (file.isEncrypted()) {
-                    fragment = FileDownloadFragment.newInstance(file, user, mObsoletePositions.contains(i));
-                } else if (PreviewMediaFragment.canBePreviewed(file)) {
-                    fragment = PreviewMediaFragment.newInstance(file, user, 0, false, file.livePhotoVideo != null);
-                } else {
-                    fragment = PreviewImageFragment.newInstance(file, mObsoletePositions.contains(i), true);
-                }
-            }
-        }
-
-        mObsoletePositions.remove(i);
-        return fragment;
-    }
-
-    public int getFilePosition(OCFile file) {
-        return mImageFiles.indexOf(file);
-    }
-
-    @Override
-    public int getCount() {
-        return mImageFiles.size();
-    }
-
-    @Override
-    public CharSequence getPageTitle(int position) {
-        OCFile file = getFileAt(position);
-
-        if (file != null) {
-            return file.getFileName();
-        } else {
-            return "";
-        }
-    }
-
-
-    public void updateFile(int position, OCFile file) {
-        FileFragment fragmentToUpdate = mCachedFragments.get(position);
-        if (fragmentToUpdate != null) {
-            mObsoleteFragments.add(fragmentToUpdate);
-        }
-        mObsoletePositions.add(position);
-        mImageFiles.set(position, file);
-    }
-
-
-    public void updateWithDownloadError(int position) {
-        FileFragment fragmentToUpdate = mCachedFragments.get(position);
-        if (fragmentToUpdate != null) {
-            mObsoleteFragments.add(fragmentToUpdate);
-        }
-        mDownloadErrors.add(position);
-    }
-
-    @Override
-    public int getItemPosition(@NonNull Object object) {
-        if (mObsoleteFragments.remove(object)) {
-            return POSITION_NONE;
-        }
-        return super.getItemPosition(object);
-    }
-
-    @NonNull
-    @Override
-    public Object instantiateItem(@NonNull ViewGroup container, int position) {
-        Object fragment = super.instantiateItem(container, position);
-        mCachedFragments.put(position, (FileFragment) fragment);
-        return fragment;
-    }
-
-    @Override
-    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
-        mCachedFragments.remove(position);
-        super.destroyItem(container, position, object);
-    }
-
-
-    public boolean pendingErrorAt(int position) {
-        return mDownloadErrors.contains(position);
-    }
-}

+ 207 - 0
app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.kt

@@ -0,0 +1,207 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2023 Alper Ozturk <alper_ozturk@proton.me>
+ * SPDX-FileCopyrightText: 2018 Tobias Kaminsky <tobias@kaminsky.me>
+ * SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
+ * SPDX-FileCopyrightText: 2015 ownCloud Inc.
+ * SPDX-FileCopyrightText: 2013 David A. Velasco <dvelasco@solidgear.es>
+ * SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
+ */
+package com.owncloud.android.ui.preview
+
+import android.util.SparseArray
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+import androidx.viewpager2.adapter.FragmentStateAdapter
+import com.nextcloud.client.account.User
+import com.nextcloud.client.preferences.AppPreferences
+import com.owncloud.android.datamodel.FileDataStorageManager
+import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.datamodel.VirtualFolderType
+import com.owncloud.android.ui.fragment.FileFragment
+import com.owncloud.android.utils.FileStorageUtils
+
+/**
+ * Adapter class that provides Fragment instances
+ */
+class PreviewImagePagerAdapter : FragmentStateAdapter {
+
+    private var selectedFile: OCFile? = null
+    private var imageFiles: MutableList<OCFile> = mutableListOf()
+    private val user: User
+    private val mObsoleteFragments: MutableSet<Any>
+    private val mObsoletePositions: MutableSet<Int>
+    private val mDownloadErrors: MutableSet<Int>
+    private val mStorageManager: FileDataStorageManager
+    private val mCachedFragments: SparseArray<FileFragment>
+
+    /**
+     * Constructor
+     *
+     * @param fragmentActivity [FragmentActivity] instance that will handle the [Fragment]s provided by the
+     * adapter.
+     * @param parentFolder    Folder where images will be searched for.
+     * @param storageManager  Bridge to database.
+     */
+    @Suppress("LongParameterList")
+    constructor(
+        fragmentActivity: FragmentActivity,
+        selectedFile: OCFile?,
+        parentFolder: OCFile?,
+        user: User,
+        storageManager: FileDataStorageManager,
+        onlyOnDevice: Boolean,
+        preferences: AppPreferences
+    ) : super(fragmentActivity) {
+        requireNotNull(parentFolder) { "NULL parent folder" }
+
+        this.user = user
+        this.selectedFile = selectedFile
+        mStorageManager = storageManager
+        imageFiles = mStorageManager.getFolderImages(parentFolder, onlyOnDevice)
+
+        val sortOrder = preferences.getSortOrderByFolder(parentFolder)
+        imageFiles = sortOrder.sortCloudFiles(imageFiles.toMutableList()).toMutableList()
+
+        mObsoleteFragments = HashSet()
+        mObsoletePositions = HashSet()
+        mDownloadErrors = HashSet()
+        mCachedFragments = SparseArray()
+    }
+
+    /**
+     * Constructor
+     *
+     * @param fragmentActivity [FragmentActivity] instance that will handle the [Fragment]s provided by the
+     * adapter.
+     * @param type            Type of virtual folder, e.g. favorite or photos
+     * @param storageManager  Bridge to database.
+     */
+    constructor(
+        fragmentActivity: FragmentActivity,
+        type: VirtualFolderType?,
+        user: User,
+        storageManager: FileDataStorageManager
+    ) : super(fragmentActivity) {
+        requireNotNull(type) { "NULL parent folder" }
+        require(type != VirtualFolderType.NONE) { "NONE virtual folder type" }
+
+        this.user = user
+        mStorageManager = storageManager
+
+        if (type == VirtualFolderType.GALLERY) {
+            imageFiles = mStorageManager.allGalleryItems
+            imageFiles = FileStorageUtils.sortOcFolderDescDateModifiedWithoutFavoritesFirst(imageFiles)
+        } else {
+            imageFiles = mStorageManager.getVirtualFolderContent(type, true)
+        }
+
+        mObsoleteFragments = HashSet()
+        mObsoletePositions = HashSet()
+        mDownloadErrors = HashSet()
+        mCachedFragments = SparseArray()
+    }
+
+    fun delete(position: Int) {
+        if (position < 0 || position >= imageFiles.size) {
+            return
+        }
+
+        mCachedFragments[position]?.let {
+            mObsoleteFragments.add(it)
+        }
+
+        mObsoletePositions.add(position)
+
+        imageFiles.removeAt(position)
+        mDownloadErrors.remove(position)
+        mCachedFragments.remove(position)
+
+        notifyItemRemoved(position)
+    }
+
+    /**
+     * Returns the image files handled by the adapter.
+     *
+     * @return OCFile desired image or null if position is not in adapter
+     */
+    @Suppress("TooGenericExceptionCaught")
+    fun getFileAt(position: Int): OCFile? {
+        return try {
+            imageFiles[position]
+        } catch (exception: IndexOutOfBoundsException) {
+            null
+        }
+    }
+
+    override fun getItemId(position: Int): Long {
+        return imageFiles[position].hashCode().toLong()
+    }
+
+    private fun addVideoOfLivePhoto(file: OCFile) {
+        file.livePhotoVideo = selectedFile
+    }
+
+    fun getItem(i: Int): Fragment {
+        val file = getFileAt(i)
+        val fragment: Fragment
+
+        if (file == null) {
+            fragment = PreviewImageErrorFragment.newInstance()
+        } else if (file.isDown) {
+            fragment = PreviewImageFragment.newInstance(file, mObsoletePositions.contains(i), false)
+        } else {
+            addVideoOfLivePhoto(file)
+
+            if (mDownloadErrors.remove(i)) {
+                fragment = FileDownloadFragment.newInstance(file, user, true)
+                (fragment as FileDownloadFragment).setError(true)
+            } else {
+                fragment = if (file.isEncrypted) {
+                    FileDownloadFragment.newInstance(file, user, mObsoletePositions.contains(i))
+                } else if (PreviewMediaFragment.canBePreviewed(file)) {
+                    PreviewMediaFragment.newInstance(file, user, 0, false, file.livePhotoVideo != null)
+                } else {
+                    PreviewImageFragment.newInstance(file, mObsoletePositions.contains(i), true)
+                }
+            }
+        }
+
+        mObsoletePositions.remove(i)
+        return fragment
+    }
+
+    fun getFilePosition(file: OCFile): Int {
+        return imageFiles.indexOf(file)
+    }
+
+    fun updateFile(position: Int, file: OCFile) {
+        val fragmentToUpdate = mCachedFragments[position]
+        if (fragmentToUpdate != null) {
+            mObsoleteFragments.add(fragmentToUpdate)
+        }
+        mObsoletePositions.add(position)
+        imageFiles[position] = file
+    }
+
+    fun updateWithDownloadError(position: Int) {
+        val fragmentToUpdate = mCachedFragments[position]
+        if (fragmentToUpdate != null) {
+            mObsoleteFragments.add(fragmentToUpdate)
+        }
+        mDownloadErrors.add(position)
+    }
+
+    fun pendingErrorAt(position: Int): Boolean {
+        return mDownloadErrors.contains(position)
+    }
+
+    override fun createFragment(position: Int): Fragment {
+        return getItem(position)
+    }
+
+    override fun getItemCount(): Int {
+        return imageFiles.size
+    }
+}

+ 5 - 4
app/src/main/res/layout/preview_image_activity.xml

@@ -10,16 +10,17 @@
   ~ SPDX-FileCopyrightText: 2013 David A. Velasco <dvelasco@solidgear.es>
   ~ SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
 -->
-<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.drawerlayout.widget.DrawerLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/drawer_layout"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-    <com.owncloud.android.ui.components.CustomViewPager
+    <androidx.viewpager2.widget.ViewPager2
         android:id="@+id/fragmentPager"
+        android:orientation="horizontal"
 		android:layout_width="match_parent"
-        android:layout_height="match_parent"
-		/>
+        android:layout_height="match_parent" />
 
     <include
         layout="@layout/drawer"

+ 1 - 1
app/src/main/res/layout/preview_image_fragment.xml

@@ -15,7 +15,7 @@
     android:id="@+id/top"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:animateLayoutChanges="true">
+    android:animateLayoutChanges="false">
 
     <com.google.android.material.textview.MaterialTextView
         android:id="@+id/live_photo_indicator"