فهرست منبع

Merge pull request #5991 from nextcloud/newDesign-loader-thumbnail

New design (UI) - loader thumbnail
Tobias Kaminsky 4 سال پیش
والد
کامیت
5ee1650064

+ 3 - 0
build.gradle

@@ -341,6 +341,9 @@ dependencies {
     ktlint "com.pinterest:ktlint:0.36.0"
     implementation 'org.conscrypt:conscrypt-android:2.4.0'
 
+    // Shimmer animation
+    implementation 'com.elyeproj.libraries:loaderviewlibrary:2.0.0'
+
     // dependencies for markdown rendering
     implementation "io.noties.markwon:core:$markwonVersion"
     implementation "io.noties.markwon:ext-strikethrough:$markwonVersion"

BIN
screenshots/com.owncloud.android.ui.preview.PreviewTextFileFragmentTest_displaySimpleTextFile.png


+ 22 - 2
src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java

@@ -428,6 +428,7 @@ public final class ThumbnailsCacheManager {
         private FileDataStorageManager mStorageManager;
         private GetMethod getMethod;
         private boolean roundedCorners = false;
+        private Listener mListener;
 
         public ThumbnailGenerationTask(ImageView imageView, FileDataStorageManager storageManager, Account account)
                 throws IllegalArgumentException {
@@ -479,7 +480,7 @@ public final class ThumbnailsCacheManager {
         @Override
         protected Bitmap doInBackground(ThumbnailGenerationTaskObject... params) {
             Bitmap thumbnail = null;
-
+            boolean isError = false;
             try {
                 if (mAccount != null) {
                     OwnCloudAccount ocAccount = new OwnCloudAccount(
@@ -514,9 +515,15 @@ public final class ThumbnailsCacheManager {
 
             } catch(OutOfMemoryError oome) {
                 Log_OC.e(TAG, "Out of memory");
+                isError = true;
             } catch (Throwable t) {
                 // the app should never break due to a problem with thumbnails
                 Log_OC.e(TAG, "Generation of thumbnail for " + mFile + " failed", t);
+                isError = true;
+            } finally {
+                if (isError && mListener != null){
+                    mListener.onError();
+                }
             }
 
             return thumbnail;
@@ -545,11 +552,19 @@ public final class ThumbnailsCacheManager {
                 }
             }
 
+            if (mListener !=null){
+                mListener.onSuccess();
+            }
+
             if (mAsyncTasks != null) {
                 mAsyncTasks.remove(this);
             }
         }
 
+        public void setListener(Listener listener){
+            mListener = listener;
+        }
+
         private Bitmap doThumbnailFromOCFileInBackground() {
             Bitmap thumbnail;
             ServerFileInterface file = (ServerFileInterface) mFile;
@@ -658,7 +673,7 @@ public final class ThumbnailsCacheManager {
          *
          * @return int
          */
-        private int getThumbnailDimension() {
+        public int getThumbnailDimension() {
             // Converts dp to pixel
             Resources r = MainApp.getAppContext().getResources();
             Double d = Math.pow(2, Math.floor(Math.log(r.getDimension(R.dimen.file_icon_size_grid)) / Math.log(2)));
@@ -696,6 +711,11 @@ public final class ThumbnailsCacheManager {
             return thumbnail;
         }
 
+        public interface Listener{
+            void onSuccess();
+            void onError();
+        }
+
     }
 
     public static class MediaThumbnailGenerationTask extends AsyncTask<Object, Void, Bitmap> {

+ 72 - 2
src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java

@@ -41,6 +41,7 @@ import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Filter;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ProgressBar;
@@ -49,6 +50,7 @@ import android.widget.TextView;
 
 import com.bumptech.glide.Glide;
 import com.bumptech.glide.request.target.BitmapImageViewTarget;
+import com.elyeproj.loaderviewlibrary.LoaderImageView;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.preferences.AppPreferences;
@@ -93,6 +95,7 @@ import java.util.Set;
 import java.util.Vector;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.core.graphics.drawable.RoundedBitmapDrawable;
 import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
 import androidx.recyclerview.widget.RecyclerView;
@@ -364,7 +367,8 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
                          mStorageManager,
                          asyncTasks,
                          gridView,
-                         activity);
+                         activity,
+                         gridViewHolder.shimmerThumbnail);
 
             if (highlightedItem != null && file.getFileId() == highlightedItem.getFileId()) {
                 gridViewHolder.itemLayout.setBackgroundColor(activity.getResources()
@@ -610,6 +614,17 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
                                     List<ThumbnailsCacheManager.ThumbnailGenerationTask> asyncTasks,
                                     boolean gridView,
                                     Context context) {
+        setThumbnail(file, thumbnailView, user, storageManager, asyncTasks, gridView, context, null);
+    }
+
+    private static void setThumbnail(OCFile file,
+                                    ImageView thumbnailView,
+                                    User user,
+                                    FileDataStorageManager storageManager,
+                                    List<ThumbnailsCacheManager.ThumbnailGenerationTask> asyncTasks,
+                                    boolean gridView,
+                                    Context context,
+                                    LoaderImageView shimmerThumbnail) {
         if (file.isFolder()) {
             thumbnailView.setImageDrawable(MimeTypeUtil
                                                .getFolderTypeIcon(file.isSharedWithMe() || file.isSharedWithSharee(),
@@ -643,7 +658,6 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
                                                                                    user.toPlatformAccount(),
                                                                                    asyncTasks,
                                                                                    !gridView);
-
                             if (thumbnail == null) {
                                 thumbnail = BitmapUtils.drawableToBitmap(
                                     MimeTypeUtil.getFileTypeIcon(file.getMimeType(),
@@ -654,6 +668,26 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
                             final ThumbnailsCacheManager.AsyncThumbnailDrawable asyncDrawable =
                                 new ThumbnailsCacheManager.AsyncThumbnailDrawable(context.getResources(),
                                                                                   thumbnail, task);
+
+                            if (shimmerThumbnail != null && shimmerThumbnail.getVisibility() == View.GONE) {
+                                if (gridView) {
+                                    configShimmerGridImageSize(shimmerThumbnail, task.getThumbnailDimension());
+                                }
+                                startShimmer(shimmerThumbnail, thumbnailView);
+                            }
+
+                            task.setListener(new ThumbnailsCacheManager.ThumbnailGenerationTask.Listener() {
+                                @Override
+                                public void onSuccess() {
+                                    stopShimmer(shimmerThumbnail, thumbnailView);
+                                }
+
+                                @Override
+                                public void onError() {
+                                    stopShimmer(shimmerThumbnail, thumbnailView);
+                                }
+                            });
+
                             thumbnailView.setImageDrawable(asyncDrawable);
                             asyncTasks.add(task);
                             task.execute(new ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file,
@@ -676,6 +710,39 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
         }
     }
 
+    @Override
+    public void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) {
+        if (holder instanceof OCFileListGridImageViewHolder) {
+            LoaderImageView thumbnailShimmer = ((OCFileListGridImageViewHolder) holder).shimmerThumbnail;
+            if (thumbnailShimmer.getVisibility() == View.VISIBLE){
+                thumbnailShimmer.setImageResource(R.drawable.background);
+                thumbnailShimmer.resetLoader();
+            }
+        }
+    }
+
+    private static void startShimmer(LoaderImageView thumbnailShimmer, ImageView thumbnailView) {
+        thumbnailShimmer.setImageResource(R.drawable.background);
+        thumbnailShimmer.resetLoader();
+        thumbnailView.setVisibility(View.GONE);
+        thumbnailShimmer.setVisibility(View.VISIBLE);
+    }
+
+    private static void stopShimmer(@Nullable LoaderImageView thumbnailShimmer, ImageView thumbnailView) {
+        if (thumbnailShimmer != null){
+            thumbnailShimmer.setVisibility(View.GONE);
+            thumbnailView.setVisibility(View.VISIBLE);
+        }
+    }
+
+    private static void configShimmerGridImageSize(LoaderImageView thumbnailShimmer, int size){
+        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,size);
+        FrameLayout.LayoutParams targetLayoutParams = (FrameLayout.LayoutParams) thumbnailShimmer.getLayoutParams();
+        params.setMargins(targetLayoutParams.leftMargin, targetLayoutParams.topMargin,
+                          targetLayoutParams.rightMargin, targetLayoutParams.bottomMargin);
+        thumbnailShimmer.setLayoutParams(params);
+    }
+
     private String getFooterText() {
         int filesCount = 0;
         int foldersCount = 0;
@@ -1197,6 +1264,9 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
         @BindView(R.id.thumbnail)
         public ImageView thumbnail;
 
+        @BindView(R.id.thumbnail_shimmer)
+        public LoaderImageView shimmerThumbnail;
+
         @BindView(R.id.favorite_action)
         public ImageView favorite;
 

+ 24 - 6
src/main/res/layout/grid_image.xml

@@ -16,6 +16,7 @@
 
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/ListItemLayout"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
@@ -29,15 +30,32 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content">
 
-        <com.owncloud.android.ui.SquareImageView
-            android:id="@+id/thumbnail"
+        <FrameLayout
+            android:id="@+id/shimmer_view_container"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:padding="@dimen/standard_eigth_padding"
-            android:scaleType="centerCrop"
-            android:src="@drawable/folder"
-            android:contentDescription="@null"/>
+            android:clipChildren="true">
 
+            <com.elyeproj.loaderviewlibrary.LoaderImageView
+                android:id="@+id/thumbnail_shimmer"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_margin="@dimen/standard_eigth_padding"
+                android:contentDescription="@null"
+                android:scaleType="centerCrop"
+                android:visibility="gone"
+                app:width_weight="0.4"
+                app:height_weight="0.6" />
+
+            <com.owncloud.android.ui.SquareImageView
+                android:id="@+id/thumbnail"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:contentDescription="@null"
+                android:padding="@dimen/standard_eigth_padding"
+                android:scaleType="centerCrop"
+                android:src="@drawable/folder" />
+        </FrameLayout>
 
         <ImageView
             android:id="@+id/favorite_action"

+ 20 - 7
src/main/res/layout/grid_item.xml

@@ -29,13 +29,26 @@
         android:layout_height="wrap_content"
         android:layout_gravity="center_horizontal">
 
-        <ImageView
-            android:id="@+id/thumbnail"
-            android:layout_width="@dimen/standard_list_item_size"
-            android:layout_height="@dimen/standard_list_item_size"
-            android:layout_gravity="center"
-            android:src="@drawable/folder"
-            android:contentDescription="@null"/>
+        <FrameLayout
+            android:id="@+id/shimmer_view_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center">
+
+            <com.elyeproj.loaderviewlibrary.LoaderImageView
+                android:id="@+id/thumbnail_shimmer"
+                android:layout_width="@dimen/standard_list_item_size"
+                android:layout_height="@dimen/standard_list_item_size"
+                android:visibility="gone" />
+
+            <ImageView
+                android:id="@+id/thumbnail"
+                android:layout_width="@dimen/standard_list_item_size"
+                android:layout_height="@dimen/standard_list_item_size"
+                android:layout_gravity="center"
+                android:contentDescription="@null"
+                android:src="@drawable/folder" />
+        </FrameLayout>
 
         <ImageView
             android:id="@+id/favorite_action"

+ 9 - 10
src/main/res/layout/grid_sync_item.xml

@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
+<?xml version="1.0" encoding="UTF-8"?><!--
   Nextcloud Android client application
 
   Copyright (C) 2017 Andy Scherzinger
@@ -18,8 +17,7 @@
   You should have received a copy of the GNU Affero General Public
   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 -->
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/grid_item_container"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
@@ -31,16 +29,17 @@
         android:id="@+id/thumbnail"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
+        android:contentDescription="@null"
         android:scaleType="centerCrop"
-        android:src="@drawable/folder"/>
+        android:src="@drawable/folder" />
 
     <ImageView
         android:id="@+id/thumbnailDarkener"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:background="#99000000"
-        android:scaleType="centerCrop"
-        android:contentDescription="@null"/>
+        android:contentDescription="@null"
+        android:scaleType="centerCrop" />
 
     <LinearLayout
         android:id="@+id/counterLayout"
@@ -57,7 +56,7 @@
             android:text="@string/synced_folders_plus"
             android:textColor="#ffffff"
             android:textSize="@dimen/grid_sync_item_layout_next_text_size"
-            android:textStyle="bold"/>
+            android:textStyle="bold" />
 
         <TextView
             android:id="@+id/counter"
@@ -67,6 +66,6 @@
             android:text="@string/placeholder_fileSize"
             android:textColor="#ffffff"
             android:textSize="@dimen/grid_sync_item_layout_counter_text_size"
-            android:textStyle="bold"/>
+            android:textStyle="bold" />
     </LinearLayout>
-</FrameLayout>
+</FrameLayout>

+ 54 - 39
src/main/res/layout/list_item.xml

@@ -17,48 +17,64 @@
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:id="@+id/ListItemLayout"
-              android:layout_width="match_parent"
-              android:layout_height="@dimen/standard_list_item_size"
-              android:background="@drawable/list_selector"
-              android:descendantFocusability="blocksDescendants"
-              android:foreground="?android:attr/selectableItemBackground"
-              android:baselineAligned="false"
-              android:orientation="horizontal">
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/ListItemLayout"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/standard_list_item_size"
+    android:background="@drawable/list_selector"
+    android:descendantFocusability="blocksDescendants"
+    android:foreground="?android:attr/selectableItemBackground"
+    android:baselineAligned="false"
+    android:orientation="horizontal">
 
     <RelativeLayout
         android:layout_width="@dimen/standard_list_item_size"
         android:layout_height="@dimen/standard_list_item_size"
-        android:paddingBottom="@dimen/standard_padding"
-        android:paddingEnd="@dimen/standard_quarter_padding"
         android:paddingStart="@dimen/zero"
-        android:paddingTop="@dimen/standard_padding">
+        android:paddingTop="@dimen/standard_padding"
+        android:paddingEnd="@dimen/standard_quarter_padding"
+        android:paddingBottom="@dimen/standard_padding">
 
-        <ImageView
-            android:id="@+id/thumbnail"
-            android:layout_width="@dimen/file_icon_size"
-            android:layout_height="@dimen/file_icon_size"
+        <FrameLayout
+            android:id="@+id/shimmer_view_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
             android:layout_centerInParent="true"
-            android:layout_marginStart="@dimen/standard_half_margin"
-            android:contentDescription="@null"
-            android:src="@drawable/folder"/>
+            android:layout_marginStart="@dimen/standard_half_margin">
+
+            <com.elyeproj.loaderviewlibrary.LoaderImageView
+                android:id="@+id/thumbnail_shimmer"
+                android:layout_width="@dimen/file_icon_size"
+                android:layout_height="@dimen/file_icon_size"
+                android:layout_marginStart="@dimen/standard_half_margin"
+                android:visibility="gone"
+                app:corners="8" />
+
+            <ImageView
+                android:id="@+id/thumbnail"
+                android:layout_width="@dimen/file_icon_size"
+                android:layout_height="@dimen/file_icon_size"
+                android:layout_marginStart="@dimen/standard_half_margin"
+                android:contentDescription="@null"
+                android:src="@drawable/folder"/>
+        </FrameLayout>
 
         <ImageView
             android:id="@+id/favorite_action"
             android:layout_width="@dimen/list_item_favorite_action_layout_width"
             android:layout_height="@dimen/list_item_favorite_action_layout_height"
-            android:layout_alignParentEnd="true"
             android:layout_alignParentTop="true"
+            android:layout_alignParentEnd="true"
             android:layout_marginEnd="@dimen/standard_quarter_margin"
             android:contentDescription="@string/favorite"
-            android:src="@drawable/favorite"/>
+            android:src="@drawable/favorite" />
 
         <ImageView
             android:id="@+id/localFileIndicator"
             android:layout_width="@dimen/list_item_local_file_indicator_layout_width"
             android:layout_height="@dimen/list_item_local_file_indicator_layout_height"
-            android:layout_alignParentBottom="true"
             android:layout_alignParentEnd="true"
+            android:layout_alignParentBottom="true"
             android:layout_marginEnd="@dimen/standard_quarter_margin"
             android:contentDescription="@string/downloader_download_succeeded_ticker"
             android:scaleType="fitCenter"
@@ -83,7 +99,7 @@
             android:singleLine="true"
             android:text="@string/placeholder_filename"
             android:textColor="@color/text_color"
-            android:textSize="@dimen/two_line_primary_text_size"/>
+            android:textSize="@dimen/two_line_primary_text_size" />
 
         <LinearLayout
             android:layout_width="match_parent"
@@ -96,18 +112,18 @@
                 android:layout_height="wrap_content"
                 android:text="@string/placeholder_fileSize"
                 android:textColor="@color/list_item_lastmod_and_filesize_text"
-                android:textSize="@dimen/two_line_secondary_text_size"/>
+                android:textSize="@dimen/two_line_secondary_text_size" />
 
             <TextView
                 android:id="@+id/file_separator"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:gravity="end"
-                android:paddingEnd="@dimen/standard_quarter_padding"
                 android:paddingStart="@dimen/zero"
+                android:paddingEnd="@dimen/standard_quarter_padding"
                 android:text="@string/info_separator"
                 android:textColor="@color/list_item_lastmod_and_filesize_text"
-                android:textSize="@dimen/two_line_secondary_text_size"/>
+                android:textSize="@dimen/two_line_secondary_text_size" />
 
             <TextView
                 android:id="@+id/last_mod"
@@ -116,7 +132,7 @@
                 android:gravity="end"
                 android:text="@string/placeholder_media_time"
                 android:textColor="@color/list_item_lastmod_and_filesize_text"
-                android:textSize="@dimen/two_line_secondary_text_size"/>
+                android:textSize="@dimen/two_line_secondary_text_size" />
 
         </LinearLayout>
 
@@ -126,8 +142,8 @@
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:layout_gravity="center_vertical"
-        android:paddingEnd="@dimen/zero"
-        android:paddingStart="@dimen/standard_half_padding">
+        android:paddingStart="@dimen/standard_half_padding"
+        android:paddingEnd="@dimen/zero">
 
         <ImageView
             android:id="@+id/unreadComments"
@@ -137,10 +153,10 @@
             android:clickable="true"
             android:contentDescription="@string/unread_comments"
             android:focusable="true"
-            android:paddingEnd="@dimen/list_item_share_right_margin"
             android:paddingStart="@dimen/standard_half_padding"
+            android:paddingEnd="@dimen/list_item_share_right_margin"
             android:src="@drawable/ic_comment"
-            android:visibility="gone"/>
+            android:visibility="gone" />
 
         <ImageView
             android:id="@+id/sharedIcon"
@@ -151,19 +167,19 @@
             android:clickable="true"
             android:contentDescription="@string/shared_icon_share"
             android:focusable="true"
-            android:paddingEnd="@dimen/list_item_share_right_margin"
             android:paddingStart="@dimen/standard_half_padding"
-            android:src="@drawable/ic_unshared"/>
+            android:paddingEnd="@dimen/list_item_share_right_margin"
+            android:src="@drawable/ic_unshared" />
 
         <RelativeLayout
             android:id="@+id/sharedAvatars"
             android:layout_width="100dp"
             android:layout_height="@dimen/file_icon_size"
-            android:layout_centerVertical="true"
             android:layout_alignEnd="@id/sharedIcon"
+            android:layout_centerVertical="true"
             android:layout_toEndOf="@id/sharedIcon"
             android:contentDescription="@string/shared_avatar_desc"
-            android:visibility="visible"/>
+            android:visibility="visible" />
 
         <ImageView
             android:id="@+id/custom_checkbox"
@@ -174,9 +190,9 @@
             android:clickable="false"
             android:contentDescription="@string/checkbox"
             android:focusable="false"
-            android:paddingEnd="@dimen/alternate_padding"
             android:paddingStart="@dimen/standard_half_padding"
-            android:src="@drawable/ic_checkbox_blank_outline"/>
+            android:paddingEnd="@dimen/alternate_padding"
+            android:src="@drawable/ic_checkbox_blank_outline" />
 
         <ImageView
             android:id="@+id/overflow_menu"
@@ -187,10 +203,9 @@
             android:clickable="true"
             android:contentDescription="@string/overflow_menu"
             android:focusable="true"
-            android:paddingEnd="@dimen/alternate_padding"
             android:paddingStart="@dimen/standard_half_padding"
-            android:src="@drawable/ic_dots_vertical"/>
+            android:paddingEnd="@dimen/alternate_padding"
+            android:src="@drawable/ic_dots_vertical" />
 
     </RelativeLayout>
-
 </LinearLayout>