Эх сурвалжийг харах

Merge pull request #8137 from nextcloud/overlappingAvatars

Enhance overlapping multi-avatar display
Tobias Kaminsky 4 жил өмнө
parent
commit
9baa1556a8

+ 193 - 0
src/main/java/com/owncloud/android/ui/AvatarGroupLayout.java

@@ -0,0 +1,193 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Andy Scherzinger
+ * @author Stefan Niedermann
+ * Copyright (C) 2021 Andy Scherzinger
+ * Copyright (C) 2021 Stefan Niedermann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.ui;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.target.BitmapImageViewTarget;
+import com.nextcloud.client.account.User;
+import com.owncloud.android.R;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.lib.resources.shares.ShareeUser;
+import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.theme.ThemeAvatarUtils;
+import com.owncloud.android.utils.theme.ThemeDrawableUtils;
+
+import java.util.List;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Px;
+import androidx.core.content.ContextCompat;
+import androidx.core.content.res.ResourcesCompat;
+import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+
+public class AvatarGroupLayout extends RelativeLayout implements DisplayUtils.AvatarGenerationListener {
+    private static final String TAG = AvatarGroupLayout.class.getSimpleName();
+
+    private final static int MAX_AVATAR_COUNT = 3;
+
+    private final Drawable borderDrawable;
+    @Px private final int avatarSize;
+    @Px private final int avatarBorderSize;
+    @Px private final int overlapPx;
+
+    public AvatarGroupLayout(Context context) {
+        this(context, null);
+    }
+
+    public AvatarGroupLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public AvatarGroupLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public AvatarGroupLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        avatarBorderSize = DisplayUtils.convertDpToPixel(2, context);
+        avatarSize = DisplayUtils.convertDpToPixel(40, context);
+        overlapPx = DisplayUtils.convertDpToPixel(24, context);
+        borderDrawable = ContextCompat.getDrawable(context, R.drawable.round_bgnd);
+        assert borderDrawable != null;
+        DrawableCompat.setTint(borderDrawable, ContextCompat.getColor(context, R.color.bg_default));
+    }
+
+    public void setAvatars(@NonNull User user, @NonNull List<ShareeUser> sharees) {
+        @NonNull Context context = getContext();
+        removeAllViews();
+        RelativeLayout.LayoutParams avatarLayoutParams;
+        int avatarCount;
+        int shareeSize = Math.min(sharees.size(), MAX_AVATAR_COUNT);
+
+        Resources resources = context.getResources();
+        float avatarRadius = resources.getDimension(R.dimen.list_item_avatar_icon_radius);
+        ShareeUser sharee;
+
+        for (avatarCount = 0; avatarCount < shareeSize; avatarCount++) {
+            avatarLayoutParams = new RelativeLayout.LayoutParams(avatarSize, avatarSize);
+            avatarLayoutParams.setMargins(0, 0, avatarCount * overlapPx, 0);
+            avatarLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
+
+            final ImageView avatar = new ImageView(context);
+            avatar.setLayoutParams(avatarLayoutParams);
+            avatar.setPadding(avatarBorderSize, avatarBorderSize, avatarBorderSize, avatarBorderSize);
+
+            avatar.setBackground(borderDrawable);
+            addView(avatar);
+            avatar.requestLayout();
+
+            if (avatarCount == 0 && sharees.size() > MAX_AVATAR_COUNT) {
+                avatar.setImageResource(R.drawable.ic_people);
+                ThemeDrawableUtils.setIconColor(avatar.getDrawable());
+            } else {
+                sharee = sharees.get(avatarCount);
+                switch (sharee.getShareType()) {
+                    case GROUP:
+                    case EMAIL:
+                    case ROOM:
+                    case CIRCLE:
+                        ThemeAvatarUtils.createAvatar(sharee.getShareType(), avatar, context);
+                        break;
+                    case FEDERATED:
+                        showFederatedShareAvatar(context, sharee.getUserId(), avatarRadius, resources, avatar);
+                        break;
+                    default:
+                        avatar.setTag(sharee);
+                        DisplayUtils.setAvatar(user,
+                                               sharee.getUserId(),
+                                               sharee.getDisplayName(),
+                                               this,
+                                               avatarRadius,
+                                               resources,
+                                               avatar,
+                                               context);
+                        break;
+                }
+            }
+        }
+
+        // Recalculate container size based on avatar count
+        int size = overlapPx * (avatarCount - 1) + avatarSize;
+        ViewGroup.LayoutParams rememberParam = getLayoutParams();
+        rememberParam.width = size;
+        setLayoutParams(rememberParam);
+    }
+
+    private void showFederatedShareAvatar(Context context, String user, float avatarRadius, Resources resources,
+                                          ImageView avatar) {
+        // maybe federated share
+        String[] split = user.split("@");
+        String userId = split[0];
+        String server = split[1];
+
+        String url = "https://" + server + "/index.php/avatar/" + userId + "/" +
+            DisplayUtils.convertDpToPixel(avatarRadius, context);
+
+        Drawable placeholder;
+        try {
+            placeholder = TextDrawable.createAvatarByUserId(userId, avatarRadius);
+        } catch (Exception e) {
+            Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e);
+            placeholder = ThemeDrawableUtils.tintDrawable(ResourcesCompat.getDrawable(resources,
+                                                                                      R.drawable.account_circle_white,
+                                                                                      null),
+                                                          R.color.black);
+        }
+
+        avatar.setTag(null);
+        Glide.with(context).load(url)
+            .asBitmap()
+            .placeholder(placeholder)
+            .error(placeholder)
+            .into(new BitmapImageViewTarget(avatar) {
+                @Override
+                protected void setResource(Bitmap resource) {
+                    RoundedBitmapDrawable circularBitmapDrawable = RoundedBitmapDrawableFactory.create(resources,
+                                                                                                       resource);
+                    circularBitmapDrawable.setCircular(true);
+                    avatar.setImageDrawable(circularBitmapDrawable);
+                }
+            });
+    }
+
+    @Override
+    public void avatarGenerated(Drawable avatarDrawable, Object callContext) {
+        ((ImageView) callContext).setImageDrawable(avatarDrawable);
+    }
+
+    @Override
+    public boolean shouldCallGeneratedCallback(String tag, Object callContext) {
+        return ((ImageView) callContext).getTag().equals(tag);
+    }
+}

+ 5 - 104
src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java

@@ -44,14 +44,10 @@ import android.widget.Filter;
 import android.widget.FrameLayout;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.ImageView;
 import android.widget.ProgressBar;
 import android.widget.ProgressBar;
-import android.widget.RelativeLayout;
 import android.widget.TextView;
 import android.widget.TextView;
 
 
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.request.target.BitmapImageViewTarget;
 import com.elyeproj.loaderviewlibrary.LoaderImageView;
 import com.elyeproj.loaderviewlibrary.LoaderImageView;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.User;
-import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.preferences.AppPreferences;
 import com.nextcloud.client.preferences.AppPreferences;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
 import com.owncloud.android.R;
@@ -73,7 +69,7 @@ import com.owncloud.android.lib.resources.shares.ShareeUser;
 import com.owncloud.android.operations.RefreshFolderOperation;
 import com.owncloud.android.operations.RefreshFolderOperation;
 import com.owncloud.android.operations.RemoteOperationFailedException;
 import com.owncloud.android.operations.RemoteOperationFailedException;
 import com.owncloud.android.services.OperationsService;
 import com.owncloud.android.services.OperationsService;
-import com.owncloud.android.ui.TextDrawable;
+import com.owncloud.android.ui.AvatarGroupLayout;
 import com.owncloud.android.ui.activity.ComponentsGetter;
 import com.owncloud.android.ui.activity.ComponentsGetter;
 import com.owncloud.android.ui.fragment.ExtendedListFragment;
 import com.owncloud.android.ui.fragment.ExtendedListFragment;
 import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface;
 import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface;
@@ -83,10 +79,8 @@ import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.FileSortOrder;
 import com.owncloud.android.utils.FileSortOrder;
 import com.owncloud.android.utils.FileStorageUtils;
 import com.owncloud.android.utils.FileStorageUtils;
 import com.owncloud.android.utils.MimeTypeUtil;
 import com.owncloud.android.utils.MimeTypeUtil;
-import com.owncloud.android.utils.theme.ThemeAvatarUtils;
 import com.owncloud.android.utils.theme.ThemeColorUtils;
 import com.owncloud.android.utils.theme.ThemeColorUtils;
 import com.owncloud.android.utils.theme.ThemeDrawableUtils;
 import com.owncloud.android.utils.theme.ThemeDrawableUtils;
-import com.owncloud.android.utils.theme.ThemeUtils;
 
 
 import java.io.File;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.ArrayList;
@@ -101,8 +95,6 @@ import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.annotation.VisibleForTesting;
 import androidx.core.content.res.ResourcesCompat;
 import androidx.core.content.res.ResourcesCompat;
-import androidx.core.graphics.drawable.RoundedBitmapDrawable;
-import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.recyclerview.widget.RecyclerView;
 import butterknife.BindView;
 import butterknife.BindView;
 import butterknife.ButterKnife;
 import butterknife.ButterKnife;
@@ -118,7 +110,6 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
     private final String userId;
     private final String userId;
     private Activity activity;
     private Activity activity;
     private AppPreferences preferences;
     private AppPreferences preferences;
-    private UserAccountManager accountManager;
     private List<OCFile> mFiles = new ArrayList<>();
     private List<OCFile> mFiles = new ArrayList<>();
     private List<OCFile> mFilesAll = new ArrayList<>();
     private List<OCFile> mFilesAll = new ArrayList<>();
     private boolean hideItemOptions;
     private boolean hideItemOptions;
@@ -149,7 +140,6 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
         Activity activity,
         Activity activity,
         User user,
         User user,
         AppPreferences preferences,
         AppPreferences preferences,
-        UserAccountManager accountManager,
         ComponentsGetter transferServiceGetter,
         ComponentsGetter transferServiceGetter,
         OCFileListFragmentInterface ocFileListFragmentInterface,
         OCFileListFragmentInterface ocFileListFragmentInterface,
         boolean argHideItemOptions,
         boolean argHideItemOptions,
@@ -158,7 +148,6 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
         this.ocFileListFragmentInterface = ocFileListFragmentInterface;
         this.ocFileListFragmentInterface = ocFileListFragmentInterface;
         this.activity = activity;
         this.activity = activity;
         this.preferences = preferences;
         this.preferences = preferences;
-        this.accountManager = accountManager;
         this.user = user;
         this.user = user;
         hideItemOptions = argHideItemOptions;
         hideItemOptions = argHideItemOptions;
         this.gridView = gridView;
         this.gridView = gridView;
@@ -401,9 +390,6 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
             if (holder instanceof OCFileListItemViewHolder) {
             if (holder instanceof OCFileListItemViewHolder) {
                 OCFileListItemViewHolder itemViewHolder = (OCFileListItemViewHolder) holder;
                 OCFileListItemViewHolder itemViewHolder = (OCFileListItemViewHolder) holder;
 
 
-                Resources resources = activity.getResources();
-                float avatarRadius = resources.getDimension(R.dimen.list_item_avatar_icon_radius);
-
                 if ((file.isSharedWithMe() || file.isSharedWithSharee()) && !multiSelect && !gridView &&
                 if ((file.isSharedWithMe() || file.isSharedWithSharee()) && !multiSelect && !gridView &&
                     !hideItemOptions) {
                     !hideItemOptions) {
                     itemViewHolder.sharedAvatars.setVisibility(View.VISIBLE);
                     itemViewHolder.sharedAvatars.setVisibility(View.VISIBLE);
@@ -424,58 +410,9 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
 
 
                     Log_OC.d(this, "sharees of " + file.getFileName() + ": " + sharees);
                     Log_OC.d(this, "sharees of " + file.getFileName() + ": " + sharees);
 
 
-                    int shareeSize = Math.min(sharees.size(), 3);
-                    int w = DisplayUtils.convertDpToPixel(40, activity);
-                    int margin = DisplayUtils.convertDpToPixel(24, activity);
-                    int size = 60 * (shareeSize - 1) + w;
-
-                    for (int i = 0; i < shareeSize; i++) {
-                        ShareeUser sharee = sharees.get(i);
-
-                        ImageView avatar = new ImageView(activity);
-
-                        if (i == 0 && sharees.size() > 3) {
-                            avatar.setImageResource(R.drawable.ic_people);
-                            ThemeDrawableUtils.setIconColor(avatar.getDrawable());
-                        } else {
-                            switch (sharee.getShareType()) {
-                                case GROUP:
-                                case EMAIL:
-                                case ROOM:
-                                case CIRCLE:
-                                    ThemeAvatarUtils.createAvatar(sharee.getShareType(), avatar, activity);
-                                    break;
-
-                                case FEDERATED:
-                                    showFederatedShareAvatar(sharee.getUserId(), avatarRadius, resources, avatar);
-                                    break;
-                                default:
-                                    avatar.setTag(sharee);
-                                    DisplayUtils.setAvatar(user,
-                                                           sharee.getUserId(),
-                                                           sharee.getDisplayName(),
-                                                           this,
-                                                           avatarRadius,
-                                                           resources,
-                                                           avatar,
-                                                           activity);
-                                    break;
-                            }
-                        }
-
-                        avatar.setOnClickListener(view -> ocFileListFragmentInterface.onShareIconClick(file));
-
-                        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(w, w);
-                        layoutParams.setMargins(0, 0, i * margin, 0);
-                        layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
-                        avatar.setLayoutParams(layoutParams);
-                        itemViewHolder.sharedAvatars.addView(avatar);
-
-                        ViewGroup.LayoutParams rememberParam = itemViewHolder.sharedAvatars.getLayoutParams();
-                        rememberParam.width = size;
-
-                        itemViewHolder.sharedAvatars.setLayoutParams(rememberParam);
-                    }
+                    itemViewHolder.sharedAvatars.setAvatars(user, sharees);
+                    itemViewHolder.sharedAvatars.setOnClickListener(
+                        view -> ocFileListFragmentInterface.onShareIconClick(file));
                 } else {
                 } else {
                     itemViewHolder.sharedAvatars.setVisibility(View.GONE);
                     itemViewHolder.sharedAvatars.setVisibility(View.GONE);
                     itemViewHolder.sharedAvatars.removeAllViews();
                     itemViewHolder.sharedAvatars.removeAllViews();
@@ -570,42 +507,6 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
         }
         }
     }
     }
 
 
-    private void showFederatedShareAvatar(String user, float avatarRadius, Resources resources, ImageView avatar) {
-        // maybe federated share
-        String[] split = user.split("@");
-        String userId = split[0];
-        String server = split[1];
-
-        String url = "https://" + server + "/index.php/avatar/" + userId + "/" +
-            DisplayUtils.convertDpToPixel(avatarRadius, activity);
-
-        Drawable placeholder;
-        try {
-            placeholder = TextDrawable.createAvatarByUserId(userId, avatarRadius);
-        } catch (Exception e) {
-            Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e);
-            placeholder = ThemeDrawableUtils.tintDrawable(ResourcesCompat.getDrawable(resources,
-                                                                                      R.drawable.account_circle_white,
-                                                                                      null),
-                                                          R.color.black);
-        }
-
-        avatar.setTag(null);
-        Glide.with(activity).load(url)
-            .asBitmap()
-            .placeholder(placeholder)
-            .error(placeholder)
-            .into(new BitmapImageViewTarget(avatar) {
-                @Override
-                protected void setResource(Bitmap resource) {
-                    RoundedBitmapDrawable circularBitmapDrawable =
-                        RoundedBitmapDrawableFactory.create(activity.getResources(), resource);
-                    circularBitmapDrawable.setCircular(true);
-                    avatar.setImageDrawable(circularBitmapDrawable);
-                }
-            });
-        }
-
     public static void setThumbnail(OCFile file,
     public static void setThumbnail(OCFile file,
                                     ImageView thumbnailView,
                                     ImageView thumbnailView,
                                     User user,
                                     User user,
@@ -1287,7 +1188,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
         public ImageView overflowMenu;
         public ImageView overflowMenu;
 
 
         @BindView(R.id.sharedAvatars)
         @BindView(R.id.sharedAvatars)
-        public RelativeLayout sharedAvatars;
+        public AvatarGroupLayout sharedAvatars;
 
 
         private OCFileListItemViewHolder(View itemView) {
         private OCFileListItemViewHolder(View itemView) {
             super(itemView);
             super(itemView);

+ 0 - 2
src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -322,7 +322,6 @@ public class OCFileListFragment extends ExtendedListFragment implements
         }
         }
     }
     }
 
 
-
     /**
     /**
      * {@inheritDoc}
      * {@inheritDoc}
      */
      */
@@ -345,7 +344,6 @@ public class OCFileListFragment extends ExtendedListFragment implements
             getActivity(),
             getActivity(),
             accountManager.getUser(),
             accountManager.getUser(),
             preferences,
             preferences,
-            accountManager,
             mContainerActivity,
             mContainerActivity,
             this,
             this,
             hideItemOptions,
             hideItemOptions,

+ 1 - 1
src/main/res/layout/list_item.xml

@@ -171,7 +171,7 @@
             android:paddingEnd="@dimen/list_item_share_right_margin"
             android:paddingEnd="@dimen/list_item_share_right_margin"
             android:src="@drawable/ic_unshared" />
             android:src="@drawable/ic_unshared" />
 
 
-        <RelativeLayout
+        <com.owncloud.android.ui.AvatarGroupLayout
             android:id="@+id/sharedAvatars"
             android:id="@+id/sharedAvatars"
             android:layout_width="100dp"
             android:layout_width="100dp"
             android:layout_height="@dimen/file_icon_size"
             android:layout_height="@dimen/file_icon_size"