Add OSM search provider (#611)

* add openstreetmaps module in data

* fix injection

* retrofit2 implementation

* tokenization

* partial rewrite of OpeningTime.fromOverpassElement

* finish rewrite of OpeningTime.fromOverpassElement

* fix merge x)

* configurable search radius

* add settings section and disable setting explicit timeout values for http-client to alleviate issues during debugging

* settings screen localization

* enable radius slider only when locations are enabled

* fix dayRange parsing, add barebones UI

* add files to git

* add location listener in SearchableItemVM that gets activated by LocationItem

* add heading listener

* Calculations, UI additions

* rename settings to LocationsSettings

* use android location library for bearing calculations

* location fix

* rotation fix, demo UI

* working buttons for launching map and website (if available)

* finish botched UI

* improve overpass query by utilizing regex for fuzzy search results

* add link to documentation for further reference

* localization comments

* remove wikipedia minification setting

* schema version, default radius 1.5km

* move osm-specific opening-time parsing to OsmLocation

* refactor with callbackFlow

* remember flow and set minimum distance update to 1m

* refactor for replacementIcon, add imperial unit option

* 'open until' UI fix

* implement serializers

* catch errors in deserializer

* hacky live sorting by distance

* give max priority to bestmatch determined by SearchVM

* add yards as additional step to metersToLocalizzedString

* move http-client from serializers of osmlocation to companion object for cache updating

* add setting for custom URL

* round yards to int

* add botched map preview

* unbotch map tiles, draw user location in map (proof of concept)

* - create MapTiles Composable
- add border around map
- add indicators for location and userlocation, when on map

* fix default imperial units setting

* fix tint color

* add OSM attribution string

* display loading animation when tiles can't be shown yet

* create compose preview of maptiles

* UI work

* being glad that API's just return null instead of throwing information

* tryStartActivity

* aniimate card row placement

* Text alignment, padding

* Rotation -PI/PI wrap fix

* fix direction arrow rotation when screen is upside down

* more icons

* icons, settings, localization
- consider other tags than "amenity" when determining location category
- add many more location categories with corresponding icons
- add settings to disable map theming and hide search results with
  LocationCategory.OTHER
- add default localizations for settings

* catch errors when deserializing location category

* move location and heading functions to Context.kt in extensions

* fix hideUncategorized criterion

* add pre-sorting by distance for location results in SearchVM by injecting Context into search()

* specify receiver parameters in ktx.Context lambdas

* move pose logic and context dependency to new module devicepose with DevicePoseProvider

* git, add the frickin' module

* search overpass for nodes and ways
include category for parcel_locker
already start searching for queries extending length 2

* make openingTimes immutable

* OsmRepository changes
- include telephone number
- don't try to repeatedly update cache if there is no value to be
  updated to
- deduplicate results with same label by category and distance (100m)
- include fixmeurl to point to openstreetmaps.org/fixthemap

* ask for center in overpass API to compute center coordinates of ways

* search for brand

* add chemist location category

* restaurant / fastfood icon shenanigans

* actually add the icons :|

* add leisure tag for leisure:fitness_centre

* return to 'open until'/'opens in'/'open next'

* adding missing UI features
- bug report dialog
- call button if phone number exists
- grid item popup

* refactor to handle 24/7 locations more comfortably

* hide hours in 'opens_in' when they are zero

* show maptiles such that user is always in view

* drawing adjustments

* cache previous zoom level to speed up tile coordinate calculations

* using remember

* using MutableIntState

* fix logic that determines whether tiles are loading

* fix for numTiles == 9

* one plus one is two plus one makes three quick maths

* animate user location indicator, remember calculations

* fix off by one when determining next opening hour

* second attempt to fix upside down arrow rotation (probably fine now)

* logging

* reconsider declination, inject samplingPeriod

* undoing the merge undo

* move localization string to i18n

* revert reordering by distance

* refactor .distanceTo

* make Location abstract class to override compareTo with cached distance to correct sort order in search results

* when it is if when you could use when

* replace Pair with dataclass

* condition check order

* not creating objects with undefined locations, removing suspend from getCategory()

* inject permissionsmanager as constructor parameter

* Store OSM settings in decentralized datastore

* Update searchable content in database on launch

* Refactor, add mechanism to load updated searchable data lazily

* Cache all OSM data in launcher database

* Add pin to favorites button to location results

* Add sealed class UpdateResult
that is returned by awaiting updatedSelf of DeferredSearchable

- update on success
- set flag on temporarily unavailable (TODO add some UI indication)
- delete and invalidate VM on permanently unavailable (Display some
  message window to user?)

* Move sorting of Locations from OsmRepository
to SearchVM using cached location in DevicePoseProvider, if available

* make use of cached location in code

* make use of DevicePoseProvider in WeatherRepository

* inject via koin

* increase getLocation().timeout() to 10 minutes since we are asking for locations only every hour, so 10 minutes seem reasonable (?)

* poll new location every time

* add icon for cached results where results are temporarily unavailable

* Refactor DeferredSearchable to UpdatableSearchable
that receives a closure to retrieve an updated self.

- moved timestamp (formerly `updatedAt`) to UpdatableSearchable
- moved logic whether to update searchable to `requestUpdatedSearchable`
  in SearchableItemVM, which gets triggered every time the details of
  the item are shown
- keep track in SearchableVM whether we should retry updating, possibly
  bypassing a timestamp value that is not old enough
- show toast upon permanently unavailable
- animate "cached_searchable" icon
- make "cached_searchable" icon clickable to show toast explaining the
  situation

* logging on PermanentlyUnavailable

* refactor OsmRepository.update()

* MapTheming adjustments. There is now darkmode, hooray!

* remove outdated comment

* code tidying

* remove unnecessary LaunchedEffect

* make outdated badge only clickable when actually outdated

* deserialize opening schedule

* set deserialized props to null if strings are blank

* also consider contact:* tagging scheme for website & phone

* tweaks

* git add Result.kt

* don't search for locations if network is not allowed

* merge fixes

* Move location search settings to preferences module

* Change wording and order of location search preferences

* Limit location search results

* Order location search results by distance

* Use a sequence

* Android Studio's suggestion wasn't as fleshed out as one would hope

* Add proguard rules

* Rename TileMapRepository to MapTileLoader

---------

Co-authored-by: MM20 <15646950+MM2-0@users.noreply.github.com>
This commit is contained in:
Christoph 2024-03-26 19:06:19 +01:00 committed by GitHub
parent 4b30b13266
commit cba387c832
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
133 changed files with 4123 additions and 79 deletions

View File

@ -4,30 +4,39 @@
<inspection_tool class="AndroidLintNewerVersionAvailable" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
</profile>
</component>

View File

@ -13,7 +13,7 @@ android {
}
packaging {
//resources.excludes.add("META-INF/DEPENDENCIES")
resources.excludes.add("META-INF/DEPENDENCIES")
resources.excludes.add("META-INF/LICENSE")
resources.excludes.add("META-INF/LICENSE.txt")
resources.excludes.add("META-INF/license.txt")
@ -166,7 +166,9 @@ dependencies {
implementation(project(":services:global-actions"))
implementation(project(":services:widgets"))
implementation(project(":services:favorites"))
implementation(project(":data:openstreetmaps"))
implementation(project(":services:plugins"))
implementation(project(":core:devicepose"))
// Uncomment this if you want annoying notifications in your debug builds yelling at you how terrible your code is
//debugImplementation(libs.leakcanary)

View File

@ -26,8 +26,10 @@ import de.mm20.launcher2.database.databaseModule
import de.mm20.launcher2.debug.initDebugMode
import de.mm20.launcher2.globalactions.globalActionsModule
import de.mm20.launcher2.notifications.notificationsModule
import de.mm20.launcher2.openstreetmaps.openStreetMapsModule
import de.mm20.launcher2.permissions.permissionsModule
import de.mm20.launcher2.data.plugins.dataPluginsModule
import de.mm20.launcher2.devicepose.devicePoseModule
import de.mm20.launcher2.plugins.servicesPluginsModule
import de.mm20.launcher2.preferences.preferencesModule
import de.mm20.launcher2.searchactions.searchActionsModule
@ -86,11 +88,13 @@ class LauncherApplication : Application(), CoroutineScope, ImageLoaderFactory {
websitesModule,
widgetsModule,
wikipediaModule,
openStreetMapsModule,
servicesTagsModule,
widgetsServiceModule,
dataPluginsModule,
servicesPluginsModule,
backupModule,
devicePoseModule,
)
)
}

View File

@ -6,6 +6,10 @@ plugins {
android {
compileSdk = libs.versions.compileSdk.get().toInt()
packaging {
resources.excludes.add("META-INF/DEPENDENCIES")
}
defaultConfig {
minSdk = libs.versions.minSdk.get().toInt()
@ -136,6 +140,7 @@ dependencies {
implementation(project(":core:crashreporter"))
implementation(project(":data:notifications"))
implementation(project(":data:contacts"))
implementation(project(":data:openstreetmaps"))
implementation(project(":core:permissions"))
implementation(project(":data:websites"))
implementation(project(":data:unitconverter"))
@ -149,4 +154,5 @@ dependencies {
implementation(project(":services:global-actions"))
implementation(project(":services:widgets"))
implementation(project(":services:favorites"))
implementation(project(":core:devicepose"))
}

View File

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application>

View File

@ -0,0 +1,28 @@
package de.mm20.launcher2.ui.animation
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.BiasAlignment
@Composable
fun animateHorizontalAlignmentAsState(
targetAlignment: Alignment.Horizontal,
animationSpec: AnimationSpec<Float> = tween()
): State<BiasAlignment.Horizontal> {
val bias by animateFloatAsState(
targetValue = when (targetAlignment) {
Alignment.Start -> -1f
Alignment.End -> 1f
else -> 0f
},
animationSpec = animationSpec
)
return remember { derivedStateOf { BiasAlignment.Horizontal(bias) } }
}

View File

@ -2,6 +2,7 @@ package de.mm20.launcher2.ui.ktx
import androidx.compose.ui.graphics.Color
import hct.Hct
import kotlin.math.atan2
import kotlin.math.roundToInt
fun Color.toHexString(): String {
@ -17,4 +18,13 @@ fun Color.toHexString(): String {
fun Color.Companion.hct(hue: Float, chroma: Float, tone: Float): Color {
val hct = Hct.from(hue.toDouble(), chroma.toDouble(), tone.toDouble())
return Color(hct.toInt())
}
}
val Color.hue: Float
get() {
val r = this.red / 255f
val g = this.green / 255f
val b = this.blue / 255f
// sqrt(3)
return atan2(1.7320508f * (g - b), 2f * r - g - b)
}

View File

@ -0,0 +1,78 @@
package de.mm20.launcher2.ui.ktx
import androidx.compose.ui.graphics.ColorMatrix
import de.mm20.launcher2.ktx.PI
import kotlin.math.cos
import kotlin.math.sin
fun ColorMatrix.invert(fraction: Float, withAlpha: Boolean = false): ColorMatrix {
assert(fraction in 0f..1f)
val scale = -2f * fraction + 1f
this *= ColorMatrix().apply {
setToScale(scale, scale, scale, if (withAlpha) scale else 1f)
this[0, 4] = 255f
this[1, 4] = 255f
this[2, 4] = 255f
}
return this
}
// https://chromium.googlesource.com/chromium/blink/+/master/Source/platform/graphics/filters/FEColorMatrix.cpp#93
fun ColorMatrix.hueRotate(deg: Float): ColorMatrix {
val cosHue = cos(deg * Float.PI / 180f)
val sinHue = sin(deg * Float.PI / 180f)
val mat = FloatArray(20)
mat[0] = 0.213f + cosHue * 0.787f - sinHue * 0.213f
mat[1] = 0.715f - cosHue * 0.715f - sinHue * 0.715f
mat[2] = 0.072f - cosHue * 0.072f + sinHue * 0.928f
mat[5] = 0.213f - cosHue * 0.213f + sinHue * 0.143f
mat[6] = 0.715f + cosHue * 0.285f + sinHue * 0.140f
mat[7] = 0.072f - cosHue * 0.072f - sinHue * 0.283f
mat[10] = 0.213f - cosHue * 0.213f - sinHue * 0.787f
mat[11] = 0.715f - cosHue * 0.715f + sinHue * 0.715f
mat[12] = 0.072f + cosHue * 0.928f + sinHue * 0.072f
mat[18] = 1f
this *= ColorMatrix(mat)
return this
}
fun ColorMatrix.contrast(contrast: Float): ColorMatrix {
this *= ColorMatrix(
floatArrayOf(
contrast, 0f, 0f, 0f, 0f,
0f, contrast, 0f, 0f, 0f,
0f, 0f, contrast, 0f, 0f,
0f, 0f, 0f, 1f, 0f
)
)
return this
}
// https://github.com/darkreader/darkreader/blob/b1bbaa74b6bd460556ab0c2a8d8e20c562e05e9b/src/generators/utils/matrix.ts#L75
fun ColorMatrix.sepia(amount: Float): ColorMatrix {
this *= ColorMatrix(
floatArrayOf(
0.393f + 0.607f * (1f - amount),
0.769f - 0.769f * (1f - amount),
0.189f - 0.189f * (1f - amount),
0f,
0f,
0.349f - 0.349f * (1f - amount),
0.686f + 0.314f * (1 - amount),
0.168f - 0.168f * (1f - amount),
0f,
0f,
0.272f - 0.272f * (1f - amount),
0.534f - 0.534f * (1 - amount),
0.131f + 0.869f * (1f - amount),
0f,
0f,
0f,
0f,
0f,
1f,
0f,
)
)
return this
}

View File

@ -0,0 +1,15 @@
package de.mm20.launcher2.ui.ktx
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.produceState
import kotlinx.coroutines.Deferred
@Composable
fun <T> Deferred<T>?.asState(initialValue: T): State<T> {
return produceState(initialValue) {
if (this@asState != null) {
value = await()
}
}
}

View File

@ -1,10 +1,19 @@
package de.mm20.launcher2.ui.ktx
import android.content.Context
import androidx.compose.animation.core.AnimationVector2D
import androidx.compose.animation.core.TwoWayConverter
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlin.math.PI
import de.mm20.launcher2.ktx.PI
import de.mm20.launcher2.ui.R
import java.text.DecimalFormat
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.roundToInt
import kotlin.math.sin
/**
* Converts the given pixel size to a Dp value based on the current density
@ -13,3 +22,52 @@ import kotlin.math.PI
fun Float.toDp(): Dp {
return (this / LocalDensity.current.density).dp
}
fun Float.roundToString(): String = this.roundToInt().toString()
fun Float.metersToLocalizedString(context: Context, imperialUnits: Boolean): String {
val decimalFormat =
DecimalFormat().apply { maximumFractionDigits = 1; minimumFractionDigits = 0 }
val (value, unit) = if (imperialUnits) {
// yee haw
val asFeet = this * 3.28084f
val isYards = asFeet >= 3f
val isMiles = asFeet >= 5280f
val value =
if (isMiles) decimalFormat.format(asFeet / 5280f)
else if (isYards) (asFeet / 3f).roundToString()
else asFeet.roundToString()
val unit = context.getString(
if (isMiles) R.string.unit_mile_symbol
else if (isYards) R.string.unit_yard_symbol
else R.string.unit_foot_symbol
)
value to unit
} else {
val isKm = this >= 1000f
val value =
if (isKm) decimalFormat.format(this / 1000f)
else this.roundToString()
val unit = context.getString(
if (isKm) R.string.unit_kilometer_symbol
else R.string.unit_meter_symbol
)
value to unit
}
return "$value $unit"
}
// https://stackoverflow.com/a/68651222
val Float.Companion.DegreesConverter
get() = TwoWayConverter<Float, AnimationVector2D>({
val rad = it * Float.PI / 180f
AnimationVector2D(sin(rad), cos(rad))
}, {
(atan2(it.v1, it.v2) * 180f / Float.PI + 360f) % 360f
})

View File

@ -2,10 +2,9 @@ package de.mm20.launcher2.ui.ktx
import androidx.compose.ui.Modifier
fun Modifier.conditional(condition: Boolean, other: Modifier): Modifier {
if (condition) {
return this then other
}
return this
}
}

View File

@ -28,6 +28,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@ -38,6 +39,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.search.Location
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.common.FavoritesTagSelector
@ -60,6 +62,9 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlin.math.ceil
private const val PRIORITY_MIN = Int.MAX_VALUE
private const val PRIORITY_MAX = Int.MIN_VALUE
@Composable
fun SearchColumn(
modifier: Modifier = Modifier,
@ -90,6 +95,7 @@ fun SearchColumn(
val unitConverter by viewModel.unitConverterResults
val calculator by viewModel.calculatorResults
val wikipedia by viewModel.articleResults
val locations by viewModel.locationResults
val website by viewModel.websiteResults
val hiddenResults by viewModel.hiddenResults
@ -100,6 +106,7 @@ fun SearchColumn(
val missingCalendarPermission by viewModel.missingCalendarPermission.collectAsState(false)
val missingShortcutsPermission by viewModel.missingAppShortcutPermission.collectAsState(false)
val missingContactsPermission by viewModel.missingContactsPermission.collectAsState(false)
val missingLocationPermission by viewModel.missingLocationPermission.collectAsState(false)
val missingFilesPermission by viewModel.missingFilesPermission.collectAsState(false)
val pinnedTags by favoritesVM.pinnedTags.collectAsState(emptyList())
@ -295,6 +302,30 @@ fun SearchColumn(
key = "contacts",
highlightedItem = bestMatch as? SavableSearchable
)
ListResults(
before = if (missingLocationPermission && !isSearchEmpty) {
{
MissingPermissionBanner(
modifier = Modifier.padding(8.dp),
text = stringResource(R.string.missing_permission_location_search),
onClick = { viewModel.requestLocationPermission(context as AppCompatActivity) },
secondaryAction = {
OutlinedButton(onClick = {
viewModel.disableLocationSearch()
}) {
Text(
stringResource(R.string.turn_off),
)
}
}
)
}
} else null,
items = locations.toImmutableList(),
reverse = reverse,
key = "locations",
highlightedItem = bestMatch as? SavableSearchable
)
for (wiki in wikipedia) {
SingleResult(highlight = bestMatch == wiki) {
ArticleItem(article = wiki)
@ -436,7 +467,7 @@ fun LazyListScope.ListResults(
key: String,
before: (@Composable () -> Unit)? = null,
after: (@Composable () -> Unit)? = null,
highlightedItem: SavableSearchable?
highlightedItem: SavableSearchable?,
) {
if (before != null) {
item(key = "$key-before") {

View File

@ -5,14 +5,15 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.devicepose.DevicePoseProvider
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LegacySettings.SearchResultOrderingSettings.Ordering
import de.mm20.launcher2.preferences.SearchResultOrder
import de.mm20.launcher2.preferences.search.CalendarSearchSettings
import de.mm20.launcher2.preferences.search.ContactSearchSettings
import de.mm20.launcher2.preferences.search.FavoritesSettings
import de.mm20.launcher2.preferences.search.FileSearchSettings
import de.mm20.launcher2.preferences.search.LocationSearchSettings
import de.mm20.launcher2.preferences.search.ShortcutSearchSettings
import de.mm20.launcher2.preferences.ui.SearchUiSettings
import de.mm20.launcher2.search.AppProfile
@ -25,6 +26,7 @@ import de.mm20.launcher2.search.File
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.SearchService
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.Location
import de.mm20.launcher2.search.Website
import de.mm20.launcher2.search.data.Calculator
import de.mm20.launcher2.search.data.UnitConverter
@ -37,6 +39,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
@ -57,6 +60,8 @@ class SearchVM : ViewModel(), KoinComponent {
private val calendarSearchSettings: CalendarSearchSettings by inject()
private val shortcutSearchSettings: ShortcutSearchSettings by inject()
private val searchUiSettings: SearchUiSettings by inject()
private val locationSearchSettings: LocationSearchSettings by inject()
private val devicePoseProvider: DevicePoseProvider by inject()
val launchOnEnter = searchUiSettings.launchOnEnter
.stateIn(viewModelScope, SharingStarted.Eagerly, false)
@ -66,6 +71,7 @@ class SearchVM : ViewModel(), KoinComponent {
val searchQuery = mutableStateOf("")
val isSearchEmpty = mutableStateOf(true)
val locationResults = mutableStateOf<List<Location>>(emptyList())
val appResults = mutableStateOf<List<Application>>(emptyList())
val workAppResults = mutableStateOf<List<Application>>(emptyList())
val appShortcutResults = mutableStateOf<List<AppShortcut>>(emptyList())
@ -94,7 +100,7 @@ class SearchVM : ViewModel(), KoinComponent {
val bestMatch = mutableStateOf<Searchable?>(null)
init {
search("", true)
search("", forceRestart = true)
}
fun launchBestMatchOrAction(context: Context) {
@ -138,6 +144,7 @@ class SearchVM : ViewModel(), KoinComponent {
results.files,
results.contacts,
results.calendars,
results.locations,
results.wikipedia,
results.websites,
results.calculators,
@ -169,6 +176,11 @@ class SearchVM : ViewModel(), KoinComponent {
resultsList = resultsList.sortedWith { a, b ->
when {
a is Location && b is Location && devicePoseProvider.lastLocation != null -> {
a.distanceTo(devicePoseProvider.lastLocation!!)
.compareTo(b.distanceTo(devicePoseProvider.lastLocation!!))
}
a is SavableSearchable && b !is SavableSearchable -> -1
a !is SavableSearchable && b is SavableSearchable -> 1
a is SavableSearchable && b is SavableSearchable -> {
@ -200,6 +212,7 @@ class SearchVM : ViewModel(), KoinComponent {
val unitConv = mutableListOf<UnitConverter>()
val calc = mutableListOf<Calculator>()
val articles = mutableListOf<Article>()
val locations = mutableListOf<Location>()
val website = mutableListOf<Website>()
val actions = mutableListOf<SearchAction>()
for (r in resultsList) {
@ -218,6 +231,7 @@ class SearchVM : ViewModel(), KoinComponent {
r is Calculator -> calc.add(r)
r is Website -> website.add(r)
r is Article -> articles.add(r)
r is Location -> locations.add(r)
r is SearchAction -> actions.add(r)
}
}
@ -230,6 +244,7 @@ class SearchVM : ViewModel(), KoinComponent {
unitConv,
calc,
events,
locations,
contacts,
articles,
website,
@ -245,6 +260,7 @@ class SearchVM : ViewModel(), KoinComponent {
contactResults.value = contacts
calendarResults.value = events
articleResults.value = articles
locationResults.value = locations
websiteResults.value = website
calculatorResults.value = calc
unitConverterResults.value = unitConv
@ -282,6 +298,19 @@ class SearchVM : ViewModel(), KoinComponent {
contactSearchSettings.setEnabled(false)
}
val missingLocationPermission = combine(
permissionsManager.hasPermission(PermissionGroup.Location),
locationSearchSettings.enabled.distinctUntilChanged()
) { perm, enabled -> !perm && enabled }
fun requestLocationPermission(context: AppCompatActivity) {
permissionsManager.requestPermission(context, PermissionGroup.Location)
}
fun disableLocationSearch() {
locationSearchSettings.setEnabled(false)
}
val missingFilesPermission = combine(
permissionsManager.hasPermission(PermissionGroup.ExternalStorage),
fileSearchSettings.localFiles

View File

@ -1,28 +1,32 @@
package de.mm20.launcher2.ui.launcher.search.common
import android.content.Context
import android.content.pm.LauncherApps
import android.content.pm.ShortcutInfo
import android.graphics.drawable.Drawable
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.ui.geometry.Rect
import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.getSystemService
import de.mm20.launcher2.appshortcuts.AppShortcutRepository
import de.mm20.launcher2.badges.BadgeService
import de.mm20.launcher2.devicepose.DevicePoseProvider
import de.mm20.launcher2.icons.IconService
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.notifications.Notification
import de.mm20.launcher2.notifications.NotificationRepository
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.search.File
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.preferences.search.LocationSearchSettings
import de.mm20.launcher2.search.AppShortcut
import de.mm20.launcher2.search.Application
import de.mm20.launcher2.search.File
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.UpdatableSearchable
import de.mm20.launcher2.search.UpdateResult
import de.mm20.launcher2.services.favorites.FavoritesService
import de.mm20.launcher2.services.tags.TagsService
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.launcher.search.ListItemViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@ -35,7 +39,9 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import kotlin.time.Duration.Companion.hours
@OptIn(ExperimentalCoroutinesApi::class)
class SearchableItemVM : ListItemViewModel(), KoinComponent {
private val favoritesService: FavoritesService by inject()
private val badgeService: BadgeService by inject()
@ -44,9 +50,14 @@ class SearchableItemVM : ListItemViewModel(), KoinComponent {
private val notificationRepository: NotificationRepository by inject()
private val appShortcutRepository: AppShortcutRepository by inject()
private val permissionsManager: PermissionsManager by inject()
private val locationSearchSettings: LocationSearchSettings by inject()
private val searchable = MutableStateFlow<SavableSearchable?>(null)
private val iconSize = MutableStateFlow<Int>(0)
val isUpToDate = MutableStateFlow(true)
val devicePoseProvider: DevicePoseProvider by inject()
val searchable = MutableStateFlow<SavableSearchable?>(null)
private val iconSize = MutableStateFlow(0)
fun init(searchable: SavableSearchable, iconSize: Int) {
this.searchable.value = searchable
this.iconSize.value = iconSize
@ -104,7 +115,7 @@ class SearchableItemVM : ListItemViewModel(), KoinComponent {
).first()
}
open fun launch(context: Context, bounds: Rect? = null): Boolean {
fun launch(context: Context, bounds: Rect? = null): Boolean {
val searchable = searchable.value ?: return false
val view = (context as? AppCompatActivity)?.window?.decorView
val options = if (bounds != null && view != null) {
@ -167,7 +178,63 @@ class SearchableItemVM : ListItemViewModel(), KoinComponent {
favoritesService.reset(searchable)
}
private var shouldRetryUpdate = false
fun requestUpdatedSearchable(context: Context) {
val searchable = searchable.value ?: return
if (searchable is UpdatableSearchable<*>) {
val updatedSelf = searchable.updatedSelf ?: return
if (!shouldRetryUpdate && System.currentTimeMillis() < searchable.timestamp + 1.hours.inWholeMilliseconds) return
viewModelScope.launch {
this@SearchableItemVM.searchable.value = with(updatedSelf()) {
when (this) {
is UpdateResult.Success -> {
isUpToDate.value = true
shouldRetryUpdate = false
favoritesService.upsert(this.result)
this.result
}
is UpdateResult.TemporarilyUnavailable -> {
isUpToDate.value = false
shouldRetryUpdate = true
return@launch
}
is UpdateResult.PermanentlyUnavailable -> {
isUpToDate.value = false
shouldRetryUpdate = false
favoritesService.delete(searchable)
Toast.makeText(
context,
R.string.unavailable_searchable,
Toast.LENGTH_LONG
).show()
Log.d("requestUpdatedSearchable", "PermanentlyUnavailable", this.cause)
null
}
}
}
}
}
}
fun requestShortcutPermission(activity: AppCompatActivity) {
permissionsManager.requestPermission(activity, PermissionGroup.AppShortcuts)
}
val useInsaneUnits = locationSearchSettings.imperialUnits
.stateIn(viewModelScope, SharingStarted.Lazily, false)
val showMap = locationSearchSettings.showMap
.stateIn(viewModelScope, SharingStarted.Lazily, false)
val applyMapTheming = locationSearchSettings.themeMap
.stateIn(viewModelScope, SharingStarted.Lazily, false)
val showPositionOnMap = locationSearchSettings.showPositionOnMap
.stateIn(viewModelScope, SharingStarted.Lazily, false)
val mapTileServerUrl = locationSearchSettings.tileServer
.stateIn(viewModelScope, SharingStarted.Lazily, "")
}

View File

@ -23,6 +23,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -50,6 +51,7 @@ import de.mm20.launcher2.search.AppShortcut
import de.mm20.launcher2.search.Application
import de.mm20.launcher2.search.Article
import de.mm20.launcher2.search.CalendarEvent
import de.mm20.launcher2.search.Location
import de.mm20.launcher2.search.Website
import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.component.LocalIconShape
@ -61,6 +63,7 @@ import de.mm20.launcher2.ui.launcher.search.common.SearchableItemVM
import de.mm20.launcher2.ui.launcher.search.contacts.ContactItemGridPopup
import de.mm20.launcher2.ui.launcher.search.files.FileItemGridPopup
import de.mm20.launcher2.ui.launcher.search.listItemViewModel
import de.mm20.launcher2.ui.launcher.search.location.LocationItemGridPopup
import de.mm20.launcher2.ui.launcher.search.shortcut.ShortcutItemGridPopup
import de.mm20.launcher2.ui.launcher.search.website.WebsiteItemGridPopup
import de.mm20.launcher2.ui.launcher.search.wikipedia.ArticleItemGridPopup
@ -86,6 +89,8 @@ fun GridItem(
viewModel.init(item, iconSize.toInt())
}
val item = viewModel.searchable.collectAsState().value ?: item
val context = LocalContext.current
var showPopup by remember(item.key) { mutableStateOf(false) }
@ -94,6 +99,10 @@ fun GridItem(
val launchOnPress = !item.preferDetailsOverLaunch
val hapticFeedback = LocalHapticFeedback.current
LaunchedEffect(showPopup) {
if (showPopup) viewModel.requestUpdatedSearchable(context)
}
Column(
modifier = modifier
.combinedClickable(
@ -212,7 +221,7 @@ fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit
.imePadding()
.padding(horizontal = 16.dp)
.then(
if (show.targetState ) {
if (show.targetState) {
Modifier.pointerInput(Unit) {
detectTapGestures(onPress = {
show.targetState = false
@ -319,6 +328,18 @@ fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit
}
)
}
is Location -> {
LocationItemGridPopup(
location = searchable,
show = show,
animationProgress = animationProgress.value,
origin = origin,
onDismiss = {
show.targetState = false
}
)
}
}
}
}

View File

@ -13,6 +13,7 @@ import de.mm20.launcher2.search.AppShortcut
import de.mm20.launcher2.search.CalendarEvent
import de.mm20.launcher2.search.Contact
import de.mm20.launcher2.search.File
import de.mm20.launcher2.search.Location
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.component.InnerCard
import de.mm20.launcher2.ui.ktx.toPixels
@ -21,6 +22,7 @@ import de.mm20.launcher2.ui.launcher.search.common.SearchableItemVM
import de.mm20.launcher2.ui.launcher.search.contacts.ContactItem
import de.mm20.launcher2.ui.launcher.search.files.FileItem
import de.mm20.launcher2.ui.launcher.search.listItemViewModel
import de.mm20.launcher2.ui.launcher.search.location.LocationItem
import de.mm20.launcher2.ui.launcher.search.shortcut.AppShortcutItem
import de.mm20.launcher2.ui.locals.LocalGridSettings
@ -39,6 +41,12 @@ fun ListItem(
LaunchedEffect(item, iconSize) {
viewModel.init(item, iconSize.toInt())
}
LaunchedEffect(showDetails) {
if (showDetails) viewModel.requestUpdatedSearchable(context)
}
val item = viewModel.searchable.collectAsState().value ?: item
var bounds by remember { mutableStateOf(Rect.Zero) }
InnerCard(
@ -64,52 +72,69 @@ fun ListItem(
onBack = { showDetails = false }
)
}
is File -> {
FileItem(
modifier = Modifier
.fillMaxWidth()
.combinedClickable(
enabled = !showDetails,
onClick = {
if (!viewModel.launch(context, bounds)) {
showDetails = true
}
},
onLongClick = { showDetails = true }
),
enabled = !showDetails,
onClick = {
if (!viewModel.launch(context, bounds)) {
showDetails = true
}
},
onLongClick = { showDetails = true }
),
file = item,
showDetails = showDetails,
onBack = { showDetails = false }
)
}
is CalendarEvent -> {
CalendarItem(
modifier = Modifier
.fillMaxWidth()
.combinedClickable(
enabled = !showDetails,
onClick = { showDetails = true },
onLongClick = { showDetails = true }
),
enabled = !showDetails,
onClick = { showDetails = true },
onLongClick = { showDetails = true }
),
calendar = item,
showDetails = showDetails,
onBack = { showDetails = false }
)
}
is Location -> {
LocationItem(
modifier = Modifier
.fillMaxWidth()
.combinedClickable(
enabled = !showDetails,
onClick = { showDetails = true },
onLongClick = { showDetails = true }),
location = item,
showDetails = showDetails,
onBack = { showDetails = false }
)
}
is AppShortcut -> {
AppShortcutItem(
shortcut = item,
modifier = Modifier
.fillMaxWidth()
.combinedClickable(
enabled = !showDetails,
onClick = {
if (!viewModel.launch(context, bounds)) {
showDetails = true
}
},
onLongClick = { showDetails = true }
),
enabled = !showDetails,
onClick = {
if (!viewModel.launch(context, bounds)) {
showDetails = true
}
},
onLongClick = { showDetails = true }
),
showDetails = showDetails,
onBack = { showDetails = false }
)

View File

@ -0,0 +1,569 @@
package de.mm20.launcher2.ui.launcher.search.location
import android.content.Intent
import android.net.Uri
import android.widget.Toast
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.EaseOutBack
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.animateValueAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandIn
import androidx.compose.animation.shrinkOut
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material.icons.rounded.ArrowUpward
import androidx.compose.material.icons.rounded.BugReport
import androidx.compose.material.icons.rounded.Map
import androidx.compose.material.icons.rounded.Phone
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material.icons.rounded.StarOutline
import androidx.compose.material.icons.rounded.TravelExplore
import androidx.compose.material.icons.twotone.CloudOff
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.roundToIntRect
import androidx.compose.ui.unit.times
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.mm20.launcher2.i18n.R
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.search.Location
import de.mm20.launcher2.ui.animation.animateHorizontalAlignmentAsState
import de.mm20.launcher2.ui.animation.animateTextStyleAsState
import de.mm20.launcher2.ui.component.DefaultToolbarAction
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
import de.mm20.launcher2.ui.component.Toolbar
import de.mm20.launcher2.ui.component.ToolbarAction
import de.mm20.launcher2.ui.ktx.DegreesConverter
import de.mm20.launcher2.ui.ktx.metersToLocalizedString
import de.mm20.launcher2.ui.ktx.toPixels
import de.mm20.launcher2.ui.launcher.search.common.SearchableItemVM
import de.mm20.launcher2.ui.launcher.search.listItemViewModel
import de.mm20.launcher2.ui.locals.LocalFavoritesEnabled
import de.mm20.launcher2.ui.locals.LocalGridSettings
import de.mm20.launcher2.ui.modifier.scale
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.launch
import java.time.DayOfWeek
import java.time.Duration
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.time.format.TextStyle
import kotlin.math.pow
@Composable
fun LocationItem(
modifier: Modifier = Modifier,
location: Location,
showDetails: Boolean,
onBack: () -> Unit,
) {
val context = LocalContext.current
val viewModel: SearchableItemVM = listItemViewModel(key = "search-${location.key}")
val iconSize = LocalGridSettings.current.iconSize.dp.toPixels()
val userLocation by remember {
viewModel.devicePoseProvider.getLocation()
}.collectAsStateWithLifecycle(viewModel.devicePoseProvider.lastLocation)
val insaneUnits by viewModel.useInsaneUnits.collectAsState()
val isUpToDate by viewModel.isUpToDate.collectAsState()
val distance = userLocation?.distanceTo(location.toAndroidLocation())
var showBugreportDialog by remember { mutableStateOf(false) }
Row(modifier = modifier) {
Column {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp, bottom = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Column(
modifier = Modifier.padding(start = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
val icon by viewModel.icon.collectAsStateWithLifecycle()
val badge by viewModel.badge.collectAsState(null)
Box(
modifier = Modifier
.size(52.dp)
.aspectRatio(1f)
) {
ShapedLauncherIcon(
size = 48.dp,
icon = { icon },
badge = { badge },
)
val targetIconAnimationValue = if (isUpToDate) 0f else 1f
val animatedIconAlpha by animateFloatAsState(
targetValue = targetIconAnimationValue,
animationSpec = tween(delayMillis = 275)
)
val animatedIconSize by animateDpAsState(
targetValue = targetIconAnimationValue * 20.dp,
animationSpec = tween(delayMillis = 275, easing = EaseOutBack)
)
Box(
Modifier
.size(22.dp)
.align(Alignment.BottomEnd)
) {
Icon(
modifier = Modifier
.size(animatedIconSize)
.alpha(animatedIconAlpha)
.align(Alignment.Center)
.clickable(!isUpToDate) {
Toast
.makeText(
context,
R.string.cached_searchable,
Toast.LENGTH_SHORT
)
.show()
},
imageVector = Icons.TwoTone.CloudOff,
contentDescription = null
)
}
}
}
Column(
modifier = Modifier.fillMaxWidth(.75f),
horizontalAlignment = Alignment.CenterHorizontally,
) {
val textStyle by animateTextStyleAsState(
if (showDetails) MaterialTheme.typography.titleMedium
else MaterialTheme.typography.titleSmall
)
val titleAlignment by animateHorizontalAlignmentAsState(
targetAlignment = if (showDetails) Alignment.CenterHorizontally else Alignment.Start
)
Text(
text = location.labelOverride ?: location.label,
modifier = Modifier.align(titleAlignment),
style = textStyle,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
softWrap = true,
)
if (!location.openingSchedule?.openingHours.isNullOrEmpty()) {
val isOpen = location.openingSchedule!!.isOpen
AnimatedVisibility(!showDetails) {
Text(
modifier = Modifier
.padding(top = 4.dp)
.fillMaxWidth(),
text = context.getString(if (isOpen) R.string.location_open else R.string.location_closed),
style = MaterialTheme.typography.labelSmall,
color = if (isOpen) MaterialTheme.colorScheme.tertiary else MaterialTheme.colorScheme.secondary,
textAlign = TextAlign.Start,
)
}
}
}
Column(
modifier = Modifier.padding(end = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceAround,
) {
val targetHeading by remember(userLocation, location) {
if (userLocation != null)
viewModel.devicePoseProvider.getHeadingToDegrees(
userLocation!!.bearingTo(
location.toAndroidLocation()
)
)
else
emptyFlow()
}.collectAsStateWithLifecycle(null)
if (targetHeading != null) {
val directionArrowAngle by animateValueAsState(
targetValue = targetHeading!!,
typeConverter = Float.DegreesConverter
)
Icon(
modifier = Modifier.rotate(directionArrowAngle),
imageVector = Icons.Rounded.ArrowUpward,
contentDescription = null
)
}
if (distance != null) {
Text(
text = distance.metersToLocalizedString(
context, insaneUnits
), style = MaterialTheme.typography.labelSmall
)
}
}
}
AnimatedVisibility(showDetails) {
Column {
val isTwentyFourSeven = location.openingSchedule?.isTwentyFourSeven ?: false
val hasOpeningHours = !location.openingSchedule?.openingHours.isNullOrEmpty()
val daysOfWeek = enumValues<DayOfWeek>()
val javaLocale = java.util.Locale.forLanguageTag(Locale.current.toLanguageTag())
val timeFormatter = DateTimeFormatter
.ofLocalizedTime(FormatStyle.SHORT)
.withLocale(javaLocale)
if (isTwentyFourSeven) {
Text(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(bottom = 8.dp),
text = stringResource(id = R.string.location_open_24_7),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.tertiary,
)
} else if (hasOpeningHours) {
val oh = location.openingSchedule!!.openingHours
val openIndex = oh.indexOfFirst { it.isOpen }
if (openIndex != -1) {
val todaySchedule = oh[openIndex]
Text(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(bottom = 8.dp),
text = stringResource(
R.string.location_open_until,
(todaySchedule.startTime + todaySchedule.duration).format(
timeFormatter
)
),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.tertiary,
)
}
}
val showMap by viewModel.showMap.collectAsState()
if (showMap) {
val zoomLevel = 19
val nTiles = 4
val tileServerUrl by viewModel.mapTileServerUrl.collectAsState()
val shape = MaterialTheme.shapes.small
val applyTheming by viewModel.applyMapTheming.collectAsState()
val showPositionOnMap by viewModel.showPositionOnMap.collectAsState()
HorizontalDivider()
MapTiles(
modifier = Modifier
.padding(top = 16.dp, bottom = 8.dp)
.align(Alignment.CenterHorizontally)
.fillMaxWidth(.9125f)
.aspectRatio(1f)
.border(1.dp, MaterialTheme.colorScheme.outline, shape)
.clip(shape)
.clickable {
viewModel.launch(context)
},
tileServerUrl = tileServerUrl,
location = location,
initialZoomLevel = zoomLevel,
numberOfTiles = nTiles,
applyTheming = applyTheming,
userLocation = if (showPositionOnMap) userLocation?.let {
UserLocation(
it.latitude,
it.longitude
)
} else null,
)
val address = buildAddress(location.street, location.houseNumber)
if (address != null) {
Text(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(bottom = 8.dp),
text = address,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.secondary
)
}
}
HorizontalDivider(Modifier.padding(top = 8.dp))
if (!isTwentyFourSeven && hasOpeningHours) {
val today = LocalDateTime.now().dayOfWeek
val oh = location.openingSchedule!!.openingHours
val nextOpeningTime =
(0..DayOfWeek.SUNDAY.ordinal)
.firstNotNullOfOrNull {
val dow =
daysOfWeek[(today.ordinal + it) % (DayOfWeek.SUNDAY.ordinal + 1)]
oh.filter {
it.dayOfWeek == dow
}.firstOrNull {
it.dayOfWeek != today || it.startTime.isAfter(LocalTime.now())
}
} ?: oh.first()
Text(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(top = 8.dp),
text = stringResource(
if (nextOpeningTime.dayOfWeek == today) R.string.location_open_next
else R.string.location_open_next_day,
if (nextOpeningTime.dayOfWeek == today) {
val untilOpenToday = Duration.between(
LocalTime.now(),
nextOpeningTime.startTime,
)
val hours = untilOpenToday.toHours()
val minutes = untilOpenToday.toMinutes() % 60L
if (hours > 0L) "${hours}h ${minutes}m"
else "${minutes}m"
} else "${
nextOpeningTime.dayOfWeek.getDisplayName(
TextStyle.FULL_STANDALONE,
javaLocale
)
} ${nextOpeningTime.startTime.format(timeFormatter)}"
),
style = MaterialTheme.typography.labelMedium,
)
}
val toolbarActions = mutableListOf<ToolbarAction>()
if (LocalFavoritesEnabled.current) {
val isPinned by viewModel.isPinned.collectAsState(false)
val favAction = if (isPinned) {
DefaultToolbarAction(
label = stringResource(R.string.menu_favorites_unpin),
icon = Icons.Rounded.Star,
action = {
viewModel.unpin()
}
)
} else {
DefaultToolbarAction(
label = stringResource(R.string.menu_favorites_pin),
icon = Icons.Rounded.StarOutline,
action = {
viewModel.pin()
})
}
toolbarActions.add(favAction)
}
if (!showMap) {
toolbarActions += DefaultToolbarAction(
label = stringResource(id = R.string.menu_map),
icon = Icons.Rounded.Map
) {
viewModel.launch(context)
}
}
location.phoneNumber?.let {
toolbarActions += DefaultToolbarAction(
label = stringResource(id = R.string.menu_dial),
icon = Icons.Rounded.Phone
) {
viewModel.viewModelScope.launch {
context.tryStartActivity(
Intent(
Intent.ACTION_DIAL, Uri.parse("tel:$it")
)
)
}
}
}
location.websiteUrl?.let {
toolbarActions += DefaultToolbarAction(
label = stringResource(id = R.string.menu_website),
icon = Icons.Rounded.TravelExplore
) {
viewModel.viewModelScope.launch {
context.tryStartActivity(
Intent(
Intent.ACTION_VIEW, Uri.parse(it)
)
)
}
}
}
location.fixMeUrl?.let {
toolbarActions += DefaultToolbarAction(
label = stringResource(id = R.string.menu_bugreport),
icon = Icons.Rounded.BugReport,
) {
showBugreportDialog = true
}
}
Toolbar(
modifier = Modifier.fillMaxWidth(),
leftActions = listOf(DefaultToolbarAction(
label = stringResource(id = R.string.menu_back),
icon = Icons.AutoMirrored.Rounded.ArrowBack
) {
onBack()
}),
rightActions = toolbarActions,
)
}
}
}
}
if (showBugreportDialog && location.fixMeUrl != null) {
AlertDialog(
containerColor = MaterialTheme.colorScheme.surface,
tonalElevation = 35.dp,
confirmButton = {
TextButton(
onClick = { showBugreportDialog = false },
shape = MaterialTheme.shapes.medium,
) {
Text(
text = stringResource(id = android.R.string.ok),
style = MaterialTheme.typography.labelLarge,
)
}
},
onDismissRequest = {
showBugreportDialog = false
},
text = {
Column(
Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Text(
text = stringResource(id = R.string.alert_bugreport),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
softWrap = true,
textAlign = TextAlign.Justify
)
HorizontalDivider(Modifier.padding(vertical = 8.dp))
Text(
modifier = modifier.clickable {
showBugreportDialog = false
viewModel.viewModelScope.launch {
context.tryStartActivity(
Intent(
Intent.ACTION_VIEW, Uri.parse(location.fixMeUrl)
)
)
}
},
text = location.fixMeUrl!!,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
)
}
}
@Composable
fun LocationItemGridPopup(
location: Location,
show: MutableTransitionState<Boolean>,
animationProgress: Float,
origin: Rect,
onDismiss: () -> Unit
) {
AnimatedVisibility(
show,
enter = expandIn(
animationSpec = tween(300),
expandFrom = Alignment.TopEnd,
) { origin.roundToIntRect().size },
exit = shrinkOut(
animationSpec = tween(300),
shrinkTowards = Alignment.TopEnd,
) { origin.roundToIntRect().size },
) {
LocationItem(
modifier = Modifier
.fillMaxWidth()
.scale(
1 - (1 - LocalGridSettings.current.iconSize / 84f) * (1 - animationProgress),
transformOrigin = TransformOrigin(1f, 0f)
)
.offset(
x = 16.dp * (1 - animationProgress).pow(10),
y = (-16).dp * (1 - animationProgress),
),
location = location,
showDetails = true,
onBack = onDismiss,
)
}
}
private fun buildAddress(
street: String?,
houseNumber: String?,
): String? {
val summary = StringBuilder()
if (street != null) {
summary.append(street, ' ')
if (houseNumber != null) {
summary.append(houseNumber, ' ')
}
}
return if (summary.isEmpty()) null else summary.toString()
}

View File

@ -0,0 +1,582 @@
package de.mm20.launcher2.ui.launcher.search.location
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.compose.animation.core.EaseInOutCirc
import androidx.compose.animation.core.EaseInOutSine
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.animateOffsetAsState
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableIntState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.minus
import coil.ImageLoader
import coil.compose.AsyncImage
import coil.compose.AsyncImagePainter
import coil.disk.DiskCache
import coil.memory.MemoryCache
import coil.request.CachePolicy
import coil.request.ImageRequest
import de.mm20.launcher2.ktx.PI
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.search.Location
import de.mm20.launcher2.search.LocationCategory
import de.mm20.launcher2.search.OpeningHours
import de.mm20.launcher2.search.OpeningSchedule
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.ui.ktx.contrast
import de.mm20.launcher2.ui.ktx.hue
import de.mm20.launcher2.ui.ktx.hueRotate
import de.mm20.launcher2.ui.ktx.invert
import de.mm20.launcher2.ui.locals.LocalDarkTheme
import kotlinx.collections.immutable.toImmutableList
import org.koin.android.ext.koin.androidContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.koin.core.context.GlobalContext
import org.koin.core.context.startKoin
import kotlin.math.asinh
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sqrt
import kotlin.math.tan
data class UserLocation(val lat: Double, val lon: Double)
@Composable
fun MapTiles(
tileServerUrl: String,
location: Location,
initialZoomLevel: Int,
numberOfTiles: Int,
userLocation: UserLocation?,
applyTheming: Boolean,
modifier: Modifier = Modifier,
// https://wiki.openstreetmap.org/wiki/Attribution_guidelines/2021-06-04_draft#Attribution_text
osmAttribution: String? = "© OpenStreetMap",
) {
val context = LocalContext.current
val tintColor = MaterialTheme.colorScheme.surface
val darkMode = LocalDarkTheme.current
val previousZoomLevel = remember { mutableIntStateOf(-1) }
val (start, stop, zoom) = remember(userLocation) {
userLocation
?.runCatching {
getEnclosingTiles(
location,
numberOfTiles,
this,
previousZoomLevel
)
}
?.onFailure {
Log.e("MapTiles", "Enclosing calculation failed", it)
}
?.getOrNull()
?: getTilesAround(location, initialZoomLevel, numberOfTiles)
}
val sideLength = stop.x - start.x + 1
val imageStates = remember { (0 until numberOfTiles).map { false }.toMutableStateList() }
val colorMatrix = remember(applyTheming, darkMode, tintColor) {
// darkreader css for openstreetmap tiles
// invert(93.7%) hue-rotate(180deg) contrast(90.6%)
val tintHueDeg = tintColor.hue * 180f / Float.PI
if (!darkMode && applyTheming) {
ColorMatrix()
.hueRotate(tintHueDeg)
} else if (darkMode) {
ColorMatrix()
.invert(0.937f)
.hueRotate(180f + if (applyTheming) tintHueDeg else 0f)
.contrast(0.906f)
} else null
}
Box(
modifier = modifier,
contentAlignment = Alignment.Center,
) {
Column(modifier = Modifier.matchParentSize()) {
for (y in start.y..stop.y) {
Row(
modifier = Modifier
.weight(1f / sideLength)
.fillMaxSize()
) {
for (x in start.x..stop.x) {
AsyncImage(
modifier = Modifier
.weight(1f / sideLength)
.fillMaxSize(),
imageLoader = MapTileLoader.loader,
model = MapTileLoader.getTileRequest(tileServerUrl, x, y, zoom),
contentDescription = null,
colorFilter = colorMatrix?.let { ColorFilter.colorMatrix(it) },
filterQuality = FilterQuality.High,
onState = {
val stateIndex =
(y - start.y) * (stop.y - start.y + 1) + (x - start.x)
when (it) {
is AsyncImagePainter.State.Loading -> imageStates[stateIndex] =
false
is AsyncImagePainter.State.Success -> imageStates[stateIndex] =
true
is AsyncImagePainter.State.Error -> {
imageStates[stateIndex] = false
Log.e(
"MapTiles",
"Error loading tile: $x, $y @$zoom",
it.result.throwable
)
}
else -> {}
}
}
)
}
}
}
}
if (imageStates.all { it }) {
val locationBorderColor =
if (applyTheming) MaterialTheme.colorScheme.error else Color(0xFFEFA521) // orange-ish
val userLocationColor =
if (applyTheming) MaterialTheme.colorScheme.onErrorContainer else Color(0xFF35A82C) // darkish green
val userLocationBorderColor =
if (applyTheming) {
MaterialTheme.colorScheme.errorContainer
} else if (darkMode) {
Color(0xFF777777)
} else {
Color(0xFFE5E5E5)
}
val infiniteTransition = rememberInfiniteTransition("infiniteTransition")
val userLocAnimation by infiniteTransition.animateFloat(
initialValue = 0.8f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(2000, easing = EaseInOutSine),
repeatMode = RepeatMode.Reverse
),
label = "userLocAnimation"
)
val poiLocAnimation by infiniteTransition.animateFloat(
initialValue = 30f,
targetValue = 20f,
animationSpec = infiniteRepeatable(
animation = tween(750, easing = EaseInOutCirc),
repeatMode = RepeatMode.Reverse
),
label = "poiLocAnimation"
)
val textMeasurer = rememberTextMeasurer()
val osmAttributionTextStyle = MaterialTheme.typography.labelSmall
val osmAttributionTextColor = MaterialTheme.colorScheme.onSurface
val osmAttributionSurface =
MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = .5f)
val (yLocation, xLocation) = remember(location, zoom) {
getDoubleTileCoordinates(
latitude = location.latitude,
longitude = location.longitude,
zoom
)
}
val (yUser, xUser) = remember(userLocation, zoom) {
if (userLocation != null) {
getDoubleTileCoordinates(
latitude = userLocation.lat,
longitude = userLocation.lon,
zoom
)
} else {
-1.0 to -1.0
}
}
val animatedUserIndicatorOffset by animateOffsetAsState(
targetValue = (Offset(
xUser.toFloat(),
yUser.toFloat()
) - start) / sideLength.toFloat(),
animationSpec = tween(
1000,
250
)
)
Canvas(modifier = Modifier.matchParentSize()) {
assert(size.width == size.height)
if (userLocation != null) {
if (start.y < yUser && yUser < stop.y + 1 &&
start.x < xUser && xUser < stop.x + 1
) {
val userIndicatorOffset = animatedUserIndicatorOffset * size.width
drawCircle(
color = userLocationBorderColor,
radius = 18.5f * userLocAnimation,
center = userIndicatorOffset,
alpha = (userLocAnimation - 0.8f) * 5f
)
drawCircle(
color = userLocationColor,
radius = 13.5f * userLocAnimation,
center = userIndicatorOffset,
)
}
}
val locationIndicatorOffset =
(Offset(
xLocation.toFloat(),
yLocation.toFloat()
) - start) / sideLength.toFloat() * size.width
drawCircle(
color = locationBorderColor,
radius = poiLocAnimation,
center = locationIndicatorOffset,
style = Stroke(width = 4f)
)
if (osmAttribution != null) {
val measureResult = textMeasurer.measure(
osmAttribution,
maxLines = 1,
style = osmAttributionTextStyle
)
val osmLabelPadding = 6f
val textOffset = Offset(
x = size.width - measureResult.size.width - osmLabelPadding,
y = size.height - measureResult.size.height - osmLabelPadding
)
drawRoundRect(
color = osmAttributionSurface,
topLeft = textOffset - Offset(osmLabelPadding, 0f),
size = Size(
width = measureResult.size.width + 2 * osmLabelPadding,
height = measureResult.size.height + osmLabelPadding
),
cornerRadius = CornerRadius(8f, 8f)
)
drawText(
measureResult,
color = osmAttributionTextColor,
topLeft = textOffset
)
}
}
} else {
val loadingColor = MaterialTheme.colorScheme.secondary
CircularProgressIndicator(
modifier = Modifier.fillMaxSize(.15f),
color = loadingColor,
strokeCap = StrokeCap.Round,
)
}
}
}
private fun getDoubleTileCoordinates(
latitude: Double,
longitude: Double,
zoomLevel: Int
): Pair<Double, Double> {
// https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Mathematics
val latRadians = Math.toRadians(latitude)
val xCoordinate = (longitude + 180.0) / 360.0 * (1 shl zoomLevel)
val yCoordinate = (1.0 - asinh(tan(latRadians)) / Math.PI) * (1 shl (zoomLevel - 1))
return yCoordinate to xCoordinate
}
data class TileCoordinateRange(val start: IntOffset, val stop: IntOffset, val zoomLevel: Int)
private fun getTilesAround(
location: Location,
zoomLevel: Int,
nTiles: Int
): TileCoordinateRange {
if (sqrt(nTiles.toDouble()) % 1.0 != 0.0)
throw IllegalArgumentException("nTiles must be a square number")
val sideLen = sqrt(nTiles.toDouble()).toInt()
val sideLenHalf = sideLen / 2
val (yCoordinate, xCoordinate) = getDoubleTileCoordinates(
location.latitude,
location.longitude,
zoomLevel
)
val xTile = xCoordinate.toInt()
val yTile = yCoordinate.toInt()
val yStart: Int
val yStop: Int
val xStart: Int
val xStop: Int
if (sideLen % 2 == 1) {
// center tile is defined
yStart = yTile - sideLenHalf
yStop = yTile + sideLenHalf
xStart = xTile - sideLenHalf
xStop = xTile + sideLenHalf
} else {
// center tile is not defined; take adjacent tiles closest to coordinate of interest
val leftOfCenter = (xCoordinate % 1.0) < 0.5
val topOfCenter = (yCoordinate % 1.0) < 0.5
yStart = if (topOfCenter) yTile - sideLenHalf else yTile - sideLenHalf + 1
yStop = if (topOfCenter) yTile + sideLenHalf - 1 else yTile + sideLenHalf
xStart = if (leftOfCenter) xTile - sideLenHalf else xTile - sideLenHalf + 1
xStop = if (leftOfCenter) xTile + sideLenHalf - 1 else xTile + sideLenHalf
}
return TileCoordinateRange(IntOffset(xStart, yStart), IntOffset(xStop, yStop), zoomLevel)
}
const val ZOOM_MAX = 19
const val ZOOM_MIN = 0
private fun getEnclosingTiles(
location: Location,
nTiles: Int,
userLocation: UserLocation,
previousZoomLevel: MutableIntState,
): TileCoordinateRange {
if (sqrt(nTiles.toDouble()) % 1.0 != 0.0)
throw IllegalArgumentException("nTiles must be a square number")
val sideLen = sqrt(nTiles.toDouble()).toInt()
val sideLenHalf = sideLen / 2
// start at previous zoom (+1) to do less calculations, because if
// - user comes closer to location:
// we might be able to increase the zoom level by one
// - user moves further away from location:
// we still iterate down to minimum zoom and there is no need to start with ZOOM_MAX for that
for (zoomLevel in previousZoomLevel
.intValue
.let { if (it == -1) ZOOM_MAX else min(it + 1, ZOOM_MAX) } downTo ZOOM_MIN
) {
val (locationY, locationX) = getDoubleTileCoordinates(
location.latitude,
location.longitude,
zoomLevel
)
val (userY, userX) = getDoubleTileCoordinates(
userLocation.lat,
userLocation.lon,
zoomLevel
)
val (locationTileY, locationTileX) = locationY.toInt() to locationX.toInt()
val (userTileY, userTileX) = userY.toInt() to userX.toInt()
if (locationTileY - sideLenHalf <= userTileY && userTileY <= locationTileY + sideLenHalf &&
locationTileX - sideLenHalf <= userTileX && userTileX <= locationTileX + sideLenHalf
) {
var xStart = min(locationTileX, userTileX)
var yStart = min(locationTileY, userTileY)
var xStop = max(locationTileX, userTileX)
var yStop = max(locationTileY, userTileY)
val xRem = sideLen - xStop + xStart - 1
if (0 < xRem) {
val leftOfCenter = (locationX % 1.0) < 0.5
val ceil = ceil(xRem / 2.0).toInt()
val floor = floor(xRem / 2.0).toInt()
if (leftOfCenter) {
xStart -= ceil
xStop += floor
} else {
xStart -= floor
xStop += ceil
}
}
val yRem = sideLen - yStop + yStart - 1
if (0 < yRem) {
val topOfCenter = (locationY % 1.0) < 0.5
val ceil = ceil(yRem / 2.0).toInt()
val floor = floor(yRem / 2.0).toInt()
if (topOfCenter) {
yStart -= ceil
yStop += floor
} else {
yStart -= floor
yStop += ceil
}
}
previousZoomLevel.intValue = zoomLevel
return TileCoordinateRange(
IntOffset(xStart, yStart),
IntOffset(xStop, yStop),
zoomLevel
)
}
}
throw IllegalStateException("Unreachable (right?) | lat: ${location.latitude} | lon: ${location.longitude} | user: $userLocation | nTiles: $nTiles")
}
private object MapTileLoader : KoinComponent {
private val context: Context by inject()
private val userAgent = "${context.packageName}/${
context.packageManager.getPackageInfo(
context.packageName,
0
)?.versionName ?: "dev"
}"
fun getTileRequest(tileServerUrl: String, x: Int, y: Int, zoom: Int): ImageRequest {
return ImageRequest.Builder(context)
.data("$tileServerUrl/$zoom/$x/$y.png")
.addHeader(
"User-Agent",
userAgent
)
.build()
}
val loader = ImageLoader
.Builder(context)
.memoryCache {
MemoryCache.Builder(context)
.maxSizePercent(0.05)
.build()
}
.memoryCachePolicy(CachePolicy.ENABLED)
.diskCache {
DiskCache.Builder()
.directory(context.cacheDir.resolve("osm_tiles"))
.maxSizePercent(0.01)
.build()
}
.diskCachePolicy(CachePolicy.ENABLED)
.respectCacheHeaders(true)
.networkCachePolicy(CachePolicy.ENABLED)
.build()
}
@Preview
@Composable
private fun MapTilesPreview() {
val context = LocalContext.current
if (GlobalContext.getKoinApplicationOrNull() == null) {
startKoin {
androidContext(context)
}
}
val borderShape = MaterialTheme.shapes.medium
MapTiles(
modifier = Modifier
.size(300.dp)
.border(
BorderStroke(1.dp, MaterialTheme.colorScheme.outline),
borderShape
)
.clip(borderShape),
tileServerUrl = "http://tile.openstreetmap.org",
location = MockLocation,
initialZoomLevel = 19,
numberOfTiles = 9,
applyTheming = false,
userLocation = UserLocation(52.51623, 13.4048)
)
}
internal object MockLocation : Location {
override val domain: String = "MOCKLOCATION"
override val key: String = "MOCKLOCATION"
override val label: String = "Brandenburger Tor"
override val fixMeUrl: String = "https://www.openstreetmap.org/fixthemap"
override val latitude = 52.5162700
override val longitude = 13.3777021
override var category: LocationCategory? = LocationCategory.OTHER
override val street: String = "Pariser Platz"
override val houseNumber: String = "1"
override val openingSchedule: OpeningSchedule =
OpeningSchedule(true, emptyList<OpeningHours>().toImmutableList())
override val websiteUrl: String = "https://en.wikipedia.org/wiki/Brandenburg_Gate"
override val phoneNumber: String = "+49 1234567"
override fun overrideLabel(label: String): SavableSearchable = TODO()
override fun launch(context: Context, options: Bundle?): Boolean =
context.tryStartActivity(
Intent(
Intent.ACTION_VIEW,
Uri.parse("https://en.wikipedia.org/wiki/Brandenburg_Gate")
)
)
override fun getSerializer(): SearchableSerializer = TODO()
}

View File

@ -48,6 +48,7 @@ import de.mm20.launcher2.ui.settings.homescreen.HomescreenSettingsScreen
import de.mm20.launcher2.ui.settings.icons.IconsSettingsScreen
import de.mm20.launcher2.ui.settings.integrations.IntegrationsSettingsScreen
import de.mm20.launcher2.ui.settings.license.LicenseScreen
import de.mm20.launcher2.ui.settings.locations.LocationsSettingsScreen
import de.mm20.launcher2.ui.settings.log.LogScreen
import de.mm20.launcher2.ui.settings.main.MainSettingsScreen
import de.mm20.launcher2.ui.settings.media.MediaIntegrationSettingsScreen
@ -151,6 +152,9 @@ class SettingsActivity : BaseActivity() {
composable("settings/search/wikipedia") {
WikipediaSettingsScreen()
}
composable("settings/search/locations") {
LocationsSettingsScreen()
}
composable("settings/search/files") {
FileSearchSettingsScreen()
}

View File

@ -0,0 +1,158 @@
package de.mm20.launcher2.ui.settings.locations
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.preferences.search.LocationSearchSettings
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.preferences.ListPreference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.component.preferences.SliderPreference
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
import de.mm20.launcher2.ui.component.preferences.TextPreference
import de.mm20.launcher2.ui.ktx.metersToLocalizedString
@Composable
fun LocationsSettingsScreen() {
val viewModel: LocationsSettingsScreenVM = viewModel()
val locations by viewModel.locations.collectAsState()
val imperialUnits by viewModel.imperialUnits.collectAsState()
val hideUncategorized by viewModel.hideUncategorized.collectAsState()
val radius by viewModel.radius.collectAsState()
val customOverpassUrl by viewModel.customOverpassUrl.collectAsState()
val showMap by viewModel.showMap.collectAsState()
val themeMap by viewModel.themeMap.collectAsState()
val showPositionOnMap by viewModel.showPositionOnMap.collectAsState()
val customTileServerUrl by viewModel.customTileServerUrl.collectAsState()
PreferenceScreen(title = stringResource(R.string.preference_search_locations)) {
item {
PreferenceCategory {
SwitchPreference(
title = stringResource(R.string.preference_search_locations),
summary = stringResource(R.string.preference_search_locations_summary),
value = locations == true,
onValueChanged = {
viewModel.setLocations(it)
}
)
}
}
item {
PreferenceCategory {
ListPreference(
title = stringResource(R.string.length_unit),
items = listOf(
stringResource(R.string.imperial) to true,
stringResource(R.string.metric) to false
),
enabled = locations == true,
value = imperialUnits,
onValueChanged = {
viewModel.setImperialUnits(it)
}
)
SliderPreference(
title = stringResource(R.string.preference_search_locations_radius),
value = radius,
min = 500,
max = 10000,
step = 500,
enabled = locations == true,
onValueChanged = {
viewModel.setRadius(it)
},
label = {
Text(
modifier = Modifier
.width(64.dp)
.padding(start = 16.dp),
text = it.toFloat()
.metersToLocalizedString(LocalContext.current, imperialUnits),
style = MaterialTheme.typography.titleSmall
)
it.toFloat()
.metersToLocalizedString(LocalContext.current, imperialUnits)
}
)
SwitchPreference(
title = stringResource(R.string.preference_search_locations_hide_uncategorized),
summary = stringResource(R.string.preference_search_locations_hide_uncategorized_summary),
value = hideUncategorized == true,
enabled = locations == true,
onValueChanged = {
viewModel.setHideUncategorized(it)
}
)
}
}
item {
PreferenceCategory {
SwitchPreference(
title = stringResource(R.string.preference_search_locations_show_map),
summary = stringResource(R.string.preference_search_locations_show_map_summary),
enabled = locations == true,
value = showMap == true,
onValueChanged = {
viewModel.setShowMap(it)
}
)
SwitchPreference(
title = stringResource(R.string.preference_search_locations_theme_map),
summary = stringResource(R.string.preference_search_locations_theme_map_summary),
value = themeMap == true,
enabled = locations == true && showMap == true,
onValueChanged = {
viewModel.setThemeMap(it)
}
)
SwitchPreference(
title = stringResource(R.string.preference_search_locations_show_position_on_map),
summary = stringResource(R.string.preference_search_locations_show_position_on_map_summary),
value = showPositionOnMap == true,
enabled = locations == true && showMap == true,
onValueChanged = {
viewModel.setShowPositionOnMap(it)
}
)
}
}
item {
PreferenceCategory(stringResource(R.string.preference_category_advanced)) {
TextPreference(
title = stringResource(R.string.preference_search_location_custom_overpass_url),
value = customOverpassUrl,
placeholder = stringResource(id = R.string.overpass_url),
summary = customOverpassUrl.takeIf { !it.isNullOrBlank() }
?: stringResource(id = R.string.overpass_url),
onValueChanged = {
viewModel.setCustomOverpassUrl(it)
}
)
TextPreference(
title = stringResource(R.string.preference_search_location_custom_tile_server_url),
value = customTileServerUrl ?: "",
placeholder = LocationSearchSettings.DefaultTileServerUrl,
summary = customTileServerUrl.takeIf { !it.isNullOrBlank() }
?: LocationSearchSettings.DefaultTileServerUrl,
onValueChanged = {
viewModel.setCustomTileServerUrl(it)
}
)
}
}
}
}

View File

@ -0,0 +1,77 @@
package de.mm20.launcher2.ui.settings.locations
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.preferences.search.LocationSearchSettings
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class LocationsSettingsScreenVM: ViewModel(), KoinComponent {
private val settings: LocationSearchSettings by inject()
val locations = settings.enabled
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
fun setLocations(openStreetMaps: Boolean) {
settings.setEnabled(openStreetMaps)
}
val imperialUnits = settings.imperialUnits
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
fun setImperialUnits(imperialUnits: Boolean) {
settings.setImperialUnits(imperialUnits)
}
val radius = settings.searchRadius
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), 1500)
fun setRadius(radius: Int) {
settings.setSearchRadius(radius)
}
val customOverpassUrl = settings.overpassUrl
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "")
fun setCustomOverpassUrl(customUrl: String) {
var url = customUrl
if (url.endsWith('/')){
url = url.substringBeforeLast('/')
}
if (url.endsWith("/api/interpreter")) {
url = url.substringBeforeLast("/api/interpreter")
}
settings.setOverpassUrl(url)
}
val showMap = settings.showMap
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
fun setShowMap(showMap: Boolean) {
settings.setShowMap(showMap)
}
val showPositionOnMap = settings.showPositionOnMap
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
fun setShowPositionOnMap(showPositionOnMap: Boolean) {
settings.setShowPositionOnMap(showPositionOnMap)
}
val customTileServerUrl = settings.tileServer
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
fun setCustomTileServerUrl(customTileServerUrl: String) {
settings.setTileServer(customTileServerUrl)
}
val hideUncategorized = settings.hideUncategorized
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
fun setHideUncategorized(hideUncategorized: Boolean) {
settings.setHideUncategorized(hideUncategorized)
}
val themeMap = settings.themeMap
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
fun setThemeMap(themeMap: Boolean) {
settings.setThemeMap(themeMap)
}
}

View File

@ -174,6 +174,20 @@ fun SearchSettingsScreen() {
}
)
val locations by viewModel.locations.collectAsStateWithLifecycle(null)
PreferenceWithSwitch(
title= stringResource(R.string.preference_search_locations),
summary = stringResource(R.string.preference_search_locations_summary),
icon = Icons.Rounded.Place,
switchValue = locations == true,
onSwitchChanged = {
viewModel.setLocations(it)
},
onClick = {
navController?.navigate("settings/search/locations")
}
)
Preference(
title = stringResource(R.string.preference_screen_search_actions),
summary = stringResource(R.string.preference_search_search_actions_summary),

View File

@ -9,15 +9,14 @@ import de.mm20.launcher2.preferences.SearchResultOrder
import de.mm20.launcher2.preferences.search.CalculatorSearchSettings
import de.mm20.launcher2.preferences.search.CalendarSearchSettings
import de.mm20.launcher2.preferences.search.ContactSearchSettings
import de.mm20.launcher2.preferences.search.LocationSearchSettings
import de.mm20.launcher2.preferences.search.ShortcutSearchSettings
import de.mm20.launcher2.preferences.search.UnitConverterSettings
import de.mm20.launcher2.preferences.search.WebsiteSearchSettings
import de.mm20.launcher2.preferences.search.WikipediaSearchSettings
import de.mm20.launcher2.preferences.ui.SearchUiSettings
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@ -32,6 +31,7 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
private val calculatorSearchSettings: CalculatorSearchSettings by inject()
private val permissionsManager: PermissionsManager by inject()
private val locationSearchSettings: LocationSearchSettings by inject()
val favorites = searchUiSettings.favorites
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
@ -95,9 +95,15 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
websiteSearchSettings.setEnabled(websites)
}
val autoFocus = searchUiSettings.openKeyboard
val locations = locationSearchSettings.enabled
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
fun setLocations(locations: Boolean) {
locationSearchSettings.setEnabled(locations)
}
val autoFocus = searchUiSettings.openKeyboard
fun setAutoFocus(autoFocus: Boolean) {
searchUiSettings.setOpenKeyboard(autoFocus)
}

View File

@ -46,7 +46,6 @@ dependencies {
implementation(libs.materialcomponents.core)
implementation(libs.koin.android)
implementation(libs.androidx.palette)
implementation(project(":core:ktx"))

View File

@ -0,0 +1,21 @@
package de.mm20.launcher2.coroutines
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
fun <T> deferred(block: suspend () -> T): Deferred<T> {
val deferred = CompletableDeferred<T>()
return object : Deferred<T> by deferred {
private val mutex = Mutex()
override suspend fun await(): T {
mutex.withLock {
if (!deferred.isCompleted) {
block().also { deferred.complete(it) }
}
}
return deferred.await()
}
}
}

View File

@ -0,0 +1,250 @@
package de.mm20.launcher2.search
import android.content.Context
import androidx.core.content.ContextCompat
import de.mm20.launcher2.base.R
import de.mm20.launcher2.icons.ColorLayer
import de.mm20.launcher2.icons.StaticLauncherIcon
import de.mm20.launcher2.icons.TintedIconLayer
import kotlinx.collections.immutable.ImmutableList
import java.time.DayOfWeek
import java.time.Duration
import java.time.LocalDate
import java.time.LocalTime
import android.location.Location as AndroidLocation
interface Location : SavableSearchable {
val latitude: Double
val longitude: Double
val fixMeUrl: String?
val category: LocationCategory?
val street: String?
val houseNumber: String?
val openingSchedule: OpeningSchedule?
val websiteUrl: String?
val phoneNumber: String?
override val preferDetailsOverLaunch: Boolean
get() = true
override fun getPlaceholderIcon(context: Context): StaticLauncherIcon {
val (resId, bgColor) = when (category) {
LocationCategory.FAST_FOOD, LocationCategory.RESTAURANT -> with(
labelOverride ?: label
) {
when {
contains(
"pizza",
ignoreCase = true
) -> R.drawable.ic_location_pizza to R.color.red
contains(
"ramen",
ignoreCase = true
) -> R.drawable.ic_location_ramen to R.color.orange
contains(
"tapas",
ignoreCase = true
) -> R.drawable.ic_location_tapas to R.color.orange
contains(
"keba" /* b or p, depending on locale */,
ignoreCase = true
) -> R.drawable.ic_location_kebab to R.color.orange
category == LocationCategory.FAST_FOOD -> R.drawable.ic_location_fastfood to R.color.orange
else -> R.drawable.ic_location_restaurant to R.color.red
}
}
LocationCategory.BAR -> R.drawable.ic_location_bar to R.color.amber
LocationCategory.CAFE, LocationCategory.COFFEE_SHOP -> R.drawable.ic_location_cafe to R.color.brown
LocationCategory.HOTEL -> R.drawable.ic_location_hotel to R.color.green
LocationCategory.SUPERMARKET -> R.drawable.ic_location_supermarket to R.color.lightblue
LocationCategory.SCHOOL -> R.drawable.ic_location_school to R.color.purple
LocationCategory.PARKING -> R.drawable.ic_location_parking to R.color.blue
LocationCategory.FUEL -> R.drawable.ic_location_fuel to R.color.teal
LocationCategory.TOILETS -> R.drawable.ic_location_toilets to R.color.blue
LocationCategory.PHARMACY -> R.drawable.ic_location_pharmacy to R.color.pink
LocationCategory.HOSPITAL, LocationCategory.CLINIC -> R.drawable.ic_location_hospital to R.color.red
LocationCategory.POST_OFFICE -> R.drawable.ic_location_post_office to R.color.yellow
LocationCategory.PUB, LocationCategory.BIERGARTEN -> R.drawable.ic_location_pub to R.color.amber
LocationCategory.GRAVE_YARD -> R.drawable.ic_location_grave_yard to R.color.grey
LocationCategory.DOCTORS -> R.drawable.ic_location_doctors to R.color.red
LocationCategory.POLICE -> R.drawable.ic_location_police to R.color.blue
LocationCategory.DENTIST -> R.drawable.ic_location_dentist to R.color.lightblue
LocationCategory.LIBRARY, LocationCategory.BOOKS -> R.drawable.ic_location_library to R.color.brown
LocationCategory.COLLEGE, LocationCategory.UNIVERSITY -> R.drawable.ic_location_college to R.color.purple
LocationCategory.ICE_CREAM -> R.drawable.ic_location_ice_cream to R.color.pink
LocationCategory.THEATRE -> R.drawable.ic_location_theatre to R.color.purple
LocationCategory.PUBLIC_BUILDING -> R.drawable.ic_location_public_building to R.color.bluegrey
LocationCategory.CINEMA -> R.drawable.ic_location_cinema to R.color.purple
LocationCategory.NIGHTCLUB -> R.drawable.ic_location_nightclub to R.color.purple
LocationCategory.CONVENIENCE -> R.drawable.ic_location_convenience to R.color.lightblue
LocationCategory.CLOTHES -> R.drawable.ic_location_clothes to R.color.pink
LocationCategory.HAIRDRESSER, LocationCategory.BEAUTY -> R.drawable.ic_location_hairdresser to R.color.pink
LocationCategory.CAR_REPAIR -> R.drawable.ic_location_car_repair to R.color.blue
LocationCategory.BAKERY -> R.drawable.ic_location_bakery to R.color.brown
LocationCategory.CAR -> R.drawable.ic_location_car to R.color.blue
LocationCategory.MOBILE_PHONE -> R.drawable.ic_location_mobile_phone to R.color.blue
LocationCategory.FURNITURE -> R.drawable.ic_location_furniture to R.color.brown
LocationCategory.ALCOHOL -> R.drawable.ic_location_alcohol to R.color.amber
LocationCategory.FLORIST -> R.drawable.ic_location_florist to R.color.green
LocationCategory.HARDWARE -> R.drawable.ic_location_hardware to R.color.brown
LocationCategory.ELECTRONICS -> R.drawable.ic_location_electronics to R.color.blue
LocationCategory.SHOES -> R.drawable.ic_location_shoes to R.color.pink
LocationCategory.MALL, LocationCategory.DEPARTMENT_STORE, LocationCategory.CHEMIST -> R.drawable.ic_location_mall to R.color.blue
LocationCategory.OPTICIAN -> R.drawable.ic_location_optician to R.color.blue
LocationCategory.JEWELRY -> R.drawable.ic_location_jewelry to R.color.pink
LocationCategory.GIFT -> R.drawable.ic_location_gift to R.color.pink
LocationCategory.BICYCLE -> R.drawable.ic_location_bicycle to R.color.blue
LocationCategory.LAUNDRY -> R.drawable.ic_location_laundry to R.color.blue
LocationCategory.COMPUTER -> R.drawable.ic_location_computer to R.color.blue
LocationCategory.TOBACCO -> R.drawable.ic_location_tobacco to R.color.amber
LocationCategory.WINE -> R.drawable.ic_location_wine to R.color.amber
LocationCategory.PHOTO -> R.drawable.ic_location_photo to R.color.blue
LocationCategory.BANK -> R.drawable.ic_location_bank to R.color.blue
LocationCategory.SOCCER -> R.drawable.ic_location_soccer to R.color.green
LocationCategory.BASKETBALL -> R.drawable.ic_location_basketball to R.color.orange
LocationCategory.TENNIS -> R.drawable.ic_location_tennis to R.color.orange
LocationCategory.FITNESS, LocationCategory.FITNESS_CENTRE -> R.drawable.ic_location_fitness to R.color.orange
LocationCategory.TRAM_STOP -> R.drawable.ic_location_tram_stop to R.color.blue
LocationCategory.RAILWAY_STOP -> R.drawable.ic_location_railway_stop to R.color.lightblue
LocationCategory.BUS_STATION, LocationCategory.BUS_STOP -> R.drawable.ic_location_bus_station to R.color.blue
LocationCategory.ATM -> R.drawable.ic_location_atm to R.color.green
LocationCategory.ART -> R.drawable.ic_location_art to R.color.deeporange
LocationCategory.KIOSK -> R.drawable.ic_location_kiosk to R.color.bluegrey
LocationCategory.MUSEUM -> R.drawable.ic_location_museum to R.color.deeporange
LocationCategory.PARCEL_LOCKER -> R.drawable.ic_location_parcel_locker to R.color.bluegrey
LocationCategory.TRAVEL_AGENCY -> R.drawable.ic_location_travel_agency to R.color.lightblue
else -> R.drawable.ic_location_place to R.color.bluegrey
}
return StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
icon = ContextCompat.getDrawable(context, resId)!!,
scale = 0.5f,
color = ContextCompat.getColor(context, bgColor)
),
backgroundLayer = ColorLayer(ContextCompat.getColor(context, bgColor))
)
}
fun toAndroidLocation(): AndroidLocation {
val location = AndroidLocation("KvaesitsoLocationProvider")
location.latitude = latitude
location.longitude = longitude
return location
}
fun distanceTo(androidLocation: AndroidLocation): Float {
return androidLocation.distanceTo(this.toAndroidLocation())
}
fun distanceTo(otherLocation: Location): Float =
this.distanceTo(otherLocation.toAndroidLocation())
}
// https://taginfo.openstreetmap.org/tags
// 'amenity', 'shop', 'sport' of which the most important
enum class LocationCategory {
RESTAURANT,
FAST_FOOD,
BAR,
CAFE,
HOTEL,
SUPERMARKET,
OTHER,
SCHOOL,
PARKING,
FUEL,
TOILETS,
PHARMACY,
HOSPITAL,
POST_OFFICE,
PUB,
GRAVE_YARD,
DOCTORS,
POLICE,
DENTIST,
LIBRARY,
COLLEGE,
ICE_CREAM,
THEATRE,
PUBLIC_BUILDING,
CINEMA,
NIGHTCLUB,
BIERGARTEN,
CLINIC,
UNIVERSITY,
DEPARTMENT_STORE,
CLOTHES,
CONVENIENCE,
HAIRDRESSER,
CAR_REPAIR,
BEAUTY,
BOOKS,
BAKERY,
CAR,
MOBILE_PHONE,
FURNITURE,
ALCOHOL,
FLORIST,
HARDWARE,
ELECTRONICS,
SHOES,
MALL,
OPTICIAN,
JEWELRY,
GIFT,
BICYCLE,
LAUNDRY,
COMPUTER,
TOBACCO,
WINE,
PHOTO,
COFFEE_SHOP,
BANK,
SOCCER,
BASKETBALL,
TENNIS,
FITNESS,
TRAM_STOP,
RAILWAY_STOP,
BUS_STATION,
ATM,
ART,
KIOSK,
BUS_STOP,
MUSEUM,
PARCEL_LOCKER,
CHEMIST,
TRAVEL_AGENCY,
FITNESS_CENTRE
}
data class OpeningHours(
val dayOfWeek: DayOfWeek,
val startTime: LocalTime,
val duration: Duration
) {
val isOpen: Boolean
get() = LocalDate.now().dayOfWeek == dayOfWeek &&
LocalTime.now().isAfter(startTime) &&
LocalTime.now().isBefore(startTime.plus(duration))
override fun toString(): String = "$dayOfWeek $startTime-${startTime.plus(duration)}"
}
data class OpeningSchedule(
val isTwentyFourSeven: Boolean,
val openingHours: ImmutableList<OpeningHours>
) {
val isOpen: Boolean
get() = isTwentyFourSeven || openingHours.any { it.isOpen }
}

View File

@ -1,3 +1,5 @@
package de.mm20.launcher2.search
import kotlinx.coroutines.Deferred
interface Searchable

View File

@ -0,0 +1,18 @@
package de.mm20.launcher2.search
/**
* Interface that can be implemented by [SavableSearchable]s to provide a way to update itself.
* Consumers of [SavableSearchable]s can check if the [SavableSearchable] implements this interface
* and decide to get an updated version of the [SavableSearchable] by calling [updatedSelf], which
* returns an [UpdateResult] that contains either an up-to-date value or specifies unavailability.
*/
interface UpdatableSearchable<T : SavableSearchable> {
val timestamp: Long
val updatedSelf: (suspend () -> UpdateResult<T>)?
}
sealed class UpdateResult<out T> {
data class Success<out T>(val result: T) : UpdateResult<T>()
data class TemporarilyUnavailable<T>(val cause: Throwable? = null) : UpdateResult<T>()
data class PermanentlyUnavailable<T>(val cause: Throwable? = null) : UpdateResult<T>()
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M200,800L200,672Q165,660 142.5,629.5Q120,599 120,560L120,280Q120,263 131.5,251.5Q143,240 160,240L320,240Q337,240 348.5,251.5Q360,263 360,280L360,560Q360,599 337.5,629.5Q315,660 280,672L280,800L320,800Q337,800 348.5,811.5Q360,823 360,840Q360,857 348.5,868.5Q337,880 320,880L160,880Q143,880 131.5,868.5Q120,857 120,840Q120,823 131.5,811.5Q143,800 160,800L200,800ZM200,440L280,440L280,320L200,320L200,440ZM240,600Q257,600 268.5,588.5Q280,577 280,560L280,520L200,520L200,560Q200,577 211.5,588.5Q223,600 240,600ZM520,880Q487,880 463.5,856.5Q440,833 440,800L440,418Q440,392 455,371.5Q470,351 494,342L532,328Q546,323 553,313.5Q560,304 560,290L560,120Q560,103 571.5,91.5Q583,80 600,80L720,80Q737,80 748.5,91.5Q760,103 760,120L760,290Q760,304 767,313.5Q774,323 788,328L826,342Q850,351 865,371.5Q880,392 880,418L880,800Q880,833 856.5,856.5Q833,880 800,880L520,880ZM640,200L680,200L680,160L640,160L640,200ZM520,480L800,480L800,418L762,404Q724,390 702,360Q680,330 680,292L680,280L640,280L640,292Q640,330 618,360Q596,390 558,404L520,418L520,480ZM520,800L800,800L800,720L520,720L520,800ZM520,640L800,640L800,560L520,560L520,640ZM240,520Q240,520 240,520Q240,520 240,520L240,520L240,520L240,520Q240,520 240,520Q240,520 240,520ZM520,640L520,560L520,560L520,640L520,640Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M480,880Q398,880 325,848.5Q252,817 197.5,762.5Q143,708 111.5,635Q80,562 80,480Q80,397 112.5,324Q145,251 200.5,197Q256,143 330,111.5Q404,80 488,80Q568,80 639,107.5Q710,135 763.5,183.5Q817,232 848.5,298.5Q880,365 880,442Q880,557 810,618.5Q740,680 640,680L566,680Q557,680 553.5,685Q550,690 550,696Q550,708 565,730.5Q580,753 580,782Q580,832 552.5,856Q525,880 480,880ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM260,520Q286,520 303,503Q320,486 320,460Q320,434 303,417Q286,400 260,400Q234,400 217,417Q200,434 200,460Q200,486 217,503Q234,520 260,520ZM380,360Q406,360 423,343Q440,326 440,300Q440,274 423,257Q406,240 380,240Q354,240 337,257Q320,274 320,300Q320,326 337,343Q354,360 380,360ZM580,360Q606,360 623,343Q640,326 640,300Q640,274 623,257Q606,240 580,240Q554,240 537,257Q520,274 520,300Q520,326 537,343Q554,360 580,360ZM700,520Q726,520 743,503Q760,486 760,460Q760,434 743,417Q726,400 700,400Q674,400 657,417Q640,434 640,460Q640,486 657,503Q674,520 700,520ZM480,800Q489,800 494.5,795Q500,790 500,782Q500,768 485,749Q470,730 470,692Q470,650 499,625Q528,600 570,600L640,600Q706,600 753,561.5Q800,523 800,442Q800,321 707.5,240.5Q615,160 488,160Q352,160 256,253Q160,346 160,480Q160,613 253.5,706.5Q347,800 480,800Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M520,560L400,560Q383,560 371.5,571.5Q360,583 360,600Q360,617 371.5,628.5Q383,640 400,640L440,640L440,640Q440,657 451.5,668.5Q463,680 480,680Q497,680 508.5,668.5Q520,657 520,640L520,640L560,640Q577,640 588.5,628.5Q600,617 600,600L600,480Q600,463 588.5,451.5Q577,440 560,440L440,440L440,400L560,400Q577,400 588.5,388.5Q600,377 600,360Q600,343 588.5,331.5Q577,320 560,320L520,320L520,320Q520,303 508.5,291.5Q497,280 480,280Q463,280 451.5,291.5Q440,303 440,320L440,320L400,320Q383,320 371.5,331.5Q360,343 360,360L360,480Q360,497 371.5,508.5Q383,520 400,520L520,520L520,560ZM160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM160,720L800,720Q800,720 800,720Q800,720 800,720L800,240Q800,240 800,240Q800,240 800,240L160,240Q160,240 160,240Q160,240 160,240L160,720Q160,720 160,720Q160,720 160,720ZM160,720Q160,720 160,720Q160,720 160,720L160,240Q160,240 160,240Q160,240 160,240L160,240Q160,240 160,240Q160,240 160,240L160,720Q160,720 160,720Q160,720 160,720L160,720Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M804,678Q821,687 834,674Q847,661 838,644L780,536L738,644L804,678ZM604,640L652,640L748,402Q751,394 746.5,388.5Q742,383 736,380L656,348Q647,345 638.5,350Q630,355 628,364L604,640ZM308,640L356,640L332,364Q330,353 321.5,349Q313,345 304,348L224,380Q216,383 212.5,388.5Q209,394 212,402L308,640ZM156,678L222,644L180,536L122,644Q113,661 126,674Q139,687 156,678ZM436,640L524,640L554,302Q556,293 549.5,286.5Q543,280 534,280L426,280Q418,280 411.5,286.5Q405,293 406,302L436,640ZM138,760Q96,760 68,728.5Q40,697 40,654Q40,642 43.5,630.5Q47,619 52,608L140,440Q126,400 141,361Q156,322 194,306L274,274Q288,269 302,267Q316,265 330,268Q344,239 369,219.5Q394,200 426,200L534,200Q566,200 591,219.5Q616,239 630,268Q644,266 658,267.5Q672,269 686,274L766,306Q806,322 822,361Q838,400 820,438L908,606Q914,617 917,629Q920,641 920,654Q920,699 889.5,729.5Q859,760 814,760Q803,760 792,757.5Q781,755 770,750L708,720L250,720L194,750Q181,757 166.5,758.5Q152,760 138,760ZM480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M237,840Q214,840 192.5,824Q171,808 164,785Q139,701 123,639.5Q107,578 97.5,531.5Q88,485 84,449Q80,413 80,380Q80,288 144,224Q208,160 300,160L500,160Q527,124 568.5,102Q610,80 660,80Q685,80 702.5,97.5Q720,115 720,140Q720,146 718.5,152Q717,158 715,163Q711,174 707.5,185Q704,196 702,209L793,300L840,300Q857,300 868.5,311.5Q880,323 880,340L880,550Q880,563 872.5,573Q865,583 852,588L767,616L717,783Q709,809 688,824.5Q667,840 640,840L560,840Q527,840 503.5,816.5Q480,793 480,760L480,760L400,760L400,760Q400,793 376.5,816.5Q353,840 320,840L237,840ZM240,760L320,760Q320,760 320,760Q320,760 320,760L320,680L560,680L560,760Q560,760 560,760Q560,760 560,760L640,760Q640,760 640,760Q640,760 640,760L702,554L800,521L800,380L760,380L620,240Q620,220 622.5,201Q625,182 630,164Q601,172 579,191.5Q557,211 547,240L300,240Q242,240 201,281Q160,322 160,380Q160,421 181,520.5Q202,620 240,760Q240,760 240,760Q240,760 240,760ZM640,440Q657,440 668.5,428.5Q680,417 680,400Q680,383 668.5,371.5Q657,360 640,360Q623,360 611.5,371.5Q600,383 600,400Q600,417 611.5,428.5Q623,440 640,440ZM480,360Q497,360 508.5,348.5Q520,337 520,320Q520,303 508.5,291.5Q497,280 480,280L360,280Q343,280 331.5,291.5Q320,303 320,320Q320,337 331.5,348.5Q343,360 360,360L480,360ZM480,462Q480,462 480,462Q480,462 480,462Q480,462 480,462Q480,462 480,462Q480,462 480,462Q480,462 480,462L480,462Q480,462 480,462Q480,462 480,462Q480,462 480,462Q480,462 480,462L480,462L480,462L480,462L480,462L480,462Q480,462 480,462Q480,462 480,462L480,462Q480,462 480,462Q480,462 480,462L480,462L480,462L480,462Q480,462 480,462Q480,462 480,462L480,462Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M440,760L440,560L138,220Q130,212 125,201.5Q120,191 120,180Q120,154 138,137Q156,120 182,120L778,120Q804,120 822,137Q840,154 840,180Q840,191 835,201.5Q830,212 822,220L520,560L520,760L680,760Q697,760 708.5,771.5Q720,783 720,800Q720,817 708.5,828.5Q697,840 680,840L280,840Q263,840 251.5,828.5Q240,817 240,800Q240,783 251.5,771.5Q263,760 280,760L440,760ZM298,280L662,280L734,200L226,200L298,280ZM480,484L591,360L369,360L480,484ZM480,484L480,484L480,484L480,484Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M162,440L276,440Q270,402 253,369Q236,336 210,310Q192,339 179.5,371.5Q167,404 162,440ZM684,440L798,440Q793,404 780.5,371.5Q768,339 750,310Q724,336 707,369Q690,402 684,440ZM210,650Q236,624 253,591Q270,558 276,520L162,520Q167,556 179.5,588.5Q192,621 210,650ZM750,650Q768,621 780.5,588.5Q793,556 798,520L684,520Q690,558 707,591Q724,624 750,650ZM358,440L440,440L440,162Q387,170 341.5,191.5Q296,213 260,248Q299,286 324.5,334.5Q350,383 358,440ZM520,440L602,440Q610,383 635.5,334.5Q661,286 700,248Q664,213 618.5,191.5Q573,170 520,162L520,440ZM440,798L440,520L358,520Q350,577 324.5,625.5Q299,674 260,712Q296,747 341.5,768.5Q387,790 440,798ZM520,798Q573,790 618.5,768.5Q664,747 700,712Q661,674 635.5,625.5Q610,577 602,520L520,520L520,798ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880Z"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M200,800Q115,800 57.5,742.5Q0,685 0,600Q0,515 58.5,457.5Q117,400 200,400Q277,400 329.5,446Q382,492 396,560L422,560L350,360L320,360Q303,360 291.5,348.5Q280,337 280,320Q280,303 291.5,291.5Q303,280 320,280L440,280Q457,280 468.5,291.5Q480,303 480,320Q480,337 468.5,348.5Q457,360 440,360L436,360L450,400L642,400L584,240Q584,240 584,240Q584,240 584,240L520,240Q503,240 491.5,228.5Q480,217 480,200Q480,183 491.5,171.5Q503,160 520,160L584,160Q610,160 630.5,174Q651,188 660,212L728,398L760,398Q843,398 901.5,456.5Q960,515 960,598Q960,682 902,741Q844,800 760,800Q688,800 633.5,755Q579,710 564,640L396,640Q382,709 328,754.5Q274,800 200,800ZM200,720Q241,720 270.5,697.5Q300,675 312,640L240,640Q223,640 211.5,628.5Q200,617 200,600Q200,583 211.5,571.5Q223,560 240,560L312,560Q300,524 270.5,502Q241,480 200,480Q149,480 114.5,514.5Q80,549 80,600Q80,650 114.5,685Q149,720 200,720ZM508,560L564,560Q569,537 577.5,517Q586,497 600,480L478,480L508,560ZM760,720Q811,720 845.5,685Q880,650 880,600Q880,549 845.5,514.5Q811,480 760,480Q758,480 758,480Q758,480 756,480L782,549Q788,565 781,579.5Q774,594 758,600Q742,606 727,599Q712,592 706,576L682,508Q662,525 651,548Q640,571 640,600Q640,650 674.5,685Q709,720 760,720ZM196,600Q196,600 196,600Q196,600 196,600Q196,600 196,600Q196,600 196,600Q196,600 196,600Q196,600 196,600L196,600Q196,600 196,600Q196,600 196,600ZM760,600Q760,600 760,600Q760,600 760,600Q760,600 760,600Q760,600 760,600Q760,600 760,600Q760,600 760,600L760,600Q760,600 760,600Q760,600 760,600Q760,600 760,600Q760,600 760,600Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M320,760L320,780Q320,805 302.5,822.5Q285,840 260,840Q235,840 217.5,822.5Q200,805 200,780L200,718Q182,698 171,673.5Q160,649 160,620L160,240Q160,157 237,118.5Q314,80 480,80Q652,80 726,117Q800,154 800,240L800,620Q800,649 789,673.5Q778,698 760,718L760,780Q760,805 742.5,822.5Q725,840 700,840Q675,840 657.5,822.5Q640,805 640,780L640,760L320,760ZM482,200Q592,200 641.5,200Q691,200 706,200L258,200Q276,200 325.5,200Q375,200 482,200ZM640,480L320,480Q287,480 263.5,480Q240,480 240,480L240,480L720,480L720,480Q720,480 696.5,480Q673,480 640,480ZM240,400L720,400L720,280L240,280L240,400ZM340,640Q365,640 382.5,622.5Q400,605 400,580Q400,555 382.5,537.5Q365,520 340,520Q315,520 297.5,537.5Q280,555 280,580Q280,605 297.5,622.5Q315,640 340,640ZM620,640Q645,640 662.5,622.5Q680,605 680,580Q680,555 662.5,537.5Q645,520 620,520Q595,520 577.5,537.5Q560,555 560,580Q560,605 577.5,622.5Q595,640 620,640ZM258,200L706,200Q691,183 641.5,171.5Q592,160 482,160Q375,160 325.5,172.5Q276,185 258,200ZM320,680L640,680Q673,680 696.5,656.5Q720,633 720,600L720,480L240,480L240,600Q240,633 263.5,656.5Q287,680 320,680Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M200,840Q183,840 171.5,828.5Q160,817 160,800Q160,783 171.5,771.5Q183,760 200,760L760,760Q777,760 788.5,771.5Q800,783 800,800Q800,817 788.5,828.5Q777,840 760,840L200,840ZM320,680Q254,680 207,633Q160,586 160,520L160,200Q160,167 183.5,143.5Q207,120 240,120L800,120Q833,120 856.5,143.5Q880,167 880,200L880,320Q880,353 856.5,376.5Q833,400 800,400L720,400L720,520Q720,586 673,633Q626,680 560,680L320,680ZM320,600L560,600Q593,600 616.5,576.5Q640,553 640,520L640,200L240,200L240,520Q240,553 263.5,576.5Q287,600 320,600ZM720,320L800,320Q800,320 800,320Q800,320 800,320L800,200Q800,200 800,200Q800,200 800,200L720,200L720,320ZM320,600Q287,600 263.5,600Q240,600 240,600L240,600L640,600L640,600Q640,600 616.5,600Q593,600 560,600L320,600Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M240,760L240,780Q240,805 222.5,822.5Q205,840 180,840Q155,840 137.5,822.5Q120,805 120,780L120,494Q120,487 121,480Q122,473 124,467L199,254Q207,230 228,215Q249,200 275,200L685,200Q711,200 732,215Q753,230 761,254L836,467Q838,473 839,480Q840,487 840,494L840,780Q840,805 822.5,822.5Q805,840 780,840Q755,840 737.5,822.5Q720,805 720,780L720,760L240,760ZM232,400L728,400L686,280Q686,280 686,280Q686,280 686,280L274,280Q274,280 274,280Q274,280 274,280L232,400ZM200,480L200,480L200,680L200,680L200,480ZM300,640Q325,640 342.5,622.5Q360,605 360,580Q360,555 342.5,537.5Q325,520 300,520Q275,520 257.5,537.5Q240,555 240,580Q240,605 257.5,622.5Q275,640 300,640ZM660,640Q685,640 702.5,622.5Q720,605 720,580Q720,555 702.5,537.5Q685,520 660,520Q635,520 617.5,537.5Q600,555 600,580Q600,605 617.5,622.5Q635,640 660,640ZM200,680L760,680L760,480L200,480L200,680Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M480,880Q463,880 451.5,868.5Q440,857 440,840L440,760L200,760Q183,760 171.5,748.5Q160,737 160,720Q160,703 171.5,691.5Q183,680 200,680L760,680Q777,680 788.5,691.5Q800,703 800,720Q800,737 788.5,748.5Q777,760 760,760L520,760L520,840Q520,857 508.5,868.5Q497,880 480,880ZM360,460Q377,460 388.5,448.5Q400,437 400,420Q400,403 388.5,391.5Q377,380 360,380Q343,380 331.5,391.5Q320,403 320,420Q320,437 331.5,448.5Q343,460 360,460ZM600,460Q617,460 628.5,448.5Q640,437 640,420Q640,403 628.5,391.5Q617,380 600,380Q583,380 571.5,391.5Q560,403 560,420Q560,437 571.5,448.5Q583,460 600,460ZM240,640Q223,640 211.5,628.5Q200,617 200,600L200,357Q200,350 201,343.5Q202,337 204,331L258,174Q266,150 287,135Q308,120 334,120L626,120Q652,120 673,135Q694,150 702,174L756,331Q758,337 759,343.5Q760,350 760,357L760,600Q760,617 748.5,628.5Q737,640 720,640Q703,640 691.5,628.5Q680,617 680,600L680,560L280,560L280,600Q280,617 268.5,628.5Q257,640 240,640ZM306,280L654,280L626,200Q626,200 626,200Q626,200 626,200L334,200Q334,200 334,200Q334,200 334,200L306,280ZM280,360L280,360L280,480L280,480L280,360ZM280,480L680,480L680,360L280,360L280,480Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M240,760L240,800Q240,817 228.5,828.5Q217,840 200,840Q183,840 171.5,828.5Q160,817 160,800L160,160Q160,143 171.5,131.5Q183,120 200,120Q217,120 228.5,131.5Q240,143 240,160L240,200L320,200L320,160Q320,143 331.5,131.5Q343,120 360,120L600,120Q617,120 628.5,131.5Q640,143 640,160L640,200L720,200L720,160Q720,143 731.5,131.5Q743,120 760,120Q777,120 788.5,131.5Q800,143 800,160L800,800Q800,817 788.5,828.5Q777,840 760,840Q743,840 731.5,828.5Q720,817 720,800L720,760L640,760L640,800Q640,817 628.5,828.5Q617,840 600,840L360,840Q343,840 331.5,828.5Q320,817 320,800L320,760L240,760ZM240,680L320,680L320,600L240,600L240,680ZM240,520L320,520L320,440L240,440L240,520ZM240,360L320,360L320,280L240,280L240,360ZM640,680L720,680L720,600L640,600L640,680ZM640,520L720,520L720,440L640,440L640,520ZM640,360L720,360L720,280L640,280L640,360ZM400,760L560,760L560,200L400,200L400,760ZM400,200L400,200L560,200L560,200L400,200Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M240,438L200,460Q186,468 170,464Q154,460 146,446L66,306Q58,292 62,276Q66,260 80,252L310,120L380,120Q389,120 394.5,125.5Q400,131 400,140L400,160Q400,193 423.5,216.5Q447,240 480,240Q513,240 536.5,216.5Q560,193 560,160L560,140Q560,131 565.5,125.5Q571,120 580,120L650,120L880,252Q894,260 898,276Q902,292 894,306L814,446Q806,460 790.5,463.5Q775,467 760,459L720,439L720,800Q720,817 708.5,828.5Q697,840 680,840L280,840Q263,840 251.5,828.5Q240,817 240,800L240,438ZM320,304L320,760L640,760L640,304L764,372L806,302L634,202L634,202Q619,253 577.5,286.5Q536,320 480,320Q424,320 382.5,286.5Q341,253 326,202L326,202L154,302L196,372L320,304ZM480,481L480,481L480,481L480,481L480,481Q480,481 480,481Q480,481 480,481Q480,481 480,481Q480,481 480,481L480,481L480,481L480,481L480,481L480,481L480,481L480,481Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M242,711Q222,700 211,681.5Q200,663 200,640L200,448L104,395Q93,389 88,380Q83,371 83,360Q83,349 88,340Q93,331 104,325L442,141Q451,136 460.5,133.5Q470,131 480,131Q490,131 499.5,133.5Q509,136 518,141L899,349Q909,354 914.5,363.5Q920,373 920,384L920,640Q920,657 908.5,668.5Q897,680 880,680Q863,680 851.5,668.5Q840,657 840,640L840,404L760,448L760,640Q760,663 749,681.5Q738,700 718,711L518,819Q509,824 499.5,826.5Q490,829 480,829Q470,829 460.5,826.5Q451,824 442,819L242,711ZM480,508Q480,508 480,508Q480,508 480,508L754,360L480,212Q480,212 480,212Q480,212 480,212L206,360L480,508ZM480,749Q480,749 480,749Q480,749 480,749L680,641Q680,641 680,641Q680,641 680,641L680,490L519,579Q510,584 500,586.5Q490,589 480,589Q470,589 460,586.5Q450,584 441,579L280,490L280,641Q280,641 280,641Q280,641 280,641L480,749ZM480,508L480,508L480,508Q480,508 480,508Q480,508 480,508L480,508L480,508Q480,508 480,508Q480,508 480,508ZM480,629Q480,629 480,629Q480,629 480,629L480,629L480,629L480,629L480,629L480,629L480,629L480,629Q480,629 480,629Q480,629 480,629L480,629ZM480,629L480,629Q480,629 480,629Q480,629 480,629L480,629L480,629L480,629L480,629L480,629L480,629L480,629Q480,629 480,629Q480,629 480,629Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M80,840Q63,840 51.5,828.5Q40,817 40,800Q40,783 51.5,771.5Q63,760 80,760L880,760Q897,760 908.5,771.5Q920,783 920,800Q920,817 908.5,828.5Q897,840 880,840L80,840ZM160,720Q127,720 103.5,696.5Q80,673 80,640L80,200Q80,167 103.5,143.5Q127,120 160,120L800,120Q833,120 856.5,143.5Q880,167 880,200L880,640Q880,673 856.5,696.5Q833,720 800,720L160,720ZM160,640L800,640Q800,640 800,640Q800,640 800,640L800,200Q800,200 800,200Q800,200 800,200L160,200Q160,200 160,200Q160,200 160,200L160,640Q160,640 160,640Q160,640 160,640ZM160,640Q160,640 160,640Q160,640 160,640L160,200Q160,200 160,200Q160,200 160,200L160,200Q160,200 160,200Q160,200 160,200L160,640Q160,640 160,640Q160,640 160,640L160,640Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M841,442L841,760Q841,793 817.5,816.5Q794,840 761,840L201,840Q168,840 144.5,816.5Q121,793 121,760L121,442Q98,421 85.5,388Q73,355 85,316L127,180Q135,154 155.5,137Q176,120 203,120L759,120Q786,120 806,136.5Q826,153 835,180L877,316Q889,355 876.5,387Q864,419 841,442ZM569,400Q596,400 610,381.5Q624,363 621,340L599,200L521,200L521,348Q521,369 535,384.5Q549,400 569,400ZM389,400Q412,400 426.5,384.5Q441,369 441,348L441,200L363,200L341,340Q337,364 351.5,382Q366,400 389,400ZM211,400Q229,400 242.5,387Q256,374 259,354L281,200L203,200Q203,200 203,200Q203,200 203,200L163,334Q157,354 169.5,377Q182,400 211,400ZM751,400Q780,400 793,377Q806,354 799,334L757,200Q757,200 757,200Q757,200 757,200L681,200L703,354Q706,374 719.5,387Q733,400 751,400ZM201,760L761,760Q761,760 761,760Q761,760 761,760L761,478Q756,480 754.5,480Q753,480 751,480Q724,480 703.5,471Q683,462 663,442Q645,460 622,470Q599,480 573,480Q546,480 522.5,470Q499,460 481,442Q464,460 441.5,470Q419,480 393,480Q364,480 340.5,470Q317,460 299,442Q278,463 257.5,471.5Q237,480 211,480Q209,480 206.5,480Q204,480 201,478L201,760Q201,760 201,760Q201,760 201,760ZM761,760L201,760Q201,760 201,760Q201,760 201,760L201,760Q204,760 206.5,760Q209,760 211,760Q237,760 257.5,760Q278,760 299,760Q308,760 318.5,760Q329,760 341,760Q353,760 366,760Q379,760 393,760Q406,760 418,760Q430,760 441.5,760Q453,760 463,760Q473,760 481,760Q499,760 522.5,760Q546,760 573,760Q586,760 598,760Q610,760 621.5,760Q633,760 643.5,760Q654,760 663,760Q683,760 703.5,760Q724,760 751,760Q753,760 754.5,760Q756,760 761,760L761,760Q761,760 761,760Q761,760 761,760ZM341,720L421,720Q429,720 435,714Q441,708 441,700Q441,692 435,686Q429,680 421,680L361,680L361,640L421,640Q429,640 435,634Q441,628 441,620L441,540Q441,532 435,526Q429,520 421,520L341,520Q333,520 327,526Q321,532 321,540Q321,548 327,554Q333,560 341,560L401,560L401,600L341,600Q333,600 327,606Q321,612 321,620L321,700Q321,708 327,714Q333,720 341,720ZM601,640L601,700Q601,708 607,714Q613,720 621,720Q629,720 635,714Q641,708 641,700L641,540Q641,532 635,526Q629,520 621,520Q613,520 607,526Q601,532 601,540L601,600L561,600L561,540Q561,532 555,526Q549,520 541,520Q533,520 527,526Q521,532 521,540L521,620Q521,628 527,634Q533,640 541,640L601,640Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M680,85Q746,85 793,132Q840,179 840,245Q840,256 838.5,274.5Q837,293 834,317L779,720Q774,758 744.5,782Q715,806 677,806Q654,806 634.5,796Q615,786 602,768L495,612Q493,608 488.5,606.5Q484,605 479,605Q475,605 463,614L359,765Q345,785 324.5,795.5Q304,806 281,806Q243,806 214,781.5Q185,757 180,719L126,317Q123,293 121.5,274.5Q120,256 120,245Q120,179 167,132Q214,85 280,85Q316,85 337.5,94.5Q359,104 379,115Q399,126 421.5,135.5Q444,145 480,145Q516,145 538.5,135.5Q561,126 581,115Q601,104 623,94.5Q645,85 680,85ZM680,165Q657,165 639.5,174.5Q622,184 601,195Q580,206 552,215.5Q524,225 480,225Q436,225 408,215.5Q380,206 359,195Q338,184 320.5,174.5Q303,165 280,165Q247,165 223.5,188.5Q200,212 200,245Q200,253 201,268Q202,283 205,303L260,708Q261,716 267,720.5Q273,725 281,725Q286,725 290,723Q294,721 296,717L397,569Q411,549 433,537Q455,525 480,525Q505,525 527,537Q549,549 563,569L666,720Q668,723 671,724.5Q674,726 678,726Q686,726 692.5,721.5Q699,717 700,709L755,303Q758,283 759,268Q760,253 760,245Q760,212 736.5,188.5Q713,165 680,165ZM480,445Q480,445 480,445Q480,445 480,445Q480,445 480,445Q480,445 480,445L480,445Q480,445 480,445Q480,445 480,445Q480,445 480,445Q480,445 480,445L480,445Q480,445 480,445Q480,445 480,445Q480,445 480,445Q480,445 480,445L480,445Q480,445 480,445Q480,445 480,445Q480,445 480,445Q480,445 480,445L480,445Q480,445 480,445Q480,445 480,445Q480,445 480,445Q480,445 480,445Q480,445 480,445Q480,445 480,445Q480,445 480,445Q480,445 480,445Q480,445 480,445Q480,445 480,445Q480,445 480,445Q480,445 480,445Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M540,880Q432,880 356,804Q280,728 280,620L280,597Q194,583 137,516.5Q80,450 80,360L80,160Q80,143 91.5,131.5Q103,120 120,120L200,120L200,120Q200,103 211.5,91.5Q223,80 240,80Q257,80 268.5,91.5Q280,103 280,120L280,200Q280,217 268.5,228.5Q257,240 240,240Q223,240 211.5,228.5Q200,217 200,200L200,200L160,200L160,360Q160,426 207,473Q254,520 320,520Q386,520 433,473Q480,426 480,360L480,200L440,200L440,200Q440,217 428.5,228.5Q417,240 400,240Q383,240 371.5,228.5Q360,217 360,200L360,120Q360,103 371.5,91.5Q383,80 400,80Q417,80 428.5,91.5Q440,103 440,120L440,120L520,120Q537,120 548.5,131.5Q560,143 560,160L560,360Q560,450 503,516.5Q446,583 360,597L360,620Q360,695 412.5,747.5Q465,800 540,800Q615,800 667.5,747.5Q720,695 720,620L720,553Q685,540 662.5,509.5Q640,479 640,440Q640,390 675,355Q710,320 760,320Q810,320 845,355Q880,390 880,440Q880,479 857.5,509.5Q835,540 800,553L800,620Q800,728 724,804Q648,880 540,880ZM760,480Q777,480 788.5,468.5Q800,457 800,440Q800,423 788.5,411.5Q777,400 760,400Q743,400 731.5,411.5Q720,423 720,440Q720,457 731.5,468.5Q743,480 760,480ZM760,440Q760,440 760,440Q760,440 760,440Q760,440 760,440Q760,440 760,440Q760,440 760,440Q760,440 760,440Q760,440 760,440Q760,440 760,440Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M360,560L360,400Q360,383 371.5,371.5Q383,360 400,360L560,360Q577,360 588.5,371.5Q600,383 600,400L600,560Q600,577 588.5,588.5Q577,600 560,600L400,600Q383,600 371.5,588.5Q360,577 360,560ZM440,520L520,520L520,440L440,440L440,520ZM360,800L360,760L280,760Q247,760 223.5,736.5Q200,713 200,680L200,600L160,600Q143,600 131.5,588.5Q120,577 120,560Q120,543 131.5,531.5Q143,520 160,520L200,520L200,440L160,440Q143,440 131.5,428.5Q120,417 120,400Q120,383 131.5,371.5Q143,360 160,360L200,360L200,280Q200,247 223.5,223.5Q247,200 280,200L360,200L360,160Q360,143 371.5,131.5Q383,120 400,120Q417,120 428.5,131.5Q440,143 440,160L440,200L520,200L520,160Q520,143 531.5,131.5Q543,120 560,120Q577,120 588.5,131.5Q600,143 600,160L600,200L680,200Q713,200 736.5,223.5Q760,247 760,280L760,360L800,360Q817,360 828.5,371.5Q840,383 840,400Q840,417 828.5,428.5Q817,440 800,440L760,440L760,520L800,520Q817,520 828.5,531.5Q840,543 840,560Q840,577 828.5,588.5Q817,600 800,600L760,600L760,680Q760,713 736.5,736.5Q713,760 680,760L600,760L600,800Q600,817 588.5,828.5Q577,840 560,840Q543,840 531.5,828.5Q520,817 520,800L520,760L440,760L440,800Q440,817 428.5,828.5Q417,840 400,840Q383,840 371.5,828.5Q360,817 360,800ZM680,680Q680,680 680,680Q680,680 680,680L680,280Q680,280 680,280Q680,280 680,280L280,280Q280,280 280,280Q280,280 280,280L280,680Q280,680 280,680Q280,680 280,680L680,680ZM480,480L480,480L480,480L480,480L480,480Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M533,520Q501,475 448.5,457.5Q396,440 340,440Q284,440 231.5,457.5Q179,475 147,520L533,520ZM340,360Q386,360 433.5,371.5Q481,383 521.5,407Q562,431 591,467Q620,503 630,552Q634,571 622,585.5Q610,600 591,600L89,600Q70,600 58,585.5Q46,571 50,552Q60,503 89.5,467Q119,431 159.5,407Q200,383 247,371.5Q294,360 340,360ZM80,760Q63,760 51.5,748.5Q40,737 40,720Q40,703 51.5,691.5Q63,680 80,680L600,680Q617,680 628.5,691.5Q640,703 640,720Q640,737 628.5,748.5Q617,760 600,760L80,760ZM788,920L720,920L720,840L776,840L832,280L450,280L446,245Q444,227 455.5,213.5Q467,200 485,200L640,200L640,80Q640,63 651.5,51.5Q663,40 680,40Q697,40 708.5,51.5Q720,63 720,80L720,200L876,200Q894,200 906,213Q918,226 916,244L854,862Q851,887 832,903.5Q813,920 788,920ZM720,840L776,840L776,840L720,840L720,840Q720,840 720,840Q720,840 720,840L720,840ZM80,920Q63,920 51.5,908.5Q40,897 40,880Q40,863 51.5,851.5Q63,840 80,840L600,840Q617,840 628.5,851.5Q640,863 640,880Q640,897 628.5,908.5Q617,920 600,920L80,920ZM340,520Q340,520 340,520Q340,520 340,520Q340,520 340,520Q340,520 340,520L340,520Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M282,338L168,452Q157,463 140.5,463.5Q124,464 112,452Q101,441 100.5,424.5Q100,408 111,396L140,366L112,338Q100,326 100,310Q100,294 112,282L168,226L139,196Q128,185 128,168.5Q128,152 140,140Q151,129 167.5,128.5Q184,128 196,139L226,168L282,112Q294,100 310,100Q326,100 338,112L366,140L396,111Q407,100 423.5,100Q440,100 452,112Q463,123 463,140Q463,157 452,168L338,282L678,622L792,508Q803,497 819.5,496.5Q836,496 848,508Q859,519 859.5,535.5Q860,552 849,564L820,594L848,622Q860,634 860,650Q860,666 848,678L792,734L821,764Q832,775 832,791.5Q832,808 820,820Q809,831 792.5,831.5Q776,832 764,821L734,792L678,848Q666,860 650,860Q634,860 622,848L594,820L564,849Q553,860 536.5,860Q520,860 508,848Q497,837 497,820Q497,803 508,792L622,678L282,338Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M480,879Q480,753 556,656Q632,559 749,530Q768,525 786.5,526.5Q805,528 819,542Q831,555 832.5,573Q834,591 829,608Q801,726 703.5,802.5Q606,879 480,879ZM578,782Q635,761 678,718Q721,675 742,618Q685,639 642,682Q599,725 578,782ZM480,880Q480,754 404,657Q328,560 211,531Q192,526 173.5,527.5Q155,529 141,543Q129,556 127.5,574Q126,592 131,609Q159,727 256.5,803.5Q354,880 480,880ZM382,782Q325,761 282,718Q239,675 218,618Q275,639 318,682Q361,725 382,782ZM578,782Q578,782 578,782Q578,782 578,782Q578,782 578,782Q578,782 578,782ZM382,782Q382,782 382,782Q382,782 382,782Q382,782 382,782Q382,782 382,782ZM480,360Q497,360 508.5,348.5Q520,337 520,320Q520,303 508.5,291.5Q497,280 480,280Q463,280 451.5,291.5Q440,303 440,320Q440,337 451.5,348.5Q463,360 480,360ZM480,600Q441,600 409.5,578.5Q378,557 364,522Q359,522 355,522.5Q351,523 346,523Q294,523 257,486Q220,449 220,397Q220,376 227,356.5Q234,337 248,320Q235,303 228,283.5Q221,264 221,243Q221,191 257.5,154Q294,117 346,117Q351,117 355,117.5Q359,118 364,118Q378,83 409.5,61.5Q441,40 480,40Q519,40 550.5,61.5Q582,83 596,118Q601,118 605,117.5Q609,117 614,117Q666,117 702.5,154Q739,191 739,243Q739,264 732.5,283.5Q726,303 712,320Q725,337 732,356.5Q739,376 739,397Q739,449 702.5,486Q666,523 614,523Q609,523 605,522.5Q601,522 596,522Q582,557 550.5,578.5Q519,600 480,600ZM614,443Q633,443 646.5,429.5Q660,416 660,397Q660,383 652.5,372.5Q645,362 633,356L598,339Q596,350 592,360.5Q588,371 583,380Q578,389 571,397Q564,405 556,412L588,435Q593,439 599.5,441Q606,443 614,443ZM598,301L633,284Q645,278 652,267Q659,256 659,243Q659,224 646,210.5Q633,197 614,197Q606,197 600,199Q594,201 588,205L555,228Q563,235 570.5,243Q578,251 583,260Q588,269 592,279.5Q596,290 598,301ZM439,208Q449,204 459,202Q469,200 480,200Q491,200 501,202Q511,204 521,208L526,164Q528,146 513.5,133Q499,120 480,120Q461,120 446.5,133Q432,146 434,164L439,208ZM480,520Q499,520 513.5,507Q528,494 526,476L521,432Q511,436 501,438Q491,440 480,440Q469,440 459,438Q449,436 439,432L434,476Q432,494 446.5,507Q461,520 480,520ZM362,301Q364,290 368,279.5Q372,269 377,260Q382,251 389,243Q396,235 404,228L372,205Q367,201 360.5,199Q354,197 346,197Q327,197 313.5,210.5Q300,224 300,243Q300,256 307.5,267Q315,278 327,284L362,301ZM346,442Q354,442 360,440.5Q366,439 372,434L405,412Q397,405 389.5,397Q382,389 377,380Q372,371 368,360.5Q364,350 362,339L327,356Q315,362 308,373Q301,384 301,397Q302,416 314.5,429Q327,442 346,442ZM583,380Q583,380 583,380Q583,380 583,380L583,380Q583,380 583,380Q583,380 583,380Q583,380 583,380Q583,380 583,380L583,380Q583,380 583,380Q583,380 583,380Q583,380 583,380Q583,380 583,380ZM583,260Q583,260 583,260Q583,260 583,260Q583,260 583,260Q583,260 583,260L583,260Q583,260 583,260Q583,260 583,260Q583,260 583,260Q583,260 583,260Q583,260 583,260Q583,260 583,260L583,260ZM480,200L480,200Q480,200 480,200Q480,200 480,200Q480,200 480,200Q480,200 480,200L480,200Q480,200 480,200Q480,200 480,200Q480,200 480,200Q480,200 480,200ZM480,440Q480,440 480,440Q480,440 480,440L480,440Q480,440 480,440Q480,440 480,440Q480,440 480,440Q480,440 480,440L480,440Q480,440 480,440Q480,440 480,440ZM377,260L377,260Q377,260 377,260Q377,260 377,260Q377,260 377,260Q377,260 377,260Q377,260 377,260Q377,260 377,260L377,260Q377,260 377,260Q377,260 377,260Q377,260 377,260Q377,260 377,260ZM377,380Q377,380 377,380Q377,380 377,380Q377,380 377,380Q377,380 377,380L377,380Q377,380 377,380Q377,380 377,380Q377,380 377,380Q377,380 377,380L377,380Q377,380 377,380Q377,380 377,380Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M160,800L160,200Q160,167 183.5,143.5Q207,120 240,120L480,120Q513,120 536.5,143.5Q560,167 560,200L560,480L600,480Q633,480 656.5,503.5Q680,527 680,560L680,740Q680,757 691.5,768.5Q703,780 720,780Q737,780 748.5,768.5Q760,757 760,740L760,452Q751,457 741,458.5Q731,460 720,460Q678,460 649,431Q620,402 620,360Q620,328 637.5,302.5Q655,277 684,266L621,203Q612,194 612,182Q612,170 621,161Q629,153 641.5,152.5Q654,152 663,160L790,284Q805,299 812.5,319Q820,339 820,360L820,740Q820,782 791,811Q762,840 720,840Q678,840 649,811Q620,782 620,740L620,540Q620,540 620,540Q620,540 620,540L560,540L560,800Q560,817 548.5,828.5Q537,840 520,840L200,840Q183,840 171.5,828.5Q160,817 160,800ZM240,400L480,400L480,200Q480,200 480,200Q480,200 480,200L240,200Q240,200 240,200Q240,200 240,200L240,400ZM720,400Q737,400 748.5,388.5Q760,377 760,360Q760,343 748.5,331.5Q737,320 720,320Q703,320 691.5,331.5Q680,343 680,360Q680,377 691.5,388.5Q703,400 720,400ZM240,760L480,760L480,480L240,480L240,760ZM480,760L240,760L240,760L480,760L480,760Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M200,840Q183,840 171.5,828.5Q160,817 160,800L160,760L160,760Q110,760 75,725Q40,690 40,640L40,440Q40,390 75,355Q110,320 160,320L160,320L160,240Q160,190 195,155Q230,120 280,120L680,120Q730,120 765,155Q800,190 800,240L800,320L800,320Q850,320 885,355Q920,390 920,440L920,640Q920,690 885,725Q850,760 800,760L800,760L800,800Q800,817 788.5,828.5Q777,840 760,840Q743,840 731.5,828.5Q720,817 720,800L720,760L240,760L240,800Q240,817 228.5,828.5Q217,840 200,840ZM160,680L800,680Q817,680 828.5,668.5Q840,657 840,640L840,440Q840,423 828.5,411.5Q817,400 800,400Q783,400 771.5,411.5Q760,423 760,440L760,600L200,600L200,440Q200,423 188.5,411.5Q177,400 160,400Q143,400 131.5,411.5Q120,423 120,440L120,640Q120,657 131.5,668.5Q143,680 160,680ZM280,520L680,520L680,440Q680,413 691,391Q702,369 720,352L720,240Q720,223 708.5,211.5Q697,200 680,200L280,200Q263,200 251.5,211.5Q240,223 240,240L240,352Q258,369 269,391Q280,413 280,440L280,520ZM480,520L480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520L480,520L480,520ZM480,680Q480,680 480,680Q480,680 480,680L480,680Q480,680 480,680Q480,680 480,680Q480,680 480,680Q480,680 480,680L480,680L480,680L480,680Q480,680 480,680Q480,680 480,680Q480,680 480,680Q480,680 480,680L480,680Q480,680 480,680Q480,680 480,680L480,680ZM480,600Q480,600 480,600Q480,600 480,600L480,600L480,600L480,600Q480,600 480,600Q480,600 480,600L480,600L480,600Q480,600 480,600Q480,600 480,600L480,600L480,600L480,600Q480,600 480,600Q480,600 480,600L480,600L480,600Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M160,800L160,440L160,440Q127,440 103.5,416.5Q80,393 80,360L80,280Q80,247 103.5,223.5Q127,200 160,200L288,200Q283,191 281.5,181Q280,171 280,160Q280,110 315,75Q350,40 400,40Q423,40 443,48.5Q463,57 480,72Q497,56 517,48Q537,40 560,40Q610,40 645,75Q680,110 680,160Q680,171 678,180.5Q676,190 672,200L800,200Q833,200 856.5,223.5Q880,247 880,280L880,360Q880,393 856.5,416.5Q833,440 800,440L800,440L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880Q207,880 183.5,856.5Q160,833 160,800ZM560,120Q543,120 531.5,131.5Q520,143 520,160Q520,177 531.5,188.5Q543,200 560,200Q577,200 588.5,188.5Q600,177 600,160Q600,143 588.5,131.5Q577,120 560,120ZM360,160Q360,177 371.5,188.5Q383,200 400,200Q417,200 428.5,188.5Q440,177 440,160Q440,143 428.5,131.5Q417,120 400,120Q383,120 371.5,131.5Q360,143 360,160ZM160,280L160,360L440,360L440,280L160,280ZM440,800L440,440L240,440L240,800Q240,800 240,800Q240,800 240,800L440,800ZM520,800L720,800Q720,800 720,800Q720,800 720,800L720,440L520,440L520,800ZM800,360L800,280L520,280L520,360L800,360Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M426,800Q417,774 403,752Q389,730 370,711Q351,692 329,677.5Q307,663 281,654Q283,683 295,708Q307,733 327,753Q347,773 372,785.5Q397,798 426,800ZM534,800Q563,797 588,785Q613,773 633,753Q653,733 665,708Q677,683 680,654Q654,663 631.5,677Q609,691 590,710Q571,729 557,751.5Q543,774 534,800ZM480,440Q546,440 593,393Q640,346 640,280L640,232L570,291L480,182L390,291L320,232L320,280Q320,346 367,393Q414,440 480,440ZM440,880Q340,880 270,810Q200,740 200,640L200,560Q271,559 334,589Q397,619 440,670L440,517Q354,503 297,436.5Q240,370 240,280L240,144Q240,118 263,107.5Q286,97 306,114L380,178L449,94Q461,80 480,80Q499,80 511,94L580,178L654,114Q674,97 697,107.5Q720,118 720,144L720,280Q720,370 663,436.5Q606,503 520,517L520,670Q563,619 626,589Q689,559 760,560L760,640Q760,740 690,810Q620,880 520,880L440,880ZM480,311Q480,311 480,311Q480,311 480,311L480,311L480,311L480,311L480,311L480,311L480,311Q480,311 480,311Q480,311 480,311ZM607,727Q607,727 607,727Q607,727 607,727Q607,727 607,727Q607,727 607,727Q607,727 607,727Q607,727 607,727Q607,727 607,727Q607,727 607,727ZM354,727Q354,727 354,727Q354,727 354,727Q354,727 354,727Q354,727 354,727Q354,727 354,727Q354,727 354,727Q354,727 354,727Q354,727 354,727Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M680,880Q663,880 651.5,868.5Q640,857 640,840Q640,823 651.5,811.5Q663,800 680,800L800,800L800,720L680,720Q663,720 651.5,708.5Q640,697 640,680Q640,663 651.5,651.5Q663,640 680,640L800,640L800,560L680,560Q663,560 651.5,548.5Q640,537 640,520Q640,503 651.5,491.5Q663,480 680,480L800,480L800,400L680,400Q663,400 651.5,388.5Q640,377 640,360Q640,343 651.5,331.5Q663,320 680,320L800,320L800,240L680,240Q663,240 651.5,228.5Q640,217 640,200Q640,183 651.5,171.5Q663,160 680,160L840,160Q873,160 896.5,183.5Q920,207 920,240L920,800Q920,833 896.5,856.5Q873,880 840,880L680,880ZM256,800L384,800Q384,800 384,800Q384,800 384,800L502,474Q502,474 502,474Q502,474 502,474L378,400L262,400L138,474Q138,474 138,474Q138,474 138,474L256,800Q256,800 256,800Q256,800 256,800ZM320,600Q320,600 320,600Q320,600 320,600L320,600Q320,600 320,600Q320,600 320,600L320,600L320,600L320,600Q320,600 320,600Q320,600 320,600L320,600Q320,600 320,600Q320,600 320,600L320,600ZM181,827L63,502Q53,475 62.5,448Q72,421 97,406L240,320L240,120Q240,103 251.5,91.5Q263,80 280,80L360,80Q377,80 388.5,91.5Q400,103 400,120L400,320L543,406Q568,421 577.5,448Q587,475 577,502L459,827Q451,851 430.5,865.5Q410,880 384,880L256,880Q230,880 209.5,865.5Q189,851 181,827Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M400,840Q383,840 371.5,828.5Q360,817 360,800L360,320L208,320Q189,320 177,306Q165,292 169,273Q182,205 236.5,162.5Q291,120 360,120L560,120Q577,120 588.5,131.5Q600,143 600,160L600,240L702,138Q710,130 721.5,125Q733,120 745,120L760,120Q777,120 788.5,131.5Q800,143 800,160L800,400Q800,417 788.5,428.5Q777,440 760,440L745,440Q733,440 721.5,435Q710,430 702,422L600,320L600,800Q600,817 588.5,828.5Q577,840 560,840L400,840ZM440,760L520,760L520,520L480,520L440,520L440,760ZM440,440L520,440L520,200L360,200Q334,200 311,210.5Q288,221 271,240L440,240L440,440ZM480,480L480,480L480,480L480,480L480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480L480,480L480,480L480,480Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M420,540L420,620Q420,645 437.5,662.5Q455,680 480,680Q505,680 522.5,662.5Q540,645 540,620L540,540L620,540Q645,540 662.5,522.5Q680,505 680,480Q680,455 662.5,437.5Q645,420 620,420L540,420L540,340Q540,315 522.5,297.5Q505,280 480,280Q455,280 437.5,297.5Q420,315 420,340L420,420L340,420Q315,420 297.5,437.5Q280,455 280,480Q280,505 297.5,522.5Q315,540 340,540L420,540ZM200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,760L760,760Q760,760 760,760Q760,760 760,760L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760ZM200,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L200,760Q200,760 200,760Q200,760 200,760L200,200Q200,200 200,200Q200,200 200,200Z"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M80,760Q63,760 51.5,748.5Q40,737 40,720L40,200Q40,183 51.5,171.5Q63,160 80,160Q97,160 108.5,171.5Q120,183 120,200L120,560L440,560L440,320Q440,287 463.5,263.5Q487,240 520,240L760,240Q826,240 873,287Q920,334 920,400L920,720Q920,737 908.5,748.5Q897,760 880,760Q863,760 851.5,748.5Q840,737 840,720L840,640L120,640L120,720Q120,737 108.5,748.5Q97,760 80,760ZM280,520Q230,520 195,485Q160,450 160,400Q160,350 195,315Q230,280 280,280Q330,280 365,315Q400,350 400,400Q400,450 365,485Q330,520 280,520ZM520,560L840,560L840,400Q840,367 816.5,343.5Q793,320 760,320L520,320L520,560ZM280,440Q297,440 308.5,428.5Q320,417 320,400Q320,383 308.5,371.5Q297,360 280,360Q263,360 251.5,371.5Q240,383 240,400Q240,417 251.5,428.5Q263,440 280,440ZM280,400Q280,400 280,400Q280,400 280,400Q280,400 280,400Q280,400 280,400Q280,400 280,400Q280,400 280,400Q280,400 280,400Q280,400 280,400ZM520,320L520,320Q520,320 520,320Q520,320 520,320L520,560L520,560L520,320Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M120,400Q120,349 149.5,308Q179,267 224,250Q242,159 313.5,99.5Q385,40 480,40Q575,40 646.5,99.5Q718,159 736,250Q781,267 810.5,308Q840,349 840,400Q840,475 787,519Q734,563 668,560L517,852Q512,863 502.5,868Q493,873 482,873Q471,873 461,868Q451,863 446,852L294,560Q223,563 171.5,519Q120,475 120,400ZM280,480Q295,480 309.5,475Q324,470 336,458L358,436L384,452Q405,466 429.5,473Q454,480 480,480Q506,480 530.5,473Q555,466 576,452L602,436L624,458Q636,470 650.5,475Q665,480 680,480Q713,480 736.5,456.5Q760,433 760,400Q760,370 741,347.5Q722,325 692,320L662,316L660,284Q655,215 603,167.5Q551,120 480,120Q409,120 357,167.5Q305,215 300,284L298,316L268,322Q238,328 219,349Q200,370 200,400Q200,433 223.5,456.5Q247,480 280,480ZM482,746L590,536L590,536Q566,548 538,554Q510,560 480,560Q453,560 425.5,554Q398,548 372,536L372,536L482,746ZM480,300L480,300L480,300Q480,300 480,300Q480,300 480,300Q480,300 480,300Q480,300 480,300Q480,300 480,300Q480,300 480,300L480,300L480,300Q480,300 480,300Q480,300 480,300Q480,300 480,300Q480,300 480,300L480,300L480,300Q480,300 480,300Q480,300 480,300Q480,300 480,300Q480,300 480,300Q480,300 480,300Q480,300 480,300L480,300L480,300Q480,300 480,300Q480,300 480,300Q480,300 480,300Q480,300 480,300Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M480,795Q463,795 447,787.5Q431,780 419,766L113,400Q104,389 99.5,376Q95,363 95,349Q95,340 96.5,330.5Q98,321 103,313L178,164Q189,144 207.5,132Q226,120 249,120L711,120Q734,120 752.5,132Q771,144 782,164L857,313Q862,321 863.5,330.5Q865,340 865,349Q865,363 860.5,376Q856,389 847,400L541,766Q529,780 513,787.5Q497,795 480,795ZM385,320L575,320L515,200L445,200L385,320ZM440,667L440,400L218,400L440,667ZM520,667L742,400L520,400L520,667ZM664,320L770,320L710,200Q710,200 710,200Q710,200 710,200L604,200L664,320ZM190,320L296,320L356,200L250,200Q250,200 250,200Q250,200 250,200L190,320Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M280,920Q267,920 258.5,911.5Q250,903 250,890L250,760L220,760Q178,760 149,731Q120,702 120,660Q120,618 149,589Q178,560 220,560L250,560L250,520L160,520Q143,520 131.5,508.5Q120,497 120,480L120,360Q120,343 131.5,331.5Q143,320 160,320L250,320L250,280L220,280Q178,280 149,251Q120,222 120,180Q120,138 149,109Q178,80 220,80L250,80L250,70Q250,57 258.5,48.5Q267,40 280,40Q293,40 301.5,48.5Q310,57 310,70L310,80L340,80Q382,80 411,109Q440,138 440,180Q440,222 411,251Q382,280 340,280L310,280L310,320L400,320Q417,320 428.5,331.5Q440,343 440,360L440,480Q440,497 428.5,508.5Q417,520 400,520L310,520L310,560L340,560Q382,560 411,589Q440,618 440,660Q440,702 411,731Q382,760 340,760L310,760L310,890Q310,903 301.5,911.5Q293,920 280,920ZM680,920Q667,920 658.5,911.5Q650,903 650,890L650,760L620,760Q578,760 549,731Q520,702 520,660Q520,618 549,589Q578,560 620,560L650,560L650,520L560,520Q543,520 531.5,508.5Q520,497 520,480L520,360Q520,343 531.5,331.5Q543,320 560,320L650,320L650,280L620,280Q578,280 549,251Q520,222 520,180Q520,138 549,109Q578,80 620,80L650,80L650,70Q650,57 658.5,48.5Q667,40 680,40Q693,40 701.5,48.5Q710,57 710,70L710,80L740,80Q782,80 811,109Q840,138 840,180Q840,222 811,251Q782,280 740,280L710,280L710,320L800,320Q817,320 828.5,331.5Q840,343 840,360L840,480Q840,497 828.5,508.5Q817,520 800,520L710,520L710,560L740,560Q782,560 811,589Q840,618 840,660Q840,702 811,731Q782,760 740,760L710,760L710,890Q710,903 701.5,911.5Q693,920 680,920ZM220,200L340,200Q348,200 354,194Q360,188 360,180Q360,172 354,166Q348,160 340,160L220,160Q212,160 206,166Q200,172 200,180Q200,188 206,194Q212,200 220,200ZM620,200L740,200Q748,200 754,194Q760,188 760,180Q760,172 754,166Q748,160 740,160L620,160Q612,160 606,166Q600,172 600,180Q600,188 606,194Q612,200 620,200ZM200,440L360,440L360,400L200,400L200,440ZM600,440L760,440L760,400L600,400L600,440ZM220,680L340,680Q348,680 354,674Q360,668 360,660Q360,652 354,646Q348,640 340,640L220,640Q212,640 206,646Q200,652 200,660Q200,668 206,674Q212,680 220,680ZM620,680L740,680Q748,680 754,674Q760,668 760,660Q760,652 754,646Q748,640 740,640L620,640Q612,640 606,646Q600,652 600,660Q600,668 606,674Q612,680 620,680ZM200,200L200,200Q200,200 200,194Q200,188 200,180Q200,172 200,166Q200,160 200,160L200,160Q200,160 200,166Q200,172 200,180Q200,188 200,194Q200,200 200,200ZM600,200L600,200Q600,200 600,194Q600,188 600,180Q600,172 600,166Q600,160 600,160L600,160Q600,160 600,166Q600,172 600,180Q600,188 600,194Q600,200 600,200ZM200,440L200,440L200,400L200,400L200,440ZM600,440L600,440L600,400L600,400L600,440ZM200,680L200,680Q200,680 200,674Q200,668 200,660Q200,652 200,646Q200,640 200,640L200,640Q200,640 200,646Q200,652 200,660Q200,668 200,674Q200,680 200,680ZM600,680L600,680Q600,680 600,674Q600,668 600,660Q600,652 600,646Q600,640 600,640L600,640Q600,640 600,646Q600,652 600,660Q600,668 600,674Q600,680 600,680Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M160,840Q127,840 103.5,816.5Q80,793 80,760L80,144Q80,137 86,134.5Q92,132 97,137L133,173Q139,179 147,179Q155,179 161,173L199,134Q205,128 213,128Q221,128 227,134L266,173Q272,179 280,179Q288,179 294,173L333,134Q339,128 347,128Q355,128 361,134L399,173Q405,179 413,179Q421,179 427,173L466,134Q472,128 480,128Q488,128 494,134L533,173Q539,179 547,179Q555,179 561,173L599,134Q605,128 613,128Q621,128 627,134L666,173Q672,179 680,179Q688,179 694,173L733,134Q739,128 747,128Q755,128 761,134L799,173Q805,179 813,179Q821,179 827,173L863,137Q868,132 874,134.5Q880,137 880,144L880,760Q880,793 856.5,816.5Q833,840 800,840L160,840ZM160,760L440,760L440,520L160,520L160,760Q160,760 160,760Q160,760 160,760ZM520,760L800,760Q800,760 800,760Q800,760 800,760L800,680L520,680L520,760ZM520,600L800,600L800,520L520,520L520,600ZM160,440L800,440L800,320L160,320L160,440Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M240,880Q207,880 183.5,856.5Q160,833 160,800L160,160Q160,127 183.5,103.5Q207,80 240,80L720,80Q753,80 776.5,103.5Q800,127 800,160L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM240,800L720,800Q720,800 720,800Q720,800 720,800L720,160Q720,160 720,160Q720,160 720,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800ZM480,760Q563,760 621.5,701.5Q680,643 680,560Q680,477 621.5,418.5Q563,360 480,360Q397,360 338.5,418.5Q280,477 280,560Q280,643 338.5,701.5Q397,760 480,760ZM480,692Q454,692 429.5,682.5Q405,673 386,654L574,466Q593,485 602.5,509.5Q612,534 612,560Q612,615 573.5,653.5Q535,692 480,692ZM320,280Q337,280 348.5,268.5Q360,257 360,240Q360,223 348.5,211.5Q337,200 320,200Q303,200 291.5,211.5Q280,223 280,240Q280,257 291.5,268.5Q303,280 320,280ZM440,280Q457,280 468.5,268.5Q480,257 480,240Q480,223 468.5,211.5Q457,200 440,200Q423,200 411.5,211.5Q400,223 400,240Q400,257 411.5,268.5Q423,280 440,280ZM240,800Q240,800 240,800Q240,800 240,800L240,160Q240,160 240,160Q240,160 240,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800L240,800Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M240,613Q254,606 269,603Q284,600 300,600L320,600L320,160L300,160Q275,160 257.5,177.5Q240,195 240,220L240,613ZM400,600L720,600L720,160Q720,160 720,160Q720,160 720,160L400,160L400,600ZM240,613Q240,613 240,613Q240,613 240,613L240,613L240,160L240,160Q240,160 240,160Q240,160 240,160L240,613ZM300,880Q242,880 201,839Q160,798 160,740L160,220Q160,162 201,121Q242,80 300,80L720,80Q753,80 776.5,103.5Q800,127 800,160L800,661Q800,669 793.5,675.5Q787,682 770,690Q756,697 748,710Q740,723 740,740Q740,757 748,770.5Q756,784 770,790Q784,796 792,806.5Q800,817 800,829L800,839Q800,856 788.5,868Q777,880 760,880L300,880ZM300,800L673,800Q667,786 663.5,771.5Q660,757 660,740Q660,724 663,709Q666,694 673,680L300,680Q274,680 257,697.5Q240,715 240,740Q240,766 257,783Q274,800 300,800Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M200,880Q167,880 143.5,856.5Q120,833 120,800L120,320Q120,287 143.5,263.5Q167,240 200,240L280,240Q280,157 338.5,98.5Q397,40 480,40Q563,40 621.5,98.5Q680,157 680,240L760,240Q793,240 816.5,263.5Q840,287 840,320L840,800Q840,833 816.5,856.5Q793,880 760,880L200,880ZM200,800L760,800Q760,800 760,800Q760,800 760,800L760,320Q760,320 760,320Q760,320 760,320L200,320Q200,320 200,320Q200,320 200,320L200,800Q200,800 200,800Q200,800 200,800ZM360,240L600,240Q600,190 565,155Q530,120 480,120Q430,120 395,155Q360,190 360,240ZM200,800Q200,800 200,800Q200,800 200,800L200,320Q200,320 200,320Q200,320 200,320L200,320Q200,320 200,320Q200,320 200,320L200,800Q200,800 200,800Q200,800 200,800L200,800ZM479,560Q553,560 613,511Q673,462 672,401Q672,384 661,372Q650,360 633,360Q619,360 608,369Q597,378 592,396Q581,434 549,457Q517,480 479,480Q441,480 408.5,457Q376,434 366,396Q361,377 351,368.5Q341,360 327,360Q310,360 298.5,372Q287,384 287,401Q287,462 346,511Q405,560 479,560Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M420,800L540,800Q548,800 554,794Q560,788 560,780Q560,772 554,766Q548,760 540,760L420,760Q412,760 406,766Q400,772 400,780Q400,788 406,794Q412,800 420,800ZM280,920Q247,920 223.5,896.5Q200,873 200,840L200,120Q200,87 223.5,63.5Q247,40 280,40L680,40Q713,40 736.5,63.5Q760,87 760,120L760,840Q760,873 736.5,896.5Q713,920 680,920L280,920ZM280,720L280,840Q280,840 280,840Q280,840 280,840L680,840Q680,840 680,840Q680,840 680,840L680,720L280,720ZM280,640L680,640L680,240L280,240L280,640ZM280,160L680,160L680,120Q680,120 680,120Q680,120 680,120L280,120Q280,120 280,120Q280,120 280,120L280,160ZM280,720L280,720L280,840Q280,840 280,840Q280,840 280,840L280,840Q280,840 280,840Q280,840 280,840L280,720ZM280,160L280,120Q280,120 280,120Q280,120 280,120L280,120Q280,120 280,120Q280,120 280,120L280,160L280,160Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M80,880L80,800L160,800L160,440L134,440Q111,440 95.5,424Q80,408 80,385Q80,373 85.5,363Q91,353 101,346L434,112Q445,104 456.5,100.5Q468,97 480,97Q492,97 503.5,100.5Q515,104 526,112L859,346Q869,353 874.5,363.5Q880,374 880,386Q880,409 864.5,424.5Q849,440 826,440L800,440L800,800L880,800L880,880L80,880ZM720,800L720,346L480,178Q480,178 480,178Q480,178 480,178L240,346L240,800L720,800ZM480,648Q490,648 498.5,643.5Q507,639 513,630L560,560L560,680Q560,697 571.5,708.5Q583,720 600,720Q617,720 628.5,708.5Q640,697 640,680L640,473Q640,459 630.5,449.5Q621,440 607,440L578,440Q570,440 562.5,444Q555,448 550,455L480,560L412,458Q407,450 398,445Q389,440 379,440L360,440Q343,440 331.5,451.5Q320,463 320,480L320,680Q320,697 331.5,708.5Q343,720 360,720Q377,720 388.5,708.5Q400,697 400,680L400,560L447,630Q453,639 461.5,643.5Q470,648 480,648ZM720,800L240,800L240,800L480,800Q480,800 480,800Q480,800 480,800L720,800L720,800Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M280,720L280,560L81,262Q67,242 79,221Q91,200 115,200L525,200Q549,200 561,221Q573,242 559,262L360,560L360,720L400,720Q417,720 428.5,731.5Q440,743 440,760Q440,777 428.5,788.5Q417,800 400,800L240,800Q223,800 211.5,788.5Q200,777 200,760Q200,743 211.5,731.5Q223,720 240,720L280,720ZM236,360L404,360L460,280L180,280L236,360ZM640,800Q590,800 555,765Q520,730 520,680Q520,630 555,595Q590,560 640,560Q651,560 661,561.5Q671,563 680,568L680,240Q680,223 691.5,211.5Q703,200 720,200L820,200Q845,200 862.5,217.5Q880,235 880,260Q880,285 862.5,302.5Q845,320 820,320L760,320L760,680Q760,730 725,765Q690,800 640,800Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M274,600Q305,600 329.5,582Q354,564 364,535L379,489Q395,441 371,400.5Q347,360 302,360L161,360L180,517Q185,552 211.5,576Q238,600 274,600ZM686,600Q722,600 748.5,576Q775,552 780,517L799,360L659,360Q614,360 590,401Q566,442 582,490L596,535Q606,564 630.5,582Q655,600 686,600ZM274,680Q208,680 158.5,636.5Q109,593 101,527L80,360L80,360Q63,360 51.5,348.5Q40,337 40,320Q40,303 51.5,291.5Q63,280 80,280L302,280Q346,280 382.5,301.5Q419,323 440,360L521,360Q542,323 578.5,301.5Q615,280 659,280L880,280Q897,280 908.5,291.5Q920,303 920,320Q920,337 908.5,348.5Q897,360 880,360L880,360L859,527Q851,593 801.5,636.5Q752,680 686,680Q629,680 583.5,647.5Q538,615 520,561L505,516Q503,509 501,501.5Q499,494 497,480L463,480Q461,492 459,499.5Q457,507 455,514L440,560Q422,614 376.5,647Q331,680 274,680Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M440,777L440,503L200,364L200,638Q200,638 200,638Q200,638 200,638L440,777ZM520,777L760,638Q760,638 760,638Q760,638 760,638L760,364L520,503L520,777ZM440,869L160,708Q141,697 130.5,679Q120,661 120,639L120,321Q120,299 130.5,281Q141,263 160,252L440,91Q459,80 480,80Q501,80 520,91L800,252Q819,263 829.5,281Q840,299 840,321L840,639Q840,661 829.5,679Q819,697 800,708L520,869Q501,880 480,880Q459,880 440,869ZM640,341L717,297L480,160Q480,160 480,160Q480,160 480,160L402,205L640,341ZM480,434L558,389L321,252L243,297L480,434Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M400,600L400,760Q400,793 376.5,816.5Q353,840 320,840Q287,840 263.5,816.5Q240,793 240,760L240,200Q240,167 263.5,143.5Q287,120 320,120L520,120Q620,120 690,190Q760,260 760,360Q760,460 690,530Q620,600 520,600L400,600ZM400,440L528,440Q561,440 584.5,416.5Q608,393 608,360Q608,327 584.5,303.5Q561,280 528,280L400,280L400,440Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M204,840Q162,840 138.5,807Q115,774 128,735L200,520L128,305Q115,266 138.5,233Q162,200 204,200L628,200L669,87Q676,67 694.5,58.5Q713,50 733,57Q753,64 761.5,82.5Q770,101 763,121L734,200L756,200Q798,200 821.5,233Q845,266 832,305L760,520L832,735Q845,774 821.5,807Q798,840 756,840L204,840ZM440,680L520,680L520,560L640,560L640,480L520,480L520,360L440,360L440,480L320,480L320,560L440,560L440,680ZM204,760L756,760Q756,760 756,760Q756,760 756,760L676,520L756,280Q756,280 756,280Q756,280 756,280L204,280Q204,280 204,280Q204,280 204,280L284,520L204,760Q204,760 204,760Q204,760 204,760ZM480,520Q480,520 480,520Q480,520 480,520L480,520L480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520L480,520L480,520Q480,520 480,520Q480,520 480,520L480,520Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M480,700Q555,700 607.5,647.5Q660,595 660,520Q660,445 607.5,392.5Q555,340 480,340Q405,340 352.5,392.5Q300,445 300,520Q300,595 352.5,647.5Q405,700 480,700ZM480,620Q438,620 409,591Q380,562 380,520Q380,478 409,449Q438,420 480,420Q522,420 551,449Q580,478 580,520Q580,562 551,591Q522,620 480,620ZM160,840Q127,840 103.5,816.5Q80,793 80,760L80,280Q80,247 103.5,223.5Q127,200 160,200L286,200L336,146Q347,134 362.5,127Q378,120 395,120L565,120Q582,120 597.5,127Q613,134 624,146L674,200L800,200Q833,200 856.5,223.5Q880,247 880,280L880,760Q880,793 856.5,816.5Q833,840 800,840L160,840ZM160,760L800,760Q800,760 800,760Q800,760 800,760L800,280Q800,280 800,280Q800,280 800,280L638,280L565,200L395,200L322,280L160,280Q160,280 160,280Q160,280 160,280L160,760Q160,760 160,760Q160,760 160,760ZM480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520L480,520L480,520L480,520L480,520L480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520L480,520Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M480,120Q568,120 659,150Q750,180 820,234Q836,246 844,263Q852,280 852,298Q852,309 848.5,320.5Q845,332 838,343L547,780Q535,798 517,807Q499,816 480,816Q461,816 443,807Q425,798 413,780L122,343Q115,332 112,321Q109,310 109,299Q109,281 117,264Q125,247 141,235Q211,182 301.5,151Q392,120 480,120ZM480,200Q397,200 327,228Q257,256 188,298Q188,298 188,298Q188,298 188,298Q188,298 188,298Q188,298 188,298L480,736Q480,736 480,736Q480,736 480,736Q480,736 480,736Q480,736 480,736L772,298Q772,298 772,298Q772,298 772,298Q772,298 772,298Q772,298 772,298Q703,255 633.5,227.5Q564,200 480,200ZM380,400Q405,400 422.5,382.5Q440,365 440,340Q440,315 422.5,297.5Q405,280 380,280Q355,280 337.5,297.5Q320,315 320,340Q320,365 337.5,382.5Q355,400 380,400ZM480,600Q505,600 522.5,582.5Q540,565 540,540Q540,515 522.5,497.5Q505,480 480,480Q455,480 437.5,497.5Q420,515 420,540Q420,565 437.5,582.5Q455,600 480,600ZM480,736Q480,736 480,736Q480,736 480,736Q480,736 480,736Q480,736 480,736Q480,736 480,736Q480,736 480,736L480,736Q480,736 480,736Q480,736 480,736Q480,736 480,736Q480,736 480,736L480,736Q480,736 480,736Q480,736 480,736Q480,736 480,736Q480,736 480,736Q480,736 480,736Q480,736 480,736Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M480,659Q579,579 629.5,505Q680,431 680,366Q680,310 659.5,270.5Q639,231 609,206.5Q579,182 544,171Q509,160 480,160Q451,160 416,171Q381,182 351,206.5Q321,231 300.5,270.5Q280,310 280,366Q280,431 330.5,505Q381,579 480,659ZM480,744Q470,744 460.5,741Q451,738 443,732Q362,669 281,573.5Q200,478 200,366Q200,295 225.5,241.5Q251,188 291,152Q331,116 381,98Q431,80 480,80Q529,80 579,98Q629,116 669,152Q709,188 734.5,241.5Q760,295 760,366Q760,478 679,573.5Q598,669 517,732Q509,738 499.5,741Q490,744 480,744ZM480,440Q513,440 536.5,416.5Q560,393 560,360Q560,327 536.5,303.5Q513,280 480,280Q447,280 423.5,303.5Q400,327 400,360Q400,393 423.5,416.5Q447,440 480,440ZM240,880Q223,880 211.5,868.5Q200,857 200,840Q200,823 211.5,811.5Q223,800 240,800L720,800Q737,800 748.5,811.5Q760,823 760,840Q760,857 748.5,868.5Q737,880 720,880L240,880ZM480,366Q480,366 480,366Q480,366 480,366Q480,366 480,366Q480,366 480,366Q480,366 480,366Q480,366 480,366Q480,366 480,366Q480,366 480,366Q480,366 480,366Q480,366 480,366Q480,366 480,366Q480,366 480,366Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M480,540L565,605Q571,610 577,605.5Q583,601 581,594L548,488L637,418Q642,413 640,406.5Q638,400 631,400L524,400L490,293Q488,286 480,286Q472,286 470,293L436,400L329,400Q322,400 319.5,406.5Q317,413 322,418L410,488L377,595Q375,602 381,606.5Q387,611 393,606L480,540ZM480,876Q473,876 467,875Q461,874 455,872Q320,827 240,705.5Q160,584 160,444L160,255Q160,230 174.5,210Q189,190 212,181L452,91Q466,86 480,86Q494,86 508,91L748,181Q771,190 785.5,210Q800,230 800,255L800,444Q800,584 720,705.5Q640,827 505,872Q499,874 493,875Q487,876 480,876ZM480,796Q584,763 652,664Q720,565 720,444L720,255Q720,255 720,255Q720,255 720,255L480,165Q480,165 480,165Q480,165 480,165L240,255Q240,255 240,255Q240,255 240,255L240,444Q240,565 308,664Q376,763 480,796ZM480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M120,880Q103,880 91.5,868.5Q80,857 80,840L80,520Q80,503 91.5,491.5Q103,480 120,480L240,480L240,320Q240,220 310,150Q380,80 480,80L640,80Q740,80 810,150Q880,220 880,320L880,840Q880,857 868.5,868.5Q857,880 840,880Q823,880 811.5,868.5Q800,857 800,840L800,760L640,760L640,840Q640,857 628.5,868.5Q617,880 600,880L120,880ZM640,680L800,680L800,320Q800,254 753,207Q706,160 640,160L480,160Q414,160 367,207Q320,254 320,320L320,480L600,480Q617,480 628.5,491.5Q640,503 640,520L640,680ZM360,674Q360,674 360,674Q360,674 360,674L560,560L160,560L360,674ZM160,800L560,800L560,630L400,721Q391,726 381,729Q371,732 360,732Q349,732 339,729Q329,726 320,721L160,630L160,800ZM160,560L160,586Q160,569 160,585Q160,601 160,612Q160,619 160,625.5Q160,632 160,639L160,630L160,800L160,800L160,630L160,639Q160,632 160,625.5Q160,619 160,612Q160,601 160,585Q160,569 160,586L160,560L160,560ZM440,400Q423,400 411.5,388.5Q400,377 400,360Q400,343 411.5,331.5Q423,320 440,320L680,320Q697,320 708.5,331.5Q720,343 720,360Q720,377 708.5,388.5Q697,400 680,400L440,400Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M320,760L600,760Q600,760 600,760Q600,760 600,760L600,360L520,360Q492,360 474,374Q456,388 431,415Q411,437 384.5,460.5Q358,484 320,495L320,760Q320,760 320,760Q320,760 320,760ZM240,760L240,494Q188,480 154,438Q120,396 120,340Q120,287 150.5,246Q181,205 229,189Q252,141 297.5,111Q343,81 400,81Q435,81 465.5,93Q496,105 521,125Q531,123 540,121.5Q549,120 560,120Q626,120 673,167Q720,214 720,280Q720,302 714.5,322Q709,342 698,360L760,360Q793,360 816.5,383.5Q840,407 840,440L840,680Q840,713 816.5,736.5Q793,760 760,760L680,760L680,760Q680,793 656.5,816.5Q633,840 600,840L320,840Q287,840 263.5,816.5Q240,793 240,760ZM200,340Q200,373 223.5,396.5Q247,420 280,420Q312,420 334.5,399Q357,378 381,352Q406,325 437.5,302.5Q469,280 520,280L640,280Q640,247 616.5,223.5Q593,200 560,200Q535,200 518,206.5Q501,213 501,213L470,187Q459,178 441.5,169.5Q424,161 400,161Q368,161 341.5,178Q315,195 301,224L287,254L255,265Q230,273 215,293.5Q200,314 200,340ZM680,680L760,680Q760,680 760,680Q760,680 760,680L760,440Q760,440 760,440Q760,440 760,440L680,440L680,680ZM320,760Q320,760 320,760Q320,760 320,760L320,760Q358,760 384.5,760Q411,760 431,760Q456,760 474,760Q492,760 520,760L600,760L600,760Q600,760 600,760Q600,760 600,760L320,760Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M240,760L240,600L200,600Q183,600 171.5,588.5Q160,577 160,560Q160,543 171.5,531.5Q183,520 200,520L244,520Q256,445 310.5,390.5Q365,336 440,324L440,160Q440,143 451.5,131.5Q463,120 480,120L680,120Q697,120 708.5,131.5Q720,143 720,160L720,240Q720,257 708.5,268.5Q697,280 680,280L520,280L520,324Q595,336 649.5,390.5Q704,445 716,520L760,520Q777,520 788.5,531.5Q800,543 800,560Q800,577 788.5,588.5Q777,600 760,600L720,600L720,760L840,760Q857,760 868.5,771.5Q880,783 880,800Q880,817 868.5,828.5Q857,840 840,840L120,840Q103,840 91.5,828.5Q80,817 80,800Q80,783 91.5,771.5Q103,760 120,760L240,760ZM320,760L440,760L440,600L320,600L320,760ZM520,760L640,760L640,600L520,600L520,760ZM326,520L634,520Q620,467 577,433.5Q534,400 480,400Q426,400 383,433.5Q340,467 326,520ZM480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520L480,520Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M160,620L160,240Q160,187 187.5,155.5Q215,124 260,107.5Q305,91 362.5,85.5Q420,80 480,80Q546,80 604.5,85.5Q663,91 706.5,107.5Q750,124 775,155.5Q800,187 800,240L800,620Q800,679 759.5,719.5Q719,760 660,760L680,780Q697,797 688,818.5Q679,840 655,840Q648,840 641.5,837.5Q635,835 630,830L560,760L400,760L330,830Q325,835 318.5,837.5Q312,840 305,840Q282,840 272.5,818.5Q263,797 280,780L300,760Q241,760 200.5,719.5Q160,679 160,620ZM480,160Q374,160 325,172.5Q276,185 258,200L706,200Q691,183 641.5,171.5Q592,160 480,160ZM240,400L440,400L440,280L240,280L240,400ZM660,480L300,480Q274,480 257,480Q240,480 240,480L240,480L720,480L720,480Q720,480 703,480Q686,480 660,480ZM520,400L720,400L720,280L520,280L520,400ZM340,640Q366,640 383,623Q400,606 400,580Q400,554 383,537Q366,520 340,520Q314,520 297,537Q280,554 280,580Q280,606 297,623Q314,640 340,640ZM620,640Q646,640 663,623Q680,606 680,580Q680,554 663,537Q646,520 620,520Q594,520 577,537Q560,554 560,580Q560,606 577,623Q594,640 620,640ZM300,680L660,680Q686,680 703,663Q720,646 720,620L720,480L240,480L240,620Q240,646 257,663Q274,680 300,680ZM480,200Q592,200 641.5,200Q691,200 706,200L258,200Q276,200 325,200Q374,200 480,200Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M320,810Q222,772 151,697.5Q80,623 80,520Q80,503 91.5,491.5Q103,480 120,480L160,480L160,196Q160,181 170,170Q180,159 195,157L847,84Q861,82 870.5,91Q880,100 880,114Q880,125 872,133.5Q864,142 853,143L420,192L420,260L850,260Q863,260 871.5,268.5Q880,277 880,290Q880,303 871.5,311.5Q863,320 850,320L420,320L420,480L840,480Q857,480 868.5,491.5Q880,503 880,520Q880,623 809,697.5Q738,772 640,810L640,840Q640,857 628.5,868.5Q617,880 600,880L360,880Q343,880 331.5,868.5Q320,857 320,840L320,810ZM320,260L360,260L360,199L320,203L320,260ZM220,260L260,260L260,210L220,214L220,260ZM320,480L360,480L360,320L320,320L320,480ZM220,480L260,480L260,320L220,320L220,480ZM400,800L560,800L560,756L610,736Q671,712 718,667Q765,622 786,560L174,560Q195,622 242,667.5Q289,713 350,736L400,756L400,800ZM480,560L480,560L480,560Q480,560 480,560Q480,560 480,560L480,560Q480,560 480,560Q480,560 480,560L480,560L480,560L480,560Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M280,360L280,120Q280,103 291.5,91.5Q303,80 320,80Q337,80 348.5,91.5Q360,103 360,120L360,360L400,360L400,120Q400,103 411.5,91.5Q423,80 440,80Q457,80 468.5,91.5Q480,103 480,120L480,360Q480,416 445.5,458Q411,500 360,514L360,840Q360,857 348.5,868.5Q337,880 320,880Q303,880 291.5,868.5Q280,857 280,840L280,514Q229,500 194.5,458Q160,416 160,360L160,120Q160,103 171.5,91.5Q183,80 200,80Q217,80 228.5,91.5Q240,103 240,120L240,360L280,360ZM680,560L600,560Q583,560 571.5,548.5Q560,537 560,520L560,280Q560,210 611.5,145Q663,80 718,80Q736,80 748,94Q760,108 760,127L760,840Q760,857 748.5,868.5Q737,880 720,880Q703,880 691.5,868.5Q680,857 680,840L680,560Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M240,880Q207,880 183.5,856.5Q160,833 160,800L160,320Q160,264 194,222Q228,180 280,166L280,120Q280,103 291.5,91.5Q303,80 320,80L360,80Q377,80 388.5,91.5Q400,103 400,120L400,160L560,160L560,120Q560,103 571.5,91.5Q583,80 600,80L640,80Q657,80 668.5,91.5Q680,103 680,120L680,166Q732,180 766,222Q800,264 800,320L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM240,800L720,800Q720,800 720,800Q720,800 720,800L720,320Q720,287 696.5,263.5Q673,240 640,240L320,240Q287,240 263.5,263.5Q240,287 240,320L240,800Q240,800 240,800Q240,800 240,800ZM580,560L580,600Q580,617 591.5,628.5Q603,640 620,640Q637,640 648.5,628.5Q660,617 660,600L660,520Q660,503 648.5,491.5Q637,480 620,480L340,480Q323,480 311.5,491.5Q300,503 300,520Q300,537 311.5,548.5Q323,560 340,560L580,560ZM480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520L480,520Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M216,380Q255,380 290,394Q325,408 354,435L736,800L760,800Q777,800 788.5,788.5Q800,777 800,760Q800,752 798.5,743Q797,734 788,725L605,542L534,328Q534,328 534,328Q534,328 534,328L460,346Q422,356 391,332Q360,308 360,269L360,185Q360,185 360,185Q360,185 360,185L332,171Q332,171 332,171Q332,171 332,171L178,377Q177,378 177,378.5Q177,379 176,380L216,380ZM216,460L170,460Q173,467 177.5,473Q182,479 188,484L512,779Q523,790 537,795Q551,800 566,800L620,800L299,493Q282,476 260.5,468Q239,460 216,460ZM566,880Q536,880 509,869Q482,858 459,838L134,543Q88,501 82.5,440Q77,379 114,329L268,123Q285,100 313.5,92.5Q342,85 368,99L396,113Q417,124 428.5,143Q440,162 440,185L440,269L514,250Q544,242 572,257.5Q600,273 610,302L675,498L845,668Q865,688 872.5,711Q880,734 880,760Q880,810 845,845Q810,880 760,880L566,880Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM680,380L734,362L750,308Q718,260 673,225.5Q628,191 574,174L520,212L520,268L680,380ZM280,380L440,268L440,212L386,174Q332,191 287,225.5Q242,260 210,308L226,362L280,380ZM238,688L284,684L314,630L256,456L200,436L160,466Q160,531 178,584.5Q196,638 238,688ZM480,800Q506,800 531,796Q556,792 580,784L608,724L582,680L378,680L352,724L380,784Q404,792 429,796Q454,800 480,800ZM390,600L570,600L626,440L480,338L336,440L390,600ZM722,688Q764,638 782,584.5Q800,531 800,466L760,438L704,456L646,630L676,684L722,688Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M640,880Q540,880 470,810Q400,740 400,640Q400,540 470,470Q540,400 640,400Q740,400 810,470Q880,540 880,640Q880,740 810,810Q740,880 640,880ZM640,800Q706,800 753,753Q800,706 800,640Q800,574 753,527Q706,480 640,480Q574,480 527,527Q480,574 480,640Q480,706 527,753Q574,800 640,800ZM160,800Q127,800 103.5,776.5Q80,753 80,720L80,416Q80,408 81.5,400Q83,392 86,384L166,200L160,200Q143,200 131.5,188.5Q120,177 120,160L120,120Q120,103 131.5,91.5Q143,80 160,80L440,80Q457,80 468.5,91.5Q480,103 480,120L480,160Q480,177 468.5,188.5Q457,200 440,200L434,200L500,352Q481,362 464,373Q447,384 432,398L348,200L252,200L160,416L160,720Q160,720 160,720Q160,720 160,720L330,720Q335,741 343.5,761.5Q352,782 364,800L160,800ZM640,360Q598,360 569,331Q540,302 540,260Q540,218 569,189Q598,160 640,160L640,360Q640,318 669,289Q698,260 740,260Q782,260 811,289Q840,318 840,360L640,360Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M280,920Q263,920 251.5,908.5Q240,897 240,880L240,560L160,560Q118,560 89,531Q60,502 60,460Q60,418 89,389Q118,360 160,360L240,360L240,320L160,320Q118,320 89,291Q60,262 60,220Q60,178 89,149Q118,120 160,120L240,120L240,80Q240,63 251.5,51.5Q263,40 280,40Q297,40 308.5,51.5Q320,63 320,80L320,120L400,120Q442,120 471,149Q500,178 500,220Q500,262 471,291Q442,320 400,320L320,320L320,360L400,360Q442,360 471,389Q500,418 500,460Q500,502 471,531Q442,560 400,560L320,560L320,880Q320,897 308.5,908.5Q297,920 280,920ZM160,480L400,480Q408,480 414,474Q420,468 420,460Q420,452 414,446Q408,440 400,440L160,440Q152,440 146,446Q140,452 140,460Q140,468 146,474Q152,480 160,480ZM160,240L400,240Q408,240 414,234Q420,228 420,220Q420,212 414,206Q408,200 400,200L160,200Q152,200 146,206Q140,212 140,220Q140,228 146,234Q152,240 160,240ZM680,840L680,554Q627,540 593.5,497.5Q560,455 560,400L560,80Q560,63 571.5,51.5Q583,40 600,40L840,40Q857,40 868.5,51.5Q880,63 880,80L880,400Q880,455 846.5,497.5Q813,540 760,554L760,840L800,840Q817,840 828.5,851.5Q840,863 840,880Q840,897 828.5,908.5Q817,920 800,920L640,920Q623,920 611.5,908.5Q600,897 600,880Q600,863 611.5,851.5Q623,840 640,840L680,840ZM720,480Q753,480 776.5,456.5Q800,433 800,400L800,320L640,320L640,400Q640,433 663.5,456.5Q687,480 720,480ZM640,240L800,240L800,120L640,120L640,240ZM140,480L140,480Q140,480 140,474Q140,468 140,460Q140,452 140,446Q140,440 140,440L140,440Q140,440 140,446Q140,452 140,460Q140,468 140,474Q140,480 140,480ZM140,240L140,240Q140,240 140,234Q140,228 140,220Q140,212 140,206Q140,200 140,200L140,200Q140,200 140,206Q140,212 140,220Q140,228 140,234Q140,240 140,240ZM720,320Q720,320 720,320Q720,320 720,320L720,320L720,320L720,320Q720,320 720,320Q720,320 720,320Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M520,582Q423,582 378,593Q333,604 301,636L165,772Q154,783 137.5,783Q121,783 109,772Q97,760 97,743.5Q97,727 109,715L244,580Q275,549 286.5,502.5Q298,456 298,360Q298,302 324,246Q350,190 398,142Q489,51 599,39Q709,27 780,100Q852,172 840,282Q828,392 738,482Q690,530 634,556Q578,582 520,582ZM412,466Q459,512 539,500Q619,488 682,425Q746,361 758.5,281.5Q771,202 724,157Q676,109 598.5,121Q521,133 456,197Q393,260 379.5,339.5Q366,419 412,466ZM720,920Q654,920 607,873Q560,826 560,760Q560,694 607,647Q654,600 720,600Q786,600 833,647Q880,694 880,760Q880,826 833,873Q786,920 720,920ZM720,840Q753,840 776.5,816.5Q800,793 800,760Q800,727 776.5,703.5Q753,680 720,680Q687,680 663.5,703.5Q640,727 640,760Q640,793 663.5,816.5Q687,840 720,840ZM720,760Q720,760 720,760Q720,760 720,760Q720,760 720,760Q720,760 720,760Q720,760 720,760Q720,760 720,760Q720,760 720,760Q720,760 720,760Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M760,300Q777,300 788.5,288.5Q800,277 800,260Q800,243 788.5,231.5Q777,220 760,220Q743,220 731.5,231.5Q720,243 720,260Q720,277 731.5,288.5Q743,300 760,300ZM600,300Q617,300 628.5,288.5Q640,277 640,260Q640,243 628.5,231.5Q617,220 600,220Q583,220 571.5,231.5Q560,243 560,260Q560,277 571.5,288.5Q583,300 600,300ZM680,360Q651,360 624.5,371.5Q598,383 586,409Q581,419 588.5,427.5Q596,436 608,436L752,436Q764,436 771.5,427.5Q779,419 774,409Q762,383 735.5,371.5Q709,360 680,360ZM280,880Q180,880 110,810Q40,740 40,640L40,440Q40,407 63.5,383.5Q87,360 120,360L440,360Q473,360 496.5,383.5Q520,407 520,440L520,640Q520,740 450,810Q380,880 280,880ZM280,800Q346,800 393,753Q440,706 440,640L440,440Q440,440 440,440Q440,440 440,440L120,440Q120,440 120,440Q120,440 120,440L120,640Q120,706 167,753Q214,800 280,800ZM680,600Q654,600 628.5,594.5Q603,589 580,578L580,484Q602,501 627.5,510.5Q653,520 680,520Q746,520 793,473Q840,426 840,360L840,160Q840,160 840,160Q840,160 840,160L520,160Q520,160 520,160Q520,160 520,160L520,300L440,300L440,160Q440,127 463.5,103.5Q487,80 520,80L840,80Q873,80 896.5,103.5Q920,127 920,160L920,360Q920,460 850,530Q780,600 680,600ZM200,580Q217,580 228.5,568.5Q240,557 240,540Q240,523 228.5,511.5Q217,500 200,500Q183,500 171.5,511.5Q160,523 160,540Q160,557 171.5,568.5Q183,580 200,580ZM360,580Q377,580 388.5,568.5Q400,557 400,540Q400,523 388.5,511.5Q377,500 360,500Q343,500 331.5,511.5Q320,523 320,540Q320,557 331.5,568.5Q343,580 360,580ZM280,716Q308,716 333,705Q358,694 372,670Q377,659 371,649.5Q365,640 354,640L206,640Q195,640 189,649.5Q183,659 188,670Q202,694 227,705Q252,716 280,716ZM280,620Q280,620 280,620Q280,620 280,620L280,620Q280,620 280,620Q280,620 280,620L280,620Q280,620 280,620Q280,620 280,620L280,620Q280,620 280,620Q280,620 280,620ZM670,340L670,340Q670,340 670,340Q670,340 670,340L670,340Q670,340 670,340Q670,340 670,340L670,340Q670,340 670,340Q670,340 670,340Q670,340 670,340Q670,340 670,340L670,340Q670,340 670,340Q670,340 670,340Q670,340 670,340Q670,340 670,340L670,340Q670,340 670,340Q670,340 670,340L670,340Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M140,760Q115,760 97.5,742.5Q80,725 80,700Q80,675 97.5,657.5Q115,640 140,640L650,640Q663,640 671.5,648.5Q680,657 680,670L680,730Q680,743 671.5,751.5Q663,760 650,760L140,760ZM750,760Q737,760 728.5,751.5Q720,743 720,730L720,670Q720,657 728.5,648.5Q737,640 750,640Q763,640 771.5,648.5Q780,657 780,670L780,730Q780,743 771.5,751.5Q763,760 750,760ZM850,760Q837,760 828.5,751.5Q820,743 820,730L820,670Q820,657 828.5,648.5Q837,640 850,640Q863,640 871.5,648.5Q880,657 880,670L880,730Q880,743 871.5,751.5Q863,760 850,760ZM750,600Q737,600 728.5,591.5Q720,583 720,570L720,548Q720,509 697,488.5Q674,468 642,468L580,468Q524,468 485,429Q446,390 446,334Q446,278 485,239Q524,200 580,200Q593,200 601.5,208.5Q610,217 610,230Q610,243 601.5,251.5Q593,260 580,260Q550,260 528,281Q506,302 506,334Q506,366 528,387Q550,408 580,408L642,408Q698,408 739,444Q780,480 780,534L780,570Q780,583 771.5,591.5Q763,600 750,600ZM850,600Q837,600 828.5,591.5Q820,583 820,570L820,510Q820,444 774,396Q728,348 660,348Q647,348 638.5,339.5Q630,331 630,318Q630,305 638.5,296.5Q647,288 660,288Q690,288 712,266Q734,244 734,214Q734,184 712,162Q690,140 660,140Q647,140 638.5,131.5Q630,123 630,110Q630,97 638.5,88.5Q647,80 660,80Q716,80 755,119Q794,158 794,214Q794,243 783,266.5Q772,290 754,310Q810,336 845,390Q880,444 880,510L880,570Q880,583 871.5,591.5Q863,600 850,600Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M220,840L220,580L200,580Q183,580 171.5,568.5Q160,557 160,540L160,360Q160,327 183.5,303.5Q207,280 240,280L360,280Q393,280 416.5,303.5Q440,327 440,360L440,540Q440,557 428.5,568.5Q417,580 400,580L380,580L380,840Q380,857 368.5,868.5Q357,880 340,880L260,880Q243,880 231.5,868.5Q220,857 220,840ZM300,240Q267,240 243.5,216.5Q220,193 220,160Q220,127 243.5,103.5Q267,80 300,80Q333,80 356.5,103.5Q380,127 380,160Q380,193 356.5,216.5Q333,240 300,240ZM600,840L600,640L535,640Q515,640 503,623.5Q491,607 498,587L582,334Q590,308 611.5,294Q633,280 660,280Q687,280 708.5,294Q730,308 738,334L822,587Q829,607 817,623.5Q805,640 785,640L720,640L720,840Q720,857 708.5,868.5Q697,880 680,880L640,880Q623,880 611.5,868.5Q600,857 600,840ZM660,240Q627,240 603.5,216.5Q580,193 580,160Q580,127 603.5,103.5Q627,80 660,80Q693,80 716.5,103.5Q740,127 740,160Q740,193 716.5,216.5Q693,240 660,240Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M160,700L160,320Q160,223 245,193Q330,163 440,160L470,100L310,100Q297,100 288.5,91.5Q280,83 280,70Q280,57 288.5,48.5Q297,40 310,40L650,40Q663,40 671.5,48.5Q680,57 680,70Q680,83 671.5,91.5Q663,100 650,100L550,100L520,160Q639,163 719.5,192.5Q800,222 800,320L800,700Q800,759 759.5,799.5Q719,840 660,840L680,860Q697,877 688,898.5Q679,920 655,920Q648,920 641.5,917.5Q635,915 630,910L560,840L400,840L330,910Q325,915 318.5,917.5Q312,920 305,920Q282,920 272.5,898.5Q263,877 280,860L300,840Q241,840 200.5,799.5Q160,759 160,700ZM660,560L300,560Q274,560 257,560Q240,560 240,560L240,560L720,560L720,560Q720,560 703,560Q686,560 660,560ZM480,720Q505,720 522.5,702.5Q540,685 540,660Q540,635 522.5,617.5Q505,600 480,600Q455,600 437.5,617.5Q420,635 420,660Q420,685 437.5,702.5Q455,720 480,720ZM478,280Q614,280 654,280Q694,280 706,280L256,280Q268,280 306,280Q344,280 478,280ZM240,480L720,480L720,360L240,360L240,480ZM300,760L660,760Q686,760 703,743Q720,726 720,700L720,560L240,560L240,700Q240,726 257,743Q274,760 300,760ZM478,240Q344,240 306,254.5Q268,269 256,280L706,280Q694,266 654,253Q614,240 478,240Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M280,640Q280,607 303.5,583.5Q327,560 360,560Q393,560 416.5,583.5Q440,607 440,640Q440,673 416.5,696.5Q393,720 360,720Q327,720 303.5,696.5Q280,673 280,640ZM200,920Q183,920 171.5,908.5Q160,897 160,880L160,560Q160,543 171.5,531.5Q183,520 200,520Q217,520 228.5,531.5Q240,543 240,560L240,760L480,760L480,600Q480,583 491.5,571.5Q503,560 520,560L720,560Q753,560 776.5,583.5Q800,607 800,640L800,880Q800,897 788.5,908.5Q777,920 760,920Q743,920 731.5,908.5Q720,897 720,880L720,840L240,840L240,880Q240,897 228.5,908.5Q217,920 200,920ZM512,306L406,412L415,470Q416,475 414.5,479Q413,483 409,487L405,491Q397,499 387.5,497Q378,495 373,486L339,422L271,385Q263,381 262,372.5Q261,364 267,358L275,350Q277,348 289,345L349,353L455,247L267,145Q256,140 255,128Q254,116 262,108L269,101Q274,96 279.5,94.5Q285,93 291,95L542,160L650,52Q662,40 679,40Q696,40 708,52Q720,64 720,81Q720,98 708,110L600,218L664,468Q666,475 664.5,481Q663,487 658,492L654,496Q645,505 632,503Q619,501 613,490L512,306ZM720,760L720,640Q720,640 720,640Q720,640 720,640L560,640L560,760L720,760ZM560,760L560,760L560,640L560,640Q560,640 560,640Q560,640 560,640L560,760Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M440,760L440,596Q354,582 297,516Q240,450 240,360L240,160Q240,143 251.5,131.5Q263,120 280,120L680,120Q697,120 708.5,131.5Q720,143 720,160L720,360Q720,450 663,516Q606,582 520,596L520,760L600,760Q617,760 628.5,771.5Q640,783 640,800Q640,817 628.5,828.5Q617,840 600,840L360,840Q343,840 331.5,828.5Q320,817 320,800Q320,783 331.5,771.5Q343,760 360,760L440,760ZM480,520Q536,520 578,486Q620,452 634,400L326,400Q340,452 382,486Q424,520 480,520ZM320,320L640,320L640,200L320,200L320,320ZM480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520Z"/>
</vector>

View File

@ -14,7 +14,7 @@ import java.io.File
object CrashReporter {
fun logException(e: Exception) {
if (e !is CancellationException) {
com.balsikandar.crashreporter.CrashReporter.logException(e)
CrashReporter.logException(e)
}
Log.e("MM20", Log.getStackTraceString(e))
}

1
core/devicepose/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,48 @@
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
}
android {
namespace = "de.mm20.launcher2.location"
compileSdk = 34
defaultConfig {
minSdk = 21
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation(libs.androidx.core)
implementation(libs.androidx.appcompat)
implementation(libs.materialcomponents.core)
implementation(libs.koin.android)
implementation(project(":core:ktx"))
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}

View File

21
core/devicepose/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

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