瀏覽代碼

Add status to sharee

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
tobiasKaminsky 4 年之前
父節點
當前提交
dbd9e6fe9b
共有 24 個文件被更改,包括 794 次插入46 次删除
  1. 7 0
      drawable_resources/user-status-away.svg
  2. 6 0
      drawable_resources/user-status-dnd.svg
  3. 二進制
      screenshots/gplay/debug/com.owncloud.android.ui.fragment.AvatarIT_showAvatarsWithStatus.png
  4. 1 1
      scripts/androidScreenshotTest
  5. 二進制
      src/androidTest/assets/christine.jpg
  6. 二進制
      src/androidTest/assets/paulette.jpg
  7. 46 0
      src/androidTest/java/com/owncloud/android/providers/UsersAndGroupsSearchProviderIT.kt
  8. 125 0
      src/androidTest/java/com/owncloud/android/ui/fragment/AvatarIT.kt
  9. 24 3
      src/androidTest/java/com/owncloud/android/ui/fragment/AvatarTestFragment.kt
  10. 0 5
      src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt
  11. 16 2
      src/debug/res/layout/avatar_fragment.xml
  12. 24 0
      src/main/java/com/nextcloud/client/account/Status.kt
  13. 27 0
      src/main/java/com/nextcloud/client/account/StatusType.kt
  14. 105 9
      src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java
  15. 127 0
      src/main/java/com/owncloud/android/ui/StatusDrawable.java
  16. 22 18
      src/main/java/com/owncloud/android/ui/TextDrawable.java
  17. 1 3
      src/main/java/com/owncloud/android/ui/adapter/ShareViewHolder.java
  18. 55 0
      src/main/java/com/owncloud/android/ui/components/AvatarWithStatus.kt
  19. 7 0
      src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java
  20. 118 4
      src/main/java/com/owncloud/android/utils/BitmapUtils.java
  21. 11 0
      src/main/res/drawable/ic_talk.xml
  22. 32 0
      src/main/res/drawable/ic_user_status_away.xml
  23. 38 0
      src/main/res/drawable/ic_user_status_dnd.xml
  24. 2 1
      src/main/res/values/strings.xml

+ 7 - 0
drawable_resources/user-status-away.svg

@@ -0,0 +1,7 @@
+<svg width="24" height="24" enable-background="new 0 0 24 24" version="1.1" viewBox="0 0 24 24"
+    xmlns="http://www.w3.org/2000/svg">
+    <rect width="24" height="24" fill="none" />
+    <path
+        d="m10.615 2.1094c-4.8491 0.68106-8.6152 4.8615-8.6152 9.8906 0 5.5 4.5 10 10 10 5.0292 0 9.2096-3.7661 9.8906-8.6152-1.4654 1.601-3.5625 2.6152-5.8906 2.6152-4.4 0-8-3.6-8-8 0-2.3281 1.0143-4.4252 2.6152-5.8906z"
+        fill="#f4a331" />
+</svg>

+ 6 - 0
drawable_resources/user-status-dnd.svg

@@ -0,0 +1,6 @@
+<svg width="24" height="24" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+    <path d="M0 0h24v24H0z" fill="none" />
+    <path d="m12 2c-5.52 0-10 4.48-10 10s4.48 10 10 10 10-4.48 10-10-4.48-10-10-10z" fill="#ed484c" />
+    <path d="m8 10h8c1.108 0 2 0.892 2 2s-0.892 2-2 2h-8c-1.108 0-2-0.892-2-2s0.892-2 2-2z" fill="#fdffff"
+        stroke-linecap="round" stroke-linejoin="round" stroke-width="2" style="paint-order:stroke markers fill" />
+</svg>

二進制
screenshots/gplay/debug/com.owncloud.android.ui.fragment.AvatarIT_showAvatarsWithStatus.png


+ 1 - 1
scripts/androidScreenshotTest

@@ -58,7 +58,7 @@ fi
 if [[ $4 = "all" ]]; then
     scripts/runAllScreenshotCombinations "noCI" "$1" "-Pandroid.testInstrumentationRunnerArguments.class=$class$method"
 else
-    ./gradlew gplayDebugExecuteScreenshotTests $record \
+    ./gradlew --offline gplayDebugExecuteScreenshotTests $record \
     -Pandroid.testInstrumentationRunnerArguments.annotation=com.owncloud.android.utils.ScreenshotTest \
     -Pandroid.testInstrumentationRunnerArguments.class=$class$method \
     $darkMode \

二進制
src/androidTest/assets/christine.jpg


二進制
src/androidTest/assets/paulette.jpg


+ 46 - 0
src/androidTest/java/com/owncloud/android/providers/UsersAndGroupsSearchProviderIT.kt

@@ -0,0 +1,46 @@
+/*
+ *
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2020 Tobias Kaminsky
+ * Copyright (C) 2020 Nextcloud GmbH
+ *
+ * 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.providers
+
+import androidx.test.espresso.intent.rule.IntentsTestRule
+import com.nextcloud.client.TestActivity
+import com.owncloud.android.AbstractOnServerIT
+import org.junit.Rule
+import org.junit.Test
+
+class UsersAndGroupsSearchProviderIT : AbstractOnServerIT() {
+    @get:Rule
+    val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
+
+    @Test
+    fun searchUser() {
+        val activity = testActivityRule.launchActivity(null)
+
+        shortSleep()
+
+        activity.runOnUiThread {
+            // fragment.search("Admin")
+        }
+
+        longSleep()
+    }
+}

+ 125 - 0
src/androidTest/java/com/owncloud/android/ui/fragment/AvatarIT.kt

@@ -22,16 +22,21 @@
 
 package com.owncloud.android.ui.fragment
 
+import android.graphics.BitmapFactory
 import androidx.test.espresso.intent.rule.IntentsTestRule
 import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
 import com.nextcloud.client.TestActivity
+import com.nextcloud.client.account.StatusType
 import com.owncloud.android.AbstractIT
 import com.owncloud.android.R
+import com.owncloud.android.ui.TextDrawable
+import com.owncloud.android.utils.BitmapUtils
 import com.owncloud.android.utils.DisplayUtils
 import com.owncloud.android.utils.ScreenshotTest
 import org.junit.Rule
 import org.junit.Test
 
+
 class AvatarIT : AbstractIT() {
     @get:Rule
     val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
@@ -60,4 +65,124 @@ class AvatarIT : AbstractIT() {
         waitForIdleSync()
         screenshot(sut)
     }
+
+    @Test
+    @ScreenshotTest
+    fun showAvatarsWithStatus() {
+        val avatarRadius = targetContext.resources.getDimension(R.dimen.list_item_avatar_icon_radius)
+        val width = DisplayUtils.convertDpToPixel(2 * avatarRadius, targetContext)
+        val sut = testActivityRule.launchActivity(null)
+        val fragment = AvatarTestFragment()
+
+        val paulette = BitmapFactory.decodeFile(getFile("paulette.jpg").absolutePath)
+        val christine = BitmapFactory.decodeFile(getFile("christine.jpg").absolutePath)
+        val textBitmap = BitmapUtils.drawableToBitmap(TextDrawable.createNamedAvatar("Admin", avatarRadius))
+
+        sut.addFragment(fragment)
+
+        runOnUiThread {
+            fragment.addBitmap(
+                BitmapUtils.createAvatarWithStatus(paulette, StatusType.online, "😘", targetContext),
+                width * 2,
+                1,
+                targetContext
+            )
+
+            fragment.addBitmap(
+                BitmapUtils.createAvatarWithStatus(christine, StatusType.online, "☁️", targetContext),
+                width * 2,
+                1,
+                targetContext
+            )
+
+            fragment.addBitmap(
+                BitmapUtils.createAvatarWithStatus(christine, StatusType.online, "🌴️", targetContext),
+                width * 2,
+                1,
+                targetContext
+            )
+
+            fragment.addBitmap(
+                BitmapUtils.createAvatarWithStatus(christine, StatusType.online, "", targetContext),
+                width * 2,
+                1,
+                targetContext
+            )
+
+            fragment.addBitmap(
+                BitmapUtils.createAvatarWithStatus(paulette, StatusType.dnd, "", targetContext),
+                width * 2,
+                1,
+                targetContext
+            )
+
+            fragment.addBitmap(
+                BitmapUtils.createAvatarWithStatus(christine, StatusType.away, "", targetContext),
+                width * 2,
+                1,
+                targetContext
+            )
+
+            fragment.addBitmap(
+                BitmapUtils.createAvatarWithStatus(paulette, StatusType.offline, "", targetContext),
+                width * 2,
+                1,
+                targetContext
+            )
+
+            fragment.addBitmap(
+                BitmapUtils.createAvatarWithStatus(textBitmap, StatusType.online, "😘", targetContext),
+                width,
+                2,
+                targetContext
+            )
+
+            fragment.addBitmap(
+                BitmapUtils.createAvatarWithStatus(textBitmap, StatusType.online, "☁️", targetContext),
+                width,
+                2,
+                targetContext
+            )
+
+            fragment.addBitmap(
+                BitmapUtils.createAvatarWithStatus(textBitmap, StatusType.online, "🌴️", targetContext),
+                width,
+                2,
+                targetContext
+            )
+
+            fragment.addBitmap(
+                BitmapUtils.createAvatarWithStatus(textBitmap, StatusType.online, "", targetContext),
+                width,
+                2,
+                targetContext
+            )
+
+            fragment.addBitmap(
+                BitmapUtils.createAvatarWithStatus(textBitmap, StatusType.dnd, "", targetContext),
+                width,
+                2,
+                targetContext
+            )
+
+            fragment.addBitmap(
+                BitmapUtils.createAvatarWithStatus(textBitmap, StatusType.away, "", targetContext),
+                width,
+                2,
+                targetContext
+            )
+
+            fragment.addBitmap(
+                BitmapUtils.createAvatarWithStatus(textBitmap, StatusType.offline, "", targetContext),
+                width,
+                2,
+                targetContext
+            )
+        }
+
+        shortSleep()
+        waitForIdleSync()
+        screenshot(sut)
+    }
+
 }

+ 24 - 3
src/androidTest/java/com/owncloud/android/ui/fragment/AvatarTestFragment.kt

@@ -22,6 +22,7 @@
 package com.owncloud.android.ui.fragment
 
 import android.content.Context
+import android.graphics.Bitmap
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
@@ -34,12 +35,14 @@ import com.owncloud.android.R
 import com.owncloud.android.ui.TextDrawable
 
 internal class AvatarTestFragment : Fragment() {
-    lateinit var list: LinearLayout
+    lateinit var list1: LinearLayout
+    lateinit var list2: LinearLayout
 
     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
         val view: View = inflater.inflate(R.layout.avatar_fragment, null)
 
-        list = view.findViewById(R.id.avatar_list)
+        list1 = view.findViewById(R.id.avatar_list1)
+        list2 = view.findViewById(R.id.avatar_list2)
 
         return view
     }
@@ -54,7 +57,25 @@ internal class AvatarTestFragment : Fragment() {
         layoutParams.setMargins(margin, margin, margin, margin)
         imageView.layoutParams = layoutParams
 
-        list.addView(imageView)
+        list1.addView(imageView)
+    }
+
+    fun addBitmap(bitmap: Bitmap, width: Int, list: Int, targetContext: Context) {
+        val margin = padding
+        val imageView = ImageView(targetContext)
+        imageView.setImageBitmap(bitmap)
+
+        val layoutParams: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams(width, width)
+        layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
+        layoutParams.setMargins(margin, margin, margin, margin)
+        imageView.layoutParams = layoutParams
+
+        if (list == 1) {
+            list1.addView(imageView)
+        } else {
+            list2.addView(imageView)
+        }
+
     }
 
     companion object {

+ 0 - 5
src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt

@@ -21,11 +21,9 @@
  */
 package com.owncloud.android.ui.fragment
 
-import android.Manifest
 import android.widget.ImageView
 import androidx.appcompat.widget.PopupMenu
 import androidx.test.espresso.intent.rule.IntentsTestRule
-import androidx.test.rule.GrantPermissionRule
 import com.nextcloud.client.TestActivity
 import com.owncloud.android.AbstractIT
 import com.owncloud.android.R
@@ -51,9 +49,6 @@ class FileDetailSharingFragmentIT : AbstractIT() {
     @get:Rule
     val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
 
-    @get:Rule
-    val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)
-
     lateinit var file: OCFile
     lateinit var folder: OCFile
     lateinit var activity: TestActivity

+ 16 - 2
src/debug/res/layout/avatar_fragment.xml

@@ -18,10 +18,24 @@
   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/>.
 -->
+
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/avatar_list"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical">
+    android:orientation="horizontal">
+
+    <LinearLayout
+        android:id="@+id/avatar_list1"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:layout_weight="1" />
+
+    <LinearLayout
+        android:id="@+id/avatar_list2"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:orientation="vertical" />
 
 </LinearLayout>

+ 24 - 0
src/main/java/com/nextcloud/client/account/Status.kt

@@ -0,0 +1,24 @@
+/*
+ *
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2020 Tobias Kaminsky
+ * Copyright (C) 2020 Nextcloud GmbH
+ *
+ * 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.nextcloud.client.account
+
+internal class Status(val status: Enum<StatusType>, val message: String, val icon: String, val clearAt: String)

+ 27 - 0
src/main/java/com/nextcloud/client/account/StatusType.kt

@@ -0,0 +1,27 @@
+/*
+ *
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2020 Tobias Kaminsky
+ * Copyright (C) 2020 Nextcloud GmbH
+ *
+ * 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.nextcloud.client.account
+
+enum class StatusType {
+    online, offline, dnd, away, unknown
+}

+ 105 - 9
src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java

@@ -27,25 +27,38 @@ import android.content.Context;
 import android.content.UriMatcher;
 import android.database.Cursor;
 import android.database.MatrixCursor;
+import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.ParcelFileDescriptor;
 import android.provider.BaseColumns;
+import android.text.TextUtils;
 import android.widget.Toast;
 
+import com.nextcloud.client.account.Status;
+import com.nextcloud.client.account.StatusType;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.UserAccountManager;
 import com.owncloud.android.R;
+import com.owncloud.android.datamodel.ArbitraryDataProvider;
 import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.ThumbnailsCacheManager;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.resources.shares.GetShareesRemoteOperation;
 import com.owncloud.android.lib.resources.shares.ShareType;
+import com.owncloud.android.ui.TextDrawable;
+import com.owncloud.android.utils.BitmapUtils;
 import com.owncloud.android.utils.ErrorMessageAdapter;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -72,6 +85,7 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
     private static final String[] COLUMNS = {
         BaseColumns._ID,
         SearchManager.SUGGEST_COLUMN_TEXT_1,
+        SearchManager.SUGGEST_COLUMN_TEXT_2,
         SearchManager.SUGGEST_COLUMN_ICON_1,
         SearchManager.SUGGEST_COLUMN_INTENT_DATA
     };
@@ -229,7 +243,8 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
                 Iterator<JSONObject> namesIt = names.iterator();
                 JSONObject item;
                 String displayName;
-                int icon = 0;
+                String subline = null;
+                Object icon = 0;
                 Uri dataUri;
                 int count = 0;
                 while (namesIt.hasNext()) {
@@ -237,13 +252,26 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
                     dataUri = null;
                     displayName = null;
                     String userName = item.getString(GetShareesRemoteOperation.PROPERTY_LABEL);
+                    String name = item.isNull("name") ? "" : item.getString("name");
                     JSONObject value = item.getJSONObject(GetShareesRemoteOperation.NODE_VALUE);
                     ShareType type = ShareType.fromValue(value.getInt(GetShareesRemoteOperation.PROPERTY_SHARE_TYPE));
                     String shareWith = value.getString(GetShareesRemoteOperation.PROPERTY_SHARE_WITH);
 
+                    Status status;
+                    JSONObject statusObject = item.optJSONObject("status");
+
+                    if (statusObject != null) {
+                        status = new Status(StatusType.valueOf(statusObject.getString("status")),
+                                            statusObject.isNull("message") ? "" : statusObject.getString("message"),
+                                            statusObject.isNull("icon") ? "" : statusObject.getString("icon"),
+                                            statusObject.isNull("clearAt") ? "" : statusObject.getString("clearAt"));
+                    } else {
+                        status = new Status(StatusType.unknown, "", "", "");
+                    }
+
                     switch (type) {
                         case GROUP:
-                            displayName = getContext().getString(R.string.share_group_clarification, userName);
+                            displayName = userName;
                             icon = R.drawable.ic_group;
                             dataUri = Uri.withAppendedPath(groupBaseUri, shareWith);
                             break;
@@ -254,30 +282,47 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
                                 dataUri = Uri.withAppendedPath(remoteBaseUri, shareWith);
 
                                 if (userName.equals(shareWith)) {
-                                    displayName = getContext().getString(R.string.share_remote_clarification, userName);
+                                    displayName = name;
+                                    subline = getContext().getString(R.string.remote);
                                 } else {
                                     String[] uriSplitted = shareWith.split("@");
-                                    displayName = getContext().getString(R.string.share_known_remote_clarification,
-                                                                         userName, uriSplitted[uriSplitted.length - 1]);
+                                    displayName = name;
+                                    subline = getContext().getString(R.string.share_known_remote_on_clarification,
+                                                                     uriSplitted[uriSplitted.length - 1]);
                                 }
                             }
                             break;
 
                         case USER:
                             displayName = userName;
-                            icon = R.drawable.ic_user;
+                            subline = status.getMessage().isEmpty() ? null : status.getMessage();
+                            Uri.Builder builder =
+                                Uri.parse("content://com.nextcloud.android.providers.UsersAndGroupsSearchProvider/icon")
+                                    .buildUpon();
+
+                            builder.appendQueryParameter("shareWith", shareWith);
+                            builder.appendQueryParameter("displayName", displayName);
+                            builder.appendQueryParameter("status", status.getStatus().toString());
+
+                            if (!TextUtils.isEmpty(status.getIcon()) && !"null".equals(status.getIcon())) {
+                                builder.appendQueryParameter("icon", status.getIcon());
+                            }
+
+                            icon = builder.build();
+
                             dataUri = Uri.withAppendedPath(userBaseUri, shareWith);
                             break;
 
                         case EMAIL:
                             icon = R.drawable.ic_email;
-                            displayName = getContext().getString(R.string.share_email_clarification, userName);
+                            displayName = name;
+                            subline = shareWith;
                             dataUri = Uri.withAppendedPath(emailBaseUri, shareWith);
                             break;
 
                         case ROOM:
-                            icon = R.drawable.ic_chat_bubble;
-                            displayName = getContext().getString(R.string.share_room_clarification, userName);
+                            icon = R.drawable.ic_talk;
+                            displayName = userName;
                             dataUri = Uri.withAppendedPath(roomBaseUri, shareWith);
                             break;
 
@@ -295,6 +340,7 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
                         response.newRow()
                             .add(count++)             // BaseColumns._ID
                             .add(displayName)         // SearchManager.SUGGEST_COLUMN_TEXT_1
+                            .add(subline)             // SearchManager.SUGGEST_COLUMN_TEXT_2
                             .add(icon)                // SearchManager.SUGGEST_COLUMN_ICON_1
                             .add(dataUri);
                     }
@@ -324,6 +370,56 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
         return 0;
     }
 
+    @Nullable
+    @Override
+    public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
+        ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(getContext().getContentResolver());
+
+        String userId = uri.getQueryParameter("shareWith");
+        String displayName = uri.getQueryParameter("displayName");
+        String accountName = accountManager.getUser().getAccountName();
+        String serverName = accountName.substring(accountName.lastIndexOf('@') + 1);
+
+        String eTag = arbitraryDataProvider.getValue(userId + "@" + serverName, ThumbnailsCacheManager.AVATAR);
+        String avatarKey = "a_" + userId + "_" + serverName + "_" + eTag;
+
+        StatusType status = StatusType.valueOf(uri.getQueryParameter("status"));
+        String icon = uri.getQueryParameter("icon");
+
+        Bitmap avatarBitmap = ThumbnailsCacheManager.getBitmapFromDiskCache(avatarKey);
+
+        if (avatarBitmap == null) {
+            float avatarRadius = getContext().getResources().getDimension(R.dimen.list_item_avatar_icon_radius);
+            avatarBitmap = BitmapUtils.drawableToBitmap(TextDrawable.createNamedAvatar(displayName, avatarRadius));
+        }
+
+        Bitmap avatar = BitmapUtils.createAvatarWithStatus(avatarBitmap, status, icon, getContext());
+
+        // create a file to write bitmap data
+        File f = new File(getContext().getCacheDir(), "test");
+        try {
+            f.createNewFile();
+
+            //Convert bitmap to byte array
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+            avatar.compress(Bitmap.CompressFormat.PNG, 90, bos);
+            byte[] bitmapData = bos.toByteArray();
+
+            //write the bytes in file
+            try (FileOutputStream fos = new FileOutputStream(f)) {
+                fos.write(bitmapData);
+            } catch (FileNotFoundException e) {
+                Log_OC.e(TAG, "File not found: " + e.getMessage());
+            }
+
+        } catch (Exception e) {
+            Log_OC.e(TAG, "Error opening file: " + e.getMessage());
+        }
+
+        return ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
+    }
+
     /**
      * Show error message
      *

+ 127 - 0
src/main/java/com/owncloud/android/ui/StatusDrawable.java

@@ -0,0 +1,127 @@
+/*
+ *
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2020 Tobias Kaminsky
+ * Copyright (C) 2020 Nextcloud GmbH
+ *
+ * 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.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+
+import com.owncloud.android.utils.BitmapUtils;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.core.content.res.ResourcesCompat;
+
+/**
+ * A Drawable object that draws a status
+ */
+public class StatusDrawable extends Drawable {
+    private String mText = null;
+    private @DrawableRes int icon = -1;
+    private Paint mTextPaint;
+    private final Paint mBackground;
+    private final float radius;
+    private Context context;
+
+    public StatusDrawable(@DrawableRes int icon, float size, Context context) {
+        radius = size;
+        this.icon = icon;
+        this.context = context;
+
+        mBackground = new Paint();
+        mBackground.setStyle(Paint.Style.FILL);
+        mBackground.setAntiAlias(true);
+        mBackground.setColor(Color.argb(200, 255, 255, 255));
+    }
+
+    public StatusDrawable(BitmapUtils.Color color, float size) {
+        radius = size;
+
+        mBackground = new Paint();
+        mBackground.setStyle(Paint.Style.FILL);
+        mBackground.setAntiAlias(true);
+        mBackground.setColor(Color.argb(color.a, color.r, color.g, color.b));
+    }
+
+    public StatusDrawable(String icon, float size) {
+        mText = icon;
+        radius = size;
+
+        mBackground = new Paint();
+        mBackground.setStyle(Paint.Style.FILL);
+        mBackground.setAntiAlias(true);
+        mBackground.setColor(Color.argb(200, 255, 255, 255));
+
+        mTextPaint = new Paint();
+        mTextPaint.setColor(Color.WHITE);
+        mTextPaint.setTextSize(size);
+        mTextPaint.setAntiAlias(true);
+        mTextPaint.setTextAlign(Paint.Align.CENTER);
+    }
+
+    /**
+     * Draw in its bounds (set via setBounds) respecting optional effects such as alpha (set via setAlpha) and color
+     * filter (set via setColorFilter) a circular background with a user's first character.
+     *
+     * @param canvas The canvas to draw into
+     */
+    @Override
+    public void draw(@NonNull Canvas canvas) {
+        if (mBackground != null) {
+            canvas.drawCircle(radius, radius, radius, mBackground);
+        }
+
+        if (mText != null) {
+            mTextPaint.setTextSize(1.6f * radius);
+            canvas.drawText(mText, radius, radius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2), mTextPaint);
+        }
+
+        if (icon != -1) {
+            Drawable drawable = ResourcesCompat.getDrawable(context.getResources(), icon, null);
+            drawable.setBounds(0,
+                               0,
+                               (int) (2 * radius),
+                               (int) (2 * radius));
+            drawable.draw(canvas);
+        }
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mTextPaint.setAlpha(alpha);
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter cf) {
+        mTextPaint.setColorFilter(cf);
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+}

+ 22 - 18
src/main/java/com/owncloud/android/ui/TextDrawable.java

@@ -2,7 +2,7 @@
  * ownCloud Android client application
  *
  * @author Andy Scherzinger
- * @author Tobias Kaminsiky
+ * @author Tobias Kaminsky
  * @author Chris Narkiewicz
  * Copyright (C) 2016 ownCloud Inc.
  * Copyright (C) 2018 Andy Scherzinger
@@ -35,7 +35,6 @@ import com.nextcloud.client.account.UserAccountManager;
 import com.owncloud.android.utils.BitmapUtils;
 import com.owncloud.android.utils.NextcloudServer;
 
-import java.security.NoSuchAlgorithmException;
 import java.util.Locale;
 
 import androidx.annotation.NonNull;
@@ -65,6 +64,8 @@ public class TextDrawable extends Drawable {
      */
     private float mRadius;
 
+    private boolean bigText = false;
+
     /**
      * Create a TextDrawable with the given radius.
      *
@@ -79,44 +80,43 @@ public class TextDrawable extends Drawable {
         mBackground = new Paint();
         mBackground.setStyle(Paint.Style.FILL);
         mBackground.setAntiAlias(true);
-        mBackground.setColor(Color.rgb(color.r, color.g, color.b));
+        mBackground.setColor(Color.argb(color.a, color.r, color.g, color.b));
 
         mTextPaint = new Paint();
         mTextPaint.setColor(Color.WHITE);
         mTextPaint.setTextSize(radius);
         mTextPaint.setAntiAlias(true);
         mTextPaint.setTextAlign(Paint.Align.CENTER);
+
+        setBounds(0, 0, (int) radius * 2, (int) radius * 2);
     }
 
     /**
-     * creates an avatar in form of a TextDrawable with the first letter of the account name in a circle with the
-     * given radius.
+     * creates an avatar in form of a TextDrawable with the first letter of the account name in a circle with the given
+     * radius.
      *
-     * @param account user account
-     * @param radiusInDp  the circle's radius
+     * @param account    user account
+     * @param radiusInDp the circle's radius
      * @return the avatar as a TextDrawable
-     * @throws NoSuchAlgorithmException     if the specified algorithm is not available when calculating the color values
      */
     @NonNull
     @NextcloudServer(max = 12)
-    public static TextDrawable createAvatar(Account account, float radiusInDp) throws
-            NoSuchAlgorithmException {
+    public static TextDrawable createAvatar(Account account, float radiusInDp) {
         String username = UserAccountManager.getDisplayName(account);
         return createNamedAvatar(username, radiusInDp);
     }
 
     /**
-     * creates an avatar in form of a TextDrawable with the first letter of the account name in a circle with the
-     * given radius.
+     * creates an avatar in form of a TextDrawable with the first letter of the account name in a circle with the given
+     * radius.
      *
-     * @param userId      userId to use
-     * @param radiusInDp  the circle's radius
+     * @param userId     userId to use
+     * @param radiusInDp the circle's radius
      * @return the avatar as a TextDrawable
-     * @throws NoSuchAlgorithmException     if the specified algorithm is not available when calculating the color values
      */
     @NonNull
     @NextcloudServer(max = 12)
-    public static TextDrawable createAvatarByUserId(String userId, float radiusInDp) throws NoSuchAlgorithmException {
+    public static TextDrawable createAvatarByUserId(String userId, float radiusInDp) {
         return createNamedAvatar(userId, radiusInDp);
     }
 
@@ -127,10 +127,9 @@ public class TextDrawable extends Drawable {
      * @param name       the name
      * @param radiusInDp the circle's radius
      * @return the avatar as a TextDrawable
-     * @throws NoSuchAlgorithmException     if the specified algorithm is not available when calculating the color values
      */
     @NonNull
-    public static TextDrawable createNamedAvatar(String name, float radiusInDp) throws NoSuchAlgorithmException {
+    public static TextDrawable createNamedAvatar(String name, float radiusInDp) {
         BitmapUtils.Color color = BitmapUtils.usernameToColor(name);
         return new TextDrawable(extractCharsFromDisplayName(name), color, radiusInDp);
     }
@@ -160,6 +159,11 @@ public class TextDrawable extends Drawable {
     @Override
     public void draw(@NonNull Canvas canvas) {
         canvas.drawCircle(mRadius, mRadius, mRadius, mBackground);
+
+        if (bigText) {
+            mTextPaint.setTextSize(1.8f * mRadius);
+        }
+
         canvas.drawText(mText, mRadius, mRadius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2), mTextPaint);
     }
 

+ 1 - 3
src/main/java/com/owncloud/android/ui/adapter/ShareViewHolder.java

@@ -31,8 +31,6 @@ import com.owncloud.android.databinding.FileDetailsShareShareItemBinding;
 import com.owncloud.android.lib.resources.shares.OCShare;
 import com.owncloud.android.ui.TextDrawable;
 
-import java.security.NoSuchAlgorithmException;
-
 import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView;
@@ -95,7 +93,7 @@ class ShareViewHolder extends RecyclerView.ViewHolder {
     private void setImage(ImageView avatar, String name, @DrawableRes int fallback) {
         try {
             avatar.setImageDrawable(TextDrawable.createNamedAvatar(name, avatarRadiusDimension));
-        } catch (NoSuchAlgorithmException | StringIndexOutOfBoundsException e) {
+        } catch (StringIndexOutOfBoundsException e) {
             avatar.setImageResource(fallback);
         }
     }

+ 55 - 0
src/main/java/com/owncloud/android/ui/components/AvatarWithStatus.kt

@@ -0,0 +1,55 @@
+/*
+ *
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2020 Tobias Kaminsky
+ * Copyright (C) 2020 Nextcloud GmbH
+ *
+ * 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.components
+
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.Paint
+import android.graphics.PixelFormat
+import android.graphics.drawable.Drawable
+import androidx.core.graphics.drawable.RoundedBitmapDrawable
+
+class AvatarWithStatus(val roundedBitmapDrawable: RoundedBitmapDrawable) : Drawable() {
+    private val redPaint: Paint = Paint().apply { setARGB(255, 255, 0, 0) }
+
+    override fun draw(canvas: Canvas) {
+        val width: Int = 100
+        val height: Int = 100
+        val radius: Float = Math.min(width, height).toFloat() / 2f
+
+        // Draw a red circle in the center
+        //canvas.drawBitmap(roundedBitmapDrawable.bitmap!!, 0f, 0f, null)
+        canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, redPaint)
+
+    }
+
+    override fun setAlpha(alpha: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun setColorFilter(colorFilter: ColorFilter?) {
+        TODO("Not yet implemented")
+    }
+
+    override fun getOpacity(): Int = PixelFormat.OPAQUE
+}

+ 7 - 0
src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java

@@ -76,6 +76,7 @@ import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.appcompat.widget.PopupMenu;
+import androidx.appcompat.widget.SearchView;
 import androidx.fragment.app.Fragment;
 import androidx.recyclerview.widget.LinearLayoutManager;
 
@@ -713,4 +714,10 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
     private boolean canReshare(OCShare share) {
         return (share.getPermissions() & SHARE_PERMISSION_FLAG) > 0;
     }
+
+    @VisibleForTesting
+    public void search(String query) {
+        SearchView searchView = getView().findViewById(R.id.searchView);
+        searchView.setQuery(query, true);
+    }
 }

+ 118 - 4
src/main/java/com/owncloud/android/utils/BitmapUtils.java

@@ -18,19 +18,27 @@
  */
 package com.owncloud.android.utils;
 
+import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapFactory.Options;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
 import android.widget.ImageView;
 
+import com.nextcloud.client.account.StatusType;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
 import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.ui.StatusDrawable;
 
 import org.apache.commons.codec.binary.Hex;
 
@@ -194,12 +202,19 @@ public final class BitmapUtils {
         return resultBitmap;
     }
 
-    public static Color usernameToColor(String name) throws NoSuchAlgorithmException {
+    public static Color usernameToColor(String name) {
         String hash = name.toLowerCase(Locale.ROOT);
 
         // already a md5 hash?
         if (!hash.matches("([0-9a-f]{4}-?){8}$")) {
-            hash = md5(hash);
+            try {
+                hash = md5(hash);
+            } catch (NoSuchAlgorithmException e) {
+                int color = getResources().getColor(R.color.primary_dark);
+                return new Color(android.graphics.Color.red(color),
+                                 android.graphics.Color.green(color),
+                                 android.graphics.Color.blue(color));
+            }
         }
 
         hash = hash.replaceAll("[^0-9a-f]", "");
@@ -279,6 +294,7 @@ public final class BitmapUtils {
     }
 
     public static class Color {
+        public int a = 255;
         public int r;
         public int g;
         public int b;
@@ -289,6 +305,13 @@ public final class BitmapUtils {
             this.b = b;
         }
 
+        public Color(int a, int r, int g, int b) {
+            this.a = a;
+            this.r = r;
+            this.g = g;
+            this.b = b;
+        }
+
         @Override
         public boolean equals(@Nullable Object obj) {
             if (!(obj instanceof Color)) {
@@ -358,9 +381,16 @@ public final class BitmapUtils {
 
         Bitmap bitmap;
         if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
-            bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+            if (drawable.getBounds().width() > 0 && drawable.getBounds().height() > 0) {
+                bitmap = Bitmap.createBitmap(drawable.getBounds().width(),
+                                             drawable.getBounds().height(),
+                                             Bitmap.Config.ARGB_8888);
+            } else {
+                bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+            }
         } else {
-            bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(),
+            bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+                                         drawable.getIntrinsicHeight(),
                                          Bitmap.Config.ARGB_8888);
         }
 
@@ -384,6 +414,90 @@ public final class BitmapUtils {
                                      imageView);
     }
 
+    public static Bitmap createAvatarWithStatus(Bitmap avatar, StatusType status, String icon, Context context) {
+        float avatarRadius = getResources().getDimension(R.dimen.list_item_avatar_icon_radius);
+        int width = DisplayUtils.convertDpToPixel(2 * avatarRadius, context);
+
+        Bitmap output = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(output);
+
+        // avatar
+        Bitmap croppedBitmap = getCroppedBitmap(avatar, width);
+
+        canvas.drawBitmap(croppedBitmap, 0f, 0f, null);
+
+        // status
+        int statusSize = width / 4;
+
+        StatusDrawable statusDrawable;
+        if (TextUtils.isEmpty(icon)) {
+            switch (status) {
+                case dnd:
+                    statusDrawable = new StatusDrawable(R.drawable.ic_user_status_dnd, statusSize, context);
+                    statusDrawable.setBounds(width / 2,
+                                             width / 2,
+                                             width,
+                                             width);
+                    break;
+
+                case online:
+                    statusDrawable = new StatusDrawable(new Color(255, 73, 179, 130), statusSize);
+                    statusDrawable.setBounds(width,
+                                             width,
+                                             width,
+                                             width);
+                    break;
+
+                case away:
+                    statusDrawable = new StatusDrawable(R.drawable.ic_user_status_away, statusSize, context);
+                    statusDrawable.setBounds(width / 2,
+                                             width / 2,
+                                             width,
+                                             width);
+                    break;
+
+                default:
+                    // do not show
+                    statusDrawable = null;
+                    break;
+            }
+        } else {
+            statusDrawable = new StatusDrawable(icon, statusSize);
+            statusDrawable.setBounds(width / 2,
+                                     width / 2,
+                                     width,
+                                     width);
+        }
+
+        if (statusDrawable != null) {
+            canvas.translate(width / 2f, width / 2f);
+            statusDrawable.draw(canvas);
+        }
+
+        return output;
+    }
+
+    /**
+     * from https://stackoverflow.com/a/12089127
+     */
+    private static Bitmap getCroppedBitmap(Bitmap bitmap, int width) {
+        Bitmap output = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(output);
+        int color = -0xbdbdbe;
+        Paint paint = new Paint();
+        Rect rect = new Rect(0, 0, width, width);
+        paint.setAntiAlias(true);
+        canvas.drawARGB(0, 0, 0, 0);
+        paint.setColor(color);
+
+        canvas.drawCircle(width / 2f, width / 2f, width / 2f, paint);
+        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
+
+        canvas.drawBitmap(Bitmap.createScaledBitmap(bitmap, width, width, false), rect, rect, paint);
+
+        return output;
+    }
+
     private static Resources getResources() {
         return MainApp.getAppContext().getResources();
     }

+ 11 - 0
src/main/res/drawable/ic_talk.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="128"
+    android:viewportHeight="128">
+    <path
+        android:fillColor="#757575"
+        android:pathData="M63.992,0.689C29.031,0.689 0.691,29.031 0.692,63.992c0,34.96 28.34,63.301 63.3,63.302 6.982,-0.014 13.881,-1.183 20.426,-3.43 4.317,-1.482 8.48,-3.433 12.411,-5.831 3.383,1.344 8.59,3.838 13.736,5.902 6.688,2.683 13.274,4.639 15.618,2.399 2.317,-2.212 0.703,-8.809 -1.647,-15.575 -2.046,-5.892 -4.649,-11.913 -5.701,-15.282 2.544,-4.415 4.535,-9.101 5.945,-13.954 1.648,-5.674 2.5,-11.574 2.512,-17.532C127.291,29.032 98.952,0.692 63.992,0.691ZM63.999,24.756l0.001,0c21.677,0 39.25,17.573 39.25,39.251 -0.001,21.677 -17.574,39.249 -39.251,39.249 -21.676,0 -39.249,-17.572 -39.25,-39.249 0,-21.678 17.573,-39.251 39.25,-39.251z"
+        android:strokeWidth="4.78543139" />
+</vector>

+ 32 - 0
src/main/res/drawable/ic_user_status_away.xml

@@ -0,0 +1,32 @@
+<!--
+  ~
+  ~ Nextcloud Android client application
+  ~
+  ~ @author Tobias Kaminsky
+  ~ Copyright (C) 2020 Tobias Kaminsky
+  ~ Copyright (C) 2020 Nextcloud GmbH
+  ~
+  ~ 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/>.
+  -->
+
+<vector android:autoMirrored="true"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24"
+    android:width="24dp"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <path
+        android:fillColor="#f4a331"
+        android:pathData="m10.615,2.1094c-4.8491,0.6811 -8.6152,4.8615 -8.6152,9.8906 0,5.5 4.5,10 10,10 5.0292,0 9.2096,-3.7661 9.8906,-8.6152 -1.4654,1.601 -3.5625,2.6152 -5.8906,2.6152 -4.4,0 -8,-3.6 -8,-8 0,-2.3281 1.0143,-4.4252 2.6152,-5.8906z" />
+</vector>

+ 38 - 0
src/main/res/drawable/ic_user_status_dnd.xml

@@ -0,0 +1,38 @@
+<!--
+  ~
+  ~ Nextcloud Android client application
+  ~
+  ~ @author Tobias Kaminsky
+  ~ Copyright (C) 2020 Tobias Kaminsky
+  ~ Copyright (C) 2020 Nextcloud GmbH
+  ~
+  ~ 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/>.
+  -->
+
+<vector android:autoMirrored="true"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24"
+    android:width="24dp"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <path
+        android:fillColor="#ed484c"
+        android:pathData="m12,2c-5.52,0 -10,4.48 -10,10s4.48,10 10,10 10,-4.48 10,-10 -4.48,-10 -10,-10z" />
+    <path
+        android:fillColor="#fdffff"
+        android:pathData="m8,10h8c1.108,0 2,0.892 2,2s-0.892,2 -2,2h-8c-1.108,0 -2,-0.892 -2,-2s0.892,-2 2,-2z"
+        android:strokeLineCap="round"
+        android:strokeLineJoin="round"
+        android:strokeWidth="2" />
+</vector>

+ 2 - 1
src/main/res/values/strings.xml

@@ -479,7 +479,7 @@
     <string name="share_remote_clarification">%1$s (remote)</string>
     <string name="share_email_clarification">%1$s (email)</string>
     <string name="share_room_clarification">%1$s (conversation)</string>
-    <string name="share_known_remote_clarification">%1$s ( at %2$s )</string>
+    <string name="share_known_remote_on_clarification">on %1$s</string>
 
     <string name="share_privilege_unshare">Unshare</string>
 
@@ -936,4 +936,5 @@
     <string name="link_share_file_drop">File drop (upload only)</string>
     <string name="could_not_retrieve_shares">Could not retrieve shares</string>
     <string name="failed_update_ui">Failed to update UI</string>
+    <string name="remote">(remote)</string>
 </resources>