mirror of
https://github.com/nextcloud/android.git
synced 2024-11-23 13:45:35 +03:00
Add status to sharee
Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
This commit is contained in:
parent
153aa77b41
commit
dbd9e6fe9b
24 changed files with 794 additions and 46 deletions
7
drawable_resources/user-status-away.svg
Normal file
7
drawable_resources/user-status-away.svg
Normal file
|
@ -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>
|
After Width: | Height: | Size: 449 B |
6
drawable_resources/user-status-dnd.svg
Normal file
6
drawable_resources/user-status-dnd.svg
Normal file
|
@ -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>
|
After Width: | Height: | Size: 473 B |
Binary file not shown.
After Width: | Height: | Size: 101 KiB |
|
@ -58,7 +58,7 @@ fi
|
||||||
if [[ $4 = "all" ]]; then
|
if [[ $4 = "all" ]]; then
|
||||||
scripts/runAllScreenshotCombinations "noCI" "$1" "-Pandroid.testInstrumentationRunnerArguments.class=$class$method"
|
scripts/runAllScreenshotCombinations "noCI" "$1" "-Pandroid.testInstrumentationRunnerArguments.class=$class$method"
|
||||||
else
|
else
|
||||||
./gradlew gplayDebugExecuteScreenshotTests $record \
|
./gradlew --offline gplayDebugExecuteScreenshotTests $record \
|
||||||
-Pandroid.testInstrumentationRunnerArguments.annotation=com.owncloud.android.utils.ScreenshotTest \
|
-Pandroid.testInstrumentationRunnerArguments.annotation=com.owncloud.android.utils.ScreenshotTest \
|
||||||
-Pandroid.testInstrumentationRunnerArguments.class=$class$method \
|
-Pandroid.testInstrumentationRunnerArguments.class=$class$method \
|
||||||
$darkMode \
|
$darkMode \
|
||||||
|
|
BIN
src/androidTest/assets/christine.jpg
Normal file
BIN
src/androidTest/assets/christine.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
BIN
src/androidTest/assets/paulette.jpg
Normal file
BIN
src/androidTest/assets/paulette.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.9 KiB |
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,16 +22,21 @@
|
||||||
|
|
||||||
package com.owncloud.android.ui.fragment
|
package com.owncloud.android.ui.fragment
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
import androidx.test.espresso.intent.rule.IntentsTestRule
|
import androidx.test.espresso.intent.rule.IntentsTestRule
|
||||||
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
|
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
|
||||||
import com.nextcloud.client.TestActivity
|
import com.nextcloud.client.TestActivity
|
||||||
|
import com.nextcloud.client.account.StatusType
|
||||||
import com.owncloud.android.AbstractIT
|
import com.owncloud.android.AbstractIT
|
||||||
import com.owncloud.android.R
|
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.DisplayUtils
|
||||||
import com.owncloud.android.utils.ScreenshotTest
|
import com.owncloud.android.utils.ScreenshotTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
|
||||||
class AvatarIT : AbstractIT() {
|
class AvatarIT : AbstractIT() {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
|
val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
|
||||||
|
@ -60,4 +65,124 @@ class AvatarIT : AbstractIT() {
|
||||||
waitForIdleSync()
|
waitForIdleSync()
|
||||||
screenshot(sut)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
package com.owncloud.android.ui.fragment
|
package com.owncloud.android.ui.fragment
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -34,12 +35,14 @@ import com.owncloud.android.R
|
||||||
import com.owncloud.android.ui.TextDrawable
|
import com.owncloud.android.ui.TextDrawable
|
||||||
|
|
||||||
internal class AvatarTestFragment : Fragment() {
|
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? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
val view: View = inflater.inflate(R.layout.avatar_fragment, null)
|
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
|
return view
|
||||||
}
|
}
|
||||||
|
@ -54,7 +57,25 @@ internal class AvatarTestFragment : Fragment() {
|
||||||
layoutParams.setMargins(margin, margin, margin, margin)
|
layoutParams.setMargins(margin, margin, margin, margin)
|
||||||
imageView.layoutParams = layoutParams
|
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 {
|
companion object {
|
||||||
|
|
|
@ -21,11 +21,9 @@
|
||||||
*/
|
*/
|
||||||
package com.owncloud.android.ui.fragment
|
package com.owncloud.android.ui.fragment
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.test.espresso.intent.rule.IntentsTestRule
|
import androidx.test.espresso.intent.rule.IntentsTestRule
|
||||||
import androidx.test.rule.GrantPermissionRule
|
|
||||||
import com.nextcloud.client.TestActivity
|
import com.nextcloud.client.TestActivity
|
||||||
import com.owncloud.android.AbstractIT
|
import com.owncloud.android.AbstractIT
|
||||||
import com.owncloud.android.R
|
import com.owncloud.android.R
|
||||||
|
@ -51,9 +49,6 @@ class FileDetailSharingFragmentIT : AbstractIT() {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
|
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 file: OCFile
|
||||||
lateinit var folder: OCFile
|
lateinit var folder: OCFile
|
||||||
lateinit var activity: TestActivity
|
lateinit var activity: TestActivity
|
||||||
|
|
|
@ -18,10 +18,24 @@
|
||||||
You should have received a copy of the GNU Affero General Public License
|
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/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/avatar_list"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="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>
|
</LinearLayout>
|
||||||
|
|
24
src/main/java/com/nextcloud/client/account/Status.kt
Normal file
24
src/main/java/com/nextcloud/client/account/Status.kt
Normal file
|
@ -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
src/main/java/com/nextcloud/client/account/StatusType.kt
Normal file
27
src/main/java/com/nextcloud/client/account/StatusType.kt
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -27,25 +27,38 @@ import android.content.Context;
|
||||||
import android.content.UriMatcher;
|
import android.content.UriMatcher;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.MatrixCursor;
|
import android.database.MatrixCursor;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.provider.BaseColumns;
|
import android.provider.BaseColumns;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.widget.Toast;
|
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.User;
|
||||||
import com.nextcloud.client.account.UserAccountManager;
|
import com.nextcloud.client.account.UserAccountManager;
|
||||||
import com.owncloud.android.R;
|
import com.owncloud.android.R;
|
||||||
|
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
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.operations.RemoteOperationResult;
|
||||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||||
import com.owncloud.android.lib.resources.shares.GetShareesRemoteOperation;
|
import com.owncloud.android.lib.resources.shares.GetShareesRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.shares.ShareType;
|
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 com.owncloud.android.utils.ErrorMessageAdapter;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
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.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -72,6 +85,7 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
|
||||||
private static final String[] COLUMNS = {
|
private static final String[] COLUMNS = {
|
||||||
BaseColumns._ID,
|
BaseColumns._ID,
|
||||||
SearchManager.SUGGEST_COLUMN_TEXT_1,
|
SearchManager.SUGGEST_COLUMN_TEXT_1,
|
||||||
|
SearchManager.SUGGEST_COLUMN_TEXT_2,
|
||||||
SearchManager.SUGGEST_COLUMN_ICON_1,
|
SearchManager.SUGGEST_COLUMN_ICON_1,
|
||||||
SearchManager.SUGGEST_COLUMN_INTENT_DATA
|
SearchManager.SUGGEST_COLUMN_INTENT_DATA
|
||||||
};
|
};
|
||||||
|
@ -229,7 +243,8 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
|
||||||
Iterator<JSONObject> namesIt = names.iterator();
|
Iterator<JSONObject> namesIt = names.iterator();
|
||||||
JSONObject item;
|
JSONObject item;
|
||||||
String displayName;
|
String displayName;
|
||||||
int icon = 0;
|
String subline = null;
|
||||||
|
Object icon = 0;
|
||||||
Uri dataUri;
|
Uri dataUri;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
while (namesIt.hasNext()) {
|
while (namesIt.hasNext()) {
|
||||||
|
@ -237,13 +252,26 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
|
||||||
dataUri = null;
|
dataUri = null;
|
||||||
displayName = null;
|
displayName = null;
|
||||||
String userName = item.getString(GetShareesRemoteOperation.PROPERTY_LABEL);
|
String userName = item.getString(GetShareesRemoteOperation.PROPERTY_LABEL);
|
||||||
|
String name = item.isNull("name") ? "" : item.getString("name");
|
||||||
JSONObject value = item.getJSONObject(GetShareesRemoteOperation.NODE_VALUE);
|
JSONObject value = item.getJSONObject(GetShareesRemoteOperation.NODE_VALUE);
|
||||||
ShareType type = ShareType.fromValue(value.getInt(GetShareesRemoteOperation.PROPERTY_SHARE_TYPE));
|
ShareType type = ShareType.fromValue(value.getInt(GetShareesRemoteOperation.PROPERTY_SHARE_TYPE));
|
||||||
String shareWith = value.getString(GetShareesRemoteOperation.PROPERTY_SHARE_WITH);
|
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) {
|
switch (type) {
|
||||||
case GROUP:
|
case GROUP:
|
||||||
displayName = getContext().getString(R.string.share_group_clarification, userName);
|
displayName = userName;
|
||||||
icon = R.drawable.ic_group;
|
icon = R.drawable.ic_group;
|
||||||
dataUri = Uri.withAppendedPath(groupBaseUri, shareWith);
|
dataUri = Uri.withAppendedPath(groupBaseUri, shareWith);
|
||||||
break;
|
break;
|
||||||
|
@ -254,30 +282,47 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
|
||||||
dataUri = Uri.withAppendedPath(remoteBaseUri, shareWith);
|
dataUri = Uri.withAppendedPath(remoteBaseUri, shareWith);
|
||||||
|
|
||||||
if (userName.equals(shareWith)) {
|
if (userName.equals(shareWith)) {
|
||||||
displayName = getContext().getString(R.string.share_remote_clarification, userName);
|
displayName = name;
|
||||||
|
subline = getContext().getString(R.string.remote);
|
||||||
} else {
|
} else {
|
||||||
String[] uriSplitted = shareWith.split("@");
|
String[] uriSplitted = shareWith.split("@");
|
||||||
displayName = getContext().getString(R.string.share_known_remote_clarification,
|
displayName = name;
|
||||||
userName, uriSplitted[uriSplitted.length - 1]);
|
subline = getContext().getString(R.string.share_known_remote_on_clarification,
|
||||||
|
uriSplitted[uriSplitted.length - 1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case USER:
|
case USER:
|
||||||
displayName = userName;
|
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);
|
dataUri = Uri.withAppendedPath(userBaseUri, shareWith);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EMAIL:
|
case EMAIL:
|
||||||
icon = R.drawable.ic_email;
|
icon = R.drawable.ic_email;
|
||||||
displayName = getContext().getString(R.string.share_email_clarification, userName);
|
displayName = name;
|
||||||
|
subline = shareWith;
|
||||||
dataUri = Uri.withAppendedPath(emailBaseUri, shareWith);
|
dataUri = Uri.withAppendedPath(emailBaseUri, shareWith);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ROOM:
|
case ROOM:
|
||||||
icon = R.drawable.ic_chat_bubble;
|
icon = R.drawable.ic_talk;
|
||||||
displayName = getContext().getString(R.string.share_room_clarification, userName);
|
displayName = userName;
|
||||||
dataUri = Uri.withAppendedPath(roomBaseUri, shareWith);
|
dataUri = Uri.withAppendedPath(roomBaseUri, shareWith);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -295,6 +340,7 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
|
||||||
response.newRow()
|
response.newRow()
|
||||||
.add(count++) // BaseColumns._ID
|
.add(count++) // BaseColumns._ID
|
||||||
.add(displayName) // SearchManager.SUGGEST_COLUMN_TEXT_1
|
.add(displayName) // SearchManager.SUGGEST_COLUMN_TEXT_1
|
||||||
|
.add(subline) // SearchManager.SUGGEST_COLUMN_TEXT_2
|
||||||
.add(icon) // SearchManager.SUGGEST_COLUMN_ICON_1
|
.add(icon) // SearchManager.SUGGEST_COLUMN_ICON_1
|
||||||
.add(dataUri);
|
.add(dataUri);
|
||||||
}
|
}
|
||||||
|
@ -324,6 +370,56 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
|
||||||
return 0;
|
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
|
* Show error message
|
||||||
*
|
*
|
||||||
|
|
127
src/main/java/com/owncloud/android/ui/StatusDrawable.java
Normal file
127
src/main/java/com/owncloud/android/ui/StatusDrawable.java
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
* ownCloud Android client application
|
* ownCloud Android client application
|
||||||
*
|
*
|
||||||
* @author Andy Scherzinger
|
* @author Andy Scherzinger
|
||||||
* @author Tobias Kaminsiky
|
* @author Tobias Kaminsky
|
||||||
* @author Chris Narkiewicz
|
* @author Chris Narkiewicz
|
||||||
* Copyright (C) 2016 ownCloud Inc.
|
* Copyright (C) 2016 ownCloud Inc.
|
||||||
* Copyright (C) 2018 Andy Scherzinger
|
* 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.BitmapUtils;
|
||||||
import com.owncloud.android.utils.NextcloudServer;
|
import com.owncloud.android.utils.NextcloudServer;
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
@ -65,6 +64,8 @@ public class TextDrawable extends Drawable {
|
||||||
*/
|
*/
|
||||||
private float mRadius;
|
private float mRadius;
|
||||||
|
|
||||||
|
private boolean bigText = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a TextDrawable with the given radius.
|
* Create a TextDrawable with the given radius.
|
||||||
*
|
*
|
||||||
|
@ -79,44 +80,43 @@ public class TextDrawable extends Drawable {
|
||||||
mBackground = new Paint();
|
mBackground = new Paint();
|
||||||
mBackground.setStyle(Paint.Style.FILL);
|
mBackground.setStyle(Paint.Style.FILL);
|
||||||
mBackground.setAntiAlias(true);
|
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 = new Paint();
|
||||||
mTextPaint.setColor(Color.WHITE);
|
mTextPaint.setColor(Color.WHITE);
|
||||||
mTextPaint.setTextSize(radius);
|
mTextPaint.setTextSize(radius);
|
||||||
mTextPaint.setAntiAlias(true);
|
mTextPaint.setAntiAlias(true);
|
||||||
mTextPaint.setTextAlign(Paint.Align.CENTER);
|
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
|
* creates an avatar in form of a TextDrawable with the first letter of the account name in a circle with the given
|
||||||
* given radius.
|
* radius.
|
||||||
*
|
*
|
||||||
* @param account user account
|
* @param account user account
|
||||||
* @param radiusInDp the circle's radius
|
* @param radiusInDp the circle's radius
|
||||||
* @return the avatar as a TextDrawable
|
* @return the avatar as a TextDrawable
|
||||||
* @throws NoSuchAlgorithmException if the specified algorithm is not available when calculating the color values
|
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@NextcloudServer(max = 12)
|
@NextcloudServer(max = 12)
|
||||||
public static TextDrawable createAvatar(Account account, float radiusInDp) throws
|
public static TextDrawable createAvatar(Account account, float radiusInDp) {
|
||||||
NoSuchAlgorithmException {
|
|
||||||
String username = UserAccountManager.getDisplayName(account);
|
String username = UserAccountManager.getDisplayName(account);
|
||||||
return createNamedAvatar(username, radiusInDp);
|
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
|
* creates an avatar in form of a TextDrawable with the first letter of the account name in a circle with the given
|
||||||
* given radius.
|
* radius.
|
||||||
*
|
*
|
||||||
* @param userId userId to use
|
* @param userId userId to use
|
||||||
* @param radiusInDp the circle's radius
|
* @param radiusInDp the circle's radius
|
||||||
* @return the avatar as a TextDrawable
|
* @return the avatar as a TextDrawable
|
||||||
* @throws NoSuchAlgorithmException if the specified algorithm is not available when calculating the color values
|
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@NextcloudServer(max = 12)
|
@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);
|
return createNamedAvatar(userId, radiusInDp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,10 +127,9 @@ public class TextDrawable extends Drawable {
|
||||||
* @param name the name
|
* @param name the name
|
||||||
* @param radiusInDp the circle's radius
|
* @param radiusInDp the circle's radius
|
||||||
* @return the avatar as a TextDrawable
|
* @return the avatar as a TextDrawable
|
||||||
* @throws NoSuchAlgorithmException if the specified algorithm is not available when calculating the color values
|
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@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);
|
BitmapUtils.Color color = BitmapUtils.usernameToColor(name);
|
||||||
return new TextDrawable(extractCharsFromDisplayName(name), color, radiusInDp);
|
return new TextDrawable(extractCharsFromDisplayName(name), color, radiusInDp);
|
||||||
}
|
}
|
||||||
|
@ -160,6 +159,11 @@ public class TextDrawable extends Drawable {
|
||||||
@Override
|
@Override
|
||||||
public void draw(@NonNull Canvas canvas) {
|
public void draw(@NonNull Canvas canvas) {
|
||||||
canvas.drawCircle(mRadius, mRadius, mRadius, mBackground);
|
canvas.drawCircle(mRadius, mRadius, mRadius, mBackground);
|
||||||
|
|
||||||
|
if (bigText) {
|
||||||
|
mTextPaint.setTextSize(1.8f * mRadius);
|
||||||
|
}
|
||||||
|
|
||||||
canvas.drawText(mText, mRadius, mRadius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2), mTextPaint);
|
canvas.drawText(mText, mRadius, mRadius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2), mTextPaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,8 +31,6 @@ import com.owncloud.android.databinding.FileDetailsShareShareItemBinding;
|
||||||
import com.owncloud.android.lib.resources.shares.OCShare;
|
import com.owncloud.android.lib.resources.shares.OCShare;
|
||||||
import com.owncloud.android.ui.TextDrawable;
|
import com.owncloud.android.ui.TextDrawable;
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes;
|
import androidx.annotation.DrawableRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
@ -95,7 +93,7 @@ class ShareViewHolder extends RecyclerView.ViewHolder {
|
||||||
private void setImage(ImageView avatar, String name, @DrawableRes int fallback) {
|
private void setImage(ImageView avatar, String name, @DrawableRes int fallback) {
|
||||||
try {
|
try {
|
||||||
avatar.setImageDrawable(TextDrawable.createNamedAvatar(name, avatarRadiusDimension));
|
avatar.setImageDrawable(TextDrawable.createNamedAvatar(name, avatarRadiusDimension));
|
||||||
} catch (NoSuchAlgorithmException | StringIndexOutOfBoundsException e) {
|
} catch (StringIndexOutOfBoundsException e) {
|
||||||
avatar.setImageResource(fallback);
|
avatar.setImageResource(fallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -76,6 +76,7 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.appcompat.widget.PopupMenu;
|
import androidx.appcompat.widget.PopupMenu;
|
||||||
|
import androidx.appcompat.widget.SearchView;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
|
@ -713,4 +714,10 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
|
||||||
private boolean canReshare(OCShare share) {
|
private boolean canReshare(OCShare share) {
|
||||||
return (share.getPermissions() & SHARE_PERMISSION_FLAG) > 0;
|
return (share.getPermissions() & SHARE_PERMISSION_FLAG) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void search(String query) {
|
||||||
|
SearchView searchView = getView().findViewById(R.id.searchView);
|
||||||
|
searchView.setQuery(query, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,19 +18,27 @@
|
||||||
*/
|
*/
|
||||||
package com.owncloud.android.utils;
|
package com.owncloud.android.utils;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.BitmapFactory.Options;
|
import android.graphics.BitmapFactory.Options;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Matrix;
|
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.BitmapDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import com.nextcloud.client.account.StatusType;
|
||||||
import com.owncloud.android.MainApp;
|
import com.owncloud.android.MainApp;
|
||||||
import com.owncloud.android.R;
|
import com.owncloud.android.R;
|
||||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||||
|
import com.owncloud.android.ui.StatusDrawable;
|
||||||
|
|
||||||
import org.apache.commons.codec.binary.Hex;
|
import org.apache.commons.codec.binary.Hex;
|
||||||
|
|
||||||
|
@ -194,12 +202,19 @@ public final class BitmapUtils {
|
||||||
return resultBitmap;
|
return resultBitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Color usernameToColor(String name) throws NoSuchAlgorithmException {
|
public static Color usernameToColor(String name) {
|
||||||
String hash = name.toLowerCase(Locale.ROOT);
|
String hash = name.toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
// already a md5 hash?
|
// already a md5 hash?
|
||||||
if (!hash.matches("([0-9a-f]{4}-?){8}$")) {
|
if (!hash.matches("([0-9a-f]{4}-?){8}$")) {
|
||||||
|
try {
|
||||||
hash = md5(hash);
|
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]", "");
|
hash = hash.replaceAll("[^0-9a-f]", "");
|
||||||
|
@ -279,6 +294,7 @@ public final class BitmapUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Color {
|
public static class Color {
|
||||||
|
public int a = 255;
|
||||||
public int r;
|
public int r;
|
||||||
public int g;
|
public int g;
|
||||||
public int b;
|
public int b;
|
||||||
|
@ -289,6 +305,13 @@ public final class BitmapUtils {
|
||||||
this.b = b;
|
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
|
@Override
|
||||||
public boolean equals(@Nullable Object obj) {
|
public boolean equals(@Nullable Object obj) {
|
||||||
if (!(obj instanceof Color)) {
|
if (!(obj instanceof Color)) {
|
||||||
|
@ -358,9 +381,16 @@ public final class BitmapUtils {
|
||||||
|
|
||||||
Bitmap bitmap;
|
Bitmap bitmap;
|
||||||
if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
|
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 {
|
} else {
|
||||||
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(),
|
bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
|
||||||
|
drawable.getIntrinsicHeight(),
|
||||||
Bitmap.Config.ARGB_8888);
|
Bitmap.Config.ARGB_8888);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,6 +414,90 @@ public final class BitmapUtils {
|
||||||
imageView);
|
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() {
|
private static Resources getResources() {
|
||||||
return MainApp.getAppContext().getResources();
|
return MainApp.getAppContext().getResources();
|
||||||
}
|
}
|
||||||
|
|
11
src/main/res/drawable/ic_talk.xml
Normal file
11
src/main/res/drawable/ic_talk.xml
Normal file
|
@ -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
src/main/res/drawable/ic_user_status_away.xml
Normal file
32
src/main/res/drawable/ic_user_status_away.xml
Normal file
|
@ -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
src/main/res/drawable/ic_user_status_dnd.xml
Normal file
38
src/main/res/drawable/ic_user_status_dnd.xml
Normal file
|
@ -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>
|
|
@ -479,7 +479,7 @@
|
||||||
<string name="share_remote_clarification">%1$s (remote)</string>
|
<string name="share_remote_clarification">%1$s (remote)</string>
|
||||||
<string name="share_email_clarification">%1$s (email)</string>
|
<string name="share_email_clarification">%1$s (email)</string>
|
||||||
<string name="share_room_clarification">%1$s (conversation)</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>
|
<string name="share_privilege_unshare">Unshare</string>
|
||||||
|
|
||||||
|
@ -936,4 +936,5 @@
|
||||||
<string name="link_share_file_drop">File drop (upload only)</string>
|
<string name="link_share_file_drop">File drop (upload only)</string>
|
||||||
<string name="could_not_retrieve_shares">Could not retrieve shares</string>
|
<string name="could_not_retrieve_shares">Could not retrieve shares</string>
|
||||||
<string name="failed_update_ui">Failed to update UI</string>
|
<string name="failed_update_ui">Failed to update UI</string>
|
||||||
|
<string name="remote">(remote)</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue