Add avatar picker and defaults.

fork-5.53.8
Alex Hart 2021-07-20 13:08:52 -03:00 zatwierdzone przez Greyson Parrelli
rodzic 0093e1d3eb
commit ed23c3fe7c
133 zmienionych plików z 4935 dodań i 859 usunięć

Wyświetl plik

@ -368,7 +368,7 @@ android {
dependencies {
implementation 'androidx.core:core-ktx:1.5.0'
implementation 'androidx.fragment:fragment-ktx:1.2.5'
implementation 'androidx.fragment:fragment-ktx:1.3.5'
lintChecks project(':lintchecks')
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -35,6 +35,7 @@ import org.signal.core.util.logging.Log;
import org.signal.core.util.tracing.Tracer;
import org.signal.glide.SignalGlideCodecs;
import org.signal.ringrtc.CallManager;
import org.thoughtcrime.securesms.avatar.AvatarPickerStorage;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.SqlCipherLibraryLoader;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
@ -152,6 +153,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
})
.addBlocking("blob-provider", this::initializeBlobProvider)
.addBlocking("feature-flags", FeatureFlags::init)
.addNonBlocking(this::cleanAvatarStorage)
.addNonBlocking(this::initializeRevealableMessageManager)
.addNonBlocking(this::initializePendingRetryReceiptManager)
.addNonBlocking(this::initializeGcmCheck)
@ -375,6 +377,11 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
BlobProvider.getInstance().initialize(this);
}
@WorkerThread
private void cleanAvatarStorage() {
AvatarPickerStorage.cleanOrphans(this);
}
@WorkerThread
private void initializeCleanup() {
int deleted = DatabaseFactory.getAttachmentDatabase(this).deleteAbandonedPreuploadedAttachments();

Wyświetl plik

@ -150,6 +150,7 @@ public class DeviceActivity extends PassphraseRequiredActivity
});
}
@SuppressLint("MissingSuperCall")
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);

Wyświetl plik

@ -171,6 +171,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
initializeObservers();
}
@SuppressLint("MissingSuperCall")
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);

Wyświetl plik

@ -208,6 +208,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement
.execute();
}
@SuppressLint("MissingSuperCall")
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);

Wyświetl plik

@ -18,6 +18,7 @@
package org.thoughtcrime.securesms;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.PictureInPictureParams;
import android.content.Context;
import android.content.Intent;
@ -180,6 +181,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
EventBus.getDefault().unregister(this);
}
@SuppressLint("MissingSuperCall")
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);

Wyświetl plik

@ -0,0 +1,79 @@
package org.thoughtcrime.securesms.avatar
import android.net.Uri
import org.thoughtcrime.securesms.R
/**
* Represents an Avatar which the user can choose, edit, and render into a bitmap via the renderer.
*/
sealed class Avatar(
open val databaseId: DatabaseId
) {
data class Resource(
val resourceId: Int,
val color: Avatars.ColorPair
) : Avatar(DatabaseId.DoNotPersist) {
override fun isSameAs(other: Avatar): Boolean {
return other is Resource && other.resourceId == resourceId
}
}
data class Text(
val text: String,
val color: Avatars.ColorPair,
override val databaseId: DatabaseId,
) : Avatar(databaseId) {
override fun withDatabaseId(databaseId: DatabaseId): Avatar {
return copy(databaseId = databaseId)
}
override fun isSameAs(other: Avatar): Boolean {
return other is Text && other.databaseId == databaseId
}
}
data class Vector(
val key: String,
val color: Avatars.ColorPair,
override val databaseId: DatabaseId,
) : Avatar(databaseId) {
override fun withDatabaseId(databaseId: DatabaseId): Avatar {
return copy(databaseId = databaseId)
}
override fun isSameAs(other: Avatar): Boolean {
return other is Vector && other.key == key
}
}
data class Photo(
val uri: Uri,
val size: Long,
override val databaseId: DatabaseId
) : Avatar(databaseId) {
override fun withDatabaseId(databaseId: DatabaseId): Avatar {
return copy(databaseId = databaseId)
}
override fun isSameAs(other: Avatar): Boolean {
return other is Photo && databaseId == other.databaseId
}
}
open fun withDatabaseId(databaseId: DatabaseId): Avatar {
throw UnsupportedOperationException()
}
abstract fun isSameAs(other: Avatar): Boolean
companion object {
fun getDefaultForSelf(): Resource = Resource(R.drawable.ic_profile_outline_40, Avatars.colors.random())
fun getDefaultForGroup(): Resource = Resource(R.drawable.ic_group_outline_40, Avatars.colors.random())
}
sealed class DatabaseId {
object DoNotPersist : DatabaseId()
object NotSet : DatabaseId()
data class Saved(val id: Long) : DatabaseId()
}
}

Wyświetl plik

@ -0,0 +1,69 @@
package org.thoughtcrime.securesms.avatar
import android.os.Bundle
import java.lang.IllegalStateException
/**
* Utility class which encapsulates reading and writing Avatar objects to and from Bundles.
*/
object AvatarBundler {
private const val TEXT = "org.thoughtcrime.securesms.avatar.TEXT"
private const val COLOR = "org.thoughtcrime.securesms.avatar.COLOR"
private const val URI = "org.thoughtcrime.securesms.avatar.URI"
private const val KEY = "org.thoughtcrime.securesms.avatar.KEY"
private const val DATABASE_ID = "org.thoughtcrime.securesms.avatar.DATABASE_ID"
private const val SIZE = "org.thoughtcrime.securesms.avatar.SIZE"
fun bundleText(text: Avatar.Text): Bundle = Bundle().apply {
putString(TEXT, text.text)
putString(COLOR, text.color.code)
putDatabaseId(DATABASE_ID, text.databaseId)
}
fun extractText(bundle: Bundle): Avatar.Text = Avatar.Text(
text = requireNotNull(bundle.getString(TEXT)),
color = Avatars.colorMap[bundle.getString(COLOR)] ?: throw IllegalStateException(),
databaseId = bundle.getDatabaseId()
)
fun bundlePhoto(photo: Avatar.Photo): Bundle = Bundle().apply {
putParcelable(URI, photo.uri)
putLong(SIZE, photo.size)
putDatabaseId(DATABASE_ID, photo.databaseId)
}
fun extractPhoto(bundle: Bundle): Avatar.Photo = Avatar.Photo(
uri = requireNotNull(bundle.getParcelable(URI)),
size = bundle.getLong(SIZE),
databaseId = bundle.getDatabaseId()
)
fun bundleVector(vector: Avatar.Vector): Bundle = Bundle().apply {
putString(KEY, vector.key)
putString(COLOR, vector.color.code)
putDatabaseId(DATABASE_ID, vector.databaseId)
}
fun extractVector(bundle: Bundle): Avatar.Vector = Avatar.Vector(
key = requireNotNull(bundle.getString(KEY)),
color = Avatars.colorMap[bundle.getString(COLOR)] ?: throw IllegalStateException(),
databaseId = bundle.getDatabaseId()
)
private fun Bundle.getDatabaseId(): Avatar.DatabaseId {
val id = getLong(DATABASE_ID, -1L)
return if (id == -1L) {
Avatar.DatabaseId.NotSet
} else {
Avatar.DatabaseId.Saved(id)
}
}
private fun Bundle.putDatabaseId(key: String, databaseId: Avatar.DatabaseId) {
if (databaseId is Avatar.DatabaseId.Saved) {
putLong(key, databaseId.id)
}
}
}

Wyświetl plik

@ -0,0 +1,42 @@
package org.thoughtcrime.securesms.avatar
import android.view.View
import android.widget.ImageView
import com.airbnb.lottie.SimpleColorFilter
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.util.MappingAdapter
import org.thoughtcrime.securesms.util.MappingModel
import org.thoughtcrime.securesms.util.MappingViewHolder
typealias OnAvatarColorClickListener = (Avatars.ColorPair) -> Unit
/**
* Selectable color item for choosing colors when editing a Text or Vector avatar.
*/
data class AvatarColorItem(
val colors: Avatars.ColorPair,
val selected: Boolean
) {
companion object {
fun registerViewHolder(adapter: MappingAdapter, onAvatarColorClickListener: OnAvatarColorClickListener) {
adapter.registerFactory(Model::class.java, MappingAdapter.LayoutFactory({ ViewHolder(it, onAvatarColorClickListener) }, R.layout.avatar_color_item))
}
}
class Model(val colorItem: AvatarColorItem) : MappingModel<Model> {
override fun areItemsTheSame(newItem: Model): Boolean = newItem.colorItem.colors == colorItem.colors
override fun areContentsTheSame(newItem: Model): Boolean = newItem.colorItem == colorItem
}
private class ViewHolder(itemView: View, private val onAvatarColorClickListener: OnAvatarColorClickListener) : MappingViewHolder<Model>(itemView) {
private val imageView: ImageView = findViewById(R.id.avatar_color_item)
override fun bind(model: Model) {
itemView.setOnClickListener { onAvatarColorClickListener(model.colorItem.colors) }
imageView.background.colorFilter = SimpleColorFilter(model.colorItem.colors.backgroundColor)
imageView.isSelected = model.colorItem.selected
}
}
}

Wyświetl plik

@ -0,0 +1,55 @@
package org.thoughtcrime.securesms.avatar
import android.content.Context
import android.net.Uri
import android.webkit.MimeTypeMap
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mms.PartAuthority
import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.storage.FileStorage
import java.io.InputStream
object AvatarPickerStorage {
private const val DIRECTORY = "avatar_picker"
private const val FILENAME_BASE = "avatar"
@JvmStatic
fun read(context: Context, fileName: String) = FileStorage.read(context, DIRECTORY, fileName)
fun save(context: Context, media: Media): Uri {
val fileName = FileStorage.save(context, PartAuthority.getAttachmentStream(context, media.uri), DIRECTORY, FILENAME_BASE, MediaUtil.getExtension(context, media.uri) ?: "")
return PartAuthority.getAvatarPickerUri(fileName)
}
fun save(context: Context, inputStream: InputStream): Uri {
val fileName = FileStorage.save(context, inputStream, DIRECTORY, FILENAME_BASE, MimeTypeMap.getSingleton().getExtensionFromMimeType(MediaUtil.IMAGE_JPEG) ?: "")
return PartAuthority.getAvatarPickerUri(fileName)
}
@JvmStatic
fun cleanOrphans(context: Context) {
val avatarFiles = FileStorage.getAllFiles(context, DIRECTORY, FILENAME_BASE)
val database = DatabaseFactory.getAvatarPickerDatabase(context)
val photoAvatars = database
.getAllAvatars()
.filterIsInstance<Avatar.Photo>()
val inDatabaseFileNames = photoAvatars.map { PartAuthority.getAvatarPickerFilename(it.uri) }
val onDiskFileNames = avatarFiles.map { it.name }
val inDatabaseButNotOnDisk = inDatabaseFileNames - onDiskFileNames
val onDiskButNotInDatabase = onDiskFileNames - inDatabaseFileNames
avatarFiles
.filter { onDiskButNotInDatabase.contains(it.name) }
.forEach { it.delete() }
photoAvatars
.filter { inDatabaseButNotOnDisk.contains(PartAuthority.getAvatarPickerFilename(it.uri)) }
.forEach { database.deleteAvatar(it) }
}
}

Wyświetl plik

@ -0,0 +1,156 @@
package org.thoughtcrime.securesms.avatar
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.net.Uri
import androidx.appcompat.content.res.AppCompatResources
import com.airbnb.lottie.SimpleColorFilter
import com.amulyakhare.textdrawable.TextDrawable
import org.signal.core.util.concurrent.SignalExecutors
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mms.PartAuthority
import org.thoughtcrime.securesms.profiles.AvatarHelper
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.util.MediaUtil
import org.whispersystems.libsignal.util.guava.Optional
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import javax.annotation.meta.Exhaustive
/**
* Renders Avatar objects into Media objects. This can involve creating a Bitmap, depending on the
* type of Avatar passed to `renderAvatar`
*/
object AvatarRenderer {
private val DIMENSIONS = AvatarHelper.AVATAR_DIMENSIONS
fun getTypeface(context: Context): Typeface {
return Typeface.createFromAsset(context.assets, "fonts/Inter-Medium.otf")
}
fun renderAvatar(context: Context, avatar: Avatar, onAvatarRendered: (Media) -> Unit, onRenderFailed: (Throwable?) -> Unit) {
@Exhaustive
when (avatar) {
is Avatar.Resource -> renderResource(context, avatar, onAvatarRendered, onRenderFailed)
is Avatar.Vector -> renderVector(context, avatar, onAvatarRendered, onRenderFailed)
is Avatar.Photo -> renderPhoto(context, avatar, onAvatarRendered)
is Avatar.Text -> renderText(context, avatar, onAvatarRendered, onRenderFailed)
}
}
@JvmStatic
fun createTextDrawable(
context: Context,
avatar: Avatar.Text,
inverted: Boolean = false,
size: Int = DIMENSIONS,
isRect: Boolean = true
): Drawable {
val typeface = getTypeface(context)
val color: Int = if (inverted) {
avatar.color.backgroundColor
} else {
avatar.color.foregroundColor
}
val builder = TextDrawable
.builder()
.beginConfig()
.fontSize(Avatars.getTextSizeForLength(context, avatar.text, size * 0.8f, size * 0.45f).toInt())
.textColor(color)
.useFont(typeface)
.width(size)
.height(size)
.endConfig()
return if (isRect) {
builder.buildRect(avatar.text, Color.TRANSPARENT)
} else {
builder.buildRound(avatar.text, Color.TRANSPARENT)
}
}
private fun renderVector(context: Context, avatar: Avatar.Vector, onAvatarRendered: (Media) -> Unit, onRenderFailed: (Throwable?) -> Unit) {
renderInBackground(context, onAvatarRendered, onRenderFailed) { canvas ->
val drawableResourceId = Avatars.getDrawableResource(avatar.key) ?: return@renderInBackground Result.failure(Exception("Drawable resource for key ${avatar.key} does not exist."))
val vector: Drawable = requireNotNull(AppCompatResources.getDrawable(context, drawableResourceId))
vector.setBounds(0, 0, DIMENSIONS, DIMENSIONS)
canvas.drawColor(avatar.color.backgroundColor)
vector.draw(canvas)
Result.success(Unit)
}
}
private fun renderText(context: Context, avatar: Avatar.Text, onAvatarRendered: (Media) -> Unit, onRenderFailed: (Throwable?) -> Unit) {
renderInBackground(context, onAvatarRendered, onRenderFailed) { canvas ->
val textDrawable = createTextDrawable(context, avatar)
canvas.drawColor(avatar.color.backgroundColor)
textDrawable.draw(canvas)
Result.success(Unit)
}
}
private fun renderPhoto(context: Context, avatar: Avatar.Photo, onAvatarRendered: (Media) -> Unit) {
SignalExecutors.BOUNDED.execute {
val blob = BlobProvider.getInstance()
.forData(AvatarPickerStorage.read(context, PartAuthority.getAvatarPickerFilename(avatar.uri)), avatar.size)
.createForSingleSessionOnDisk(context)
onAvatarRendered(createMedia(blob, avatar.size))
}
}
private fun renderResource(context: Context, avatar: Avatar.Resource, onAvatarRendered: (Media) -> Unit, onRenderFailed: (Throwable?) -> Unit) {
renderInBackground(context, onAvatarRendered, onRenderFailed) { canvas ->
val resource: Drawable = requireNotNull(AppCompatResources.getDrawable(context, avatar.resourceId))
resource.colorFilter = SimpleColorFilter(avatar.color.foregroundColor)
val padding = (DIMENSIONS * 0.2).toInt()
resource.setBounds(0 + padding, 0 + padding, DIMENSIONS - padding, DIMENSIONS - padding)
canvas.drawColor(avatar.color.backgroundColor)
resource.draw(canvas)
Result.success(Unit)
}
}
private fun renderInBackground(context: Context, onAvatarRendered: (Media) -> Unit, onRenderFailed: (Throwable?) -> Unit, drawAvatar: (Canvas) -> Result<Unit>) {
SignalExecutors.BOUNDED.execute {
val canvasBitmap = Bitmap.createBitmap(DIMENSIONS, DIMENSIONS, Bitmap.Config.ARGB_8888)
val canvas = Canvas(canvasBitmap)
val drawResult = drawAvatar(canvas)
if (drawResult.isFailure) {
canvasBitmap.recycle()
onRenderFailed(drawResult.exceptionOrNull())
}
val outStream = ByteArrayOutputStream()
val compressed = canvasBitmap.compress(Bitmap.CompressFormat.JPEG, 80, outStream)
canvasBitmap.recycle()
if (!compressed) {
onRenderFailed(IOException("Failed to compress bitmap"))
return@execute
}
val bytes = outStream.toByteArray()
val inStream = ByteArrayInputStream(bytes)
val uri = BlobProvider.getInstance().forData(inStream, bytes.size.toLong()).createForSingleSessionOnDisk(context)
onAvatarRendered(createMedia(uri, bytes.size.toLong()))
}
}
private fun createMedia(uri: Uri, size: Long): Media {
return Media(uri, MediaUtil.IMAGE_JPEG, System.currentTimeMillis(), DIMENSIONS, DIMENSIONS, size, 0, false, false, Optional.absent(), Optional.absent(), Optional.absent())
}
}

Wyświetl plik

@ -0,0 +1,153 @@
package org.thoughtcrime.securesms.avatar
import android.content.Context
import android.graphics.Paint
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import androidx.annotation.Px
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import kotlin.math.abs
import kotlin.math.min
object Avatars {
/**
* Enum class mirroring AvatarColors codes but utilizing foreground colors for text or icon tinting.
*/
enum class ForegroundColor(private val code: String, @ColorInt val colorInt: Int) {
A100("A100", 0xFF3838F5.toInt()),
A110("A110", 0xFF1251D3.toInt()),
A120("A120", 0xFF086DA0.toInt()),
A130("A130", 0xFF067906.toInt()),
A140("A140", 0xFF661AFF.toInt()),
A150("A150", 0xFF9F00F0.toInt()),
A160("A160", 0xFFB8057C.toInt()),
A170("A170", 0xFFBE0404.toInt()),
A180("A180", 0xFF836B01.toInt()),
A190("A190", 0xFF7D6F40.toInt()),
A200("A200", 0xFF4F4F6D.toInt()),
A210("A210", 0xFF5C5C5C.toInt());
fun deserialize(code: String): ForegroundColor {
return values().find { it.code == code } ?: throw IllegalArgumentException()
}
fun serialize(): String = code
}
/**
* Mapping which associates color codes to ColorPair objects containing background and foreground colors.
*/
val colorMap: Map<String, ColorPair> = ForegroundColor.values().map {
ColorPair(AvatarColor.deserialize(it.serialize()), it)
}.associateBy {
it.code
}
val colors: List<ColorPair> = colorMap.values.toList()
val defaultAvatarsForSelf = linkedMapOf(
"avatar_abstract_01" to DefaultAvatar(R.drawable.ic_avatar_abstract_01, "A130"),
"avatar_abstract_02" to DefaultAvatar(R.drawable.ic_avatar_abstract_02, "A120"),
"avatar_abstract_03" to DefaultAvatar(R.drawable.ic_avatar_abstract_03, "A170"),
"avatar_cat" to DefaultAvatar(R.drawable.ic_avatar_cat, "A190"),
"avatar_dog" to DefaultAvatar(R.drawable.ic_avatar_dog, "A140"),
"avatar_fox" to DefaultAvatar(R.drawable.ic_avatar_fox, "A190"),
"avatar_tucan" to DefaultAvatar(R.drawable.ic_avatar_tucan, "A120"),
"avatar_sloth" to DefaultAvatar(R.drawable.ic_avatar_sloth, "A160"),
"avatar_dinosaur" to DefaultAvatar(R.drawable.ic_avatar_dinosour, "A130"),
"avatar_pig" to DefaultAvatar(R.drawable.ic_avatar_pig, "A180"),
"avatar_incognito" to DefaultAvatar(R.drawable.ic_avatar_incognito, "A220"),
"avatar_ghost" to DefaultAvatar(R.drawable.ic_avatar_ghost, "A100")
)
val defaultAvatarsForGroup = linkedMapOf(
"avatar_heart" to DefaultAvatar(R.drawable.ic_avatar_heart, "A180"),
"avatar_house" to DefaultAvatar(R.drawable.ic_avatar_house, "A120"),
"avatar_melon" to DefaultAvatar(R.drawable.ic_avatar_melon, "A110"),
"avatar_drink" to DefaultAvatar(R.drawable.ic_avatar_drink, "A170"),
"avatar_celebration" to DefaultAvatar(R.drawable.ic_avatar_celebration, "A100"),
"avatar_balloon" to DefaultAvatar(R.drawable.ic_avatar_balloon, "A220"),
"avatar_book" to DefaultAvatar(R.drawable.ic_avatar_book, "A100"),
"avatar_briefcase" to DefaultAvatar(R.drawable.ic_avatar_briefcase, "A180"),
"avatar_sunset" to DefaultAvatar(R.drawable.ic_avatar_sunset, "A120"),
"avatar_surfboard" to DefaultAvatar(R.drawable.ic_avatar_surfboard, "A110"),
"avatar_soccerball" to DefaultAvatar(R.drawable.ic_avatar_soccerball, "A130"),
"avatar_football" to DefaultAvatar(R.drawable.ic_avatar_football, "A220"),
)
@DrawableRes
fun getDrawableResource(key: String): Int? {
val defaultAvatar = defaultAvatarsForSelf.getOrDefault(key, defaultAvatarsForGroup[key])
return defaultAvatar?.vectorDrawableId
}
private fun textPaint(context: Context) = Paint().apply {
isAntiAlias = true
typeface = AvatarRenderer.getTypeface(context)
textSize = 1f
}
/**
* Calculate the text size for a give string using a maximum desired width and a maximum desired font size.
*/
@JvmStatic
fun getTextSizeForLength(context: Context, text: String, @Px maxWidth: Float, @Px maxSize: Float): Float {
val paint = textPaint(context)
return branchSizes(0f, maxWidth / 2, maxWidth, maxSize, paint, text)
}
/**
* Uses binary search to determine optimal font size to within 1% given the input parameters.
*/
private fun branchSizes(@Px lastFontSize: Float, @Px fontSize: Float, @Px target: Float, @Px maxFontSize: Float, paint: Paint, text: String): Float {
paint.textSize = fontSize
val textWidth = paint.measureText(text)
val delta = abs(lastFontSize - fontSize) / 2f
val isWithinThreshold = abs(1f - (textWidth / target)) <= 0.01f
if (textWidth == 0f) {
return maxFontSize
}
if (delta == 0f) {
return min(maxFontSize, fontSize)
}
return when {
fontSize >= maxFontSize -> {
maxFontSize
}
isWithinThreshold -> {
fontSize
}
textWidth > target -> {
branchSizes(fontSize, fontSize - delta, target, maxFontSize, paint, text)
}
else -> {
branchSizes(fontSize, fontSize + delta, target, maxFontSize, paint, text)
}
}
}
@JvmStatic
fun getForegroundColor(avatarColor: AvatarColor): ForegroundColor {
return ForegroundColor.values().firstOrNull { it.serialize() == avatarColor.serialize() } ?: ForegroundColor.A210
}
data class DefaultAvatar(
@DrawableRes val vectorDrawableId: Int,
val colorCode: String
)
data class ColorPair(
val backgroundAvatarColor: AvatarColor,
val foregroundAvatarColor: ForegroundColor
) {
@ColorInt val backgroundColor: Int = backgroundAvatarColor.colorInt()
@ColorInt val foregroundColor: Int = foregroundAvatarColor.colorInt
val code: String = backgroundAvatarColor.serialize()
}
}

Wyświetl plik

@ -0,0 +1,65 @@
package org.thoughtcrime.securesms.avatar.photo
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.commit
import androidx.fragment.app.setFragmentResult
import androidx.navigation.Navigation
import org.signal.core.util.ThreadUtil
import org.signal.core.util.concurrent.SignalExecutors
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.avatar.AvatarBundler
import org.thoughtcrime.securesms.avatar.AvatarPickerStorage
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.scribbles.ImageEditorFragment
class PhotoEditorFragment : Fragment(R.layout.avatar_photo_editor_fragment), ImageEditorFragment.Controller {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val args = PhotoEditorFragmentArgs.fromBundle(requireArguments())
val photo = AvatarBundler.extractPhoto(args.photoAvatar)
val imageEditorFragment = ImageEditorFragment.newInstanceForAvatarEdit(photo.uri)
childFragmentManager.commit {
add(R.id.fragment_container, imageEditorFragment, IMAGE_EDITOR)
}
}
override fun onTouchEventsNeeded(needed: Boolean) {
}
override fun onRequestFullScreen(fullScreen: Boolean, hideKeyboard: Boolean) {
}
override fun onDoneEditing() {
val args = PhotoEditorFragmentArgs.fromBundle(requireArguments())
val applicationContext = requireContext().applicationContext
val imageEditorFragment: ImageEditorFragment = childFragmentManager.findFragmentByTag(IMAGE_EDITOR) as ImageEditorFragment
SignalExecutors.BOUNDED.execute {
val editedImageUri = imageEditorFragment.renderToSingleUseBlob()
val size = BlobProvider.getFileSize(editedImageUri) ?: 0
val inputStream = BlobProvider.getInstance().getStream(applicationContext, editedImageUri)
val onDiskUri = AvatarPickerStorage.save(applicationContext, inputStream)
val photo = AvatarBundler.extractPhoto(args.photoAvatar)
val database = DatabaseFactory.getAvatarPickerDatabase(applicationContext)
val newPhoto = photo.copy(uri = onDiskUri, size = size)
database.update(newPhoto)
BlobProvider.getInstance().delete(requireContext(), photo.uri)
ThreadUtil.runOnMain {
setFragmentResult(REQUEST_KEY_EDIT, AvatarBundler.bundlePhoto(newPhoto))
Navigation.findNavController(requireView()).popBackStack()
}
}
}
companion object {
const val REQUEST_KEY_EDIT = "org.thoughtcrime.securesms.avatar.photo.EDIT"
private const val IMAGE_EDITOR = "image_editor"
}
}

Wyświetl plik

@ -0,0 +1,223 @@
package org.thoughtcrime.securesms.avatar.picker
import android.Manifest
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.Gravity
import android.view.View
import android.widget.PopupMenu
import android.widget.Toast
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResult
import androidx.fragment.app.setFragmentResultListener
import androidx.fragment.app.viewModels
import androidx.navigation.Navigation
import androidx.recyclerview.widget.RecyclerView
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.avatar.Avatar
import org.thoughtcrime.securesms.avatar.AvatarBundler
import org.thoughtcrime.securesms.avatar.photo.PhotoEditorFragment
import org.thoughtcrime.securesms.avatar.text.TextAvatarCreationFragment
import org.thoughtcrime.securesms.avatar.vector.VectorAvatarCreationFragment
import org.thoughtcrime.securesms.components.ButtonStripItemView
import org.thoughtcrime.securesms.components.recyclerview.GridDividerDecoration
import org.thoughtcrime.securesms.groups.ParcelableGroupId
import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.util.MappingAdapter
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.visible
import java.util.Objects
/**
* Primary Avatar picker fragment, displays current user avatar and a list of recently used avatars and defaults.
*/
class AvatarPickerFragment : Fragment(R.layout.avatar_picker_fragment) {
companion object {
const val REQUEST_KEY_SELECT_AVATAR = "org.thoughtcrime.securesms.avatar.picker.SELECT_AVATAR"
const val SELECT_AVATAR_MEDIA = "org.thoughtcrime.securesms.avatar.picker.SELECT_AVATAR_MEDIA"
private const val REQUEST_CODE_SELECT_IMAGE = 1
}
private val viewModel: AvatarPickerViewModel by viewModels(factoryProducer = this::createFactory)
private fun createFactory(): AvatarPickerViewModel.Factory {
val args = AvatarPickerFragmentArgs.fromBundle(requireArguments())
val groupId = ParcelableGroupId.get(args.groupId)
return AvatarPickerViewModel.Factory(AvatarPickerRepository(requireContext()), groupId, args.isNewGroup, args.groupAvatarMedia)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val toolbar: Toolbar = view.findViewById(R.id.avatar_picker_toolbar)
val recycler: RecyclerView = view.findViewById(R.id.avatar_picker_recycler)
val cameraButton: ButtonStripItemView = view.findViewById(R.id.avatar_picker_camera)
val photoButton: ButtonStripItemView = view.findViewById(R.id.avatar_picker_photo)
val textButton: ButtonStripItemView = view.findViewById(R.id.avatar_picker_text)
val saveButton: View = view.findViewById(R.id.avatar_picker_save)
val clearButton: View = view.findViewById(R.id.avatar_picker_clear)
recycler.addItemDecoration(GridDividerDecoration(4, ViewUtil.dpToPx(16)))
val adapter = MappingAdapter()
AvatarPickerItem.register(adapter, this::onAvatarClick, this::onAvatarLongClick)
recycler.adapter = adapter
val avatarViewHolder = AvatarPickerItem.ViewHolder(view)
viewModel.state.observe(viewLifecycleOwner) { state ->
if (state.currentAvatar != null) {
avatarViewHolder.bind(AvatarPickerItem.Model(state.currentAvatar, false))
}
clearButton.visible = state.canClear
val wasEnabled = saveButton.isEnabled
saveButton.isEnabled = state.canSave
if (wasEnabled != state.canSave) {
val alpha = if (state.canSave) 1f else 0.5f
saveButton.animate().cancel()
saveButton.animate().alpha(alpha)
}
adapter.submitList(state.selectableAvatars.map { AvatarPickerItem.Model(it, it == state.currentAvatar) })
}
toolbar.setNavigationOnClickListener { Navigation.findNavController(it).popBackStack() }
cameraButton.setOnIconClickedListener { openCameraCapture() }
photoButton.setOnIconClickedListener { openGallery() }
textButton.setOnIconClickedListener { openTextEditor(null) }
saveButton.setOnClickListener { v ->
viewModel.save {
setFragmentResult(
REQUEST_KEY_SELECT_AVATAR,
Bundle().apply {
putParcelable(SELECT_AVATAR_MEDIA, it)
}
)
Navigation.findNavController(v).popBackStack()
}
}
clearButton.setOnClickListener { viewModel.clear() }
setFragmentResultListener(TextAvatarCreationFragment.REQUEST_KEY_TEXT) { _, bundle ->
val text = AvatarBundler.extractText(bundle)
viewModel.onAvatarEditCompleted(text)
}
setFragmentResultListener(VectorAvatarCreationFragment.REQUEST_KEY_VECTOR) { _, bundle ->
val vector = AvatarBundler.extractVector(bundle)
viewModel.onAvatarEditCompleted(vector)
}
setFragmentResultListener(PhotoEditorFragment.REQUEST_KEY_EDIT) { _, bundle ->
val photo = AvatarBundler.extractPhoto(bundle)
viewModel.onAvatarEditCompleted(photo)
}
}
override fun onResume() {
super.onResume()
ViewUtil.hideKeyboard(requireContext(), requireView())
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_SELECT_IMAGE && resultCode == Activity.RESULT_OK && data != null) {
val media: Media = Objects.requireNonNull(data.getParcelableExtra(AvatarSelectionActivity.EXTRA_MEDIA))
viewModel.onAvatarPhotoSelectionCompleted(media)
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
private fun onAvatarClick(avatar: Avatar, isSelected: Boolean) {
if (isSelected) {
openEditor(avatar)
} else {
viewModel.onAvatarSelectedFromGrid(avatar)
}
}
private fun onAvatarLongClick(anchorView: View, avatar: Avatar): Boolean {
val menuRes = when (avatar) {
is Avatar.Photo -> R.menu.avatar_picker_context
is Avatar.Text -> R.menu.avatar_picker_context
is Avatar.Vector -> return false
is Avatar.Resource -> return false
}
val popup = PopupMenu(context, anchorView, Gravity.TOP)
popup.menuInflater.inflate(menuRes, popup.menu)
popup.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.action_delete -> viewModel.delete(avatar)
}
true
}
popup.show()
return true
}
fun openEditor(avatar: Avatar) {
when (avatar) {
is Avatar.Photo -> openPhotoEditor(avatar)
is Avatar.Resource -> throw UnsupportedOperationException()
is Avatar.Text -> openTextEditor(avatar)
is Avatar.Vector -> openVectorEditor(avatar)
}
}
fun openPhotoEditor(photo: Avatar.Photo) {
Navigation.findNavController(requireView())
.navigate(AvatarPickerFragmentDirections.actionAvatarPickerFragmentToAvatarPhotoEditorFragment(AvatarBundler.bundlePhoto(photo)))
}
fun openVectorEditor(vector: Avatar.Vector) {
Navigation.findNavController(requireView())
.navigate(AvatarPickerFragmentDirections.actionAvatarPickerFragmentToVectorAvatarCreationFragment(AvatarBundler.bundleVector(vector)))
}
fun openTextEditor(text: Avatar.Text?) {
val bundle = if (text != null) AvatarBundler.bundleText(text) else null
Navigation.findNavController(requireView())
.navigate(AvatarPickerFragmentDirections.actionAvatarPickerFragmentToTextAvatarCreationFragment(bundle))
}
fun openCameraCapture() {
Permissions.with(this)
.request(Manifest.permission.CAMERA)
.ifNecessary()
.onAllGranted {
val intent = AvatarSelectionActivity.getIntentForCameraCapture(requireContext())
startActivityForResult(intent, REQUEST_CODE_SELECT_IMAGE)
}
.onAnyDenied {
Toast.makeText(requireContext(), R.string.AvatarSelectionBottomSheetDialogFragment__taking_a_photo_requires_the_camera_permission, Toast.LENGTH_SHORT)
.show()
}
.execute()
}
fun openGallery() {
Permissions.with(this)
.request(Manifest.permission.READ_EXTERNAL_STORAGE)
.ifNecessary()
.onAllGranted {
val intent = AvatarSelectionActivity.getIntentForGallery(requireContext())
startActivityForResult(intent, REQUEST_CODE_SELECT_IMAGE)
}
.onAnyDenied {
Toast.makeText(requireContext(), R.string.AvatarSelectionBottomSheetDialogFragment__viewing_your_gallery_requires_the_storage_permission, Toast.LENGTH_SHORT)
.show()
}
.execute()
}
}

Wyświetl plik

@ -0,0 +1,150 @@
package org.thoughtcrime.securesms.avatar.picker
import android.util.TypedValue
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.view.setPadding
import androidx.core.widget.addTextChangedListener
import com.airbnb.lottie.SimpleColorFilter
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.avatar.Avatar
import org.thoughtcrime.securesms.avatar.AvatarRenderer
import org.thoughtcrime.securesms.avatar.Avatars
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.util.MappingAdapter
import org.thoughtcrime.securesms.util.MappingModel
import org.thoughtcrime.securesms.util.MappingViewHolder
import org.thoughtcrime.securesms.util.visible
typealias OnAvatarClickListener = (Avatar, Boolean) -> Unit
typealias OnAvatarLongClickListener = (View, Avatar) -> Boolean
object AvatarPickerItem {
private val SELECTION_CHANGED = Any()
fun register(adapter: MappingAdapter, onAvatarClickListener: OnAvatarClickListener, onAvatarLongClickListener: OnAvatarLongClickListener) {
adapter.registerFactory(Model::class.java, MappingAdapter.LayoutFactory({ ViewHolder(it, onAvatarClickListener, onAvatarLongClickListener) }, R.layout.avatar_picker_item))
}
class Model(val avatar: Avatar, val isSelected: Boolean) : MappingModel<Model> {
override fun areItemsTheSame(newItem: Model): Boolean = avatar.isSameAs(newItem.avatar)
override fun areContentsTheSame(newItem: Model): Boolean = avatar == newItem.avatar && isSelected == newItem.isSelected
override fun getChangePayload(newItem: Model): Any? {
return if (newItem.avatar == avatar && isSelected != newItem.isSelected) {
SELECTION_CHANGED
} else {
null
}
}
}
class ViewHolder(
itemView: View,
private val onAvatarClickListener: OnAvatarClickListener? = null,
private val onAvatarLongClickListener: OnAvatarLongClickListener? = null
) : MappingViewHolder<Model>(itemView) {
private val imageView: ImageView = itemView.findViewById(R.id.avatar_picker_item_image)
private val textView: TextView = itemView.findViewById(R.id.avatar_picker_item_text)
private val selectedFader: View? = itemView.findViewById(R.id.avatar_picker_item_fader)
private val selectedOverlay: View? = itemView.findViewById(R.id.avatar_picker_item_selection_overlay)
init {
textView.typeface = AvatarRenderer.getTypeface(context)
textView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
updateTextSize()
}
textView.addTextChangedListener(
afterTextChanged = {
updateTextSize()
}
)
}
private fun updateTextSize() {
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, Avatars.getTextSizeForLength(context, textView.text.toString(), textView.measuredWidth * 0.8f, textView.measuredHeight * 0.45f))
}
override fun bind(model: Model) {
val alpha = if (model.isSelected) 1f else 0f
val scale = if (model.isSelected) 0.9f else 1f
imageView.animate().cancel()
textView.animate().cancel()
selectedOverlay?.animate()?.cancel()
selectedFader?.animate()?.cancel()
if (model.isSelected) {
itemView.setOnLongClickListener {
onAvatarLongClickListener?.invoke(itemView, model.avatar) ?: false
}
} else {
itemView.setOnLongClickListener(null)
}
itemView.setOnClickListener { onAvatarClickListener?.invoke(model.avatar, model.isSelected) }
if (payload.isNotEmpty() && payload.contains(SELECTION_CHANGED)) {
imageView.animate().scaleX(scale).scaleY(scale)
textView.animate().scaleX(scale).scaleY(scale)
selectedOverlay?.animate()?.alpha(alpha)
selectedFader?.animate()?.alpha(alpha)
return
}
imageView.scaleX = scale
imageView.scaleY = scale
textView.scaleX = scale
textView.scaleY = scale
selectedFader?.alpha = alpha
selectedOverlay?.alpha = alpha
imageView.clearColorFilter()
imageView.setPadding(0)
when (model.avatar) {
is Avatar.Text -> {
textView.visible = true
if (textView.text.toString() != model.avatar.text) {
textView.text = model.avatar.text
}
imageView.setImageDrawable(null)
imageView.background.colorFilter = SimpleColorFilter(model.avatar.color.backgroundColor)
textView.setTextColor(model.avatar.color.foregroundColor)
}
is Avatar.Vector -> {
textView.visible = false
val drawableId = Avatars.getDrawableResource(model.avatar.key)
if (drawableId == null) {
imageView.setImageDrawable(null)
} else {
imageView.setImageDrawable(AppCompatResources.getDrawable(context, drawableId))
}
imageView.background.colorFilter = SimpleColorFilter(model.avatar.color.backgroundColor)
}
is Avatar.Photo -> {
textView.visible = false
GlideApp.with(imageView).load(DecryptableStreamUriLoader.DecryptableUri(model.avatar.uri)).into(imageView)
}
is Avatar.Resource -> {
imageView.setPadding((imageView.width * 0.2).toInt())
textView.visible = false
GlideApp.with(imageView).clear(imageView)
imageView.setImageResource(model.avatar.resourceId)
imageView.colorFilter = SimpleColorFilter(model.avatar.color.foregroundColor)
imageView.background.colorFilter = SimpleColorFilter(model.avatar.color.backgroundColor)
}
}
}
}
}

Wyświetl plik

@ -0,0 +1,175 @@
package org.thoughtcrime.securesms.avatar.picker
import android.content.Context
import android.net.Uri
import android.widget.Toast
import io.reactivex.rxjava3.core.Single
import org.signal.core.util.StreamUtil
import org.signal.core.util.ThreadUtil
import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.avatar.Avatar
import org.thoughtcrime.securesms.avatar.AvatarPickerStorage
import org.thoughtcrime.securesms.avatar.AvatarRenderer
import org.thoughtcrime.securesms.avatar.Avatars
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.profiles.AvatarHelper
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.NameUtil
import org.whispersystems.signalservice.api.util.StreamDetails
import java.io.IOException
private val TAG = Log.tag(AvatarPickerRepository::class.java)
class AvatarPickerRepository(context: Context) {
private val applicationContext = context.applicationContext
fun getAvatarForSelf(): Single<Avatar> = Single.fromCallable {
val details: StreamDetails? = AvatarHelper.getSelfProfileAvatarStream(applicationContext)
if (details != null) {
try {
val bytes = StreamUtil.readFully(details.stream)
Avatar.Photo(
BlobProvider.getInstance().forData(bytes).createForSingleSessionInMemory(),
details.length,
Avatar.DatabaseId.DoNotPersist
)
} catch (e: IOException) {
Log.w(TAG, "Failed to read avatar!")
getDefaultAvatarForSelf()
}
} else {
getDefaultAvatarForSelf()
}
}
fun getAvatarForGroup(groupId: GroupId): Single<Avatar> = Single.fromCallable {
val recipient = Recipient.externalGroupExact(applicationContext, groupId)
if (AvatarHelper.hasAvatar(applicationContext, recipient.id)) {
try {
val bytes = AvatarHelper.getAvatarBytes(applicationContext, recipient.id)
Avatar.Photo(
BlobProvider.getInstance().forData(bytes).createForSingleSessionInMemory(),
AvatarHelper.getAvatarLength(applicationContext, recipient.id),
Avatar.DatabaseId.DoNotPersist
)
} catch (e: IOException) {
Log.w(TAG, "Failed to read group avatar!")
getDefaultAvatarForGroup()
}
} else {
getDefaultAvatarForGroup()
}
}
fun getPersistedAvatarsForSelf(): Single<List<Avatar>> = Single.fromCallable {
DatabaseFactory.getAvatarPickerDatabase(applicationContext).getAvatarsForSelf()
}
fun getPersistedAvatarsForGroup(groupId: GroupId): Single<List<Avatar>> = Single.fromCallable {
DatabaseFactory.getAvatarPickerDatabase(applicationContext).getAvatarsForGroup(groupId)
}
fun getDefaultAvatarsForSelf(): Single<List<Avatar>> = Single.fromCallable {
Avatars.defaultAvatarsForSelf.entries.mapIndexed { index, entry ->
Avatar.Vector(entry.key, color = Avatars.colors[index % Avatars.colors.size], Avatar.DatabaseId.NotSet)
}
}
fun getDefaultAvatarsForGroup(): Single<List<Avatar>> = Single.fromCallable {
Avatars.defaultAvatarsForGroup.entries.mapIndexed { index, entry ->
Avatar.Vector(entry.key, color = Avatars.colors[index % Avatars.colors.size], Avatar.DatabaseId.NotSet)
}
}
fun writeMediaToMultiSessionStorage(media: Media, onMediaWrittenToMultiSessionStorage: (Uri) -> Unit) {
SignalExecutors.BOUNDED.execute {
onMediaWrittenToMultiSessionStorage(AvatarPickerStorage.save(applicationContext, media))
}
}
fun persistAvatarForSelf(avatar: Avatar, onPersisted: (Avatar) -> Unit) {
SignalExecutors.BOUNDED.execute {
val avatarDatabase = DatabaseFactory.getAvatarPickerDatabase(applicationContext)
val savedAvatar = avatarDatabase.saveAvatarForSelf(avatar)
avatarDatabase.markUsage(savedAvatar)
onPersisted(savedAvatar)
}
}
fun persistAvatarForGroup(avatar: Avatar, groupId: GroupId, onPersisted: (Avatar) -> Unit) {
SignalExecutors.BOUNDED.execute {
val avatarDatabase = DatabaseFactory.getAvatarPickerDatabase(applicationContext)
val savedAvatar = avatarDatabase.saveAvatarForGroup(avatar, groupId)
avatarDatabase.markUsage(savedAvatar)
onPersisted(savedAvatar)
}
}
fun persistAndCreateMediaForSelf(avatar: Avatar, onSaved: (Media) -> Unit) {
SignalExecutors.BOUNDED.execute {
if (avatar.databaseId !is Avatar.DatabaseId.DoNotPersist) {
persistAvatarForSelf(avatar) {
AvatarRenderer.renderAvatar(applicationContext, avatar, onSaved, this::handleRenderFailure)
}
} else {
AvatarRenderer.renderAvatar(applicationContext, avatar, onSaved, this::handleRenderFailure)
}
}
}
fun persistAndCreateMediaForGroup(avatar: Avatar, groupId: GroupId, onSaved: (Media) -> Unit) {
SignalExecutors.BOUNDED.execute {
if (avatar.databaseId !is Avatar.DatabaseId.DoNotPersist) {
persistAvatarForGroup(avatar, groupId) {
AvatarRenderer.renderAvatar(applicationContext, avatar, onSaved, this::handleRenderFailure)
}
} else {
AvatarRenderer.renderAvatar(applicationContext, avatar, onSaved, this::handleRenderFailure)
}
}
}
fun createMediaForNewGroup(avatar: Avatar, onSaved: (Media) -> Unit) {
SignalExecutors.BOUNDED.execute {
AvatarRenderer.renderAvatar(applicationContext, avatar, onSaved, this::handleRenderFailure)
}
}
fun handleRenderFailure(throwable: Throwable?) {
Log.w(TAG, "Failed to render avatar.", throwable)
ThreadUtil.postToMain {
Toast.makeText(applicationContext, R.string.AvatarPickerRepository__failed_to_save_avatar, Toast.LENGTH_SHORT).show()
}
}
fun getDefaultAvatarForSelf(): Avatar {
val initials = NameUtil.getAbbreviation(Recipient.self().getDisplayName(applicationContext))
return if (initials.isNullOrBlank()) {
Avatar.getDefaultForSelf()
} else {
Avatar.Text(initials, Avatars.colors.random(), Avatar.DatabaseId.NotSet)
}
}
fun getDefaultAvatarForGroup(): Avatar {
return Avatar.getDefaultForGroup()
}
fun delete(avatar: Avatar, onDelete: () -> Unit) {
SignalExecutors.BOUNDED.execute {
if (avatar.databaseId is Avatar.DatabaseId.Saved) {
val avatarDatabase = DatabaseFactory.getAvatarPickerDatabase(applicationContext)
avatarDatabase.deleteAvatar(avatar)
}
onDelete()
}
}
}

Wyświetl plik

@ -0,0 +1,10 @@
package org.thoughtcrime.securesms.avatar.picker
import org.thoughtcrime.securesms.avatar.Avatar
data class AvatarPickerState(
val currentAvatar: Avatar? = null,
val selectableAvatars: List<Avatar> = listOf(),
val canSave: Boolean = false,
val canClear: Boolean = false
)

Wyświetl plik

@ -0,0 +1,193 @@
package org.thoughtcrime.securesms.avatar.picker
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.schedulers.Schedulers
import org.thoughtcrime.securesms.avatar.Avatar
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.util.livedata.Store
sealed class AvatarPickerViewModel(private val repository: AvatarPickerRepository) : ViewModel() {
private val disposables = CompositeDisposable()
private val store = Store(AvatarPickerState())
val state: LiveData<AvatarPickerState> = store.stateLiveData
protected abstract fun getAvatar(): Single<Avatar>
protected abstract fun getDefaultAvatarFromRepository(): Avatar
protected abstract fun getPersistedAvatars(): Single<List<Avatar>>
protected abstract fun getDefaultAvatars(): Single<List<Avatar>>
protected abstract fun persistAvatar(avatar: Avatar, onPersisted: (Avatar) -> Unit)
protected abstract fun persistAndCreateMedia(avatar: Avatar, onSaved: (Media) -> Unit)
fun delete(avatar: Avatar) {
repository.delete(avatar) {
refreshSelectableAvatars()
}
}
fun clear() {
store.update {
val avatar = getDefaultAvatarFromRepository()
it.copy(currentAvatar = avatar, canSave = isSaveable(avatar), canClear = false)
}
}
fun save(onSaved: (Media) -> Unit) {
val avatar = store.state.currentAvatar ?: throw AssertionError()
persistAndCreateMedia(avatar, onSaved)
}
fun onAvatarSelectedFromGrid(avatar: Avatar) {
store.update { it.copy(currentAvatar = avatar, canSave = isSaveable(avatar), canClear = true) }
}
fun onAvatarEditCompleted(avatar: Avatar) {
persistAvatar(avatar) { saved ->
store.update { it.copy(currentAvatar = saved, canSave = isSaveable(saved), canClear = true) }
refreshSelectableAvatars()
}
}
fun onAvatarPhotoSelectionCompleted(media: Media) {
repository.writeMediaToMultiSessionStorage(media) { multiSessionUri ->
persistAvatar(Avatar.Photo(multiSessionUri, media.size, Avatar.DatabaseId.NotSet)) { avatar ->
store.update { it.copy(currentAvatar = avatar, canSave = isSaveable(avatar), canClear = true) }
refreshSelectableAvatars()
}
}
}
protected fun refreshAvatar() {
disposables.add(
getAvatar().subscribeOn(Schedulers.io()).subscribe { avatar ->
store.update { it.copy(currentAvatar = avatar, canSave = isSaveable(avatar), canClear = !isSaveable(avatar)) }
}
)
}
protected fun refreshSelectableAvatars() {
disposables.add(
Single.zip(getPersistedAvatars(), getDefaultAvatars()) { custom, def ->
val customKeys = custom.filterIsInstance(Avatar.Vector::class.java).map { it.key }
custom + def.filterNot {
it is Avatar.Vector && customKeys.contains(it.key)
}
}.subscribeOn(Schedulers.io()).subscribe { avatars ->
store.update { it.copy(selectableAvatars = avatars) }
}
)
}
private fun isSaveable(avatar: Avatar) = !(avatar is Avatar.Photo && avatar.databaseId == Avatar.DatabaseId.DoNotPersist)
override fun onCleared() {
disposables.dispose()
}
private class SelfAvatarPickerViewModel(private val repository: AvatarPickerRepository) : AvatarPickerViewModel(repository) {
init {
refreshAvatar()
refreshSelectableAvatars()
}
override fun getAvatar(): Single<Avatar> = repository.getAvatarForSelf()
override fun getDefaultAvatarFromRepository(): Avatar = repository.getDefaultAvatarForSelf()
override fun getPersistedAvatars(): Single<List<Avatar>> = repository.getPersistedAvatarsForSelf()
override fun getDefaultAvatars(): Single<List<Avatar>> = repository.getDefaultAvatarsForSelf()
override fun persistAvatar(avatar: Avatar, onPersisted: (Avatar) -> Unit) {
repository.persistAvatarForSelf(avatar, onPersisted)
}
override fun persistAndCreateMedia(avatar: Avatar, onSaved: (Media) -> Unit) {
repository.persistAndCreateMediaForSelf(avatar, onSaved)
}
}
private class GroupAvatarPickerViewModel(
private val groupId: GroupId,
private val repository: AvatarPickerRepository,
groupAvatarMedia: Media?
) : AvatarPickerViewModel(repository) {
private val initialAvatar: Avatar? = groupAvatarMedia?.let { Avatar.Photo(it.uri, it.size, Avatar.DatabaseId.DoNotPersist) }
init {
refreshAvatar()
refreshSelectableAvatars()
}
override fun getAvatar(): Single<Avatar> {
return if (initialAvatar != null) {
Single.just(initialAvatar)
} else {
repository.getAvatarForGroup(groupId)
}
}
override fun getDefaultAvatarFromRepository(): Avatar = repository.getDefaultAvatarForGroup()
override fun getPersistedAvatars(): Single<List<Avatar>> = repository.getPersistedAvatarsForGroup(groupId)
override fun getDefaultAvatars(): Single<List<Avatar>> = repository.getDefaultAvatarsForGroup()
override fun persistAvatar(avatar: Avatar, onPersisted: (Avatar) -> Unit) {
repository.persistAvatarForGroup(avatar, groupId, onPersisted)
}
override fun persistAndCreateMedia(avatar: Avatar, onSaved: (Media) -> Unit) {
repository.persistAndCreateMediaForGroup(avatar, groupId, onSaved)
}
}
private class NewGroupAvatarPickerViewModel(
private val repository: AvatarPickerRepository,
initialMedia: Media?
) : AvatarPickerViewModel(repository) {
private val initialAvatar: Avatar? = initialMedia?.let { Avatar.Photo(it.uri, it.size, Avatar.DatabaseId.DoNotPersist) }
init {
refreshAvatar()
refreshSelectableAvatars()
}
override fun getAvatar(): Single<Avatar> {
return if (initialAvatar != null) {
Single.just(initialAvatar)
} else {
Single.just(getDefaultAvatarFromRepository())
}
}
override fun getDefaultAvatarFromRepository(): Avatar = repository.getDefaultAvatarForGroup()
override fun getPersistedAvatars(): Single<List<Avatar>> = Single.just(listOf())
override fun getDefaultAvatars(): Single<List<Avatar>> = repository.getDefaultAvatarsForGroup()
override fun persistAvatar(avatar: Avatar, onPersisted: (Avatar) -> Unit) = onPersisted(avatar)
override fun persistAndCreateMedia(avatar: Avatar, onSaved: (Media) -> Unit) = repository.createMediaForNewGroup(avatar, onSaved)
}
class Factory(
private val repository: AvatarPickerRepository,
private val groupId: GroupId?,
private val isNewGroup: Boolean,
private val groupAvatarMedia: Media?
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
val viewModel = if (groupId == null && !isNewGroup) {
SelfAvatarPickerViewModel(repository)
} else if (groupId == null) {
NewGroupAvatarPickerViewModel(repository, groupAvatarMedia)
} else {
GroupAvatarPickerViewModel(groupId, repository, groupAvatarMedia)
}
return requireNotNull(modelClass.cast(viewModel))
}
}
}

Wyświetl plik

@ -0,0 +1,148 @@
package org.thoughtcrime.securesms.avatar.text
import android.os.Bundle
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import androidx.appcompat.widget.Toolbar
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResult
import androidx.fragment.app.viewModels
import androidx.navigation.Navigation
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionManager
import com.google.android.material.tabs.TabLayout
import org.signal.core.util.EditTextUtil
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.avatar.Avatar
import org.thoughtcrime.securesms.avatar.AvatarBundler
import org.thoughtcrime.securesms.avatar.AvatarColorItem
import org.thoughtcrime.securesms.avatar.Avatars
import org.thoughtcrime.securesms.avatar.picker.AvatarPickerItem
import org.thoughtcrime.securesms.components.BoldSelectionTabItem
import org.thoughtcrime.securesms.components.ControllableTabLayout
import org.thoughtcrime.securesms.components.recyclerview.GridDividerDecoration
import org.thoughtcrime.securesms.util.MappingAdapter
import org.thoughtcrime.securesms.util.ViewUtil
/**
* Fragment to create an avatar based off of a Vector or Text (via a pager)
*/
class TextAvatarCreationFragment : Fragment(R.layout.text_avatar_creation_fragment_hidden_recycler) {
private val viewModel: TextAvatarCreationViewModel by viewModels(factoryProducer = this::createFactory)
private lateinit var textInput: EditText
private lateinit var recycler: RecyclerView
private val withRecyclerSet = ConstraintSet()
private val withoutRecyclerSet = ConstraintSet()
private fun createFactory(): TextAvatarCreationViewModel.Factory {
val args = TextAvatarCreationFragmentArgs.fromBundle(requireArguments())
val textBundle = args.textAvatar
val text = if (textBundle != null) {
AvatarBundler.extractText(textBundle)
} else {
Avatar.Text("", Avatars.colors.random(), Avatar.DatabaseId.NotSet)
}
return TextAvatarCreationViewModel.Factory(text)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val toolbar: Toolbar = view.findViewById(R.id.text_avatar_creation_toolbar)
val tabLayout: ControllableTabLayout = view.findViewById(R.id.text_avatar_creation_tabs)
val doneButton: View = view.findViewById(R.id.text_avatar_creation_done)
withRecyclerSet.load(requireContext(), R.layout.text_avatar_creation_fragment)
withoutRecyclerSet.load(requireContext(), R.layout.text_avatar_creation_fragment_hidden_recycler)
recycler = view.findViewById(R.id.text_avatar_creation_recycler)
textInput = view.findViewById(R.id.avatar_picker_item_text)
toolbar.setNavigationOnClickListener { Navigation.findNavController(it).popBackStack() }
BoldSelectionTabItem.registerListeners(tabLayout)
val onTabSelectedListener = OnTabSelectedListener()
tabLayout.addOnTabSelectedListener(onTabSelectedListener)
onTabSelectedListener.onTabSelected(requireNotNull(tabLayout.getTabAt(tabLayout.selectedTabPosition)))
val adapter = MappingAdapter()
recycler.addItemDecoration(GridDividerDecoration(4, ViewUtil.dpToPx(16)))
AvatarColorItem.registerViewHolder(adapter) {
viewModel.setColor(it)
}
recycler.adapter = adapter
val viewHolder = AvatarPickerItem.ViewHolder(view)
viewModel.state.observe(viewLifecycleOwner) { state ->
EditTextUtil.setCursorColor(textInput, state.currentAvatar.color.foregroundColor)
val hadText = textInput.length() > 0
viewHolder.bind(AvatarPickerItem.Model(state.currentAvatar, false))
if (!hadText) {
textInput.setSelection(textInput.length())
}
adapter.submitList(state.colors().map { AvatarColorItem.Model(it) })
}
EditTextUtil.addGraphemeClusterLimitFilter(textInput, 3)
textInput.doAfterTextChanged {
if (it != null) {
viewModel.setText(it.toString())
}
}
doneButton.setOnClickListener { v ->
setFragmentResult(REQUEST_KEY_TEXT, AvatarBundler.bundleText(viewModel.getCurrentAvatar()))
Navigation.findNavController(v).popBackStack()
}
textInput.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
doneButton.performClick()
true
} else {
false
}
}
}
private inner class OnTabSelectedListener : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
when (tab.position) {
0 -> {
textInput.isEnabled = true
ViewUtil.focusAndShowKeyboard(textInput)
val constraintLayout = requireView() as ConstraintLayout
TransitionManager.endTransitions(constraintLayout)
withoutRecyclerSet.applyTo(constraintLayout)
TransitionManager.beginDelayedTransition(constraintLayout)
textInput.setSelection(textInput.length())
}
1 -> {
textInput.isEnabled = false
ViewUtil.hideKeyboard(requireContext(), textInput)
val constraintLayout = requireView() as ConstraintLayout
TransitionManager.endTransitions(constraintLayout)
withRecyclerSet.applyTo(constraintLayout)
TransitionManager.beginDelayedTransition(constraintLayout)
}
}
}
override fun onTabUnselected(tab: TabLayout.Tab?) = Unit
override fun onTabReselected(tab: TabLayout.Tab?) = Unit
}
companion object {
const val REQUEST_KEY_TEXT = "org.thoughtcrime.securesms.avatar.text.TEXT"
}
}

Wyświetl plik

@ -0,0 +1,11 @@
package org.thoughtcrime.securesms.avatar.text
import org.thoughtcrime.securesms.avatar.Avatar
import org.thoughtcrime.securesms.avatar.AvatarColorItem
import org.thoughtcrime.securesms.avatar.Avatars
data class TextAvatarCreationState(
val currentAvatar: Avatar.Text,
) {
fun colors(): List<AvatarColorItem> = Avatars.colors.map { AvatarColorItem(it, currentAvatar.color == it) }
}

Wyświetl plik

@ -0,0 +1,33 @@
package org.thoughtcrime.securesms.avatar.text
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import org.thoughtcrime.securesms.avatar.Avatar
import org.thoughtcrime.securesms.avatar.Avatars
import org.thoughtcrime.securesms.util.livedata.Store
class TextAvatarCreationViewModel(initialText: Avatar.Text) : ViewModel() {
private val store = Store(TextAvatarCreationState(initialText))
val state: LiveData<TextAvatarCreationState> = store.stateLiveData
fun setColor(colorPair: Avatars.ColorPair) {
store.update { it.copy(currentAvatar = it.currentAvatar.copy(color = colorPair)) }
}
fun setText(text: String) {
store.update { it.copy(currentAvatar = it.currentAvatar.copy(text = text)) }
}
fun getCurrentAvatar(): Avatar.Text {
return store.state.currentAvatar
}
class Factory(private val initialText: Avatar.Text) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return requireNotNull(modelClass.cast(TextAvatarCreationViewModel(initialText)))
}
}
}

Wyświetl plik

@ -0,0 +1,64 @@
package org.thoughtcrime.securesms.avatar.vector
import android.os.Bundle
import android.view.View
import android.widget.ImageView
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResult
import androidx.fragment.app.viewModels
import androidx.navigation.Navigation
import androidx.recyclerview.widget.RecyclerView
import com.airbnb.lottie.SimpleColorFilter
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.avatar.AvatarBundler
import org.thoughtcrime.securesms.avatar.AvatarColorItem
import org.thoughtcrime.securesms.avatar.Avatars
import org.thoughtcrime.securesms.components.recyclerview.GridDividerDecoration
import org.thoughtcrime.securesms.util.MappingAdapter
import org.thoughtcrime.securesms.util.ViewUtil
/**
* Fragment to create an avatar based off a default vector.
*/
class VectorAvatarCreationFragment : Fragment(R.layout.vector_avatar_creation_fragment) {
private val viewModel: VectorAvatarCreationViewModel by viewModels(factoryProducer = this::createFactory)
private fun createFactory(): VectorAvatarCreationViewModel.Factory {
val args = VectorAvatarCreationFragmentArgs.fromBundle(requireArguments())
val vectorBundle = args.vectorAvatar
return VectorAvatarCreationViewModel.Factory(AvatarBundler.extractVector(vectorBundle))
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val toolbar: Toolbar = view.findViewById(R.id.vector_avatar_creation_toolbar)
val recycler: RecyclerView = view.findViewById(R.id.vector_avatar_creation_recycler)
val doneButton: View = view.findViewById(R.id.vector_avatar_creation_done)
val preview: ImageView = view.findViewById(R.id.vector_avatar_creation_image)
val adapter = MappingAdapter()
recycler.adapter = adapter
recycler.addItemDecoration(GridDividerDecoration(4, ViewUtil.dpToPx(16)))
AvatarColorItem.registerViewHolder(adapter) {
viewModel.setColor(it)
}
viewModel.state.observe(viewLifecycleOwner) { state ->
preview.background.colorFilter = SimpleColorFilter(state.currentAvatar.color.backgroundColor)
preview.setImageResource(requireNotNull(Avatars.getDrawableResource(state.currentAvatar.key)))
adapter.submitList(state.colors().map { AvatarColorItem.Model(it) })
}
toolbar.setNavigationOnClickListener { Navigation.findNavController(view).popBackStack() }
doneButton.setOnClickListener {
setFragmentResult(REQUEST_KEY_VECTOR, AvatarBundler.bundleVector(viewModel.getCurrentAvatar()))
Navigation.findNavController(it).popBackStack()
}
}
companion object {
const val REQUEST_KEY_VECTOR = "org.thoughtcrime.securesms.avatar.text.VECTOR"
}
}

Wyświetl plik

@ -0,0 +1,11 @@
package org.thoughtcrime.securesms.avatar.vector
import org.thoughtcrime.securesms.avatar.Avatar
import org.thoughtcrime.securesms.avatar.AvatarColorItem
import org.thoughtcrime.securesms.avatar.Avatars
data class VectorAvatarCreationState(
val currentAvatar: Avatar.Vector,
) {
fun colors(): List<AvatarColorItem> = Avatars.colors.map { AvatarColorItem(it, currentAvatar.color == it) }
}

Wyświetl plik

@ -0,0 +1,27 @@
package org.thoughtcrime.securesms.avatar.vector
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import org.thoughtcrime.securesms.avatar.Avatar
import org.thoughtcrime.securesms.avatar.Avatars
import org.thoughtcrime.securesms.util.livedata.Store
class VectorAvatarCreationViewModel(initialAvatar: Avatar.Vector) : ViewModel() {
private val store = Store(VectorAvatarCreationState(initialAvatar))
val state: LiveData<VectorAvatarCreationState> = store.stateLiveData
fun setColor(colorPair: Avatars.ColorPair) {
store.update { it.copy(currentAvatar = it.currentAvatar.copy(color = colorPair)) }
}
fun getCurrentAvatar() = store.state.currentAvatar
class Factory(private val initialAvatar: Avatar.Vector) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return requireNotNull(modelClass.cast(VectorAvatarCreationViewModel(initialAvatar)))
}
}
}

Wyświetl plik

@ -106,7 +106,7 @@ public final class AvatarImageView extends AppCompatImageView {
outlinePaint = ThemeUtil.isDarkTheme(context) ? DARK_THEME_OUTLINE_PAINT : LIGHT_THEME_OUTLINE_PAINT;
unknownRecipientDrawable = new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20).asDrawable(context, AvatarColor.UNKNOWN.colorInt(), inverted);
unknownRecipientDrawable = new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20).asDrawable(context, AvatarColor.UNKNOWN, inverted);
blurred = false;
chatColors = null;
}
@ -248,7 +248,7 @@ public final class AvatarImageView extends AppCompatImageView {
requestManager.clear(this);
if (fallbackPhotoProvider != null) {
setImageDrawable(fallbackPhotoProvider.getPhotoForRecipientWithoutName()
.asDrawable(getContext(), AvatarColor.UNKNOWN.colorInt(), inverted));
.asDrawable(getContext(), AvatarColor.UNKNOWN, inverted));
} else {
setImageDrawable(unknownRecipientDrawable);
}
@ -285,7 +285,7 @@ public final class AvatarImageView extends AppCompatImageView {
{
Drawable fallback = Util.firstNonNull(fallbackPhotoProvider, Recipient.DEFAULT_FALLBACK_PHOTO_PROVIDER)
.getPhotoForGroup()
.asDrawable(getContext(), color.colorInt());
.asDrawable(getContext(), color);
GlideApp.with(this)
.load(avatarBytes)

Wyświetl plik

@ -0,0 +1,88 @@
package org.thoughtcrime.securesms.components
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import android.widget.TextView
import androidx.core.widget.doAfterTextChanged
import com.google.android.material.tabs.TabLayout
import org.thoughtcrime.securesms.R
import java.util.Objects
/**
* Custom View for Tabs which will render bold text when the view is selected
*/
class BoldSelectionTabItem @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
private lateinit var unselectedTextView: TextView
private lateinit var selectedTextView: TextView
override fun onFinishInflate() {
super.onFinishInflate()
unselectedTextView = findViewById(android.R.id.text1)
selectedTextView = findViewById(R.id.text1_bold)
unselectedTextView.doAfterTextChanged {
selectedTextView.text = it
}
}
fun select() {
unselectedTextView.alpha = 0f
selectedTextView.alpha = 1f
}
fun unselect() {
unselectedTextView.alpha = 1f
selectedTextView.alpha = 0f
}
companion object {
@JvmStatic
fun registerListeners(tabLayout: ControllableTabLayout) {
val newTabListener = NewTabListener()
val onTabSelectedListener = OnTabSelectedListener()
(0 until tabLayout.tabCount).mapNotNull { tabLayout.getTabAt(it) }.forEach {
newTabListener.onNewTab(it)
if (it.isSelected) {
onTabSelectedListener.onTabSelected(it)
} else {
onTabSelectedListener.onTabUnselected(it)
}
}
tabLayout.setNewTabListener(newTabListener)
tabLayout.addOnTabSelectedListener(onTabSelectedListener)
}
}
private class NewTabListener : ControllableTabLayout.NewTabListener {
override fun onNewTab(tab: TabLayout.Tab) {
val customView = tab.customView
if (customView == null) {
tab.setCustomView(R.layout.bold_selection_tab_item)
}
}
}
private class OnTabSelectedListener : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
val view = Objects.requireNonNull(tab.customView) as BoldSelectionTabItem
view.select()
}
override fun onTabUnselected(tab: TabLayout.Tab) {
val view = Objects.requireNonNull(tab.customView) as BoldSelectionTabItem
view.unselect()
}
override fun onTabReselected(tab: TabLayout.Tab) = Unit
}
}

Wyświetl plik

@ -0,0 +1,41 @@
package org.thoughtcrime.securesms.components
import android.content.Context
import android.util.AttributeSet
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import org.thoughtcrime.securesms.R
class ButtonStripItemView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
private val iconView: ImageView
private val labelView: TextView
init {
inflate(context, R.layout.button_strip_item_view, this)
iconView = findViewById(R.id.icon)
labelView = findViewById(R.id.label)
val array = context.obtainStyledAttributes(attrs, R.styleable.ButtonStripItemView)
val icon = array.getDrawable(R.styleable.ButtonStripItemView_bsiv_icon)
val contentDescription = array.getString(R.styleable.ButtonStripItemView_bsiv_icon_contentDescription)
val label = array.getString(R.styleable.ButtonStripItemView_bsiv_label)
iconView.setImageDrawable(icon)
iconView.contentDescription = contentDescription
labelView.text = label
array.recycle()
}
fun setOnIconClickedListener(onIconClickedListener: (() -> Unit)?) {
iconView.setOnClickListener { onIconClickedListener?.invoke() }
}
}

Wyświetl plik

@ -0,0 +1,50 @@
package org.thoughtcrime.securesms.components.recyclerview
import android.graphics.Rect
import android.view.View
import androidx.annotation.Px
import androidx.recyclerview.widget.RecyclerView
import org.thoughtcrime.securesms.util.ViewUtil
/**
* Decoration which will add an equal amount of space between each item in a grid.
*/
open class GridDividerDecoration(
private val spanCount: Int,
@Px private val space: Int
) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
return setItemOffsets(parent.getChildAdapterPosition(view), view, outRect)
}
protected fun setItemOffsets(position: Int, view: View, outRect: Rect) {
val column = position % spanCount
val isRtl = ViewUtil.isRtl(view)
val distanceFromEnd = spanCount - 1 - column
val spaceStart = (column / spanCount.toFloat()) * space
val spaceEnd = (distanceFromEnd / spanCount.toFloat()) * space
outRect.setStart(spaceStart.toInt(), isRtl)
outRect.setEnd(spaceEnd.toInt(), isRtl)
outRect.bottom = space
}
private fun Rect.setEnd(end: Int, isRtl: Boolean) {
if (isRtl) {
left = end
} else {
right = end
}
}
private fun Rect.setStart(start: Int, isRtl: Boolean) {
if (isRtl) {
right = start
} else {
left = start
}
}
}

Wyświetl plik

@ -3,11 +3,15 @@ package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
public interface FallbackContactPhoto {
public Drawable asDrawable(Context context, int color);
public Drawable asDrawable(Context context, int color, boolean inverted);
public Drawable asSmallDrawable(Context context, int color, boolean inverted);
public Drawable asCallCard(Context context);
Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color);
Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted);
Drawable asSmallDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted);
Drawable asCallCard(@NonNull Context context);
}

Wyświetl plik

@ -10,6 +10,8 @@ import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.graphics.drawable.DrawableCompat;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.avatar.Avatars;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Objects;
@ -26,33 +28,33 @@ public final class FallbackPhoto20dp implements FallbackContactPhoto {
}
@Override
public Drawable asDrawable(Context context, int color) {
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color) {
return buildDrawable(context, color);
}
@Override
public Drawable asDrawable(Context context, int color, boolean inverted) {
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return buildDrawable(context, color);
}
@Override
public Drawable asSmallDrawable(Context context, int color, boolean inverted) {
public Drawable asSmallDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return buildDrawable(context, color);
}
@Override
public Drawable asCallCard(Context context) {
public Drawable asCallCard(@NonNull Context context) {
throw new UnsupportedOperationException();
}
private @NonNull Drawable buildDrawable(@NonNull Context context, int color) {
private @NonNull Drawable buildDrawable(@NonNull Context context, @NonNull AvatarColor color) {
Drawable background = DrawableCompat.wrap(Objects.requireNonNull(AppCompatResources.getDrawable(context, R.drawable.circle_tintable))).mutate();
Drawable foreground = AppCompatResources.getDrawable(context, drawable20dp);
Drawable gradient = AppCompatResources.getDrawable(context, R.drawable.avatar_gradient);
LayerDrawable drawable = new LayerDrawable(new Drawable[]{background, foreground, gradient});
Drawable foreground = Objects.requireNonNull(AppCompatResources.getDrawable(context, drawable20dp));
LayerDrawable drawable = new LayerDrawable(new Drawable[]{background, foreground});
int foregroundInset = ViewUtil.dpToPx(2);
DrawableCompat.setTint(background, color);
DrawableCompat.setTint(background, color.colorInt());
DrawableCompat.setTint(foreground, Avatars.getForegroundColor(color).getColorInt());
drawable.setLayerInset(1, foregroundInset, foregroundInset, foregroundInset, foregroundInset);

Wyświetl plik

@ -1,57 +1,55 @@
package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.avatar.Avatars;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Objects;
public final class FallbackPhoto80dp implements FallbackContactPhoto {
@DrawableRes private final int drawable80dp;
private final int backgroundColor;
@DrawableRes private final int drawable80dp;
private final AvatarColor color;
public FallbackPhoto80dp(@DrawableRes int drawable80dp, int backgroundColor) {
this.drawable80dp = drawable80dp;
this.backgroundColor = backgroundColor;
public FallbackPhoto80dp(@DrawableRes int drawable80dp, @NonNull AvatarColor color) {
this.drawable80dp = drawable80dp;
this.color = color;
}
@Override
public Drawable asDrawable(Context context, int color) {
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color) {
return buildDrawable(context);
}
@Override
public Drawable asDrawable(Context context, int color, boolean inverted) {
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return buildDrawable(context);
}
@Override
public Drawable asSmallDrawable(Context context, int color, boolean inverted) {
public Drawable asSmallDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
throw new UnsupportedOperationException();
}
@Override
public Drawable asCallCard(Context context) {
Drawable background = new ColorDrawable(backgroundColor);
Drawable foreground = AppCompatResources.getDrawable(context, drawable80dp);
int transparent20 = ContextCompat.getColor(context, R.color.signal_transparent_20);
Drawable gradient = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, new int[]{ Color.TRANSPARENT, transparent20 });
LayerDrawable drawable = new LayerDrawable(new Drawable[]{background, foreground, gradient});
public Drawable asCallCard(@NonNull Context context) {
Drawable background = new ColorDrawable(color.colorInt());
Drawable foreground = Objects.requireNonNull(AppCompatResources.getDrawable(context, drawable80dp));
LayerDrawable drawable = new LayerDrawable(new Drawable[]{background, foreground});
int foregroundInset = ViewUtil.dpToPx(24);
DrawableCompat.setTint(foreground, Avatars.getForegroundColor(color).getColorInt());
drawable.setLayerInset(1, foregroundInset, foregroundInset, foregroundInset, foregroundInset);
return drawable;
@ -59,12 +57,12 @@ public final class FallbackPhoto80dp implements FallbackContactPhoto {
private @NonNull Drawable buildDrawable(@NonNull Context context) {
Drawable background = DrawableCompat.wrap(Objects.requireNonNull(AppCompatResources.getDrawable(context, R.drawable.circle_tintable))).mutate();
Drawable foreground = AppCompatResources.getDrawable(context, drawable80dp);
Drawable gradient = AppCompatResources.getDrawable(context, R.drawable.avatar_gradient);
LayerDrawable drawable = new LayerDrawable(new Drawable[]{background, foreground, gradient});
Drawable foreground = Objects.requireNonNull(AppCompatResources.getDrawable(context, drawable80dp));
LayerDrawable drawable = new LayerDrawable(new Drawable[]{background, foreground});
int foregroundInset = ViewUtil.dpToPx(24);
DrawableCompat.setTint(background, backgroundColor);
DrawableCompat.setTint(background, color.colorInt());
DrawableCompat.setTint(foreground, Avatars.getForegroundColor(color).getColorInt());
drawable.setLayerInset(1, foregroundInset, foregroundInset, foregroundInset, foregroundInset);

Wyświetl plik

@ -1,79 +1,72 @@
package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.text.TextUtils;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.content.ContextCompat;
import com.amulyakhare.textdrawable.TextDrawable;
import com.airbnb.lottie.SimpleColorFilter;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.avatar.Avatar;
import org.thoughtcrime.securesms.avatar.AvatarRenderer;
import org.thoughtcrime.securesms.avatar.Avatars;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.util.ContextUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.NameUtil;
import java.util.regex.Pattern;
import java.util.Objects;
public class GeneratedContactPhoto implements FallbackContactPhoto {
private static final Pattern PATTERN = Pattern.compile("[^\\p{L}\\p{Nd}\\p{S}]+");
private static final Typeface TYPEFACE = Typeface.create("sans-serif-medium", Typeface.NORMAL);
private final String name;
private final int fallbackResId;
private final int targetSize;
private final int fontSize;
public GeneratedContactPhoto(@NonNull String name, @DrawableRes int fallbackResId) {
this(name, fallbackResId, -1, ViewUtil.dpToPx(24));
this(name, fallbackResId, -1);
}
public GeneratedContactPhoto(@NonNull String name, @DrawableRes int fallbackResId, int targetSize, int fontSize) {
public GeneratedContactPhoto(@NonNull String name, @DrawableRes int fallbackResId, int targetSize) {
this.name = name;
this.fallbackResId = fallbackResId;
this.targetSize = targetSize;
this.fontSize = fontSize;
}
@Override
public Drawable asDrawable(Context context, int color) {
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color) {
return asDrawable(context, color,false);
}
@Override
public Drawable asDrawable(Context context, int color, boolean inverted) {
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
int targetSize = this.targetSize != -1
? this.targetSize
: context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size);
String character = getAbbreviation(name);
String character = NameUtil.getAbbreviation(name);
if (!TextUtils.isEmpty(character)) {
Drawable base = TextDrawable.builder()
.beginConfig()
.width(targetSize)
.height(targetSize)
.useFont(TYPEFACE)
.fontSize(fontSize)
.textColor(inverted ? color : Color.WHITE)
.endConfig()
.buildRound(character, inverted ? Color.WHITE : color);
Avatars.ForegroundColor foregroundColor = Avatars.getForegroundColor(color);
Avatar.Text avatar = new Avatar.Text(character, new Avatars.ColorPair(color, foregroundColor), Avatar.DatabaseId.DoNotPersist.INSTANCE);
Drawable foreground = AvatarRenderer.createTextDrawable(context, avatar, inverted, targetSize, false);
Drawable background = Objects.requireNonNull(ContextCompat.getDrawable(context, R.drawable.circle_tintable));
Drawable gradient = ContextUtil.requireDrawable(context, R.drawable.avatar_gradient);
return new LayerDrawable(new Drawable[] { base, gradient });
background.setColorFilter(new SimpleColorFilter(inverted ? foregroundColor.getColorInt() : color.colorInt()));
return new LayerDrawable(new Drawable[] { background, foreground });
}
return newFallbackDrawable(context, color, inverted);
}
@Override
public Drawable asSmallDrawable(Context context, int color, boolean inverted) {
public Drawable asSmallDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return asDrawable(context, color, inverted);
}
@ -81,32 +74,12 @@ public class GeneratedContactPhoto implements FallbackContactPhoto {
return fallbackResId;
}
protected Drawable newFallbackDrawable(@NonNull Context context, int color, boolean inverted) {
protected Drawable newFallbackDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return new ResourceContactPhoto(fallbackResId).asDrawable(context, color, inverted);
}
private @Nullable String getAbbreviation(String name) {
String[] parts = name.split(" ");
StringBuilder builder = new StringBuilder();
int count = 0;
for (int i = 0; i < parts.length && count < 2; i++) {
String cleaned = PATTERN.matcher(parts[i]).replaceFirst("");
if (!TextUtils.isEmpty(cleaned)) {
builder.appendCodePoint(cleaned.codePointAt(0));
count++;
}
}
if (builder.length() == 0) {
return null;
} else {
return builder.toString();
}
}
@Override
public Drawable asCallCard(Context context) {
public Drawable asCallCard(@NonNull Context context) {
return AppCompatResources.getDrawable(context, R.drawable.ic_person_large);
}

Wyświetl plik

@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
@ -15,8 +14,9 @@ import androidx.appcompat.content.res.AppCompatResources;
import com.amulyakhare.textdrawable.TextDrawable;
import com.makeramen.roundedimageview.RoundedDrawable;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ContextUtil;
import org.jetbrains.annotations.NotNull;
import org.thoughtcrime.securesms.avatar.Avatars;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
public class ResourceContactPhoto implements FallbackContactPhoto {
@ -45,38 +45,34 @@ public class ResourceContactPhoto implements FallbackContactPhoto {
}
@Override
public @NonNull Drawable asDrawable(@NonNull Context context, int color) {
public @NonNull Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color) {
return asDrawable(context, color, false);
}
@Override
public @NonNull Drawable asDrawable(@NonNull Context context, int color, boolean inverted) {
public @NonNull Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return buildDrawable(context, resourceId, color, inverted);
}
@Override
public @NonNull Drawable asSmallDrawable(@NonNull Context context, int color, boolean inverted) {
public @NonNull Drawable asSmallDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return buildDrawable(context, smallResourceId, color, inverted);
}
private @NonNull Drawable buildDrawable(@NonNull Context context, int resourceId, int color, boolean inverted) {
Drawable background = TextDrawable.builder().buildRound(" ", inverted ? Color.WHITE : color);
RoundedDrawable foreground = (RoundedDrawable) RoundedDrawable.fromDrawable(AppCompatResources.getDrawable(context, resourceId));
private @NonNull Drawable buildDrawable(@NonNull Context context, int resourceId, @NonNull AvatarColor color, boolean inverted) {
Avatars.ForegroundColor foregroundColor = Avatars.getForegroundColor(color);
Drawable background = TextDrawable.builder().buildRound(" ", inverted ? foregroundColor.getColorInt() : color.colorInt());
RoundedDrawable foreground = (RoundedDrawable) RoundedDrawable.fromDrawable(AppCompatResources.getDrawable(context, resourceId));
//noinspection ConstantConditions
foreground.setScaleType(scaleType);
foreground.setColorFilter(inverted ? color.colorInt() : foregroundColor.getColorInt(), PorterDuff.Mode.SRC_ATOP);
if (inverted) {
foreground.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
}
Drawable gradient = ContextUtil.requireDrawable(context, R.drawable.avatar_gradient);
return new ExpandingLayerDrawable(new Drawable[] {background, foreground, gradient});
return new ExpandingLayerDrawable(new Drawable[] {background, foreground});
}
@Override
public @Nullable Drawable asCallCard(@NonNull Context context) {
public @Nullable Drawable asCallCard(@NotNull @NonNull Context context) {
return AppCompatResources.getDrawable(context, callCardResourceId);
}

Wyświetl plik

@ -3,33 +3,36 @@ package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import com.makeramen.roundedimageview.RoundedDrawable;
import org.jetbrains.annotations.NotNull;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
public class TransparentContactPhoto implements FallbackContactPhoto {
public TransparentContactPhoto() {}
@Override
public Drawable asDrawable(Context context, int color) {
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color) {
return asDrawable(context, color, false);
}
@Override
public Drawable asDrawable(Context context, int color, boolean inverted) {
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return RoundedDrawable.fromDrawable(context.getResources().getDrawable(android.R.color.transparent));
}
@Override
public Drawable asSmallDrawable(Context context, int color, boolean inverted) {
public Drawable asSmallDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return asDrawable(context, color, inverted);
}
@Override
public Drawable asCallCard(Context context) {
public Drawable asCallCard(@NonNull Context context) {
return ContextCompat.getDrawable(context, R.drawable.ic_contact_picture_large);
}

Wyświetl plik

@ -1129,6 +1129,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
updateReminders();
}
@SuppressLint("MissingSuperCall")
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
@ -1302,7 +1303,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
GlideApp.with(this)
.asBitmap()
.load(recipient.getContactPhoto())
.error(recipient.getFallbackContactPhoto().asDrawable(this, recipient.getAvatarColor().colorInt(), false))
.error(recipient.getFallbackContactPhoto().asDrawable(this, recipient.getAvatarColor(), false))
.into(new CustomTarget<Bitmap>() {
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {

Wyświetl plik

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.colors;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
@ -11,56 +12,20 @@ import java.util.Objects;
* A serializable set of color constants that can be used for avatars.
*/
public enum AvatarColor {
C000("C000", 0xFFD00B0B),
C010("C010", 0xFFC72A0A),
C020("C020", 0xFFB34209),
C030("C030", 0xFF9C5711),
C040("C040", 0xFF866118),
C050("C050", 0xFF76681E),
C060("C060", 0xFF6C6C13),
C070("C070", 0xFF5E6E0C),
C080("C080", 0xFF507406),
C090("C090", 0xFF3D7406),
C100("C100", 0xFF2D7906),
C110("C110", 0xFF1A7906),
C120("C120", 0xFF067906),
C130("C130", 0xFF067919),
C140("C140", 0xFF06792D),
C150("C150", 0xFF067940),
C160("C160", 0xFF067953),
C170("C170", 0xFF067462),
C180("C180", 0xFF067474),
C190("C190", 0xFF077288),
C200("C200", 0xFF086DA0),
C210("C210", 0xFF0A69C7),
C220("C220", 0xFF0D59F2),
C230("C230", 0xFF3454F4),
C240("C240", 0xFF5151F6),
C250("C250", 0xFF6447F5),
C260("C260", 0xFF7A3DF5),
C270("C270", 0xFF8F2AF4),
C280("C280", 0xFFA20CED),
C290("C290", 0xFFAF0BD0),
C300("C300", 0xFFB80AB8),
C310("C310", 0xFFC20AA3),
C320("C320", 0xFFC70A88),
C330("C330", 0xFFCB0B6B),
C340("C340", 0xFFD00B4D),
C350("C350", 0xFFD00B2C),
CRIMSON("crimson", ChatColorsPalette.Bubbles.CRIMSON.asSingleColor()),
VERMILLION("vermillion", ChatColorsPalette.Bubbles.VERMILION.asSingleColor()),
BURLAP("burlap", ChatColorsPalette.Bubbles.BURLAP.asSingleColor()),
FOREST("forest", ChatColorsPalette.Bubbles.FOREST.asSingleColor()),
WINTERGREEN("wintergreen", ChatColorsPalette.Bubbles.WINTERGREEN.asSingleColor()),
TEAL("teal", ChatColorsPalette.Bubbles.TEAL.asSingleColor()),
BLUE("blue", ChatColorsPalette.Bubbles.BLUE.asSingleColor()),
INDIGO("indigo", ChatColorsPalette.Bubbles.INDIGO.asSingleColor()),
VIOLET("violet", ChatColorsPalette.Bubbles.VIOLET.asSingleColor()),
PLUM("plum", ChatColorsPalette.Bubbles.PLUM.asSingleColor()),
TAUPE("taupe", ChatColorsPalette.Bubbles.TAUPE.asSingleColor()),
STEEL("steel", ChatColorsPalette.Bubbles.STEEL.asSingleColor()),
ULTRAMARINE("ultramarine", ChatColorsPalette.Bubbles.ULTRAMARINE.asSingleColor()),
UNKNOWN("unknown", ChatColorsPalette.Bubbles.STEEL.asSingleColor());
A100("A100", 0xFFE3E3FE),
A110("A110", 0xFFDDE7FC),
A120("A120", 0xFFD8E8F0),
A130("A130", 0xFFCDE4CD),
A140("A140", 0xFFEAE0F8),
A150("A150", 0xFFF5E3FE),
A160("A160", 0xFFF6D8EC),
A170("A170", 0xFFF5D7D7),
A180("A180", 0xFFFEF5D0),
A190("A190", 0xFFEAE6D5),
A200("A200", 0xFFD2D2DC),
A210("A210", 0xFFD7D7D9);
public static final AvatarColor UNKNOWN = A210;
/** Fast map of name to enum, while also giving us a location to map old colors to new ones. */
private static final Map<String, AvatarColor> NAME_MAP = new HashMap<>();
@ -69,61 +34,83 @@ public enum AvatarColor {
NAME_MAP.put(color.serialize(), color);
}
NAME_MAP.put("red", CRIMSON);
NAME_MAP.put("orange", VERMILLION);
NAME_MAP.put("deep_orange", VERMILLION);
NAME_MAP.put("brown", BURLAP);
NAME_MAP.put("green", FOREST);
NAME_MAP.put("light_green", WINTERGREEN);
NAME_MAP.put("teal", TEAL);
NAME_MAP.put("blue", BLUE);
NAME_MAP.put("indigo", INDIGO);
NAME_MAP.put("purple", VIOLET);
NAME_MAP.put("deep_purple", VIOLET);
NAME_MAP.put("pink", PLUM);
NAME_MAP.put("blue_grey", TAUPE);
NAME_MAP.put("grey", STEEL);
NAME_MAP.put("ultramarine", ULTRAMARINE);
NAME_MAP.put("C020", A170);
NAME_MAP.put("C030", A170);
NAME_MAP.put("C040", A180);
NAME_MAP.put("C050", A180);
NAME_MAP.put("C000", A190);
NAME_MAP.put("C060", A190);
NAME_MAP.put("C070", A190);
NAME_MAP.put("C080", A130);
NAME_MAP.put("C090", A130);
NAME_MAP.put("C100", A130);
NAME_MAP.put("C110", A130);
NAME_MAP.put("C120", A130);
NAME_MAP.put("C130", A130);
NAME_MAP.put("C140", A130);
NAME_MAP.put("C150", A130);
NAME_MAP.put("C160", A130);
NAME_MAP.put("C170", A120);
NAME_MAP.put("C180", A120);
NAME_MAP.put("C190", A120);
NAME_MAP.put("C200", A110);
NAME_MAP.put("C210", A110);
NAME_MAP.put("C220", A110);
NAME_MAP.put("C230", A100);
NAME_MAP.put("C240", A100);
NAME_MAP.put("C250", A100);
NAME_MAP.put("C260", A100);
NAME_MAP.put("C270", A140);
NAME_MAP.put("C280", A140);
NAME_MAP.put("C290", A140);
NAME_MAP.put("C300", A150);
NAME_MAP.put("C010", A170);
NAME_MAP.put("C310", A150);
NAME_MAP.put("C320", A150);
NAME_MAP.put("C330", A160);
NAME_MAP.put("C340", A160);
NAME_MAP.put("C350", A160);
NAME_MAP.put("crimson", A170);
NAME_MAP.put("vermillion", A170);
NAME_MAP.put("burlap", A190);
NAME_MAP.put("forest", A130);
NAME_MAP.put("wintergreen", A130);
NAME_MAP.put("teal", A120);
NAME_MAP.put("blue", A110);
NAME_MAP.put("indigo", A100);
NAME_MAP.put("violet", A140);
NAME_MAP.put("plum", A150);
NAME_MAP.put("taupe", A190);
NAME_MAP.put("steel", A210);
NAME_MAP.put("ultramarine", A100);
NAME_MAP.put("unknown", A210);
NAME_MAP.put("red", A170);
NAME_MAP.put("orange", A170);
NAME_MAP.put("deep_orange", A170);
NAME_MAP.put("brown", A190);
NAME_MAP.put("green", A130);
NAME_MAP.put("light_green", A130);
NAME_MAP.put("purple", A140);
NAME_MAP.put("deep_purple", A140);
NAME_MAP.put("pink", A150);
NAME_MAP.put("blue_grey", A190);
NAME_MAP.put("grey", A210);
}
/** Colors that can be assigned via {@link #random()}. */
private static final AvatarColor[] RANDOM_OPTIONS = new AvatarColor[] {
C000,
C010,
C020,
C030,
C040,
C050,
C060,
C070,
C080,
C090,
C100,
C110,
C120,
C130,
C140,
C150,
C160,
C170,
C180,
C190,
C200,
C210,
C220,
C230,
C240,
C250,
C260,
C270,
C280,
C290,
C300,
C310,
C320,
C330,
C340,
C350,
A100,
A110,
A120,
A130,
A140,
A150,
A160,
A170,
A180,
A190,
A200,
A210
};
private final String name;
@ -148,6 +135,6 @@ public enum AvatarColor {
}
public static @NonNull AvatarColor deserialize(@NonNull String name) {
return Objects.requireNonNull(NAME_MAP.getOrDefault(name, C000));
return Objects.requireNonNull(NAME_MAP.getOrDefault(name, A210));
}
}

Wyświetl plik

@ -31,6 +31,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.helpers.ClassicOpenHelper;
import org.thoughtcrime.securesms.database.helpers.SQLCipherMigrationHelper;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.AvatarPickerDatabase;
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob;
import org.thoughtcrime.securesms.util.SqlUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@ -70,6 +71,7 @@ public class DatabaseFactory {
private final ChatColorsDatabase chatColorsDatabase;
private final EmojiSearchDatabase emojiSearchDatabase;
private final MessageSendLogDatabase messageSendLogDatabase;
private final AvatarPickerDatabase avatarPickerDatabase;
public static DatabaseFactory getInstance(Context context) {
if (instance == null) {
@ -200,6 +202,10 @@ public class DatabaseFactory {
return getInstance(context).messageSendLogDatabase;
}
public static AvatarPickerDatabase getAvatarPickerDatabase(Context context) {
return getInstance(context).avatarPickerDatabase;
}
public static SQLiteDatabase getBackupDatabase(Context context) {
return getInstance(context).databaseHelper.getReadableDatabase().getSqlCipherDatabase();
}
@ -259,8 +265,9 @@ public class DatabaseFactory {
this.mentionDatabase = new MentionDatabase(context, databaseHelper);
this.paymentDatabase = new PaymentDatabase(context, databaseHelper);
this.chatColorsDatabase = new ChatColorsDatabase(context, databaseHelper);
this.emojiSearchDatabase = new EmojiSearchDatabase(context, databaseHelper);
this.messageSendLogDatabase = new MessageSendLogDatabase(context, databaseHelper);
this.emojiSearchDatabase = new EmojiSearchDatabase(context, databaseHelper);
this.messageSendLogDatabase = new MessageSendLogDatabase(context, databaseHelper);
this.avatarPickerDatabase = new AvatarPickerDatabase(context, databaseHelper);
}
public void onApplicationLevelUpgrade(@NonNull Context context, @NonNull MasterSecret masterSecret,

Wyświetl plik

@ -57,6 +57,7 @@ import org.thoughtcrime.securesms.database.SqlCipherErrorHandler;
import org.thoughtcrime.securesms.database.StickerDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.UnknownStorageIdDatabase;
import org.thoughtcrime.securesms.database.model.AvatarPickerDatabase;
import org.thoughtcrime.securesms.database.model.databaseprotos.ReactionList;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
@ -207,8 +208,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
private static final int THREAD_AUTOINCREMENT = 108;
private static final int MMS_AUTOINCREMENT = 109;
private static final int ABANDONED_ATTACHMENT_CLEANUP = 110;
private static final int AVATAR_PICKER = 111;
private static final int DATABASE_VERSION = 110;
private static final int DATABASE_VERSION = 111;
private static final String DATABASE_NAME = "signal.db";
private final Context context;
@ -245,6 +247,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
db.execSQL(PaymentDatabase.CREATE_TABLE);
db.execSQL(ChatColorsDatabase.CREATE_TABLE);
db.execSQL(EmojiSearchDatabase.CREATE_TABLE);
db.execSQL(AvatarPickerDatabase.CREATE_TABLE);
executeStatements(db, SearchDatabase.CREATE_TABLE);
executeStatements(db, RemappedRecordsDatabase.CREATE_TABLE);
executeStatements(db, MessageSendLogDatabase.CREATE_TABLE);
@ -1934,6 +1937,24 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
db.delete("part", "mid != -8675309 AND mid NOT IN (SELECT _id FROM mms)", null);
}
if (oldVersion < AVATAR_PICKER) {
db.execSQL("CREATE TABLE avatar_picker (_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"last_used INTEGER DEFAULT 0, " +
"group_id TEXT DEFAULT NULL, " +
"avatar BLOB NOT NULL)");
try (Cursor cursor = db.query("recipient", new String[] { "_id" }, "color IS NULL", null, null, null, null)) {
while (cursor.moveToNext()) {
long id = cursor.getInt(cursor.getColumnIndexOrThrow("_id"));
ContentValues values = new ContentValues(1);
values.put("color", AvatarColor.random().serialize());
db.update("recipient", values, "_id = ?", new String[] { String.valueOf(id) });
}
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();

Wyświetl plik

@ -0,0 +1,185 @@
package org.thoughtcrime.securesms.database.model
import android.content.ContentValues
import android.content.Context
import android.net.Uri
import org.thoughtcrime.securesms.avatar.Avatar
import org.thoughtcrime.securesms.avatar.Avatars
import org.thoughtcrime.securesms.database.Database
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.database.model.databaseprotos.CustomAvatar
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.util.CursorUtil
import org.thoughtcrime.securesms.util.SqlUtil
/**
* Database which manages the record keeping for custom created avatars.
*/
class AvatarPickerDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Database(context, databaseHelper) {
companion object {
private const val TABLE_NAME = "avatar_picker"
private const val ID = "_id"
private const val LAST_USED = "last_used"
private const val GROUP_ID = "group_id"
private const val AVATAR = "avatar"
//language=sql
@JvmField
val CREATE_TABLE = """
CREATE TABLE $TABLE_NAME (
$ID INTEGER PRIMARY KEY AUTOINCREMENT,
$LAST_USED INTEGER DEFAULT 0,
$GROUP_ID TEXT DEFAULT NULL,
$AVATAR BLOB NOT NULL
)
""".trimIndent()
}
fun saveAvatarForSelf(avatar: Avatar): Avatar {
return saveAvatar(avatar, null)
}
fun saveAvatarForGroup(avatar: Avatar, groupId: GroupId): Avatar {
return saveAvatar(avatar, groupId)
}
fun markUsage(avatar: Avatar) {
val databaseId = avatar.databaseId
if (databaseId !is Avatar.DatabaseId.Saved) {
throw IllegalArgumentException("Must save this avatar before trying to mark usage.")
}
val db = databaseHelper.writableDatabase
val where = ID_WHERE
val args = SqlUtil.buildArgs(databaseId.id)
val values = ContentValues(1)
values.put(LAST_USED, System.currentTimeMillis())
db.update(TABLE_NAME, values, where, args)
}
fun update(avatar: Avatar) {
val databaseId = avatar.databaseId
if (databaseId !is Avatar.DatabaseId.Saved) {
throw IllegalArgumentException("Cannot update an unsaved avatar")
}
val db = databaseHelper.writableDatabase
val where = ID_WHERE
val values = ContentValues(1)
values.put(AVATAR, avatar.toProto().toByteArray())
db.update(TABLE_NAME, values, where, SqlUtil.buildArgs(databaseId.id))
}
fun deleteAvatar(avatar: Avatar) {
val databaseId = avatar.databaseId
if (databaseId !is Avatar.DatabaseId.Saved) {
throw IllegalArgumentException("Cannot delete an unsaved avatar.")
}
val db = databaseHelper.writableDatabase
val where = ID_WHERE
val args = SqlUtil.buildArgs(databaseId.id)
db.delete(TABLE_NAME, where, args)
}
private fun saveAvatar(avatar: Avatar, groupId: GroupId?): Avatar {
val db = databaseHelper.writableDatabase
val databaseId = avatar.databaseId
if (databaseId is Avatar.DatabaseId.DoNotPersist) {
throw IllegalArgumentException("Cannot persist this avatar")
}
if (databaseId is Avatar.DatabaseId.Saved) {
val values = ContentValues(2)
values.put(AVATAR, avatar.toProto().toByteArray())
db.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(databaseId.id))
return avatar
} else {
val values = ContentValues(4)
values.put(AVATAR, avatar.toProto().toByteArray())
if (groupId != null) {
values.put(GROUP_ID, groupId.toString())
}
val id = db.insert(TABLE_NAME, null, values)
if (id == -1L) {
throw AssertionError("Failed to save avatar")
}
return avatar.withDatabaseId(Avatar.DatabaseId.Saved(id))
}
}
fun getAllAvatars(): List<Avatar> {
val db = databaseHelper.readableDatabase
val results = mutableListOf<Avatar>()
db.query(TABLE_NAME, SqlUtil.buildArgs(ID, AVATAR), null, null, null, null, null)?.use {
while (it.moveToNext()) {
val id = CursorUtil.requireLong(it, ID)
val blob = CursorUtil.requireBlob(it, AVATAR)
val proto = CustomAvatar.parseFrom(blob)
results.add(proto.toAvatar(id))
}
}
return results
}
fun getAvatarsForSelf(): List<Avatar> {
return getAvatars(null)
}
fun getAvatarsForGroup(groupId: GroupId): List<Avatar> {
return getAvatars(groupId)
}
private fun getAvatars(groupId: GroupId?): List<Avatar> {
val db = databaseHelper.readableDatabase
val orderBy = "$LAST_USED DESC"
val results = mutableListOf<Avatar>()
val (where, args) = if (groupId == null) {
Pair("$GROUP_ID is NULL", null)
} else {
Pair("$GROUP_ID = ?", SqlUtil.buildArgs(groupId))
}
db.query(TABLE_NAME, SqlUtil.buildArgs(ID, AVATAR), where, args, null, null, orderBy)?.use {
while (it.moveToNext()) {
val id = CursorUtil.requireLong(it, ID)
val blob = CursorUtil.requireBlob(it, AVATAR)
val proto = CustomAvatar.parseFrom(blob)
results.add(proto.toAvatar(id))
}
}
return results
}
private fun Avatar.toProto(): CustomAvatar {
return when (this) {
is Avatar.Photo -> CustomAvatar.newBuilder().setPhoto(CustomAvatar.Photo.newBuilder().setUri(this.uri.toString())).build()
is Avatar.Text -> CustomAvatar.newBuilder().setText(CustomAvatar.Text.newBuilder().setText(this.text).setColors(this.color.code)).build()
is Avatar.Vector -> CustomAvatar.newBuilder().setVector(CustomAvatar.Vector.newBuilder().setKey(this.key).setColors(this.color.code)).build()
else -> throw AssertionError()
}
}
private fun CustomAvatar.toAvatar(id: Long): Avatar {
return when {
hasPhoto() -> Avatar.Photo(Uri.parse(photo.uri), photo.size, Avatar.DatabaseId.Saved(id))
hasText() -> Avatar.Text(text.text, Avatars.colorMap[text.colors] ?: Avatars.colors[0], Avatar.DatabaseId.Saved(id))
hasVector() -> Avatar.Vector(vector.key, Avatars.colorMap[vector.colors] ?: Avatars.colors[0], Avatar.DatabaseId.Saved(id))
else -> throw AssertionError()
}
}
}

Wyświetl plik

@ -9,6 +9,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.navigation.NavGraph;
import androidx.navigation.Navigation;
import androidx.navigation.fragment.NavHostFragment;
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
import org.thoughtcrime.securesms.R;
@ -46,9 +47,11 @@ public class AddGroupDetailsActivity extends PassphraseRequiredActivity implemen
if (bundle == null) {
ArrayList<RecipientId> recipientIds = getIntent().getParcelableArrayListExtra(EXTRA_RECIPIENTS);
AddGroupDetailsFragmentArgs arguments = new AddGroupDetailsFragmentArgs.Builder(recipientIds.toArray(new RecipientId[0])).build();
NavGraph graph = Navigation.findNavController(this, R.id.nav_host_fragment).getGraph();
NavHostFragment fragment = NavHostFragment.create(R.navigation.create_group, arguments.toBundle());
Navigation.findNavController(this, R.id.nav_host_fragment).setGraph(graph, arguments.toBundle());
getSupportFragmentManager().beginTransaction()
.replace(R.id.nav_host_fragment, fragment)
.commit();
}
}

Wyświetl plik

@ -18,25 +18,26 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.lifecycle.ViewModelProviders;
import androidx.navigation.Navigation;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import com.dd.CircularProgressButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.signal.core.util.EditTextUtil;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.avatar.picker.AvatarPickerFragment;
import org.thoughtcrime.securesms.components.settings.app.privacy.expire.ExpireTimerSettingsFragment;
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
import org.thoughtcrime.securesms.groups.ui.creategroup.dialogs.NonGv2MemberDialog;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity;
import org.thoughtcrime.securesms.mediasend.AvatarSelectionBottomSheetDialogFragment;
import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
import org.thoughtcrime.securesms.mms.GlideApp;
@ -58,7 +59,6 @@ import java.util.Objects;
public class AddGroupDetailsFragment extends LoggingFragment {
private static final int AVATAR_PLACEHOLDER_INSET_DP = 18;
private static final short REQUEST_CODE_AVATAR = 27621;
private static final short REQUEST_DISAPPEARING_TIMER = 28621;
private CircularProgressButton create;
@ -112,7 +112,7 @@ public class AddGroupDetailsFragment extends LoggingFragment {
initializeViewModel();
avatar.setOnClickListener(v -> showAvatarSelectionBottomSheet());
avatar.setOnClickListener(v -> showAvatarPicker());
members.setRecipientClickListener(this::handleRecipientClick);
EditTextUtil.addGraphemeClusterLimitFilter(name, FeatureFlags.getMaxGroupNameGraphemeLength());
name.addTextChangedListener(new AfterTextChanged(editable -> viewModel.setName(editable.toString())));
@ -154,44 +154,46 @@ public class AddGroupDetailsFragment extends LoggingFragment {
});
name.requestFocus();
getParentFragmentManager().setFragmentResultListener(AvatarPickerFragment.REQUEST_KEY_SELECT_AVATAR,
getViewLifecycleOwner(),
(key, bundle) -> handleMediaResult(bundle));
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == REQUEST_CODE_AVATAR && resultCode == Activity.RESULT_OK && data != null) {
if (data.getBooleanExtra("delete", false)) {
viewModel.setAvatar(null);
return;
}
final Media result = data.getParcelableExtra(AvatarSelectionActivity.EXTRA_MEDIA);
final DecryptableStreamUriLoader.DecryptableUri decryptableUri = new DecryptableStreamUriLoader.DecryptableUri(result.getUri());
GlideApp.with(this)
.asBitmap()
.load(decryptableUri)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop()
.override(AvatarHelper.AVATAR_DIMENSIONS, AvatarHelper.AVATAR_DIMENSIONS)
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
viewModel.setAvatar(Objects.requireNonNull(BitmapUtil.toByteArray(resource)));
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
} else if (requestCode == REQUEST_DISAPPEARING_TIMER && resultCode == Activity.RESULT_OK && data != null) {
if (requestCode == REQUEST_DISAPPEARING_TIMER && resultCode == Activity.RESULT_OK && data != null) {
viewModel.setDisappearingMessageTimer(data.getIntExtra(ExpireTimerSettingsFragment.FOR_RESULT_VALUE, SignalStore.settings().getUniversalExpireTimer()));
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
private void handleMediaResult(Bundle data) {
final Media result = data.getParcelable(AvatarPickerFragment.SELECT_AVATAR_MEDIA);
final DecryptableStreamUriLoader.DecryptableUri decryptableUri = new DecryptableStreamUriLoader.DecryptableUri(result.getUri());
viewModel.setAvatarMedia(result);
GlideApp.with(this)
.asBitmap()
.load(decryptableUri)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop()
.override(AvatarHelper.AVATAR_DIMENSIONS, AvatarHelper.AVATAR_DIMENSIONS)
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
viewModel.setAvatar(Objects.requireNonNull(BitmapUtil.toByteArray(resource)));
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
}
private void initializeViewModel() {
AddGroupDetailsFragmentArgs args = AddGroupDetailsFragmentArgs.fromBundle(requireArguments());
AddGroupDetailsRepository repository = new AddGroupDetailsRepository(requireContext());
@ -211,15 +213,15 @@ public class AddGroupDetailsFragment extends LoggingFragment {
}
private void handleRecipientClick(@NonNull Recipient recipient) {
new AlertDialog.Builder(requireContext())
.setMessage(getString(R.string.AddGroupDetailsFragment__remove_s_from_this_group, recipient.getDisplayName(requireContext())))
.setCancelable(true)
.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.cancel())
.setPositiveButton(R.string.AddGroupDetailsFragment__remove, (dialog, which) -> {
viewModel.delete(recipient.getId());
dialog.dismiss();
})
.show();
new MaterialAlertDialogBuilder(requireContext())
.setMessage(getString(R.string.AddGroupDetailsFragment__remove_s_from_this_group, recipient.getDisplayName(requireContext())))
.setCancelable(true)
.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.cancel())
.setPositiveButton(R.string.AddGroupDetailsFragment__remove, (dialog, which) -> {
viewModel.delete(recipient.getId());
dialog.dismiss();
})
.show();
}
private void handleGroupCreateResult(@NonNull GroupCreateResult groupCreateResult) {
@ -263,13 +265,15 @@ public class AddGroupDetailsFragment extends LoggingFragment {
.alpha(isEnabled ? 1f : 0.5f);
}
private void showAvatarSelectionBottomSheet() {
AvatarSelectionBottomSheetDialogFragment.create(viewModel.hasAvatar(), true, REQUEST_CODE_AVATAR, true)
.show(getChildFragmentManager(), "BOTTOM");
private void showAvatarPicker() {
Media media = viewModel.getAvatarMedia();
Navigation.findNavController(requireView()).navigate(AddGroupDetailsFragmentDirections.actionAddGroupDetailsFragmentToAvatarPicker(null, media).setIsNewGroup(true));
}
public interface Callback {
void onGroupCreated(@NonNull RecipientId recipientId, long threadId, @NonNull List<Recipient> invitedMembers);
void onNavigationButtonPressed();
}
}

Wyświetl plik

@ -15,6 +15,7 @@ import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
@ -42,6 +43,8 @@ public final class AddGroupDetailsViewModel extends ViewModel {
private final AddGroupDetailsRepository repository;
private final LiveData<List<Recipient>> nonGv2CapableMembers;
private Media avatarMedia;
private AddGroupDetailsViewModel(@NonNull Collection<RecipientId> recipientIds,
@NonNull AddGroupDetailsRepository repository)
{
@ -152,6 +155,14 @@ public final class AddGroupDetailsViewModel extends ViewModel {
disappearingMessagesTimer.setValue(timer);
}
public void setAvatarMedia(@Nullable Media media) {
this.avatarMedia = media;
}
public @Nullable Media getAvatarMedia() {
return avatarMedia;
}
static final class Factory implements ViewModelProvider.Factory {
private final Collection<RecipientId> recipientIds;

Wyświetl plik

@ -67,7 +67,8 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
private enum EditingPurpose {
IMAGE,
AVATAR_CIRCLE,
AVATAR_CAPTURE,
AVATAR_EDIT,
WALLPAPER
}
@ -95,8 +96,14 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
return new EditorModel(EditingPurpose.IMAGE, 0, EditorElementHierarchy.create());
}
public static EditorModel createForCircleEditing() {
EditorModel editorModel = new EditorModel(EditingPurpose.AVATAR_CIRCLE, 1, EditorElementHierarchy.createForCircleEditing());
public static EditorModel createForAvatarCapture() {
EditorModel editorModel = new EditorModel(EditingPurpose.AVATAR_CAPTURE, 1, EditorElementHierarchy.createForCircleEditing());
editorModel.setCropAspectLock(true);
return editorModel;
}
public static EditorModel createForAvatarEdit() {
EditorModel editorModel = new EditorModel(EditingPurpose.AVATAR_EDIT, 1, EditorElementHierarchy.createForCircleEditing());
editorModel.setCropAspectLock(true);
return editorModel;
}
@ -642,7 +649,7 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
if (imageCropMatrix.isIdentity()) {
imageCropMatrix.set(cropMatrix);
if (editingPurpose == EditingPurpose.AVATAR_CIRCLE || editingPurpose == EditingPurpose.WALLPAPER) {
if (editingPurpose == EditingPurpose.AVATAR_CAPTURE || editingPurpose == EditingPurpose.WALLPAPER || editingPurpose == EditingPurpose.AVATAR_EDIT) {
Matrix userCropMatrix = editorElementHierarchy.getCropEditorElement().getLocalMatrix();
if (size.x > size.y) {
userCropMatrix.setScale(fixedRatio * size.y / (float) size.x, 1f);
@ -658,7 +665,7 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
}
switch (editingPurpose) {
case AVATAR_CIRCLE: {
case AVATAR_CAPTURE: {
startCrop();
break;
}
@ -667,6 +674,8 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
startCrop();
break;
}
default:
break;
}
}
}

Wyświetl plik

@ -25,7 +25,7 @@ class InsightsUserAvatar {
}
private Drawable fallbackDrawable(@NonNull Context context) {
return fallbackContactPhoto.asDrawable(context, fallbackColor.colorInt());
return fallbackContactPhoto.asDrawable(context, fallbackColor);
}
void load(ImageView into) {

Wyświetl plik

@ -2,15 +2,14 @@ package org.thoughtcrime.securesms.mediaoverview
import android.graphics.Rect
import android.view.View
import androidx.annotation.Px
import androidx.recyclerview.widget.RecyclerView
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.components.recyclerview.GridDividerDecoration
internal class MediaGridDividerDecoration(
private val spanCount: Int,
@Px private val space: Int,
spanCount: Int,
space: Int,
private val adapter: MediaGalleryAllAdapter
) : RecyclerView.ItemDecoration() {
) : GridDividerDecoration(spanCount, space) {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val holder = parent.getChildViewHolder(view)
@ -28,32 +27,6 @@ internal class MediaGridDividerDecoration(
return
}
val column = itemSectionOffset % spanCount
val isRtl = ViewUtil.isRtl(view)
val distanceFromEnd = spanCount - 1 - column
val spaceStart = (column / spanCount.toFloat()) * space
val spaceEnd = (distanceFromEnd / spanCount.toFloat()) * space
outRect.setStart(spaceStart.toInt(), isRtl)
outRect.setEnd(spaceEnd.toInt(), isRtl)
outRect.bottom = space
}
private fun Rect.setEnd(end: Int, isRtl: Boolean) {
if (isRtl) {
left = end
} else {
right = end
}
}
private fun Rect.setStart(start: Int, isRtl: Boolean) {
if (isRtl) {
right = start
} else {
left = start
}
setItemOffsets(itemSectionOffset, view, outRect)
}
}

Wyświetl plik

@ -38,6 +38,7 @@ import com.google.android.material.tabs.TabLayout;
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AnimatingToggle;
import org.thoughtcrime.securesms.components.BoldSelectionTabItem;
import org.thoughtcrime.securesms.components.ControllableTabLayout;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MediaDatabase;
@ -98,8 +99,7 @@ public final class MediaOverviewActivity extends PassphraseRequiredActivity {
boolean allThreads = threadId == MediaDatabase.ALL_THREADS;
tabLayout.setNewTabListener(new NewTabListener());
tabLayout.addOnTabSelectedListener(new OnTabSelectedListener());
BoldSelectionTabItem.registerListeners(tabLayout);
fillTabLayoutIfFits(tabLayout);
tabLayout.setupWithViewPager(viewPager);
viewPager.setAdapter(new MediaOverviewPagerAdapter(getSupportFragmentManager()));
@ -286,34 +286,4 @@ public final class MediaOverviewActivity extends PassphraseRequiredActivity {
return pages.get(position).second();
}
}
private static final class NewTabListener implements ControllableTabLayout.NewTabListener {
@Override
public void onNewTab(@NonNull TabLayout.Tab tab) {
View customView = tab.getCustomView();
if (customView == null) {
tab.setCustomView(R.layout.media_overview_tab_item);
}
}
}
private static final class OnTabSelectedListener implements TabLayout.OnTabSelectedListener {
@Override
public void onTabSelected(@NonNull TabLayout.Tab tab) {
MediaOverviewTabItem view = (MediaOverviewTabItem) Objects.requireNonNull(tab.getCustomView());
view.select();
}
@Override
public void onTabUnselected(@NonNull TabLayout.Tab tab) {
MediaOverviewTabItem view = (MediaOverviewTabItem) Objects.requireNonNull(tab.getCustomView());
view.unselect();
}
@Override
public void onTabReselected(@NonNull TabLayout.Tab tab) {
// Intentionally Blank.
}
}
}

Wyświetl plik

@ -1,39 +0,0 @@
package org.thoughtcrime.securesms.mediaoverview
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import android.widget.TextView
import androidx.core.widget.doAfterTextChanged
import org.thoughtcrime.securesms.R
class MediaOverviewTabItem @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
private lateinit var unselectedTextView: TextView
private lateinit var selectedTextView: TextView
override fun onFinishInflate() {
super.onFinishInflate()
unselectedTextView = findViewById(android.R.id.text1)
selectedTextView = findViewById(R.id.text1_bold)
unselectedTextView.doAfterTextChanged {
selectedTextView.text = it
}
}
fun select() {
unselectedTextView.alpha = 0f
selectedTextView.alpha = 1f
}
fun unselect() {
unselectedTextView.alpha = 1f
selectedTextView.alpha = 0f
}
}

Wyświetl plik

@ -157,7 +157,7 @@ public class AvatarSelectionActivity extends AppCompatActivity implements Camera
currentMedia = media;
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, ImageEditorFragment.newInstanceForAvatar(media.getUri()), IMAGE_EDITOR)
.replace(R.id.fragment_container, ImageEditorFragment.newInstanceForAvatarCapture(media.getUri()), IMAGE_EDITOR)
.addToBackStack(IMAGE_EDITOR)
.commit();
}

Wyświetl plik

@ -1,234 +0,0 @@
package org.thoughtcrime.securesms.mediasend;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.core.content.ContextCompat;
import androidx.core.util.Consumer;
import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.RecyclerView;
import com.annimon.stream.Stream;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import org.thoughtcrime.securesms.ClearAvatarPromptActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.util.ThemeUtil;
import java.util.ArrayList;
import java.util.List;
public class AvatarSelectionBottomSheetDialogFragment extends BottomSheetDialogFragment {
private static final String ARG_OPTIONS = "options";
private static final String ARG_REQUEST_CODE = "request_code";
private static final String ARG_IS_GROUP = "is_group";
public static DialogFragment create(boolean includeClear, boolean includeCamera, short requestCode, boolean isGroup) {
DialogFragment fragment = new AvatarSelectionBottomSheetDialogFragment();
List<SelectionOption> selectionOptions = new ArrayList<>(3);
Bundle args = new Bundle();
if (includeCamera) {
selectionOptions.add(SelectionOption.CAPTURE);
}
selectionOptions.add(SelectionOption.GALLERY);
if (includeClear) {
selectionOptions.add(SelectionOption.DELETE);
}
String[] options = Stream.of(selectionOptions)
.map(SelectionOption::getCode)
.toArray(String[]::new);
args.putStringArray(ARG_OPTIONS, options);
args.putShort(ARG_REQUEST_CODE, requestCode);
args.putBoolean(ARG_IS_GROUP, isGroup);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
setStyle(DialogFragment.STYLE_NORMAL,
ThemeUtil.isDarkTheme(requireContext()) ? R.style.Theme_Signal_BottomSheetDialog_Fixed
: R.style.Theme_Signal_Light_BottomSheetDialog_Fixed);
super.onCreate(savedInstanceState);
if (getOptionsCount() == 1) {
askForPermissionIfNeededAndLaunch(getOptionsFromArguments().get(0));
}
}
@Override
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.avatar_selection_bottom_sheet_dialog_fragment, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
RecyclerView recyclerView = view.findViewById(R.id.avatar_selection_bottom_sheet_dialog_fragment_recycler);
recyclerView.setAdapter(new SelectionOptionAdapter(getOptionsFromArguments(), this::askForPermissionIfNeededAndLaunch));
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
@SuppressWarnings("ConstantConditions")
private int getOptionsCount() {
return requireArguments().getStringArray(ARG_OPTIONS).length;
}
@SuppressWarnings("ConstantConditions")
private List<SelectionOption> getOptionsFromArguments() {
String[] optionCodes = requireArguments().getStringArray(ARG_OPTIONS);
return Stream.of(optionCodes).map(SelectionOption::fromCode).toList();
}
private void askForPermissionIfNeededAndLaunch(@NonNull SelectionOption option) {
if (option == SelectionOption.CAPTURE) {
Permissions.with(this)
.request(Manifest.permission.CAMERA)
.ifNecessary()
.onAllGranted(() -> launchOptionAndDismiss(option))
.onAnyDenied(() -> Toast.makeText(requireContext(), R.string.AvatarSelectionBottomSheetDialogFragment__taking_a_photo_requires_the_camera_permission, Toast.LENGTH_SHORT)
.show())
.execute();
} else if (option == SelectionOption.GALLERY) {
Permissions.with(this)
.request(Manifest.permission.READ_EXTERNAL_STORAGE)
.ifNecessary()
.onAllGranted(() -> launchOptionAndDismiss(option))
.onAnyDenied(() -> Toast.makeText(requireContext(), R.string.AvatarSelectionBottomSheetDialogFragment__viewing_your_gallery_requires_the_storage_permission, Toast.LENGTH_SHORT)
.show())
.execute();
} else {
launchOptionAndDismiss(option);
}
}
private void launchOptionAndDismiss(@NonNull SelectionOption option) {
Intent intent = createIntent(requireContext(), option, requireArguments().getBoolean(ARG_IS_GROUP));
int requestCode = requireArguments().getShort(ARG_REQUEST_CODE);
if (getParentFragment() != null) {
requireParentFragment().startActivityForResult(intent, requestCode);
} else {
requireActivity().startActivityForResult(intent, requestCode);
}
dismiss();
}
private static Intent createIntent(@NonNull Context context, @NonNull SelectionOption selectionOption, boolean isGroup) {
switch (selectionOption) {
case CAPTURE:
return AvatarSelectionActivity.getIntentForCameraCapture(context);
case GALLERY:
return AvatarSelectionActivity.getIntentForGallery(context);
case DELETE:
return isGroup ? ClearAvatarPromptActivity.createForGroupProfilePhoto()
: ClearAvatarPromptActivity.createForUserProfilePhoto();
default:
throw new IllegalStateException("Unknown option: " + selectionOption);
}
}
private enum SelectionOption {
CAPTURE("capture", R.string.AvatarSelectionBottomSheetDialogFragment__take_photo, R.drawable.ic_camera_24),
GALLERY("gallery", R.string.AvatarSelectionBottomSheetDialogFragment__choose_from_gallery, R.drawable.ic_photo_24),
DELETE("delete", R.string.AvatarSelectionBottomSheetDialogFragment__remove_photo, R.drawable.ic_trash_24);
private final String code;
private final @StringRes int label;
private final @DrawableRes int icon;
SelectionOption(@NonNull String code, @StringRes int label, @DrawableRes int icon) {
this.code = code;
this.label = label;
this.icon = icon;
}
public @NonNull String getCode() {
return code;
}
static SelectionOption fromCode(@NonNull String code) {
for (SelectionOption option : values()) {
if (option.code.equals(code)) {
return option;
}
}
throw new IllegalStateException("Unknown option: " + code);
}
}
private static class SelectionOptionViewHolder extends RecyclerView.ViewHolder {
private final AppCompatTextView optionView;
SelectionOptionViewHolder(@NonNull View itemView, @NonNull Consumer<Integer> onClick) {
super(itemView);
itemView.setOnClickListener(v -> {
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
onClick.accept(getAdapterPosition());
}
});
optionView = (AppCompatTextView) itemView;
}
void bind(@NonNull SelectionOption selectionOption) {
optionView.setCompoundDrawablesWithIntrinsicBounds(ContextCompat.getDrawable(optionView.getContext(), selectionOption.icon), null, null, null);
optionView.setText(selectionOption.label);
}
}
private static class SelectionOptionAdapter extends RecyclerView.Adapter<SelectionOptionViewHolder> {
private final List<SelectionOption> options;
private final Consumer<SelectionOption> onOptionClicked;
private SelectionOptionAdapter(@NonNull List<SelectionOption> options, @NonNull Consumer<SelectionOption> onOptionClicked) {
this.options = options;
this.onOptionClicked = onOptionClicked;
}
@NonNull
@Override
public SelectionOptionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.avatar_selection_bottom_sheet_dialog_fragment_option, parent, false);
return new SelectionOptionViewHolder(view, (position) -> onOptionClicked.accept(options.get(position)));
}
@Override
public void onBindViewHolder(@NonNull SelectionOptionViewHolder holder, int position) {
holder.bind(options.get(position));
}
@Override
public int getItemCount() {
return options.size();
}
}
}

Wyświetl plik

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.mediasend;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.graphics.PorterDuff;
@ -395,6 +396,7 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med
}
}
@SuppressLint("MissingSuperCall")
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);

Wyświetl plik

@ -8,10 +8,10 @@ import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.apache.http.auth.AUTH;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.avatar.AvatarPickerStorage;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.emoji.EmojiFiles;
import org.thoughtcrime.securesms.providers.BlobProvider;
@ -24,22 +24,25 @@ import java.io.InputStream;
public class PartAuthority {
private static final String AUTHORITY = BuildConfig.APPLICATION_ID;
private static final String PART_URI_STRING = "content://" + AUTHORITY + "/part";
private static final String STICKER_URI_STRING = "content://" + AUTHORITY + "/sticker";
private static final String WALLPAPER_URI_STRING = "content://" + AUTHORITY + "/wallpaper";
private static final String EMOJI_URI_STRING = "content://" + AUTHORITY + "/emoji";
private static final Uri PART_CONTENT_URI = Uri.parse(PART_URI_STRING);
private static final Uri STICKER_CONTENT_URI = Uri.parse(STICKER_URI_STRING);
private static final Uri WALLPAPER_CONTENT_URI = Uri.parse(WALLPAPER_URI_STRING);
private static final Uri EMOJI_CONTENT_URI = Uri.parse(EMOJI_URI_STRING);
private static final String AUTHORITY = BuildConfig.APPLICATION_ID;
private static final String PART_URI_STRING = "content://" + AUTHORITY + "/part";
private static final String STICKER_URI_STRING = "content://" + AUTHORITY + "/sticker";
private static final String WALLPAPER_URI_STRING = "content://" + AUTHORITY + "/wallpaper";
private static final String EMOJI_URI_STRING = "content://" + AUTHORITY + "/emoji";
private static final String AVATAR_PICKER_URI_STRING = "content://" + AUTHORITY + "/avatar_picker";
private static final Uri PART_CONTENT_URI = Uri.parse(PART_URI_STRING);
private static final Uri STICKER_CONTENT_URI = Uri.parse(STICKER_URI_STRING);
private static final Uri WALLPAPER_CONTENT_URI = Uri.parse(WALLPAPER_URI_STRING);
private static final Uri EMOJI_CONTENT_URI = Uri.parse(EMOJI_URI_STRING);
private static final Uri AVATAR_PICKER_CONTENT_URI = Uri.parse(AVATAR_PICKER_URI_STRING);
private static final int PART_ROW = 1;
private static final int PERSISTENT_ROW = 2;
private static final int BLOB_ROW = 3;
private static final int STICKER_ROW = 4;
private static final int WALLPAPER_ROW = 5;
private static final int EMOJI_ROW = 6;
private static final int PART_ROW = 1;
private static final int PERSISTENT_ROW = 2;
private static final int BLOB_ROW = 3;
private static final int STICKER_ROW = 4;
private static final int WALLPAPER_ROW = 5;
private static final int EMOJI_ROW = 6;
private static final int AVATAR_PICKER_ROW = 7;
private static final UriMatcher uriMatcher;
@ -49,6 +52,7 @@ public class PartAuthority {
uriMatcher.addURI(AUTHORITY, "sticker/#", STICKER_ROW);
uriMatcher.addURI(AUTHORITY, "wallpaper/*", WALLPAPER_ROW);
uriMatcher.addURI(AUTHORITY, "emoji/*", EMOJI_ROW);
uriMatcher.addURI(AUTHORITY, "avatar_picker/*", AVATAR_PICKER_ROW);
uriMatcher.addURI(DeprecatedPersistentBlobProvider.AUTHORITY, DeprecatedPersistentBlobProvider.EXPECTED_PATH_OLD, PERSISTENT_ROW);
uriMatcher.addURI(DeprecatedPersistentBlobProvider.AUTHORITY, DeprecatedPersistentBlobProvider.EXPECTED_PATH_NEW, PERSISTENT_ROW);
uriMatcher.addURI(BlobProvider.AUTHORITY, BlobProvider.PATH, BLOB_ROW);
@ -66,13 +70,14 @@ public class PartAuthority {
int match = uriMatcher.match(uri);
try {
switch (match) {
case PART_ROW: return DatabaseFactory.getAttachmentDatabase(context).getAttachmentStream(new PartUriParser(uri).getPartId(), 0);
case STICKER_ROW: return DatabaseFactory.getStickerDatabase(context).getStickerStream(ContentUris.parseId(uri));
case PERSISTENT_ROW: return DeprecatedPersistentBlobProvider.getInstance(context).getStream(context, ContentUris.parseId(uri));
case BLOB_ROW: return BlobProvider.getInstance().getStream(context, uri);
case WALLPAPER_ROW: return WallpaperStorage.read(context, getWallpaperFilename(uri));
case EMOJI_ROW: return EmojiFiles.openForReading(context, getEmojiFilename(uri));
default: return context.getContentResolver().openInputStream(uri);
case PART_ROW: return DatabaseFactory.getAttachmentDatabase(context).getAttachmentStream(new PartUriParser(uri).getPartId(), 0);
case STICKER_ROW: return DatabaseFactory.getStickerDatabase(context).getStickerStream(ContentUris.parseId(uri));
case PERSISTENT_ROW: return DeprecatedPersistentBlobProvider.getInstance(context).getStream(context, ContentUris.parseId(uri));
case BLOB_ROW: return BlobProvider.getInstance().getStream(context, uri);
case WALLPAPER_ROW: return WallpaperStorage.read(context, getWallpaperFilename(uri));
case EMOJI_ROW: return EmojiFiles.openForReading(context, getEmojiFilename(uri));
case AVATAR_PICKER_ROW: return AvatarPickerStorage.read(context, getAvatarPickerFilename(uri));
default: return context.getContentResolver().openInputStream(uri);
}
} catch (SecurityException se) {
throw new IOException(se);
@ -169,6 +174,10 @@ public class PartAuthority {
return Uri.withAppendedPath(WALLPAPER_CONTENT_URI, filename);
}
public static Uri getAvatarPickerUri(String filename) {
return Uri.withAppendedPath(AVATAR_PICKER_CONTENT_URI, filename);
}
public static Uri getEmojiUri(String sprite) {
return Uri.withAppendedPath(EMOJI_CONTENT_URI, sprite);
}
@ -181,6 +190,10 @@ public class PartAuthority {
return uri.getPathSegments().get(1);
}
public static String getAvatarPickerFilename(Uri uri) {
return uri.getPathSegments().get(1);
}
public static boolean isLocalUri(final @NonNull Uri uri) {
int match = uriMatcher.match(uri);
switch (match) {

Wyświetl plik

@ -52,7 +52,7 @@ data class NotificationConversation(
return if (SignalStore.settings().messageNotificationsPrivacy.isDisplayContact) {
recipient.getContactDrawable(context)
} else {
GeneratedContactPhoto("Unknown", R.drawable.ic_profile_outline_40).asDrawable(context, AvatarColor.UNKNOWN.colorInt())
GeneratedContactPhoto("Unknown", R.drawable.ic_profile_outline_40).asDrawable(context, AvatarColor.UNKNOWN)
}
}

Wyświetl plik

@ -56,12 +56,12 @@ fun Recipient.getContactDrawable(context: Context): Drawable? {
)
.get()
} catch (e: InterruptedException) {
fallbackContactPhoto.asDrawable(context, avatarColor.colorInt())
fallbackContactPhoto.asDrawable(context, avatarColor)
} catch (e: ExecutionException) {
fallbackContactPhoto.asDrawable(context, avatarColor.colorInt())
fallbackContactPhoto.asDrawable(context, avatarColor)
}
} else {
fallbackContactPhoto.asDrawable(context, avatarColor.colorInt())
fallbackContactPhoto.asDrawable(context, avatarColor)
}
}

Wyświetl plik

@ -14,6 +14,7 @@ import android.view.Display;
import android.view.ViewGroup;
import android.view.WindowManager;
import androidx.activity.result.ActivityResultCallback;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;

Wyświetl plik

@ -9,6 +9,7 @@ import androidx.core.util.Consumer;
import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.groups.GroupChangeException;
import org.thoughtcrime.securesms.groups.GroupId;
@ -34,6 +35,11 @@ class EditGroupProfileRepository implements EditProfileRepository {
this.groupId = groupId;
}
@Override
public void getCurrentAvatarColor(@NonNull Consumer<AvatarColor> avatarColorConsumer) {
SimpleTask.run(() -> Recipient.resolved(getRecipientId()).getAvatarColor(), avatarColorConsumer::accept);
}
@Override
public void getCurrentProfileName(@NonNull Consumer<ProfileName> profileNameConsumer) {
profileNameConsumer.accept(ProfileName.EMPTY);

Wyświetl plik

@ -10,6 +10,7 @@ import androidx.annotation.NonNull;
import androidx.navigation.NavDirections;
import androidx.navigation.NavGraph;
import androidx.navigation.Navigation;
import androidx.navigation.fragment.NavHostFragment;
import org.thoughtcrime.securesms.BaseActivity;
import org.thoughtcrime.securesms.R;
@ -61,10 +62,10 @@ public class EditProfileActivity extends BaseActivity implements EditProfileFrag
setContentView(R.layout.profile_create_activity);
if (bundle == null) {
Bundle extras = getIntent().getExtras();
NavGraph graph = Navigation.findNavController(this, R.id.nav_host_fragment).getGraph();
Navigation.findNavController(this, R.id.nav_host_fragment).setGraph(graph, extras != null ? extras : new Bundle());
NavHostFragment fragment = NavHostFragment.create(R.navigation.edit_profile, getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, fragment)
.commit();
}
}

Wyświetl plik

@ -5,6 +5,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.View;
@ -20,7 +21,9 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.widget.Toolbar;
import androidx.lifecycle.ViewModelProviders;
import androidx.navigation.Navigation;
import com.airbnb.lottie.SimpleColorFilter;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.dd.CircularProgressButton;
@ -29,11 +32,10 @@ import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.avatar.Avatars;
import org.thoughtcrime.securesms.avatar.picker.AvatarPickerFragment;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity;
import org.thoughtcrime.securesms.mediasend.AvatarSelectionBottomSheetDialogFragment;
import org.thoughtcrime.securesms.groups.ParcelableGroupId;
import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.profiles.manage.EditProfileNameFragment;
@ -47,7 +49,6 @@ import org.thoughtcrime.securesms.util.views.LearnMoreTextView;
import java.io.IOException;
import java.io.InputStream;
import static android.app.Activity.RESULT_OK;
import static org.thoughtcrime.securesms.profiles.edit.EditProfileActivity.EXCLUDE_SYSTEM;
import static org.thoughtcrime.securesms.profiles.edit.EditProfileActivity.GROUP_ID;
import static org.thoughtcrime.securesms.profiles.edit.EditProfileActivity.NEXT_BUTTON_TEXT;
@ -57,7 +58,6 @@ import static org.thoughtcrime.securesms.profiles.edit.EditProfileActivity.SHOW_
public class EditProfileFragment extends LoggingFragment {
private static final String TAG = Log.tag(EditProfileFragment.class);
private static final short REQUEST_CODE_SELECT_AVATAR = 31726;
private static final int MAX_DESCRIPTION_GLYPHS = 480;
private static final int MAX_DESCRIPTION_BYTES = 8192;
@ -69,6 +69,8 @@ public class EditProfileFragment extends LoggingFragment {
private EditText familyName;
private View reveal;
private TextView preview;
private ImageView avatarPreviewBackground;
private ImageView avatarPreview;
private Intent nextIntent;
@ -100,45 +102,38 @@ public class EditProfileFragment extends LoggingFragment {
initializeResources(view, groupId);
initializeProfileAvatar();
initializeProfileName();
getParentFragmentManager().setFragmentResultListener(AvatarPickerFragment.REQUEST_KEY_SELECT_AVATAR, getViewLifecycleOwner(), (key, bundle) -> {
Media media = bundle.getParcelable(AvatarPickerFragment.SELECT_AVATAR_MEDIA);
handleMediaFromResult(media);
});
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
private void handleMediaFromResult(@NonNull Media media) {
SimpleTask.run(() -> {
try {
InputStream stream = BlobProvider.getInstance().getStream(requireContext(), media.getUri());
if (requestCode == REQUEST_CODE_SELECT_AVATAR && resultCode == RESULT_OK) {
if (data != null && data.getBooleanExtra("delete", false)) {
viewModel.setAvatar(null);
avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_camera_solid_white_24).asDrawable(requireActivity(), AvatarColor.UNKNOWN.colorInt()));
return;
return StreamUtil.readFully(stream);
} catch (IOException ioException) {
Log.w(TAG, ioException);
return null;
}
SimpleTask.run(() -> {
try {
Media result = data.getParcelableExtra(AvatarSelectionActivity.EXTRA_MEDIA);
InputStream stream = BlobProvider.getInstance().getStream(requireContext(), result.getUri());
return StreamUtil.readFully(stream);
} catch (IOException ioException) {
Log.w(TAG, ioException);
return null;
}
},
(avatarBytes) -> {
if (avatarBytes != null) {
viewModel.setAvatar(avatarBytes);
GlideApp.with(EditProfileFragment.this)
.load(avatarBytes)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.circleCrop()
.into(avatar);
} else {
Toast.makeText(requireActivity(), R.string.CreateProfileActivity_error_setting_profile_photo, Toast.LENGTH_LONG).show();
}
});
}
},
(avatarBytes) -> {
if (avatarBytes != null) {
viewModel.setAvatarMedia(media);
viewModel.setAvatar(avatarBytes);
GlideApp.with(EditProfileFragment.this)
.load(avatarBytes)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.circleCrop()
.into(avatar);
} else {
Toast.makeText(requireActivity(), R.string.CreateProfileActivity_error_setting_profile_photo, Toast.LENGTH_LONG).show();
}
});
}
private void initializeViewModel(boolean excludeSystem, @Nullable GroupId groupId, boolean hasSavedInstanceState) {
@ -160,15 +155,17 @@ public class EditProfileFragment extends LoggingFragment {
Bundle arguments = requireArguments();
boolean isEditingGroup = groupId != null;
this.toolbar = view.findViewById(R.id.toolbar);
this.title = view.findViewById(R.id.title);
this.avatar = view.findViewById(R.id.avatar);
this.givenName = view.findViewById(R.id.given_name);
this.familyName = view.findViewById(R.id.family_name);
this.finishButton = view.findViewById(R.id.finish_button);
this.reveal = view.findViewById(R.id.reveal);
this.preview = view.findViewById(R.id.name_preview);
this.nextIntent = arguments.getParcelable(NEXT_INTENT);
this.toolbar = view.findViewById(R.id.toolbar);
this.title = view.findViewById(R.id.title);
this.avatar = view.findViewById(R.id.avatar);
this.givenName = view.findViewById(R.id.given_name);
this.familyName = view.findViewById(R.id.family_name);
this.finishButton = view.findViewById(R.id.finish_button);
this.reveal = view.findViewById(R.id.reveal);
this.preview = view.findViewById(R.id.name_preview);
this.avatarPreviewBackground = view.findViewById(R.id.avatar_background);
this.avatarPreview = view.findViewById(R.id.avatar_placeholder);
this.nextIntent = arguments.getParcelable(NEXT_INTENT);
this.avatar.setOnClickListener(v -> startAvatarSelection());
@ -255,6 +252,13 @@ public class EditProfileFragment extends LoggingFragment {
.circleCrop()
.into(avatar);
});
viewModel.avatarColor().observe(getViewLifecycleOwner(), avatarColor -> {
Avatars.ForegroundColor foregroundColor = Avatars.getForegroundColor(avatarColor);
avatarPreview.getDrawable().setColorFilter(new SimpleColorFilter(foregroundColor.getColorInt()));
avatarPreviewBackground.getDrawable().setColorFilter(new SimpleColorFilter(avatarColor.colorInt()));
});
}
private static void updateFieldIfNeeded(@NonNull EditText field, @NonNull String value) {
@ -273,11 +277,12 @@ public class EditProfileFragment extends LoggingFragment {
}
private void startAvatarSelection() {
AvatarSelectionBottomSheetDialogFragment.create(viewModel.canRemoveProfilePhoto(),
true,
REQUEST_CODE_SELECT_AVATAR,
viewModel.isGroup())
.show(getChildFragmentManager(), null);
if (viewModel.isGroup()) {
Parcelable groupId = ParcelableGroupId.from(viewModel.getGroupId());
Navigation.findNavController(requireView()).navigate(EditProfileFragmentDirections.actionCreateProfileFragmentToAvatarPicker((ParcelableGroupId) groupId, viewModel.getAvatarMedia()));
} else {
Navigation.findNavController(requireView()).navigate(EditProfileFragmentDirections.actionCreateProfileFragmentToAvatarPicker(null, null));
}
}
private void handleUpload() {

Wyświetl plik

@ -4,11 +4,14 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Consumer;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.whispersystems.libsignal.util.guava.Optional;
interface EditProfileRepository {
void getCurrentAvatarColor(@NonNull Consumer<AvatarColor> avatarColorConsumer);
void getCurrentProfileName(@NonNull Consumer<ProfileName> profileNameConsumer);
void getCurrentAvatar(@NonNull Consumer<byte[]> avatarConsumer);

Wyświetl plik

@ -8,7 +8,9 @@ import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.profiles.edit.EditProfileRepository.UploadResult;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
@ -29,10 +31,12 @@ class EditProfileViewModel extends ViewModel {
private final MutableLiveData<byte[]> originalAvatar = new MutableLiveData<>();
private final MutableLiveData<String> originalDisplayName = new MutableLiveData<>();
private final SingleLiveEvent<UploadResult> uploadResult = new SingleLiveEvent<>();
private final MutableLiveData<AvatarColor> avatarColor = new MutableLiveData<>();
private final LiveData<Boolean> isFormValid;
private final EditProfileRepository repository;
private final GroupId groupId;
private String originalDescription;
private Media avatarMedia;
private EditProfileViewModel(@NonNull EditProfileRepository repository, boolean hasInstanceState, @Nullable GroupId groupId) {
this.repository = repository;
@ -59,9 +63,15 @@ class EditProfileViewModel extends ViewModel {
internalAvatar.setValue(value);
originalAvatar.setValue(value);
});
repository.getCurrentAvatarColor(avatarColor::setValue);
}
}
public LiveData<AvatarColor> avatarColor() {
return Transformations.distinctUntilChanged(avatarColor);
}
public LiveData<String> givenName() {
return Transformations.distinctUntilChanged(givenName);
}
@ -90,6 +100,18 @@ class EditProfileViewModel extends ViewModel {
return groupId != null;
}
public @Nullable Media getAvatarMedia() {
return avatarMedia;
}
public void setAvatarMedia(@Nullable Media avatarMedia) {
this.avatarMedia = avatarMedia;
}
public @Nullable GroupId getGroupId() {
return groupId;
}
public boolean canRemoveProfilePhoto() {
return hasAvatar();
}

Wyświetl plik

@ -9,6 +9,7 @@ import androidx.core.util.Consumer;
import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileContentUpdateJob;
@ -42,6 +43,11 @@ public class EditSelfProfileRepository implements EditProfileRepository {
this.excludeSystem = excludeSystem;
}
@Override
public void getCurrentAvatarColor(@NonNull Consumer<AvatarColor> avatarColorConsumer) {
SimpleTask.run(() -> Recipient.self().getAvatarColor(), avatarColorConsumer::accept);
}
@Override
public void getCurrentProfileName(@NonNull Consumer<ProfileName> profileNameConsumer) {
ProfileName storedProfileName = Recipient.self().getProfileName();

Wyświetl plik

@ -23,9 +23,9 @@ import com.bumptech.glide.Glide;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.avatar.picker.AvatarPickerFragment;
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity;
import org.thoughtcrime.securesms.mediasend.AvatarSelectionBottomSheetDialogFragment;
import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.profiles.manage.ManageProfileViewModel.AvatarState;
@ -35,8 +35,7 @@ import static android.app.Activity.RESULT_OK;
public class ManageProfileFragment extends LoggingFragment {
private static final String TAG = Log.tag(ManageProfileFragment.class);
private static final short REQUEST_CODE_SELECT_AVATAR = 31726;
private static final String TAG = Log.tag(ManageProfileFragment.class);
private Toolbar toolbar;
private ImageView avatarView;
@ -86,22 +85,11 @@ public class ManageProfileFragment extends LoggingFragment {
this.aboutContainer.setOnClickListener(v -> {
Navigation.findNavController(v).navigate(ManageProfileFragmentDirections.actionManageAbout());
});
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_SELECT_AVATAR && resultCode == RESULT_OK) {
if (data != null && data.getBooleanExtra("delete", false)) {
viewModel.onAvatarSelected(requireContext(), null);
return;
}
Media result = data.getParcelableExtra(AvatarSelectionActivity.EXTRA_MEDIA);
getParentFragmentManager().setFragmentResultListener(AvatarPickerFragment.REQUEST_KEY_SELECT_AVATAR, getViewLifecycleOwner(), (key, bundle) -> {
Media result = bundle.getParcelable(AvatarPickerFragment.SELECT_AVATAR_MEDIA);
viewModel.onAvatarSelected(requireContext(), result);
}
});
}
private void initializeViewModel() {
@ -193,10 +181,6 @@ public class ManageProfileFragment extends LoggingFragment {
}
private void onAvatarClicked() {
AvatarSelectionBottomSheetDialogFragment.create(viewModel.canRemoveAvatar(),
true,
REQUEST_CODE_SELECT_AVATAR,
false)
.show(getChildFragmentManager(), null);
Navigation.findNavController(requireView()).navigate(ManageProfileFragmentDirections.actionManageProfileFragmentToAvatarPicker(null, null));
}
}

Wyświetl plik

@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto20dp;
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.ViewUtil;
@ -122,7 +123,7 @@ public class ReviewBannerView extends LinearLayout {
}
@Override
protected Drawable newFallbackDrawable(@NonNull Context context, int color, boolean inverted) {
protected Drawable newFallbackDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return new FallbackPhoto20dp(getFallbackResId()).asDrawable(context, color, inverted);
}
}

Wyświetl plik

@ -793,11 +793,11 @@ public class Recipient {
}
public @NonNull Drawable getFallbackContactPhotoDrawable(Context context, boolean inverted, @Nullable FallbackPhotoProvider fallbackPhotoProvider) {
return getFallbackContactPhoto(Util.firstNonNull(fallbackPhotoProvider, DEFAULT_FALLBACK_PHOTO_PROVIDER)).asDrawable(context, avatarColor.colorInt(), inverted);
return getFallbackContactPhoto(Util.firstNonNull(fallbackPhotoProvider, DEFAULT_FALLBACK_PHOTO_PROVIDER)).asDrawable(context, avatarColor, inverted);
}
public @NonNull Drawable getSmallFallbackContactPhotoDrawable(Context context, boolean inverted, @Nullable FallbackPhotoProvider fallbackPhotoProvider) {
return getFallbackContactPhoto(Util.firstNonNull(fallbackPhotoProvider, DEFAULT_FALLBACK_PHOTO_PROVIDER)).asSmallDrawable(context, avatarColor.colorInt(), inverted);
return getFallbackContactPhoto(Util.firstNonNull(fallbackPhotoProvider, DEFAULT_FALLBACK_PHOTO_PROVIDER)).asSmallDrawable(context, avatarColor, inverted);
}
public @NonNull FallbackContactPhoto getFallbackContactPhoto() {

Wyświetl plik

@ -144,7 +144,7 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
avatar.setFallbackPhotoProvider(new Recipient.FallbackPhotoProvider() {
@Override
public @NonNull FallbackContactPhoto getPhotoForLocalNumber() {
return new FallbackPhoto80dp(R.drawable.ic_note_80, recipient.getAvatarColor().colorInt());
return new FallbackPhoto80dp(R.drawable.ic_note_80, recipient.getAvatarColor());
}
});
avatar.setAvatar(recipient);

Wyświetl plik

@ -16,6 +16,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
@ -51,6 +52,7 @@ import org.whispersystems.libsignal.util.Pair;
import java.io.ByteArrayOutputStream;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import static android.app.Activity.RESULT_OK;
@ -60,8 +62,8 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
private static final String TAG = Log.tag(ImageEditorFragment.class);
private static final String KEY_IMAGE_URI = "image_uri";
private static final String KEY_IS_AVATAR_MODE = "avatar_mode";
private static final String KEY_IMAGE_URI = "image_uri";
private static final String KEY_MODE = "mode";
private static final int SELECT_STICKER_REQUEST_CODE = 124;
@ -104,15 +106,22 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
private ImageEditorHud imageEditorHud;
private ImageEditorView imageEditorView;
public static ImageEditorFragment newInstanceForAvatar(@NonNull Uri imageUri) {
public static ImageEditorFragment newInstanceForAvatarCapture(@NonNull Uri imageUri) {
ImageEditorFragment fragment = newInstance(imageUri);
fragment.requireArguments().putBoolean(KEY_IS_AVATAR_MODE, true);
fragment.requireArguments().putString(KEY_MODE, Mode.AVATAR_CAPTURE.code);
return fragment;
}
public static ImageEditorFragment newInstanceForAvatarEdit(@NonNull Uri imageUri) {
ImageEditorFragment fragment = newInstance(imageUri);
fragment.requireArguments().putString(KEY_MODE, Mode.AVATAR_EDIT.code);
return fragment;
}
public static ImageEditorFragment newInstance(@NonNull Uri imageUri) {
Bundle args = new Bundle();
args.putParcelable(KEY_IMAGE_URI, imageUri);
args.putString(KEY_MODE, Mode.NORMAL.code);
ImageEditorFragment fragment = new ImageEditorFragment();
fragment.setArguments(args);
@ -123,10 +132,16 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!(getActivity() instanceof Controller)) {
Fragment parent = getParentFragment();
if (parent instanceof Controller) {
controller = (Controller) parent;
} else if (getActivity() instanceof Controller) {
controller = (Controller) getActivity();
} else {
throw new IllegalStateException("Parent activity must implement Controller interface.");
}
controller = (Controller) getActivity();
Bundle arguments = getArguments();
if (arguments != null) {
imageUri = arguments.getParcelable(KEY_IMAGE_URI);
@ -152,7 +167,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
boolean isAvatarMode = requireArguments().getBoolean(KEY_IS_AVATAR_MODE, false);
Mode mode = Mode.getByCode(requireArguments().getString(KEY_MODE));
imageEditorHud = view.findViewById(R.id.scribble_hud);
imageEditorView = view.findViewById(R.id.image_editor_view);
@ -171,14 +186,28 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
}
if (editorModel == null) {
editorModel = isAvatarMode ? EditorModel.createForCircleEditing() : EditorModel.create();
switch (mode) {
case AVATAR_EDIT:
editorModel = EditorModel.createForAvatarEdit();
break;
case AVATAR_CAPTURE:
editorModel = EditorModel.createForAvatarCapture();
break;
default:
editorModel = EditorModel.create();
break;
}
EditorElement image = new EditorElement(new UriGlideRenderer(imageUri, true, imageMaxWidth, imageMaxHeight));
image.getFlags().setSelectable(false).persist();
editorModel.addElement(image);
}
if (isAvatarMode) {
if (mode == Mode.AVATAR_CAPTURE || mode == Mode.AVATAR_EDIT) {
imageEditorHud.setUpForAvatarEditing();
}
if (mode == Mode.AVATAR_CAPTURE) {
imageEditorHud.enterMode(ImageEditorHud.Mode.CROP);
}
@ -460,24 +489,27 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
}
private void performSaveToDisk() {
SimpleTask.run(() -> {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Bitmap image = imageEditorView.getModel().render(requireContext());
image.compress(Bitmap.CompressFormat.JPEG, 80, outputStream);
return BlobProvider.getInstance()
.forData(outputStream.toByteArray())
.withMimeType(MediaUtil.IMAGE_JPEG)
.createForSingleUseInMemory();
}, uri -> {
SimpleTask.run(this::renderToSingleUseBlob, uri -> {
SaveAttachmentTask saveTask = new SaveAttachmentTask(requireContext());
SaveAttachmentTask.Attachment attachment = new SaveAttachmentTask.Attachment(uri, MediaUtil.IMAGE_JPEG, System.currentTimeMillis(), null);
saveTask.executeOnExecutor(SignalExecutors.BOUNDED, attachment);
});
}
@WorkerThread
public @NonNull Uri renderToSingleUseBlob() {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Bitmap image = imageEditorView.getModel().render(requireContext());
image.compress(Bitmap.CompressFormat.JPEG, 80, outputStream);
image.recycle();
return BlobProvider.getInstance()
.forData(outputStream.toByteArray())
.withMimeType(MediaUtil.IMAGE_JPEG)
.createForSingleUseInMemory();
}
private void refreshUniqueColors() {
imageEditorHud.setColorPalette(imageEditorView.getModel().getUniqueColorsIgnoringAlpha());
}
@ -587,4 +619,35 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
this.position.preConcat(imageProjectionMatrix);
}
}
private enum Mode {
NORMAL("normal"),
AVATAR_CAPTURE("avatar_capture"),
AVATAR_EDIT("avatar_edit");
private final String code;
Mode(@NonNull String code) {
this.code = code;
}
String getCode() {
return code;
}
static Mode getByCode(@Nullable String code) {
if (code == null) {
return NORMAL;
}
for (Mode mode : values()) {
if (Objects.equals(code, mode.code)) {
return mode;
}
}
return NORMAL;
}
}
}

Wyświetl plik

@ -154,6 +154,6 @@ public final class AvatarUtil {
private static Drawable getFallback(@NonNull Context context, @NonNull Recipient recipient) {
String name = Optional.fromNullable(recipient.getDisplayName(context)).or("");
return new GeneratedContactPhoto(name, R.drawable.ic_profile_outline_40).asDrawable(context, recipient.getAvatarColor().colorInt());
return new GeneratedContactPhoto(name, R.drawable.ic_profile_outline_40).asDrawable(context, recipient.getAvatarColor());
}
}

Wyświetl plik

@ -13,7 +13,6 @@ import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
@ -23,9 +22,8 @@ import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto80dp;
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.SystemContactPhoto;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequest;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.whispersystems.libsignal.util.ByteUtil;
@ -186,8 +184,8 @@ public final class ConversationShortcutPhoto implements Key {
photoSource = R.drawable.ic_profile_80;
}
FallbackContactPhoto photo = recipient.isSelf() || recipient.isGroup() ? new FallbackPhoto80dp(photoSource, recipient.getAvatarColor().colorInt())
: new ShortcutGeneratedContactPhoto(recipient.getDisplayName(context), photoSource, ViewUtil.dpToPx(80), ViewUtil.dpToPx(28), recipient.getAvatarColor().colorInt());
FallbackContactPhoto photo = recipient.isSelf() || recipient.isGroup() ? new FallbackPhoto80dp(photoSource, recipient.getAvatarColor())
: new ShortcutGeneratedContactPhoto(recipient.getDisplayName(context), photoSource, ViewUtil.dpToPx(80), recipient.getAvatarColor());
Bitmap toWrap = DrawableUtil.toBitmap(photo.asCallCard(context), ViewUtil.dpToPx(80), ViewUtil.dpToPx(80));
Bitmap wrapped = DrawableUtil.wrapBitmapForShortcutInfo(toWrap);
@ -199,20 +197,20 @@ public final class ConversationShortcutPhoto implements Key {
private static final class ShortcutGeneratedContactPhoto extends GeneratedContactPhoto {
private final int color;
private final AvatarColor color;
public ShortcutGeneratedContactPhoto(@NonNull String name, int fallbackResId, int targetSize, int fontSize, int color) {
super(name, fallbackResId, targetSize, fontSize);
public ShortcutGeneratedContactPhoto(@NonNull String name, int fallbackResId, int targetSize, @NonNull AvatarColor color) {
super(name, fallbackResId, targetSize);
this.color = color;
}
@Override
protected Drawable newFallbackDrawable(@NonNull Context context, int color, boolean inverted) {
return new FallbackPhoto80dp(getFallbackResId(), color).asDrawable(context, -1);
protected Drawable newFallbackDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return new FallbackPhoto80dp(getFallbackResId(), color).asDrawable(context, AvatarColor.UNKNOWN);
}
@Override public Drawable asCallCard(Context context) {
@Override public Drawable asCallCard(@NonNull Context context) {
return new FallbackPhoto80dp(getFallbackResId(), color).asCallCard(context);
}
}

Wyświetl plik

@ -7,6 +7,7 @@ import android.view.ViewGroup;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
@ -33,16 +34,16 @@ import kotlin.jvm.functions.Function1;
* override compiler typing recommendations when binding and diffing.
* <p></p>
* General pattern for implementation:
* <ol>
* <li>Create {@link MappingModel}s for the items in the list. These encapsulate data massaging methods for views to use and the diff logic.</li>
* <li>Create {@link MappingViewHolder}s for each item type in the list and their corresponding {@link Factory}.</li>
* <li>Create an instance or subclass of {@link MappingAdapter} and register the mapping of model type to view holder factory for that model type.</li>
* </ol>
* Event listeners, click or otherwise, are handled at the view holder level and should be passed into the appropriate view holder factories. This
* pattern mimics how we pass data into view models via factories.
* <p></p>
* NOTE: There can only be on factory registered per model type. Registering two for the same type will result in the last one being used. However, the
* same factory can be registered multiple times for multiple model types (if the model type class hierarchy supports it).
* <ol>
* <li>Create {@link MappingModel}s for the items in the list. These encapsulate data massaging methods for views to use and the diff logic.</li>
* <li>Create {@link MappingViewHolder}s for each item type in the list and their corresponding {@link Factory}.</li>
* <li>Create an instance or subclass of {@link MappingAdapter} and register the mapping of model type to view holder factory for that model type.</li>
* </ol>
* Event listeners, click or otherwise, are handled at the view holder level and should be passed into the appropriate view holder factories. This
* pattern mimics how we pass data into view models via factories.
* <p></p>
* NOTE: There can only be on factory registered per model type. Registering two for the same type will result in the last one being used. However, the
* same factory can be registered multiple times for multiple model types (if the model type class hierarchy supports it).
*/
public class MappingAdapter extends ListAdapter<MappingModel<?>, MappingViewHolder<?>> {
@ -102,6 +103,12 @@ public class MappingAdapter extends ListAdapter<MappingModel<?>, MappingViewHold
return Objects.requireNonNull(factories.get(viewType)).createViewHolder(parent);
}
@Override
public void onBindViewHolder(@NonNull MappingViewHolder<?> holder, int position, @NonNull List<Object> payloads) {
holder.setPayload(payloads);
onBindViewHolder(holder, position);
}
@Override
public void onBindViewHolder(@NonNull MappingViewHolder holder, int position) {
//noinspection unchecked
@ -142,6 +149,16 @@ public class MappingAdapter extends ListAdapter<MappingModel<?>, MappingViewHold
}
return false;
}
@Override
public @Nullable Object getChangePayload(@NonNull MappingModel oldItem, @NonNull MappingModel newItem) {
if (oldItem.getClass() == newItem.getClass()) {
//noinspection unchecked
return oldItem.getChangePayload(newItem);
}
return null;
}
}
public interface Factory<T extends MappingModel<T>> {

Wyświetl plik

@ -1,8 +1,13 @@
package org.thoughtcrime.securesms.util;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public interface MappingModel<T> {
boolean areItemsTheSame(@NonNull T newItem);
boolean areContentsTheSame(@NonNull T newItem);
default @Nullable Object getChangePayload(@NonNull T newItem) {
return null;
}
}

Wyświetl plik

@ -7,13 +7,18 @@ import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.lifecycle.LifecycleOwner;
import java.util.LinkedList;
import java.util.List;
public abstract class MappingViewHolder<Model extends MappingModel<Model>> extends LifecycleViewHolder implements LifecycleOwner {
protected final Context context;
protected final Context context;
protected final List<Object> payload;
public MappingViewHolder(@NonNull View itemView) {
super(itemView);
context = itemView.getContext();
payload = new LinkedList<>();
}
public <T extends View> T findViewById(@IdRes int id) {
@ -26,6 +31,11 @@ public abstract class MappingViewHolder<Model extends MappingModel<Model>> exten
public abstract void bind(@NonNull Model model);
public void setPayload(@NonNull List<Object> payload) {
this.payload.clear();
this.payload.addAll(payload);
}
public static final class SimpleViewHolder<Model extends MappingModel<Model>> extends MappingViewHolder<Model> {
public SimpleViewHolder(@NonNull View itemView) {
super(itemView);

Wyświetl plik

@ -0,0 +1,34 @@
package org.thoughtcrime.securesms.util
import android.text.TextUtils
import java.util.regex.Pattern
object NameUtil {
private val PATTERN = Pattern.compile("[^\\p{L}\\p{Nd}\\p{S}]+")
/**
* Returns an abbreviation of the input, up to two characters long.
*/
@JvmStatic
fun getAbbreviation(name: String): String? {
val parts = name.split(" ").toTypedArray()
val builder = StringBuilder()
var count = 0
var i = 0
while (i < parts.size && count < 2) {
val cleaned = PATTERN.matcher(parts[i]).replaceFirst("")
if (!TextUtils.isEmpty(cleaned)) {
builder.appendCodePoint(cleaned.codePointAt(0))
count++
}
i++
}
return if (builder.isEmpty()) {
null
} else {
builder.toString()
}
}
}

Wyświetl plik

@ -0,0 +1,92 @@
package org.thoughtcrime.securesms.util.storage;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import org.signal.core.util.StreamUtil;
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* Manages the storage of custom files.
*/
public final class FileStorage {
/**
* Saves the provided input stream as a new file.
*/
@WorkerThread
public static @NonNull String save(@NonNull Context context,
@NonNull InputStream inputStream,
@NonNull String directoryName,
@NonNull String fileNameBase,
@NonNull String extension
) throws IOException
{
File directory = context.getDir(directoryName, Context.MODE_PRIVATE);
File file = File.createTempFile(fileNameBase, "." + extension, directory);
StreamUtil.copy(inputStream, getOutputStream(context, file));
return file.getName();
}
@WorkerThread
public static @NonNull InputStream read(@NonNull Context context,
@NonNull String directoryName,
@NonNull String filename) throws IOException
{
File directory = context.getDir(directoryName, Context.MODE_PRIVATE);
File file = new File(directory, filename);
return getInputStream(context, file);
}
@WorkerThread
public static @NonNull List<String> getAll(@NonNull Context context,
@NonNull String directoryName,
@NonNull String fileNameBase)
{
return getAllFiles(context, directoryName, fileNameBase).stream()
.map(File::getName)
.collect(Collectors.toList());
}
@WorkerThread
public static @NonNull List<File> getAllFiles(@NonNull Context context,
@NonNull String directoryName,
@NonNull String fileNameBase)
{
File directory = context.getDir(directoryName, Context.MODE_PRIVATE);
File[] allFiles = directory.listFiles(pathname -> pathname.getName().contains(fileNameBase));
if (allFiles != null) {
return Arrays.asList(allFiles);
} else {
return Collections.emptyList();
}
}
private static @NonNull OutputStream getOutputStream(@NonNull Context context, File outputFile) throws IOException {
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
return ModernEncryptingPartOutputStream.createFor(attachmentSecret, outputFile, true).second;
}
private static @NonNull InputStream getInputStream(@NonNull Context context, File inputFile) throws IOException {
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
return ModernDecryptingPartInputStream.createFor(attachmentSecret, inputFile, 0);
}
}

Wyświetl plik

@ -58,7 +58,7 @@ public class ChatWallpaperViewModel extends ViewModel {
});
} else {
liveRecipient = null;
wallpaperPreviewPortrait = new DefaultValueLiveData<>(new WallpaperPreviewPortrait.SolidColor(AvatarColor.ULTRAMARINE));
wallpaperPreviewPortrait = new DefaultValueLiveData<>(new WallpaperPreviewPortrait.SolidColor(AvatarColor.A100));
}
}

Wyświetl plik

@ -6,25 +6,18 @@ import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.annimon.stream.Stream;
import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.util.storage.FileStorage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* Manages the storage of custom wallpaper files.
@ -41,36 +34,23 @@ public final class WallpaperStorage {
*/
@WorkerThread
public static @NonNull ChatWallpaper save(@NonNull Context context, @NonNull InputStream wallpaperStream, @NonNull String extension) throws IOException {
File directory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
File file = File.createTempFile(FILENAME_BASE, "." + extension, directory);
String name = FileStorage.save(context, wallpaperStream, DIRECTORY, FILENAME_BASE, extension);
StreamUtil.copy(wallpaperStream, getOutputStream(context, file));
return ChatWallpaperFactory.create(PartAuthority.getWallpaperUri(file.getName()));
return ChatWallpaperFactory.create(PartAuthority.getWallpaperUri(name));
}
@WorkerThread
public static @NonNull InputStream read(@NonNull Context context, String filename) throws IOException {
File directory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
File wallpaperFile = new File(directory, filename);
return getInputStream(context, wallpaperFile);
return FileStorage.read(context, DIRECTORY, filename);
}
@WorkerThread
public static @NonNull List<ChatWallpaper> getAll(@NonNull Context context) {
File directory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
File[] allFiles = directory.listFiles(pathname -> pathname.getName().contains(FILENAME_BASE));
if (allFiles != null) {
return Stream.of(allFiles)
.map(File::getName)
.map(PartAuthority::getWallpaperUri)
.map(ChatWallpaperFactory::create)
.toList();
} else {
return Collections.emptyList();
}
return FileStorage.getAll(context, DIRECTORY, FILENAME_BASE)
.stream()
.map(PartAuthority::getWallpaperUri)
.map(ChatWallpaperFactory::create)
.collect(Collectors.toList());
}
/**
@ -97,14 +77,4 @@ public final class WallpaperStorage {
Log.w(TAG, "Failed to delete " + filename + "!");
}
}
private static @NonNull OutputStream getOutputStream(@NonNull Context context, File outputFile) throws IOException {
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
return ModernEncryptingPartOutputStream.createFor(attachmentSecret, outputFile, true).second;
}
private static @NonNull InputStream getInputStream(@NonNull Context context, File inputFile) throws IOException {
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
return ModernDecryptingPartInputStream.createFor(attachmentSecret, inputFile, 0);
}
}

Wyświetl plik

@ -149,4 +149,28 @@ message ChatColor {
message RecipientExtras {
bool manuallyShownAvatar = 1;
}
message CustomAvatar {
message Text {
string text = 1;
string colors = 2;
}
message Vector {
string key = 1;
string colors = 2;
}
message Photo {
string uri = 1;
int64 size = 2;
}
oneof avatar {
Text text = 1;
Vector vector = 2;
Photo photo = 3;
}
}

Wyświetl plik

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<alpha
android:fromAlpha="0"
android:toAlpha="1.0"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@android:anim/linear_interpolator"
android:startOffset="66"
android:duration="50"/>
<scale
android:fromXScale="1.1"
android:toXScale="1"
android:fromYScale="1.1"
android:toYScale="1"
android:pivotX="50%"
android:pivotY="50%"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@anim/fragment_fast_out_extra_slow_in"
android:duration="300"/>
</set>

Wyświetl plik

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
android:zAdjustment="top">
<alpha
android:fromAlpha="1"
android:toAlpha="0.0"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@android:anim/linear_interpolator"
android:startOffset="66"
android:duration="50"/>
<scale
android:fromXScale="1"
android:toXScale="0.9"
android:fromYScale="1"
android:toYScale="0.9"
android:pivotX="50%"
android:pivotY="50%"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@anim/fragment_fast_out_extra_slow_in"
android:duration="300"/>
</set>

Wyświetl plik

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:shareInterpolator="false">
<alpha
android:fromAlpha="0"
android:toAlpha="1.0"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@android:anim/linear_interpolator"
android:startOffset="35"
android:duration="50"/>
<scale
android:fromXScale="0.85"
android:toXScale="1"
android:fromYScale="0.85"
android:toYScale="1"
android:pivotX="50%"
android:pivotY="50%"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@anim/fragment_fast_out_extra_slow_in"
android:duration="300"
tools:targetApi="lollipop" />
</set>

Wyświetl plik

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<!-- Fade out, over a black surface, which simulates a black scrim -->
<alpha
android:fromAlpha="1"
android:toAlpha="0.0"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@android:anim/linear_interpolator"
android:startOffset="35"
android:duration="50"/>
<scale
android:fromXScale="1"
android:toXScale="1.15"
android:fromYScale="1"
android:toYScale="1.15"
android:pivotX="50%"
android:pivotY="50%"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@anim/fragment_fast_out_extra_slow_in"
android:duration="300"/>
</set>

Wyświetl plik

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<shape android:shape="oval">
<stroke android:width="3dp" android:color="@color/signal_background_primary" />
</shape>
</item>
</selector>

Wyświetl plik

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:width="3dp"
android:color="@color/signal_inverse_primary" />
</shape>

Wyświetl plik

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/transparent_black_20" />
</shape>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<path
android:pathData="M25.7169,40.6825C25.313,40.4399 20.076,34.8719 19.9414,34.508C19.8067,34.1441 16.3199,26.2834 16.1853,25.9195C16.0507,25.5556 15.6468,20.5941 15.5122,20.2302C15.3775,19.8663 16.5892,14.4196 16.5892,14.4196L19.9481,10.7865L24.9158,8.2451L30.6913,8.4877L36.1976,12.4848L38.3449,18.6593L38.6141,24.8338L35.5244,33.4284L30.5567,38.2625L27.6016,40.6825L27.7363,41.8956L26.39,42.9874L24.7812,42.2595L24.9158,41.0464L25.7169,40.6825Z"
android:fillColor="#E75B14"/>
<path
android:pathData="M41.0156,52.7974C40.5984,52.2108 40.0707,51.6911 39.4751,51.2471C37.9155,50.0841 35.9996,49.5732 34.1038,49.2505C33.604,49.1651 33.1042,49.0916 32.6045,49.0181L32.6043,49.0181C32.042,48.9354 31.4796,48.8527 30.9173,48.7531L30.903,48.7504C30.0602,48.5966 29.172,48.4345 28.4367,47.9732C26.832,46.961 27.0575,44.8325 27.3706,43.1745C27.9944,43.0119 28.5756,42.6587 28.8894,42.0968C29.2001,41.5385 29.0551,40.8961 28.6492,40.4759C32.997,37.4968 36.3146,33.3382 38.0601,28.6648C39.0147,26.1055 39.5256,23.3945 39.4584,20.6957C39.3845,17.7907 38.6181,14.7947 36.7492,12.3869C35.0417,10.1854 32.5611,8.5176 29.6435,7.899C26.6788,7.2622 23.4923,7.9172 20.9647,9.4091C18.3765,10.9374 16.5479,13.2603 15.6068,15.9045C14.6051,18.7246 14.5581,21.7388 15.1496,24.6317C15.7816,27.6702 17.0252,30.5874 18.484,33.3711C19.721,35.7303 21.2604,38.1138 23.5663,39.7634C23.9831,40.0616 24.4135,40.3352 24.8649,40.5775C24.4654,40.933 24.293,41.5076 24.4458,42.0497C24.612,42.6292 25.1325,42.9836 25.7022,43.1549C25.4156,44.7099 25.2621,46.4131 26.225,47.7649C27.3156,49.2982 29.4717,49.6254 31.2265,49.8916C31.3104,49.9043 31.3934,49.9169 31.4753,49.9295C31.7592,49.9726 32.0459,50.0132 32.3341,50.054L32.3343,50.0541C33.9772,50.2866 35.6668,50.5258 37.1424,51.2807C38.1427,51.7952 39.0655,52.5223 39.6594,53.4645C40.1183,53.254 40.5705,53.0315 41.0156,52.7974ZM21.0588,34.8267C22.3898,37.0039 24.1175,39.0599 26.6116,40.2243C26.6685,40.2505 26.7143,40.2817 26.7497,40.3163C27.2035,40.0276 27.6473,39.7229 28.0771,39.4056C32.3796,36.2155 35.4921,31.8003 36.9374,26.9606C38.4231,21.9632 38.3088,15.7408 34.3022,11.6834C32.5678,9.9246 30.1006,8.5964 27.5057,8.5479C24.8637,8.4994 22.2621,9.6517 20.3731,11.3438C15.8824,15.3769 15.795,21.7388 17.4286,26.9C18.2958,29.6352 19.5395,32.334 21.0588,34.8267ZM26.4752,41.1245C26.4086,41.1878 26.3257,41.237 26.234,41.2632C26.1943,41.2754 26.1713,41.282 26.1636,41.2825C26.1329,41.3048 26.1372,41.304 26.1735,41.2833C26.1158,41.4059 26.1297,41.3622 26.1394,41.3316C26.1478,41.3052 26.153,41.2885 26.1063,41.3976C26.0592,41.5052 26.0458,41.6396 26.0659,41.7606C26.0928,41.9152 26.1668,42.0094 26.2878,42.0968C26.3617,42.1438 26.4962,42.164 26.55,42.164C26.6306,42.164 26.7853,42.1438 26.9264,42.0766C27.0743,42.0026 27.2155,41.8951 27.3029,41.7472C27.3701,41.6329 27.4037,41.4716 27.3768,41.3371C27.3712,41.3088 27.3608,41.2723 27.3476,41.2354C27.0521,41.2905 26.7026,41.2447 26.4752,41.1245Z"
android:fillColor="#4B064B"
android:fillType="evenOdd"/>
</vector>

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -0,0 +1,108 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<path
android:pathData="M12.7468,36.1143L12.8876,11.071L15.7167,9.6563L22.9237,12.6268L44.1248,21.541L44.6907,24.5115L44.8315,44.8875L41.8642,47.5762L31.1215,44.1802L16.8457,38.662L12.7468,36.1143Z"
android:fillColor="#DC674F"/>
<path
android:pathData="M12.3642,36.6302C14.1733,37.5264 15.9956,38.3907 17.8338,39.2204C19.6695,40.0501 21.5183,40.8453 23.3805,41.6059C25.2347,42.3638 27.1022,43.0871 28.983,43.7759C30.8717,44.4673 32.7737,45.1242 34.6864,45.7465C36.5911,46.3661 38.5037,46.9512 40.4296,47.499C40.6714,47.5682 40.9158,47.6373 41.1575,47.7038C41.3381,47.7543 41.5852,47.7623 41.726,47.6134C41.8535,47.4751 41.7286,47.3075 41.5772,47.265C39.6513,46.7251 37.7333,46.1481 35.826,45.5391C33.9266,44.9301 32.0406,44.2892 30.1651,43.611C28.2817,42.9302 26.4089,42.2175 24.552,41.4676C22.6925,40.7203 20.8489,39.9358 19.0186,39.122C17.2069,38.3136 15.4112,37.4759 13.6313,36.6009C13.4055,36.4892 13.1771,36.3776 12.9513,36.2659C12.7733,36.1781 12.5395,36.1542 12.3642,36.2659C12.2261,36.3589 12.1969,36.5478 12.3642,36.6302Z"
android:fillColor="#631505"/>
<path
android:pathData="M12.2978,10.9806C12.2978,14.2942 12.2978,17.6104 12.2978,20.924C12.2978,24.2375 12.2978,27.5538 12.2978,30.8673C12.2978,32.7289 12.2978,34.5905 12.2978,36.4493C12.2978,36.7179 13.1293,36.7206 13.1293,36.4493C13.1293,33.1358 13.1293,29.8195 13.1293,26.506C13.1293,23.1924 13.1293,19.8762 13.1293,16.5626C13.1293,14.701 13.1293,12.8395 13.1293,10.9806C13.1266,10.712 12.2978,10.7093 12.2978,10.9806Z"
android:fillColor="#631505"/>
<path
android:pathData="M41.1309,22.1233C41.1309,25.4369 41.1309,28.7531 41.1309,32.0667C41.1309,35.3643 41.1309,38.6593 41.1309,41.9569C41.1309,43.8185 41.1309,45.68 41.1309,47.5389C41.1309,47.882 41.9597,47.882 41.9597,47.5389C41.9597,44.2253 41.9597,40.9091 41.9597,37.5955C41.9597,34.2979 41.9597,31.003 41.9597,27.7054C41.9597,25.8438 41.9597,23.9822 41.9597,22.1233C41.9597,21.7829 41.1309,21.7803 41.1309,22.1233Z"
android:fillColor="#631505"/>
<path
android:pathData="M12.5634,11.3103C14.3937,12.1241 16.2559,12.8634 18.1314,13.5602C20.0015,14.2543 21.8902,14.9085 23.7816,15.5467C25.681,16.1903 27.5857,16.8206 29.485,17.4721C31.3711,18.1183 33.2519,18.7858 35.1141,19.5012C36.9736,20.2166 38.8172,20.9798 40.6235,21.8255C40.8493,21.9319 41.0725,22.0382 41.2956,22.1446C41.4683,22.2271 41.6782,22.2377 41.8641,22.1978C41.9358,22.1818 42.1855,22.0781 42.0129,21.9957C40.2038,21.1261 38.3602,20.3336 36.4928,19.5996C34.62,18.863 32.7286,18.1822 30.8266,17.5226C28.9298,16.8658 27.0278,16.2328 25.1258,15.5919C23.2158,14.951 21.3085,14.2995 19.4171,13.608C17.5363,12.9219 15.6688,12.1959 13.8332,11.3981C13.6048,11.2997 13.379,11.1987 13.1505,11.1003C12.9832,11.0258 12.7308,11.0364 12.5634,11.1003C12.4094,11.1588 12.4227,11.2465 12.5634,11.3103Z"
android:fillColor="#631505"/>
<path
android:pathData="M41.6383,22.4132C41.6569,22.3946 41.6728,22.3786 41.6914,22.36C41.7207,22.3334 41.7366,22.3015 41.7419,22.2643C41.7552,22.2297 41.7525,22.1951 41.7339,22.1606C41.718,22.1233 41.6941,22.0941 41.6596,22.0728C41.625,22.0409 41.5878,22.0196 41.5427,22.0063C41.5108,21.9983 41.4763,21.9877 41.4444,21.9797C41.37,21.9664 41.2983,21.9664 41.2239,21.9797C41.192,21.9877 41.1575,21.9983 41.1256,22.0063C41.0698,22.0249 41.0194,22.0568 40.9769,22.0994C40.9583,22.118 40.9423,22.134 40.9237,22.1526C40.8945,22.1792 40.8786,22.2111 40.8733,22.2483C40.86,22.2829 40.8626,22.3175 40.8812,22.352C40.8972,22.3893 40.9211,22.4185 40.9556,22.4398C40.9901,22.4717 41.0273,22.493 41.0725,22.5063C41.1044,22.5143 41.1389,22.5249 41.1708,22.5329C41.2452,22.5462 41.3169,22.5462 41.3913,22.5329C41.4231,22.5249 41.4577,22.5143 41.4895,22.5063C41.548,22.4877 41.5958,22.4557 41.6383,22.4132Z"
android:fillColor="#631505"/>
<path
android:pathData="M12.3642,12.0656C13.5198,14.6106 15.0286,16.9668 16.6358,19.2459C18.2164,21.4824 19.8634,23.6684 21.3297,25.9821C22.2276,27.3969 23.0697,28.8436 23.864,30.3195C23.9596,30.4977 24.1828,30.5562 24.374,30.519C24.5122,30.4924 24.7619,30.3461 24.6636,30.1653C23.2663,27.5697 21.7096,25.0646 20.0068,22.6552C18.4555,20.4612 16.827,18.3231 15.4005,16.044C14.5691,14.717 13.8146,13.3394 13.1665,11.914C13.0841,11.7305 12.8371,11.6773 12.6564,11.7146C12.5077,11.7412 12.2792,11.8795 12.3642,12.0656Z"
android:fillColor="#631505"/>
<path
android:pathData="M44.1513,22.7084C44.1513,25.6098 44.1513,28.5085 44.1513,31.4099C44.1513,34.2926 44.1513,37.1754 44.1513,40.0581C44.1513,41.6857 44.1513,43.3132 44.1513,44.9407C44.1513,45.2545 44.9801,45.2572 44.9801,44.9407C44.9801,42.0394 44.9801,39.1407 44.9801,36.2393C44.9801,33.3565 44.9801,30.4738 44.9801,27.591C44.9801,25.9635 44.9801,24.336 44.9801,22.7084C44.9828,22.3946 44.1513,22.392 44.1513,22.7084Z"
android:fillColor="#631505"/>
<path
android:pathData="M27.4316,31.846C29.0547,30.3913 30.9328,29.2531 32.7578,28.075C34.7209,26.8065 36.684,25.538 38.6445,24.2694C39.7443,23.5594 40.844,22.8467 41.9438,22.1366C42.0899,22.0435 42.0979,21.8866 41.9438,21.7936C41.7844,21.6978 41.5161,21.6899 41.3567,21.7936C39.3803,23.0701 37.4013,24.3492 35.4249,25.6257C33.5043,26.8676 31.5651,28.0856 29.6657,29.3595C28.6403,30.0482 27.6362,30.7742 26.7144,31.6013C26.5683,31.7316 26.733,31.8885 26.8631,31.9337C27.0438,31.9949 27.2855,31.9789 27.4316,31.846Z"
android:fillColor="#631505"/>
<path
android:pathData="M41.896,47.3687C41.8694,47.3342 41.8429,47.2996 41.8163,47.265C41.6968,47.39 41.5772,47.515 41.4577,47.64C42.3316,47.6692 43.0489,47.0948 43.6997,46.5816C44.0158,46.3316 44.3054,46.0603 44.5339,45.7279C44.765,45.3902 44.9137,45.0072 45.0492,44.6216C45.1741,44.2653 44.3532,44.1855 44.2496,44.4886C44.1274,44.837 43.9972,45.1827 43.8007,45.4965C43.6174,45.7891 43.357,46.0284 43.0914,46.2438C42.8337,46.4539 42.5628,46.656 42.2838,46.8369C42.143,46.9273 41.9996,47.0124 41.8508,47.0682C41.6755,47.1347 41.6171,47.1427 41.4603,47.1374C41.2611,47.1294 40.9211,47.2783 41.1017,47.5123C41.1283,47.5469 41.1548,47.5815 41.1814,47.6161C41.37,47.8687 42.159,47.7118 41.896,47.3687Z"
android:fillColor="#631505"/>
<path
android:pathData="M41.8402,22.5196C42.0235,22.259 42.2413,22.0462 42.5017,21.8787C42.6398,21.7883 42.7222,21.7484 42.8683,21.6899C42.9426,21.6606 43.0197,21.634 43.0967,21.6101C43.1233,21.6021 43.1233,21.6021 43.0941,21.6101C43.1126,21.6048 43.1339,21.5994 43.1525,21.5968C43.1923,21.5888 43.2322,21.5808 43.272,21.5729C43.3066,21.5675 43.3384,21.5622 43.373,21.5569C43.4447,21.5463 43.3863,21.5489 43.4527,21.5516C43.4474,21.5516 43.5908,21.5622 43.5324,21.5542C43.5961,21.5622 43.5563,21.5596 43.6041,21.5729C43.6758,21.5968 43.5855,21.5489 43.6439,21.5941C43.6439,21.5941 43.7236,21.6553 43.6864,21.6234C43.7369,21.6686 43.7821,21.7244 43.8219,21.7803C43.9627,21.9904 44.0398,22.3068 44.0955,22.5701C44.1115,22.6446 44.2257,22.7084 44.2868,22.7323C44.3851,22.7696 44.5046,22.7775 44.6056,22.7589C44.7809,22.727 44.9376,22.618 44.8951,22.4265C44.8208,22.0808 44.7198,21.7324 44.494,21.4505C44.1726,21.049 43.6572,20.9718 43.1737,21.033C42.3688,21.1314 41.5905,21.5861 41.1203,22.2483C40.8626,22.6127 41.6436,22.7962 41.8402,22.5196Z"
android:fillColor="#631505"/>
<path
android:pathData="M23.8241,29.0217C23.7763,30.1706 23.7763,31.3221 23.8215,32.4736C23.8295,32.6677 24.0712,32.7582 24.2359,32.7528C24.3873,32.7475 24.6583,32.6757 24.6503,32.4736C24.6051,31.3248 24.6078,30.1733 24.653,29.0217C24.6689,28.6494 23.8401,28.6415 23.8241,29.0217Z"
android:fillColor="#631505"/>
<path
android:pathData="M12.4014,11.0524C12.4041,11.0577 12.4041,11.0604 12.4067,11.0657C12.42,11.0178 12.4359,10.9726 12.4492,10.9247C12.4439,10.9274 12.4386,10.9327 12.4359,10.9354C12.6245,10.9141 12.8158,10.8902 13.0044,10.8689C12.9991,10.8662 12.9938,10.8662 12.9885,10.8636C13.0575,10.9168 13.1266,10.97 13.193,11.0231C13.1877,10.97 13.3365,10.8556 13.3737,10.8237C13.448,10.7599 13.5277,10.7014 13.6127,10.6535C13.7828,10.5551 13.9687,10.486 14.144,10.4354C14.6355,10.2918 15.1402,10.2147 15.6184,10.0285C15.7724,9.9674 15.7592,9.8317 15.6184,9.7679C15.443,9.6881 15.2093,9.6988 15.0313,9.7679C14.5266,9.9621 13.9846,10.0179 13.4799,10.1934C13.2541,10.2732 13.0363,10.3716 12.8344,10.5019C12.7388,10.5657 12.6458,10.6375 12.5634,10.7173C12.4891,10.7918 12.3483,10.9035 12.3589,11.0205C12.3695,11.1162 12.4864,11.1588 12.5634,11.18C12.7494,11.2279 13.0018,11.2332 13.1611,11.1082C13.2143,11.0657 13.2249,11.0258 13.2037,10.9673C13.201,10.962 13.201,10.9593 13.1983,10.954C13.1771,10.8955 13.0549,10.8556 13.0071,10.845C12.9061,10.8184 12.7892,10.8131 12.6883,10.8264C12.5767,10.8396 12.343,10.8928 12.4014,11.0524Z"
android:fillColor="#631505"/>
<path
android:pathData="M15.4935,10.1908C17.3211,10.954 19.1461,11.7172 20.9871,12.4486C22.8413,13.1852 24.7087,13.8793 26.5948,14.5255C28.4729,15.1691 30.3271,15.8765 32.1601,16.6397C34.001,17.4056 35.826,18.2061 37.6563,18.9986C39.5132,19.8017 41.3674,20.6048 43.2242,21.408C43.458,21.509 43.6918,21.6101 43.9255,21.7111C44.1035,21.7883 44.3665,21.7643 44.494,21.6021C44.6189,21.4425 44.5099,21.2537 44.3452,21.1819C42.4884,20.3788 40.6342,19.5757 38.7773,18.7725C36.9337,17.9747 35.0928,17.1689 33.2439,16.3871C31.4189,15.6159 29.578,14.8819 27.7052,14.233C25.8245,13.5815 23.9543,12.9193 22.1001,12.1933C20.2565,11.4699 18.4289,10.712 16.6013,9.9488C16.3701,9.853 16.139,9.7573 15.9106,9.6589C15.7299,9.5844 15.4696,9.6057 15.3421,9.7679C15.2199,9.9301 15.3262,10.1216 15.4935,10.1908Z"
android:fillColor="#631505"/>
<path
android:pathData="M26.3664,30.2956C26.3584,31.3912 26.3531,32.4896 26.3451,33.5852C26.3425,33.8831 27.1739,33.8857 27.1739,33.5852C27.1819,32.4896 27.1872,31.3912 27.1952,30.2956C27.1978,29.9977 26.3664,29.9951 26.3664,30.2956Z"
android:fillColor="#631505"/>
<path
android:pathData="M24.026,29.1308C24.807,29.5324 25.5854,29.9339 26.3664,30.3355C26.539,30.4232 26.7834,30.4312 26.9534,30.3355C27.0996,30.253 27.1102,30.0935 26.9534,30.0137C26.1725,29.6121 25.3941,29.2106 24.6131,28.809C24.4404,28.7212 24.1961,28.7133 24.026,28.809C23.8799,28.8914 23.8667,29.051 24.026,29.1308Z"
android:fillColor="#631505"/>
<path
android:pathData="M23.8082,32.1492C23.856,32.8193 24.1748,33.5108 24.7831,33.8432C25.4286,34.1969 26.3929,34.2314 26.988,33.7474C27.1261,33.6331 26.9481,33.5453 26.8392,33.5187C26.6825,33.4815 26.4089,33.4682 26.2707,33.5772C26.1804,33.6517 26.0874,33.7102 25.9759,33.7448C25.9546,33.7528 25.9227,33.7581 25.9015,33.7607C25.8776,33.7634 25.851,33.7661 25.8271,33.7687C25.9174,33.7634 25.7926,33.7661 25.7846,33.7661C25.8271,33.7687 25.8165,33.7687 25.7793,33.7634C25.6598,33.7474 25.8138,33.774 25.7448,33.7581C25.673,33.7421 25.6598,33.7395 25.6066,33.7182C25.4951,33.6756 25.3968,33.6171 25.3011,33.5453C24.8761,33.2156 24.6742,32.6677 24.637,32.1465C24.6211,31.9045 23.7923,31.9231 23.8082,32.1492Z"
android:fillColor="#631505"/>
<path
android:pathData="M24.1243,31.1599C24.7964,31.4391 25.4711,31.7184 26.1432,31.9976C26.3132,32.0694 26.5603,32.0588 26.7303,31.9976C26.8817,31.9417 26.8738,31.854 26.7303,31.7928C26.0582,31.5136 25.3835,31.2344 24.7114,30.9551C24.5414,30.8833 24.2943,30.894 24.1243,30.9551C23.9729,31.011 23.9809,31.1014 24.1243,31.1599Z"
android:fillColor="#631505"/>
<path
android:pathData="M28.0107,29.41C28.0054,29.4074 28.0027,29.4074 27.9974,29.4047C27.8327,29.3542 27.506,29.3542 27.3864,29.5164C27.3705,29.5403 27.3572,29.5696 27.3652,29.5988C27.4024,29.7238 27.5538,29.7637 27.6707,29.777C27.9337,29.8063 28.1143,29.7398 28.3242,29.5935C28.4623,29.4951 28.5978,29.3967 28.7333,29.2957C29.0016,29.0936 29.2646,28.8808 29.5196,28.6601C29.6577,28.5404 29.4877,28.4367 29.3708,28.4074C29.2114,28.3649 28.9405,28.3569 28.8023,28.4739C28.5925,28.6548 28.3773,28.8329 28.1568,29.0031C28.0506,29.0856 27.9416,29.168 27.8327,29.2478C27.7318,29.3223 27.6282,29.418 27.5166,29.4738C27.6521,29.4579 27.7849,29.4419 27.9204,29.426C27.9098,29.426 27.8991,29.4233 27.8912,29.4233C27.9921,29.4818 28.0931,29.543 28.1967,29.6015C28.194,29.6068 28.1914,29.6121 28.1887,29.6175C27.9841,29.6547 27.7823,29.6919 27.5777,29.7292C27.583,29.7318 27.5857,29.7318 27.591,29.7345C27.7557,29.785 28.0187,29.7823 28.1595,29.668C28.3082,29.543 28.1196,29.4446 28.0107,29.41Z"
android:fillColor="#631505"/>
<path
android:pathData="M30.9913,27.7373C31.4827,27.4474 31.9423,27.107 32.3673,26.724C32.4975,26.607 32.3354,26.5219 32.2185,26.4927C32.0698,26.4554 31.7802,26.4368 31.6501,26.5538C31.225,26.9368 30.7655,27.2745 30.274,27.5671C30.112,27.6628 30.3351,27.7771 30.4228,27.7984C30.6034,27.841 30.8266,27.833 30.9913,27.7373Z"
android:fillColor="#631505"/>
<path
android:pathData="M34.2295,25.7108C34.6811,25.4954 35.1141,25.2508 35.5285,24.9689C35.6825,24.8651 35.6799,24.679 35.5285,24.5726C35.3664,24.4609 35.1034,24.4609 34.9414,24.5726C34.5801,24.8173 34.2056,25.038 33.8097,25.2268C33.661,25.2986 33.5282,25.4529 33.661,25.6098C33.7832,25.756 34.0621,25.7906 34.2295,25.7108Z"
android:fillColor="#631505"/>
<path
android:pathData="M37.1861,23.8386C37.7068,23.6046 38.2062,23.3254 38.679,23.0036C38.8251,22.9052 38.6206,22.8334 38.5303,22.8148C38.3735,22.7829 38.1052,22.7669 37.9618,22.8653C37.4889,23.1871 36.9895,23.469 36.4689,23.7003C36.2962,23.7775 36.5539,23.8759 36.6176,23.8892C36.8036,23.9264 37.0134,23.9184 37.1861,23.8386Z"
android:fillColor="#631505"/>
<path
android:pathData="M39.4494,22.3707C39.909,22.2191 40.3499,22.009 40.7537,21.7431C40.9025,21.6447 40.6926,21.5729 40.605,21.5569C40.4456,21.525 40.1826,21.509 40.0365,21.6048C39.6699,21.8468 39.2794,22.0409 38.8623,22.1765C38.8278,22.1872 38.7401,22.2217 38.7401,22.2723C38.7401,22.3255 38.8278,22.3574 38.8623,22.368C39.0403,22.4318 39.2741,22.4292 39.4494,22.3707Z"
android:fillColor="#631505"/>
<path
android:pathData="M13.5862,12.6241C13.9873,13.2464 14.3858,13.8713 14.7869,14.4936C14.8825,14.6399 15.1455,14.6452 15.2969,14.6266C15.3819,14.6159 15.6821,14.5415 15.5865,14.3926C15.1854,13.7703 14.7869,13.1453 14.3858,12.523C14.2901,12.3768 14.0271,12.3714 13.8757,12.3901C13.7907,12.4007 13.4905,12.4752 13.5862,12.6241Z"
android:fillColor="#631505"/>
<path
android:pathData="M16.0195,16.3844C16.3728,16.7727 16.6703,17.2088 16.9174,17.6716C17.0475,17.9162 17.8604,17.8311 17.717,17.5625C17.4513,17.0599 17.1193,16.5972 16.7367,16.1743C16.5614,15.9855 15.7618,16.1025 16.0195,16.3844Z"
android:fillColor="#631505"/>
<path
android:pathData="M18.0411,19.3257C18.3997,19.9826 18.838,20.5942 19.348,21.1447C19.4835,21.291 19.7412,21.3043 19.9165,21.2378C20.0626,21.1819 20.2008,21.025 20.0653,20.8788C19.5898,20.3655 19.178,19.8017 18.8406,19.1874C18.745,19.0145 18.5112,18.972 18.3306,19.0039C18.2031,19.0252 17.9454,19.1502 18.0411,19.3257Z"
android:fillColor="#631505"/>
<path
android:pathData="M20.4319,22.7164C20.7294,23.1764 21.0296,23.6392 21.3271,24.0992C21.436,24.2668 21.6485,24.3146 21.8371,24.2801C21.9513,24.2615 22.2356,24.1285 22.1267,23.961C21.8291,23.5009 21.529,23.0381 21.2314,22.5781C21.1225,22.4105 20.91,22.3627 20.7214,22.3972C20.6072,22.4185 20.3229,22.5488 20.4319,22.7164Z"
android:fillColor="#631505"/>
<path
android:pathData="M22.2728,25.7055C22.565,26.3225 22.9342,26.9022 23.3725,27.4235C23.5027,27.5804 23.7604,27.6043 23.941,27.5299C24.1004,27.4634 24.22,27.2825 24.0898,27.1256C23.6834,26.6443 23.3433,26.1177 23.0724,25.5486C22.9847,25.3651 22.7509,25.304 22.5623,25.3412C22.4136,25.3731 22.1851,25.5194 22.2728,25.7055Z"
android:fillColor="#631505"/>
<path
android:pathData="M22.8519,12.0603C22.9237,11.7066 23.9119,10.1508 24.1244,10.079C24.3369,10.0072 27.0943,9.2307 27.5884,9.4435C28.0825,9.6562 30.9089,10.2227 31.4057,10.4327C31.8998,10.6455 34.6572,13.1932 34.6572,13.1932L35.0105,14.7489L34.5855,16.5892L34.8671,17.863L32.6755,17.1556C32.6755,17.1556 32.6038,16.1663 32.6755,15.9536C32.7472,15.7408 34.2295,14.7516 34.2295,14.7516L33.4512,13.7623L29.9898,11.7784L27.5167,10.7891L25.3968,11.1428L23.5585,11.922L22.8519,12.0603Z"
android:fillColor="#DC674F"/>
<path
android:pathData="M23.5081,13.0363C23.0644,12.2465 23.4098,11.2173 24.058,10.6455C24.4219,10.3238 24.8682,10.1217 25.3251,10.002C25.5801,9.9355 25.7687,9.9009 26.0397,9.869C26.1619,9.8531 26.2867,9.8424 26.4089,9.8318C26.4381,9.8291 26.4674,9.8265 26.4966,9.8265C26.4328,9.8318 26.5125,9.8265 26.5205,9.8265C26.6002,9.8238 26.6772,9.8185 26.7569,9.8158C27.862,9.7786 28.9326,9.9036 30.0058,10.2466C30.9701,10.5551 31.8679,11.0524 32.6357,11.712C33.4645,12.4247 34.1259,13.3209 34.5244,14.3421C34.5483,14.4032 34.6652,14.4431 34.7157,14.4564C34.8166,14.4857 34.9335,14.4883 35.0344,14.475C35.1487,14.4591 35.3877,14.4006 35.324,14.2384C34.5164,12.18 32.6941,10.7041 30.646,9.9887C29.4798,9.5818 28.2286,9.4089 26.9933,9.4249C25.8351,9.4382 24.5866,9.5765 23.6037,10.2466C22.7111,10.8583 22.1347,12.1215 22.7058,13.1374C22.836,13.3687 23.6462,13.281 23.5081,13.0363Z"
android:fillColor="#631505"/>
<path
android:pathData="M21.4334,13.3528C21.4254,13.3554 21.5556,13.3182 21.5104,13.3262C21.614,13.3049 21.4759,13.3208 21.5848,13.3235C21.6379,13.3235 21.7123,13.3341 21.8053,13.3501C21.9912,13.3793 22.1719,13.4139 22.3605,13.4246C22.7244,13.4458 23.1282,13.3714 23.3912,13.1001C23.5267,12.9618 23.3806,12.8156 23.2424,12.7677C23.0724,12.7092 22.8094,12.7172 22.6739,12.8555C22.6739,12.8555 22.6075,12.9166 22.6261,12.9007C22.6155,12.906 22.6049,12.914 22.5943,12.9193C22.6182,12.9113 22.6182,12.9113 22.5969,12.9193C22.573,12.9299 22.5172,12.9432 22.573,12.9299C22.5624,12.9326 22.4428,12.9459 22.5332,12.9406C22.5013,12.9432 22.4694,12.9432 22.4375,12.9432C22.4056,12.9432 22.3525,12.9379 22.4136,12.9432C22.3392,12.9352 22.2649,12.9246 22.1931,12.9113C21.8026,12.8422 21.3988,12.789 21.019,12.9352C20.8808,12.9884 20.7347,13.1267 20.8702,13.2677C20.9951,13.4006 21.266,13.4166 21.4334,13.3528Z"
android:fillColor="#631505"/>
<path
android:pathData="M23.176,13.1107C23.2956,12.7863 23.4974,12.4885 23.7392,12.2464C23.9756,12.0098 24.3024,11.7864 24.5707,11.6481C25.3198,11.2625 26.0955,11.1109 26.9269,11.1827C27.2829,11.212 27.7664,11.3263 28.125,11.4486C28.55,11.5922 28.9618,11.7704 29.3656,11.9592C30.1253,12.3156 30.8452,12.7198 31.5412,13.1772C32.3408,13.7038 33.0979,14.2968 33.8071,14.943C34.1126,15.2223 34.7768,14.8447 34.3942,14.4936C33.0527,13.2676 31.5279,12.2438 29.8889,11.4593C29.0388,11.0524 28.133,10.6827 27.1846,10.571C26.2177,10.4594 25.2295,10.5976 24.3422,11.0045C23.4921,11.3955 22.7111,12.0417 22.3764,12.9405C22.217,13.3607 23.0326,13.491 23.176,13.1107Z"
android:fillColor="#631505"/>
<path
android:pathData="M34.0064,13.4033C34.1445,13.6081 34.2588,13.8288 34.3384,14.0628C34.3995,14.241 34.4952,14.5282 34.2906,14.6479C34.3119,14.6346 34.187,14.6878 34.2455,14.6692C34.2003,14.6851 34.1498,14.6931 34.1047,14.7011C34.1498,14.6931 34.1445,14.6958 34.0887,14.7037C34.0409,14.7117 33.9931,14.7197 33.9453,14.7277C33.8842,14.7383 33.8231,14.749 33.7647,14.7622C33.5203,14.8128 33.2732,14.8819 33.0527,14.9963C32.6489,15.2064 32.3514,15.5601 32.2505,16.0068C32.1522,16.443 32.2983,16.9163 32.5905,17.2434C32.7074,17.3764 32.9385,17.3817 33.1005,17.3631C33.159,17.3551 33.5043,17.278 33.3901,17.153C33.0926,16.8233 32.9677,16.3605 33.0979,15.9297C33.2201,15.5281 33.5362,15.2303 33.9347,15.1159C34.1684,15.0495 34.365,15.0362 34.5961,14.9803C34.8033,14.9324 35.0477,14.85 35.1726,14.6639C35.3134,14.4511 35.2151,14.1852 35.1407,13.9671C35.0583,13.7331 34.9468,13.5123 34.8086,13.3076C34.713,13.1666 34.4447,13.1719 34.2986,13.1879C34.2216,13.1985 33.9134,13.2624 34.0064,13.4033Z"
android:fillColor="#631505"/>
<path
android:pathData="M34.5802,14.2995C33.7487,15.4484 33.77,17.0945 34.6439,18.2141C34.7714,18.3763 35.0318,18.4029 35.2124,18.3258C35.3771,18.254 35.486,18.0652 35.3612,17.903C34.6386,16.9748 34.6067,15.5627 35.2974,14.6107C35.5764,14.225 34.8086,13.9857 34.5802,14.2995Z"
android:fillColor="#631505"/>
</vector>

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -0,0 +1,112 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<path
android:pathData="M22.9294,10.5988L25.0924,12.5739L26.2234,11.0701L26.0349,9.6607L24.8097,8.7179L22.9294,10.5988Z"
android:fillColor="#39B54A"/>
<path
android:pathData="M40.8883,21.7L42.6743,19.0649L45.5912,20.5687L43.4283,22.9209L40.8883,21.7Z"
android:fillColor="#FF8649"/>
<path
android:pathData="M38.0703,15.3033L37.5991,17.2784L40.9826,16.2413L40.6056,14.3605L38.0703,15.3033Z"
android:fillColor="#FCEE21"/>
<path
android:pathData="M24.8515,20.3442L24.9982,16.2413L28.9472,16.1471L34.0271,19.065L36.4681,21.9782L38.0703,25.6503L38.7253,29.0349L36.6566,31.7596L36.2902,31.8586L36.3739,31.8538L16.351,40.5086L11.6481,42.1066C11.7423,41.8238 24.8097,16.807 24.8097,16.807V20.1916L24.8515,20.3442ZM25.9747,24.447L26.6899,27.0598L30.1677,30.9158L33.0846,32.0424L33.3175,32.029L29.8897,30.5387L27.1612,27.4369L25.9747,24.447Z"
android:fillColor="#FCEE21"
android:fillType="evenOdd"/>
<path
android:pathData="M35.2476,35.1488L37.0336,33.4565L39.0081,34.7717L37.1278,37.124L35.2476,35.1488Z"
android:fillColor="#FCEE21"/>
<path
android:pathData="M15.974,33.4566C15.974,33.4566 16.4452,35.9031 16.5395,36.186C16.6337,36.4688 18.514,38.4439 18.514,38.4439L19.923,38.821L24.4374,37.223L23.0284,36.3745L20.3942,34.3004L18.792,31.1986L18.415,28.2807L15.974,33.4566Z"
android:fillColor="#8F2AF4"/>
<path
android:pathData="M21.2378,23.5809C21.2378,23.5809 24.904,16.6231 24.904,16.9012C24.904,17.1794 24.9983,21.8839 24.9983,21.8839L26.69,27.055L30.3562,30.911L34.1167,32.4148L31.4825,34.1071L29.6022,34.5785L26.6853,33.8242L23.9568,31.472L21.8881,28.4645L21.2378,23.5809Z"
android:fillColor="#8F2AF4"/>
<path
android:pathData="M12.1099,42.5026C13.6838,39.118 15.3049,35.7617 16.9731,32.4242C18.6412,29.0868 20.3565,25.7729 22.1143,22.4825C23.1038,20.63 24.1123,18.7868 25.1349,16.9484C25.3422,16.576 24.3243,16.4487 24.1359,16.7928C22.3263,20.0502 20.5592,23.3358 18.8392,26.6449C17.1191,29.9541 15.4463,33.2916 13.8158,36.6479C12.8969,38.5382 11.9968,40.4379 11.1109,42.3423C10.9271,42.7383 11.945,42.8514 12.1099,42.5026Z"
android:fillColor="#09165F"/>
<path
android:pathData="M24.4469,16.7517C24.0022,20.0881 24.423,23.605 26.0729,26.5849C27.4933,29.1514 29.8988,31.4136 32.7778,32.2358C34.3512,32.6873 36.0633,32.5733 37.3402,31.4802C38.3923,30.5772 38.9949,29.2036 39.0427,27.8301C39.1049,26.1429 38.464,24.508 37.6415,23.0585C36.8667,21.6945 35.9246,20.4017 34.8007,19.2991C32.4526,16.9941 29.1958,15.5921 25.8625,16.0578C25.4369,16.1148 25.0208,16.2051 24.6095,16.324C24.4135,16.381 24.4421,16.5188 24.6095,16.5854C24.8391,16.6757 25.126,16.6519 25.3556,16.5854C28.225,15.7727 31.2522,17.0939 33.366,18.9997C34.4612,19.9835 35.3842,21.1479 36.1685,22.3883C37.0054,23.7096 37.7228,25.1544 37.938,26.7133C38.1245,28.0915 37.8662,29.4841 37.0341,30.6199C36.6659,31.119 36.202,31.5467 35.6424,31.8319C35.0542,32.1313 34.4373,32.1788 33.7965,32.0362C32.4287,31.7416 31.1279,30.9526 30.0614,30.0781C28.9902,29.2036 28.0863,28.1295 27.369,26.9509C25.7286,24.2609 25.1595,21.0433 25.3843,17.9351C25.413,17.5359 25.456,17.1367 25.5086,16.7422C25.5277,16.5188 24.4852,16.457 24.4469,16.7517Z"
android:fillColor="#09165F"/>
<path
android:pathData="M18.1902,27.9196C17.9076,31.2022 19.4058,34.4392 22.0064,36.1818C22.7131,36.6529 23.4717,37.0125 24.2726,37.2557C24.4705,37.3165 24.8191,37.3266 24.984,37.1696C25.1536,37.0075 24.9416,36.8909 24.8003,36.8454C22.096,36.0196 19.9947,33.5476 19.3916,30.6044C19.2079,29.723 19.1608,28.8163 19.2362,27.9146C19.2503,27.6106 18.2185,27.5701 18.1902,27.9196Z"
android:fillColor="#09165F"/>
<path
android:pathData="M11.8259,42.775C15.068,41.4032 18.3149,40.0362 21.557,38.6644C24.7708,37.3021 27.9846,35.9351 31.189,34.5397C32.9939,33.7525 34.5186,32.9688 36.314,32.158C36.5025,32.0731 36.6627,31.8704 36.4978,31.6772C36.3423,31.4933 35.9983,31.4556 35.7862,31.5499C32.5913,32.9923 29.6528,34.3795 26.4296,35.7606C23.2157,37.1371 19.9972,38.4947 16.7739,39.8523C14.9502,40.6254 13.1218,41.3938 11.2981,42.1669C11.1049,42.247 10.9541,42.4591 11.1144,42.6477C11.2746,42.8362 11.6092,42.8645 11.8259,42.775Z"
android:fillColor="#09165F"/>
<path
android:pathData="M15.4745,33.5462C15.5358,35.955 16.794,38.7975 19.546,38.887C19.7156,38.8917 20.0596,38.8258 20.0644,38.6042C20.0691,38.3779 19.6968,38.3261 19.546,38.3214C18.5941,38.2931 17.8119,37.52 17.3595,36.7469C16.7987,35.7806 16.5395,34.6539 16.5112,33.5462C16.5018,33.1549 15.4651,33.1596 15.4745,33.5462Z"
android:fillColor="#09165F"/>
<path
android:pathData="M20.7666,23.1934C20.4416,25.3643 20.9739,27.5904 22.0384,29.5037C23.0605,31.3434 24.5914,32.9992 26.5509,33.9098C27.6672,34.4296 28.906,34.6503 30.1401,34.5308C30.2673,34.517 30.5829,34.448 30.5028,34.264C30.4227,34.0846 30.0129,34.0984 29.8669,34.1122C27.9781,34.2916 26.1882,33.1694 24.9164,31.9045C23.4703,30.465 22.4105,28.5746 21.9583,26.6107C21.6993,25.4885 21.638,24.3295 21.8076,23.1934C21.8453,22.9221 20.8184,22.8669 20.7666,23.1934Z"
android:fillColor="#09165F"/>
<path
android:pathData="M37.4153,15.2703C37.3682,16.0292 37.3163,16.7881 37.2692,17.5471C37.2409,17.9902 38.2777,18.0043 38.3059,17.5471C38.3531,16.7881 38.4049,16.0292 38.452,15.2703C38.4803,14.8319 37.4436,14.8177 37.4153,15.2703Z"
android:fillColor="#09165F"/>
<path
android:pathData="M38.2541,15.3598C39.0505,15.1241 39.8469,14.8931 40.6433,14.6575C40.3841,14.5915 40.1249,14.5255 39.861,14.4595C39.8987,14.7564 39.9317,15.0534 39.9694,15.3551C39.9835,15.4871 40.0024,15.6238 40.0165,15.7558C40.0307,15.8595 40.1013,16.0999 40.0212,16.1801C40.0637,16.1471 40.1108,16.1141 40.1532,16.0811C39.96,16.1753 39.7102,16.213 39.5076,16.2885C39.2673,16.3733 39.0269,16.477 38.8007,16.5902C38.3483,16.8164 37.9242,17.0898 37.5331,17.4104C37.354,17.5565 37.5802,17.6838 37.7169,17.7215C37.9195,17.7734 38.2494,17.7828 38.4285,17.6414C39.0364,17.1464 39.7291,16.7551 40.4831,16.5336C40.6574,16.4817 40.8977,16.4535 41.0203,16.3026C41.1334,16.1565 41.0768,15.9726 41.058,15.8029C41.0061,15.3551 40.9496,14.9073 40.8977,14.4595C40.8648,14.1813 40.2946,14.2096 40.1155,14.2615C39.3191,14.4972 38.5227,14.7282 37.7263,14.9639C37.5802,15.0063 37.3729,15.1194 37.5425,15.275C37.7028,15.4258 38.0656,15.4164 38.2541,15.3598Z"
android:fillColor="#09165F"/>
<path
android:pathData="M22.5977,10.3714C23.2998,11.2058 24.0679,11.9789 24.8926,12.6907C25.0764,12.851 25.378,12.8651 25.6041,12.7944C25.7597,12.7425 25.9764,12.5634 25.7879,12.4031C24.9586,11.6913 24.1952,10.9183 23.493,10.0839C23.2621,9.8152 22.2678,9.9849 22.5977,10.3714Z"
android:fillColor="#09165F"/>
<path
android:pathData="M23.4478,10.4715C24.0322,9.9294 24.6212,9.392 25.2056,8.8499C25.3893,8.6802 25.1914,8.4351 25.0218,8.3644C24.7814,8.2654 24.5034,8.3172 24.3102,8.4916C23.7259,9.0337 23.1368,9.5711 22.5525,10.1132C22.3687,10.2829 22.5666,10.528 22.7363,10.5987C22.9813,10.693 23.2594,10.6459 23.4478,10.4715Z"
android:fillColor="#09165F"/>
<path
android:pathData="M24.2914,8.5906C24.7343,9.0903 25.1726,9.5899 25.6155,10.0896C25.7475,10.2358 26.0773,10.2169 26.2517,10.2028C26.3082,10.198 26.7324,10.1368 26.6145,10.0048C26.1716,9.5051 25.7333,9.0054 25.2904,8.5057C25.1584,8.3596 24.8286,8.3785 24.6542,8.3926C24.5929,8.3973 24.1735,8.4586 24.2914,8.5906Z"
android:fillColor="#09165F"/>
<path
android:pathData="M25.7946,10.2122C25.3563,10.9099 25.0877,11.6735 24.9605,12.4843C24.9228,12.7247 25.3092,12.8331 25.4789,12.8331C25.7098,12.8331 25.9595,12.7342 25.9972,12.4843C26.115,11.7395 26.3978,11.0324 26.7936,10.396C26.9303,10.1839 26.591,10.0001 26.4308,9.9718C26.1857,9.9247 25.9312,9.9954 25.7946,10.2122Z"
android:fillColor="#09165F"/>
<path
android:pathData="M36.2462,33.057C35.7419,33.684 35.2377,34.3062 34.7335,34.9332C34.4177,35.3244 35.4073,35.4894 35.6288,35.216C36.1331,34.5891 36.6373,33.9668 37.1415,33.3399C37.4572,32.9486 36.4676,32.7836 36.2462,33.057Z"
android:fillColor="#09165F"/>
<path
android:pathData="M34.8094,35.4836C35.6246,36.0162 36.3409,36.6667 36.9394,37.4304C37.0996,37.6331 37.42,37.6755 37.6509,37.5718C37.8583,37.4775 37.9949,37.2324 37.8347,37.0297C37.1891,36.2095 36.4163,35.493 35.5398,34.9226C35.323,34.7812 35.0214,34.7576 34.8046,34.9226C34.6303,35.064 34.602,35.3468 34.8094,35.4836Z"
android:fillColor="#09165F"/>
<path
android:pathData="M36.4917,33.433C37.076,33.9327 37.698,34.3758 38.3578,34.767C38.5792,34.899 38.8714,34.932 39.0929,34.767C39.2672,34.6351 39.3097,34.3428 39.0929,34.2155C38.4332,33.8243 37.8111,33.3812 37.2268,32.8815C36.8404,32.5562 36.0016,33.0135 36.4917,33.433Z"
android:fillColor="#09165F"/>
<path
android:pathData="M38.2117,34.3758C37.7546,35.2196 37.387,36.1011 37.109,37.0203C36.9488,37.5577 37.9619,37.7085 38.108,37.2277C38.3861,36.3085 38.7536,35.427 39.2107,34.5832C39.3332,34.3522 39.0364,34.1401 38.8479,34.0977C38.5887,34.0458 38.3389,34.1448 38.2117,34.3758Z"
android:fillColor="#09165F"/>
<path
android:pathData="M40.9542,21.7869C41.4772,20.9054 42.0946,20.0852 42.7826,19.3262C42.9475,19.1424 42.7826,18.935 42.5988,18.8596C42.3773,18.7747 42.0569,18.7983 41.8872,18.9821C41.1521,19.7882 40.5065,20.665 39.9504,21.6031C39.8232,21.8199 40.1531,21.9943 40.3133,22.0226C40.5536,22.0697 40.8269,22.0037 40.9542,21.7869Z"
android:fillColor="#09165F"/>
<path
android:pathData="M42.0947,19.3195C42.9052,19.9182 43.8053,20.3848 44.7572,20.7054C44.9833,20.7808 45.2661,20.8091 45.4923,20.7054C45.6572,20.63 45.6996,20.4744 45.4923,20.4037C44.5922,20.102 43.7534,19.6683 42.99,19.1027C42.8062,18.9659 42.4905,18.9754 42.2785,19.0272C42.1512,19.0602 41.9062,19.1828 42.0947,19.3195Z"
android:fillColor="#09165F"/>
<path
android:pathData="M40.2898,22.1761C41.2323,22.3788 42.1512,22.6522 43.0513,22.9964C43.2727,23.0812 43.5979,23.0623 43.7628,22.8691C43.9231,22.6805 43.7723,22.4637 43.579,22.3883C42.5989,22.0159 41.5951,21.7189 40.5726,21.4973C40.3605,21.4502 40.0259,21.5162 39.9364,21.7424C39.8374,21.9829 40.1013,22.1337 40.2898,22.1761Z"
android:fillColor="#09165F"/>
<path
android:pathData="M44.8952,20.5738C44.1899,21.2522 43.5566,21.9874 42.9952,22.7707C42.8705,22.9414 43.8589,23.0727 44.0124,22.8539C44.5738,22.0705 45.2071,21.3353 45.9124,20.657C46.0468,20.5301 45.6005,20.4644 45.543,20.46C45.3558,20.4469 45.0392,20.4382 44.8952,20.5738Z"
android:fillColor="#09165F"/>
<path
android:pathData="M33.8245,27.0409L36.3597,26.381L39.2109,26.1953L41.3454,26.7581L44.5404,28.4504L46.9861,30.7083L45.2944,32.3064L43.4141,33.532L41.8166,30.8969L38.9939,28.4504L36.4766,27.4531L33.8245,27.0409Z"
android:fillColor="#2E51FF"/>
<path
android:pathData="M46.0654,30.5343C45.1768,31.5319 44.152,32.3925 43.0143,33.0828C42.8309,33.1915 42.8262,33.3807 43.0143,33.4894C43.2164,33.6029 43.5455,33.6124 43.7477,33.4894C44.9465,32.766 46.0324,31.8724 46.9633,30.8227C47.2971,30.4492 46.3051,30.2648 46.0654,30.5343Z"
android:fillColor="#09165F"/>
<path
android:pathData="M34.0789,27.2575C37.0116,27.239 39.8496,28.8513 41.6227,31.1982C42.1416,31.8819 42.5702,32.635 42.8951,33.4388C42.9898,33.6698 43.9553,33.5959 43.8516,33.3464C42.6198,30.3158 39.9127,28.089 36.8357,27.2575C35.9378,27.0172 35.0039,26.9017 34.0744,26.9064C33.9752,26.9064 33.5781,26.9156 33.5781,27.0819C33.5826,27.2436 33.9932,27.2575 34.0789,27.2575Z"
android:fillColor="#09165F"/>
<path
android:pathData="M34.3947,27.0362C36.11,26.4187 37.9714,26.2631 39.7621,26.6214C41.402,26.9466 42.9382,27.682 44.2529,28.7144C44.9975,29.3036 45.6619,29.9871 46.2321,30.7461C46.3594,30.9111 46.6845,30.9063 46.8683,30.8875C46.9484,30.8781 47.3537,30.8026 47.2311,30.6424C46.1285,29.1811 44.6865,28.0073 43.0419,27.2059C41.204,26.3103 39.1447,25.9426 37.109,26.0557C35.9309,26.1217 34.7717,26.3574 33.6595,26.7581C33.471,26.8288 33.4899,26.9655 33.6595,27.0409C33.8763,27.1258 34.1732,27.1116 34.3947,27.0362Z"
android:fillColor="#09165F"/>
<path
android:pathData="M32.2035,8.3644L30.606,18.4286L28.3488,23.2227L30.8887,21.6247L33.9895,17.1087L35.8697,9.8681L32.2035,8.3644Z"
android:fillColor="#DF579A"/>
<path
android:pathData="M32.194,8.7886C33.226,9.0054 34.5596,9.1468 35.1675,10.1321C35.3466,10.4196 36.3503,10.3018 36.1665,10.0001C35.4503,8.8404 33.9376,8.6189 32.7171,8.3596C32.5003,8.3125 32.1846,8.2842 32.0055,8.4492C31.8406,8.6 32.0338,8.7556 32.194,8.7886Z"
android:fillColor="#09165F"/>
<path
android:pathData="M35.1446,9.7576C34.9755,13.8111 33.4195,17.7541 30.8198,20.8759C30.0901,21.75 29.2783,22.5568 28.394,23.2772C28.2152,23.4261 28.4423,23.5558 28.5824,23.5894C28.7902,23.6422 29.1333,23.6518 29.3121,23.5078C32.5159,20.8807 34.7774,17.2114 35.7293,13.2011C35.9951,12.0677 36.1546,10.915 36.2029,9.7528C36.2222,9.4454 35.1591,9.4262 35.1446,9.7576Z"
android:fillColor="#09165F"/>
<path
android:pathData="M28.3487,22.6428C28.2545,22.7748 28.1555,22.9068 28.0613,23.0388C28.377,23.1331 28.6927,23.2274 29.0085,23.3216C30.9029,19.8522 32.1234,16.0339 32.6276,12.1072C32.7689,10.99 32.8491,9.8634 32.8726,8.7367C32.882,8.2418 31.8453,8.2371 31.8359,8.7367C31.7605,12.654 30.9405,16.5383 29.4373,20.1539C29.0132,21.1674 28.5372,22.1573 28.0095,23.1237C27.9011,23.3264 28.1508,23.5149 28.311,23.562C28.5137,23.6233 28.82,23.5998 28.9566,23.4112C29.0509,23.2792 29.1498,23.1472 29.2441,23.0152C29.5834,22.5391 28.6174,22.2657 28.3487,22.6428Z"
android:fillColor="#09165F"/>
</vector>

Wyświetl plik

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<path
android:pathData="M24.6686,55.804L26.531,43.3124L29.6031,33.1218L33.3958,30.5059L41.338,28.9865L45.4182,27.1703L46.8036,25.7157L46.8802,22.876L45.5741,19.3047L43.3888,18.1376L37.3481,17.1846L34.2161,16.9617C34.2161,16.9617 33.1983,16.3764 32.9792,16.3761C32.7602,16.3758 32.4708,14.2621 32.4708,14.2621L30.9454,10.8365L28.6875,9.3773L25.9204,9.3008L22.2031,9.9531L19.2882,11.2598L18.0474,13.8059C18.0474,13.8059 18.3352,17.23 18.3349,17.449C18.3346,17.668 18.5502,20.435 18.5502,20.435L17.2356,23.5651C17.2356,23.5651 13.8764,29.8243 13.876,30.1163C13.8757,30.3606 11.7771,36.3123 11.0492,38.3766C10.9072,38.7791 10.8174,39.0338 10.8055,39.0695C10.7322,39.2884 9.4117,47.1526 9.4117,47.1526L9.4094,48.9382C13.5727,52.6374 18.8469,55.1138 24.6686,55.804Z"
android:fillColor="#8CC63F"/>
<path
android:pathData="M25.1992,55.8616C25.2376,55.2401 25.2813,54.6189 25.331,53.998C25.7539,48.6804 26.6148,43.3598 28.3297,38.2957C29.0443,36.1796 29.8831,33.987 31.4257,32.3282C32.2044,31.4897 33.1653,30.874 34.2464,30.5067C35.2739,30.1559 36.3634,30.0158 37.4376,29.8777C37.5548,29.8626 37.6717,29.8476 37.7884,29.8323C40.348,29.4997 42.8531,28.8714 45.1036,27.5602C46.0389,27.0139 46.927,26.2668 47.235,25.1904C47.5797,23.9936 47.2346,22.5952 46.9112,21.4268C46.6095,20.3424 46.0887,19.3635 45.1184,18.7418C44.1663,18.1311 43.0386,17.8778 41.9326,17.7012C41.0041,17.5518 40.0682,17.4301 39.1327,17.3086L39.1326,17.3086L39.1321,17.3085C38.755,17.2595 38.378,17.2105 38.0015,17.1597L38.0007,17.1596C37.2892,17.0638 36.5777,16.9681 35.8662,16.8686C35.6983,16.8465 35.5304,16.8235 35.3625,16.8005C35.1946,16.7775 35.0267,16.7544 34.8588,16.7323C34.7859,16.7223 34.7111,16.7141 34.6354,16.7058L34.6352,16.7058C34.4039,16.6804 34.1653,16.6542 33.9536,16.5742C33.445,16.3828 33.2961,15.7684 33.1769,15.2763C33.1661,15.2318 33.1555,15.1882 33.145,15.1461C33.0741,14.8594 33.0106,14.5719 32.9471,14.2844C32.8836,13.9969 32.82,13.7094 32.7492,13.4227C32.4622,12.2945 32.0181,11.2574 31.132,10.4715C29.2722,8.8266 26.527,8.7867 24.2155,9.2364C22.9374,9.4866 21.641,9.8427 20.4211,10.3011C19.3728,10.6939 18.4192,11.3242 17.925,12.3602C17.1952,13.891 17.4459,15.5175 17.6953,17.1357C17.8162,17.9198 17.9367,18.702 17.9453,19.4704C17.9618,20.8246 17.4967,22.003 16.8381,23.1629C16.6289,23.5288 16.4158,23.8922 16.2027,24.2557C15.7934,24.9536 15.384,25.6518 15.0014,26.3689C13.8597,28.5137 12.8676,30.7389 12.0323,33.0192C10.3619,37.5759 9.2972,42.3488 8.8749,47.1809C8.8403,47.5753 8.81,47.9702 8.7839,48.3653C9.1373,48.6988 9.4993,49.0232 9.8697,49.3382C10.0733,45.0957 10.7706,40.8768 11.9436,36.7932C12.6036,34.4945 13.4169,32.2434 14.3798,30.0546C15.35,27.844 16.499,25.7393 17.7246,23.664C18.3539,22.599 18.9321,21.4755 19.0578,20.2237C19.1622,19.1825 18.9967,18.1494 18.8321,17.1215L18.832,17.1212C18.7809,16.8018 18.7298,16.4829 18.6869,16.1645C18.5459,15.1058 18.5035,13.9779 18.8406,12.9526C19.1924,11.88 19.9673,11.1582 21.001,10.7325C22.1662,10.2522 23.448,9.9253 24.6786,9.675C25.8398,9.4392 27.041,9.3531 28.1906,9.6721C29.2014,9.9508 30.1536,10.5323 30.7621,11.3981C31.3705,12.2643 31.5987,13.3028 31.8227,14.3222L31.8227,14.3225C31.8509,14.451 31.8791,14.5791 31.9079,14.7065C31.9254,14.7835 31.9424,14.8623 31.9597,14.942C32.0611,15.4103 32.17,15.9136 32.4097,16.3131C32.7194,16.8282 33.3033,17.0005 33.8617,17.0961C34.7732,17.25 35.6932,17.3656 36.6124,17.4812L36.6126,17.4812C37.0551,17.5369 37.4976,17.5925 37.9388,17.6524C38.343,17.7077 38.7479,17.7594 39.1528,17.8111L39.1535,17.8112C40.0429,17.9249 40.9325,18.0386 41.8152,18.1901C42.8226,18.363 43.8554,18.6526 44.6284,19.3544C45.5109,20.1549 45.7977,21.3816 46.019,22.5025C46.2585,23.7036 46.4284,24.9923 45.6495,26.0425C44.973,26.9542 43.8661,27.4966 42.8543,27.9516C41.6854,28.4757 40.4656,28.8611 39.2095,29.1077C38.5285,29.2405 37.8357,29.332 37.1429,29.4235C35.4319,29.6495 33.7202,29.8755 32.1835,30.7231C30.2619,31.7829 29.0401,33.6939 28.1834,35.6529C27.1985,37.9074 26.5018,40.3119 25.922,42.6982C25.3055,45.2379 24.8642,47.8215 24.5506,50.4163C24.3366,52.1816 24.1811,53.9523 24.0691,55.7261C24.4435,55.7788 24.8202,55.8239 25.1992,55.8616Z"
android:fillColor="#000000"/>
<path
android:pathData="M30.1882,19.7988C30.1874,20.4024 29.6973,20.8912 29.0936,20.8904C28.4898,20.8897 28.001,20.3997 28.0018,19.796C28.0025,19.1924 28.4926,18.7036 29.0963,18.7044C29.7001,18.7052 30.1889,19.1951 30.1882,19.7988Z"
android:fillColor="#000000"/>
</vector>

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -0,0 +1,102 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<path
android:pathData="M15.9897,41.4276L13.9001,34.2314L14.0676,31.2205L15.2402,27.3708L16.7435,25.7815L21.5048,23.6889L31.8649,24.0244L37.8824,25.9492L39.7208,28.1257L40.3028,34.2314L38.1337,41.0124L34.6245,45.5288L29.1094,47.873L22.3423,47.3698L19.3314,45.7804L15.9897,41.4276Z"
android:fillColor="#DC674F"/>
<path
android:pathData="M15.7385,26.2847C15.7385,26.2847 18.4102,24.7793 18.829,24.4438C19.2477,24.1083 25.4286,22.8544 25.4286,22.8544L33.7828,24.3599L38.7953,25.9493L39.3816,27.4547L36.7099,29.2118L32.3632,30.8012L27.7694,31.556L22.0031,31.0528L17.4931,29.3796L15.8223,28.0418L15.7385,26.2847Z"
android:fillColor="#EDEDED"/>
<path
android:pathData="M13.494,32.0844C15.5794,35.435 19.3859,37.1837 23.184,37.6911C25.2946,37.9721 27.4512,37.9218 29.5617,37.6744C31.4503,37.4521 33.318,37.0286 35.0977,36.3534C37.0407,35.6153 38.8707,34.5334 40.3657,33.0783C40.5709,32.877 40.3322,32.705 40.1312,32.6505C39.8841,32.5792 39.4402,32.5624 39.235,32.7637C37.9746,33.9841 36.4964,34.9318 34.8841,35.6237C33.3012,36.3031 31.6136,36.7308 29.947,36.974C26.2912,37.5108 22.3214,37.4353 18.9546,35.7034C17.2544,34.8311 15.7636,33.5479 14.7502,31.9208C14.5995,31.6818 14.1891,31.6692 13.9462,31.7028C13.808,31.7196 13.3432,31.8454 13.494,32.0844Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M16.0609,35.2254C16.1991,36.8986 16.8943,38.618 18.2678,39.6454C19.6999,40.7147 21.7519,41.025 23.3766,40.2157C24.2895,39.7628 24.9805,38.9912 25.6337,38.2322C26.0316,37.7709 24.7795,37.578 24.5031,37.8967C23.9922,38.4838 23.473,39.1002 22.8197,39.5363C22.514,39.7376 22.2502,39.8509 21.9194,39.9431C21.9068,39.9473 21.8063,39.9683 21.8901,39.9515C21.8482,39.9599 21.8063,39.9683 21.7686,39.9725C21.6849,39.9851 21.6011,39.9976 21.5215,40.0018C21.6346,39.9934 21.4252,40.006 21.3834,40.006C21.3415,40.006 21.2996,40.006 21.2577,40.006C21.0567,40.0018 21.3792,40.0228 21.1782,40.0018C21.0944,39.9934 21.0107,39.985 20.9269,39.9683C20.885,39.9599 20.8432,39.9557 20.8013,39.9473C20.7887,39.9431 20.6882,39.9221 20.772,39.9431C20.5877,39.897 20.4035,39.8467 20.2234,39.7754C19.9847,39.6789 19.6455,39.4944 19.3984,39.3099C18.8164,38.878 18.3683,38.2993 18.0375,37.6576C17.648,36.8986 17.4387,36.0725 17.3675,35.2254C17.3465,34.9528 16.9236,34.8899 16.7142,34.8941C16.5425,34.8983 16.0358,34.9528 16.0609,35.2254Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M27.9202,37.6995C28.6237,39.1127 30.0935,40.0311 31.6555,40.1569C32.5642,40.2282 33.4813,40.0856 34.3062,39.6998C35.0474,39.3518 35.6546,38.8024 36.1236,38.1398C36.6429,37.406 36.9821,36.5589 37.2878,35.7202C37.3841,35.4602 37.0282,35.3427 36.8313,35.3218C36.6303,35.2966 36.1195,35.2924 36.0273,35.5482C35.5625,36.8189 35.0014,38.2111 33.8288,38.9953C33.5734,39.1673 33.2886,39.3015 33.0165,39.3853C32.8573,39.4356 32.7275,39.4692 32.6186,39.486C32.5433,39.4985 32.4637,39.5111 32.3883,39.5195C32.3423,39.5237 32.3004,39.5279 32.2543,39.5321C32.3464,39.5279 32.2292,39.5321 32.1957,39.5321C32.1161,39.5321 32.0408,39.5321 31.9612,39.5321C31.8272,39.5279 31.9067,39.5279 31.9361,39.5321C31.89,39.5279 31.8481,39.5237 31.8021,39.5195C31.7141,39.5069 31.6262,39.4944 31.5382,39.4776C31.6052,39.4902 31.5508,39.4818 31.5382,39.4776C31.488,39.465 31.4377,39.4524 31.3875,39.4398C31.0944,39.356 30.864,39.2511 30.5793,39.0834C29.9721,38.7227 29.4947,38.1692 29.1806,37.5402C28.988,37.1334 27.7024,37.2634 27.9202,37.6995Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M13.9755,29.5641C14.9973,30.9438 16.3708,31.9838 17.849,32.8225C19.3649,33.6821 21.0107,34.3783 22.6689,34.9109C23.5902,35.2044 24.524,35.4392 25.4872,35.5399C26.4671,35.6447 27.4428,35.6238 28.4185,35.5105C30.3113,35.2883 32.1454,34.676 33.8958,33.9421C35.9268,33.0951 37.895,32.0928 39.8506,31.0822C40.1018,30.9522 40.2861,30.6754 40.0851,30.4112C39.8966,30.1638 39.4611,30.0967 39.189,30.2351C37.4972,31.1073 35.7928,31.9628 34.055,32.7386C32.4051,33.4725 30.7468,34.1015 28.9713,34.4244C27.2502,34.7389 25.5667,34.6802 23.8457,34.2105C22.2376,33.7702 20.6087,33.0825 19.1095,32.2983C17.602,31.5141 16.1447,30.4699 15.1062,29.0651C14.7377,28.5744 13.5233,28.956 13.9755,29.5641Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M27.4344,38.1483C27.5391,39.5657 27.7485,41.0838 29.0383,41.9225C29.2895,42.086 29.7083,42.0734 29.9637,41.9225C30.2108,41.7757 30.194,41.5367 29.9637,41.3857C28.9252,40.7105 28.8247,39.2638 28.7451,38.1483C28.7242,37.8589 28.3264,37.7625 28.0919,37.7667C27.8909,37.7709 27.4135,37.8547 27.4344,38.1483Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M31.9486,43.994C33.6781,44.0989 35.617,44.1366 37.0784,43.0463C37.3046,42.8786 37.3255,42.6144 37.0784,42.4466C36.8355,42.2873 36.3959,42.2663 36.153,42.4466C35.8347,42.6815 35.5918,42.8157 35.2233,42.9415C35.0265,43.0128 35.0014,43.0169 34.7836,43.0631C34.591,43.105 34.3942,43.1344 34.1974,43.1595C34.1513,43.1637 34.1094,43.1679 34.0634,43.1721C34.1346,43.1637 34.0717,43.1721 34.0382,43.1721C33.9377,43.1805 33.8372,43.1847 33.7367,43.1889C33.5441,43.1973 33.3515,43.1973 33.163,43.1973C32.7568,43.1931 32.3548,43.1721 31.9528,43.1469C31.7099,43.1302 31.2996,43.2727 31.2996,43.5705C31.2954,43.8892 31.7099,43.9773 31.9486,43.994Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M28.9964,31.7238C28.9713,32.705 28.519,33.6696 27.7987,34.286C27.5684,34.4831 27.5475,34.7976 27.7987,34.9947C28.0416,35.1834 28.4813,35.2002 28.7242,34.9947C29.6831,34.1728 30.2694,32.9944 30.3029,31.7238C30.3238,31.0612 29.0131,31.057 28.9964,31.7238Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M24.4319,38.2489C24.5617,39.4189 24.2519,40.6224 23.519,41.5534C23.0961,42.0902 24.3398,42.3502 24.6497,41.956C25.4746,40.9118 25.8892,39.5782 25.7426,38.2489C25.7091,37.9553 25.3448,37.8421 25.0894,37.8505C24.8884,37.8547 24.4026,37.9512 24.4319,38.2489Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M22.1413,42.7486C21.6723,43.1889 21.1028,43.5621 20.5458,43.8095C20.3993,43.8766 20.2485,43.9353 20.0978,43.9898C20.0098,44.0192 19.9219,44.0486 19.8339,44.0779C19.7921,44.0905 19.746,44.1031 19.6999,44.1157C19.746,44.1031 19.7293,44.1073 19.6581,44.124C19.5031,44.1618 19.344,44.1953 19.1849,44.2247C19.0927,44.2415 19.0048,44.254 18.9127,44.2666C18.8876,44.2708 18.6447,44.296 18.7452,44.2876C18.4981,44.3086 18.0919,44.3966 18.0919,44.7237C18.0919,45.0215 18.5023,45.1766 18.7452,45.1598C20.4453,45.0256 22.0241,44.3631 23.272,43.1931C23.5023,42.975 23.2552,42.6857 23.0374,42.5976C22.7359,42.476 22.3758,42.5305 22.1413,42.7486Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M17.7737,29.9709C17.7067,30.2057 17.5727,30.428 17.4052,30.6041C17.2711,30.7425 17.045,30.885 16.9152,30.9186C16.7603,30.9605 16.4462,31.1073 16.6807,31.2792C16.9026,31.4428 17.3214,31.4428 17.5768,31.3757C18.3013,31.1786 18.8792,30.7089 19.0844,29.9709C19.1639,29.6857 17.8909,29.5557 17.7737,29.9709Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M24.1304,31.556C23.9085,31.9502 23.6028,32.357 23.3013,32.6338C22.9369,32.9734 22.6019,33.1915 22.1832,33.3886C21.9152,33.5144 21.7561,33.8247 21.9487,34.0847C22.1413,34.3489 22.5601,34.3992 22.8448,34.2692C23.8498,33.8038 24.7167,33.0405 25.2611,32.0718C25.416,31.7951 25.3197,31.5099 25.0265,31.3757C24.7711,31.2583 24.2895,31.2751 24.1304,31.556Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M35.8933,29.6899C35.6463,30.8347 35.0391,31.9041 34.1471,32.6379C33.921,32.8225 33.8958,33.1076 34.1471,33.2879C34.3858,33.4599 34.8339,33.485 35.0726,33.2879C36.1111,32.4325 36.8732,31.2625 37.1538,29.9373C37.2166,29.6354 36.9863,29.4215 36.6973,29.3754C36.4628,29.3334 35.9603,29.3796 35.8933,29.6899Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M23.0165,43.6166C23.7577,42.7863 24.7544,42.3628 25.8515,42.216C25.9729,42.1992 25.8138,42.216 25.9353,42.2076C26.0023,42.2034 26.0651,42.1992 26.1321,42.195C26.2828,42.1866 26.4378,42.1866 26.5885,42.1908C26.6639,42.195 26.7393,42.1992 26.8147,42.2034C26.8482,42.2076 26.8817,42.2076 26.9152,42.2118C26.8356,42.2034 26.8984,42.2118 26.9152,42.2118C27.0743,42.2286 27.2376,42.2537 27.3925,42.2873C28.0039,42.4047 28.4855,42.5766 29.0676,42.866C29.6664,43.1637 30.2233,43.5537 30.7552,44.0108C30.998,44.2205 31.4336,44.2037 31.6806,44.0108C31.9361,43.8137 31.9151,43.4992 31.6806,43.3021C29.524,41.4444 26.4503,40.5302 23.7535,41.7421C23.0375,42.065 22.4093,42.5263 21.8859,43.1092C21.6681,43.3525 21.8733,43.6837 22.1204,43.797C22.4219,43.9354 22.7946,43.8641 23.0165,43.6166Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M21.978,43.2602C22.3256,44.7195 23.8498,45.3695 25.2066,45.5247C26.0441,45.6211 26.89,45.575 27.7275,45.5205C28.5525,45.466 29.3774,45.3443 30.1522,45.0508C30.8808,44.774 31.555,44.3211 32.0114,43.6753C32.1664,43.4573 31.6764,43.344 31.555,43.3315C31.3205,43.3063 30.9101,43.3021 30.751,43.5285C30.0558,44.514 28.9043,44.8495 27.7778,44.9543C27.158,45.0131 26.509,45.0466 25.8473,45.0173C25.6296,45.0047 25.2652,44.9585 24.9972,44.8873C24.7669,44.8243 24.5408,44.7321 24.3356,44.6063C23.8415,44.3002 23.4269,43.8431 23.2887,43.2685C23.1882,42.8324 21.9026,42.9373 21.978,43.2602Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M15.1062,26.0625C12.9286,29.2873 12.757,33.3592 13.7871,37.0202C14.3189,38.9115 15.1523,40.7189 16.1489,42.4047C17.0702,43.9563 18.2343,45.424 19.8005,46.3592C21.5174,47.3866 23.5149,47.8143 25.4914,47.9653C27.4554,48.1163 29.457,47.9066 31.3247,47.2692C34.658,46.1369 37.2292,43.4908 38.7535,40.3666C40.4746,36.8357 40.7719,32.7721 40.395,28.9099C40.3447,28.4192 40.2861,27.9286 40.2191,27.4379C40.1772,27.1276 39.8632,26.9599 39.5658,26.9683C39.3397,26.9767 38.8665,27.115 38.9126,27.4379C39.4402,31.2205 39.4318,35.2044 38.0583,38.8192C37.493,40.3037 36.7099,41.696 35.6965,42.9205C35.1856,43.5411 34.6454,44.0863 34.0424,44.5811C33.7325,44.8369 33.4059,45.076 33.0709,45.2982C32.7317,45.5205 32.4553,45.684 32.1287,45.8476C31.3205,46.2585 30.5081,46.5479 29.6622,46.745C28.7326,46.9631 27.9202,47.0553 26.9989,47.0721C25.2401,47.1056 23.4311,46.875 21.7938,46.2166C20.1774,45.5624 18.9378,44.405 17.9412,42.975C16.911,41.4947 16.0986,39.8257 15.4831,38.1315C14.2519,34.7515 13.9755,30.9102 15.6045,27.6099C15.7888,27.2367 15.9981,26.8802 16.2285,26.5321C16.6514,25.9199 15.437,25.5718 15.1062,26.0625Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M40.3699,27.3457C39.3272,25.836 37.6102,25.0896 35.931,24.5277C33.9168,23.8567 31.8356,23.3744 29.7292,23.1018C27.7443,22.846 25.7259,22.7496 23.7284,22.8628C21.8608,22.9677 19.968,23.2612 18.2385,23.9993C17.3633,24.3725 16.5007,24.838 15.7762,25.4628C15.4286,25.7605 15.0853,26.1338 14.9177,26.5699C14.7419,27.027 14.8256,27.4925 15.0518,27.916C15.9186,29.5389 17.7904,30.3315 19.4655,30.8096C23.004,31.8202 26.777,32.0928 30.416,31.5225C32.1999,31.2457 33.9629,30.7592 35.6253,30.0505C36.467,29.6899 37.2836,29.2831 38.0667,28.8093C38.4352,28.587 38.7911,28.3438 39.1513,28.1089C39.1722,28.0964 39.1973,28.0838 39.2225,28.0712C39.1136,28.1341 39.1973,28.0796 39.2308,28.067C39.2978,28.0418 39.3607,28.0167 39.4277,27.9915C39.6035,27.9286 39.7794,27.8657 39.9427,27.7776C40.2149,27.6351 40.4662,27.3164 40.1437,27.0647C39.8967,26.8718 39.5449,26.8257 39.2476,26.9305C39.0592,26.9934 38.7618,27.2493 39.0131,27.4464C39.0759,27.4967 39.0047,27.4128 38.9963,27.3793C38.9838,27.3122 39.0089,27.2786 39.0466,27.2283C39.0131,27.2702 39.1345,27.1738 39.0256,27.2367C38.9461,27.2828 39.0382,27.2325 39.055,27.2283C39.0215,27.2409 38.988,27.2576 38.9545,27.2702C38.8749,27.2996 38.7953,27.3289 38.7158,27.3583C38.385,27.4841 38.1169,27.6476 37.8238,27.8405C37.1203,28.306 36.3833,28.7254 35.6253,29.0944C34.9176,29.4383 34.1597,29.7402 33.385,29.996C32.5768,30.2644 31.8523,30.4573 31.0609,30.6167C30.2401,30.7844 29.411,30.9018 28.5776,30.9773C28.5232,30.9815 28.4646,30.9857 28.4101,30.9899C28.3976,30.9899 28.2803,30.9983 28.3808,30.9941C28.2803,31.0025 28.1756,31.0067 28.0751,31.0151C27.8616,31.0276 27.6438,31.0402 27.4302,31.0444C26.9989,31.057 26.5676,31.0612 26.1363,31.0528C25.2485,31.036 24.5869,30.9857 23.7242,30.8809C22.8909,30.776 22.0618,30.6334 21.241,30.4447C20.8851,30.3651 20.55,30.2812 20.1522,30.1638C19.7502,30.0505 19.3482,29.9205 18.9546,29.7696C18.2762,29.5054 17.5727,29.1363 17.0157,28.608C16.7226,28.3312 16.4546,28.0125 16.2745,27.6518C16.0819,27.266 16.0735,26.9138 16.2871,26.5364C16.6807,25.8402 17.4889,25.3706 18.1715,25.0057C19.633,24.2173 21.2033,23.8609 22.8574,23.6889C22.9747,23.6764 23.0877,23.6638 23.205,23.6554C23.251,23.6512 23.2971,23.647 23.3432,23.6428C23.251,23.6512 23.3641,23.6428 23.3767,23.6386C23.607,23.6218 23.8415,23.6093 24.0718,23.5967C24.4864,23.5799 24.9051,23.5715 25.3197,23.5715C26.2368,23.5715 27.2041,23.6051 28.0668,23.6847C29.9972,23.8567 31.9151,24.1964 33.7786,24.7206C34.6622,24.968 35.5458,25.2447 36.4,25.5928C37.0617,25.8654 37.7778,26.2302 38.3556,26.7293C38.6362,26.9725 38.8958,27.2409 39.1052,27.547C39.2811,27.7986 39.6203,27.8573 39.9092,27.8112C40.0684,27.7818 40.5416,27.5973 40.3699,27.3457Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M28.2719,28.9895C28.4394,28.8763 34.4527,23.9154 34.4527,23.9154L35.8472,24.8086L31.2785,28.2682L29.1052,29.5515"
android:fillColor="#FFFF00"/>
<path
android:pathData="M44.0925,15.0461L44.9301,13.0919C44.9301,13.0919 45.6001,12.9241 45.7676,12.9241C45.9351,12.9241 46.7684,13.9264 46.7684,13.9264L46.379,15.0419H44.0925V15.0461Z"
android:fillColor="#FFFF00"/>
<path
android:pathData="M30.9771,17.277L44.0674,15.2138L43.008,27.7651L40.9477,27.3205L37.271,26.0373L33.8162,23.0767C33.8162,23.0767 31.5884,20.4012 31.6429,20.2335C31.6973,20.0657 30.9771,17.277 30.9771,17.277Z"
android:fillColor="#39B54A"/>
<path
android:pathData="M43.523,27.589C43.812,24.4229 44.1009,21.2525 44.3898,18.0864C44.4736,17.189 44.5532,16.2874 44.6369,15.39C44.662,15.1174 43.3597,15.0587 43.3304,15.39C43.0414,18.5561 42.7525,21.7264 42.4636,24.8925C42.3798,25.7899 42.3002,26.6916 42.2165,27.589C42.1872,27.8616 43.4895,27.9203 43.523,27.589Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M30.1772,17.2645C30.4996,19.4157 31.534,21.4161 32.9787,23.0264C34.5239,24.7457 36.5172,25.987 38.6487,26.8383C39.8882,27.3331 41.1822,27.6854 42.4887,27.9412C42.7483,27.9915 43.1545,28.0209 43.3848,27.8657C43.6151,27.7106 43.2885,27.6015 43.1503,27.5764C38.8748,26.7461 34.7165,24.5361 32.5934,20.5816C32.0365,19.5457 31.6596,18.4261 31.4837,17.2645C31.4335,16.9248 30.1395,17.0045 30.1772,17.2645Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M31.1404,17.4951C34.4402,16.9332 37.7484,16.4174 41.0649,15.9435C42.0113,15.8093 42.9619,15.6751 43.9125,15.5493C44.0423,15.5325 44.4946,15.457 44.3689,15.2306C44.2517,15.0167 43.7534,15.0251 43.5649,15.0503C40.1814,15.5074 36.802,16.0106 33.431,16.5599C32.4469,16.7193 31.4628,16.8828 30.4829,17.0506C30.3196,17.0799 30.0223,17.2393 30.2484,17.407C30.4829,17.5748 30.8682,17.5412 31.1404,17.4951Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M34.0465,23.6009C31.9528,25.3538 29.8632,27.1109 27.7694,28.8638C27.5516,29.0483 27.5139,29.3083 27.7694,29.4886C28.0039,29.6522 28.4603,29.6857 28.6948,29.4886C30.7886,27.7357 32.8782,25.9786 34.972,24.2257C35.1898,24.0412 35.2274,23.7812 34.972,23.6009C34.7375,23.4373 34.2769,23.4038 34.0465,23.6009Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M44.6369,15.3229C44.6914,14.7945 44.75,14.199 45.0599,13.7545C45.1185,13.6706 45.2358,13.549 45.2902,13.5113C45.286,13.5155 45.4326,13.4232 45.3781,13.4526C45.4158,13.4358 45.4577,13.4232 45.4954,13.4106C45.4409,13.4232 45.4493,13.419 45.5205,13.4064C45.6001,13.398 45.6043,13.3938 45.5331,13.4064C45.554,13.4064 45.5749,13.4064 45.5959,13.4064C45.5247,13.4022 45.5289,13.4022 45.6043,13.4148C45.5456,13.4022 45.554,13.4064 45.6252,13.4274C45.5624,13.3897 45.6964,13.4693 45.7048,13.4819C45.688,13.4693 45.7885,13.5742 45.8095,13.5993C45.9267,13.7713 45.977,14.0103 45.9141,14.2116C45.8765,14.329 45.8053,14.4213 45.7131,14.5009C45.6671,14.5429 45.6378,14.5596 45.5708,14.5974C45.5749,14.5974 45.4242,14.6771 45.4828,14.6477C44.8966,14.9245 44.1177,15.0545 43.5272,15.1006C43.3011,15.1174 42.874,15.1803 42.874,15.4906C42.874,15.7758 43.3137,15.8974 43.5272,15.8806C44.3354,15.8177 45.1436,15.6835 45.91,15.4151C46.5213,15.2013 47.2081,14.7945 47.2416,14.069C47.2751,13.3267 46.6428,12.8193 45.977,12.6768C45.2483,12.5174 44.3857,12.7648 43.925,13.3729C43.4937,13.9348 43.389,14.6477 43.322,15.3355C43.2927,15.6248 43.7743,15.7296 43.9753,15.7255C44.2307,15.7045 44.6076,15.6248 44.6369,15.3229Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M27.5265,29.2915C27.9327,29.929 28.9084,29.9122 29.5281,29.669C29.8296,29.5515 30.1018,29.367 30.374,29.1909C30.6588,29.0106 30.9477,28.8261 31.2283,28.6373C32.3254,27.9161 33.3933,27.1444 34.4234,26.3351C35.0181,25.8696 35.6001,25.3915 36.1696,24.8967C36.3958,24.7038 36.1236,24.5109 35.9351,24.4564C35.6713,24.3767 35.2651,24.3809 35.039,24.5738C33.9335,25.5257 32.7903,26.4274 31.6052,27.2744C31.0147,27.698 30.4117,28.109 29.8003,28.5073C29.5114,28.6961 29.2225,28.889 28.9251,29.0567C28.8623,29.0944 28.7953,29.128 28.7283,29.1573C28.632,29.1993 28.6446,29.1699 28.699,29.1783C28.7116,29.1783 28.8581,29.1951 28.8539,29.1993C28.8414,29.2161 28.7911,29.1406 28.7744,29.1196C28.6194,28.8806 28.2216,28.8596 27.9704,28.8932C27.8364,28.9141 27.3715,29.0483 27.5265,29.2915Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M11.8398,14.1528C11.8398,13.9851 12.8993,12.7019 12.8993,12.7019L13.9001,12.5341L20.6966,23.4121L23.8708,28.3186L22.5349,28.8763L21.2535,28.6541L11.8398,14.1528Z"
android:fillColor="#F3C2DA"/>
<path
android:pathData="M11.4588,14.3918C13.7243,18.2583 16.1154,22.0492 18.6363,25.7521C19.344,26.7963 20.0643,27.8321 20.7929,28.8595C21.1363,29.346 22.3549,28.9895 21.9236,28.3815C19.3356,24.7247 16.8692,20.9799 14.5325,17.1596C13.875,16.0818 13.226,14.9999 12.5895,13.9138C12.4303,13.6412 11.9446,13.637 11.6933,13.7419C11.4044,13.8677 11.2997,14.1193 11.4588,14.3918Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M12.6858,14.1529C12.6816,13.8174 12.7946,13.5322 13.0208,13.268C13.0291,13.2596 13.1129,13.1716 13.0585,13.2219C13.0878,13.1967 13.1171,13.1716 13.1464,13.1464C13.1841,13.1171 13.2218,13.0877 13.2595,13.0584C13.2888,13.0374 13.3725,12.9913 13.2972,13.029C13.339,13.008 13.3809,12.9871 13.427,12.9661C13.4479,12.9577 13.5735,12.9116 13.4605,12.9493C13.5526,12.92 13.6489,12.9074 13.7452,12.8948C14.0258,12.8613 14.3064,12.5719 14.2017,12.2826C14.0844,11.9555 13.6908,11.9009 13.3977,11.9345C12.3005,12.0561 11.3667,13.0416 11.3751,14.1529C11.3792,14.8154 12.69,14.8154 12.6858,14.1529Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M12.9035,12.5593C15.4831,16.8115 18.1464,21.0134 20.8934,25.1608C21.6597,26.3183 22.4344,27.4715 23.2175,28.6247C23.5316,29.086 24.7585,28.7757 24.3482,28.1718C21.5927,24.1167 18.9169,20.0028 16.3248,15.8386C15.5961,14.6728 14.88,13.4986 14.164,12.3244C13.9965,12.0477 13.6698,11.9512 13.36,12.0141C13.159,12.0477 12.736,12.2825 12.9035,12.5593Z"
android:fillColor="#4A155D"/>
<path
android:pathData="M21.6555,28.1089C21.354,28.0796 20.9311,28.1047 20.7929,28.436C20.6882,28.6834 20.8808,28.8889 21.0777,29.0063C21.4252,29.216 21.8272,29.3125 22.2292,29.3334C22.9621,29.3754 23.7284,29.0986 24.2225,28.545C24.4277,28.3102 24.2267,28.0418 23.988,27.9454C23.7075,27.8321 23.3013,27.8657 23.0919,28.1005C22.9872,28.2179 22.9286,28.2725 22.7694,28.3563C22.7359,28.3773 22.7359,28.3773 22.7736,28.3521C22.7569,28.3605 22.7401,28.3689 22.7192,28.3773C22.6815,28.3899 22.648,28.4067 22.6103,28.4192C22.5643,28.436 22.5349,28.4402 22.5978,28.4234C22.5517,28.436 22.5014,28.4444 22.4554,28.4528C22.4428,28.457 22.3214,28.4654 22.3967,28.4612C22.4763,28.457 22.3507,28.4612 22.3381,28.4612C22.3088,28.4612 22.1748,28.4486 22.3088,28.4654C22.2711,28.4612 22.2334,28.457 22.1957,28.4486C22.1581,28.4402 22.1204,28.4318 22.0827,28.4234C22.1455,28.4402 22.1162,28.436 22.0743,28.415C22.0785,28.415 21.9403,28.3563 21.9822,28.3773C22.0241,28.3983 21.9026,28.3228 21.9026,28.3228C21.9571,28.3857 22.0157,28.4528 22.0701,28.5157C22.0617,28.4989 22.0534,28.4863 22.045,28.4696C22.045,28.5492 22.045,28.6247 22.045,28.7044C22.0324,28.7212 22.0241,28.7337 22.0115,28.7505C21.7644,28.8176 21.5174,28.8847 21.2745,28.9518C21.287,28.9518 21.2954,28.9518 21.308,28.956C21.5844,28.9812 21.9906,28.956 22.112,28.6499C22.2209,28.3689 21.8984,28.1341 21.6555,28.1089Z"
android:fillColor="#4A155D"/>
</vector>

Wyświetl plik

@ -0,0 +1,53 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<path
android:pathData="M13.0257,45.2932C12.2701,44.7193 12.3681,43.5083 12.7728,40.2603C13.2975,36.0504 13.5694,33.9186 13.9488,32.6257C14.7265,29.9926 15.6939,28.0973 16.0511,27.4257C18.027,23.7046 20.4107,21.33 21.435,20.3777C22.4277,19.4537 25.1877,17.0255 29.424,15.2596C31.2291,14.5059 32.6549,14.1464 33.8816,13.8342C36.1578,13.2571 38.8229,12.6012 42.2087,12.8282C44.2226,12.9638 45.0888,12.7336 45.5725,13.3328C46.1226,14.0108 45.8033,14.96 45.4871,16.7732C44.9434,19.8983 45.4555,19.5987 44.9813,22.1436C44.3933,25.3065 43.2583,27.6022 42.5439,29.0213C41.88,30.3426 40.9568,32.1716 39.2623,34.1394C37.2896,36.4288 35.3579,37.6429 32.8699,39.1723C29.4398,41.2851 26.6609,42.3226 23.8693,43.3664C20.278,44.7098 22.7186,43.4358 15.2892,45.3783C14.6728,45.5392 13.6927,45.8009 13.0257,45.2932Z"
android:strokeWidth="1.6"
android:fillColor="#DC674F"
android:strokeColor="#631505"/>
<path
android:pathData="M13.2789,35.3945C13.7816,36.1734 15.337,36.0914 16.8956,38.3304C17.6417,39.4026 19.2919,40.6955 19.8389,42.7768C19.9685,43.275 20.1171,44.0728 20.6798,44.2873C20.9517,44.3913 21.1793,44.2778 22.1088,43.8679C23.5535,43.2309 24.2775,42.9124 24.2965,42.9439C24.3186,42.9817 24.0404,43.2624 23.876,43.1962C23.6168,43.0921 23.9961,42.2659 23.7084,41.3514C23.5188,40.7459 23.2279,40.7144 22.6146,39.8409C22.036,39.021 22.1119,38.7908 21.6061,37.911C21.0781,36.9965 20.528,36.4351 19.7567,35.6468C19.1528,35.0287 18.5616,34.5557 17.4014,33.6349C14.7869,31.5536 14.357,31.4779 14.1198,31.6229C13.8985,31.7554 13.9523,32.0108 13.7847,33.0483C13.5002,34.7827 12.9912,34.953 13.2789,35.3945Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M32.3707,14.4208C32.2032,15.1398 34.7228,15.6979 37.2488,18.0283C38.4059,19.0974 39.1773,20.179 39.857,21.1314C40.5999,22.172 41.2638,23.1086 41.874,24.4866C42.6359,26.2084 42.7939,27.4446 43.1354,27.4225C43.3345,27.4099 43.3377,26.981 43.8088,25.6597C44.245,24.4362 44.4758,24.1524 44.6497,23.2284C44.7445,22.7239 44.8109,22.3612 44.7351,21.885C44.6592,21.412 44.4853,21.0714 43.9795,20.2925C43.3409,19.3055 42.971,18.7379 42.2976,17.9432C41.7854,17.3377 41.3808,16.9372 40.7833,16.3507C39.8507,15.4267 39.3828,14.9663 39.016,14.7582C38.0107,14.1811 37.6503,14.4586 36.5786,13.7522C36.0728,13.418 35.9589,13.2319 35.5638,13.1625C35.2603,13.1089 34.9505,13.1499 33.7965,13.6671C32.8671,14.0833 32.3992,14.2915 32.3707,14.4208Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M13.857,45.7252C13.5409,44.0318 13.3923,42.3069 13.4049,40.5851C13.4176,38.8696 13.5883,37.1573 13.9108,35.4733C14.2332,33.7925 14.7074,32.1401 15.3271,30.5444C15.9499,28.9393 16.7149,27.3878 17.6191,25.9215C18.5075,24.4772 19.5286,23.1117 20.6762,21.8629C21.8428,20.5952 23.1326,19.4474 24.5205,18.4288C25.2223,17.9116 25.8736,17.4828 26.6133,17.0413C27.3531,16.5998 28.1119,16.193 28.8864,15.8209C29.2689,15.638 29.6578,15.4614 30.0498,15.2943C30.2458,15.2123 30.4418,15.1303 30.641,15.0514C30.6884,15.0325 30.7358,15.0136 30.7833,14.9947C30.8402,14.9726 30.7833,14.9947 30.7738,14.9978C30.7991,14.9884 30.8212,14.9789 30.8465,14.9695C30.9508,14.9285 31.052,14.8906 31.1563,14.8528C31.9625,14.5532 32.7813,14.2883 33.6127,14.0644C34.0332,13.9509 34.4537,13.8468 34.8773,13.7491C34.9279,13.7365 34.9785,13.727 35.0259,13.7144C35.067,13.7049 35.1776,13.6828 35.0733,13.7049C35.1808,13.6828 35.2915,13.6608 35.3989,13.6387C35.6076,13.5977 35.8162,13.5567 36.0281,13.522C36.8753,13.3738 37.7289,13.2634 38.5857,13.1909C38.6868,13.1814 38.788,13.1751 38.8923,13.1688C38.965,13.1625 38.8575,13.172 38.8512,13.172C38.8765,13.1688 38.9018,13.1688 38.9271,13.1657C38.9872,13.1625 39.0472,13.1594 39.1041,13.1562C39.3159,13.1436 39.5278,13.1341 39.7427,13.1278C40.1759,13.1121 40.609,13.1089 41.0452,13.1152C41.472,13.1215 41.8957,13.1373 42.3193,13.1625C42.3699,13.1657 42.4205,13.1688 42.471,13.172C42.3572,13.1625 42.4489,13.172 42.5058,13.1751C42.6165,13.1846 42.7271,13.1909 42.8378,13.2004C43.0591,13.2193 43.2772,13.2414 43.4985,13.2666C43.9127,13.3139 44.3268,13.3707 44.7378,13.4369C44.9622,13.4716 45.3954,13.4526 45.5155,13.213C45.6419,12.9639 45.2341,12.8472 45.0729,12.8219C43.3278,12.535 41.5447,12.4246 39.7712,12.4845C37.9502,12.5444 36.145,12.7778 34.3683,13.1688C32.6422,13.5472 30.954,14.0896 29.3258,14.7708C27.6914,15.4519 26.1297,16.2908 24.6501,17.2589C23.1958,18.2112 21.8301,19.296 20.5877,20.5101C19.3294,21.7336 18.1945,23.0865 17.1986,24.5276C16.2059,25.9656 15.3492,27.4982 14.641,29.097C13.9266,30.699 13.367,32.3671 12.9528,34.07C12.5387,35.7697 12.2795,37.5073 12.1815,39.2543C12.0866,40.9919 12.153,42.7389 12.3869,44.4607C12.4439,44.8832 12.5134,45.3058 12.5924,45.7252C12.6399,45.9838 13.0034,46.05 13.2247,46.0437C13.367,46.0406 13.9076,45.9807 13.857,45.7252Z"
android:fillColor="#631505"/>
<path
android:pathData="M25.0832,31.0238C27.312,28.4663 29.7116,26.0571 32.2597,23.8181C32.9742,23.1906 33.6981,22.5788 34.4347,21.9765C34.6118,21.8346 34.0396,21.7305 33.9921,21.7274C33.7455,21.7084 33.4199,21.7021 33.2144,21.8693C30.5841,24.0136 28.0961,26.3314 25.7724,28.8037C25.1212,29.4975 24.4857,30.2007 23.8598,30.9166C23.7112,31.0868 24.2297,31.1594 24.3024,31.1657C24.4636,31.1783 24.6312,31.1783 24.7892,31.1468C24.8904,31.1247 25.0169,31.1026 25.0832,31.0238Z"
android:fillColor="#631505"/>
<path
android:pathData="M29.0066,23.4428C30.0151,24.2848 30.7137,25.4138 31.0267,26.6972C31.052,26.8013 31.2354,26.8833 31.3176,26.9117C31.463,26.9621 31.6527,26.9747 31.8044,26.9463C32.0415,26.9022 32.3134,26.7855 32.247,26.5049C31.9214,25.1741 31.1532,23.96 30.1004,23.0802C29.876,22.891 29.5092,22.85 29.2342,22.9509C29.0508,23.0203 28.7695,23.2473 29.0066,23.4428Z"
android:fillColor="#631505"/>
<path
android:pathData="M24.8019,28.6114C24.7861,28.6082 24.7734,28.6051 24.7576,28.6019C24.8493,28.6303 24.9441,28.6587 25.0358,28.6871C25.0991,28.7659 25.118,28.7722 25.0896,28.706C24.9916,28.8321 24.8967,28.9551 24.7987,29.0812C24.9758,29.0213 24.8493,29.0182 24.4225,29.0686C24.3656,29.0434 24.3656,29.0434 24.4225,29.0718C24.3782,29.0371 24.5079,29.1411 24.5205,29.1538C24.6122,29.2389 24.6912,29.3367 24.7703,29.4313C24.9505,29.6426 25.1243,29.857 25.2951,30.0777C25.6523,30.5381 25.9906,31.0143 26.313,31.5031C26.4679,31.7365 26.8347,31.7806 27.0907,31.7365C27.2362,31.7112 27.6914,31.5693 27.5333,31.3265C27.1287,30.7116 26.6956,30.1219 26.234,29.5479C26.0285,29.2925 25.8262,29.0024 25.5764,28.788C25.1971,28.46 24.6375,28.3339 24.1601,28.5105C23.9862,28.5767 23.7997,28.6776 23.8693,28.8857C23.9515,29.1317 24.2423,29.2074 24.4636,29.2547C24.6881,29.3051 25.1243,29.261 25.2413,29.0213C25.3709,28.7596 24.9663,28.6461 24.8019,28.6114Z"
android:fillColor="#631505"/>
<path
android:pathData="M25.3614,27.2901C26.2972,27.9901 27.0781,28.8699 27.6693,29.8759C27.8621,30.207 29.0888,30.0683 28.8896,29.7308C28.27,28.6744 27.4353,27.7473 26.4553,27.0157C26.234,26.8486 25.8483,26.8454 25.5891,26.9179C25.431,26.9621 25.1307,27.1166 25.3614,27.2901Z"
android:fillColor="#631505"/>
<path
android:pathData="M27.0781,25.7732C27.3342,25.953 27.5808,26.1517 27.8084,26.3661C27.8495,26.4039 27.7736,26.3314 27.84,26.3945C27.8685,26.426 27.9001,26.4544 27.9317,26.4828C27.9854,26.5364 28.0392,26.5931 28.0898,26.6468C28.2004,26.7666 28.3047,26.8864 28.4059,27.0126C28.6019,27.2585 28.7821,27.514 28.9433,27.782C28.9623,27.8104 28.9781,27.8388 28.9939,27.8703C28.9591,27.8104 29.0129,27.905 29.0192,27.9144C29.0572,27.9807 29.0919,28.05 29.1267,28.1163C29.2026,28.2676 29.2721,28.4222 29.3385,28.5767C29.4555,28.8573 29.838,28.9646 30.1162,28.8983C30.3565,28.8416 30.6821,28.6271 30.5588,28.3307C30.0182,27.0441 29.1172,25.9215 27.9759,25.1142C27.7325,24.9407 27.3215,24.9187 27.0813,25.1142C26.8568,25.3002 26.8283,25.5966 27.0781,25.7732Z"
android:fillColor="#631505"/>
<path
android:pathData="M31.0267,22.1373C31.15,22.2193 31.2733,22.3076 31.3934,22.399C31.4187,22.418 31.4472,22.44 31.4725,22.4621C31.4693,22.459 31.5736,22.5441 31.5389,22.5157C31.5072,22.4905 31.5768,22.5504 31.5831,22.5536C31.6147,22.5819 31.6464,22.6103 31.6748,22.6355C31.8961,22.8374 32.1016,23.0581 32.2913,23.2883C32.6643,23.7487 32.9299,24.206 33.1607,24.7768C33.2713,25.0479 33.6855,25.1205 33.9384,25.0669C34.166,25.0164 34.4948,24.843 34.381,24.556C33.891,23.3325 33.0216,22.2792 31.9246,21.5476C31.6811,21.3868 31.2701,21.371 31.0299,21.5476C30.8023,21.7147 30.7833,21.9765 31.0267,22.1373Z"
android:fillColor="#631505"/>
<path
android:pathData="M44.7125,13.4085C44.9085,16.5966 44.6841,19.8163 43.9727,22.932C43.2677,26.0129 42.0727,28.9866 40.3276,31.6324C38.5635,34.3034 36.2462,36.536 33.6001,38.3303C30.9097,40.153 27.9507,41.5185 24.8904,42.6096C23.1643,43.2245 21.4539,43.7385 19.6709,44.1832C18.7478,44.4134 17.8215,44.6215 16.8888,44.8076C15.9246,44.9999 15.1121,45.1387 14.17,45.2806C13.955,45.3121 13.7401,45.3437 13.5251,45.372C13.4745,45.3783 13.4239,45.3846 13.3702,45.3941C13.2279,45.413 13.1995,45.3657 13.4745,45.4067C13.9361,45.4793 13.7748,45.4351 13.7211,45.5329C13.7685,45.4446 13.5662,45.6022 13.661,45.5675C13.5693,45.5896 13.4745,45.6117 13.3828,45.6338C13.3417,45.6369 13.3006,45.6369 13.2595,45.6401C13.1109,45.6149 12.9624,45.5896 12.8138,45.5675C12.7663,45.5297 12.7158,45.4887 12.6683,45.4509C12.6715,45.6779 13.152,45.7063 13.3006,45.7031C13.4397,45.7 13.9361,45.6811 13.9329,45.4509C13.9297,45.1103 13.2848,45.1198 13.0698,45.145C12.8833,45.1639 12.4976,45.2427 12.4723,45.4887C12.4439,45.7662 12.9118,45.864 13.0983,45.886C13.386,45.9207 13.6673,45.8829 13.9519,45.8451C14.9635,45.7063 15.972,45.5392 16.9774,45.35C18.9248,44.981 20.8564,44.5143 22.7564,43.9498C26.1708,42.9376 29.5124,41.6446 32.6106,39.8787C35.5665,38.1916 38.279,36.0409 40.394,33.3636C42.4489,30.762 43.9032,27.7284 44.82,24.5497C45.7495,21.33 46.1257,17.9558 46.0308,14.61C46.0182,14.2095 46.0024,13.809 45.9771,13.4116C45.9644,13.1814 45.5029,13.1562 45.3448,13.1594C45.2152,13.1594 44.6967,13.1814 44.7125,13.4085Z"
android:fillColor="#631505"/>
<path
android:pathData="M35.4938,13.8374C35.6993,13.9004 35.9016,13.9667 36.104,14.0423C36.1862,14.0739 36.2715,14.1054 36.3537,14.1369C36.4043,14.1559 36.4517,14.1748 36.5023,14.1969C36.4201,14.1622 36.6035,14.241 36.6193,14.2473C36.9955,14.4081 37.3622,14.5879 37.7194,14.7834C38.4624,15.1902 39.101,15.6254 39.7554,16.1615C41.0137,17.1864 42.0569,18.454 42.8473,19.8668C43.2867,20.6552 43.644,21.4877 43.919,22.3486C43.9506,22.4495 44.1277,22.522 44.2099,22.5472C44.3584,22.5945 44.545,22.6072 44.6967,22.5819C44.9054,22.5472 45.2247,22.44 45.1393,22.172C44.6303,20.5921 43.8431,19.1131 42.803,17.817C41.7092,16.4516 40.3656,15.3163 38.8449,14.446C37.9882,13.954 37.0745,13.5599 36.1324,13.2697C35.8732,13.1909 35.478,13.1909 35.2662,13.3896C35.0575,13.5819 35.2978,13.7775 35.4938,13.8374Z"
android:fillColor="#631505"/>
<path
android:pathData="M31.7254,14.6636C32.2597,14.9127 32.7781,15.1965 33.2935,15.4866C33.4136,15.5529 33.5337,15.6222 33.6539,15.6916C33.7139,15.7263 33.774,15.761 33.8341,15.7925C33.9194,15.843 33.7772,15.7547 33.8594,15.8051C33.9004,15.8304 33.9415,15.8556 33.9795,15.8808C34.2134,16.029 34.4411,16.1899 34.6655,16.357C35.1144,16.6913 35.557,17.0381 35.9902,17.3976C36.2051,17.5742 36.4138,17.754 36.6193,17.94C36.7141,18.0252 36.809,18.1135 36.9006,18.1986C36.9481,18.2428 36.9923,18.2869 37.0397,18.3311C37.065,18.3563 37.0935,18.3815 37.1188,18.4068C37.1283,18.4162 37.2231,18.5077 37.1852,18.473C37.2231,18.5077 37.3559,18.6433 37.4254,18.7126C37.5266,18.8167 37.6309,18.9239 37.7289,19.028C37.9155,19.2235 38.0957,19.4222 38.2759,19.624C38.2948,19.6461 38.3264,19.6902 38.3517,19.7091C38.4181,19.7564 38.3865,19.8416 38.3581,19.6145C38.3739,19.7312 38.4023,19.8163 38.4813,19.9078C38.5699,20.015 38.6679,20.1285 38.769,20.2231C38.8544,20.3051 38.7975,20.2421 38.7785,20.2137C38.8101,20.2578 38.8259,20.3209 38.8544,20.3619C38.8829,20.4029 38.9145,20.4407 38.9461,20.4786C39.0441,20.5953 39.1389,20.7119 39.2338,20.8318C39.5847,21.2764 39.904,21.7463 40.2043,22.2256C40.4952,22.6955 40.7639,23.2 41.0326,23.7109C41.3456,24.3037 41.5985,24.9092 41.8293,25.5399C41.9463,25.8584 42.0632,26.1737 42.1739,26.4954C42.2782,26.7981 42.3225,27.1166 42.4363,27.4131C42.5469,27.7 42.9326,27.8167 43.214,27.7473C43.4701,27.6843 43.7704,27.4604 43.6566,27.1576C43.5523,26.8864 43.5112,26.5995 43.4226,26.322C43.331,26.0381 43.2203,25.7606 43.1192,25.48C42.9105,24.906 42.7018,24.3447 42.4268,23.7992C41.8767,22.6986 41.295,21.6485 40.5647,20.6615C40.3972,20.4344 40.2233,20.2137 40.0399,19.9961C40.0146,19.9677 39.9925,19.9362 39.9641,19.9078C39.9704,19.9173 39.9735,19.9236 39.9799,19.933C39.9988,19.9646 40.002,19.9677 39.9893,19.9425C40.0178,19.933 39.8882,19.7722 39.8597,19.7407C39.8091,19.6839 39.7586,19.6303 39.708,19.5735C39.6858,19.5483 39.6606,19.5105 39.6321,19.4884C39.6195,19.4758 39.6068,19.46 39.5942,19.4474C39.6037,19.4915 39.6131,19.5388 39.6226,19.583C39.5878,19.4474 39.572,19.378 39.4835,19.2676C39.4424,19.2172 39.395,19.1667 39.3507,19.1194C39.2496,19.0091 39.1516,18.8987 39.0472,18.7883C38.8607,18.5865 38.671,18.3847 38.4782,18.1892C37.6594,17.3566 36.7742,16.6124 35.8479,15.8966C35.3104,15.4803 34.754,15.124 34.1628,14.7897C33.5717,14.4555 32.9805,14.1243 32.364,13.8374C32.1016,13.7144 31.6748,13.7522 31.4978,14.0108C31.3207,14.2662 31.4598,14.5406 31.7254,14.6636Z"
android:fillColor="#631505"/>
<path
android:pathData="M13.9519,32.1496C17.3789,33.9344 20.2399,36.7379 22.1558,40.0837C22.7027,41.0392 23.1737,42.0388 23.5594,43.07C23.5974,43.1678 23.7649,43.2371 23.8503,43.2655C23.9989,43.3097 24.1854,43.3254 24.3371,43.2971C24.53,43.2655 24.8778,43.1583 24.7797,42.8966C24.0842,41.0486 23.1232,39.3048 21.9345,37.7249C20.7363,36.1355 19.3263,34.707 17.7361,33.5119C16.8256,32.8276 15.8582,32.2158 14.8497,31.6892C14.5936,31.5536 14.2111,31.5536 13.955,31.6892C13.7243,31.809 13.7179,32.0266 13.9519,32.1496Z"
android:fillColor="#631505"/>
<path
android:pathData="M12.8549,35.5048C13.1457,35.7224 13.4366,35.9463 13.7179,36.1765C13.8602,36.2932 13.9993,36.4099 14.1384,36.5266C14.2048,36.5833 14.2712,36.6401 14.3376,36.6969C14.4134,36.7599 14.2491,36.618 14.3755,36.7315C14.4134,36.7662 14.4545,36.8009 14.4925,36.8356C15.0394,37.3212 15.5642,37.8289 16.0637,38.3619C17.0754,39.4309 17.989,40.5914 18.7889,41.8244C19.1998,42.4614 19.6203,43.2056 19.9807,43.9278C20.1135,44.1958 20.4802,44.2872 20.7584,44.2242C20.9702,44.1769 21.3401,43.9814 21.201,43.7007C19.8574,41.0045 17.989,38.5826 15.7602,36.5613C15.1153,35.9779 14.4451,35.4229 13.7495,34.9025C13.5156,34.7291 13.0888,34.7291 12.8549,34.9025C12.6209,35.076 12.6209,35.3314 12.8549,35.5048Z"
android:fillColor="#631505"/>
</vector>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<path
android:pathData="M28.3918,45.6503C26.6706,46.0998 25.0904,42.9066 20.0612,36.6197C17.2883,33.1525 16.0464,31.9659 14.5136,29.0283C13.3259,26.7435 12.795,25.0051 12.6671,23.1523C12.5879,21.995 12.4904,20.2042 13.4419,18.2689C13.847,17.4346 14.7443,15.6036 16.771,14.8311C18.4692,14.1823 20.0221,14.6402 21.0379,14.9365C21.6152,15.1083 23.6594,15.7348 25.2249,17.626C26.7905,19.5173 26.7092,21.2619 27.4597,21.2859C28.2216,21.3096 28.296,19.5252 30.1681,17.5633C30.6959,17.0111 31.7188,15.9648 33.38,15.4199C34.9563,14.906 36.2957,15.1073 37.1064,15.2381C38.1703,15.4131 40.0793,15.7178 41.4281,17.2383C42.3334,18.2633 42.5596,19.3592 42.8213,20.694C43.406,23.6989 42.8385,26.2458 42.4845,27.467C41.7146,30.1397 40.4309,31.8676 38.9378,33.8757C38.1004,35.0023 38.5094,34.2994 34.5868,38.7881C33.0299,40.5696 31.3322,42.2294 29.8693,44.0939C28.9141,45.3039 28.8065,45.5412 28.3918,45.6503Z"
android:fillColor="#DF579A"/>
<path
android:pathData="M28.8814,45.3619C26.2254,42.9709 23.8027,40.3332 21.4306,37.6655C19.171,35.126 16.9096,32.5236 15.3566,29.4838C13.8586,26.5624 12.9862,23.1031 13.7679,19.8474C14.1253,18.3519 14.88,16.9364 16.0763,15.9479C17.2001,15.0244 18.5636,14.6765 19.9863,14.9896C23.1429,15.6868 25.669,18.3386 26.6092,21.3676C26.6915,21.6394 27.2862,21.6222 27.497,21.5876C27.7305,21.5466 28.0762,21.428 27.982,21.1394C27.0983,18.286 24.8543,15.8888 22.0697,14.7924C20.3734,14.1216 18.4036,13.8929 16.6658,14.5599C15.1602,15.1404 13.9332,16.2554 13.161,17.66C11.4646,20.7536 11.908,24.6137 13.1212,27.7833C14.4028,31.1393 16.6431,33.9993 18.9812,36.6795C21.42,39.4766 23.9152,42.2493 26.6216,44.7988C26.9628,45.1203 27.3096,45.4359 27.6564,45.7514C27.8916,45.9674 28.3544,45.9426 28.6379,45.8488C28.8591,45.7796 29.1223,45.5778 28.8814,45.3619Z"
android:fillColor="#730173"/>
<path
android:pathData="M28.0482,21.446C29.1461,18.8324 31.2353,16.4586 34.0276,15.6412C35.2307,15.2866 36.4565,15.3197 37.6575,15.6849C38.8756,16.0496 40.0066,16.7653 40.7859,17.7881C41.7734,19.0849 42.0919,20.8008 42.1608,22.3925C42.2336,24.1212 41.9511,25.8373 41.4148,27.4808C40.3372,30.7964 38.2964,33.66 36.0016,36.2454C33.6015,38.9537 30.8955,41.3738 28.7185,44.2756C28.4374,44.6493 28.1679,45.0284 27.9041,45.4073C27.4867,46.0134 28.8496,46.2368 29.1517,45.7997C30.256,44.2027 31.4891,42.7047 32.8221,41.2953C34.1217,39.9212 35.4742,38.5969 36.7444,37.195C39.1853,34.514 41.4345,31.5359 42.6572,28.0904C43.2603,26.3822 43.6199,24.561 43.6018,22.745C43.5867,21.0318 43.2804,19.1442 42.2945,17.7046C40.5796,15.2007 37.2767,14.1936 34.3603,14.6776C31.2217,15.1966 28.6263,17.4822 27.1602,20.2264C26.9812,20.5686 26.8138,20.9161 26.6637,21.2689C26.4226,21.8357 27.8397,21.9547 28.0482,21.446Z"
android:fillColor="#730173"/>
</vector>

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -0,0 +1,57 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<path
android:pathData="M9.6737,34.757L13.9371,34.1543L15.971,32.025C15.971,32.025 17.5995,30.4025 19.4263,30.9133C21.2531,31.4242 22.9778,31.4273 23.6851,32.7489C24.3924,34.0704 25.4039,35.9993 25.4039,35.9993C25.4039,35.9993 27.2339,34.7854 27.8404,34.8857C28.4469,34.986 30.4807,34.9936 30.7816,35.2956C31.0863,35.6014 31.8005,33.1644 32.2052,33.066C32.6098,32.9637 33.7303,31.6493 34.341,31.5474C34.9517,31.4455 36.8805,30.434 37.6925,30.8438C38.5045,31.2498 40.3314,31.7606 40.7349,32.2689C41.1384,32.7771 41.6443,33.69 42.1516,33.7939C42.6589,33.8979 44.4874,33.493 44.4874,33.493L47.2276,33.1927L47.2258,34.2077C47.2258,34.2077 46.0078,34.61 45.297,35.2192C44.5861,35.8284 43.3674,36.6351 43.1604,37.1422C42.9534,37.6493 42.2445,39.2736 42.0375,39.7807C41.8344,40.2878 41.3252,41.1988 40.7139,41.606C40.1027,42.0094 38.4797,42.7199 37.6665,42.9207C36.8534,43.1214 35.2321,42.9162 34.5189,42.7127C33.8095,42.5091 32.1865,41.1859 31.8821,40.6778C31.5778,40.1698 30.7667,39.2563 30.768,38.5428C30.7693,37.8292 30.4659,36.8175 30.4659,36.8175C30.4659,36.8175 29.4517,36.4112 28.7416,36.6121C28.0315,36.813 26.7116,36.6084 26.3064,37.0121C25.9012,37.4158 25.4934,37.1136 25.2901,37.72C25.0867,38.3301 24.8821,39.6462 24.473,40.0537C24.0678,40.4574 23.3519,41.7764 22.4393,42.08C21.5268,42.3836 21.2206,42.8905 19.9004,42.8881C18.5801,42.8856 16.5503,42.7789 15.9409,42.1711C15.3315,41.5632 13.9131,40.9501 13.6099,39.8315C13.3067,38.713 13.2084,38.2053 12.5987,37.7997C11.9889,37.3941 10.4681,36.4755 10.4681,36.4755L9.6563,35.9665L9.6585,34.7569L9.6737,34.757Z"
android:fillColor="#666666"/>
<path
android:pathData="M3.8447,28.7208C4.1217,28.6274 16.003,23.5626 16.003,23.5626L17.9382,19.1417L22.3556,10.3934L24.4743,11.2208L27.0535,11.9582L29.9996,10.8539L32.3952,9.566L33.96,9.2891L36.1687,11.4078L37.7335,16.843L39.4818,21.4473L41.417,22.6452C41.417,22.6452 48.8774,25.7782 49.1544,25.6847C49.4313,25.5913 52.1005,26.7891 52.1005,26.7891L44.8235,27.5265L36.0752,27.4503L26.5896,28.0042L15.9995,28.5374L6.8808,29.0913L3.8447,28.7208Z"
android:fillColor="#666666"/>
<path
android:pathData="M24.7343,37.0015C24.7185,39.4092 23.2338,41.6425 20.9924,42.5008C20.8282,42.5654 20.8015,42.5729 20.6297,42.6222C20.4808,42.6639 20.3319,42.7018 20.1792,42.7321C20.1449,42.7396 20.1105,42.7472 20.0762,42.751C20.1716,42.732 20.0838,42.751 20.0647,42.7509C19.9884,42.7622 19.9082,42.7735 19.8319,42.7848C19.7556,42.7962 19.6754,42.8036 19.5953,42.8111C19.5609,42.8149 19.5266,42.8148 19.4884,42.8186C19.5953,42.8111 19.477,42.8185 19.435,42.8185C19.2938,42.822 19.1488,42.8218 19.0076,42.8177C19.0114,42.8177 18.8359,42.8097 18.9275,42.8137C19.0038,42.8177 18.8932,42.8098 18.8741,42.8098C18.7863,42.802 18.6986,42.7942 18.6147,42.7826C18.5384,42.771 18.4583,42.7594 18.382,42.7478C18.3629,42.744 18.2752,42.7286 18.3705,42.7478C18.3362,42.7401 18.3019,42.7362 18.2675,42.7286C18.1149,42.6977 17.9662,42.6593 17.8175,42.6171C17.6458,42.5672 17.6192,42.5595 17.4552,42.4943C17.3141,42.4406 17.173,42.3793 17.0396,42.3142C16.7765,42.1878 16.5517,42.0615 16.3077,41.8931C15.839,41.5717 15.4123,41.1856 15.0583,40.7423C13.5126,38.8201 13.304,36.0647 14.5709,33.9417C15.1642,32.9507 16.0509,32.1281 17.0821,31.6492C17.2119,31.5884 17.3418,31.5314 17.4754,31.482C17.6472,31.4175 17.6587,31.4137 17.8381,31.3606C17.987,31.3189 18.1359,31.281 18.2886,31.2507C18.3229,31.2432 18.3573,31.2356 18.3916,31.2319C18.3687,31.2356 18.2924,31.2469 18.4031,31.2319C18.4909,31.2206 18.5749,31.2055 18.6626,31.1942C18.7313,31.1867 18.8,31.1792 18.8725,31.1717C18.9069,31.1679 18.9412,31.168 18.9794,31.1643C18.8573,31.1717 18.9756,31.1642 19.0061,31.1643C19.174,31.157 19.3457,31.1611 19.5136,31.1652C19.6319,31.1769 19.5365,31.1653 19.5136,31.1652C19.5479,31.1691 19.5823,31.1692 19.6204,31.1731C19.6891,31.1808 19.7616,31.1848 19.8303,31.1963C19.9066,31.2041 19.9867,31.2157 20.063,31.2311C20.0782,31.235 20.185,31.2504 20.1202,31.2388C20.0668,31.2273 20.2079,31.2581 20.227,31.2619C21.295,31.4928 22.3394,32.0671 23.1354,32.9233C23.9465,33.7986 24.4902,34.9024 24.6673,36.0818C24.7126,36.3833 24.7311,36.6924 24.7343,37.0015C24.7339,37.2266 25.1803,37.2618 25.3291,37.2621C25.4741,37.2585 25.9282,37.2326 25.9248,37.0037C25.9183,34.3364 24.1751,31.9369 21.6621,31.0547C19.0004,30.1226 15.8779,30.7961 14.027,32.9715C12.3062,34.9907 12.0832,37.9856 13.4718,40.2433C14.9708,42.6805 17.9299,43.7239 20.6782,43.1985C23.2815,42.6996 25.4031,40.6315 25.8429,37.9995C25.8969,37.6676 25.9242,37.3357 25.9248,36.9999C25.9293,36.6412 24.735,36.6352 24.7343,37.0015Z"
android:fillColor="#000000"/>
<path
android:pathData="M32.036,39.9798C30.8303,37.8598 31.0413,35.1777 32.5749,33.2764C32.9459,32.8154 33.4045,32.4118 33.9011,32.0883C34.138,31.9362 34.413,31.7878 34.6765,31.67C34.94,31.5522 35.1538,31.4763 35.4669,31.3929C35.5318,31.374 35.6005,31.3588 35.6692,31.3437C35.7036,31.3361 35.7379,31.3286 35.7685,31.321C35.6654,31.3437 35.7761,31.321 35.8028,31.3172C35.9555,31.2908 36.1043,31.2682 36.257,31.2532C36.2837,31.2494 36.5241,31.2308 36.3791,31.242C36.4478,31.2383 36.5127,31.2346 36.5814,31.2347C36.734,31.2312 36.8866,31.2353 37.0354,31.2394C37.1499,31.2434 37.0507,31.2394 37.0278,31.2394C37.0621,31.2432 37.0965,31.2433 37.127,31.2472C37.2033,31.2549 37.2758,31.2627 37.3521,31.2705C37.4284,31.2782 37.5009,31.2898 37.5772,31.3052C37.6115,31.3091 37.642,31.3168 37.6763,31.3245C37.6878,31.3283 37.8213,31.3515 37.7526,31.3399C37.6725,31.3245 37.8327,31.3591 37.8289,31.3591C37.8632,31.3668 37.8937,31.3745 37.9281,31.3822C37.9929,31.3976 38.0577,31.4168 38.1226,31.436C38.2675,31.4782 38.4124,31.5243 38.5535,31.5779C38.6488,31.6125 38.5954,31.5933 38.5726,31.5856C38.6107,31.6009 38.6488,31.6163 38.6908,31.6316C38.7518,31.6584 38.8128,31.6853 38.8738,31.7121C39.0187,31.7772 39.1597,31.85 39.3007,31.9304C39.5409,32.0644 39.7429,32.1983 39.9677,32.3742C40.444,32.7452 40.8629,33.1848 41.2016,33.6853C41.8904,34.7016 42.2545,35.8966 42.2332,37.1252C42.2158,38.3004 41.8473,39.4407 41.1816,40.4086C40.8449,40.8964 40.4359,41.3231 39.9697,41.6847C39.7328,41.8712 39.4959,42.0234 39.2362,42.168C39.1025,42.2402 38.9688,42.3087 38.8275,42.3695C38.6786,42.4379 38.5602,42.4834 38.4609,42.5214C38.3235,42.5708 38.1822,42.6163 38.0371,42.6542C37.9608,42.6731 37.8882,42.6921 37.8119,42.711C37.7775,42.7186 37.7432,42.7261 37.7126,42.7337C37.8081,42.7148 37.6821,42.7375 37.6516,42.7451C37.518,42.7677 37.3806,42.7903 37.247,42.8015C37.2126,42.8053 37.1783,42.8091 37.1439,42.8128C37.1439,42.8128 36.976,42.8278 37.0714,42.8203C37.1516,42.8128 37.0409,42.8202 37.0256,42.824C36.9837,42.824 36.9417,42.8277 36.8997,42.8276C36.7471,42.8312 36.5983,42.8271 36.4456,42.823C36.4189,42.8229 36.3274,42.8151 36.4533,42.823C36.4189,42.8191 36.3846,42.819 36.3541,42.8152C36.2701,42.8074 36.1862,42.7996 36.1023,42.788C35.9688,42.7687 35.8352,42.7494 35.7056,42.7224C35.8162,42.7455 35.6026,42.6993 35.5797,42.6916C35.4996,42.6724 35.4157,42.6494 35.3356,42.6263C34.8322,42.4804 34.3213,42.2543 33.864,41.9444C33.3761,41.6192 32.9266,41.2215 32.5611,40.763C32.367,40.5069 32.1919,40.251 32.036,39.9798C31.8344,39.636 30.6703,39.7636 30.8872,40.1341C32.287,42.5444 35.1427,43.7593 37.8718,43.3216C39.2459,43.099 40.5597,42.4795 41.5536,41.493C42.4787,40.5751 43.0952,39.401 43.3303,38.1193C43.8198,35.4606 42.451,32.768 40.1029,31.4778C37.6518,30.1301 34.4155,30.418 32.2792,32.2342C30.2727,33.94 29.5349,36.7814 30.4653,39.2442C30.583,39.5535 30.7236,39.8476 30.8872,40.1341C31.085,40.4817 32.249,40.3541 32.036,39.9798Z"
android:fillColor="#000000"/>
<path
android:pathData="M25.9341,36.0841C26.2818,35.8405 26.5797,35.6846 26.9692,35.5518C27.0608,35.5214 27.1563,35.4911 27.2517,35.4646C27.1792,35.4835 27.2861,35.457 27.309,35.4494C27.3586,35.438 27.4044,35.4267 27.454,35.4191C27.5495,35.4002 27.6487,35.3813 27.7441,35.3701C27.8014,35.3625 27.8624,35.355 27.9197,35.3475C28.0266,35.3362 27.9044,35.3475 27.9693,35.3438C28.1677,35.3327 28.3623,35.333 28.5608,35.341C28.5951,35.3449 28.6943,35.3527 28.6103,35.3449C28.6676,35.3527 28.7286,35.3566 28.7858,35.3643C28.885,35.376 28.9804,35.3952 29.0757,35.4145C29.3809,35.4761 29.7927,35.6142 30.0901,35.775C30.3417,35.909 30.6812,35.9364 30.9334,35.7766C31.1397,35.6472 31.1707,35.3725 30.9343,35.25C29.1236,34.2927 26.7844,34.3724 25.088,35.556C24.8779,35.7006 24.8622,35.941 25.087,36.0826C25.3157,36.228 25.7087,36.2401 25.9341,36.0841Z"
android:fillColor="#000000"/>
<path
android:pathData="M9.3687,34.6648C9.3648,34.6724 9.3572,34.6762 9.3533,34.6838C9.5977,34.6156 9.842,34.5473 10.0864,34.4791C10.0787,34.4791 10.0673,34.4753 10.0596,34.4752C10.1779,34.5212 10.2961,34.5673 10.4143,34.6133C10.4066,34.6056 10.399,34.6018 10.3914,34.5941C10.418,34.6438 10.4446,34.6934 10.4713,34.7431C10.4712,34.7507 10.4712,34.7545 10.4712,34.7622C10.4215,34.8041 10.368,34.8498 10.3184,34.8916C10.3298,34.884 10.3451,34.8802 10.3565,34.8726C10.2687,34.8992 10.1809,34.9219 10.0931,34.9485C10.6275,34.8426 11.1847,34.8016 11.7267,34.7301C12.2801,34.6587 12.8373,34.5834 13.3908,34.5119C13.5434,34.4931 13.9252,34.3793 13.8073,34.1501C13.6933,33.9286 13.2774,33.9164 13.0751,33.9427C12.2507,34.0519 11.4225,34.161 10.5942,34.2664C10.3958,34.2927 10.2011,34.3191 10.0027,34.3454C9.7775,34.3755 9.4569,34.3902 9.3192,34.5998C9.189,34.8018 9.4139,34.9587 9.5931,35.0124C9.8982,35.1045 10.2951,35.0786 10.5093,34.8233C10.5705,34.7509 10.502,34.6439 10.4486,34.5942C10.361,34.514 10.2047,34.4717 10.094,34.4562C9.8422,34.4291 9.5446,34.4552 9.3687,34.6648Z"
android:fillColor="#000000"/>
<path
android:pathData="M10.1651,35.2424C10.1689,35.2386 10.1766,35.2348 10.1804,35.2348C10.3179,35.1626 10.4898,35.056 10.4749,34.8728C10.4377,34.3271 9.5907,34.2721 9.2962,34.6341C9.1891,34.7675 9.2003,34.9392 9.2,35.1033C9.1997,35.2597 9.1994,35.4162 9.1991,35.5726C9.1988,35.7405 9.1794,35.9274 9.2516,36.0878C9.3352,36.2711 9.5105,36.3745 9.6859,36.4549C9.9413,36.5699 10.2006,36.6772 10.4484,36.815C10.8753,37.0486 11.3669,37.3891 11.7708,37.7332C12.1823,38.0812 12.5516,38.4864 12.879,38.9143C13.0541,39.1436 13.4433,39.171 13.6953,39.0685C13.9321,38.9697 14.0851,38.722 13.91,38.4927C13.3467,37.759 12.661,37.1129 11.895,36.5926C11.5024,36.3285 11.0908,36.095 10.6638,35.8958C10.5685,35.8499 10.4731,35.8115 10.3778,35.7694C10.1262,35.6621 10.4044,35.8572 10.3967,35.8572C10.4006,35.8572 10.3971,35.6549 10.3971,35.6473C10.3973,35.5252 10.3976,35.4069 10.3978,35.2848C10.3979,35.2123 10.3981,35.1398 10.3982,35.0673C10.3982,35.0368 10.4253,34.8613 10.3907,34.9605C10.341,35.0214 10.2875,35.0824 10.2378,35.1471C10.2454,35.1395 10.2569,35.1319 10.2645,35.1243C10.0699,35.1583 9.879,35.1961 9.6843,35.2301C9.692,35.2301 9.6996,35.2339 9.7072,35.2339C9.5891,35.1689 9.4709,35.1038 9.3527,35.0387C9.3565,35.0463 9.3641,35.054 9.3679,35.0578C9.3414,34.9852 9.3148,34.9165 9.2882,34.844C9.2882,34.8516 9.2882,34.8592 9.2882,34.8669C9.3876,34.7449 9.487,34.6268 9.5826,34.5049C9.5788,34.5087 9.5712,34.5125 9.5674,34.5125C9.3497,34.6266 9.1623,34.8514 9.3526,35.0883C9.5201,35.2985 9.9283,35.3679 10.1651,35.2424Z"
android:fillColor="#000000"/>
<path
android:pathData="M42.2428,33.9925C43.7351,33.8159 45.2502,33.6775 46.7271,33.581C46.9179,33.5699 47.3224,33.521 47.3229,33.2501C47.3234,32.9982 46.9115,32.9059 46.7283,32.917C45.1255,33.0209 43.5226,33.163 41.9273,33.3508C41.7479,33.3734 41.4004,33.5178 41.5106,33.7584C41.6208,34.0028 42.0214,34.0188 42.2428,33.9925Z"
android:fillColor="#000000"/>
<path
android:pathData="M46.3611,33.3933C46.4176,33.7788 46.4818,34.1643 46.5498,34.546C46.6491,34.4623 46.7485,34.3823 46.844,34.2985C45.7485,34.5522 44.702,35.0539 43.8307,35.7659C42.9976,36.4474 42.145,37.3921 42.0895,38.5215C42.0697,38.9107 43.2602,38.9319 43.28,38.5237C43.3276,37.5126 44.1152,36.6669 44.8604,36.0578C45.6209,35.4372 46.4993,35.0115 47.446,34.7919C47.5338,34.773 47.7668,34.678 47.7403,34.5444C47.6685,34.1627 47.6082,33.7772 47.5516,33.3917C47.5177,33.1512 47.1515,33.101 46.9569,33.1044C46.8271,33.1118 46.3272,33.1567 46.3611,33.3933Z"
android:fillColor="#000000"/>
<path
android:pathData="M25.9884,37.7364C26.3361,37.4929 26.634,37.337 27.0234,37.2041C27.1151,37.1738 27.2105,37.1434 27.3059,37.1169C27.2334,37.1358 27.3403,37.1093 27.3632,37.1017C27.4128,37.0904 27.4586,37.079 27.5083,37.0715C27.6037,37.0526 27.7029,37.0337 27.7984,37.0224C27.8556,37.0149 27.9167,37.0074 27.9739,36.9998C28.0808,36.9886 27.9587,36.9998 28.0235,36.9961C28.222,36.985 28.4166,36.9854 28.615,36.9934C28.6493,36.9972 28.7485,37.0051 28.6646,36.9973C28.7218,37.005 28.7829,37.0089 28.8401,37.0167C28.9393,37.0283 29.0346,37.0476 29.13,37.0668C29.4351,37.1284 29.847,37.2666 30.1443,37.4274C30.3959,37.5614 30.7355,37.5887 30.9876,37.4289C31.1939,37.2995 31.2249,37.0249 30.9886,36.9023C29.1778,35.9451 26.8386,36.0247 25.1422,37.2083C24.9321,37.3529 24.9164,37.5933 25.1413,37.7349C25.3699,37.8803 25.7629,37.8963 25.9884,37.7364Z"
android:fillColor="#000000"/>
<path
android:pathData="M3.6786,29.2747C5.2572,29.517 6.8566,29.472 8.4456,29.3612C10.0451,29.2504 11.641,29.1258 13.2404,29.0185C16.4184,28.8039 19.5965,28.6169 22.7745,28.4611C29.1341,28.1461 35.5006,27.9418 41.867,27.8484C45.3947,27.7964 48.9501,27.7895 52.4294,27.1144C52.6267,27.0764 52.9002,26.9206 52.8067,26.6782C52.7167,26.4428 52.3532,26.3909 52.142,26.4324C49.099,27.0244 45.9971,27.0833 42.9091,27.1248C39.7622,27.1664 36.6188,27.2321 33.4753,27.3256C27.1435,27.516 20.8116,27.8137 14.4902,28.2257C12.7246,28.34 10.9625,28.4646 9.2003,28.5961C7.459,28.7242 5.7003,28.8558 3.9659,28.5892C3.7478,28.5546 3.3947,28.5961 3.3012,28.835C3.2043,29.0739 3.4813,29.2435 3.6786,29.2747Z"
android:fillColor="#000000"/>
<path
android:pathData="M16.273,23.7944C19.5099,23.7044 22.7434,23.6144 25.9803,23.5244C29.2172,23.4344 32.4506,23.3444 35.6875,23.2544C37.505,23.2025 39.326,23.154 41.1435,23.1021C41.3443,23.0951 41.6836,23.0086 41.6836,22.7524C41.6836,22.5031 41.3374,22.3958 41.1435,22.4027C37.9066,22.4928 34.6732,22.5828 31.4363,22.6728C28.1994,22.7628 24.9659,22.8528 21.729,22.9428C19.9115,22.9947 18.0906,23.0432 16.273,23.0951C16.0723,23.1021 15.733,23.1886 15.733,23.4448C15.733,23.6975 16.0792,23.8014 16.273,23.7944Z"
android:fillColor="#000000"/>
<path
android:pathData="M16.4946,23.3513C17.8275,19.7855 19.4546,16.3305 21.3586,13.0348C21.9021,12.0966 22.4664,11.1688 23.0515,10.2583C23.19,10.0402 22.8334,9.8533 22.6741,9.8291C22.418,9.791 22.1549,9.8464 22.0095,10.0714C19.9531,13.2737 18.1633,16.6456 16.6642,20.1456C16.2384,21.1426 15.8334,22.15 15.456,23.1644C15.2691,23.6456 16.3388,23.7702 16.4946,23.3513Z"
android:fillColor="#000000"/>
<path
android:pathData="M22.2587,10.5387C23.19,10.9923 24.1178,11.5081 25.094,11.8612C26.0634,12.2143 27.0777,12.3113 28.0782,12.0308C28.9679,11.7816 29.7676,11.3281 30.5639,10.8676C31.3255,10.4245 32.1044,9.9329 32.9595,9.7702C33.0184,9.7598 33.0772,9.7494 33.1361,9.739C33.3161,9.7079 33.0495,9.739 33.2295,9.7287C33.3473,9.7217 33.465,9.7183 33.5861,9.7287C33.5619,9.7252 33.503,9.7148 33.6173,9.7321C33.6761,9.739 33.735,9.7494 33.7904,9.7633C33.9012,9.7875 33.9635,9.8083 34.0881,9.8637C34.1296,9.881 34.1712,9.9017 34.2127,9.9225C34.1262,9.8775 34.2543,9.9502 34.2612,9.9537C34.3235,9.9918 34.3858,10.0368 34.4447,10.0818C34.5659,10.1752 34.6766,10.286 34.8082,10.4453C35.2513,10.9957 35.5006,11.7262 35.7014,12.429C35.9714,13.3706 36.2033,14.3192 36.4941,15.2539C36.8057,16.2613 37.1484,17.2618 37.5258,18.2484C37.73,18.7816 37.9412,19.3147 38.1628,19.8409C38.3532,20.2979 38.5436,20.7549 38.7929,21.1842C39.0352,21.6031 39.3156,21.9839 39.6895,22.292C40.0842,22.6209 40.5654,22.7801 41.0708,22.8182C41.2854,22.832 41.6109,22.6936 41.6109,22.4408C41.6109,22.1673 41.282,22.0808 41.0708,22.0635C41.1989,22.0739 41.0466,22.06 41.0258,22.0566C40.9808,22.0496 40.9358,22.0393 40.8908,22.0254C41.0016,22.0566 40.8596,22.0116 40.8458,22.0046C40.8042,21.9873 40.7627,21.97 40.7211,21.9492C40.8077,21.9908 40.6796,21.925 40.6727,21.9181C40.6484,21.9008 40.6208,21.8835 40.5965,21.8662C40.5584,21.8385 40.5204,21.8108 40.4857,21.7831C40.4615,21.7623 40.4373,21.745 40.413,21.7242C40.4407,21.7485 40.4338,21.7415 40.3923,21.7069C40.1188,21.4438 39.9041,21.1253 39.731,20.8033C39.281,19.9656 38.9452,19.0516 38.6059,18.1654C38.2355,17.1995 37.8962,16.2232 37.5881,15.2366C37.2904,14.2845 37.0411,13.3221 36.778,12.3632C36.5738,11.6189 36.3176,10.8815 35.8745,10.2445C35.3863,9.5452 34.6316,9.0778 33.78,8.9878C32.7553,8.8805 31.7721,9.2405 30.8824,9.7148C30.0792,10.1441 29.321,10.6461 28.5179,11.013C28.0644,11.2207 27.7286,11.3419 27.2577,11.4146C27.2058,11.4215 27.1504,11.4285 27.0985,11.4354C27.1989,11.425 26.9912,11.4423 26.9739,11.4423C26.8631,11.4458 26.7488,11.4458 26.638,11.4388C26.4719,11.4319 26.1222,11.3592 25.9318,11.3038C25.4125,11.1584 24.9175,10.9161 24.4259,10.6807C23.8858,10.4176 23.3423,10.1545 22.8022,9.8914C22.5807,9.784 22.2241,9.8325 22.0614,10.0264C21.8918,10.2376 22.0579,10.4418 22.2587,10.5387Z"
android:fillColor="#000000"/>
<path
android:pathData="M22.276,10.7915C22.868,11.6431 23.4808,12.4774 24.1178,13.2979C24.7201,14.0699 25.3225,14.8523 25.9907,15.5724C26.6588,16.2925 27.4654,16.8014 28.4763,16.8083C29.463,16.8152 30.4081,16.379 31.2009,15.8216C32.0075,15.2573 32.7518,14.5303 33.6311,14.1288C33.8354,14.0353 33.9912,13.8103 33.825,13.606C33.6623,13.4052 33.3092,13.3671 33.0841,13.4675C32.2775,13.838 31.6024,14.3988 30.9031,14.9354C30.2488,15.4408 29.5564,15.8978 28.7775,16.0224C28.7775,16.0224 28.594,16.0467 28.6979,16.0363C28.6425,16.0432 28.5871,16.0432 28.5283,16.0432C28.4902,16.0432 28.4521,16.0432 28.4175,16.0432C28.414,16.0432 28.234,16.0294 28.3378,16.0397C28.1163,16.0155 28.0055,15.9809 27.8186,15.8978C27.6766,15.832 27.5174,15.7282 27.365,15.607C27.2162,15.4893 27.0881,15.3647 26.9565,15.2262C26.3161,14.5477 25.7414,13.7999 25.1667,13.0694C24.4847,12.2005 23.8304,11.3142 23.2004,10.4072C22.9338,10.0125 21.9264,10.2895 22.276,10.7915Z"
android:fillColor="#000000"/>
<path
android:pathData="M40.5342,22.7039C43.4249,23.8395 46.3329,24.9438 49.2513,26.0101C50.0753,26.3113 50.9027,26.609 51.7301,26.9067C51.9412,26.9829 52.2943,26.9656 52.4709,26.8132C52.6648,26.6471 52.4224,26.5121 52.277,26.4567C49.414,25.4319 46.5649,24.3726 43.7261,23.2786C42.916,22.9671 42.1094,22.652 41.3027,22.337C41.0673,22.2435 40.7731,22.2193 40.5377,22.337C40.3542,22.427 40.3196,22.6209 40.5342,22.7039Z"
android:fillColor="#000000"/>
<path
android:pathData="M4.3814,28.9804C7.4694,27.4502 10.6059,26.0101 13.7805,24.6669C14.6875,24.2826 15.598,23.9052 16.5119,23.5383C16.7162,23.4552 16.692,23.2682 16.5119,23.1817C16.2835,23.0709 15.9788,23.0882 15.7469,23.1817C12.548,24.473 9.3908,25.8578 6.275,27.336C5.3853,27.7584 4.4991,28.1876 3.6163,28.6238C3.4259,28.7173 3.4224,28.8869 3.6163,28.9804C3.8309,29.0843 4.1633,29.0877 4.3814,28.9804Z"
android:fillColor="#000000"/>
</vector>

Some files were not shown because too many files have changed in this diff Show More