add offline Android inventory workflows
This commit is contained in:
1 parent
74999c6ae6
commit
5f2e755898
419 files changed
+3433
-203
No files matched your search
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
alias(libs.plugins.ksp)
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -52,6 +53,10 @@ dependencies {
|
||||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||
implementation(libs.androidx.lifecycle.runtime.compose)
|
||||
implementation(libs.kotlinx.coroutines.android)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
implementation(libs.androidx.room.runtime)
|
||||
implementation(libs.androidx.room.ktx)
|
||||
ksp(libs.androidx.room.compiler)
|
||||
implementation(libs.google.code.scanner)
|
||||
implementation(libs.zxing.core)
|
||||
implementation(libs.androidx.camera.camera2)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
@@ -29,7 +30,6 @@
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.Manifest"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
|
||||
@@ -3,6 +3,7 @@ package app.boxmanifest
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONObject
|
||||
import org.json.JSONArray
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
|
||||
@@ -16,8 +17,12 @@ data class ApiNode(
|
||||
val description: String? = null,
|
||||
val typeId: String? = null,
|
||||
val tagIds: List<String> = emptyList(),
|
||||
val revision: Long = 1,
|
||||
val pending: Boolean = false,
|
||||
)
|
||||
|
||||
class ApiException(val status: Int, val code: String?, message: String) : Exception(message)
|
||||
|
||||
data class ImportNode(
|
||||
val name: String,
|
||||
val description: String?,
|
||||
@@ -33,6 +38,19 @@ data class TreeImportPreview(
|
||||
val nodes: List<ImportNode>,
|
||||
)
|
||||
|
||||
data class ServerInfo(
|
||||
val serverId: String,
|
||||
val name: String,
|
||||
val apiVersion: Int,
|
||||
)
|
||||
|
||||
interface MetadataOption { val id: String; val name: String }
|
||||
data class NodeType(override val id: String, override val name: String, val description: String?, val iconKey: String?, val color: String?) : MetadataOption
|
||||
data class Tag(override val id: String, override val name: String, val color: String?) : MetadataOption
|
||||
|
||||
const val DEFAULT_TYPE_COLOR = "#9b9b9b"
|
||||
const val DEFAULT_TAG_COLOR = "#8c9ea5"
|
||||
|
||||
enum class ChildHandling {
|
||||
WITH_SUBTREE,
|
||||
PROMOTE_CHILDREN,
|
||||
@@ -40,33 +58,68 @@ enum class ChildHandling {
|
||||
}
|
||||
|
||||
class BoxManifestApi(
|
||||
private val baseUrl: String = "http://127.0.0.1:8080",
|
||||
baseUrl: String = app.boxmanifest.data.DEFAULT_SERVER_URL,
|
||||
) {
|
||||
fun canonicalNodeUrl(lookupCode: String): String = "$baseUrl/n/$lookupCode"
|
||||
@Volatile
|
||||
private var baseUrl = baseUrl.trimEnd('/')
|
||||
|
||||
fun setBaseUrl(value: String) {
|
||||
baseUrl = value.trimEnd('/')
|
||||
}
|
||||
|
||||
suspend fun getSystemInfo(): ServerInfo {
|
||||
val response = request("GET", "/api/system/info")
|
||||
return ServerInfo(
|
||||
serverId = response.getString("serverId"),
|
||||
name = response.getString("name"),
|
||||
apiVersion = response.getInt("apiVersion"),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getTree(): ApiNode = request("GET", "/api/tree").toNode()
|
||||
|
||||
suspend fun createNode(parentId: String?, name: String, description: String?, quantity: Long?): ApiNode {
|
||||
suspend fun getNodeTypes(): List<NodeType> = requestArray("GET", "/api/node-types").let { values ->
|
||||
List(values.length()) { values.getJSONObject(it).toNodeType() }
|
||||
}
|
||||
|
||||
suspend fun getTags(): List<Tag> = requestArray("GET", "/api/tags").let { values ->
|
||||
List(values.length()) { values.getJSONObject(it).toTag() }
|
||||
}
|
||||
|
||||
suspend fun createNodeType(name: String, color: String = DEFAULT_TYPE_COLOR): NodeType =
|
||||
request("POST", "/api/node-types", JSONObject().put("name", name).put("color", color).put("iconKey", "cube")).toNodeType()
|
||||
|
||||
suspend fun updateNodeType(type: NodeType): NodeType =
|
||||
request("PUT", "/api/node-types/${type.id}", JSONObject().put("name", type.name).put("description", type.description ?: JSONObject.NULL).put("color", type.color ?: JSONObject.NULL).put("iconKey", type.iconKey ?: "cube")).toNodeType()
|
||||
|
||||
suspend fun deleteNodeType(id: String) { request("DELETE", "/api/node-types/$id", expectBody = false) }
|
||||
|
||||
suspend fun createTag(name: String, color: String = DEFAULT_TAG_COLOR): Tag =
|
||||
request("POST", "/api/tags", JSONObject().put("name", name).put("color", color)).toTag()
|
||||
|
||||
suspend fun updateTag(tag: Tag): Tag =
|
||||
request("PUT", "/api/tags/${tag.id}", JSONObject().put("name", tag.name).put("color", tag.color ?: JSONObject.NULL)).toTag()
|
||||
|
||||
suspend fun deleteTag(id: String) { request("DELETE", "/api/tags/$id", expectBody = false) }
|
||||
|
||||
suspend fun classifyNodes(ids: Set<String>, typeId: String? = null, setType: Boolean = false, addTagIds: List<String> = emptyList(), removeTagIds: List<String> = emptyList()) {
|
||||
request(
|
||||
"POST", "/api/nodes/bulk/classification",
|
||||
JSONObject().put("nodeIds", JSONArray(ids.toList())).put("setType", setType)
|
||||
.put("typeId", typeId ?: JSONObject.NULL).put("addTagIds", JSONArray(addTagIds)).put("removeTagIds", JSONArray(removeTagIds)),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun createNode(parentId: String?, name: String, description: String?, quantity: Long?, clientId: String? = null): ApiNode {
|
||||
val body = JSONObject()
|
||||
.put("name", name)
|
||||
.put("description", description ?: JSONObject.NULL)
|
||||
.put("quantity", quantity ?: JSONObject.NULL)
|
||||
if (parentId != null) body.put("parentId", parentId)
|
||||
if (clientId != null) body.put("clientId", clientId)
|
||||
return request("POST", "/api/nodes", body).toNode()
|
||||
}
|
||||
|
||||
suspend fun createNodes(parentId: String, names: List<String>, description: String?): List<ApiNode> {
|
||||
val values = org.json.JSONArray()
|
||||
names.forEach(values::put)
|
||||
val response = request(
|
||||
"POST",
|
||||
"/api/nodes/bulk",
|
||||
JSONObject().put("parentId", parentId).put("names", values).put("description", description ?: JSONObject.NULL),
|
||||
)
|
||||
val nodes = response.getJSONArray("nodes")
|
||||
return List(nodes.length()) { index -> nodes.getJSONObject(index).toNode() }
|
||||
}
|
||||
|
||||
suspend fun previewTreeImport(parentId: String, manifestJson: String): TreeImportPreview {
|
||||
val manifest = JSONObject(manifestJson)
|
||||
val response = request(
|
||||
@@ -94,14 +147,15 @@ class BoxManifestApi(
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun updateNode(id: String, name: String, description: String?, quantity: Long?): ApiNode =
|
||||
suspend fun updateNode(id: String, name: String, description: String?, quantity: Long?, baseRevision: Long? = null): ApiNode =
|
||||
request(
|
||||
"PATCH",
|
||||
"/api/nodes/$id",
|
||||
JSONObject()
|
||||
.put("name", name)
|
||||
.put("description", description ?: JSONObject.NULL)
|
||||
.put("quantity", quantity ?: JSONObject.NULL),
|
||||
.put("quantity", quantity ?: JSONObject.NULL)
|
||||
.apply { if (baseRevision != null) put("baseRevision", baseRevision) },
|
||||
).toNode()
|
||||
|
||||
suspend fun moveNode(id: String, targetParentId: String, handling: ChildHandling): ApiNode =
|
||||
@@ -163,18 +217,37 @@ class BoxManifestApi(
|
||||
val stream = if (status in 200..299) connection.inputStream else connection.errorStream
|
||||
val text = stream?.bufferedReader()?.use { it.readText() }.orEmpty()
|
||||
if (status !in 200..299) {
|
||||
val message = runCatching { JSONObject(text).getString("message") }
|
||||
.getOrDefault("Request failed with status $status")
|
||||
error(message)
|
||||
val error = runCatching { JSONObject(text) }.getOrNull()
|
||||
val message = error?.optString("message")?.takeIf(String::isNotBlank)
|
||||
?: "Request failed with status $status"
|
||||
throw ApiException(status, error?.optString("code")?.takeIf(String::isNotBlank), message)
|
||||
}
|
||||
if (!expectBody || text.isBlank()) JSONObject() else JSONObject(text)
|
||||
} finally {
|
||||
connection.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun requestArray(method: String, path: String): JSONArray = withContext(Dispatchers.IO) {
|
||||
val connection = URL(baseUrl + path).openConnection() as HttpURLConnection
|
||||
try {
|
||||
connection.requestMethod = method
|
||||
connection.connectTimeout = 5_000
|
||||
connection.readTimeout = 10_000
|
||||
connection.setRequestProperty("Accept", "application/json")
|
||||
val status = connection.responseCode
|
||||
val stream = if (status in 200..299) connection.inputStream else connection.errorStream
|
||||
val text = stream?.bufferedReader()?.use { it.readText() }.orEmpty()
|
||||
if (status !in 200..299) throw ApiException(status, null, "Request failed with status $status")
|
||||
JSONArray(text)
|
||||
} finally { connection.disconnect() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun JSONObject.toNode(): ApiNode {
|
||||
private fun JSONObject.toNodeType() = NodeType(getString("id"), getString("name"), optString("description").takeUnless { isNull("description") }, optString("iconKey").takeUnless { isNull("iconKey") }, optString("color").takeUnless { isNull("color") })
|
||||
private fun JSONObject.toTag() = Tag(getString("id"), getString("name"), optString("color").takeUnless { isNull("color") })
|
||||
|
||||
internal fun JSONObject.toNode(): ApiNode {
|
||||
val childValues = optJSONArray("children")
|
||||
val children = if (childValues == null) {
|
||||
emptyList()
|
||||
@@ -192,10 +265,29 @@ private fun JSONObject.toNode(): ApiNode {
|
||||
tagIds = optJSONArray("tagIds")?.let { values ->
|
||||
List(values.length()) { index -> values.getString(index) }
|
||||
} ?: emptyList(),
|
||||
revision = optLong("revision", 1),
|
||||
children = children,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun ApiNode.toJson(): JSONObject {
|
||||
val childValues = org.json.JSONArray()
|
||||
children.forEach { childValues.put(it.toJson()) }
|
||||
val tagValues = org.json.JSONArray()
|
||||
tagIds.forEach(tagValues::put)
|
||||
return JSONObject()
|
||||
.put("id", id)
|
||||
.put("lookupCode", lookupCode)
|
||||
.put("parentId", parentId ?: JSONObject.NULL)
|
||||
.put("name", name)
|
||||
.put("description", description ?: JSONObject.NULL)
|
||||
.put("quantity", quantity ?: JSONObject.NULL)
|
||||
.put("typeId", typeId ?: JSONObject.NULL)
|
||||
.put("tagIds", tagValues)
|
||||
.put("revision", revision)
|
||||
.put("children", childValues)
|
||||
}
|
||||
|
||||
private fun JSONObject.toImportNode(): ImportNode {
|
||||
val childrenJson = optJSONArray("children")
|
||||
return ImportNode(
|
||||
|
||||
@@ -19,7 +19,7 @@ class MainActivity : ComponentActivity() {
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
ManifestTheme {
|
||||
val treeViewModel: TreeViewModel = viewModel(factory = TreeViewModel.factory())
|
||||
val treeViewModel: TreeViewModel = viewModel(factory = TreeViewModel.factory(applicationContext))
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||
TreeScreen(
|
||||
viewModel = treeViewModel,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package app.boxmanifest.data
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
|
||||
class ConnectivityMonitor(context: Context) {
|
||||
private val manager = context.applicationContext.getSystemService(ConnectivityManager::class.java)
|
||||
|
||||
val connected: Flow<Boolean> = callbackFlow {
|
||||
fun current(): Boolean = manager.activeNetwork?.let(manager::getNetworkCapabilities)
|
||||
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true
|
||||
|
||||
val callback = object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onAvailable(network: Network) { trySend(current()) }
|
||||
override fun onLost(network: Network) { trySend(current()) }
|
||||
override fun onCapabilitiesChanged(network: Network, capabilities: NetworkCapabilities) { trySend(current()) }
|
||||
}
|
||||
trySend(current())
|
||||
manager.registerNetworkCallback(NetworkRequest.Builder().addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build(), callback)
|
||||
awaitClose { manager.unregisterNetworkCallback(callback) }
|
||||
}.distinctUntilChanged()
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package app.boxmanifest.data
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Database
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.Query
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.Transaction
|
||||
|
||||
@Entity(tableName = "nodes", indices = [Index("parentId")])
|
||||
data class LocalNode(
|
||||
@PrimaryKey val id: String,
|
||||
val lookupCode: String,
|
||||
val parentId: String?,
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val quantity: Long?,
|
||||
val typeId: String?,
|
||||
val tagIdsJson: String,
|
||||
val revision: Long,
|
||||
val sortOrder: Int,
|
||||
val pendingCreate: Boolean = false,
|
||||
)
|
||||
|
||||
@Entity(tableName = "pending_operations", indices = [Index(value = ["nodeId"], unique = true)])
|
||||
data class PendingOperation(
|
||||
@PrimaryKey(autoGenerate = true) val operationId: Long = 0,
|
||||
val kind: String,
|
||||
val nodeId: String,
|
||||
val parentId: String?,
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val quantity: Long?,
|
||||
val baseRevision: Long?,
|
||||
val status: String = STATUS_PENDING,
|
||||
val error: String? = null,
|
||||
) {
|
||||
companion object {
|
||||
const val CREATE = "CREATE"
|
||||
const val UPDATE = "UPDATE"
|
||||
const val STATUS_PENDING = "PENDING"
|
||||
const val STATUS_CONFLICT = "CONFLICT"
|
||||
}
|
||||
}
|
||||
|
||||
@Dao
|
||||
interface InventoryDao {
|
||||
@Query("SELECT * FROM nodes ORDER BY sortOrder")
|
||||
suspend fun nodes(): List<LocalNode>
|
||||
|
||||
@Query("SELECT * FROM nodes WHERE id = :id")
|
||||
suspend fun node(id: String): LocalNode?
|
||||
|
||||
@Query("SELECT COALESCE(MAX(sortOrder), -1) + 1 FROM nodes")
|
||||
suspend fun nextSortOrder(): Int
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun putNode(node: LocalNode)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun putNodes(nodes: List<LocalNode>)
|
||||
|
||||
@Query("DELETE FROM nodes")
|
||||
suspend fun deleteNodes()
|
||||
|
||||
@Transaction
|
||||
suspend fun replaceNodes(nodes: List<LocalNode>) {
|
||||
deleteNodes()
|
||||
putNodes(nodes)
|
||||
}
|
||||
|
||||
@Query("SELECT * FROM pending_operations ORDER BY operationId")
|
||||
suspend fun operations(): List<PendingOperation>
|
||||
|
||||
@Query("SELECT * FROM pending_operations WHERE nodeId = :nodeId")
|
||||
suspend fun operationFor(nodeId: String): PendingOperation?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun putOperation(operation: PendingOperation)
|
||||
|
||||
@Query("DELETE FROM pending_operations WHERE operationId = :id")
|
||||
suspend fun deleteOperation(id: Long)
|
||||
|
||||
@Query("UPDATE pending_operations SET status = :status, error = :error WHERE operationId = :id")
|
||||
suspend fun markOperation(id: Long, status: String, error: String?)
|
||||
|
||||
@Query("DELETE FROM pending_operations")
|
||||
suspend fun deleteOperations()
|
||||
|
||||
@Query("SELECT COUNT(*) FROM pending_operations WHERE status = 'PENDING'")
|
||||
suspend fun pendingCount(): Int
|
||||
|
||||
@Query("SELECT error FROM pending_operations WHERE status = 'CONFLICT' ORDER BY operationId LIMIT 1")
|
||||
suspend fun firstConflict(): String?
|
||||
|
||||
@Query("SELECT * FROM pending_operations WHERE status = 'CONFLICT' ORDER BY operationId LIMIT 1")
|
||||
suspend fun firstConflictOperation(): PendingOperation?
|
||||
|
||||
@Transaction
|
||||
suspend fun clearAll() {
|
||||
deleteOperations()
|
||||
deleteNodes()
|
||||
}
|
||||
}
|
||||
|
||||
@Database(entities = [LocalNode::class, PendingOperation::class], version = 1, exportSchema = false)
|
||||
abstract class LocalInventoryDatabase : RoomDatabase() {
|
||||
abstract fun inventory(): InventoryDao
|
||||
|
||||
companion object {
|
||||
@Volatile private var instance: LocalInventoryDatabase? = null
|
||||
|
||||
fun get(context: Context): LocalInventoryDatabase = instance ?: synchronized(this) {
|
||||
instance ?: Room.databaseBuilder(
|
||||
context.applicationContext,
|
||||
LocalInventoryDatabase::class.java,
|
||||
"box-manifest-local.db",
|
||||
).build().also { instance = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +1,267 @@
|
||||
package app.boxmanifest.data
|
||||
|
||||
import androidx.room.withTransaction
|
||||
import app.boxmanifest.ApiException
|
||||
import app.boxmanifest.ApiNode
|
||||
import app.boxmanifest.BoxManifestApi
|
||||
import app.boxmanifest.ChildHandling
|
||||
import app.boxmanifest.ServerInfo
|
||||
import app.boxmanifest.NodeType
|
||||
import app.boxmanifest.Tag
|
||||
import app.boxmanifest.DEFAULT_TAG_COLOR
|
||||
import app.boxmanifest.DEFAULT_TYPE_COLOR
|
||||
import app.boxmanifest.TreeImportPreview
|
||||
import org.json.JSONArray
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import java.util.UUID
|
||||
|
||||
data class TreeLoadResult(
|
||||
val root: ApiNode,
|
||||
val fromCache: Boolean,
|
||||
val pendingOperations: Int = 0,
|
||||
val syncConflict: String? = null,
|
||||
)
|
||||
|
||||
class NodeRepository(
|
||||
private val database: LocalInventoryDatabase,
|
||||
private val api: BoxManifestApi = BoxManifestApi(),
|
||||
) {
|
||||
suspend fun loadTree(): ApiNode = api.getTree()
|
||||
private val dao = database.inventory()
|
||||
|
||||
suspend fun createChild(parentId: String, name: String, description: String?, quantity: Long?) {
|
||||
api.createNode(parentId, name, description, quantity)
|
||||
fun setServerUrl(url: String) = api.setBaseUrl(url)
|
||||
suspend fun testServer(url: String): ServerInfo = BoxManifestApi(url).getSystemInfo()
|
||||
suspend fun nodeTypes(): List<NodeType> = api.getNodeTypes()
|
||||
suspend fun tags(): List<Tag> = api.getTags()
|
||||
suspend fun createNodeType(name: String, color: String = DEFAULT_TYPE_COLOR): NodeType = api.createNodeType(name, color)
|
||||
suspend fun updateNodeType(type: NodeType): NodeType = api.updateNodeType(type)
|
||||
suspend fun deleteNodeType(id: String) = api.deleteNodeType(id)
|
||||
suspend fun createTag(name: String, color: String = DEFAULT_TAG_COLOR): Tag = api.createTag(name, color)
|
||||
suspend fun updateTag(tag: Tag): Tag = api.updateTag(tag)
|
||||
suspend fun deleteTag(id: String) = api.deleteTag(id)
|
||||
suspend fun clearCache() = dao.clearAll()
|
||||
|
||||
suspend fun loadTree(): TreeLoadResult {
|
||||
synchronize()
|
||||
val pending = dao.pendingCount()
|
||||
val conflict = dao.firstConflict()
|
||||
if (pending == 0 && conflict == null) {
|
||||
try {
|
||||
return TreeLoadResult(loadTreeFromServer(), fromCache = false)
|
||||
} catch (failure: Throwable) {
|
||||
if (failure is CancellationException) throw failure
|
||||
// Fall through to the durable local view.
|
||||
}
|
||||
}
|
||||
val local = loadLocalTree() ?: throw IllegalStateException("No saved inventory is available")
|
||||
return TreeLoadResult(local, fromCache = true, pendingOperations = pending, syncConflict = conflict)
|
||||
}
|
||||
|
||||
suspend fun createChildren(parentId: String, names: List<String>, description: String?) {
|
||||
api.createNodes(parentId, names, description)
|
||||
suspend fun loadTreeFromServer(): ApiNode {
|
||||
val root = api.getTree()
|
||||
dao.replaceNodes(flatten(root))
|
||||
return root
|
||||
}
|
||||
|
||||
suspend fun previewTreeImport(parentId: String, manifestJson: String): TreeImportPreview =
|
||||
api.previewTreeImport(parentId, manifestJson)
|
||||
|
||||
suspend fun commitTreeImport(planId: String) {
|
||||
api.commitTreeImport(planId)
|
||||
suspend fun createChild(parentId: String, name: String, description: String?, quantity: Long?): TreeLoadResult {
|
||||
queueCreates(parentId, listOf(PendingCreate(name, description, quantity)))
|
||||
return loadTree()
|
||||
}
|
||||
|
||||
suspend fun update(id: String, name: String, description: String?, quantity: Long?) {
|
||||
api.updateNode(id, name, description, quantity)
|
||||
suspend fun createChildren(parentId: String, names: List<String>, description: String?): TreeLoadResult {
|
||||
queueCreates(parentId, names.map { PendingCreate(it, description, null) })
|
||||
return loadTree()
|
||||
}
|
||||
|
||||
suspend fun update(id: String, name: String, description: String?, quantity: Long?): TreeLoadResult {
|
||||
database.withTransaction {
|
||||
val node = dao.node(id) ?: error("Node is not stored locally")
|
||||
val existing = dao.operationFor(id)
|
||||
if (existing?.status == PendingOperation.STATUS_CONFLICT) {
|
||||
error("Resolve the existing sync conflict before editing this node")
|
||||
}
|
||||
val changed = node.copy(name = name.trim(), description = description, quantity = quantity)
|
||||
dao.putNode(changed)
|
||||
dao.putOperation(
|
||||
PendingOperation(
|
||||
operationId = existing?.operationId ?: 0,
|
||||
kind = existing?.kind ?: PendingOperation.UPDATE,
|
||||
nodeId = id,
|
||||
parentId = if (existing?.kind == PendingOperation.CREATE) existing.parentId else null,
|
||||
name = changed.name,
|
||||
description = changed.description,
|
||||
quantity = changed.quantity,
|
||||
baseRevision = existing?.baseRevision ?: node.revision,
|
||||
),
|
||||
)
|
||||
}
|
||||
return loadTree()
|
||||
}
|
||||
|
||||
suspend fun synchronize(): SyncSummary {
|
||||
for (operation in dao.operations()) {
|
||||
if (operation.status == PendingOperation.STATUS_CONFLICT) break
|
||||
try {
|
||||
when (operation.kind) {
|
||||
PendingOperation.CREATE -> api.createNode(
|
||||
parentId = operation.parentId,
|
||||
name = operation.name,
|
||||
description = operation.description,
|
||||
quantity = operation.quantity,
|
||||
clientId = operation.nodeId,
|
||||
)
|
||||
PendingOperation.UPDATE -> api.updateNode(
|
||||
id = operation.nodeId,
|
||||
name = operation.name,
|
||||
description = operation.description,
|
||||
quantity = operation.quantity,
|
||||
baseRevision = operation.baseRevision,
|
||||
)
|
||||
else -> error("Unknown queued operation ${operation.kind}")
|
||||
}
|
||||
dao.deleteOperation(operation.operationId)
|
||||
} catch (failure: ApiException) {
|
||||
if (failure.status >= 500) break
|
||||
dao.markOperation(
|
||||
operation.operationId,
|
||||
PendingOperation.STATUS_CONFLICT,
|
||||
if (failure.code == "REVISION_CONFLICT") "${operation.name} changed on the server"
|
||||
else failure.message ?: "Queued change was rejected",
|
||||
)
|
||||
break
|
||||
} catch (failure: Throwable) {
|
||||
if (failure is CancellationException) throw failure
|
||||
break
|
||||
}
|
||||
}
|
||||
return SyncSummary(dao.pendingCount(), dao.firstConflict())
|
||||
}
|
||||
|
||||
suspend fun resolveConflict(keepLocal: Boolean): TreeLoadResult {
|
||||
val operation = dao.firstConflictOperation() ?: return loadTree()
|
||||
if (keepLocal) {
|
||||
dao.putOperation(operation.copy(baseRevision = null, status = PendingOperation.STATUS_PENDING, error = null))
|
||||
} else {
|
||||
dao.deleteOperation(operation.operationId)
|
||||
}
|
||||
return loadTree()
|
||||
}
|
||||
|
||||
suspend fun previewTreeImport(parentId: String, manifestJson: String): TreeImportPreview = api.previewTreeImport(parentId, manifestJson)
|
||||
suspend fun commitTreeImport(planId: String) = api.commitTreeImport(planId)
|
||||
|
||||
suspend fun move(id: String, targetParentId: String, childHandling: ChildHandling) {
|
||||
requireOnlineReady()
|
||||
api.moveNode(id, targetParentId, childHandling)
|
||||
}
|
||||
|
||||
suspend fun moveMany(ids: Set<String>, targetParentId: String, childHandling: ChildHandling) {
|
||||
requireOnlineReady()
|
||||
api.moveNodes(ids, targetParentId, childHandling)
|
||||
}
|
||||
|
||||
suspend fun delete(id: String) {
|
||||
requireOnlineReady()
|
||||
api.deleteNode(id)
|
||||
}
|
||||
|
||||
suspend fun deleteMany(ids: Set<String>) {
|
||||
requireOnlineReady()
|
||||
api.deleteNodes(ids)
|
||||
}
|
||||
|
||||
suspend fun classify(ids: Set<String>, typeId: String? = null, setType: Boolean = false, addTags: List<String> = emptyList(), removeTags: List<String> = emptyList()) {
|
||||
requireOnlineReady()
|
||||
api.classifyNodes(ids, typeId, setType, addTags, removeTags)
|
||||
}
|
||||
|
||||
private suspend fun requireOnlineReady() {
|
||||
val summary = synchronize()
|
||||
check(summary.pendingOperations == 0 && summary.conflict == null) { "Pending offline changes must sync first" }
|
||||
api.getSystemInfo()
|
||||
}
|
||||
|
||||
private suspend fun queueCreates(parentId: String, creates: List<PendingCreate>) {
|
||||
database.withTransaction {
|
||||
check(dao.node(parentId) != null) { "Parent is not stored locally" }
|
||||
var order = dao.nextSortOrder()
|
||||
creates.forEach { create ->
|
||||
val id = UUID.randomUUID().toString().replace("-", "")
|
||||
val node = LocalNode(
|
||||
id = id,
|
||||
lookupCode = id.take(6).uppercase(),
|
||||
parentId = parentId,
|
||||
name = create.name.trim(),
|
||||
description = create.description,
|
||||
quantity = create.quantity,
|
||||
typeId = null,
|
||||
tagIdsJson = "[]",
|
||||
revision = 1,
|
||||
sortOrder = order++,
|
||||
pendingCreate = true,
|
||||
)
|
||||
dao.putNode(node)
|
||||
dao.putOperation(
|
||||
PendingOperation(
|
||||
kind = PendingOperation.CREATE,
|
||||
nodeId = id,
|
||||
parentId = parentId,
|
||||
name = node.name,
|
||||
description = create.description,
|
||||
quantity = create.quantity,
|
||||
baseRevision = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadLocalTree(): ApiNode? {
|
||||
val values = dao.nodes()
|
||||
if (values.isEmpty()) return null
|
||||
val children = values.groupBy(LocalNode::parentId)
|
||||
fun build(value: LocalNode): ApiNode = value.toApiNode(children[value.id].orEmpty().map(::build))
|
||||
return values.firstOrNull { it.id == "root" }?.let(::build)
|
||||
}
|
||||
|
||||
private fun flatten(root: ApiNode): List<LocalNode> {
|
||||
var order = 0
|
||||
val result = mutableListOf<LocalNode>()
|
||||
fun visit(node: ApiNode) {
|
||||
result += node.toLocalNode(order++)
|
||||
node.children.forEach(::visit)
|
||||
}
|
||||
visit(root)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
data class SyncSummary(val pendingOperations: Int, val conflict: String?)
|
||||
|
||||
private data class PendingCreate(val name: String, val description: String?, val quantity: Long?)
|
||||
|
||||
private fun LocalNode.toApiNode(children: List<ApiNode>) = ApiNode(
|
||||
id = id,
|
||||
lookupCode = lookupCode,
|
||||
parentId = parentId,
|
||||
name = name,
|
||||
description = description,
|
||||
quantity = quantity,
|
||||
typeId = typeId,
|
||||
tagIds = JSONArray(tagIdsJson).let { values -> List(values.length()) { values.getString(it) } },
|
||||
revision = revision,
|
||||
pending = pendingCreate,
|
||||
children = children,
|
||||
)
|
||||
|
||||
private fun ApiNode.toLocalNode(order: Int) = LocalNode(
|
||||
id = id,
|
||||
lookupCode = lookupCode,
|
||||
parentId = parentId,
|
||||
name = name,
|
||||
description = description,
|
||||
quantity = quantity,
|
||||
typeId = typeId,
|
||||
tagIdsJson = JSONArray(tagIds).toString(),
|
||||
revision = revision,
|
||||
sortOrder = order,
|
||||
)
|
||||
@@ -0,0 +1,63 @@
|
||||
package app.boxmanifest.data
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.preferences.core.emptyPreferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.io.IOException
|
||||
import java.net.URI
|
||||
|
||||
const val DEFAULT_SERVER_URL = "http://box-manifest.hosts.uuard.com:8080"
|
||||
|
||||
data class ServerConfig(
|
||||
val url: String = DEFAULT_SERVER_URL,
|
||||
val serverId: String? = null,
|
||||
val serverName: String? = null,
|
||||
)
|
||||
|
||||
private val Context.serverDataStore by preferencesDataStore(name = "server")
|
||||
|
||||
class ServerSettings(context: Context) {
|
||||
private val appContext = context.applicationContext
|
||||
private val urlKey = stringPreferencesKey("url")
|
||||
private val idKey = stringPreferencesKey("server_id")
|
||||
private val nameKey = stringPreferencesKey("server_name")
|
||||
|
||||
val config: Flow<ServerConfig> = appContext.serverDataStore.data
|
||||
.catch { failure ->
|
||||
if (failure is IOException) emit(emptyPreferences()) else throw failure
|
||||
}
|
||||
.map { preferences ->
|
||||
ServerConfig(
|
||||
url = preferences[urlKey] ?: DEFAULT_SERVER_URL,
|
||||
serverId = preferences[idKey],
|
||||
serverName = preferences[nameKey],
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun load(): ServerConfig = config.first()
|
||||
|
||||
suspend fun save(config: ServerConfig) {
|
||||
appContext.serverDataStore.edit { preferences ->
|
||||
preferences[urlKey] = config.url
|
||||
config.serverId?.let { preferences[idKey] = it } ?: preferences.remove(idKey)
|
||||
config.serverName?.let { preferences[nameKey] = it } ?: preferences.remove(nameKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun normalizeServerUrl(value: String): String {
|
||||
val candidate = value.trim().let { if ("://" in it) it else "http://$it" }.trimEnd('/')
|
||||
val uri = runCatching { URI(candidate) }.getOrElse { throw IllegalArgumentException("Enter a valid server URL") }
|
||||
require(uri.scheme == "http" || uri.scheme == "https") { "Server URL must use http or https" }
|
||||
require(!uri.host.isNullOrBlank()) { "Server URL must include a host" }
|
||||
require(uri.rawQuery == null && uri.rawFragment == null && (uri.path.isNullOrBlank() || uri.path == "/")) {
|
||||
"Server URL cannot include a path, query, or fragment"
|
||||
}
|
||||
return candidate
|
||||
}
|
||||
@@ -3,9 +3,7 @@ package app.boxmanifest.labels
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.VibrationEffect
|
||||
import android.os.Vibrator
|
||||
import android.os.VibratorManager
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
@@ -197,16 +195,6 @@ private fun createBarcodeScanner(): BarcodeScanner = BarcodeScanning.getClient(
|
||||
)
|
||||
|
||||
private fun vibrate(context: Context) {
|
||||
val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
context.getSystemService(VibratorManager::class.java).defaultVibrator
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
vibrator.vibrate(VibrationEffect.createOneShot(120, VibrationEffect.DEFAULT_AMPLITUDE))
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
vibrator.vibrate(120)
|
||||
}
|
||||
context.getSystemService(VibratorManager::class.java).defaultVibrator
|
||||
.vibrate(VibrationEffect.createOneShot(120, VibrationEffect.DEFAULT_AMPLITUDE))
|
||||
}
|
||||
@@ -84,7 +84,7 @@ fun scanNodeCode(activity: Activity, onCode: (String) -> Unit, onError: (String)
|
||||
val code = barcode.rawValue?.let(::extractLookupCode)
|
||||
if (code == null) onError("That QR code is not a Box Manifest label") else onCode(code)
|
||||
}
|
||||
.addOnFailureListener { onError(it.message ?: "Could not open QR scanner") }
|
||||
.addOnFailureListener { onError("Failed to scan code") }
|
||||
}
|
||||
|
||||
fun extractLookupCode(value: String): String? {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package app.boxmanifest.tree
|
||||
|
||||
import app.boxmanifest.ApiNode
|
||||
|
||||
internal const val NO_TYPE_FILTER = "__no_type__"
|
||||
internal enum class FilterMode { AND, OR }
|
||||
internal enum class QuantityFilter { SET, NOT_SET, ZERO, ONE }
|
||||
|
||||
internal data class SearchFilters(
|
||||
val name: String = "",
|
||||
val description: String = "",
|
||||
val typeId: String? = null,
|
||||
val tagIds: Set<String> = emptySet(),
|
||||
val quantity: QuantityFilter? = null,
|
||||
) {
|
||||
val count: Int
|
||||
get() = listOf(name.isNotBlank(), description.isNotBlank(), typeId != null, quantity != null).count { it } + tagIds.size
|
||||
|
||||
fun matches(node: ApiNode, mode: FilterMode): Boolean {
|
||||
val matches = buildList {
|
||||
if (name.isNotBlank()) add(node.name.contains(name.trim(), ignoreCase = true))
|
||||
if (description.isNotBlank()) add(node.description.orEmpty().contains(description.trim(), ignoreCase = true))
|
||||
if (typeId != null) add(if (typeId == NO_TYPE_FILTER) node.typeId == null else node.typeId == typeId)
|
||||
tagIds.forEach { tagId -> add(tagId in node.tagIds) }
|
||||
quantity?.let { filter ->
|
||||
add(when (filter) {
|
||||
QuantityFilter.SET -> node.quantity != null
|
||||
QuantityFilter.NOT_SET -> node.quantity == null
|
||||
QuantityFilter.ZERO -> node.quantity == 0L
|
||||
QuantityFilter.ONE -> node.quantity == 1L
|
||||
})
|
||||
}
|
||||
}
|
||||
return matches.isEmpty() || if (mode == FilterMode.AND) matches.all { it } else matches.any { it }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun List<ApiNode>.matchingFilters(typeId: String?, tagIds: Set<String>, mode: FilterMode = FilterMode.AND): List<ApiNode> =
|
||||
filter { node ->
|
||||
val matches = buildList {
|
||||
if (typeId != null) add(
|
||||
if (typeId == NO_TYPE_FILTER) node.typeId == null else node.typeId == typeId,
|
||||
)
|
||||
tagIds.forEach { tagId -> add(tagId in node.tagIds) }
|
||||
}
|
||||
matches.isEmpty() || if (mode == FilterMode.AND) matches.all { it } else matches.any { it }
|
||||
}
|
||||
|
||||
internal fun List<ApiNode>.selectableIds(): Set<String> =
|
||||
filterNot { it.id == "root" || it.id == "unsorted" }.mapTo(mutableSetOf(), ApiNode::id)
|
||||
File diff suppressed because it is too large.
Load diff
@@ -2,6 +2,9 @@ package app.boxmanifest.tree
|
||||
|
||||
import app.boxmanifest.ApiNode
|
||||
import app.boxmanifest.TreeImportPreview
|
||||
import app.boxmanifest.NodeType
|
||||
import app.boxmanifest.Tag
|
||||
import app.boxmanifest.data.DEFAULT_SERVER_URL
|
||||
|
||||
data class TreeUiState(
|
||||
val root: ApiNode? = null,
|
||||
@@ -10,6 +13,17 @@ data class TreeUiState(
|
||||
val loading: Boolean = false,
|
||||
val error: String? = null,
|
||||
val importPreview: TreeImportPreview? = null,
|
||||
val usingCachedTree: Boolean = false,
|
||||
val serverUrl: String = DEFAULT_SERVER_URL,
|
||||
val serverId: String? = null,
|
||||
val serverName: String? = null,
|
||||
val serverSettingsBusy: Boolean = false,
|
||||
val serverTestStatus: ServerTestStatus = ServerTestStatus.IDLE,
|
||||
val serverTestMessage: String? = null,
|
||||
val pendingOperations: Int = 0,
|
||||
val syncConflict: String? = null,
|
||||
val nodeTypes: List<NodeType> = emptyList(),
|
||||
val tags: List<Tag> = emptyList(),
|
||||
) {
|
||||
val currentNode: ApiNode?
|
||||
get() = root?.find(currentNodeId)
|
||||
@@ -21,6 +35,8 @@ data class TreeUiState(
|
||||
get() = root?.pathTo(currentNodeId).orEmpty()
|
||||
}
|
||||
|
||||
enum class ServerTestStatus { IDLE, TESTING, SAVING, SUCCESS, ERROR }
|
||||
|
||||
fun ApiNode.find(id: String): ApiNode? {
|
||||
if (this.id == id) return this
|
||||
return children.firstNotNullOfOrNull { it.find(id) }
|
||||
|
||||
@@ -1,27 +1,169 @@
|
||||
package app.boxmanifest.tree
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.boxmanifest.ChildHandling
|
||||
import app.boxmanifest.NodeType
|
||||
import app.boxmanifest.Tag
|
||||
import app.boxmanifest.data.NodeRepository
|
||||
import app.boxmanifest.data.ConnectivityMonitor
|
||||
import app.boxmanifest.data.LocalInventoryDatabase
|
||||
import app.boxmanifest.data.ServerConfig
|
||||
import app.boxmanifest.data.ServerSettings
|
||||
import app.boxmanifest.data.TreeLoadResult
|
||||
import app.boxmanifest.data.normalizeServerUrl
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class TreeViewModel(
|
||||
private val repository: NodeRepository,
|
||||
private val serverSettings: ServerSettings,
|
||||
private val connectivityMonitor: ConnectivityMonitor,
|
||||
) : ViewModel() {
|
||||
private val _state = MutableStateFlow(TreeUiState())
|
||||
val state: StateFlow<TreeUiState> = _state.asStateFlow()
|
||||
|
||||
init {
|
||||
refresh()
|
||||
viewModelScope.launch {
|
||||
val config = serverSettings.load()
|
||||
repository.setServerUrl(config.url)
|
||||
_state.update {
|
||||
it.copy(serverUrl = config.url, serverId = config.serverId, serverName = config.serverName)
|
||||
}
|
||||
refresh()
|
||||
connectivityMonitor.connected.drop(1).collectLatest { connected ->
|
||||
if (connected) refresh()
|
||||
}
|
||||
}
|
||||
viewModelScope.launch {
|
||||
state.map { Triple(it.pendingOperations, it.syncConflict, it.usingCachedTree) }
|
||||
.distinctUntilChanged()
|
||||
.collectLatest { (pending, conflict, cached) ->
|
||||
if (conflict == null && (pending > 0 || cached)) {
|
||||
while (true) {
|
||||
delay(15_000)
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun refresh() = perform { repository.loadTree() }
|
||||
fun testServerUrl(value: String) {
|
||||
viewModelScope.launch {
|
||||
_state.update {
|
||||
it.copy(
|
||||
serverSettingsBusy = true,
|
||||
serverTestStatus = ServerTestStatus.TESTING,
|
||||
serverTestMessage = null,
|
||||
)
|
||||
}
|
||||
runCatching {
|
||||
val url = normalizeServerUrl(value)
|
||||
repository.testServer(url)
|
||||
}.onSuccess { info ->
|
||||
_state.update {
|
||||
it.copy(
|
||||
serverSettingsBusy = false,
|
||||
serverTestStatus = ServerTestStatus.SUCCESS,
|
||||
serverTestMessage = "Connected to ${info.name} (API ${info.apiVersion})",
|
||||
)
|
||||
}
|
||||
}.onFailure { failure ->
|
||||
_state.update {
|
||||
it.copy(
|
||||
serverSettingsBusy = false,
|
||||
serverTestStatus = ServerTestStatus.ERROR,
|
||||
serverTestMessage = failure.message ?: "Connection failed",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun saveServerUrl(value: String) {
|
||||
viewModelScope.launch {
|
||||
_state.update {
|
||||
it.copy(
|
||||
serverSettingsBusy = true,
|
||||
serverTestStatus = ServerTestStatus.SAVING,
|
||||
serverTestMessage = null,
|
||||
)
|
||||
}
|
||||
runCatching {
|
||||
val url = normalizeServerUrl(value)
|
||||
val info = repository.testServer(url)
|
||||
val previousId = _state.value.serverId
|
||||
if (previousId != null && previousId != info.serverId) repository.clearCache()
|
||||
serverSettings.save(ServerConfig(url, info.serverId, info.name))
|
||||
repository.setServerUrl(url)
|
||||
Triple(url, info.serverId, info.name)
|
||||
}.onSuccess { (url, id, name) ->
|
||||
_state.update {
|
||||
it.copy(
|
||||
serverUrl = url,
|
||||
serverId = id,
|
||||
serverName = name,
|
||||
serverSettingsBusy = false,
|
||||
serverTestStatus = ServerTestStatus.SUCCESS,
|
||||
serverTestMessage = "Server saved and connected",
|
||||
)
|
||||
}
|
||||
refresh()
|
||||
}.onFailure { failure ->
|
||||
_state.update {
|
||||
it.copy(
|
||||
serverSettingsBusy = false,
|
||||
serverTestStatus = ServerTestStatus.ERROR,
|
||||
serverTestMessage = failure.message ?: "Connection failed",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clearServerTestStatus() {
|
||||
_state.update {
|
||||
it.copy(serverTestStatus = ServerTestStatus.IDLE, serverTestMessage = null)
|
||||
}
|
||||
}
|
||||
|
||||
fun refresh(showCacheFailure: Boolean = false) {
|
||||
viewModelScope.launch {
|
||||
_state.update { it.copy(loading = true, error = null) }
|
||||
runCatching { repository.loadTree() }
|
||||
.onSuccess { result ->
|
||||
val metadata = if (result.fromCache) null else runCatching { repository.nodeTypes() to repository.tags() }.getOrNull()
|
||||
_state.update { previous ->
|
||||
val validTarget = result.root.find(previous.currentNodeId)?.id ?: result.root.id
|
||||
previous.copy(
|
||||
root = result.root,
|
||||
currentNodeId = validTarget,
|
||||
loading = false,
|
||||
usingCachedTree = result.fromCache,
|
||||
pendingOperations = result.pendingOperations,
|
||||
syncConflict = result.syncConflict,
|
||||
error = if (result.fromCache && showCacheFailure) "Server is still unavailable" else null,
|
||||
nodeTypes = metadata?.first ?: previous.nodeTypes,
|
||||
tags = metadata?.second ?: previous.tags,
|
||||
)
|
||||
}
|
||||
}
|
||||
.onFailure { failure ->
|
||||
_state.update { it.copy(loading = false, error = failure.message ?: "Request failed") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun openNode(id: String) {
|
||||
val current = _state.value
|
||||
@@ -51,14 +193,12 @@ class TreeViewModel(
|
||||
return true
|
||||
}
|
||||
|
||||
fun createChild(name: String, description: String?, quantity: Long?) = perform {
|
||||
fun createChild(name: String, description: String?, quantity: Long?) = performLoad {
|
||||
repository.createChild(_state.value.currentNodeId, name, description, quantity)
|
||||
repository.loadTree()
|
||||
}
|
||||
|
||||
fun createChildren(names: List<String>, description: String?) = perform {
|
||||
fun createChildren(names: List<String>, description: String?) = performLoad {
|
||||
repository.createChildren(_state.value.currentNodeId, names, description)
|
||||
repository.loadTree()
|
||||
}
|
||||
|
||||
fun previewTreeImport(manifestJson: String) {
|
||||
@@ -84,23 +224,22 @@ class TreeViewModel(
|
||||
_state.update { it.copy(importPreview = null) }
|
||||
perform {
|
||||
repository.commitTreeImport(planId)
|
||||
repository.loadTree()
|
||||
repository.loadTreeFromServer()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateCurrent(name: String, description: String?, quantity: Long?) = perform {
|
||||
fun updateCurrent(name: String, description: String?, quantity: Long?) = performLoad {
|
||||
repository.update(_state.value.currentNodeId, name, description, quantity)
|
||||
repository.loadTree()
|
||||
}
|
||||
|
||||
fun moveCurrent(targetParentId: String, handling: ChildHandling) = perform {
|
||||
repository.move(_state.value.currentNodeId, targetParentId, handling)
|
||||
repository.loadTree()
|
||||
repository.loadTreeFromServer()
|
||||
}
|
||||
|
||||
fun moveMany(ids: Set<String>, targetParentId: String, handling: ChildHandling) = perform {
|
||||
repository.moveMany(ids, targetParentId, handling)
|
||||
repository.loadTree()
|
||||
repository.loadTreeFromServer()
|
||||
}
|
||||
|
||||
fun deleteCurrent() {
|
||||
@@ -108,19 +247,78 @@ class TreeViewModel(
|
||||
val destination = current.parentId ?: return
|
||||
perform(targetNodeId = destination, resetBackStack = true) {
|
||||
repository.delete(current.id)
|
||||
repository.loadTree()
|
||||
repository.loadTreeFromServer()
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteMany(ids: Set<String>) = perform {
|
||||
repository.deleteMany(ids)
|
||||
repository.loadTree()
|
||||
repository.loadTreeFromServer()
|
||||
}
|
||||
|
||||
fun assignType(ids: Set<String>, typeId: String?) = perform {
|
||||
repository.classify(ids, typeId = typeId, setType = true)
|
||||
repository.loadTreeFromServer()
|
||||
}
|
||||
|
||||
fun toggleTag(ids: Set<String>, tagId: String, apply: Boolean) = perform {
|
||||
repository.classify(ids, addTags = if (apply) listOf(tagId) else emptyList(), removeTags = if (apply) emptyList() else listOf(tagId))
|
||||
repository.loadTreeFromServer()
|
||||
}
|
||||
|
||||
fun clearTags(ids: Set<String>, tagIds: List<String>) = perform {
|
||||
repository.classify(ids, removeTags = tagIds)
|
||||
repository.loadTreeFromServer()
|
||||
}
|
||||
|
||||
fun createType(name: String) = mutateMetadata { repository.createNodeType(name) }
|
||||
fun createType(name: String, color: String) = mutateMetadata { repository.createNodeType(name, color) }
|
||||
fun updateType(type: NodeType) = mutateMetadata { repository.updateNodeType(type) }
|
||||
fun deleteType(id: String) = mutateMetadata { repository.deleteNodeType(id) }
|
||||
fun createTag(name: String) = mutateMetadata { repository.createTag(name) }
|
||||
fun createTag(name: String, color: String) = mutateMetadata { repository.createTag(name, color) }
|
||||
fun updateTag(tag: Tag) = mutateMetadata { repository.updateTag(tag) }
|
||||
fun deleteTag(id: String) = mutateMetadata { repository.deleteTag(id) }
|
||||
|
||||
private fun mutateMetadata(action: suspend () -> Any?) {
|
||||
viewModelScope.launch {
|
||||
runCatching { action() }
|
||||
.onSuccess { refresh() }
|
||||
.onFailure { failure -> _state.update { it.copy(error = failure.message) } }
|
||||
}
|
||||
}
|
||||
|
||||
fun clearError() {
|
||||
_state.update { it.copy(error = null) }
|
||||
}
|
||||
|
||||
fun resolveSyncConflict(keepLocal: Boolean) = performLoad {
|
||||
repository.resolveConflict(keepLocal)
|
||||
}
|
||||
|
||||
private fun performLoad(action: suspend () -> TreeLoadResult) {
|
||||
viewModelScope.launch {
|
||||
_state.update { it.copy(loading = true, error = null) }
|
||||
runCatching { action() }
|
||||
.onSuccess { result ->
|
||||
_state.update { previous ->
|
||||
val validTarget = result.root.find(previous.currentNodeId)?.id ?: result.root.id
|
||||
previous.copy(
|
||||
root = result.root,
|
||||
currentNodeId = validTarget,
|
||||
loading = false,
|
||||
usingCachedTree = result.fromCache,
|
||||
pendingOperations = result.pendingOperations,
|
||||
syncConflict = result.syncConflict,
|
||||
)
|
||||
}
|
||||
}
|
||||
.onFailure { failure ->
|
||||
_state.update { it.copy(loading = false, error = failure.message ?: "Request failed") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun perform(
|
||||
targetNodeId: String? = null,
|
||||
resetBackStack: Boolean = false,
|
||||
@@ -138,6 +336,9 @@ class TreeViewModel(
|
||||
currentNodeId = validTarget,
|
||||
backStack = if (resetBackStack) emptyList() else previous.backStack,
|
||||
loading = false,
|
||||
usingCachedTree = false,
|
||||
pendingOperations = 0,
|
||||
syncConflict = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -150,11 +351,16 @@ class TreeViewModel(
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun factory(repository: NodeRepository = NodeRepository()): ViewModelProvider.Factory =
|
||||
fun factory(
|
||||
context: Context,
|
||||
repository: NodeRepository = NodeRepository(LocalInventoryDatabase.get(context)),
|
||||
serverSettings: ServerSettings = ServerSettings(context),
|
||||
connectivityMonitor: ConnectivityMonitor = ConnectivityMonitor(context),
|
||||
): ViewModelProvider.Factory =
|
||||
object : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T =
|
||||
TreeViewModel(repository) as T
|
||||
TreeViewModel(repository, serverSettings, connectivityMonitor) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,18 @@ fun CloseIcon(modifier: Modifier = Modifier, contentDescription: String? = "Clos
|
||||
fun MoreIcon(modifier: Modifier = Modifier, contentDescription: String? = "More options") =
|
||||
ManifestIcon(R.drawable.ic_action_more_24, contentDescription, modifier)
|
||||
|
||||
@Composable
|
||||
fun EditIcon(modifier: Modifier = Modifier, contentDescription: String? = "Edit") =
|
||||
ManifestIcon(R.drawable.ic_action_edit, contentDescription, modifier)
|
||||
|
||||
@Composable
|
||||
fun FilterIcon(modifier: Modifier = Modifier, contentDescription: String? = "Filter") =
|
||||
ManifestIcon(R.drawable.ic_filter_24, contentDescription, modifier)
|
||||
|
||||
@Composable
|
||||
fun AppMenuIcon(modifier: Modifier = Modifier, contentDescription: String? = "Open settings") =
|
||||
ManifestIcon(R.drawable.ic_app_mark_24, contentDescription, modifier)
|
||||
|
||||
@Composable
|
||||
private fun ManifestIcon(
|
||||
@DrawableRes drawable: Int,
|
||||
|
||||
@@ -8,16 +8,26 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.boxmanifest.ApiNode
|
||||
import app.boxmanifest.NodeType
|
||||
import app.boxmanifest.Tag
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import android.graphics.Color as AndroidColor
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalResources
|
||||
import androidx.compose.ui.res.painterResource
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@@ -27,6 +37,8 @@ fun NodeRow(
|
||||
modifier: Modifier = Modifier,
|
||||
selected: Boolean = false,
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
nodeType: NodeType? = null,
|
||||
tags: List<Tag> = emptyList(),
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
@@ -34,17 +46,18 @@ fun NodeRow(
|
||||
color = if (selected) MaterialTheme.colorScheme.surfaceVariant else MaterialTheme.colorScheme.surface,
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
)
|
||||
.combinedClickable(onClick = onClick, onLongClick = onLongClick),
|
||||
.combinedClickable(onClick = onClick, onLongClick = onLongClick)
|
||||
.padding(horizontal = 8.dp, vertical = 6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.size(40.dp),
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
color = nodeType?.color?.let { runCatching { Color(AndroidColor.parseColor(it)) }.getOrNull()?.copy(alpha = .16f) } ?: MaterialTheme.colorScheme.surfaceVariant,
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Text(if (selected) "✓" else node.name.firstOrNull()?.uppercase() ?: "•")
|
||||
if (nodeType != null) NodeTypeIcon(nodeType) else Text(node.name.firstOrNull()?.uppercase() ?: "•")
|
||||
}
|
||||
}
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
@@ -65,7 +78,31 @@ fun NodeRow(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
if (tags.isNotEmpty()) {
|
||||
Row(Modifier.padding(top = 2.dp), horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
tags.take(3).forEach { TagChip(it) }
|
||||
if (tags.size > 3) Text("+${tags.size - 3}", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
}
|
||||
}
|
||||
RightChevronIcon(Modifier.size(18.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NodeTypeIcon(type: NodeType, modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
val resources = LocalResources.current
|
||||
val resource = remember(type.iconKey, resources) { type.iconKey?.let { resources.getIdentifier("type_icon_${it.replace('-', '_')}", "drawable", context.packageName) } ?: 0 }
|
||||
val color = type.color?.let { runCatching { Color(AndroidColor.parseColor(it)) }.getOrNull() } ?: MaterialTheme.colorScheme.onSurfaceVariant
|
||||
if (resource != 0) Icon(painterResource(resource), contentDescription = type.name, tint = color, modifier = modifier.size(22.dp))
|
||||
else Text(type.name.firstOrNull()?.uppercase() ?: "T", color = color)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TagChip(tag: Tag) {
|
||||
val color = tag.color?.let { runCatching { Color(AndroidColor.parseColor(it)) }.getOrNull() } ?: MaterialTheme.colorScheme.onSurfaceVariant
|
||||
Surface(color = color.copy(alpha = .16f), contentColor = color, shape = RoundedCornerShape(3.dp)) {
|
||||
Text(tag.name, style = MaterialTheme.typography.labelSmall, maxLines = 1, modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package app.boxmanifest.ui.components
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
|
||||
fun Context.showErrorMessage(message: String) {
|
||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M9,8H10.5V19H9V8Z" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M13.5,8H15V19H13.5V8Z" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M2.75,4L21.25,4A0.75,0.75 0,0 1,22 4.75L22,4.75A0.75,0.75 0,0 1,21.25 5.5L2.75,5.5A0.75,0.75 0,0 1,2 4.75L2,4.75A0.75,0.75 0,0 1,2.75 4z" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M8.4375,2.5C8.4375,1.5335 9.221,0.75 10.1875,0.75H13.6875C14.654,0.75 15.4375,1.5335 15.4375,2.5V4.75H8.4375V2.5Z" android:strokeWidth="1.5" android:fillColor="#00000000" android:strokeColor="#FF000000" />
|
||||
<path android:pathData="M5.5978,19.1282L5.0302,4.75H18.9848L18.4053,19.1309C18.3351,20.8735 16.902,22.25 15.1579,22.25H8.8452C7.1002,22.25 5.6666,20.8719 5.5978,19.1282Z" android:strokeWidth="1.5" android:fillColor="#00000000" android:strokeColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M19.5,8L21.293,6.207C21.683,5.817 21.683,5.183 21.293,4.793L20.207,3.707C19.817,3.317 19.183,3.317 18.793,3.707L17,5.5L19.5,8ZM6.931,19.759L18,9.4L15.5,7L4.5,17L3,21L6.931,19.759Z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512">
|
||||
<path android:pathData="M342,444h46a56,56 0,0 0,56 -56V342" android:strokeLineJoin="round" android:strokeWidth="44" android:fillColor="#00000000" android:strokeColor="#FF000000" android:strokeLineCap="round" />
|
||||
<path android:pathData="M444,170V124a56,56 0,0 0,-56 -56H342" android:strokeLineJoin="round" android:strokeWidth="44" android:fillColor="#00000000" android:strokeColor="#FF000000" android:strokeLineCap="round" />
|
||||
<path android:pathData="M170,444H124a56,56 0,0 1,-56 -56V342" android:strokeLineJoin="round" android:strokeWidth="44" android:fillColor="#00000000" android:strokeColor="#FF000000" android:strokeLineCap="round" />
|
||||
<path android:pathData="M68,170V124a56,56 0,0 1,56 -56h46" android:strokeLineJoin="round" android:strokeWidth="44" android:fillColor="#00000000" android:strokeColor="#FF000000" android:strokeLineCap="round" />
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M13,15C14.105,15 15,14.105 15,13V8C15,6.964 14.213,6.113 13.204,6.011L13,6H8L7.796,6.011C6.854,6.106 6.106,6.854 6.011,7.796L6,8V13C6,14.105 6.895,15 8,15H13ZM19.5,9C19.776,9 20,8.776 20,8.5V6.5C20,6.224 19.776,6 19.5,6H17.5C17.224,6 17,6.224 17,6.5V8.5C17,8.776 17.224,9 17.5,9H19.5ZM25.5,9C25.776,9 26,8.776 26,8.5V6.5C26,6.224 25.776,6 25.5,6H23.5C23.224,6 23,6.224 23,6.5V8.5C23,8.776 23.224,9 23.5,9H25.5ZM8,13.5C7.724,13.5 7.5,13.276 7.5,13V8C7.5,7.724 7.724,7.5 8,7.5H13C13.276,7.5 13.5,7.724 13.5,8V13C13.5,13.276 13.276,13.5 13,13.5H8ZM11.5,12C11.776,12 12,11.776 12,11.5V9.5C12,9.224 11.776,9 11.5,9H9.5C9.224,9 9,9.224 9,9.5V11.5C9,11.776 9.224,12 9.5,12H11.5ZM22.5,12C22.776,12 23,11.776 23,11.5L23,9.5C23,9.224 22.776,9 22.5,9L20.5,9C20.224,9 20,9.224 20,9.5V11.5C20,11.776 20.224,12 20.5,12H22.5ZM19.5,15C19.776,15 20,14.776 20,14.5V12.5C20,12.224 19.776,12 19.5,12H17.5C17.224,12 17,12.224 17,12.5V14.5C17,14.776 17.224,15 17.5,15H19.5ZM25.5,15C25.776,15 26,14.776 26,14.5V12.5C26,12.224 25.776,12 25.5,12H23.5C23.224,12 23,12.224 23,12.5V14.5C23,14.776 23.224,15 23.5,15H25.5ZM13,26C14.105,26 15,25.105 15,24V19C15,17.964 14.213,17.113 13.204,17.011L13,17H8L7.796,17.011C6.854,17.106 6.106,17.854 6.011,18.796L6,19L6,24C6,25.105 6.895,26 8,26H13ZM24,26C25.105,26 26,25.105 26,24V19C26,17.964 25.213,17.113 24.204,17.011L24,17H19L18.796,17.011C17.854,17.106 17.106,17.854 17.011,18.796L17,19V24C17,25.105 17.895,26 19,26L24,26ZM8,24.5C7.724,24.5 7.5,24.276 7.5,24L7.5,19C7.5,18.724 7.724,18.5 8,18.5H13C13.276,18.5 13.5,18.724 13.5,19V24C13.5,24.276 13.276,24.5 13,24.5H8ZM19,24.5C18.724,24.5 18.5,24.276 18.5,24V19C18.5,18.724 18.724,18.5 19,18.5H24C24.276,18.5 24.5,18.724 24.5,19V24C24.5,24.276 24.276,24.5 24,24.5L19,24.5ZM11.5,23C11.776,23 12,22.776 12,22.5V20.5C12,20.224 11.776,20 11.5,20H9.5C9.224,20 9,20.224 9,20.5V22.5C9,22.776 9.224,23 9.5,23H11.5ZM22.5,23C22.776,23 23,22.776 23,22.5V20.5C23,20.224 22.776,20 22.5,20H20.5C20.224,20 20,20.224 20,20.5V22.5C20,22.776 20.224,23 20.5,23L22.5,23Z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,6 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M6.5156,11.2388H17.0117V12.7388H6.5156V11.2388Z" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M17.5,2.4888H6.5C4.2909,2.4888 2.5,4.2796 2.5,6.4888V17.4888C2.5,19.6979 4.2909,21.4888 6.5,21.4888H17.5C19.7091,21.4888 21.5,19.6979 21.5,17.4888V6.4888C21.5,4.2796 19.7091,2.4888 17.5,2.4888ZM6.5,0.9888C3.4624,0.9888 1,3.4512 1,6.4888V17.4888C1,20.5263 3.4624,22.9888 6.5,22.9888H17.5C20.5376,22.9888 23,20.5263 23,17.4888V6.4888C23,3.4512 20.5376,0.9888 17.5,0.9888H6.5Z" android:fillColor="#FF000000" android:fillType="evenOdd" />
|
||||
<path android:pathData="M11.4697,6.4697C11.7626,6.1768 12.2374,6.1768 12.5303,6.4697L18.0607,12L12.5303,17.5303C12.2374,17.8232 11.7626,17.8232 11.4697,17.5303C11.1768,17.2374 11.1768,16.7626 11.4697,16.4697L15.9393,12L11.4697,7.5303C11.1768,7.2374 11.1768,6.7626 11.4697,6.4697Z" android:fillColor="#FF000000" android:fillType="evenOdd" />
|
||||
</vector>
|
||||
@@ -0,0 +1,13 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M4.5,7.5L12,3.5L19.5,7.5V16.5L12,20.5L4.5,16.5Z M4.5,7.5L12,11.5L19.5,7.5 M12,11.5V20.5"
|
||||
android:strokeColor="#FF000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.5" />
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#FF000000" android:pathData="M1,4C1,3.5858 1.3358,3.25 1.75,3.25H22.25C22.6642,3.25 23,3.5858 23,4C23,4.4142 22.6642,4.75 22.25,4.75H1.75C1.3358,4.75 1,4.4142 1,4Z" />
|
||||
<path android:fillColor="#FF000000" android:pathData="M5.5,12C5.5,11.5858 5.8358,11.25 6.25,11.25H17.75C18.1642,11.25 18.5,11.5858 18.5,12C18.5,12.4142 18.1642,12.75 17.75,12.75H6.25C5.8358,12.75 5.5,12.4142 5.5,12Z" />
|
||||
<path android:fillColor="#FF000000" android:pathData="M10,20C10,19.5858 10.3358,19.25 10.75,19.25H13.25C13.6642,19.25 14,19.5858 14,20C14,20.4142 13.6642,20.75 13.25,20.75H10.75C10.3358,20.75 10,20.4142 10,20Z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,13 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M4,5.5H20V10.5H4Z M4,13.5H20V18.5H4Z M7,8H7.01 M7,16H7.01"
|
||||
android:strokeColor="#FF777777"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.5" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M256 112a56 56 0 1156-56 56.06 56.06 0 01-56 56z" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M432 112.8l-.45.12-.42.13c-1 .28-2 .58-3 .89-18.61 5.46-108.93 30.92-172.56 30.92-59.13 0-141.28-22-167.56-29.47a73.79 73.79 0 00-8-2.58c-19-5-32 14.3-32 31.94 0 17.47 15.7 25.79 31.55 31.76v.28l95.22 29.74c9.73 3.73 12.33 7.54 13.6 10.84 4.13 10.59.83 31.56-.34 38.88l-5.8 45-32.19 176.19q-.15.72-.27 1.47l-.23 1.27c-2.32 16.15 9.54 31.82 32 31.82 19.6 0 28.25-13.53 32-31.94s28-157.57 42-157.57 42.84 157.57 42.84 157.57c3.75 18.41 12.4 31.94 32 31.94 22.52 0 34.38-15.74 32-31.94a57.17 57.17 0 00-.76-4.06L329 301.27l-5.79-45c-4.19-26.21-.82-34.87.32-36.9a1.09 1.09 0 00.08-.15c1.08-2 6-6.48 17.48-10.79l89.28-31.21a16.9 16.9 0 001.62-.52c16-6 32-14.3 32-31.93S451 107.81 432 112.8z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48zm80 224h-64v64a16 16 0 01-32 0v-64h-64a16 16 0 010-32h64v-64a16 16 0 0132 0v64h64a16 16 0 010 32z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M186.62 464H160a16 16 0 01-14.57-22.6l64.46-142.25L113.1 297l-35.3 42.77C71.07 348.23 65.7 352 52 352H34.08a17.66 17.66 0 01-14.7-7.06c-2.38-3.21-4.72-8.65-2.44-16.41l19.82-71c.15-.53.33-1.06.53-1.58a.38.38 0 000-.15 14.82 14.82 0 01-.53-1.59l-19.84-71.45c-2.15-7.61.2-12.93 2.56-16.06a16.83 16.83 0 0113.6-6.7H52c10.23 0 20.16 4.59 26 12l34.57 42.05 97.32-1.44-64.44-142A16 16 0 01160 48h26.91a25 25 0 0119.35 9.8l125.05 152 57.77-1.52c4.23-.23 15.95-.31 18.66-.31C463 208 496 225.94 496 256c0 9.46-3.78 27-29.07 38.16-14.93 6.6-34.85 9.94-59.21 9.94-2.68 0-14.37-.08-18.66-.31l-57.76-1.54-125.36 152a25 25 0 01-19.32 9.75z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M153.59 110.46A21.41 21.41 0 00152.48 79 62.67 62.67 0 00112 64l-3.27.09h-.48C74.4 66.15 48 95.55 48.07 131c0 19 8 29.06 14.32 37.11a20.61 20.61 0 0014.7 7.8c.26 0 .7.05 2 .05a19.06 19.06 0 0013.75-5.89zM403.79 64.11l-3.27-.1H400a62.67 62.67 0 00-40.52 15 21.41 21.41 0 00-1.11 31.44l60.77 59.65a19.06 19.06 0 0013.79 5.9c1.28 0 1.72 0 2-.05a20.61 20.61 0 0014.69-7.8c6.36-8.05 14.28-18.08 14.32-37.11.06-35.49-26.34-64.89-60.15-66.93z" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M256.07 96c-97 0-176 78.95-176 176a175.23 175.23 0 0040.81 112.56l-36.12 36.13a16 16 0 1022.63 22.62l36.12-36.12a175.63 175.63 0 00225.12 0l36.13 36.12a16 16 0 1022.63-22.62l-36.13-36.13A175.17 175.17 0 00432.07 272c0-97-78.95-176-176-176zm16 176a16 16 0 01-16 16h-80a16 16 0 010-32h64v-96a16 16 0 0132 0z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M368 96H144a16 16 0 010-32h224a16 16 0 010 32zM400 144H112a16 16 0 010-32h288a16 16 0 010 32zM419.13 448H92.87A44.92 44.92 0 0148 403.13V204.87A44.92 44.92 0 0192.87 160h326.26A44.92 44.92 0 01464 204.87v198.26A44.92 44.92 0 01419.13 448z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48zm0 319.91a20 20 0 1120-20 20 20 0 01-20 20zm21.72-201.15l-5.74 122a16 16 0 01-32 0l-5.74-121.94v-.05a21.74 21.74 0 1143.44 0z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M122.06 122.06c-44.37 44.37-66.71 100.61-78 145.28l200.6 200.56c44.67-11.25 100.91-33.59 145.28-78s66.71-100.61 78-145.28L267.34 44.1c-44.67 11.25-100.91 33.59-145.28 77.96zm256.73 256.72zM300.65 189L323 166.71A15.78 15.78 0 01345.29 189L323 211.35l11.16 11.17a15.78 15.78 0 01-22.32 22.32l-11.16-11.16L278.32 256l11.16 11.16a15.78 15.78 0 11-22.32 22.32L256 278.32l-22.32 22.33 11.16 11.16a15.78 15.78 0 11-22.32 22.32L211.35 323 189 345.29A15.78 15.78 0 01166.71 323L189 300.65l-11.16-11.17a15.78 15.78 0 0122.32-22.32l11.16 11.16L233.68 256l-11.16-11.16a15.78 15.78 0 1122.32-22.32L256 233.68l22.32-22.33-11.16-11.16a15.78 15.78 0 0122.32-22.32zM476.57 199.63c7.31-54.53 4-120.26-20-144.21s-89.68-27.3-144.21-20c-2.51.34-5.16.72-7.91 1.15l171 171c.4-2.78.78-5.43 1.12-7.94zM35.43 312.37c-7.31 54.53-4 120.26 20 144.21C72.17 473.33 109.34 480 148.84 480a387 387 0 0050.79-3.43c2.51-.34 5.16-.72 7.91-1.15l-171-171c-.39 2.79-.77 5.44-1.11 7.95z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M456 128a40 40 0 00-37.23 54.6l-84.17 84.17a39.86 39.86 0 00-29.2 0l-60.17-60.17a40 40 0 10-74.46 0L70.6 306.77a40 40 0 1022.63 22.63L193.4 229.23a39.86 39.86 0 0029.2 0l60.17 60.17a40 40 0 1074.46 0l84.17-84.17A40 40 0 10456 128z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M250.54 129.17l-67.8-67.8A209.65 209.65 0 0086.32 136h161.4a4 4 0 002.82-6.83zM167.72 168H67.63a207.34 207.34 0 00-16.15 125.9l119.06-119.07a4 4 0 00-2.82-6.83zM344 167.72V67.56a207.82 207.82 0 00-125.89-16.08l119.06 119.06a4 4 0 006.83-2.82zM460.52 218.1L341.46 337.17a4 4 0 002.82 6.83h100.09a207.34 207.34 0 0016.15-125.9zM382.83 250.54l67.83-67.82A209.08 209.08 0 00376 86.2v161.52a4 4 0 006.83 2.82zM221.68 341.77a8 8 0 005.54 2.23h59.66a8 8 0 005.7-2.39l49.18-50a8 8 0 002.3-5.62l-.06-60.81a8 8 0 00-2.38-5.69l-50-49.25a8 8 0 00-5.63-2.3l-60.84.06a8 8 0 00-5.69 2.38l-49.25 50a8 8 0 00-2.3 5.63l.06 60.78a8 8 0 002.45 5.76zM261.46 382.83l67.8 67.8A209.65 209.65 0 00425.68 376h-161.4a4 4 0 00-2.82 6.83zM168 344.28v100.16a207.82 207.82 0 00125.89 16.08L174.83 341.46a4 4 0 00-6.83 2.82zM129.17 261.46l-67.83 67.83A209.1 209.1 0 00136 425.8V264.28a4 4 0 00-6.83-2.82z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M104 160a56 56 0 1156-56 56.06 56.06 0 01-56 56zM256 160a56 56 0 1156-56 56.06 56.06 0 01-56 56zM408 160a56 56 0 1156-56 56.06 56.06 0 01-56 56zM104 312a56 56 0 1156-56 56.06 56.06 0 01-56 56zM256 312a56 56 0 1156-56 56.06 56.06 0 01-56 56zM408 312a56 56 0 1156-56 56.06 56.06 0 01-56 56zM104 464a56 56 0 1156-56 56.06 56.06 0 01-56 56zM256 464a56 56 0 1156-56 56.06 56.06 0 01-56 56zM408 464a56 56 0 1156-56 56.06 56.06 0 01-56 56z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M64 164v244a56 56 0 0056 56h272a56 56 0 0056-56V164a4 4 0 00-4-4H68a4 4 0 00-4 4zm267 151.63l-63.69 63.68a16 16 0 01-22.62 0L181 315.63c-6.09-6.09-6.65-16-.85-22.38a16 16 0 0123.16-.56L240 329.37V224.45c0-8.61 6.62-16 15.23-16.43A16 16 0 01272 224v105.37l36.69-36.68a16 16 0 0123.16.56c5.8 6.37 5.24 16.29-.85 22.38z" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M32,48h448v80h-448z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M48 256c0 114.87 93.13 208 208 208s208-93.13 208-208S370.87 48 256 48 48 141.13 48 256zm212.65-91.36a16 16 0 01.09 22.63L208.42 240H342a16 16 0 010 32H208.42l52.32 52.73A16 16 0 11238 347.27l-79.39-80a16 16 0 010-22.54l79.39-80a16 16 0 0122.65-.09z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M256 464c114.87 0 208-93.13 208-208S370.87 48 256 48 48 141.13 48 256s93.13 208 208 208zm-91.36-212.65a16 16 0 0122.63-.09L240 303.58V170a16 16 0 0132 0v133.58l52.73-52.32A16 16 0 11347.27 274l-80 79.39a16 16 0 01-22.54 0l-80-79.39a16 16 0 01-.09-22.65z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M464 256c0-114.87-93.13-208-208-208S48 141.13 48 256s93.13 208 208 208 208-93.13 208-208zm-212.65 91.36a16 16 0 01-.09-22.63L303.58 272H170a16 16 0 010-32h133.58l-52.32-52.73A16 16 0 11274 164.73l79.39 80a16 16 0 010 22.54l-79.39 80a16 16 0 01-22.65.09z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M58.79 439.13A16 16 0 0148 424c0-73.1 14.68-131.56 43.65-173.77 35-51 90.21-78.46 164.35-81.87V88a16 16 0 0127.05-11.57l176 168a16 16 0 010 23.14l-176 168A16 16 0 01256 424v-79.77c-45 1.36-79 8.65-106.07 22.64-29.25 15.12-50.46 37.71-73.32 67a16 16 0 01-17.82 5.28z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M48 256c0 114.87 93.13 208 208 208s208-93.13 208-208S370.87 48 256 48 48 141.13 48 256zm96 66.67c5.45-61.45 34.14-117.09 122.87-117.09v-37.32a8.32 8.32 0 0114-6L365.42 242a8.2 8.2 0 010 11.94L281 333.71a8.32 8.32 0 01-14-6v-37.29c-57.07 0-84.51 13.47-108.58 38.68-5.49 5.65-15.07 1.32-14.42-6.43z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M448 440a16 16 0 01-12.61-6.15c-22.86-29.27-44.07-51.86-73.32-67C335 352.88 301 345.59 256 344.23V424a16 16 0 01-27 11.57l-176-168a16 16 0 010-23.14l176-168A16 16 0 01256 88v80.36c74.14 3.41 129.38 30.91 164.35 81.87C449.32 292.44 464 350.9 464 424a16 16 0 01-16 16z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M256 48C141.13 48 48 141.13 48 256s93.13 208 208 208 208-93.13 208-208S370.87 48 256 48zm97.67 281.1c-24.07-25.21-51.51-38.68-108.58-38.68v37.32a8.32 8.32 0 01-14.05 6L146.58 254a8.2 8.2 0 010-11.94L231 162.29a8.32 8.32 0 0114.05 6v37.32c88.73 0 117.42 55.64 122.87 117.09.73 7.72-8.85 12.05-14.25 6.4z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M256 48C141.13 48 48 141.13 48 256s93.13 208 208 208 208-93.13 208-208S370.87 48 256 48zm91.36 212.65a16 16 0 01-22.63.09L272 208.42V342a16 16 0 01-32 0V208.42l-52.73 52.32A16 16 0 11164.73 238l80-79.39a16 16 0 0122.54 0l80 79.39a16 16 0 01.09 22.65z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M255.46 48.74c-114.84 0-208 93.11-208 208s93.12 208 208 208 208-93.12 208-208-93.15-208-208-208zM380.28 252c-2.85 32.63-16.79 49.7-28 58.26S327.61 322.58 316 320.5a41.61 41.61 0 01-26.82-17.19 62.06 62.06 0 01-44 17.57 51.66 51.66 0 01-38.55-16.83c-11.38-12.42-17-30.36-15.32-49.23 3-35 30.91-57.39 56.87-61.48 27.2-4.29 52.23 6.54 62.9 19.46l3.85 4.66-6.34 50.38c-1.19 14.34 3.28 23.48 12.29 25.1 2.39.42 8.1-.13 14.37-4.93 6.72-5.15 15.14-16 17.1-38.47 2.35-26.54-4.35-49.19-19.25-65.49-15.49-16.9-39.09-25.84-68.23-25.84-54 0-101.81 44.43-106.58 99-2.28 26.2 5.67 50.68 22.4 68.93 16.36 17.86 39.31 27.74 64.66 27.74 19 0 30.61-2.05 49.48-8.78a14 14 0 019.4 26.38c-21.82 7.77-36.68 10.4-58.88 10.4-33.28 0-63.57-13.06-85.3-36.77C138 321 127.42 288.94 130.4 254.82c2.91-33.33 18.45-64.63 43.77-88.12s57.57-36.49 90.7-36.49c37.2 0 67.93 12.08 88.87 34.93 20.09 21.91 29.51 52.75 26.54 86.86z" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M252.57 221c-14.83 2.33-31.56 15.84-33.34 36.26-1 11.06 2 21.22 8.07 27.87a23.65 23.65 0 0017.91 7.75c20.31 0 34.73-14.94 36.75-38.06a14 14 0 01.34-2.07l3.2-25.45a49.61 49.61 0 00-32.93-6.3z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M216.08 192v143.85a40.08 40.08 0 0080.15 0l.13-188.55a67.94 67.94 0 10-135.87 0v189.82a95.51 95.51 0 10191 0V159.74" android:fillColor="@android:color/transparent" android:strokeColor="#FF000000" android:strokeWidth="32" android:strokeLineCap="round" android:strokeLineJoin="miter" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M403.13 96H156.87a44.9 44.9 0 00-33.68 15.27 15.88 15.88 0 00-1.91 2.7L32 247.75a16 16 0 000 16.5l89.15 133.57a16.24 16.24 0 002 2.88 44.89 44.89 0 0033.7 15.3h246.28A44.92 44.92 0 00448 371.13V140.87A44.92 44.92 0 00403.13 96zM348 311a16 16 0 11-22.63 22.62L271.67 280 218 333.65A16 16 0 01195.35 311L249 257.33l-53.69-53.69A16 16 0 01218 181l53.69 53.7 53.67-53.7A16 16 0 01348 203.64l-53.7 53.69z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M454.65 169.4A31.82 31.82 0 00432 160h-64v-16a112 112 0 00-224 0v16H80a32 32 0 00-32 32v216c0 39 33 72 72 72h272a72.22 72.22 0 0050.48-20.55 69.48 69.48 0 0021.52-50.2V192a31.75 31.75 0 00-9.35-22.6zM176 144a80 80 0 01160 0v16H176z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M454.66 169.4A31.86 31.86 0 00432 160h-64v-16a112 112 0 00-224 0v16H80a32 32 0 00-32 32v216c0 39 33 72 72 72h272a72.22 72.22 0 0050.48-20.55 69.48 69.48 0 0021.52-50.2V192a31.78 31.78 0 00-9.34-22.6zM320 336h-48v48a16 16 0 01-32 0v-48h-48a16 16 0 010-32h48v-48a16 16 0 0132 0v48h48a16 16 0 010 32zm16-176H176v-16a80 80 0 01160 0z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M454.65 169.4A31.82 31.82 0 00432 160h-64v-16a112 112 0 00-224 0v16H80a32 32 0 00-32 32v216c0 39 33 72 72 72h272a72.22 72.22 0 0050.48-20.55 69.48 69.48 0 0021.52-50.2V192a31.75 31.75 0 00-9.35-22.6zM332.49 274l-89.6 112a16 16 0 01-12.23 6h-.26a16 16 0 01-12.16-5.6l-38.4-44.88a16 16 0 1124.32-20.8L230 350.91 307.51 254a16 16 0 0125 20zM336 160H176v-16a80 80 0 01160 0z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M454.65 169.4A31.82 31.82 0 00432 160h-64v-16a112 112 0 00-224 0v16H80a32 32 0 00-32 32v216c0 39 33 72 72 72h272a72.22 72.22 0 0050.48-20.55 69.48 69.48 0 0021.52-50.2V192a31.75 31.75 0 00-9.35-22.6zM176 144a80 80 0 01160 0v16H176zm192 96a112 112 0 01-224 0v-16a16 16 0 0132 0v16a80 80 0 00160 0v-16a16 16 0 0132 0z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M454.66 169.4A31.86 31.86 0 00432 160h-64v-16a112 112 0 00-224 0v16H80a32 32 0 00-32 32v216c0 39 33 72 72 72h272a72.22 72.22 0 0050.48-20.55 69.48 69.48 0 0021.52-50.2V192a31.78 31.78 0 00-9.34-22.6zM320 336H192a16 16 0 010-32h128a16 16 0 010 32zm16-176H176v-16a80 80 0 01160 0z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M391 307.27c32.75-46.35 46.59-101.63 39-155.68A175.82 175.82 0 00231.38 2c-96 13.49-163.14 102.58-149.65 198.58 7.57 53.89 36.12 103.16 80.37 138.74 24.58 19.68 52.31 33.5 78.62 39.68a8 8 0 016 9.22l-4.87 26.38a16.29 16.29 0 001.48 10.57 16 16 0 0014.2 8.61 15.21 15.21 0 002.23-.16l17.81-2.5a2 2 0 012.09 1.14c16.72 36.31 45.46 63.85 82.15 78.36a16 16 0 0021-9.65c2.83-8.18-1.64-17.07-9.68-20.28a118.57 118.57 0 01-59.3-51.88 2 2 0 011.45-3l7.4-1a16.54 16.54 0 0010.08-5.23 16 16 0 002.39-17.8l-12.06-24.23a8 8 0 013.26-10.55c23.59-13.17 46.45-34 64.65-59.73zm-154.9 16.78a16 16 0 01-5.88-1.12c-41.26-16.32-76.3-52.7-91.45-94.94a16 16 0 1130.12-10.8c14.5 40.44 47.27 65.77 73.1 76a16 16 0 01-5.89 30.88z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M56,256a200,200 0 1,0 400,0a200,200 0 1,0 -400,0" android:fillColor="@android:color/transparent" android:strokeColor="#FF000000" android:strokeWidth="48" android:strokeLineCap="butt" android:strokeLineJoin="miter" />
|
||||
<path android:pathData="M114.58 114.58l282.84 282.84" android:fillColor="#FF000000" android:strokeColor="#FF000000" android:strokeWidth="48" android:strokeLineCap="butt" android:strokeLineJoin="miter" />
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M275.8 157a16 16 0 00-22.63 0l-93.34 93.34a16 16 0 000 22.63l79.2 79.2a16 16 0 0022.63 0L355 258.83a16 16 0 000-22.63zM137.21 295.6a47.81 47.81 0 01-9.43-13.38L69 341a72.2 72.2 0 000 102 72.37 72.37 0 00102 0l58.77-58.76a47.81 47.81 0 01-13.38-9.43zM392 48a71.55 71.55 0 00-51 21l-55.92 55.91a48.05 48.05 0 0113.36 9.45l79.19 79.19a48.05 48.05 0 019.45 13.36L443 171a72 72 0 00-51-123z" android:fillColor="@android:color/transparent" />
|
||||
<path android:pathData="M275.8 157a16 16 0 00-22.63 0l-93.34 93.34a16 16 0 000 22.63l79.2 79.2a16 16 0 0022.63 0L355 258.83a16 16 0 000-22.63zm-56.49 110.31a16 16 0 110-22.62 16 16 0 010 22.62zm48 48a16 16 0 110-22.62 16 16 0 010 22.62zm0-96a16 16 0 110-22.62 16 16 0 010 22.62zm48 48a16 16 0 110-22.62 16 16 0 010 22.62zM465.61 46.39a104.38 104.38 0 00-147.25 0l-69.76 69.89a4 4 0 004.2 6.58 35.74 35.74 0 0111.69-2.54 47.7 47.7 0 0133.94 14.06l79.19 79.19a47.7 47.7 0 0114.06 33.94 35.68 35.68 0 01-2.54 11.69 4 4 0 006.58 4.2l69.89-69.76a104.38 104.38 0 000-147.25z" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M254.34 386.83a47.91 47.91 0 01-33.94-14l-79.19-79.23a47.81 47.81 0 01-9.43-13.38c-4.59-9.7-1.39-25 2.48-36.9a4 4 0 00-6.64-4l-77.23 77.04a104.12 104.12 0 00147.25 147.25l72.75-72.88a4 4 0 00-4.21-6.58c-4.18 1.58-8.4 2.68-11.84 2.68z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M480 496H48a32 32 0 01-32-32V32a16 16 0 0132 0v432h432a16 16 0 010 32z" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M156 432h-40a36 36 0 01-36-36V244a36 36 0 0136-36h40a36 36 0 0136 36v152a36 36 0 01-36 36zM300 432h-40a36 36 0 01-36-36V196a36 36 0 0136-36h40a36 36 0 0136 36v200a36 36 0 01-36 36zM443.64 432h-40a36 36 0 01-36-36V132a36 36 0 0136-36h40a36 36 0 0136 36v264a36 36 0 01-36 36z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M467 176a29.94 29.94 0 00-25.32 12.5 2 2 0 01-3.64-1.14v-36.65c0-20.75-16.34-38.21-37.08-38.7A38 38 0 00362 150v82a2 2 0 01-2 2H152a2 2 0 01-2-2v-81.29c0-20.75-16.34-38.21-37.08-38.7A38 38 0 0074 150v37.38a2 2 0 01-3.64 1.14A29.94 29.94 0 0045 176c-16.3.51-29 14.31-29 30.62v98.72c0 16.31 12.74 30.11 29 30.62a29.94 29.94 0 0025.32-12.5 2 2 0 013.68 1.16v36.67C74 382 90.34 399.5 111.08 400A38 38 0 00150 362v-82a2 2 0 012-2h208a2 2 0 012 2v81.29c0 20.75 16.34 38.21 37.08 38.7A38 38 0 00438 362v-37.38a2 2 0 013.64-1.14A29.94 29.94 0 00467 336c16.3-.51 29-14.31 29-30.62v-98.74c0-16.31-12.74-30.11-29-30.64z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M419.13 96H419l-35.05.33L128 96h-.16l-36.74.33C66.93 96.38 48 116.07 48 141.2v230.27c0 25.15 19 44.86 43.2 44.86h.15l36.71-.33 255.92.33h.17l35.07-.33A44.91 44.91 0 00464 371.13V140.87A44.92 44.92 0 00419.13 96zM144 320a16 16 0 01-32 0V192a16 16 0 0132 0zm64 32a16 16 0 01-32 0V160a16 16 0 0132 0zm64-16a16 16 0 01-32 0V176a16 16 0 0132 0zm64 16a16 16 0 01-32 0V160a16 16 0 0132 0zm64-32a16 16 0 01-32 0V192a16 16 0 0132 0z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M444 295.67l-.47-26.07a205.42 205.42 0 01-39.27-4.73l-5 17.35a14 14 0 11-26.88-7.81l5-17.38a206.47 206.47 0 01-36.05-17.35l-10.44 14.77a14 14 0 01-22.87-16.16l10.41-14.73a204.8 204.8 0 01-30-30L273.71 204a14 14 0 01-16.16-22.87l14.74-10.42a205.3 205.3 0 01-17.38-36.06l-17.32 5a14 14 0 11-7.81-26.89l17.3-5a205.88 205.88 0 01-4.43-32.59q-.17-3-.24-6l-18.22-.33a14 14 0 01-13.74-14A208 208 0 0055 202.42a16 16 0 0115 15.66l.44 24.43c1.58.05 3.16.11 4.73.2a205.88 205.88 0 0132.59 4.43l5-17.3a14 14 0 0126.89 7.81l-5 17.32a205.21 205.21 0 0136 17.38L181 257.61a14 14 0 0122.87 16.16l-10.39 14.73a205.65 205.65 0 0115.79 14.23 203.79 203.79 0 0114.23 15.79l14.73-10.41A14 14 0 01254.39 331l-14.76 10.43A206.86 206.86 0 01257 377.47l17.38-5.05a14 14 0 017.81 26.89l-17.35 5a205.89 205.89 0 014.7 38.28l18.27.33a16 16 0 0115.71 16.28 11.69 11.69 0 01-.08 1.19 208 208 0 00153.39-151.03A14 14 0 01444 295.67z" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M301.49 56.2A14 14 0 01287.5 70h-.25l-16.81-.3c.05 1.31.1 2.62.17 3.93a178.83 178.83 0 003.44 26.31l16.29-4.74a14 14 0 017.81 26.8l-16.27 4.73a178.06 178.06 0 0013.33 27.69l13.84-9.78a14 14 0 1116.16 22.87l-13.86 9.79q5.25 6.33 11.12 12.19c3.9 3.91 8 7.6 12.2 11.1l9.78-13.84A14 14 0 11367.32 203l-9.76 13.8a178.83 178.83 0 0027.68 13.33l4.76-16.28a14 14 0 0126.89 7.81l-4.73 16.26a177.72 177.72 0 0030.95 3.65l-.16-9a14 14 0 0113.75-14.24h.25a14.67 14.67 0 012.59.25 208 208 0 00-158-163.51c-.06.4-.04.76-.05 1.13zM208.44 457.55a16 16 0 0116.28-15.71l16.76.29a178.49 178.49 0 00-3.62-29.95l-16.26 4.72a14 14 0 11-7.81-26.9l16.21-4.7a177.92 177.92 0 00-13.33-27.68l-13.8 9.76a14 14 0 11-16.16-22.87l13.84-9.78c-3.5-4.22-7.19-8.3-11.1-12.2s-8-7.62-12.19-11.12l-9.79 13.86a14 14 0 11-22.87-16.16l9.78-13.84a177.16 177.16 0 00-27.69-13.33L122 298.21a14 14 0 11-26.9-7.81l4.73-16.29a177.32 177.32 0 00-26.31-3.44c-.89-.05-1.79-.08-2.68-.12l.16 10.59a16 16 0 01-15.71 16.28H55a16 16 0 01-3.94-.51 208 208 0 00157.65 163.87 15.72 15.72 0 01-.27-3.23z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M424.11 192H360L268.8 70.4a16 16 0 00-25.6 0L152 192H87.89a32.57 32.57 0 00-32.62 32.44 30.3 30.3 0 001.31 9l46.27 163.14a50.72 50.72 0 0048.84 36.91h208.62a51.21 51.21 0 0049-36.86l46.33-163.36a15.62 15.62 0 00.46-2.36l.53-4.93a13.3 13.3 0 00.09-1.55A32.57 32.57 0 00424.11 192zM256 106.67L320 192H192zm0 245a37.7 37.7 0 1137.88-37.7A37.87 37.87 0 01256 351.63z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M256 233.37l34.45-34.45a207.08 207.08 0 01-50.12-135.25c0-5 .19-10.05.54-15A207.09 207.09 0 00120.67 98zM313.14 176.23L391.33 98A207.07 207.07 0 00273 48.8c-.41 4.9-.64 9.86-.64 14.87a175.25 175.25 0 0040.78 112.56zM335.77 198.86a175.25 175.25 0 00112.56 40.81c5 0 10-.23 14.87-.64A207.07 207.07 0 00414 120.67zM176.23 313.14a175.23 175.23 0 00-112.56-40.81q-7.52 0-14.87.64A207.07 207.07 0 0098 391.33zM256 278.63l-34.45 34.45a207.08 207.08 0 0150.12 135.25c0 5-.19 10.05-.54 15A207.06 207.06 0 00391.33 414zM448.33 271.67a207.08 207.08 0 01-135.25-50.12L278.63 256 414 391.33a207.09 207.09 0 0049.39-120.2c-5.01.35-10.02.54-15.06.54zM233.37 256L98 120.67a207.06 207.06 0 00-49.39 120.2c5-.35 10-.54 15-.54a207.08 207.08 0 01135.25 50.12zM120.67 414A207.07 207.07 0 00239 463.2q.63-7.35.64-14.87a175.23 175.23 0 00-40.81-112.56z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,6 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M48 322.3V189.7A29.74 29.74 0 0177.7 160h137.44l24.4-32H77.7A61.77 61.77 0 0016 189.7v132.6A61.77 61.77 0 0077.7 384h96.85a22.57 22.57 0 01.26-7.32l.15-.75.21-.73 6.5-23.2H77.7A29.74 29.74 0 0148 322.3zM386.3 128h-98.64a22.69 22.69 0 01-.27 7.2l-.15.74-.21.73-6.54 23.33H386.3a29.74 29.74 0 0129.7 29.7v132.6a29.74 29.74 0 01-29.7 29.7H247l-24.42 32H386.3a61.77 61.77 0 0061.7-61.7V189.7a61.77 61.77 0 00-61.7-61.7z" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M162.65 294.16a24.37 24.37 0 01-21.56-13 25 25 0 011.42-25.83l.31-.46.33-.44L197.62 183H89.69a20 20 0 00-20 20v106a20 20 0 0020 20h98.42l9.78-34.86z" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M276.07 280.89l27.07-35.49a5.2 5.2 0 00.77-1.91 5 5 0 00.08-.66 5 5 0 00-.08-1.29 5.11 5.11 0 00-.68-1.75 4.76 4.76 0 00-.78-.95 3.48 3.48 0 00-.48-.38 4 4 0 00-1.11-.55 4.28 4.28 0 00-1.31-.2h-61.62l12.12-43.21 3.23-11.5 6.21-22.16.51-1.84 7.79-27.76a3.51 3.51 0 00.05-.55v-.16c0-.05 0-.26-.05-.38s0-.09 0-.14a2.2 2.2 0 00-.17-.45 3.77 3.77 0 00-.26-.39l-.09-.1a2.73 2.73 0 00-.25-.23l-.1-.08a3.14 3.14 0 00-.39-.24 2 2 0 00-.41-.14H265.53a2.3 2.3 0 00-.45 0 1.9 1.9 0 00-.42.15l-.13.07-.3.21-.11.1a2.4 2.4 0 00-.36.41l-18 23.63-13.14 17.22-9.85 12.83-63.71 83.55a5.72 5.72 0 00-.44.8 4.78 4.78 0 00-.35 1.09 4.7 4.7 0 00-.08 1.29 4.86 4.86 0 002 3.71 4.74 4.74 0 00.54.31 4.31 4.31 0 001.89.43h61.62L194.42 380.6a3.64 3.64 0 000 .56v.15a2.32 2.32 0 00.06.38.58.58 0 000 .14 2.2 2.2 0 00.17.45 3.62 3.62 0 00.26.38l.09.1.25.24a.39.39 0 01.1.08 2.22 2.22 0 00.39.23 2.83 2.83 0 00.41.14h.13a1.86 1.86 0 00.33 0h.13a2.32 2.32 0 00.45-.06 2.05 2.05 0 00.41-.16l.13-.07.3-.21.11-.09a2.4 2.4 0 00.36-.41L221.82 352l17.53-23z" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M319.5 256.93l-.46.6L264.51 329h109.8a20 20 0 0020-20V203a20 20 0 00-20-20H274.05l-9.74 34.73h35.24A24.35 24.35 0 01321 230.5a25.21 25.21 0 01-1 25.79zM480 202.67a16 16 0 00-16 16v74.66a16 16 0 0032 0v-74.66a16 16 0 00-16-16z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M31,144h400v224h-400z" android:fillColor="@android:color/transparent" android:strokeColor="#FF000000" android:strokeWidth="32" android:strokeLineCap="square" android:strokeLineJoin="miter" />
|
||||
<path android:pathData="M479 218.67v74.66" android:fillColor="@android:color/transparent" android:strokeColor="#FF000000" android:strokeWidth="32" android:strokeLineCap="round" android:strokeLineJoin="miter" />
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M32,144h400v224h-400z" android:fillColor="@android:color/transparent" android:strokeColor="#FF000000" android:strokeWidth="32" android:strokeLineCap="square" android:strokeLineJoin="miter" />
|
||||
<path android:pathData="M85.69,198.93h292.63v114.14h-292.63z" android:fillColor="#FF000000" android:strokeColor="#FF000000" android:strokeWidth="32" android:strokeLineCap="square" android:strokeLineJoin="miter" />
|
||||
<path android:pathData="M480 218.67v74.66" android:fillColor="@android:color/transparent" android:strokeColor="#FF000000" android:strokeWidth="32" android:strokeLineCap="round" android:strokeLineJoin="miter" />
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M32,144h400v224h-400z" android:fillColor="@android:color/transparent" android:strokeColor="#FF000000" android:strokeWidth="32" android:strokeLineCap="square" android:strokeLineJoin="miter" />
|
||||
<path android:pathData="M85.69,198.93h154.31v114.13h-154.31z" android:fillColor="#FF000000" android:strokeColor="#FF000000" android:strokeWidth="32" android:strokeLineCap="square" android:strokeLineJoin="miter" />
|
||||
<path android:pathData="M480 218.67v74.66" android:fillColor="@android:color/transparent" android:strokeColor="#FF000000" android:strokeWidth="32" android:strokeLineCap="round" android:strokeLineJoin="miter" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M444 32H128c-19.38 0-45.9 4.34-64.11 24.77C52.17 69.92 48 85.66 48 96a16 16 0 0013.8 15.85C91.7 116 96 117.79 96 136v264a80.07 80.07 0 0080 80h192a80.11 80.11 0 0080-80V96c0-12.55 7.46-27.25 10-31.36l.1-.14c.22-.35.5-.72.78-1.1 2-2.79 5.09-7 5.09-12.95C464 39.79 454.89 32 444 32zM84.11 83.08c5.24-8.87 17.17-19 44.29-19h294.43C419.3 72.87 416 84.27 416 96v64H128v-24c0-37.32-21.35-48.14-43.89-52.92z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M432 230.7a79.44 79.44 0 00-32-6.7H112a79.51 79.51 0 00-32 6.69A80.09 80.09 0 0032 304v112a16 16 0 0032 0v-8a8.1 8.1 0 018-8h368a8.1 8.1 0 018 8v8a16 16 0 0032 0V304a80.09 80.09 0 00-48-73.3zM376 80H136a56 56 0 00-56 56v72a4 4 0 005.11 3.84A95.5 95.5 0 01112 208h4.23a4 4 0 004-3.55A32 32 0 01152 176h56a32 32 0 0131.8 28.45 4 4 0 004 3.55h24.46a4 4 0 004-3.55A32 32 0 01304 176h56a32 32 0 0131.8 28.45 4 4 0 004 3.55h4.2a95.51 95.51 0 0126.89 3.85A4 4 0 00432 208v-72a56 56 0 00-56-56z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M392 208h-24v-5.74A63.93 63.93 0 00321.65 96a111 111 0 00-27.59-47.29A108.62 108.62 0 00216 16c-29.91 0-57.78 12.28-79 34.68a56 56 0 00-67.51 77.54A63.91 63.91 0 0080 231.39V440a56.06 56.06 0 0056 56h176a56.06 56.06 0 0056-56v-8h24a72.08 72.08 0 0072-72v-80a72.08 72.08 0 00-72-72zM176 416a16 16 0 01-32 0V256a16 16 0 0132 0zm64 0a16 16 0 01-32 0V256a16 16 0 0132 0zm64 0a16 16 0 01-32 0V256a16 16 0 0132 0zm16-224c-8.33 0-20.55-5.18-26.69-11.31A16 16 0 00282 176H160a16 16 0 00-15 10.53c-6.83 18.68-23.6 21.47-33 21.47a32 32 0 010-64c.09 0 9.12.34 16.4 5.8a16 16 0 1019.2-25.6A63.69 63.69 0 00112 112a63.55 63.55 0 00-14 1.57A24 24 0 01120 80a23.78 23.78 0 0119.38 9.84 51.35 51.35 0 014.71 7.9A16 16 0 00176 96c0-6.77-3.61-15.17-10.76-25-.46-.63-1-1.25-1.45-1.86C178.39 55.44 196.64 48 216 48a76.86 76.86 0 0155.23 23.18A80.2 80.2 0 01292.61 142a16 16 0 0012.73 18.71 16.29 16.29 0 003 .28 16 16 0 0015.7-13 111.78 111.78 0 001.96-19.42 32 32 0 01-6 63.43zm112 168a40 40 0 01-40 40h-24V240h24a40 40 0 0140 40z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M388 448a92 92 0 1192-92 92.1 92.1 0 01-92 92zm0-152a60 60 0 1060 60 60.07 60.07 0 00-60-60zM124 448a92 92 0 1192-92 92.1 92.1 0 01-92 92zm0-152a60 60 0 1060 60 60.07 60.07 0 00-60-60zM320 128a31.89 31.89 0 0032-32.1A31.55 31.55 0 00320.2 64a32 32 0 10-.2 64z" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M367.55 192h-43.76a4 4 0 01-3.51-2.08l-31.74-58.17a31 31 0 00-49.38-7.75l-69.86 70.4a32.56 32.56 0 00-9.3 22.4c0 17.4 12.6 23.6 18.5 27.1 28.5 16.42 48.57 28.43 59.58 35.1a4 4 0 011.92 3.41v69.12c0 8.61 6.62 16 15.23 16.43A16 16 0 00272 352v-86a16 16 0 00-6.66-13l-37-26.61a4 4 0 01-.58-6l42-44.79a4 4 0 016.42.79L298 215.77a16 16 0 0014 8.23h56a16 16 0 0016-16.77c-.42-8.61-7.84-15.23-16.45-15.23z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M114.917 82.831C126.817 70.758 142.711 64 164 64c18.436 0 33.934 7.239 44.611 19.607C219.057 95.707 224 111.638 224 128v34.809c8.45-1.583 19.147-2.809 32-2.809 12.853 0 23.55 1.226 32 2.809V128c0-16.362 4.943-32.293 15.389-44.393C314.066 71.239 329.564 64 348 64c21.289 0 37.183 6.758 49.083 18.831 11.072 11.233 17.546 26.036 22.792 39.275l.636 1.672 68.207 181.915s2.59 7.307 2.915 8.392A103.992 103.992 0 01496 344c0 57.438-46.562 104-104 104s-104-46.562-104-104v-60.461l-.04-.01C281.089 281.825 270.382 280 256 280c-14.382 0-25.089 1.825-31.96 3.529l-.04.01V344c0 57.438-46.562 104-104 104S16 401.438 16 344c0-10.399 1.526-20.441 4.367-29.916.152-.692.352-1.383.602-2.068.585-1.739 1.641-4.628 2.314-6.324l68.842-183.586c5.246-13.239 11.721-28.042 22.792-39.275zM320 343.905c.051-39.721 32.267-71.905 72-71.905 30.372 0 56.352 18.806 66.93 45.408l1.358 3.71A71.916 71.916 0 01464 344c0 39.764-32.236 72-72 72-39.751 0-71.978-32.214-72-71.96V344m-200-72c-30.371 0-56.35 18.805-66.93 45.406l-1.358 3.714A71.923 71.923 0 0048 344c0 39.764 32.236 72 72 72s72-32.236 72-72-32.236-72-72-72z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M388 160.77a20 20 0 00-5.85-14.91l-112-112A20 20 0 00236 48v164.52l-79-67.71a20 20 0 00-26 30.38L225.27 256 131 336.81a20 20 0 1026 30.38l79-67.71V464a20 20 0 0034.14 14.14l112-112a20 20 0 00-1.14-29.33L286.73 256 381 175.19a20 20 0 007-14.42zm-49.42 192.36L276 415.72V299.49zM276 212.52V96.28l62.59 62.59z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M416 473.14a6.83 6.83 0 00-3.57-6c-27.07-14.55-51.76-36.82-62.62-48a10.05 10.05 0 00-12.72-1.51c-50.33 32.42-111.61 32.44-161.95.05a10.09 10.09 0 00-12.82 1.56c-10.77 11.28-35.19 33.3-62.43 47.75a7.11 7.11 0 00-3.89 5.73 6.73 6.73 0 007.92 7.15c20.85-4.18 41-13.68 60.2-23.83a8.71 8.71 0 018-.06 185.14 185.14 0 00167.81 0 8.82 8.82 0 018.09.06c19.1 10 39.22 19.59 60 23.8a6.73 6.73 0 008-6.71zM476.71 246.91c-3.49-8.39-10.9-14.89-20.9-18.35L432 219.08V136a64 64 0 00-64-64h-32v-8a40 40 0 00-40-40h-80a40 40 0 00-40 40v8h-32a64 64 0 00-64 64v83.15l-23.58 9.39c-9.94 3.3-17.63 10-21.15 18.44-2.45 5.89-5.25 15-1.3 26.46l.1.3 46.66 119.44A23.33 23.33 0 00102.58 408c.5 0 1 0 1.53-.05 31.32-2 56-17.27 72.6-31.61C200.42 396.81 228.31 408 256 408s55.43-11.2 79.14-31.7c16.59 14.36 41.3 29.67 72.61 31.65a23.36 23.36 0 0023.37-14.74l46.65-119c3.28-8.09 2.9-17.76-1.06-27.3zM269 154.21l-1.14-.4a39.53 39.53 0 00-23.73 0l-.58.18-126.07 50.23a4 4 0 01-5.48-3.72V136a32 32 0 0132-32h224a32 32 0 0132 32v64.44a4 4 0 01-5.48 3.72z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M200,56a56,56 0 1,0 112,0a56,56 0 1,0 -112,0" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M437 128H75a27 27 0 000 54h101.88c6.91 0 15 3.09 19.58 15 5.35 13.83 2.73 40.54-.57 61.23l-4.32 24.45a.42.42 0 01-.12.35l-34.6 196.81A27.43 27.43 0 00179 511.58a27.06 27.06 0 0031.42-22.29l23.91-136.8S242 320 256 320c14.23 0 21.74 32.49 21.74 32.49l23.91 136.92a27.24 27.24 0 1053.62-9.6L320.66 283a.45.45 0 00-.11-.35l-4.33-24.45c-3.3-20.69-5.92-47.4-.57-61.23 4.56-11.88 12.91-15 19.28-15H437a27 27 0 000-54z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M273.38 368.37c-.81-9.23-8.86-16.44-18.55-16.44a18.63 18.63 0 00-18.2 14.07l-18.2 88.36a35.59 35.59 0 00-.93 7.87c0 19.93 16.68 33.77 37.33 33.77s37.34-13.84 37.34-33.77a36.16 36.16 0 00-1.29-9.45zM411.05 407.89a42.66 42.66 0 00-5.95-4.36L335.57 355c-6.77-4.24-14-4.13-19.25 1a13.52 13.52 0 00-2 17.19l52.5 69a38 38 0 004 4.69c9.1 10.16 29.28 10.72 40.37 0 10.94-10.96 10.81-30.39-.14-38.99zM463.3 335.93h-70.42c-4.55 0-8.88 3.35-8.88 8.15s2.95 7.85 6.92 9.16l66.43 20.55C467 376 480 367.44 480 356.71c0-13.63-7.6-20.78-16.7-20.78zM128.22 344.08c0-4.95-4.55-8.15-9.45-8.15H48.35c-8.87 0-16.35 9.58-16.35 20.31S44.62 376 54.3 373.79l67.43-20.55c4.39-1.31 6.49-4.24 6.49-9.16zM176.55 355.05L107 403.41a32.29 32.29 0 00-6 4.34 26.33 26.33 0 000 38.56 29.41 29.41 0 0040.36 0 30.75 30.75 0 004-4.68L197.9 373c3.5-5.57 2.92-12.48-2-17a15 15 0 00-19.35-.95zM293.46 242.39c10-5.42 19.45-10.54 28.31-16.16 42.46-26.92 62.23-59 62.23-101 0-64.66-56.07-104.4-108.82-109-6.49-.57-15-.42-19.91 3.88s-5.88 12.56-6.15 19.1c-1.38 33.61-28.38 59-57 85.86-28 26.3-56.93 53.49-62.71 91-8.41 54.68 21.74 86.71 40.46 100.93a31.88 31.88 0 0019.3 6.51 32.32 32.32 0 007.41-.87 31.75 31.75 0 0021.27-16.95c14.91-30.39 45.77-47.12 75.61-63.3z" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M139.82 156.57c12.22-15.76 27-29.68 41.37-43.15 19.92-18.72 37.81-35.58 46.5-53.86-12.24-7.37-27.36-11.36-46.51-11.36-17.79 0-20.39 5.18-20.39 19.06 0 12.56-6.53 20.54-14.34 30.65C137.8 109.1 128 121.77 128 140.84c0 10.23 1.29 18.77 4.2 26.37q3.5-5.32 7.62-10.64zM330.34 239.74c-9.33 5.92-19 11.16-29.25 16.71-28.91 15.68-56.21 30.48-68.88 56.28a62.6 62.6 0 01-1.88 3.61 8 8 0 003.89 11.3c12.31 5.1 25.13 8.27 38.91 8.27a111.42 111.42 0 0078.24-31.37A107.45 107.45 0 00384 226.85a86.56 86.56 0 00-1.33-15 8 8 0 00-13.8-4c-10.18 11.47-22.93 22-38.53 31.89z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M202.24 74C166.11 56.75 115.61 48.3 48 48a31.36 31.36 0 00-17.92 5.33A32 32 0 0016 79.9V366c0 19.34 13.76 33.93 32 33.93 71.07 0 142.36 6.64 185.06 47a4.11 4.11 0 006.94-3V106.82a15.89 15.89 0 00-5.46-12A143 143 0 00202.24 74zM481.92 53.3A31.33 31.33 0 00464 48c-67.61.3-118.11 8.71-154.24 26a143.31 143.31 0 00-32.31 20.78 15.93 15.93 0 00-5.45 12v337.13a3.93 3.93 0 006.68 2.81c25.67-25.5 70.72-46.82 185.36-46.81a32 32 0 0032-32v-288a32 32 0 00-14.12-26.61z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M400 480a16 16 0 01-10.63-4L256 357.41 122.63 476A16 16 0 0196 464V96a64.07 64.07 0 0164-64h192a64.07 64.07 0 0164 64v368a16 16 0 01-16 16z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M400 0H176a64.11 64.11 0 00-62 48h228a74 74 0 0174 74v304.89l22 17.6a16 16 0 0019.34.5 16.41 16.41 0 006.66-13.42V64a64 64 0 00-64-64z" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M320 80H112a64 64 0 00-64 64v351.62A16.36 16.36 0 0054.6 509a16 16 0 0019.71-.71L216 388.92l141.69 119.32a16 16 0 0019.6.79 16.4 16.4 0 006.71-13.44V144a64 64 0 00-64-64z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M414.39 97.61A224 224 0 1097.61 414.39 224 224 0 10414.39 97.61zM288 224a24 24 0 1124-24 24 24 0 01-24 24zm8-72a24 24 0 1124-24 24 24 0 01-24 24zm64 40a24 24 0 1124-24 24 24 0 01-24 24z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M336 80H176a16 16 0 00-16 16v16h192V96a16 16 0 00-16-16z" android:fillColor="@android:color/transparent" />
|
||||
<path android:pathData="M496 176a64.07 64.07 0 00-64-64h-48V96a48.05 48.05 0 00-48-48H176a48.05 48.05 0 00-48 48v16H80a64.07 64.07 0 00-64 64v48h480zm-144-64H160V96a16 16 0 0116-16h160a16 16 0 0116 16zM336 264a24 24 0 01-24 24H200a24 24 0 01-24-24v-4a4 4 0 00-4-4H16v144a64 64 0 0064 64h352a64 64 0 0064-64V256H340a4 4 0 00-4 4z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M416 48H96a64 64 0 00-64 64v288a64 64 0 0064 64h320a64 64 0 0064-64V112a64 64 0 00-64-64zm24 96H72a8 8 0 01-8-8v-24a32.09 32.09 0 0132-32h320a32.09 32.09 0 0132 32v24a8 8 0 01-8 8z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M233.15 360.11a15.7 15.7 0 01-4.92-.77 16 16 0 01-10.92-13c-2.15-15-19.95-32.46-36.62-35.85a16 16 0 01-8.69-26.33l211.09-235.1c.19-.22.39-.43.59-.63a56.57 56.57 0 0179.89 0 56.51 56.51 0 01.11 79.78l-219 227a16 16 0 01-11.53 4.9zM119.89 480.11c-32.14 0-65.45-16.89-84.85-43a16 16 0 0112.85-25.54c5.34 0 20-4.87 20-20.57 0-39.07 31.4-70.86 70-70.86s70 31.79 70 70.86c0 49.12-39.48 89.11-88 89.11z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M463.55 272.13H400v-48.2q0-4.32-.27-8.47c29.57-27.88 32.25-64.63 32.27-103 0-8.61-6.64-16-15.25-16.41A16 16 0 00400 112c0 28-1.86 48.15-9.9 63.84-19.22-41.15-65.78-63.91-134.1-63.91-39.8 0-74.19 9.13-99.43 26.39-14.9 10.19-26.2 22.91-33.7 37.72C114 160.65 112 141 112 112.46c0-8.61-6.6-16-15.2-16.44A16 16 0 0080 112c0 37.63 2.61 73.73 32.44 101.63q-.43 5.06-.44 10.3v48.2H48.45c-8.61 0-16 6.62-16.43 15.23a16 16 0 0016 16.77h64V320a143.32 143.32 0 0010.39 53.69C96.74 396.64 80.18 422 80 463.34c0 8.74 6.62 16.3 15.36 16.65A16 16 0 00112 464c0-27.66 9.1-44.71 26.17-61.32A144.37 144.37 0 00220 459.42a16 16 0 0020-15.49V192.45c0-8.61 6.62-16 15.23-16.43A16 16 0 01272 192v251.93a16 16 0 0020 15.49 144.4 144.4 0 0081.82-56.74c17 16.54 26.09 33.52 26.17 60.95a16.27 16.27 0 0015.1 16.37A16 16 0 00432 464c0-41.68-16.6-67.23-42.39-90.31A143.32 143.32 0 00400 320v-15.87h64a16 16 0 0016-16.77c-.42-8.61-7.84-15.23-16.45-15.23z" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M321.39 104l.32.09c13.57 3.8 25.07-10.55 18.2-22.85A95.86 95.86 0 00256.21 32h-.42a95.87 95.87 0 00-84.19 50.13c-6.84 12.58 5.14 27 18.84 22.86 19.71-6 41.79-9.06 65.56-9.06 24.09 0 46.09 2.72 65.39 8.07z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M469.54 120.52a16 16 0 00-25.54-4L382.56 178a16.12 16.12 0 01-22.63 0l-26.56-26.6a16 16 0 010-22.63l61.18-61.19a16 16 0 00-4.78-25.92C343.56 21 285.88 31.78 249.51 67.88c-30.9 30.68-40.11 78.62-25.25 131.53a15.89 15.89 0 01-4.49 16L53.29 367.46a64.17 64.17 0 1090.6 90.64l153.68-166.85a15.9 15.9 0 0115.77-4.57 179.3 179.3 0 0046.22 6.37c33.4 0 62.71-10.81 83.85-31.64 39.15-38.57 45.12-103.99 26.13-140.89zM99.48 447.15a32 32 0 1128.34-28.35 32 32 0 01-28.34 28.35z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M288 464h-64a16 16 0 000 32h64a16 16 0 000-32zM304 416h-96a16 16 0 000 32h96a16 16 0 000-32zM369.42 62.69C339.35 32.58 299.07 16 256 16A159.62 159.62 0 0096 176c0 46.62 17.87 90.23 49 119.64l4.36 4.09C167.37 316.57 192 339.64 192 360v24a16 16 0 0016 16h24a8 8 0 008-8V274.82a8 8 0 00-5.13-7.47A130.73 130.73 0 01208.71 253a16 16 0 1118.58-26c7.4 5.24 21.65 13 28.71 13s21.31-7.78 28.73-13a16 16 0 0118.56 26 130.73 130.73 0 01-26.16 14.32 8 8 0 00-5.13 7.47V392a8 8 0 008 8h24a16 16 0 0016-16v-24c0-19.88 24.36-42.93 42.15-59.77l4.91-4.66C399.08 265 416 223.61 416 176a159.16 159.16 0 00-46.58-113.31z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M400 32H112a48 48 0 00-48 48v320a47.91 47.91 0 0016 35.74V454a26 26 0 0026 26h28a26 26 0 0026-26v-6h192v6a26 26 0 0026 26h28a26 26 0 0026-26v-18.26A47.91 47.91 0 00448 400V80a48 48 0 00-48-48zM147.47 399.82a32 32 0 1128.35-28.35 32 32 0 01-28.35 28.35zM236 288H112a16 16 0 01-16-16V144a16 16 0 0116-16h124a4 4 0 014 4v152a4 4 0 01-4 4zm20-192H112.46c-8.6 0-16-6.6-16.44-15.19A16 16 0 01112 64h287.54c8.6 0 16 6.6 16.44 15.19A16 16 0 01400 96H256zm20 32h124a16 16 0 0116 16v128a16 16 0 01-16 16H276a4 4 0 01-4-4V132a4 4 0 014-4zm60.18 243.47a32 32 0 1128.35 28.35 32 32 0 01-28.35-28.35z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M432 176H320V64a48 48 0 00-48-48H80a48 48 0 00-48 48v416a16 16 0 0016 16h104a8 8 0 008-8v-71.55c0-8.61 6.62-16 15.23-16.43A16 16 0 01192 416v72a8 8 0 008 8h264a16 16 0 0016-16V224a48 48 0 00-48-48zM98.08 431.87a16 16 0 1113.79-13.79 16 16 0 01-13.79 13.79zm0-80a16 16 0 1113.79-13.79 16 16 0 01-13.79 13.79zm0-80a16 16 0 1113.79-13.79 16 16 0 01-13.79 13.79zm0-80a16 16 0 1113.79-13.79 16 16 0 01-13.79 13.79zm0-80a16 16 0 1113.79-13.79 16 16 0 01-13.79 13.79zm80 240a16 16 0 1113.79-13.79 16 16 0 01-13.79 13.79zm0-80a16 16 0 1113.79-13.79 16 16 0 01-13.79 13.79zm0-80a16 16 0 1113.79-13.79 16 16 0 01-13.79 13.79zm0-80a16 16 0 1113.79-13.79 16 16 0 01-13.79 13.79zm80 320a16 16 0 1113.79-13.79 16 16 0 01-13.79 13.79zm0-80a16 16 0 1113.79-13.79 16 16 0 01-13.79 13.79zm0-80a16 16 0 1113.79-13.79 16 16 0 01-13.79 13.79zm0-80a16 16 0 1113.79-13.79 16 16 0 01-13.79 13.79zm0-80a16 16 0 1113.79-13.79 16 16 0 01-13.79 13.79zM444 464H320V208h112a16 16 0 0116 16v236a4 4 0 01-4 4z" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M400 400a16 16 0 1016 16 16 16 0 00-16-16zM400 320a16 16 0 1016 16 16 16 0 00-16-16zM400 240a16 16 0 1016 16 16 16 0 00-16-16zM336 400a16 16 0 1016 16 16 16 0 00-16-16zM336 320a16 16 0 1016 16 16 16 0 00-16-16zM336 240a16 16 0 1016 16 16 16 0 00-16-16z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M432 64H96a16 16 0 00-16 16v192a96.11 96.11 0 0096 96h112a96.11 96.11 0 0096-96v-80h18a62.07 62.07 0 0062-62V96a32 32 0 00-32-32zm0 66a30 30 0 01-30 30h-18V96h48zM400 400H64a16 16 0 000 32h336a16 16 0 000-32z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M416 80a48.05 48.05 0 00-48-48H144a48.05 48.05 0 00-48 48v352a48.05 48.05 0 0048 48h224a48.05 48.05 0 0048-48zM168 432a24 24 0 1124-24 24 24 0 01-24 24zm0-80a24 24 0 1124-24 24 24 0 01-24 24zm0-80a24 24 0 1124-24 24 24 0 01-24 24zm88 160a24 24 0 1124-24 24 24 0 01-24 24zm0-80a24 24 0 1124-24 24 24 0 01-24 24zm0-80a24 24 0 1124-24 24 24 0 01-24 24zm112 136a24 24 0 01-48 0v-80a24 24 0 0148 0zm-24-136a24 24 0 1124-24 24 24 0 01-24 24zm19.31-100.69A16 16 0 01352 176H160a16 16 0 01-16-16V96a16 16 0 0116-16h192a16 16 0 0116 16v64a16 16 0 01-4.69 11.31z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M480 128a64 64 0 00-64-64h-16V48.45c0-8.61-6.62-16-15.23-16.43A16 16 0 00368 48v16H144V48.45c0-8.61-6.62-16-15.23-16.43A16 16 0 00112 48v16H96a64 64 0 00-64 64v12a4 4 0 004 4h440a4 4 0 004-4zM32 416a64 64 0 0064 64h320a64 64 0 0064-64V179a3 3 0 00-3-3H35a3 3 0 00-3 3zm344-208a24 24 0 11-24 24 24 24 0 0124-24zm0 80a24 24 0 11-24 24 24 24 0 0124-24zm-80-80a24 24 0 11-24 24 24 24 0 0124-24zm0 80a24 24 0 11-24 24 24 24 0 0124-24zm0 80a24 24 0 11-24 24 24 24 0 0124-24zm-80-80a24 24 0 11-24 24 24 24 0 0124-24zm0 80a24 24 0 11-24 24 24 24 0 0124-24zm-80-80a24 24 0 11-24 24 24 24 0 0124-24zm0 80a24 24 0 11-24 24 24 24 0 0124-24z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M480 128a64 64 0 00-64-64h-16V48.45c0-8.61-6.62-16-15.23-16.43A16 16 0 00368 48v16H144V48.45c0-8.61-6.62-16-15.23-16.43A16 16 0 00112 48v16H96a64 64 0 00-64 64v12a4 4 0 004 4h440a4 4 0 004-4zM32 416a64 64 0 0064 64h320a64 64 0 0064-64V180a4 4 0 00-4-4H36a4 4 0 00-4 4z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M416 64h-16V48.45c0-8.61-6.62-16-15.23-16.43A16 16 0 00368 48v16H144V48.45c0-8.61-6.62-16-15.23-16.43A16 16 0 00112 48v16H96a64 64 0 00-64 64v12a4 4 0 004 4h440a4 4 0 004-4v-12a64 64 0 00-64-64zM476 176H36a4 4 0 00-4 4v236a64 64 0 0064 64h320a64 64 0 0064-64V180a4 4 0 00-4-4zM239.58 401.1c-12.17 9.61-28.75 14.9-46.7 14.9-27.87 0-48.48-18.16-57.66-33.7a16 16 0 0127.56-16.3c1.08 1.84 11.15 18 30.1 18 16.66 0 36.12-7.29 36.12-27.82 0-6.25-1.22-14.95-7-20.88-8.54-8.74-22.75-12.67-30.11-12.67a16 16 0 010-32c4.85 0 17.41-2.6 25.28-10.65a22 22 0 006.57-16.08c0-23.23-28.63-23.9-31.89-23.9-17.34 0-23.8 10.61-24.07 11.06a16 16 0 11-27.55-16.26c7.64-13 25.22-26.8 51.62-26.8 16.44 0 31.76 4.77 43.13 13.42 13.39 10.2 20.76 25.28 20.76 42.48A54 54 0 01240 302.35c-1.15 1.18-2.36 2.28-3.59 3.35a66.18 66.18 0 018.42 7.23c10.56 10.8 16.14 25.75 16.14 43.25.03 18.06-7.58 34.01-21.39 44.92zM368 396a16 16 0 01-32 0V256.29l-22.51 16.59a16 16 0 11-19-25.76l43.42-32a16 16 0 019.49-3.12h4.6a16 16 0 0116 16z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M391 480c-19.52 0-46.94-7.06-88-30-49.93-28-88.55-53.85-138.21-103.38C116.91 298.77 93.61 267.79 61 208.45c-36.84-67-30.56-102.12-23.54-117.13C45.82 73.38 58.16 62.65 74.11 52a176.3 176.3 0 0128.64-15.2c1-.43 1.93-.84 2.76-1.21 4.95-2.23 12.45-5.6 21.95-2 6.34 2.38 12 7.25 20.86 16 18.17 17.92 43 57.83 52.16 77.43 6.15 13.21 10.22 21.93 10.23 31.71 0 11.45-5.76 20.28-12.75 29.81-1.31 1.79-2.61 3.5-3.87 5.16-7.61 10-9.28 12.89-8.18 18.05 2.23 10.37 18.86 41.24 46.19 68.51s57.31 42.85 67.72 45.07c5.38 1.15 8.33-.59 18.65-8.47 1.48-1.13 3-2.3 4.59-3.47 10.66-7.93 19.08-13.54 30.26-13.54h.06c9.73 0 18.06 4.22 31.86 11.18 18 9.08 59.11 33.59 77.14 51.78 8.77 8.84 13.66 14.48 16.05 20.81 3.6 9.53.21 17-2 22-.37.83-.78 1.74-1.21 2.75a176.49 176.49 0 01-15.29 28.58c-10.63 15.9-21.4 28.21-39.38 36.58A67.42 67.42 0 01391 480z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M192,272a64,64 0 1,0 128,0a64,64 0 1,0 -128,0" android:fillColor="#FF000000" />
|
||||
<path android:pathData="M432 144h-59c-3 0-6.72-1.94-9.62-5l-25.94-40.94a15.52 15.52 0 00-1.37-1.85C327.11 85.76 315 80 302 80h-92c-13 0-25.11 5.76-34.07 16.21a15.52 15.52 0 00-1.37 1.85l-25.94 41c-2.22 2.42-5.34 5-8.62 5v-8a16 16 0 00-16-16h-24a16 16 0 00-16 16v8h-4a48.05 48.05 0 00-48 48V384a48.05 48.05 0 0048 48h352a48.05 48.05 0 0048-48V192a48.05 48.05 0 00-48-48zM256 368a96 96 0 1196-96 96.11 96.11 0 01-96 96z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M432 144h-59c-3 0-6.72-1.94-9.62-5l-25.94-40.94a15.52 15.52 0 00-1.37-1.85C327.11 85.76 315 80 302 80h-92c-13 0-25.11 5.76-34.07 16.21a15.52 15.52 0 00-1.37 1.85l-25.94 41c-2.22 2.42-5.34 5-8.62 5v-8a16 16 0 00-16-16h-24a16 16 0 00-16 16v8h-4a48.05 48.05 0 00-48 48V384a48.05 48.05 0 0048 48h352a48.05 48.05 0 0048-48V192a48.05 48.05 0 00-48-48zM316.84 346.3a96.06 96.06 0 01-155.66-59.18 16 16 0 01-16.49-26.43l20-20a16 16 0 0122.62 0l20 20A16 16 0 01196 288a17.31 17.31 0 01-2-.14 64.07 64.07 0 00102.66 33.63 16 16 0 1120.21 24.81zm50.47-63l-20 20a16 16 0 01-22.62 0l-20-20a16 16 0 0113.09-27.2A64 64 0 00215 222.64 16 16 0 11194.61 198a96 96 0 01156 59 16 16 0 0116.72 26.35z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M447.68 220.78a16 16 0 00-1-3.08l-37.78-88.16C400.19 109.17 379 96 354.89 96H157.11c-24.09 0-45.3 13.17-54 33.54L65.29 217.7A15.72 15.72 0 0064 224v176a16 16 0 0016 16h32a16 16 0 0016-16v-16h256v16a16 16 0 0016 16h32a16 16 0 0016-16V224a16.15 16.15 0 00-.32-3.22zM144 320a32 32 0 1132-32 32 32 0 01-32 32zm224 0a32 32 0 1132-32 32 32 0 01-32 32zM104.26 208l28.23-65.85C136.11 133.69 146 128 157.11 128h197.78c11.1 0 21 5.69 24.62 14.15L407.74 208z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M494.26 276.22c-3.6-40.41-9.53-48.28-11.77-51.24-5.15-6.84-13.39-11.31-22.11-16a3.6 3.6 0 01-.91-5.68 15.93 15.93 0 004.53-12.53A16.27 16.27 0 00447.65 176h-15.6a17 17 0 00-2 .13 8.5 8.5 0 00-1.41-.47c-9.24-19.53-21.89-46.27-48.11-59.32C341.64 97 270 96 256 96s-85.64 1-124.48 20.31c-26.22 13.05-38.87 39.79-48.11 59.32l-.08.16a6.52 6.52 0 00-1.35.34 17 17 0 00-2-.13H64.35A16.27 16.27 0 0048 190.77a15.93 15.93 0 004.59 12.47 3.6 3.6 0 01-.91 5.68c-8.72 4.72-17 9.19-22.11 16-2.24 3-8.16 10.83-11.77 51.24-2 22.74-2.3 46.28-.73 61.44 3.29 31.5 9.46 50.54 9.72 51.33a16 16 0 0013.2 10.87v.2a16 16 0 0016 16h56a16 16 0 0016-16c8.61 0 14.6-1.54 20.95-3.18a158.83 158.83 0 0128-4.91C207.45 389 237.79 388 256 388c17.84 0 49.52 1 80.08 3.91a159.16 159.16 0 0128.11 4.93c6.08 1.56 11.85 3 19.84 3.15a16 16 0 0016 16h56a16 16 0 0016-16v-.12A16 16 0 00485.27 389c.26-.79 6.43-19.83 9.72-51.33 1.57-15.17 1.29-38.67-.73-61.45zm-381.93-86.91c8-17 17.15-36.24 33.44-44.35 23.54-11.72 72.33-17 110.23-17s86.69 5.24 110.23 17c16.29 8.11 25.4 27.36 33.44 44.35l1 2.17a8 8 0 01-7.44 11.42C360 202 290 199.12 256 199.12s-104 2.95-137.28 3.85a8 8 0 01-7.44-11.42c.35-.74.72-1.49 1.05-2.24zm11.93 79.63A427.17 427.17 0 0172.42 272c-10.6 0-21.53-3-23.56-12.44-1.39-6.35-1.24-9.92-.49-13.51C49 243 50 240.78 55 240c13-2 20.27.51 41.55 6.78 14.11 4.15 24.29 9.68 30.09 14.06 2.91 2.16 1.36 7.8-2.38 8.1zm221.38 82c-13.16 1.5-39.48.95-89.34.95s-76.17.55-89.33-.95c-13.58-1.51-30.89-14.35-19.07-25.79 7.87-7.54 26.23-13.18 50.68-16.35s34.8-4.8 57.62-4.8 32.12 1 57.62 4.81 44.77 9.52 50.68 16.35c10.78 12.24-5.29 24.19-18.86 25.84zm117.5-91.39c-2 9.48-13 12.44-23.56 12.44a455.91 455.91 0 01-52.84-3.06c-3.06-.29-4.48-5.66-1.38-8.1 5.71-4.49 16-9.91 30.09-14.06 21.28-6.27 33.55-8.78 44.09-6.69 2.57.51 3.93 3.27 4.09 5a40.64 40.64 0 01-.49 14.48z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M32 376a56 56 0 0056 56h336a56 56 0 0056-56V222H32zm66-76a30 30 0 0130-30h48a30 30 0 0130 30v20a30 30 0 01-30 30h-48a30 30 0 01-30-30zM424 80H88a56 56 0 00-56 56v26h448v-26a56 56 0 00-56-56z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M321.94 98L158.82 237.78a24 24 0 000 36.44L321.94 414c15.57 13.34 39.62 2.28 39.62-18.22v-279.6c0-20.5-24.05-31.56-39.62-18.18z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M48 256c0 114.87 93.13 208 208 208s208-93.13 208-208S370.87 48 256 48 48 141.13 48 256zm252-74.14v148.28a16 16 0 01-26.23 12.29l-89.09-74.13a16 16 0 010-24.6l89.09-74.13A16 16 0 01300 181.86z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:pathData="M98 190.06l139.78 163.12a24 24 0 0036.44 0L414 190.06c13.34-15.57 2.28-39.62-18.22-39.62h-279.6c-20.5 0-31.56 24.05-18.18 39.62z" android:fillColor="#FF000000" />
|
||||
</vector>
|
||||
Loaded 100 of 419 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user