Browse Source

Merge pull request #9985 from nextcloud/fastscrollWithLib

Fastscroll for all lists
Tobias Kaminsky 3 years ago
parent
commit
31dda2e0fb

+ 2 - 0
app/build.gradle

@@ -281,6 +281,8 @@ dependencies {
     implementation "com.google.android.exoplayer:exoplayer:$exoplayerVersion"
     implementation "com.google.android.exoplayer:extension-okhttp:$exoplayerVersion"
 
+    implementation 'com.simplecityapps:recyclerview-fastscroll:2.0.1'
+    
     // Shimmer animation
     implementation 'com.elyeproj.libraries:loaderviewlibrary:2.0.0'
 

+ 1 - 0
app/src/androidTest/java/com/owncloud/android/ui/activity/UploadFilesActivityIT.kt

@@ -70,6 +70,7 @@ class UploadFilesActivityIT : AbstractIT() {
         }
 
         waitForIdleSync()
+        shortSleep()
 
         screenshot(sut)
     }

+ 4 - 4
app/src/main/java/com/owncloud/android/ui/EmptyRecyclerView.java

@@ -25,14 +25,14 @@ import android.content.Context;
 import android.util.AttributeSet;
 import android.view.View;
 
+import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView;
+
 import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.RecyclerView;
 
 /**
- * Extends RecyclerView to show a custom view if no data is available
- * Inspired by http://alexzh.com/tutorials/how-to-setemptyview-to-recyclerview
+ * Extends RecyclerView to show a custom view if no data is available Inspired by http://alexzh.com/tutorials/how-to-setemptyview-to-recyclerview
  */
-public class EmptyRecyclerView extends RecyclerView {
+public class EmptyRecyclerView extends FastScrollRecyclerView {
     private View mEmptyView;
     private boolean hasFooter = false;
 

+ 2 - 2
app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -485,7 +485,7 @@ public class FileDisplayActivity extends FileActivity
 
                 SearchEvent searchEvent = intent.getParcelableExtra(OCFileListFragment.SEARCH_EVENT);
                 if (searchEvent != null) {
-                    if (SearchRemoteOperation.SearchType.PHOTO_SEARCH.equals(searchEvent.getSearchType())) {
+                    if (SearchRemoteOperation.SearchType.PHOTO_SEARCH == searchEvent.getSearchType()) {
                         Log_OC.d(this, "Switch to photo search fragment");
 
                         GalleryFragment photoFragment = new GalleryFragment();
@@ -493,7 +493,7 @@ public class FileDisplayActivity extends FileActivity
                         bundle.putParcelable(OCFileListFragment.SEARCH_EVENT, searchEvent);
                         photoFragment.setArguments(bundle);
                         setLeftFragment(photoFragment);
-                    } else if (searchEvent.getSearchType().equals(SearchRemoteOperation.SearchType.SHARED_FILTER)) {
+                    } else if (searchEvent.getSearchType() == SearchRemoteOperation.SearchType.SHARED_FILTER) {
                         Log_OC.d(this, "Switch to shared fragment");
                         SharedListFragment sharedListFragment = new SharedListFragment();
                         Bundle bundle = new Bundle();

+ 50 - 6
app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java

@@ -84,10 +84,13 @@ import com.owncloud.android.utils.MimeTypeUtil;
 import com.owncloud.android.utils.theme.CapabilityUtils;
 import com.owncloud.android.utils.theme.ThemeColorUtils;
 import com.owncloud.android.utils.theme.ThemeDrawableUtils;
+import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView;
 
 import java.io.File;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
@@ -104,7 +107,7 @@ import androidx.recyclerview.widget.RecyclerView;
  * This Adapter populates a RecyclerView with all files and folders in a Nextcloud instance.
  */
 public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
-    implements DisplayUtils.AvatarGenerationListener {
+    implements DisplayUtils.AvatarGenerationListener, FastScrollRecyclerView.SectionedAdapter {
 
     private static final int showFilenameColumnThreshold = 4;
     private final ComponentsGetter transferServiceGetter;
@@ -137,6 +140,9 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
     private boolean showShareAvatar = false;
     private OCFile highlightedItem;
     private boolean showMetadata = true;
+    private FileSortOrder sortOrder;
+
+    private final SimpleDateFormat dateFormat = new SimpleDateFormat("MMMM yyyy", Locale.getDefault());
 
     public OCFileListAdapter(
         Activity activity,
@@ -370,6 +376,11 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
 
             OCFile file = getItem(position);
 
+            if (file == null) {
+                Log_OC.e(this, "Cannot bind on view holder on a null file");
+                return;
+            }
+
             boolean gridImage = MimeTypeUtil.isImage(file) || MimeTypeUtil.isVideo(file);
 
             gridViewHolder.getThumbnail().setTag(file.getFileId());
@@ -770,13 +781,18 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
         return output;
     }
 
-    public OCFile getItem(int position) {
+    public @Nullable
+    OCFile getItem(int position) {
         int newPosition = position;
 
         if (shouldShowHeader() && position > 0) {
             newPosition = position - 1;
         }
 
+        if (newPosition >= mFiles.size()) {
+            return null;
+        }
+
         return mFiles.get(newPosition);
     }
 
@@ -812,7 +828,12 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
             }
         }
 
-        if (MimeTypeUtil.isImageOrVideo(getItem(position))) {
+        OCFile item = getItem(position);
+        if (item == null) {
+            return VIEWTYPE_ITEM;
+        }
+
+        if (MimeTypeUtil.isImageOrVideo(item)) {
             return VIEWTYPE_IMAGE;
         } else {
             return VIEWTYPE_ITEM;
@@ -875,7 +896,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
             if (!limitToMimeType.isEmpty()) {
                 mFiles = filterByMimeType(mFiles, limitToMimeType);
             }
-            FileSortOrder sortOrder = preferences.getSortOrderByFolder(directory);
+            sortOrder = preferences.getSortOrderByFolder(directory);
             mFiles = sortOrder.sortCloudFiles(mFiles);
             mFilesAll.clear();
             mFilesAll.addAll(mFiles);
@@ -941,7 +962,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
             searchType == SearchType.RECENTLY_MODIFIED_SEARCH) {
             mFiles = FileStorageUtils.sortOcFolderDescDateModifiedWithoutFavoritesFirst(mFiles);
         } else if (searchType != SearchType.SHARED_FILTER) {
-            FileSortOrder sortOrder = preferences.getSortOrderByFolder(folder);
+            sortOrder = preferences.getSortOrderByFolder(folder);
             mFiles = sortOrder.sortCloudFiles(mFiles);
         }
 
@@ -1067,6 +1088,8 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
         preferences.setSortOrder(folder, sortOrder);
         mFiles = sortOrder.sortCloudFiles(mFiles);
         notifyDataSetChanged();
+
+        this.sortOrder = sortOrder;
     }
 
     public Set<OCFile> getCheckedItems() {
@@ -1149,7 +1172,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
                 if (!preferences.isShowHiddenFilesEnabled()) {
                     mFiles = filterHiddenFiles(mFiles);
                 }
-                FileSortOrder sortOrder = preferences.getSortOrderByFolder(currentDirectory);
+                sortOrder = preferences.getSortOrderByFolder(currentDirectory);
                 mFiles = sortOrder.sortCloudFiles(mFiles);
             }
 
@@ -1209,6 +1232,27 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
         showMetadata = bool;
     }
 
+    @NonNull
+    @Override
+    public String getSectionName(int position) {
+        OCFile file = getItem(position);
+
+        if (file == null) {
+            return "";
+        }
+
+        if (sortOrder.getType() == FileSortOrder.SortType.ALPHABET) {
+            return String.valueOf(file.getFileName().charAt(0)).toUpperCase(Locale.getDefault());
+        } else if (sortOrder.getType() == FileSortOrder.SortType.DATE) {
+            long milliseconds = file.getModificationTimestamp();
+            Date date = new Date(milliseconds);
+            return dateFormat.format(date);
+        } else {
+            // Size
+            return DisplayUtils.bytesToHumanReadable(file.getFileLength());
+        }
+    }
+
     @VisibleForTesting
     public void setShowShareAvatar(boolean bool) {
         showShareAvatar = bool;

+ 9 - 0
app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.java

@@ -213,6 +213,10 @@ public class GalleryFragment extends OCFileListFragment {
                     // Almost reached the end, continue to load new photos
                     OCFile lastFile = mAdapter.getItem(lastVisibleItem - 1);
 
+                    if (lastFile == null) {
+                        return;
+                    }
+
                     daySpan = 30;
                     endDate = lastFile.getModificationTimestamp() / 1000;
                     startDate = endDate - (daySpan * 24 * 60 * 60);
@@ -229,4 +233,9 @@ public class GalleryFragment extends OCFileListFragment {
             }
         }
     }
+
+    @Override
+    public boolean isGalleryFragment() {
+        return true;
+    }
 }

+ 6 - 1
app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -1638,7 +1638,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
 
     @Override
     public void onRefresh() {
-        if (isSearchEventSet(searchEvent) && searchFragment) {
+        if (searchFragment && isSearchEventSet(searchEvent)) {
             handleSearchEvent(searchEvent);
 
             mRefreshListLayout.setRefreshing(false);
@@ -1656,6 +1656,11 @@ public class OCFileListFragment extends ExtendedListFragment implements
         return searchFragment;
     }
 
+    @Override
+    public boolean isGalleryFragment() {
+        return false;
+    }
+
     /**
      * De-/select all elements in the current list view.
      *

+ 2 - 0
app/src/main/java/com/owncloud/android/ui/interfaces/OCFileListFragmentInterface.java

@@ -50,4 +50,6 @@ public interface OCFileListFragmentInterface {
     void onHeaderClicked();
 
     boolean isSearchFragment();
+
+    boolean isGalleryFragment();
 }

+ 19 - 0
app/src/main/java/com/owncloud/android/utils/FileSortOrder.kt

@@ -38,6 +38,10 @@ open class FileSortOrder(@JvmField var name: String, var isAscending: Boolean) {
         trashBinView, localFileListView
     }
 
+    enum class SortType {
+        SIZE, ALPHABET, DATE
+    }
+
     companion object {
         const val sort_a_to_z_id = "sort_a_to_z"
         const val sort_z_to_a_id = "sort_z_to_a"
@@ -106,4 +110,19 @@ open class FileSortOrder(@JvmField var name: String, var isAscending: Boolean) {
     open fun sortTrashbinFiles(files: MutableList<TrashbinFile>): List<TrashbinFile> {
         return files
     }
+
+    open fun getType(): SortType {
+        return when (name) {
+            sort_z_to_a_id,
+            sort_a_to_z_id -> SortType.ALPHABET
+
+            sort_small_to_big_id,
+            sort_big_to_small_id -> SortType.SIZE
+
+            sort_new_to_old_id,
+            sort_old_to_new_id -> SortType.DATE
+
+            else -> SortType.ALPHABET
+        }
+    }
 }

+ 6 - 1
app/src/main/res/layout/list_fragment.xml

@@ -33,7 +33,12 @@
         <com.owncloud.android.ui.EmptyRecyclerView
             android:id="@+id/list_root"
             android:layout_width="match_parent"
-            android:layout_height="match_parent" />
+            android:layout_height="match_parent"
+            app:fastScrollPopupBgColor="@color/color_accent"
+            app:fastScrollPopupTextColor="@color/login_text_color"
+            app:fastScrollThumbColor="@color/color_accent"
+            app:fastScrollAutoHide="true"
+            app:fastScrollAutoHideDelay="1500" />
     </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
 
     <include

+ 1 - 1
scripts/analysis/findbugs-results.txt

@@ -1 +1 @@
-628
+626