Add support for location plugins (#772)
* Cherry pick location refactor * Refactor :data:openstreetmaps to :data:locations * contract, plugin sdk * Implement serialization, module tweaks * Include check for out-of-date departures in SearchableItemVM.requestUpdatedSearchable() * settings for location plugins * Try not to be too lazy * more fiddling with the plugin SDK * add departures in MapView with mock data for debug builds * change icons * add boats * animate departure lazycolumn * Add MarqueeText for text overflow handling * Define height for Departures in case there is no map to display * Don't inclure railway / highway tags for OSM since there will be location plugins for that * sort by time * - Apply pre-merge changes to LocationSettings - Add banner warning about slowed down location search for large search radii * ditch `showLocationOnMap` * LocationItem: make `showOpeningSchedule` toggleable * LocationItem: make Navigation AssistChip work for people that don't have google maps installed * LocationItem: resolve TODOs * MapTiles: ditch unused code, animate userIndicator * Reintroduce departure list * Add LineColor * Add osm tag `stars` as `userRating` https://taginfo.openstreetmap.org/keys/stars#overview * typealias -> import * Don't add Navigation Chip when there is no way to resolve navigation intents * Add settings migration * Set plugin SDK version to 1.2.0-SNAPSHOT * Deduplicate shared plugin classes, use kotlinx.serialization * Fix imports * Use ZonedDateTime for depature times * Add more line types * Rewrite location serialization * Replace street/houseNumber with address * Add attribution field * Add plugin config * Reject location search requests without lat lon parameters * Add default values to plugin location class * Don't crash if column value is null * Add docs comments to LocationCategory values * Refactor OpeningSchedule as polymorphic * remove dead corpse *ahem* code * Split LocationCategory into category and icon (Also update to Kotlin 2.0, please don't do this at home) * Add more location icons * Fix (?) location deserializer * Add more location icons * More icons * Meh * Add Pub * Disable Github Maven repo if credentials are missing * Add location search specific settings to plugin details screen * Add language parameter * Unbreak the build * Refactor plugin SDK (with breaking changes) * Set plugin SDK version to 2.0.0-SNAPSHOT * Document SDK breaking changes * Implement LocationProvider.getQuery * Add a typesafe cursor API * Oops I did it again next time maybe check if the code is actually compiling before pushing * Add missing return statement * Fix list serialization * Departure time UI adjustment * Use typesafe cursor for weather plugins * Add userRatingCount and emailAddress fields * grrr * Rename and extend LineTypes * Add default lineType to Departure to fix serialization errors * Fix refreshing stored plugin locations * Adapt line name column width to available departures * Fix plugin settings screen category overlap * add LocationItem.GenericTransit * Fix crash during deserialization of locations * Update SDK docs * Replace plugin "official" mark with "verified developer" mark before anyone gets sued * show 'now' when departure is in less than one minute * Add typesafe Bundle API * Implement plugin API changes * Plugin SDK: Fix refresh result not being returned * Update docs * apply alpha to departures that have departured * better (maybe): reduce saturation instead of alpha * Add default values for Attribution * Display attribution * Rearrange location result layout * Reduce searchable update interval to 1 minute * Pass last update time to refresh function * Change refresh path and ensure that timestamp is only update when the item was updated * categorize osm location * Update docs * Optimize location search - run providers in parallel - flatten code * add experimental address parsing for OSM * add poi_category_townhall * Fix popup closing when favorites items are updated * Revert "Fix popup closing when favorites items are updated" This reverts commit fc517fd066c7f8109b6d6df2d4f536af66398207. * Fork AndroidAddressFormatter to `:libs:address-formatter` * migrate `:libs:address-formatter` dependencies to version catalog and update them * also consider addr:{suburb,hamlet} for `Address.city` if city tag is missing * Move poi strings back to strings.xml * Update Jetpack Compose * Move address-formatter back to its original package, add license and readme * Move address-formatter back to its original package --------- Co-authored-by: MM20 <15646950+MM2-0@users.noreply.github.com>
This commit is contained in:
@@ -48,6 +48,9 @@ dependencies {
|
||||
|
||||
implementation(libs.koin.android)
|
||||
implementation(libs.androidx.palette)
|
||||
runtimeOnly(libs.androidx.compose.ui)
|
||||
runtimeOnly(libs.androidx.compose.runtime)
|
||||
implementation(libs.androidx.compose.materialicons)
|
||||
|
||||
implementation(project(":core:ktx"))
|
||||
implementation(project(":core:i18n"))
|
||||
|
||||
@@ -0,0 +1,538 @@
|
||||
package de.mm20.launcher2.icons
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.material.icons.materialPath
|
||||
import androidx.compose.ui.graphics.vector.group
|
||||
|
||||
val Icons.Rounded.Battery0Bar
|
||||
get() = materialIcon("Icons.Rounded.Battery0Bar") {
|
||||
materialPath {
|
||||
moveTo(17f, 5f)
|
||||
verticalLineToRelative(16f)
|
||||
curveToRelative(0f, 0.55f, -0.45f, 1f, -1f, 1f)
|
||||
horizontalLineTo(8f)
|
||||
curveToRelative(-0.55f, 0f, -1f, -0.45f, -1f, -1f)
|
||||
verticalLineTo(5f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
verticalLineTo(3f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveToRelative(0.55f, 0f, 1f, 0.45f, 1f, 1f)
|
||||
verticalLineToRelative(1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveTo(16.55f, 4f, 17f, 4.45f, 17f, 5f)
|
||||
close()
|
||||
moveTo(15f, 6f)
|
||||
horizontalLineTo(9f)
|
||||
verticalLineToRelative(14f)
|
||||
horizontalLineToRelative(6f)
|
||||
verticalLineTo(6f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.Battery1Bar
|
||||
get() = materialIcon("Icons.Rounded.Battery1Bar") {
|
||||
materialPath {
|
||||
moveTo(17f, 5f)
|
||||
verticalLineToRelative(16f)
|
||||
curveToRelative(0f, 0.55f, -0.45f, 1f, -1f, 1f)
|
||||
horizontalLineTo(8f)
|
||||
curveToRelative(-0.55f, 0f, -1f, -0.45f, -1f, -1f)
|
||||
verticalLineTo(5f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
verticalLineTo(3f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveToRelative(0.55f, 0f, 1f, 0.45f, 1f, 1f)
|
||||
verticalLineToRelative(1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveTo(16.55f, 4f, 17f, 4.45f, 17f, 5f)
|
||||
close()
|
||||
moveTo(15f, 6f)
|
||||
horizontalLineTo(9f)
|
||||
verticalLineToRelative(12f)
|
||||
horizontalLineToRelative(6f)
|
||||
verticalLineTo(6f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.Battery2Bar
|
||||
get() = materialIcon("Icons.Rounded.Battery2Bar") {
|
||||
materialPath {
|
||||
moveTo(17f, 5f)
|
||||
verticalLineToRelative(16f)
|
||||
curveToRelative(0f, 0.55f, -0.45f, 1f, -1f, 1f)
|
||||
horizontalLineTo(8f)
|
||||
curveToRelative(-0.55f, 0f, -1f, -0.45f, -1f, -1f)
|
||||
verticalLineTo(5f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
verticalLineTo(3f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveToRelative(0.55f, 0f, 1f, 0.45f, 1f, 1f)
|
||||
verticalLineToRelative(1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveTo(16.55f, 4f, 17f, 4.45f, 17f, 5f)
|
||||
close()
|
||||
moveTo(15f, 6f)
|
||||
horizontalLineTo(9f)
|
||||
verticalLineToRelative(10f)
|
||||
horizontalLineToRelative(6f)
|
||||
verticalLineTo(6f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.Battery3Bar
|
||||
get() = materialIcon("Icons.Rounded.Battery3Bar") {
|
||||
materialPath {
|
||||
moveTo(17f, 5f)
|
||||
verticalLineToRelative(16f)
|
||||
curveToRelative(0f, 0.55f, -0.45f, 1f, -1f, 1f)
|
||||
horizontalLineTo(8f)
|
||||
curveToRelative(-0.55f, 0f, -1f, -0.45f, -1f, -1f)
|
||||
verticalLineTo(5f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
verticalLineTo(3f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveToRelative(0.55f, 0f, 1f, 0.45f, 1f, 1f)
|
||||
verticalLineToRelative(1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveTo(16.55f, 4f, 17f, 4.45f, 17f, 5f)
|
||||
close()
|
||||
moveTo(15f, 6f)
|
||||
horizontalLineTo(9f)
|
||||
verticalLineToRelative(8f)
|
||||
horizontalLineToRelative(6f)
|
||||
verticalLineTo(6f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.Battery4Bar
|
||||
get() = materialIcon("Icons.Rounded.Battery4Bar") {
|
||||
materialPath {
|
||||
moveTo(17f, 5f)
|
||||
verticalLineToRelative(16f)
|
||||
curveToRelative(0f, 0.55f, -0.45f, 1f, -1f, 1f)
|
||||
horizontalLineTo(8f)
|
||||
curveToRelative(-0.55f, 0f, -1f, -0.45f, -1f, -1f)
|
||||
verticalLineTo(5f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
verticalLineTo(3f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveToRelative(0.55f, 0f, 1f, 0.45f, 1f, 1f)
|
||||
verticalLineToRelative(1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveTo(16.55f, 4f, 17f, 4.45f, 17f, 5f)
|
||||
close()
|
||||
moveTo(15f, 6f)
|
||||
horizontalLineTo(9f)
|
||||
verticalLineToRelative(6f)
|
||||
horizontalLineToRelative(6f)
|
||||
verticalLineTo(6f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.Battery5Bar
|
||||
get() = materialIcon("Icons.Rounded.Battery5Bar") {
|
||||
materialPath {
|
||||
moveTo(17f, 5f)
|
||||
verticalLineToRelative(16f)
|
||||
curveToRelative(0f, 0.55f, -0.45f, 1f, -1f, 1f)
|
||||
horizontalLineTo(8f)
|
||||
curveToRelative(-0.55f, 0f, -1f, -0.45f, -1f, -1f)
|
||||
verticalLineTo(5f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
verticalLineTo(3f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveToRelative(0.55f, 0f, 1f, 0.45f, 1f, 1f)
|
||||
verticalLineToRelative(1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveTo(16.55f, 4f, 17f, 4.45f, 17f, 5f)
|
||||
close()
|
||||
moveTo(15f, 6f)
|
||||
horizontalLineTo(9f)
|
||||
verticalLineToRelative(4f)
|
||||
horizontalLineToRelative(6f)
|
||||
verticalLineTo(6f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.Battery6Bar
|
||||
get() = materialIcon("Icons.Rounded.Battery6Bar") {
|
||||
materialPath {
|
||||
moveTo(17f, 5f)
|
||||
verticalLineToRelative(16f)
|
||||
curveToRelative(0f, 0.55f, -0.45f, 1f, -1f, 1f)
|
||||
horizontalLineTo(8f)
|
||||
curveToRelative(-0.55f, 0f, -1f, -0.45f, -1f, -1f)
|
||||
verticalLineTo(5f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
verticalLineTo(3f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveToRelative(0.55f, 0f, 1f, 0.45f, 1f, 1f)
|
||||
verticalLineToRelative(1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveTo(16.55f, 4f, 17f, 4.45f, 17f, 5f)
|
||||
close()
|
||||
moveTo(15f, 6f)
|
||||
horizontalLineTo(9f)
|
||||
verticalLineToRelative(2f)
|
||||
horizontalLineToRelative(6f)
|
||||
verticalLineTo(6f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.BatteryCharging0Bar
|
||||
get() = materialIcon("Icons.Rounded.BatteryCharging0Bar") {
|
||||
group(
|
||||
scaleX = 0.26458333f,
|
||||
scaleY = 0.26458333f,
|
||||
) {
|
||||
materialPath {
|
||||
moveTo(41.574219f, 7.5585938f)
|
||||
curveTo(39.495479f, 7.5585937f, 37.794922f, 9.2591505f, 37.794922f, 11.337891f)
|
||||
lineTo(37.794922f, 15.117188f)
|
||||
lineTo(30.236328f, 15.117188f)
|
||||
curveTo(28.157588f, 15.117187f, 26.457031f, 16.819697f, 26.457031f, 18.898438f)
|
||||
lineTo(26.457031f, 79.369141f)
|
||||
curveTo(26.457031f, 81.447881f, 28.157588f, 83.150391f, 30.236328f, 83.150391f)
|
||||
lineTo(60.472656f, 83.150391f)
|
||||
curveTo(62.551396f, 83.150391f, 64.251953f, 81.447881f, 64.251953f, 79.369141f)
|
||||
lineTo(64.251953f, 18.898438f)
|
||||
curveTo(64.251953f, 16.819697f, 62.551396f, 15.117188f, 60.472656f, 15.117188f)
|
||||
lineTo(52.914062f, 15.117188f)
|
||||
lineTo(52.914062f, 11.337891f)
|
||||
curveTo(52.914063f, 9.2591505f, 51.213506f, 7.5585938f, 49.134766f, 7.5585938f)
|
||||
lineTo(41.574219f, 7.5585938f)
|
||||
close()
|
||||
moveTo(34.015625f, 22.677734f)
|
||||
lineTo(56.693359f, 22.677734f)
|
||||
lineTo(56.693359f, 75.589844f)
|
||||
lineTo(34.015625f, 75.589844f)
|
||||
lineTo(34.015625f, 22.677734f)
|
||||
close()
|
||||
moveTo(47.253906f, 32.126953f)
|
||||
curveTo(46.605486f, 32.112843f, 45.950535f, 32.41843f, 45.582031f, 33.109375f)
|
||||
lineTo(35.490234f, 52.005859f)
|
||||
curveTo(34.809919f, 53.290899f, 35.716123f, 54.802734f, 37.152344f, 54.802734f)
|
||||
lineTo(41.574219f, 54.802734f)
|
||||
lineTo(41.574219f, 68.03125f)
|
||||
curveTo(41.574219f, 69.996604f, 44.219867f, 70.640241f, 45.126953f, 68.939453f)
|
||||
lineTo(55.21875f, 50.041016f)
|
||||
curveTo(55.899065f, 48.755976f, 54.955066f, 47.244141f, 53.556641f, 47.244141f)
|
||||
lineTo(49.134766f, 47.244141f)
|
||||
lineTo(49.134766f, 34.015625f)
|
||||
curveTo(49.134766f, 32.848696f, 48.201598f, 32.147575f, 47.253906f, 32.126953f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.BatteryCharging1Bar
|
||||
get() = materialIcon("Icons.Rounded.BatteryCharging1Bar") {
|
||||
group(
|
||||
scaleX = 0.26458333f,
|
||||
scaleY = 0.26458333f,
|
||||
) {
|
||||
materialPath {
|
||||
moveTo(41.574219f, 7.5585938f)
|
||||
curveTo(39.495479f, 7.5585937f, 37.794922f, 9.2591505f, 37.794922f, 11.337891f)
|
||||
lineTo(37.794922f, 15.117188f)
|
||||
lineTo(30.236328f, 15.117188f)
|
||||
curveTo(28.157588f, 15.117187f, 26.457031f, 16.819697f, 26.457031f, 18.898438f)
|
||||
lineTo(26.457031f, 79.369141f)
|
||||
curveTo(26.457031f, 81.447881f, 28.157588f, 83.150391f, 30.236328f, 83.150391f)
|
||||
lineTo(60.472656f, 83.150391f)
|
||||
curveTo(62.551396f, 83.150391f, 64.251953f, 81.447881f, 64.251953f, 79.369141f)
|
||||
lineTo(64.251953f, 18.898438f)
|
||||
curveTo(64.251953f, 16.819697f, 62.551396f, 15.117188f, 60.472656f, 15.117188f)
|
||||
lineTo(52.914062f, 15.117188f)
|
||||
lineTo(52.914062f, 11.337891f)
|
||||
curveTo(52.914063f, 9.2591505f, 51.213506f, 7.5585938f, 49.134766f, 7.5585938f)
|
||||
lineTo(41.574219f, 7.5585938f)
|
||||
close()
|
||||
moveTo(34.015625f, 22.677734f)
|
||||
lineTo(56.693359f, 22.677734f)
|
||||
lineTo(56.693359f, 68.03125f)
|
||||
lineTo(45.611328f, 68.03125f)
|
||||
lineTo(45.126953f, 68.939453f)
|
||||
curveTo(44.219867f, 70.640241f, 41.574219f, 69.996604f, 41.574219f, 68.03125f)
|
||||
lineTo(34.015625f, 68.03125f)
|
||||
lineTo(34.015625f, 22.677734f)
|
||||
close()
|
||||
moveTo(41.574219f, 68.03125f)
|
||||
lineTo(45.611328f, 68.03125f)
|
||||
lineTo(55.21875f, 50.041016f)
|
||||
curveTo(55.899065f, 48.755976f, 54.955066f, 47.244141f, 53.556641f, 47.244141f)
|
||||
lineTo(49.134766f, 47.244141f)
|
||||
lineTo(49.134766f, 34.015625f)
|
||||
curveTo(49.134766f, 32.050271f, 46.489118f, 31.408588f, 45.582031f, 33.109375f)
|
||||
lineTo(35.490234f, 52.005859f)
|
||||
curveTo(34.809919f, 53.290899f, 35.716123f, 54.802734f, 37.152344f, 54.802734f)
|
||||
lineTo(41.574219f, 54.802734f)
|
||||
lineTo(41.574219f, 68.03125f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.BatteryCharging2Bar
|
||||
get() = materialIcon("Icons.Rounded.BatteryCharging2Bar") {
|
||||
group(
|
||||
scaleX = 0.26458333f,
|
||||
scaleY = 0.26458333f,
|
||||
) {
|
||||
materialPath {
|
||||
moveTo(41.574219f, 7.5585938f)
|
||||
curveTo(39.495479f, 7.5585937f, 37.794922f, 9.2591505f, 37.794922f, 11.337891f)
|
||||
lineTo(37.794922f, 15.117188f)
|
||||
lineTo(30.236328f, 15.117188f)
|
||||
curveTo(28.157588f, 15.117187f, 26.457031f, 16.819697f, 26.457031f, 18.898438f)
|
||||
lineTo(26.457031f, 79.369141f)
|
||||
curveTo(26.457031f, 81.447881f, 28.157588f, 83.150391f, 30.236328f, 83.150391f)
|
||||
lineTo(60.472656f, 83.150391f)
|
||||
curveTo(62.551396f, 83.150391f, 64.251953f, 81.447881f, 64.251953f, 79.369141f)
|
||||
lineTo(64.251953f, 18.898438f)
|
||||
curveTo(64.251953f, 16.819697f, 62.551396f, 15.117188f, 60.472656f, 15.117188f)
|
||||
lineTo(52.914062f, 15.117188f)
|
||||
lineTo(52.914062f, 11.337891f)
|
||||
curveTo(52.914063f, 9.2591505f, 51.213506f, 7.5585938f, 49.134766f, 7.5585938f)
|
||||
lineTo(41.574219f, 7.5585938f)
|
||||
close()
|
||||
moveTo(34.015625f, 22.677734f)
|
||||
lineTo(56.693359f, 22.677734f)
|
||||
lineTo(56.693359f, 60.472656f)
|
||||
lineTo(49.648438f, 60.472656f)
|
||||
lineTo(45.126953f, 68.939453f)
|
||||
curveTo(44.219867f, 70.640241f, 41.574219f, 69.996604f, 41.574219f, 68.03125f)
|
||||
lineTo(41.574219f, 60.472656f)
|
||||
lineTo(34.015625f, 60.472656f)
|
||||
lineTo(34.015625f, 22.677734f)
|
||||
close()
|
||||
moveTo(41.574219f, 60.472656f)
|
||||
lineTo(49.648438f, 60.472656f)
|
||||
lineTo(55.21875f, 50.041016f)
|
||||
curveTo(55.899065f, 48.755976f, 54.955066f, 47.244141f, 53.556641f, 47.244141f)
|
||||
lineTo(49.134766f, 47.244141f)
|
||||
lineTo(49.134766f, 34.015625f)
|
||||
curveTo(49.134766f, 32.050271f, 46.489118f, 31.408588f, 45.582031f, 33.109375f)
|
||||
lineTo(35.490234f, 52.005859f)
|
||||
curveTo(34.809919f, 53.290899f, 35.716123f, 54.802734f, 37.152344f, 54.802734f)
|
||||
lineTo(41.574219f, 54.802734f)
|
||||
lineTo(41.574219f, 60.472656f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.BatteryCharging3Bar
|
||||
get() = materialIcon("Icons.Rounded.BatteryCharging3Bar") {
|
||||
group(
|
||||
scaleX = 0.26458333f,
|
||||
scaleY = 0.26458333f,
|
||||
) {
|
||||
materialPath {
|
||||
moveTo(41.574219f, 7.5585938f)
|
||||
curveTo(39.495479f, 7.5585937f, 37.794922f, 9.2591505f, 37.794922f, 11.337891f)
|
||||
lineTo(37.794922f, 15.117188f)
|
||||
lineTo(30.236328f, 15.117188f)
|
||||
curveTo(28.157588f, 15.117187f, 26.457031f, 16.819697f, 26.457031f, 18.898438f)
|
||||
lineTo(26.457031f, 79.369141f)
|
||||
curveTo(26.457031f, 81.447881f, 28.157588f, 83.150391f, 30.236328f, 83.150391f)
|
||||
lineTo(60.472656f, 83.150391f)
|
||||
curveTo(62.551396f, 83.150391f, 64.251953f, 81.447881f, 64.251953f, 79.369141f)
|
||||
lineTo(64.251953f, 18.898438f)
|
||||
curveTo(64.251953f, 16.819697f, 62.551396f, 15.117188f, 60.472656f, 15.117188f)
|
||||
lineTo(52.914062f, 15.117188f)
|
||||
lineTo(52.914062f, 11.337891f)
|
||||
curveTo(52.914063f, 9.2591505f, 51.213506f, 7.5585938f, 49.134766f, 7.5585938f)
|
||||
lineTo(41.574219f, 7.5585938f)
|
||||
close()
|
||||
moveTo(34.015625f, 22.677734f)
|
||||
lineTo(56.693359f, 22.677734f)
|
||||
lineTo(56.693359f, 52.914062f)
|
||||
lineTo(53.683594f, 52.914062f)
|
||||
lineTo(45.126953f, 68.939453f)
|
||||
curveTo(44.219867f, 70.640241f, 41.574219f, 69.996604f, 41.574219f, 68.03125f)
|
||||
lineTo(41.574219f, 54.802734f)
|
||||
lineTo(37.152344f, 54.802734f)
|
||||
curveTo(36.048819f, 54.802734f, 35.258483f, 53.910469f, 35.259766f, 52.914062f)
|
||||
lineTo(34.015625f, 52.914062f)
|
||||
lineTo(34.015625f, 22.677734f)
|
||||
close()
|
||||
moveTo(35.259766f, 52.914062f)
|
||||
lineTo(53.683594f, 52.914062f)
|
||||
lineTo(55.21875f, 50.041016f)
|
||||
curveTo(55.899065f, 48.755976f, 54.955066f, 47.244141f, 53.556641f, 47.244141f)
|
||||
lineTo(49.134766f, 47.244141f)
|
||||
lineTo(49.134766f, 34.015625f)
|
||||
curveTo(49.134766f, 32.050271f, 46.489118f, 31.408588f, 45.582031f, 33.109375f)
|
||||
lineTo(35.490234f, 52.005859f)
|
||||
curveTo(35.332642f, 52.303535f, 35.260152f, 52.613661f, 35.259766f, 52.914062f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.BatteryCharging4Bar
|
||||
get() = materialIcon("Icons.Rounded.BatteryCharging4Bar") {
|
||||
group(
|
||||
scaleX = 0.26458333f,
|
||||
scaleY = 0.26458333f,
|
||||
) {
|
||||
materialPath {
|
||||
moveTo(41.574219f, 7.5585938f)
|
||||
curveTo(39.495479f, 7.5585937f, 37.794922f, 9.2591505f, 37.794922f, 11.337891f)
|
||||
lineTo(37.794922f, 15.117188f)
|
||||
lineTo(30.236328f, 15.117188f)
|
||||
curveTo(28.157588f, 15.117187f, 26.457031f, 16.819697f, 26.457031f, 18.898438f)
|
||||
lineTo(26.457031f, 79.369141f)
|
||||
curveTo(26.457031f, 81.447881f, 28.157588f, 83.150391f, 30.236328f, 83.150391f)
|
||||
lineTo(60.472656f, 83.150391f)
|
||||
curveTo(62.551396f, 83.150391f, 64.251953f, 81.447881f, 64.251953f, 79.369141f)
|
||||
lineTo(64.251953f, 18.898438f)
|
||||
curveTo(64.251953f, 16.819697f, 62.551396f, 15.117188f, 60.472656f, 15.117188f)
|
||||
lineTo(52.914062f, 15.117188f)
|
||||
lineTo(52.914062f, 11.337891f)
|
||||
curveTo(52.914063f, 9.2591505f, 51.213506f, 7.5585938f, 49.134766f, 7.5585938f)
|
||||
lineTo(41.574219f, 7.5585938f)
|
||||
close()
|
||||
moveTo(34.015625f, 22.677734f)
|
||||
lineTo(56.693359f, 22.677734f)
|
||||
lineTo(56.693359f, 45.353516f)
|
||||
lineTo(49.134766f, 45.353516f)
|
||||
lineTo(49.134766f, 47.244141f)
|
||||
lineTo(53.556641f, 47.244141f)
|
||||
curveTo(54.955066f, 47.244141f, 55.899065f, 48.755976f, 55.21875f, 50.041016f)
|
||||
lineTo(45.126953f, 68.939453f)
|
||||
curveTo(44.219867f, 70.640241f, 41.574219f, 69.996604f, 41.574219f, 68.03125f)
|
||||
lineTo(41.574219f, 54.802734f)
|
||||
lineTo(37.152344f, 54.802734f)
|
||||
curveTo(35.716123f, 54.802734f, 34.809919f, 53.290899f, 35.490234f, 52.005859f)
|
||||
lineTo(39.042969f, 45.353516f)
|
||||
lineTo(34.015625f, 45.353516f)
|
||||
lineTo(34.015625f, 22.677734f)
|
||||
close()
|
||||
moveTo(39.042969f, 45.353516f)
|
||||
lineTo(49.134766f, 45.353516f)
|
||||
lineTo(49.134766f, 34.015625f)
|
||||
curveTo(49.134766f, 32.050271f, 46.489118f, 31.408588f, 45.582031f, 33.109375f)
|
||||
lineTo(39.042969f, 45.353516f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.BatteryCharging5Bar
|
||||
get() = materialIcon("Icons.Rounded.BatteryCharging5Bar") {
|
||||
group(
|
||||
scaleX = 0.26458333f,
|
||||
scaleY = 0.26458333f,
|
||||
) {
|
||||
materialPath {
|
||||
moveTo(41.574219f, 7.5585938f)
|
||||
curveTo(39.495479f, 7.5585937f, 37.794922f, 9.2591505f, 37.794922f, 11.337891f)
|
||||
lineTo(37.794922f, 15.117188f)
|
||||
lineTo(30.236328f, 15.117188f)
|
||||
curveTo(28.157588f, 15.117187f, 26.457031f, 16.819697f, 26.457031f, 18.898438f)
|
||||
lineTo(26.457031f, 79.369141f)
|
||||
curveTo(26.457031f, 81.447881f, 28.157588f, 83.150391f, 30.236328f, 83.150391f)
|
||||
lineTo(60.472656f, 83.150391f)
|
||||
curveTo(62.551396f, 83.150391f, 64.251953f, 81.447881f, 64.251953f, 79.369141f)
|
||||
lineTo(64.251953f, 18.898438f)
|
||||
curveTo(64.251953f, 16.819697f, 62.551396f, 15.117188f, 60.472656f, 15.117188f)
|
||||
lineTo(52.914062f, 15.117188f)
|
||||
lineTo(52.914062f, 11.337891f)
|
||||
curveTo(52.914063f, 9.2591505f, 51.213506f, 7.5585938f, 49.134766f, 7.5585938f)
|
||||
lineTo(41.574219f, 7.5585938f)
|
||||
close()
|
||||
moveTo(34.015625f, 22.677734f)
|
||||
lineTo(56.693359f, 22.677734f)
|
||||
lineTo(56.693359f, 37.794922f)
|
||||
lineTo(49.134766f, 37.794922f)
|
||||
lineTo(49.134766f, 47.244141f)
|
||||
lineTo(53.556641f, 47.244141f)
|
||||
curveTo(54.955066f, 47.244141f, 55.899065f, 48.755976f, 55.21875f, 50.041016f)
|
||||
lineTo(45.126953f, 68.939453f)
|
||||
curveTo(44.219867f, 70.640241f, 41.574219f, 69.996604f, 41.574219f, 68.03125f)
|
||||
lineTo(41.574219f, 54.802734f)
|
||||
lineTo(37.152344f, 54.802734f)
|
||||
curveTo(35.716123f, 54.802734f, 34.809919f, 53.290899f, 35.490234f, 52.005859f)
|
||||
lineTo(43.080078f, 37.794922f)
|
||||
lineTo(34.015625f, 37.794922f)
|
||||
lineTo(34.015625f, 22.677734f)
|
||||
close()
|
||||
moveTo(43.080078f, 37.794922f)
|
||||
lineTo(49.134766f, 37.794922f)
|
||||
lineTo(49.134766f, 34.015625f)
|
||||
curveTo(49.134766f, 32.050271f, 46.489118f, 31.408588f, 45.582031f, 33.109375f)
|
||||
lineTo(43.080078f, 37.794922f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.BatteryCharging6Bar
|
||||
get() = materialIcon("Icons.Rounded.BatteryCharging6Bar") {
|
||||
group(
|
||||
scaleX = 0.26458333f,
|
||||
scaleY = 0.26458333f,
|
||||
) {
|
||||
materialPath {
|
||||
moveTo(41.574219f, 7.5585938f)
|
||||
curveTo(39.495479f, 7.5585937f, 37.794922f, 9.2591505f, 37.794922f, 11.337891f)
|
||||
lineTo(37.794922f, 15.117188f)
|
||||
lineTo(30.236328f, 15.117188f)
|
||||
curveTo(28.157588f, 15.117187f, 26.457031f, 16.819697f, 26.457031f, 18.898438f)
|
||||
lineTo(26.457031f, 79.369141f)
|
||||
curveTo(26.457031f, 81.447881f, 28.157588f, 83.150391f, 30.236328f, 83.150391f)
|
||||
lineTo(60.472656f, 83.150391f)
|
||||
curveTo(62.551396f, 83.150391f, 64.251953f, 81.447881f, 64.251953f, 79.369141f)
|
||||
lineTo(64.251953f, 18.898438f)
|
||||
curveTo(64.251953f, 16.819697f, 62.551396f, 15.117188f, 60.472656f, 15.117188f)
|
||||
lineTo(52.914062f, 15.117188f)
|
||||
lineTo(52.914062f, 11.337891f)
|
||||
curveTo(52.914063f, 9.2591505f, 51.213506f, 7.5585938f, 49.134766f, 7.5585938f)
|
||||
lineTo(41.574219f, 7.5585938f)
|
||||
close()
|
||||
moveTo(34.015625f, 22.677734f)
|
||||
lineTo(56.693359f, 22.677734f)
|
||||
lineTo(56.693359f, 30.236328f)
|
||||
lineTo(34.015625f, 30.236328f)
|
||||
lineTo(34.015625f, 22.677734f)
|
||||
close()
|
||||
moveTo(47.253906f, 32.126953f)
|
||||
curveTo(48.201598f, 32.147575f, 49.134766f, 32.848696f, 49.134766f, 34.015625f)
|
||||
lineTo(49.134766f, 47.244141f)
|
||||
lineTo(53.556641f, 47.244141f)
|
||||
curveTo(54.955066f, 47.244141f, 55.899065f, 48.755976f, 55.21875f, 50.041016f)
|
||||
lineTo(45.126953f, 68.939453f)
|
||||
curveTo(44.219867f, 70.640241f, 41.574219f, 69.996604f, 41.574219f, 68.03125f)
|
||||
lineTo(41.574219f, 54.802734f)
|
||||
lineTo(37.152344f, 54.802734f)
|
||||
curveTo(35.716123f, 54.802734f, 34.809919f, 53.290899f, 35.490234f, 52.005859f)
|
||||
lineTo(45.582031f, 33.109375f)
|
||||
curveTo(45.950535f, 32.41843f, 46.605486f, 32.112843f, 47.253906f, 32.126953f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
package de.mm20.launcher2.icons
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
||||
sealed interface LauncherIconLayer
|
||||
|
||||
@@ -53,4 +54,9 @@ data class TextLayer(
|
||||
val color: Int = 0,
|
||||
) : LauncherIconLayer
|
||||
|
||||
data class VectorLayer(
|
||||
val vector: ImageVector,
|
||||
val color: Int = 0,
|
||||
) : LauncherIconLayer
|
||||
|
||||
object TransparentLayer: LauncherIconLayer
|
||||
@@ -1,9 +1,11 @@
|
||||
package de.mm20.launcher2.plugin
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import de.mm20.launcher2.plugin.config.SearchPluginConfig
|
||||
import de.mm20.launcher2.plugin.config.QueryPluginConfig
|
||||
import de.mm20.launcher2.plugin.config.WeatherPluginConfig
|
||||
import de.mm20.launcher2.plugin.contracts.PluginContract
|
||||
|
||||
@@ -11,7 +13,25 @@ class PluginApi(
|
||||
private val pluginAuthority: String,
|
||||
private val contentResolver: ContentResolver,
|
||||
) {
|
||||
fun getSearchPluginConfig(): SearchPluginConfig? {
|
||||
fun getConfig(): Bundle? {
|
||||
val configBundle = try {
|
||||
contentResolver.call(
|
||||
Uri.Builder()
|
||||
.scheme("content")
|
||||
.authority(pluginAuthority)
|
||||
.build(),
|
||||
PluginContract.Methods.GetConfig,
|
||||
null,
|
||||
null
|
||||
) ?: return null
|
||||
} catch (e: Exception) {
|
||||
Log.e("MM20", "Plugin $pluginAuthority threw exception", e)
|
||||
return null
|
||||
}
|
||||
return configBundle
|
||||
}
|
||||
|
||||
fun getSearchPluginConfig(): QueryPluginConfig? {
|
||||
val configBundle = try {
|
||||
contentResolver.call(
|
||||
Uri.Builder()
|
||||
@@ -27,7 +47,7 @@ class PluginApi(
|
||||
return null
|
||||
}
|
||||
|
||||
return SearchPluginConfig(configBundle)
|
||||
return QueryPluginConfig(configBundle)
|
||||
}
|
||||
|
||||
fun getWeatherPluginConfig(): WeatherPluginConfig? {
|
||||
|
||||
@@ -10,7 +10,7 @@ data class PluginPackage(
|
||||
val author: String? = null,
|
||||
val settings: Intent? = null,
|
||||
val plugins: List<Plugin>,
|
||||
val isOfficial: Boolean = false,
|
||||
val isVerified: Boolean = false,
|
||||
) {
|
||||
val enabled: Boolean = plugins.all { it.enabled }
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package de.mm20.launcher2.plugin
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.CancellationSignal
|
||||
import android.util.Log
|
||||
import de.mm20.launcher2.plugin.config.QueryPluginConfig
|
||||
import de.mm20.launcher2.plugin.contracts.LocationPluginContract
|
||||
import de.mm20.launcher2.plugin.contracts.PluginContract
|
||||
import de.mm20.launcher2.plugin.contracts.SearchPluginContract
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
private class NotUpdated : Exception("Not updated")
|
||||
|
||||
abstract class QueryPluginApi<TQuery, TResult>(
|
||||
private val context: Context,
|
||||
private val pluginAuthority: String,
|
||||
) {
|
||||
private fun getLanguage() = context.resources.configuration.locales[0].language
|
||||
|
||||
fun getConfig(): QueryPluginConfig? {
|
||||
val configBundle = try {
|
||||
context.contentResolver.call(
|
||||
Uri.Builder()
|
||||
.scheme("content")
|
||||
.authority(pluginAuthority)
|
||||
.build(),
|
||||
PluginContract.Methods.GetConfig,
|
||||
null,
|
||||
null
|
||||
) ?: return null
|
||||
} catch (e: Exception) {
|
||||
Log.e("MM20", "Plugin $pluginAuthority threw exception", e)
|
||||
return null
|
||||
}
|
||||
return QueryPluginConfig(configBundle)
|
||||
}
|
||||
|
||||
suspend fun search(query: TQuery, allowNetwork: Boolean): List<TResult> = withContext(Dispatchers.IO) {
|
||||
val lang = getLanguage()
|
||||
val uri = Uri.Builder()
|
||||
.scheme("content")
|
||||
.authority(pluginAuthority)
|
||||
.path(LocationPluginContract.Paths.Search)
|
||||
.appendQueryParameters(query)
|
||||
.appendQueryParameter(
|
||||
SearchPluginContract.Params.AllowNetwork,
|
||||
allowNetwork.toString()
|
||||
)
|
||||
.appendQueryParameter(
|
||||
SearchPluginContract.Params.Lang,
|
||||
lang
|
||||
)
|
||||
.build()
|
||||
val cancellationSignal = CancellationSignal()
|
||||
|
||||
return@withContext suspendCancellableCoroutine {
|
||||
it.invokeOnCancellation {
|
||||
cancellationSignal.cancel()
|
||||
}
|
||||
val cursor = try {
|
||||
context.contentResolver.query(
|
||||
uri,
|
||||
null,
|
||||
null,
|
||||
cancellationSignal
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e("MM20", "Plugin $pluginAuthority threw exception", e)
|
||||
it.resume(emptyList())
|
||||
return@suspendCancellableCoroutine
|
||||
}
|
||||
|
||||
if (cursor == null) {
|
||||
Log.e("MM20", "Plugin $pluginAuthority returned null cursor")
|
||||
it.resume(emptyList())
|
||||
return@suspendCancellableCoroutine
|
||||
}
|
||||
|
||||
val results = cursor.getData() ?: emptyList()
|
||||
it.resume(results)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun get(id: String): Result<TResult?> = withContext(Dispatchers.IO) {
|
||||
val uri = Uri.Builder()
|
||||
.scheme("content")
|
||||
.authority(pluginAuthority)
|
||||
.path(SearchPluginContract.Paths.Root)
|
||||
.appendPath(id)
|
||||
.appendQueryParameter(SearchPluginContract.Params.Lang, getLanguage())
|
||||
.build()
|
||||
|
||||
val cancellationSignal = CancellationSignal()
|
||||
|
||||
return@withContext suspendCancellableCoroutine {
|
||||
it.invokeOnCancellation {
|
||||
cancellationSignal.cancel()
|
||||
}
|
||||
val cursor = try {
|
||||
context.contentResolver.query(
|
||||
uri,
|
||||
null,
|
||||
null,
|
||||
cancellationSignal
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e("MM20", "Plugin $pluginAuthority threw exception", e)
|
||||
it.resume(Result.failure(e))
|
||||
return@suspendCancellableCoroutine
|
||||
}
|
||||
|
||||
if (cursor == null) {
|
||||
Log.e("MM20", "Plugin $pluginAuthority returned null cursor")
|
||||
it.resume(Result.success(null))
|
||||
return@suspendCancellableCoroutine
|
||||
}
|
||||
|
||||
val result = cursor.getData()?.firstOrNull()
|
||||
|
||||
if (result == null) {
|
||||
it.resume(Result.success(null))
|
||||
return@suspendCancellableCoroutine
|
||||
}
|
||||
|
||||
it.resume(Result.success(result))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun refresh(item: TResult, lastUpdate: Long): Result<TResult?> = withContext(Dispatchers.IO) {
|
||||
val uri = Uri.Builder()
|
||||
.scheme("content")
|
||||
.authority(pluginAuthority)
|
||||
.path(SearchPluginContract.Paths.Refresh)
|
||||
.appendQueryParameter(SearchPluginContract.Params.Lang, getLanguage())
|
||||
.appendQueryParameter(SearchPluginContract.Params.UpdatedAt, lastUpdate.toString())
|
||||
.build()
|
||||
|
||||
val cancellationSignal = CancellationSignal()
|
||||
|
||||
return@withContext suspendCancellableCoroutine {
|
||||
it.invokeOnCancellation {
|
||||
cancellationSignal.cancel()
|
||||
}
|
||||
val cursor = try {
|
||||
context.contentResolver.query(
|
||||
uri,
|
||||
null,
|
||||
item.toBundle(),
|
||||
cancellationSignal
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e("MM20", "Plugin $pluginAuthority threw exception", e)
|
||||
it.resume(Result.failure(e))
|
||||
return@suspendCancellableCoroutine
|
||||
}
|
||||
|
||||
if (cursor == null) {
|
||||
Log.e("MM20", "Plugin $pluginAuthority returned null cursor")
|
||||
it.resume(Result.failure(IllegalArgumentException()))
|
||||
return@suspendCancellableCoroutine
|
||||
}
|
||||
|
||||
if (cursor.extras?.getBoolean(SearchPluginContract.Extras.NotUpdated) == true) {
|
||||
it.resume(Result.failure(NotUpdated()))
|
||||
return@suspendCancellableCoroutine
|
||||
}
|
||||
|
||||
val result = cursor.getData()?.firstOrNull()
|
||||
|
||||
if (result == null) {
|
||||
it.resume(Result.success(null))
|
||||
return@suspendCancellableCoroutine
|
||||
}
|
||||
|
||||
it.resume(Result.success(result))
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun Uri.Builder.appendQueryParameters(query: TQuery): Uri.Builder
|
||||
|
||||
protected abstract fun Cursor.getData(): List<TResult>?
|
||||
|
||||
protected abstract fun TResult.toBundle(): Bundle
|
||||
}
|
||||
@@ -2,8 +2,8 @@ package de.mm20.launcher2.plugin.config
|
||||
|
||||
import android.os.Bundle
|
||||
|
||||
fun SearchPluginConfig(bundle: Bundle): SearchPluginConfig? {
|
||||
return SearchPluginConfig(
|
||||
fun QueryPluginConfig(bundle: Bundle): QueryPluginConfig {
|
||||
return QueryPluginConfig(
|
||||
storageStrategy = valueOfOrElse(
|
||||
bundle.getString(
|
||||
"storageStrategy",
|
||||
|
||||
@@ -1,16 +1,135 @@
|
||||
package de.mm20.launcher2.search
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.DirectionsBike
|
||||
import androidx.compose.material.icons.rounded.AccountBalance
|
||||
import androidx.compose.material.icons.rounded.AttachMoney
|
||||
import androidx.compose.material.icons.rounded.Attractions
|
||||
import androidx.compose.material.icons.rounded.BakeryDining
|
||||
import androidx.compose.material.icons.rounded.Bed
|
||||
import androidx.compose.material.icons.rounded.Bento
|
||||
import androidx.compose.material.icons.rounded.Book
|
||||
import androidx.compose.material.icons.rounded.BreakfastDining
|
||||
import androidx.compose.material.icons.rounded.BrunchDining
|
||||
import androidx.compose.material.icons.rounded.CarRental
|
||||
import androidx.compose.material.icons.rounded.CarRepair
|
||||
import androidx.compose.material.icons.rounded.Casino
|
||||
import androidx.compose.material.icons.rounded.Checkroom
|
||||
import androidx.compose.material.icons.rounded.Church
|
||||
import androidx.compose.material.icons.rounded.ContentCut
|
||||
import androidx.compose.material.icons.rounded.Diamond
|
||||
import androidx.compose.material.icons.rounded.DirectionsBoat
|
||||
import androidx.compose.material.icons.rounded.DirectionsBus
|
||||
import androidx.compose.material.icons.rounded.DirectionsCar
|
||||
import androidx.compose.material.icons.rounded.DirectionsTransit
|
||||
import androidx.compose.material.icons.rounded.Discount
|
||||
import androidx.compose.material.icons.rounded.DownhillSkiing
|
||||
import androidx.compose.material.icons.rounded.ElectricScooter
|
||||
import androidx.compose.material.icons.rounded.EvStation
|
||||
import androidx.compose.material.icons.rounded.Fastfood
|
||||
import androidx.compose.material.icons.rounded.Festival
|
||||
import androidx.compose.material.icons.rounded.FitnessCenter
|
||||
import androidx.compose.material.icons.rounded.Flight
|
||||
import androidx.compose.material.icons.rounded.Forest
|
||||
import androidx.compose.material.icons.rounded.Gavel
|
||||
import androidx.compose.material.icons.rounded.Hiking
|
||||
import androidx.compose.material.icons.rounded.Hotel
|
||||
import androidx.compose.material.icons.rounded.Icecream
|
||||
import androidx.compose.material.icons.rounded.Kayaking
|
||||
import androidx.compose.material.icons.rounded.KebabDining
|
||||
import androidx.compose.material.icons.rounded.Liquor
|
||||
import androidx.compose.material.icons.rounded.LocalAtm
|
||||
import androidx.compose.material.icons.rounded.LocalBar
|
||||
import androidx.compose.material.icons.rounded.LocalCafe
|
||||
import androidx.compose.material.icons.rounded.LocalCarWash
|
||||
import androidx.compose.material.icons.rounded.LocalConvenienceStore
|
||||
import androidx.compose.material.icons.rounded.LocalFireDepartment
|
||||
import androidx.compose.material.icons.rounded.LocalFlorist
|
||||
import androidx.compose.material.icons.rounded.LocalGasStation
|
||||
import androidx.compose.material.icons.rounded.LocalGroceryStore
|
||||
import androidx.compose.material.icons.rounded.LocalHospital
|
||||
import androidx.compose.material.icons.rounded.LocalLaundryService
|
||||
import androidx.compose.material.icons.rounded.LocalMall
|
||||
import androidx.compose.material.icons.rounded.LocalParking
|
||||
import androidx.compose.material.icons.rounded.LocalPharmacy
|
||||
import androidx.compose.material.icons.rounded.LocalPizza
|
||||
import androidx.compose.material.icons.rounded.LocalPolice
|
||||
import androidx.compose.material.icons.rounded.LocalPostOffice
|
||||
import androidx.compose.material.icons.rounded.LocalTaxi
|
||||
import androidx.compose.material.icons.rounded.LunchDining
|
||||
import androidx.compose.material.icons.rounded.Moped
|
||||
import androidx.compose.material.icons.rounded.Mosque
|
||||
import androidx.compose.material.icons.rounded.Motorcycle
|
||||
import androidx.compose.material.icons.rounded.Museum
|
||||
import androidx.compose.material.icons.rounded.MusicNote
|
||||
import androidx.compose.material.icons.rounded.Newspaper
|
||||
import androidx.compose.material.icons.rounded.Nightlife
|
||||
import androidx.compose.material.icons.rounded.NordicWalking
|
||||
import androidx.compose.material.icons.rounded.Palette
|
||||
import androidx.compose.material.icons.rounded.Paragliding
|
||||
import androidx.compose.material.icons.rounded.Park
|
||||
import androidx.compose.material.icons.rounded.Pets
|
||||
import androidx.compose.material.icons.rounded.Phone
|
||||
import androidx.compose.material.icons.rounded.Place
|
||||
import androidx.compose.material.icons.rounded.Pool
|
||||
import androidx.compose.material.icons.rounded.RamenDining
|
||||
import androidx.compose.material.icons.rounded.Restaurant
|
||||
import androidx.compose.material.icons.rounded.School
|
||||
import androidx.compose.material.icons.rounded.ShoppingBag
|
||||
import androidx.compose.material.icons.rounded.Skateboarding
|
||||
import androidx.compose.material.icons.rounded.Snowboarding
|
||||
import androidx.compose.material.icons.rounded.SoupKitchen
|
||||
import androidx.compose.material.icons.rounded.Sports
|
||||
import androidx.compose.material.icons.rounded.SportsBar
|
||||
import androidx.compose.material.icons.rounded.SportsBaseball
|
||||
import androidx.compose.material.icons.rounded.SportsBasketball
|
||||
import androidx.compose.material.icons.rounded.SportsCricket
|
||||
import androidx.compose.material.icons.rounded.SportsFootball
|
||||
import androidx.compose.material.icons.rounded.SportsGolf
|
||||
import androidx.compose.material.icons.rounded.SportsGymnastics
|
||||
import androidx.compose.material.icons.rounded.SportsHandball
|
||||
import androidx.compose.material.icons.rounded.SportsHockey
|
||||
import androidx.compose.material.icons.rounded.SportsMartialArts
|
||||
import androidx.compose.material.icons.rounded.SportsMotorsports
|
||||
import androidx.compose.material.icons.rounded.SportsRugby
|
||||
import androidx.compose.material.icons.rounded.SportsSoccer
|
||||
import androidx.compose.material.icons.rounded.SportsTennis
|
||||
import androidx.compose.material.icons.rounded.SportsVolleyball
|
||||
import androidx.compose.material.icons.rounded.Stadium
|
||||
import androidx.compose.material.icons.rounded.Subway
|
||||
import androidx.compose.material.icons.rounded.Surfing
|
||||
import androidx.compose.material.icons.rounded.Synagogue
|
||||
import androidx.compose.material.icons.rounded.TakeoutDining
|
||||
import androidx.compose.material.icons.rounded.TempleBuddhist
|
||||
import androidx.compose.material.icons.rounded.TempleHindu
|
||||
import androidx.compose.material.icons.rounded.TheaterComedy
|
||||
import androidx.compose.material.icons.rounded.Theaters
|
||||
import androidx.compose.material.icons.rounded.Train
|
||||
import androidx.compose.material.icons.rounded.Tram
|
||||
import androidx.compose.material.icons.rounded.Wc
|
||||
import androidx.core.content.ContextCompat
|
||||
import de.mm20.launcher2.base.R
|
||||
import de.mm20.launcher2.icons.CableCar
|
||||
import de.mm20.launcher2.icons.Candle
|
||||
import de.mm20.launcher2.icons.ColorLayer
|
||||
import de.mm20.launcher2.icons.Dentistry
|
||||
import de.mm20.launcher2.icons.Eyeglasses
|
||||
import de.mm20.launcher2.icons.Monument
|
||||
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 de.mm20.launcher2.icons.Stethoscope
|
||||
import de.mm20.launcher2.icons.VectorLayer
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
import de.mm20.launcher2.search.location.Address
|
||||
import de.mm20.launcher2.search.location.Attribution
|
||||
import de.mm20.launcher2.search.location.Departure
|
||||
import de.mm20.launcher2.search.location.LocationIcon
|
||||
import de.mm20.launcher2.search.location.OpeningHours
|
||||
import de.mm20.launcher2.search.location.OpeningSchedule
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
import java.time.temporal.TemporalAdjusters
|
||||
import android.location.Location as AndroidLocation
|
||||
|
||||
@@ -20,128 +139,182 @@ interface Location : SavableSearchable {
|
||||
val longitude: Double
|
||||
val fixMeUrl: String?
|
||||
|
||||
val category: LocationCategory?
|
||||
val icon: LocationIcon?
|
||||
val category: String?
|
||||
|
||||
val address: Address?
|
||||
|
||||
val street: String?
|
||||
val houseNumber: String?
|
||||
val openingSchedule: OpeningSchedule?
|
||||
val websiteUrl: String?
|
||||
val phoneNumber: String?
|
||||
val emailAddress: String?
|
||||
|
||||
val userRating: Float?
|
||||
val userRatingCount: Int?
|
||||
|
||||
val openingSchedule: OpeningSchedule?
|
||||
val departures: List<Departure>?
|
||||
|
||||
val attribution: Attribution?
|
||||
get() = null
|
||||
|
||||
override val preferDetailsOverLaunch: Boolean
|
||||
get() = true
|
||||
|
||||
override fun launch(context: Context, options: Bundle?): Boolean {
|
||||
return context.tryStartActivity(
|
||||
Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse("geo:$latitude,$longitude?q=${Uri.encode(label)}")
|
||||
),
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
val (vector, bgColor) = when (icon) {
|
||||
// blue: transportation
|
||||
LocationIcon.Car -> Icons.Rounded.DirectionsCar to R.color.blue
|
||||
LocationIcon.CarRental -> Icons.Rounded.CarRental to R.color.blue
|
||||
LocationIcon.CarRepair -> Icons.Rounded.CarRepair to R.color.blue
|
||||
LocationIcon.CarWash -> Icons.Rounded.LocalCarWash to R.color.blue
|
||||
LocationIcon.ChargingStation -> Icons.Rounded.EvStation to R.color.blue
|
||||
LocationIcon.GasStation -> Icons.Rounded.LocalGasStation to R.color.blue
|
||||
LocationIcon.Parking -> Icons.Rounded.LocalParking to R.color.blue
|
||||
LocationIcon.Bus -> Icons.Rounded.DirectionsBus to R.color.blue
|
||||
LocationIcon.Tram -> Icons.Rounded.Tram to R.color.blue
|
||||
LocationIcon.Subway -> Icons.Rounded.Subway to R.color.blue
|
||||
LocationIcon.Train -> Icons.Rounded.Train to R.color.blue
|
||||
LocationIcon.CableCar -> Icons.Rounded.CableCar to R.color.blue
|
||||
LocationIcon.Airport -> Icons.Rounded.Flight to R.color.blue
|
||||
LocationIcon.Boat -> Icons.Rounded.DirectionsBoat to R.color.blue
|
||||
LocationIcon.Moped -> Icons.Rounded.Moped to R.color.blue
|
||||
LocationIcon.Bike -> Icons.AutoMirrored.Rounded.DirectionsBike to R.color.blue
|
||||
LocationIcon.Motorcycle -> Icons.Rounded.Motorcycle to R.color.blue
|
||||
LocationIcon.ElectricScooter -> Icons.Rounded.ElectricScooter to R.color.blue
|
||||
LocationIcon.Taxi -> Icons.Rounded.LocalTaxi to R.color.blue
|
||||
LocationIcon.GenericTransit -> Icons.Rounded.DirectionsTransit to R.color.blue
|
||||
|
||||
contains(
|
||||
"ramen",
|
||||
ignoreCase = true
|
||||
) -> R.drawable.ic_location_ramen to R.color.orange
|
||||
// cyan: art, culture, entertainment
|
||||
LocationIcon.ArtGallery -> Icons.Rounded.Palette to R.color.cyan
|
||||
LocationIcon.Museum -> Icons.Rounded.Museum to R.color.cyan
|
||||
LocationIcon.Theater -> Icons.Rounded.TheaterComedy to R.color.cyan
|
||||
LocationIcon.MovieTheater -> Icons.Rounded.Theaters to R.color.cyan
|
||||
LocationIcon.AmusementPark -> Icons.Rounded.Attractions to R.color.cyan
|
||||
LocationIcon.NightClub -> Icons.Rounded.Nightlife to R.color.cyan
|
||||
LocationIcon.ConcertHall -> Icons.Rounded.MusicNote to R.color.cyan
|
||||
LocationIcon.Stadium -> Icons.Rounded.Stadium to R.color.cyan
|
||||
LocationIcon.Casino -> Icons.Rounded.Casino to R.color.cyan
|
||||
LocationIcon.Circus -> Icons.Rounded.Festival to R.color.cyan
|
||||
// pink: hotels
|
||||
LocationIcon.Hotel -> Icons.Rounded.Hotel to R.color.pink
|
||||
// orange: food and drink
|
||||
LocationIcon.Restaurant -> Icons.Rounded.Restaurant to R.color.orange
|
||||
LocationIcon.Cafe -> Icons.Rounded.LocalCafe to R.color.orange
|
||||
LocationIcon.FastFood -> Icons.Rounded.Fastfood to R.color.orange
|
||||
LocationIcon.Pizza -> Icons.Rounded.LocalPizza to R.color.orange
|
||||
LocationIcon.Burger -> Icons.Rounded.LunchDining to R.color.orange
|
||||
LocationIcon.Kebab -> Icons.Rounded.KebabDining to R.color.orange
|
||||
LocationIcon.IceCream -> Icons.Rounded.Icecream to R.color.orange
|
||||
LocationIcon.Ramen -> Icons.Rounded.RamenDining to R.color.orange
|
||||
LocationIcon.Soup -> Icons.Rounded.SoupKitchen to R.color.orange
|
||||
LocationIcon.Bar -> Icons.Rounded.LocalBar to R.color.orange
|
||||
LocationIcon.Brunch -> Icons.Rounded.BrunchDining to R.color.orange
|
||||
LocationIcon.Breakfast -> Icons.Rounded.BreakfastDining to R.color.orange
|
||||
LocationIcon.Pub -> Icons.Rounded.SportsBar to R.color.orange
|
||||
LocationIcon.JapaneseCuisine -> Icons.Rounded.Bento to R.color.orange
|
||||
LocationIcon.AsianCuisine -> Icons.Rounded.TakeoutDining to R.color.orange
|
||||
// indigo: business and shopping
|
||||
LocationIcon.Shopping -> Icons.Rounded.ShoppingBag to R.color.indigo
|
||||
LocationIcon.Supermarket -> Icons.Rounded.LocalGroceryStore to R.color.indigo
|
||||
LocationIcon.Florist -> Icons.Rounded.LocalFlorist to R.color.indigo
|
||||
LocationIcon.Kiosk -> Icons.Rounded.Newspaper to R.color.indigo
|
||||
LocationIcon.FurnitureStore -> Icons.Rounded.Bed to R.color.indigo
|
||||
LocationIcon.CellPhoneStore -> Icons.Rounded.Phone to R.color.indigo
|
||||
LocationIcon.BookStore -> Icons.Rounded.Book to R.color.indigo
|
||||
LocationIcon.ClothingStore -> Icons.Rounded.Checkroom to R.color.indigo
|
||||
LocationIcon.ConvenienceStore -> Icons.Rounded.LocalConvenienceStore to R.color.indigo
|
||||
LocationIcon.DiscountStore -> Icons.Rounded.Discount to R.color.indigo
|
||||
LocationIcon.JewelryStore -> Icons.Rounded.Diamond to R.color.indigo
|
||||
LocationIcon.ShoppingMall -> Icons.Rounded.LocalMall to R.color.indigo
|
||||
LocationIcon.LiquorStore -> Icons.Rounded.Liquor to R.color.indigo
|
||||
LocationIcon.PetStore -> Icons.Rounded.Pets to R.color.indigo
|
||||
LocationIcon.Bakery -> Icons.Rounded.BakeryDining to R.color.indigo
|
||||
LocationIcon.Optician -> Icons.Rounded.Eyeglasses to R.color.indigo
|
||||
LocationIcon.Pharmacy -> Icons.Rounded.LocalPharmacy to R.color.indigo
|
||||
LocationIcon.HairSalon -> Icons.Rounded.ContentCut to R.color.indigo
|
||||
LocationIcon.Laundromat -> Icons.Rounded.LocalLaundryService to R.color.indigo
|
||||
// purple: sports and recreation
|
||||
LocationIcon.Sports -> Icons.Rounded.Sports to R.color.purple
|
||||
LocationIcon.FitnessCenter -> Icons.Rounded.FitnessCenter to R.color.purple
|
||||
LocationIcon.Soccer -> Icons.Rounded.SportsSoccer to R.color.purple
|
||||
LocationIcon.Basketball -> Icons.Rounded.SportsBasketball to R.color.purple
|
||||
LocationIcon.Golf -> Icons.Rounded.SportsGolf to R.color.purple
|
||||
LocationIcon.Tennis -> Icons.Rounded.SportsTennis to R.color.purple
|
||||
LocationIcon.Baseball -> Icons.Rounded.SportsBaseball to R.color.purple
|
||||
LocationIcon.Rugby -> Icons.Rounded.SportsRugby to R.color.purple
|
||||
LocationIcon.AmericanFootball -> Icons.Rounded.SportsFootball to R.color.purple
|
||||
LocationIcon.Hiking -> Icons.Rounded.Hiking to R.color.purple
|
||||
LocationIcon.Swimming -> Icons.Rounded.Pool to R.color.purple
|
||||
LocationIcon.Surfing -> Icons.Rounded.Surfing to R.color.purple
|
||||
LocationIcon.Motorsports -> Icons.Rounded.SportsMotorsports to R.color.purple
|
||||
LocationIcon.Handball -> Icons.Rounded.SportsHandball to R.color.purple
|
||||
LocationIcon.Volleyball -> Icons.Rounded.SportsVolleyball to R.color.purple
|
||||
LocationIcon.Skiing -> Icons.Rounded.DownhillSkiing to R.color.purple
|
||||
LocationIcon.Kayaking -> Icons.Rounded.Kayaking to R.color.purple
|
||||
LocationIcon.Skateboarding -> Icons.Rounded.Skateboarding to R.color.purple
|
||||
LocationIcon.Cricket -> Icons.Rounded.SportsCricket to R.color.purple
|
||||
LocationIcon.MartialArts -> Icons.Rounded.SportsMartialArts to R.color.purple
|
||||
LocationIcon.NordicWalking -> Icons.Rounded.NordicWalking to R.color.purple
|
||||
LocationIcon.Paragliding -> Icons.Rounded.Paragliding to R.color.purple
|
||||
LocationIcon.Gymnastics -> Icons.Rounded.SportsGymnastics to R.color.purple
|
||||
LocationIcon.Snowboarding -> Icons.Rounded.Snowboarding to R.color.purple
|
||||
LocationIcon.Hockey -> Icons.Rounded.SportsHockey to R.color.purple
|
||||
// green: finances
|
||||
LocationIcon.Bank -> Icons.Rounded.AttachMoney to R.color.green
|
||||
LocationIcon.Atm -> Icons.Rounded.LocalAtm to R.color.green
|
||||
// red: health
|
||||
LocationIcon.Hospital -> Icons.Rounded.LocalHospital to R.color.red
|
||||
LocationIcon.Clinic -> Icons.Rounded.LocalHospital to R.color.red
|
||||
LocationIcon.Dentist -> Icons.Rounded.Dentistry to R.color.red
|
||||
LocationIcon.Physician -> Icons.Rounded.Stethoscope to R.color.red
|
||||
// light green: nature
|
||||
LocationIcon.Park -> Icons.Rounded.Park to R.color.lightgreen
|
||||
LocationIcon.Forest -> Icons.Rounded.Forest to R.color.lightgreen
|
||||
// brown: places of worship and remembrance
|
||||
LocationIcon.Monument -> Icons.Rounded.Monument to R.color.brown
|
||||
LocationIcon.Church -> Icons.Rounded.Church to R.color.brown
|
||||
LocationIcon.Mosque -> Icons.Rounded.Mosque to R.color.brown
|
||||
LocationIcon.Synagogue -> Icons.Rounded.Synagogue to R.color.brown
|
||||
LocationIcon.HinduTemple -> Icons.Rounded.TempleHindu to R.color.brown
|
||||
LocationIcon.BuddhistTemple -> Icons.Rounded.TempleBuddhist to R.color.brown
|
||||
LocationIcon.Candle -> Icons.Rounded.Candle to R.color.brown
|
||||
// bluegrey: public services
|
||||
LocationIcon.GovernmentBuilding -> Icons.Rounded.AccountBalance to R.color.bluegrey
|
||||
LocationIcon.Police -> Icons.Rounded.LocalPolice to R.color.bluegrey
|
||||
LocationIcon.FireDepartment -> Icons.Rounded.LocalFireDepartment to R.color.bluegrey
|
||||
LocationIcon.Courthouse -> Icons.Rounded.Gavel to R.color.bluegrey
|
||||
LocationIcon.PostOffice -> Icons.Rounded.LocalPostOffice to R.color.bluegrey
|
||||
LocationIcon.Library -> Icons.Rounded.Book to R.color.bluegrey
|
||||
LocationIcon.School -> Icons.Rounded.School to R.color.bluegrey
|
||||
LocationIcon.University -> Icons.Rounded.School to R.color.bluegrey
|
||||
LocationIcon.PublicBathroom -> Icons.Rounded.Wc to R.color.bluegrey
|
||||
|
||||
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_STATION -> 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
|
||||
null -> Icons.Rounded.Place to R.color.bluegrey
|
||||
}
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = TintedIconLayer(
|
||||
icon = ContextCompat.getDrawable(context, resId)!!,
|
||||
scale = 0.5f,
|
||||
foregroundLayer = VectorLayer(
|
||||
vector = vector,
|
||||
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 toAndroidLocation(): AndroidLocation =
|
||||
AndroidLocation("KvaesitsoLocationProvider").apply {
|
||||
this.latitude = this@Location.latitude
|
||||
this.longitude = this@Location.longitude
|
||||
}
|
||||
|
||||
fun distanceTo(androidLocation: AndroidLocation): Float {
|
||||
return androidLocation.distanceTo(this.toAndroidLocation())
|
||||
@@ -149,107 +322,25 @@ interface Location : SavableSearchable {
|
||||
|
||||
fun distanceTo(otherLocation: Location): Float =
|
||||
this.distanceTo(otherLocation.toAndroidLocation())
|
||||
|
||||
fun distanceTo(
|
||||
latitude: Double,
|
||||
longitude: Double,
|
||||
locationProvider: String = "KvaesitsoLocationProvider"
|
||||
): Float =
|
||||
this.distanceTo(AndroidLocation(locationProvider).apply {
|
||||
this.latitude = latitude
|
||||
this.longitude = longitude
|
||||
})
|
||||
}
|
||||
|
||||
// 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_STATION,
|
||||
RAILWAY_STOP,
|
||||
BUS_STATION,
|
||||
ATM,
|
||||
ART,
|
||||
KIOSK,
|
||||
BUS_STOP,
|
||||
MUSEUM,
|
||||
PARCEL_LOCKER,
|
||||
CHEMIST,
|
||||
TRAVEL_AGENCY,
|
||||
FITNESS_CENTRE
|
||||
fun OpeningSchedule.isOpen(date: LocalDateTime = LocalDateTime.now()): Boolean = when (this) {
|
||||
is OpeningSchedule.TwentyFourSeven -> true
|
||||
is OpeningSchedule.Hours -> openingHours.any { it.isOpen(date) }
|
||||
}
|
||||
|
||||
data class OpeningHours(
|
||||
val dayOfWeek: DayOfWeek,
|
||||
val startTime: LocalTime,
|
||||
val duration: Duration
|
||||
) {
|
||||
|
||||
fun isOpen(date: LocalDateTime = LocalDateTime.now()): Boolean {
|
||||
val startTime = date.with(TemporalAdjusters.previousOrSame(dayOfWeek)).with(startTime)
|
||||
val endTime = startTime.plus(duration)
|
||||
return date in startTime..<endTime
|
||||
}
|
||||
|
||||
override fun toString(): String = "$dayOfWeek $startTime-${startTime.plus(duration)}"
|
||||
}
|
||||
|
||||
data class OpeningSchedule(
|
||||
val isTwentyFourSeven: Boolean,
|
||||
val openingHours: ImmutableList<OpeningHours>
|
||||
) {
|
||||
fun isOpen(date: LocalDateTime = LocalDateTime.now()): Boolean {
|
||||
return isTwentyFourSeven || openingHours.any { it.isOpen(date) }
|
||||
}
|
||||
fun OpeningHours.isOpen(date: LocalDateTime = LocalDateTime.now()): Boolean {
|
||||
val startTime = date.with(TemporalAdjusters.previousOrSame(dayOfWeek)).with(startTime)
|
||||
val endTime = startTime.plus(duration)
|
||||
return date in startTime..<endTime
|
||||
}
|
||||
@@ -8,7 +8,7 @@ package de.mm20.launcher2.search
|
||||
*/
|
||||
interface UpdatableSearchable<T : SavableSearchable> {
|
||||
val timestamp: Long
|
||||
val updatedSelf: (suspend () -> UpdateResult<T>)?
|
||||
val updatedSelf: (suspend (SavableSearchable) -> UpdateResult<T>)?
|
||||
}
|
||||
|
||||
sealed class UpdateResult<out T> {
|
||||
@@ -16,3 +16,16 @@ sealed class UpdateResult<out T> {
|
||||
data class TemporarilyUnavailable<T>(val cause: Throwable? = null) : UpdateResult<T>()
|
||||
data class PermanentlyUnavailable<T>(val cause: Throwable? = null) : UpdateResult<T>()
|
||||
}
|
||||
|
||||
fun <T>Result<T?>.asUpdateResult(): UpdateResult<T> {
|
||||
return if (isSuccess) {
|
||||
val refreshed = getOrNull()
|
||||
if (refreshed == null) {
|
||||
UpdateResult.PermanentlyUnavailable()
|
||||
} else {
|
||||
UpdateResult.Success(refreshed)
|
||||
}
|
||||
} else {
|
||||
UpdateResult.TemporarilyUnavailable(exceptionOrNull())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package de.mm20.launcher2.search
|
||||
|
||||
import de.mm20.launcher2.search.location.OpeningHours
|
||||
import de.mm20.launcher2.search.location.OpeningSchedule
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
|
||||
@@ -10,7 +10,9 @@ import android.hardware.SensorManager
|
||||
import android.location.Location
|
||||
import android.location.LocationListener
|
||||
import android.location.LocationManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.getSystemService
|
||||
import de.mm20.launcher2.ktx.PI
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
|
||||
@@ -757,7 +757,7 @@
|
||||
<string name="preference_default_filter_summary">Dostosuj domyślny filtr wyszukiwania</string>
|
||||
<string name="preference_filter_bar">Pokaż pasek filtrów</string>
|
||||
<string name="preference_filter_bar_summary">Pokaż szybkie filtry nad klawiaturą</string>
|
||||
<string name="location_closes">Zamyka się za</string>
|
||||
<string name="location_closes">Zamyka się za %1$s</string>
|
||||
<string name="location_closes_other_day">Zamknięcie %1$s, %2$s</string>
|
||||
<string name="location_opens">Otwórz o %1$s</string>
|
||||
<string name="location_opens_other_day">Otwórz %1$s, %2$s</string>
|
||||
|
||||
@@ -925,6 +925,7 @@
|
||||
<string name="search_filter_tools">Tools</string>
|
||||
<string name="search_filter_online">Online results</string>
|
||||
<string name="search_filter_apps">Apps</string>
|
||||
<string name="plugin_type_locationsearch">Location search</string>
|
||||
<string name="preference_default_filter">Default filter</string>
|
||||
<string name="preference_default_filter_summary">Customize the default filter for searches</string>
|
||||
<string name="preference_filter_bar">Show filter bar</string>
|
||||
@@ -932,6 +933,8 @@
|
||||
<string name="preference_customize_filter_bar">Customize filter bar</string>
|
||||
<string name="preference_customize_filter_bar_summary">Customize which items are included in the filter bar</string>
|
||||
<string name="filter_settings_network_warning">The current filter enables online results by default. Your search queries might unintentionally be sent to external web services. For privacy reasons, this is not recommended.</string>
|
||||
<string name="preference_search_osm_locations">OpenStreetMap</string>
|
||||
<string name="preference_search_osm_locations_summary">Search OpenStreetMap for shops and other places in the local area</string>
|
||||
<string name="poi_category_restaurant">Restaurant</string>
|
||||
<string name="poi_category_fast_food">Fast food</string>
|
||||
<string name="poi_category_bar">Bar</string>
|
||||
@@ -1006,4 +1009,50 @@
|
||||
<string name="poi_category_chemist">Drug store</string>
|
||||
<string name="poi_category_travel_agency">Travel agency</string>
|
||||
<string name="poi_category_fitness_center">Fitness center</string>
|
||||
<string name="poi_category_church">Church</string>
|
||||
<string name="poi_category_mosque">Mosque</string>
|
||||
<string name="poi_category_buddhist_temple">Buddhist temple</string>
|
||||
<string name="poi_category_hindu_temple">Hindu temple</string>
|
||||
<string name="poi_category_synagogue">Synagogue</string>
|
||||
<string name="poi_category_pizza_restaurant">Pizza restaurant</string>
|
||||
<string name="poi_category_burger_restaurant">Burger restaurant</string>
|
||||
<string name="poi_category_place_of_worship">Place of worship</string>
|
||||
<string name="poi_category_chinese_restaurant">Chinese restaurant</string>
|
||||
<string name="poi_category_japanese_restaurant">Japanese restaurant</string>
|
||||
<string name="poi_category_kebab_restaurant">Kebab restaurant</string>
|
||||
<string name="poi_category_asian_restaurant">Asian restaurant</string>
|
||||
<string name="poi_category_ramen_restaurant">Ramen restaurant</string>
|
||||
<string name="poi_category_soup_restaurant">Soup restaurant</string>
|
||||
<string name="poi_category_brunch_restaurant">Brunch restaurant</string>
|
||||
<string name="poi_category_breakfast_restaurant">Breakfast restaurant</string>
|
||||
<string name="poi_category_car_wash">Car wash</string>
|
||||
<string name="poi_category_charging_station">Charging station</string>
|
||||
<string name="poi_category_motorcycle_rental">Motorcycle rental</string>
|
||||
<string name="poi_category_gallery">Gallery</string>
|
||||
<string name="poi_category_amusement_park">Amusement park</string>
|
||||
<string name="poi_category_concert_hall">Concert hall</string>
|
||||
<string name="poi_category_stadium">Stadium</string>
|
||||
<string name="poi_category_casino">Casino</string>
|
||||
<string name="poi_category_shopping_center">Shopping center</string>
|
||||
<string name="poi_category_discount_store">Discount store</string>
|
||||
<string name="poi_category_pet">Pet store</string>
|
||||
<string name="poi_category_shopping">Shopping</string>
|
||||
<string name="poi_category_swimming">Swimming pool</string>
|
||||
<string name="poi_category_martial_arts">Martial arts</string>
|
||||
<string name="poi_category_golf">Golf</string>
|
||||
<string name="poi_category_gymnastics">Gymnastics</string>
|
||||
<string name="poi_category_ice_hockey">Ice hockey</string>
|
||||
<string name="poi_category_baseball">Baseball</string>
|
||||
<string name="poi_category_american_football">American football</string>
|
||||
<string name="poi_category_handball">Handball</string>
|
||||
<string name="poi_category_volleyball">Volleyball</string>
|
||||
<string name="poi_category_skiing">Skiing</string>
|
||||
<string name="poi_category_cricket">Cricket</string>
|
||||
<string name="poi_category_park">Park</string>
|
||||
<string name="poi_category_monument">Monument</string>
|
||||
<string name="poi_category_government_building">Government building</string>
|
||||
<string name="poi_category_fire_station">Fire station</string>
|
||||
<string name="poi_category_courthouse">Courthouse</string>
|
||||
<string name="poi_category_townhall">Townhall</string>
|
||||
<string name="preference_search_locations_radius_large_radius_warning">Large search radii can significantly slow down location search.</string>
|
||||
</resources>
|
||||
@@ -2,9 +2,8 @@ package de.mm20.launcher2.ktx
|
||||
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.decodeFromString
|
||||
|
||||
inline fun <reified T>Json.decodeFromStringOrNull(json: String?): T? {
|
||||
inline fun <reified T> Json.decodeFromStringOrNull(json: String?): T? {
|
||||
if (json == null) return null
|
||||
return try {
|
||||
decodeFromString(json)
|
||||
|
||||
@@ -2,19 +2,17 @@ package de.mm20.launcher2.ktx
|
||||
|
||||
import android.app.ActivityOptions
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
|
||||
fun PendingIntent.sendWithBackgroundPermission() {
|
||||
fun PendingIntent.sendWithBackgroundPermission(context: Context) {
|
||||
if (isAtLeastApiLevel(34)) {
|
||||
val options = ActivityOptions.makeBasic()
|
||||
.setPendingIntentCreatorBackgroundActivityStartMode(
|
||||
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
|
||||
)
|
||||
.setPendingIntentBackgroundActivityStartMode(
|
||||
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
|
||||
)
|
||||
.toBundle()
|
||||
send(options)
|
||||
send(context, 0, null, null, null, null, options)
|
||||
} else {
|
||||
send()
|
||||
send(context, 0, null)
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package de.mm20.launcher2.preferences
|
||||
import android.content.Context
|
||||
import de.mm20.launcher2.preferences.migrations.Migration1
|
||||
import de.mm20.launcher2.preferences.migrations.Migration2
|
||||
import de.mm20.launcher2.preferences.migrations.Migration3
|
||||
import de.mm20.launcher2.settings.BaseSettings
|
||||
|
||||
internal class LauncherDataStore(
|
||||
@@ -15,6 +16,7 @@ internal class LauncherDataStore(
|
||||
migrations = listOf(
|
||||
Migration1(legacyDataStore),
|
||||
Migration2(),
|
||||
Migration3(),
|
||||
),
|
||||
) {
|
||||
|
||||
|
||||
@@ -135,7 +135,9 @@ data class LauncherSettingsData internal constructor(
|
||||
val weatherProviderSettings: Map<String, ProviderSettings> = emptyMap(),
|
||||
val weatherImperialUnits: Boolean = false,
|
||||
|
||||
@Deprecated("Use locationSearchProviders instead")
|
||||
val locationSearchEnabled: Boolean = false,
|
||||
val locationSearchProviders: Set<String> = setOf("openstreetmaps"),
|
||||
val locationSearchImperialUnits: Boolean = false,
|
||||
val locationSearchRadius: Int = 1500,
|
||||
val locationSearchHideUncategorized: Boolean = true,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package de.mm20.launcher2.preferences.migrations
|
||||
|
||||
import androidx.datastore.core.DataMigration
|
||||
import de.mm20.launcher2.preferences.LauncherSettingsData
|
||||
|
||||
class Migration3: DataMigration<LauncherSettingsData> {
|
||||
override suspend fun cleanUp() {
|
||||
}
|
||||
|
||||
override suspend fun shouldMigrate(currentData: LauncherSettingsData): Boolean {
|
||||
return currentData.schemaVersion < 3
|
||||
}
|
||||
|
||||
override suspend fun migrate(currentData: LauncherSettingsData): LauncherSettingsData {
|
||||
return currentData.copy(
|
||||
schemaVersion = 3,
|
||||
locationSearchProviders = buildSet {
|
||||
if (currentData.locationSearchEnabled) {
|
||||
add("openstreetmaps")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
+27
-16
@@ -1,16 +1,16 @@
|
||||
package de.mm20.launcher2.preferences.search
|
||||
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class LocationSearchSettings internal constructor(
|
||||
private val launcherDataStore: LauncherDataStore,
|
||||
) {
|
||||
|
||||
val data
|
||||
get() = launcherDataStore.data.map {
|
||||
LocationSearchSettingsData(
|
||||
enabled = it.locationSearchEnabled,
|
||||
providers = it.locationSearchProviders,
|
||||
searchRadius = it.locationSearchRadius,
|
||||
hideUncategorized = it.locationSearchHideUncategorized,
|
||||
overpassUrl = it.locationSearchOverpassUrl,
|
||||
@@ -22,12 +22,32 @@ class LocationSearchSettings internal constructor(
|
||||
)
|
||||
}
|
||||
|
||||
val enabled
|
||||
get() = launcherDataStore.data.map { it.locationSearchEnabled }
|
||||
val enabledProviders: Flow<Set<String>>
|
||||
get() = launcherDataStore.data.map { it.locationSearchProviders }
|
||||
|
||||
fun setEnabled(enabled: Boolean) {
|
||||
val osmLocations
|
||||
get() = launcherDataStore.data.map { it.locationSearchProviders.contains("openstreetmaps") }
|
||||
|
||||
fun setOsmLocations(osmLocations: Boolean) {
|
||||
launcherDataStore.update {
|
||||
it.copy(locationSearchEnabled = enabled)
|
||||
if (osmLocations) {
|
||||
it.copy(locationSearchProviders = it.locationSearchProviders + "openstreetmaps")
|
||||
} else {
|
||||
it.copy(locationSearchProviders = it.locationSearchProviders - "openstreetmaps")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val enabledPlugins: Flow<Set<String>>
|
||||
get() = launcherDataStore.data.map { it.locationSearchProviders - "openstreetmaps" }
|
||||
|
||||
fun setPluginEnabled(authority: String, enabled: Boolean) {
|
||||
launcherDataStore.update {
|
||||
if (enabled) {
|
||||
it.copy(locationSearchProviders = it.locationSearchProviders + authority)
|
||||
} else {
|
||||
it.copy(locationSearchProviders = it.locationSearchProviders - authority)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,15 +96,6 @@ class LocationSearchSettings internal constructor(
|
||||
}
|
||||
}
|
||||
|
||||
val showPositionOnMap
|
||||
get() = launcherDataStore.data.map { it.locationSearchShowPositionOnMap }
|
||||
|
||||
fun setShowPositionOnMap(showPositionOnMap: Boolean) {
|
||||
launcherDataStore.update {
|
||||
it.copy(locationSearchShowPositionOnMap = showPositionOnMap)
|
||||
}
|
||||
}
|
||||
|
||||
val themeMap
|
||||
get() = launcherDataStore.data.map { it.locationSearchThemeMap }
|
||||
|
||||
@@ -111,7 +122,7 @@ class LocationSearchSettings internal constructor(
|
||||
}
|
||||
|
||||
data class LocationSearchSettingsData(
|
||||
val enabled: Boolean = false,
|
||||
val providers: Set<String> = setOf("openstreetmaps"),
|
||||
val searchRadius: Int = 1500,
|
||||
val hideUncategorized: Boolean = true,
|
||||
val overpassUrl: String = LocationSearchSettings.DefaultOverpassUrl,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.plugin.serialization)
|
||||
alias(libs.plugins.dokka)
|
||||
`maven-publish`
|
||||
signing
|
||||
@@ -46,6 +47,11 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.bundles.kotlin)
|
||||
}
|
||||
|
||||
tasks.dokkaHtml {
|
||||
outputDirectory.set(layout.buildDirectory.dir("dokka"))
|
||||
}
|
||||
@@ -95,12 +101,16 @@ publishing {
|
||||
}
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/MM2-0/Kvaesitso")
|
||||
credentials {
|
||||
username = project.findProperty("gpr.user") as String? ?: System.getenv("USERNAME")
|
||||
password = project.findProperty("gpr.key") as String? ?: System.getenv("TOKEN")
|
||||
val ghUser = project.findProperty("gpr.user") as String? ?: System.getenv("USERNAME")
|
||||
if (ghUser == "MM2-0") {
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/MM2-0/Kvaesitso")
|
||||
credentials {
|
||||
username =
|
||||
project.findProperty("gpr.user") as String? ?: System.getenv("USERNAME")
|
||||
password = project.findProperty("gpr.key") as String? ?: System.getenv("TOKEN")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,4 +3,5 @@ package de.mm20.launcher2.plugin
|
||||
enum class PluginType {
|
||||
FileSearch,
|
||||
Weather,
|
||||
LocationSearch,
|
||||
}
|
||||
+1
-4
@@ -1,9 +1,6 @@
|
||||
package de.mm20.launcher2.plugin.config
|
||||
|
||||
import android.os.Bundle
|
||||
import de.mm20.launcher2.plugin.config.StorageStrategy
|
||||
|
||||
data class SearchPluginConfig(
|
||||
data class QueryPluginConfig(
|
||||
/**
|
||||
* Strategy to store items from this provider in the launcher database
|
||||
* @see [StorageStrategy]
|
||||
@@ -1,9 +1,13 @@
|
||||
package de.mm20.launcher2.plugin.config
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Defines how the launcher should store search results from a plugin (i.e. when the search result is
|
||||
* added to favorites).
|
||||
*/
|
||||
@Serializable
|
||||
enum class StorageStrategy {
|
||||
/**
|
||||
* The launcher only stores the ID and the plugin provider for the search result. To restore the
|
||||
@@ -13,6 +17,7 @@ enum class StorageStrategy {
|
||||
* particular, the plugin provider must be able to restore a search result without any network
|
||||
* requests.
|
||||
*/
|
||||
@SerialName("ref")
|
||||
StoreReference,
|
||||
|
||||
/**
|
||||
@@ -21,13 +26,6 @@ enum class StorageStrategy {
|
||||
* Use this strategy if your plugin needs to perform network requests to retrieve search
|
||||
* results and if you don't want to implement a cache for search results.
|
||||
*/
|
||||
@SerialName("copy")
|
||||
StoreCopy,
|
||||
|
||||
/**
|
||||
* The launcher stores all relevant information in its own internal database, like [StoreCopy].
|
||||
* A fresh copy is fetched from the plugin provider when the user opens the search result's
|
||||
* detail view. This allows the plugin provider to update the search result at a later point in
|
||||
* time, without the time constraints of [StoreReference].
|
||||
*/
|
||||
Deferred,
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
package de.mm20.launcher2.plugin.contracts
|
||||
|
||||
import android.database.Cursor
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import de.mm20.launcher2.plugin.data.RowBuilderScope
|
||||
import de.mm20.launcher2.serialization.Json
|
||||
import kotlinx.serialization.encodeToString
|
||||
|
||||
abstract class Columns {
|
||||
internal val columns = mutableSetOf<String>()
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal inline fun <reified T : Any> Columns.column(name: String): Column<T> {
|
||||
val column = when (T::class) {
|
||||
Int::class -> IntColumn(name)
|
||||
Long::class -> LongColumn(name)
|
||||
String::class -> StringColumn(name)
|
||||
Short::class -> ShortColumn(name)
|
||||
Boolean::class -> BooleanColumn(name)
|
||||
Double::class -> DoubleColumn(name)
|
||||
Float::class -> FloatColumn(name)
|
||||
ByteArray::class -> BlobColumn(name)
|
||||
else -> {
|
||||
if (T::class.java.isEnum) {
|
||||
SerializableColumn(
|
||||
name,
|
||||
{ it.toString() },
|
||||
{ v -> T::class.java.enumConstants?.find { it.toString() == v } }
|
||||
)
|
||||
} else {
|
||||
SerializableColumn(
|
||||
name,
|
||||
{ Json.Lenient.encodeToString(it) },
|
||||
{ Json.Lenient.decodeFromString(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
} as Column<T>
|
||||
columns.add(name)
|
||||
return column
|
||||
}
|
||||
|
||||
sealed interface Column<T> {
|
||||
val name: String
|
||||
fun Cursor.readAtIndex(index: Int): T?
|
||||
fun RowBuilderScope.toCursorValue(value: T?): Any? {
|
||||
return value
|
||||
}
|
||||
|
||||
fun Bundle.put(value: T?)
|
||||
fun Bundle.get(): T?
|
||||
}
|
||||
|
||||
internal class IntColumn(
|
||||
override val name: String
|
||||
) : Column<Int> {
|
||||
override fun Cursor.readAtIndex(index: Int): Int {
|
||||
return getInt(index)
|
||||
}
|
||||
|
||||
override fun Bundle.put(value: Int?) {
|
||||
if (value == null) {
|
||||
remove(name)
|
||||
} else {
|
||||
putInt(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun Bundle.get(): Int {
|
||||
return getInt(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class LongColumn(
|
||||
override val name: String
|
||||
) : Column<Long> {
|
||||
override fun Cursor.readAtIndex(index: Int): Long {
|
||||
return getLong(index)
|
||||
}
|
||||
|
||||
override fun Bundle.put(value: Long?) {
|
||||
if (value == null) {
|
||||
remove(name)
|
||||
} else {
|
||||
putLong(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun Bundle.get(): Long {
|
||||
return getLong(name)
|
||||
}
|
||||
}
|
||||
|
||||
internal class DoubleColumn(
|
||||
override val name: String
|
||||
) : Column<Double> {
|
||||
override fun Cursor.readAtIndex(index: Int): Double {
|
||||
return getDouble(index)
|
||||
}
|
||||
|
||||
override fun Bundle.put(value: Double?) {
|
||||
if (value == null) {
|
||||
remove(name)
|
||||
} else {
|
||||
putDouble(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun Bundle.get(): Double {
|
||||
return getDouble(name)
|
||||
}
|
||||
}
|
||||
|
||||
internal class FloatColumn(
|
||||
override val name: String
|
||||
) : Column<Float> {
|
||||
override fun Cursor.readAtIndex(index: Int): Float {
|
||||
return getFloat(index)
|
||||
}
|
||||
|
||||
override fun Bundle.put(value: Float?) {
|
||||
if (value == null) {
|
||||
remove(name)
|
||||
} else {
|
||||
putFloat(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun Bundle.get(): Float {
|
||||
return getFloat(name)
|
||||
}
|
||||
}
|
||||
|
||||
internal class StringColumn(
|
||||
override val name: String
|
||||
) : Column<String> {
|
||||
override fun Cursor.readAtIndex(index: Int): String? {
|
||||
return getString(index)
|
||||
}
|
||||
|
||||
override fun Bundle.put(value: String?) {
|
||||
if (value == null) {
|
||||
remove(name)
|
||||
} else {
|
||||
putString(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun Bundle.get(): String? {
|
||||
return getString(name)
|
||||
}
|
||||
}
|
||||
|
||||
internal class ShortColumn(
|
||||
override val name: String
|
||||
) : Column<Short> {
|
||||
override fun Cursor.readAtIndex(index: Int): Short {
|
||||
return getShort(index)
|
||||
}
|
||||
|
||||
override fun Bundle.put(value: Short?) {
|
||||
if (value == null) {
|
||||
remove(name)
|
||||
} else {
|
||||
putShort(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun Bundle.get(): Short {
|
||||
return getShort(name)
|
||||
}
|
||||
}
|
||||
|
||||
internal class BooleanColumn(
|
||||
override val name: String
|
||||
) : Column<Boolean> {
|
||||
override fun Cursor.readAtIndex(index: Int): Boolean {
|
||||
return getInt(index) != 0
|
||||
}
|
||||
|
||||
override fun RowBuilderScope.toCursorValue(value: Boolean?): Any? {
|
||||
value ?: return null
|
||||
return if (value) 1 else 0
|
||||
}
|
||||
|
||||
override fun Bundle.put(value: Boolean?) {
|
||||
if (value == null) {
|
||||
remove(name)
|
||||
} else {
|
||||
putBoolean(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun Bundle.get(): Boolean? {
|
||||
return getBoolean(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class BlobColumn(
|
||||
override val name: String
|
||||
) : Column<ByteArray> {
|
||||
|
||||
override fun Cursor.readAtIndex(index: Int): ByteArray? {
|
||||
return getBlob(index)
|
||||
}
|
||||
|
||||
override fun Bundle.put(value: ByteArray?) {
|
||||
if (value == null) {
|
||||
remove(name)
|
||||
} else {
|
||||
putByteArray(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun Bundle.get(): ByteArray? {
|
||||
return getByteArray(name)
|
||||
}
|
||||
}
|
||||
|
||||
internal class SerializableColumn<T>(
|
||||
override val name: String,
|
||||
val serialize: (T) -> String,
|
||||
val deserialize: (String) -> T
|
||||
) : Column<T> {
|
||||
override fun Cursor.readAtIndex(index: Int): T? {
|
||||
val string = getString(index)
|
||||
try {
|
||||
return deserialize(string)
|
||||
} catch (e: Exception) {
|
||||
Log.e("MM20", "Failed to read column value", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
override fun RowBuilderScope.toCursorValue(value: T?): Any? {
|
||||
return try {
|
||||
serialize(value ?: return null)
|
||||
} catch (e: Exception) {
|
||||
Log.e("MM20", "Failed to write column value", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun Bundle.put(value: T?) {
|
||||
val serialized = try {
|
||||
value?.let(serialize)
|
||||
} catch (e: Exception) {
|
||||
Log.e("MM20", "Failed to serialize column value", e)
|
||||
null
|
||||
}
|
||||
if (serialized == null) {
|
||||
remove(name)
|
||||
} else {
|
||||
putString(name, serialized)
|
||||
}
|
||||
}
|
||||
|
||||
override fun Bundle.get(): T? {
|
||||
val string = getString(name) ?: return null
|
||||
return try {
|
||||
deserialize(string)
|
||||
} catch (e: Exception) {
|
||||
Log.e("MM20", "Failed to deserialize column value", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
-21
@@ -2,64 +2,63 @@ package de.mm20.launcher2.plugin.contracts
|
||||
|
||||
abstract class FilePluginContract {
|
||||
|
||||
object FileColumns {
|
||||
object FileColumns: Columns() {
|
||||
/**
|
||||
* The unique ID of the file.
|
||||
* Type: String
|
||||
*/
|
||||
const val Id = "id"
|
||||
val Id = column<String>("id")
|
||||
|
||||
/**
|
||||
* The display name of the file.
|
||||
* Type: String
|
||||
*/
|
||||
const val DisplayName = "display_name"
|
||||
val DisplayName = column<String>("display_name")
|
||||
|
||||
/**
|
||||
* The MIME type of the file. This is used to determine how to open the file. Make sure that
|
||||
* this is either a common MIME type or that your app can handle this MIME type.
|
||||
* Type: String?
|
||||
*/
|
||||
const val MimeType = "mime_type"
|
||||
val MimeType = column<String>("mime_type")
|
||||
|
||||
/**
|
||||
* The size of the file in bytes.
|
||||
* Type: Long?
|
||||
*/
|
||||
const val Size = "size"
|
||||
val Size = column<Long>("size")
|
||||
|
||||
/**
|
||||
* The display path of the file.
|
||||
* Type: String?
|
||||
*/
|
||||
const val Path = "path"
|
||||
val Path = column<String>("path")
|
||||
|
||||
/**
|
||||
* The URI to view the file.
|
||||
* Type: String?
|
||||
*/
|
||||
const val ContentUri = "uri"
|
||||
val ContentUri = column<String>("uri")
|
||||
|
||||
const val ThumbnailUri = "thumbnail_uri"
|
||||
val ThumbnailUri = column<String>("thumbnail_uri")
|
||||
|
||||
/**
|
||||
* Whether the file is a directory.
|
||||
* Type: Int
|
||||
*/
|
||||
const val IsDirectory = "is_directory"
|
||||
val IsDirectory = column<Boolean>("is_directory")
|
||||
|
||||
const val Owner = "owner"
|
||||
val Owner = column<String>("owner")
|
||||
|
||||
const val MetaTitle = "meta_title"
|
||||
const val MetaArtist = "meta_artist"
|
||||
const val MetaAlbum = "meta_album"
|
||||
const val MetaDuration = "meta_duration"
|
||||
const val MetaYear = "meta_year"
|
||||
const val MetaWidth = "meta_width"
|
||||
const val MetaHeight = "meta_height"
|
||||
const val MetaLocation = "meta_location"
|
||||
const val MetaAppName = "meta_app_name"
|
||||
const val MetaAppPackageName = "meta_app_package_name"
|
||||
const val MetaAppMinSdkVersion = "meta_app_min_sdk_version"
|
||||
val MetaTitle = column<String>("meta_title")
|
||||
val MetaArtist = column<String>("meta_artist")
|
||||
val MetaAlbum = column<String>("meta_album")
|
||||
val MetaDuration = column<Long>("meta_duration")
|
||||
val MetaYear = column<Int>("meta_year")
|
||||
val MetaWidth = column<Int>("meta_width")
|
||||
val MetaHeight = column<Int>("meta_height")
|
||||
val MetaLocation = column<String>("meta_location")
|
||||
val MetaAppName = column<String>("meta_app_name")
|
||||
val MetaAppPackageName = column<String>("meta_app_package_name")
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
package de.mm20.launcher2.plugin.contracts
|
||||
|
||||
import de.mm20.launcher2.search.location.Address
|
||||
import de.mm20.launcher2.search.location.Attribution
|
||||
import de.mm20.launcher2.search.location.Departure
|
||||
import de.mm20.launcher2.search.location.LocationIcon
|
||||
import de.mm20.launcher2.search.location.OpeningSchedule
|
||||
|
||||
abstract class LocationPluginContract {
|
||||
object Paths {
|
||||
const val Search = "search"
|
||||
const val Get = "get"
|
||||
}
|
||||
|
||||
object Params {
|
||||
/**
|
||||
* Search query.
|
||||
* Type: String
|
||||
*/
|
||||
const val Query = "query"
|
||||
|
||||
/**
|
||||
* Latitude of user's current location in degrees.
|
||||
* Type: Double?
|
||||
*/
|
||||
const val UserLatitude = "user_latitude"
|
||||
|
||||
/**
|
||||
* Longitude of user's current location in degrees.
|
||||
* Type: Double?
|
||||
*/
|
||||
const val UserLongitude = "user_longitude"
|
||||
|
||||
/**
|
||||
* Search radius in meters.
|
||||
* Type: Long
|
||||
*/
|
||||
const val SearchRadius = "search_radius"
|
||||
|
||||
/**
|
||||
* Whether to allow network requests.
|
||||
* Type: Boolean
|
||||
*/
|
||||
const val AllowNetwork = "network"
|
||||
}
|
||||
|
||||
object GetParams {
|
||||
/**
|
||||
* Unique identifier of location to look up.
|
||||
* Type: String
|
||||
*/
|
||||
const val Id = "id"
|
||||
}
|
||||
|
||||
object LocationColumns : Columns() {
|
||||
/**
|
||||
* Unique identifier of location.
|
||||
* Type: String
|
||||
*/
|
||||
val Id = column<String>("id")
|
||||
|
||||
/**
|
||||
* Display name of location.
|
||||
* Type: String
|
||||
*/
|
||||
val Label = column<String>("label")
|
||||
|
||||
/**
|
||||
* Latitude of location in degrees.
|
||||
* Type: Double
|
||||
*/
|
||||
val Latitude = column<Double>("latitude")
|
||||
|
||||
/**
|
||||
* Longitude of location in degrees.
|
||||
* Type: Double
|
||||
*/
|
||||
val Longitude = column<Double>("longitude")
|
||||
|
||||
/**
|
||||
* Url for users to report / fix incorrect data.
|
||||
* Type: String?
|
||||
*/
|
||||
val FixMeUrl = column<String>("fix_me_url")
|
||||
|
||||
/**
|
||||
* Icon of location.
|
||||
* Type: String? (enum LocationIcon)
|
||||
*/
|
||||
val Icon = column<LocationIcon>("icon")
|
||||
|
||||
/**
|
||||
* Location category.
|
||||
* Type: String?
|
||||
*/
|
||||
val Category = column<String>("category")
|
||||
|
||||
/**
|
||||
* Street name of location.
|
||||
* Type: String? (JSON)
|
||||
*/
|
||||
val Address = column<Address>("address")
|
||||
|
||||
/**
|
||||
* Opening schedule of location, encoded as JSON.
|
||||
* Type: String? (JSON)
|
||||
*/
|
||||
val OpeningSchedule = column<OpeningSchedule>("opening_schedule")
|
||||
|
||||
/**
|
||||
* Website URL of location.
|
||||
* Type: String?
|
||||
*/
|
||||
val WebsiteUrl = column<String>("website_url")
|
||||
|
||||
/**
|
||||
* Phone number of location.
|
||||
* Type: String?
|
||||
*/
|
||||
val PhoneNumber = column<String>("phone_number")
|
||||
|
||||
val EmailAddress = column<String>("email_address")
|
||||
|
||||
/**
|
||||
* User rating of location, from 0.0 (worst) to 1.0 (best)
|
||||
* Type: Float?
|
||||
*/
|
||||
val UserRating = column<Float>("user_rating")
|
||||
val UserRatingCount = column<Int>("user_rating_count")
|
||||
|
||||
/**
|
||||
* Public transport departures originating from this location, encoded as JSON.
|
||||
* Type: String? (JSON)
|
||||
*/
|
||||
val Departures = column<List<Departure>>("departures")
|
||||
|
||||
/**
|
||||
* Attribution information for location data.
|
||||
* Type: String? (JSON)
|
||||
*/
|
||||
val Attribution = column<Attribution>("attribution")
|
||||
}
|
||||
}
|
||||
+16
-2
@@ -4,7 +4,21 @@ abstract class SearchPluginContract {
|
||||
object Paths {
|
||||
const val Search = "search"
|
||||
const val Root = "root"
|
||||
const val QueryParam = "query"
|
||||
const val AllowNetworkParam = "network"
|
||||
const val Refresh = "refresh"
|
||||
@Deprecated("Use Paths.Query instead")
|
||||
const val QueryParam = Params.Query
|
||||
@Deprecated("Use Params.AllowNetwork instead")
|
||||
const val AllowNetworkParam = Params.AllowNetwork
|
||||
@Deprecated("Use Params.Lang instead")
|
||||
const val LangParam = Params.Lang
|
||||
}
|
||||
object Params {
|
||||
const val AllowNetwork = "network"
|
||||
const val Lang = "lang"
|
||||
const val UpdatedAt = "updatedAt"
|
||||
const val Query = "query"
|
||||
}
|
||||
object Extras {
|
||||
const val NotUpdated = "notUpdated"
|
||||
}
|
||||
}
|
||||
+26
-24
@@ -1,5 +1,7 @@
|
||||
package de.mm20.launcher2.plugin.contracts
|
||||
|
||||
import de.mm20.launcher2.weather.WeatherIcon
|
||||
|
||||
object WeatherPluginContract {
|
||||
object Paths {
|
||||
const val Forecasts = "forecasts"
|
||||
@@ -14,25 +16,25 @@ object WeatherPluginContract {
|
||||
const val Language = "lang"
|
||||
}
|
||||
|
||||
object ForecastColumns {
|
||||
const val Timestamp = "timestamp"
|
||||
const val CreatedAt = "created_at"
|
||||
const val Temperature = "temperature"
|
||||
const val TemperatureMin = "temperature_min"
|
||||
const val TemperatureMax = "temperature_max"
|
||||
const val Pressure = "pressure"
|
||||
const val Humidity = "humidity"
|
||||
const val WindSpeed = "wind_speed"
|
||||
const val WindDirection = "wind_direction"
|
||||
const val Precipitation = "precipitation"
|
||||
const val RainProbability = "rain_probability"
|
||||
const val Clouds = "clouds"
|
||||
const val Location = "location"
|
||||
const val Provider = "provider"
|
||||
const val ProviderUrl = "provider_url"
|
||||
const val Night = "night"
|
||||
const val Icon = "icon"
|
||||
const val Condition = "condition"
|
||||
object ForecastColumns : Columns() {
|
||||
val Timestamp = column<Long>("timestamp")
|
||||
val CreatedAt = column<Long>("created_at")
|
||||
val Temperature = column<Double>("temperature")
|
||||
val TemperatureMin = column<Double>("temperature_min")
|
||||
val TemperatureMax = column<Double>("temperature_max")
|
||||
val Pressure = column<Double>("pressure")
|
||||
val Humidity = column<Int>("humidity")
|
||||
val WindSpeed = column<Double>("wind_speed")
|
||||
val WindDirection = column<Double>("wind_direction")
|
||||
val Precipitation = column<Double>("precipitation")
|
||||
val RainProbability = column<Int>("rain_probability")
|
||||
val Clouds = column<Int>("clouds")
|
||||
val Location = column<String>("location")
|
||||
val Provider = column<String>("provider")
|
||||
val ProviderUrl = column<String>("provider_url")
|
||||
val Night = column<Boolean>("night")
|
||||
val Icon = column<WeatherIcon>("icon")
|
||||
val Condition = column<String>("condition")
|
||||
}
|
||||
|
||||
object LocationParams {
|
||||
@@ -40,10 +42,10 @@ object WeatherPluginContract {
|
||||
const val Language = "lang"
|
||||
}
|
||||
|
||||
object LocationColumns {
|
||||
const val Id = "id"
|
||||
const val Lat = "lat"
|
||||
const val Lon = "lon"
|
||||
const val Name = "name"
|
||||
object LocationColumns : Columns() {
|
||||
val Id = column<String>("id")
|
||||
val Lat = column<Double>("lat")
|
||||
val Lon = column<Double>("lon")
|
||||
val Name = column<String>("name")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.mm20.launcher2.plugin.data
|
||||
|
||||
import android.os.Bundle
|
||||
import de.mm20.launcher2.plugin.contracts.Column
|
||||
|
||||
operator fun <T> Bundle.get(column: Column<T>): T? {
|
||||
return with(column) {
|
||||
if (!containsKey(column.name)) return null
|
||||
get()
|
||||
}
|
||||
}
|
||||
|
||||
operator fun <T> Bundle.set(column: Column<T>, value: T?) {
|
||||
with(column) {
|
||||
put(value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package de.mm20.launcher2.plugin.data
|
||||
|
||||
import android.database.Cursor
|
||||
import android.database.MatrixCursor
|
||||
import android.util.Log
|
||||
import de.mm20.launcher2.plugin.contracts.Column
|
||||
import de.mm20.launcher2.plugin.contracts.Columns
|
||||
|
||||
interface ColumnsScope {
|
||||
operator fun <T : Any> Cursor.get(column: Column<T>): T?
|
||||
operator fun Cursor.contains(column: Column<*>): Boolean
|
||||
}
|
||||
|
||||
internal class ColumnsScopeImpl(
|
||||
private val columnIndices: Map<String, Int>,
|
||||
private val cursor: Cursor
|
||||
) : ColumnsScope {
|
||||
override operator fun <T : Any> Cursor.get(column: Column<T>): T? {
|
||||
val index = columnIndices[column.name] ?: return null
|
||||
if (index == -1) return null
|
||||
if (cursor.isNull(index)) return null
|
||||
try {
|
||||
return with(column) { cursor.readAtIndex(index) }
|
||||
} catch (e: Exception) {
|
||||
Log.e("MM20", "Failed to get column value", e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun Cursor.contains(column: Column<*>): Boolean {
|
||||
return columnIndices.containsKey(column.name)
|
||||
}
|
||||
}
|
||||
|
||||
fun Cursor.withColumns(columns: Columns, block: ColumnsScope.() -> Unit) {
|
||||
val scope = ColumnsScopeImpl(
|
||||
columns.columns.associateWith { name -> getColumnIndex(name) },
|
||||
this
|
||||
)
|
||||
scope.block()
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun <T> buildCursor(
|
||||
columns: Columns,
|
||||
data: List<T>,
|
||||
rowBuilder: RowBuilderScope.(item: T) -> Unit
|
||||
): Cursor {
|
||||
val cursor = MatrixCursor(columns.columns.toTypedArray(), data.size)
|
||||
val values = Array<Any?>(columns.columns.size) { null }
|
||||
val scope = RowBuilderScopeImpl(
|
||||
columnIndices = columns.columns.withIndex().associate { (index, name) -> name to index },
|
||||
values = values
|
||||
)
|
||||
repeat(data.size) {
|
||||
values.fill(null)
|
||||
scope.rowBuilder(data[it])
|
||||
cursor.addRow(scope.values)
|
||||
}
|
||||
return cursor
|
||||
}
|
||||
|
||||
interface RowBuilderScope {
|
||||
fun <T : Any> put(column: Column<T>, value: T?)
|
||||
}
|
||||
|
||||
internal class RowBuilderScopeImpl(
|
||||
private val columnIndices: Map<String, Int>,
|
||||
internal val values: Array<Any?>
|
||||
) : RowBuilderScope {
|
||||
override fun <T : Any> put(column: Column<T>, value: T?) {
|
||||
value ?: return
|
||||
val index = columnIndices[column.name] ?: return
|
||||
values[index] = with(column) { toCursorValue(value) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package de.mm20.launcher2.search.location
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Address(
|
||||
/**
|
||||
* Address line 1 (e.g. street name and number)
|
||||
*/
|
||||
val address: String? = null,
|
||||
/**
|
||||
* Address line 2 (e.g. apartment number)
|
||||
*/
|
||||
val address2: String? = null,
|
||||
/**
|
||||
* Address line 3 (e.g. additional information)
|
||||
*/
|
||||
val address3: String? = null,
|
||||
/**
|
||||
* City
|
||||
*/
|
||||
val city: String? = null,
|
||||
/**
|
||||
* State, province, or region
|
||||
*/
|
||||
val state: String? = null,
|
||||
/**
|
||||
* Postal code
|
||||
*/
|
||||
val postalCode: String? = null,
|
||||
/**
|
||||
* Country
|
||||
*/
|
||||
val country: String? = null,
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
package de.mm20.launcher2.search.location
|
||||
|
||||
import android.net.Uri
|
||||
import de.mm20.launcher2.serialization.UriSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Attribution(
|
||||
val text: String? = null,
|
||||
@Serializable(with = UriSerializer::class)
|
||||
val iconUrl: Uri? = null,
|
||||
val url: String? = null,
|
||||
)
|
||||
@@ -0,0 +1,47 @@
|
||||
package de.mm20.launcher2.search.location
|
||||
|
||||
import android.graphics.Color
|
||||
import de.mm20.launcher2.serialization.ColorSerializer
|
||||
import de.mm20.launcher2.serialization.DurationSerializer
|
||||
import de.mm20.launcher2.serialization.ZonedDateTimeSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.time.Duration
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
@Serializable
|
||||
data class Departure(
|
||||
/**
|
||||
* The scheduled time of the departure.
|
||||
*/
|
||||
@Serializable(with = ZonedDateTimeSerializer::class)
|
||||
val time: ZonedDateTime,
|
||||
/**
|
||||
* The delay of the departure.
|
||||
* `Duration.ZERO` if the departure is on time,
|
||||
* `null` if no real-time data is available.
|
||||
*/
|
||||
@Serializable(with = DurationSerializer::class)
|
||||
val delay: Duration?,
|
||||
/**
|
||||
* Name of the line (i.e. "11", "U2", "S1").
|
||||
*/
|
||||
val line: String,
|
||||
val lastStop: String?,
|
||||
val type: LineType? = null,
|
||||
@Serializable(with = ColorSerializer::class)
|
||||
val lineColor: Color?,
|
||||
)
|
||||
|
||||
enum class LineType {
|
||||
Bus,
|
||||
Tram,
|
||||
Subway,
|
||||
CommuterTrain,
|
||||
RegionalTrain,
|
||||
Train,
|
||||
HighSpeedTrain,
|
||||
Boat,
|
||||
Monorail,
|
||||
CableCar,
|
||||
Airplane,
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package de.mm20.launcher2.search.location
|
||||
|
||||
enum class LocationIcon {
|
||||
Car,
|
||||
CarRental,
|
||||
CarRepair,
|
||||
CarWash,
|
||||
ChargingStation,
|
||||
GasStation,
|
||||
GenericTransit,
|
||||
Parking,
|
||||
Bus,
|
||||
Tram,
|
||||
Train,
|
||||
Subway,
|
||||
CableCar,
|
||||
Airport,
|
||||
Boat,
|
||||
Taxi,
|
||||
Moped,
|
||||
Bike,
|
||||
Motorcycle,
|
||||
ElectricScooter,
|
||||
|
||||
ArtGallery,
|
||||
Museum,
|
||||
Theater,
|
||||
MovieTheater,
|
||||
AmusementPark,
|
||||
NightClub,
|
||||
ConcertHall,
|
||||
Stadium,
|
||||
Casino,
|
||||
Circus,
|
||||
|
||||
Hotel,
|
||||
|
||||
Restaurant,
|
||||
Cafe,
|
||||
FastFood,
|
||||
Pizza,
|
||||
Burger,
|
||||
Kebab,
|
||||
IceCream,
|
||||
Ramen,
|
||||
Soup,
|
||||
Bar,
|
||||
Brunch,
|
||||
Breakfast,
|
||||
Pub,
|
||||
JapaneseCuisine,
|
||||
AsianCuisine,
|
||||
|
||||
Shopping,
|
||||
Florist,
|
||||
Kiosk,
|
||||
FurnitureStore,
|
||||
CellPhoneStore,
|
||||
BookStore,
|
||||
ClothingStore,
|
||||
ConvenienceStore,
|
||||
DiscountStore,
|
||||
JewelryStore,
|
||||
LiquorStore,
|
||||
PetStore,
|
||||
ShoppingMall,
|
||||
Supermarket,
|
||||
Bakery,
|
||||
Optician,
|
||||
Pharmacy,
|
||||
HairSalon,
|
||||
Laundromat,
|
||||
|
||||
Sports,
|
||||
FitnessCenter,
|
||||
Soccer,
|
||||
Basketball,
|
||||
Tennis,
|
||||
Golf,
|
||||
Baseball,
|
||||
AmericanFootball,
|
||||
Hiking,
|
||||
Swimming,
|
||||
Surfing,
|
||||
Motorsports,
|
||||
Handball,
|
||||
Volleyball,
|
||||
Skiing,
|
||||
Kayaking,
|
||||
Skateboarding,
|
||||
Cricket,
|
||||
MartialArts,
|
||||
NordicWalking,
|
||||
Paragliding,
|
||||
Gymnastics,
|
||||
Snowboarding,
|
||||
Hockey,
|
||||
Rugby,
|
||||
|
||||
Bank,
|
||||
Atm,
|
||||
|
||||
Physician,
|
||||
Dentist,
|
||||
Hospital,
|
||||
Clinic,
|
||||
|
||||
Park,
|
||||
Forest,
|
||||
|
||||
Monument,
|
||||
Church,
|
||||
Mosque,
|
||||
Synagogue,
|
||||
BuddhistTemple,
|
||||
HinduTemple,
|
||||
Candle,
|
||||
|
||||
GovernmentBuilding,
|
||||
Police,
|
||||
FireDepartment,
|
||||
Courthouse,
|
||||
PostOffice,
|
||||
Library,
|
||||
School,
|
||||
University,
|
||||
PublicBathroom;
|
||||
|
||||
companion object {
|
||||
fun valueOfOrNull(string: String): LocationIcon? {
|
||||
return try {
|
||||
valueOf(string)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package de.mm20.launcher2.search.location
|
||||
|
||||
import de.mm20.launcher2.serialization.DurationSerializer
|
||||
import de.mm20.launcher2.serialization.LocalTimeSerializer
|
||||
import de.mm20.launcher2.serialization.OpeningScheduleSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonNames
|
||||
import java.time.DayOfWeek
|
||||
import java.time.Duration
|
||||
import java.time.LocalTime
|
||||
|
||||
@Serializable
|
||||
data class OpeningHours(
|
||||
@JsonNames("day")
|
||||
val dayOfWeek: DayOfWeek,
|
||||
@JsonNames("openingTime")
|
||||
@Serializable(with = LocalTimeSerializer::class)
|
||||
val startTime: LocalTime,
|
||||
@Serializable(with = DurationSerializer::class)
|
||||
val duration: Duration
|
||||
) {
|
||||
override fun toString(): String = "$dayOfWeek $startTime-${startTime.plus(duration)}"
|
||||
}
|
||||
|
||||
@Serializable(with = OpeningScheduleSerializer::class)
|
||||
sealed interface OpeningSchedule {
|
||||
data object TwentyFourSeven : OpeningSchedule
|
||||
@Serializable
|
||||
data class Hours(@Serializable val openingHours: List<OpeningHours>) : OpeningSchedule
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package de.mm20.launcher2.serialization
|
||||
|
||||
import android.graphics.Color
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
object ColorSerializer: KSerializer<Color> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(javaClass.canonicalName!!, PrimitiveKind.INT)
|
||||
|
||||
override fun deserialize(decoder: Decoder): Color {
|
||||
return Color.valueOf(decoder.decodeInt())
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Color) {
|
||||
encoder.encodeInt(value.toArgb())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package de.mm20.launcher2.serialization
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import java.time.DayOfWeek
|
||||
|
||||
object DayOfWeekSerializer : KSerializer<DayOfWeek> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
PrimitiveSerialDescriptor(javaClass.canonicalName!!, PrimitiveKind.INT)
|
||||
|
||||
override fun deserialize(decoder: Decoder): DayOfWeek {
|
||||
return DayOfWeek.of(decoder.decodeInt())
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: DayOfWeek) {
|
||||
encoder.encodeInt(value.value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package de.mm20.launcher2.serialization
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import java.time.Duration
|
||||
|
||||
object DurationSerializer: KSerializer<Duration> {
|
||||
override val descriptor = PrimitiveSerialDescriptor(javaClass.canonicalName!!, PrimitiveKind.LONG)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Duration) {
|
||||
encoder.encodeLong(value.toMillis())
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Duration {
|
||||
return Duration.ofMillis(decoder.decodeLong())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.mm20.launcher2.serialization
|
||||
|
||||
/**
|
||||
* Default Json serializer configurations
|
||||
*/
|
||||
object Json {
|
||||
/**
|
||||
* A Json serializer configuration that aims to be as forgiving as possible.
|
||||
* Suitable for external data sources, and legacy data that may not be fully compliant with the current schema.
|
||||
*/
|
||||
val Lenient = kotlinx.serialization.json.Json {
|
||||
ignoreUnknownKeys = true
|
||||
explicitNulls = false
|
||||
isLenient = true
|
||||
coerceInputValues = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package de.mm20.launcher2.serialization
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import java.time.LocalTime
|
||||
|
||||
object LocalTimeSerializer: KSerializer<LocalTime> {
|
||||
override val descriptor = PrimitiveSerialDescriptor(javaClass.canonicalName!!, PrimitiveKind.LONG)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: LocalTime) {
|
||||
// We use millis here for backwards compatibility in LocationSerializer
|
||||
encoder.encodeLong(value.toNanoOfDay() / 1000000L)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): LocalTime {
|
||||
return LocalTime.ofNanoOfDay(decoder.decodeLong() * 1000000L)
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package de.mm20.launcher2.serialization
|
||||
|
||||
import android.util.Log
|
||||
import de.mm20.launcher2.search.location.OpeningSchedule
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.PolymorphicSerializer
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.decodeFromJsonElement
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
|
||||
object OpeningScheduleSerializer : KSerializer<OpeningSchedule> {
|
||||
override val descriptor = PolymorphicSerializer(OpeningSchedule::class).descriptor
|
||||
|
||||
override fun serialize(encoder: Encoder, value: OpeningSchedule) {
|
||||
when (value) {
|
||||
is OpeningSchedule.TwentyFourSeven -> encoder.encodeString("twentyFourSeven")
|
||||
|
||||
is OpeningSchedule.Hours -> encoder.encodeSerializableValue(
|
||||
OpeningSchedule.Hours.serializer(),
|
||||
value
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): OpeningSchedule {
|
||||
decoder as JsonDecoder
|
||||
val jsonElement = decoder.decodeJsonElement()
|
||||
|
||||
if ((jsonElement as? JsonPrimitive)?.content == "twentyFourSeven") {
|
||||
return OpeningSchedule.TwentyFourSeven
|
||||
}
|
||||
|
||||
if ("openingHours" in jsonElement.jsonObject.keys &&
|
||||
/* backwards compatibility */ !jsonElement.jsonObject["openingHours"]!!.jsonArray.isEmpty()
|
||||
) {
|
||||
return try {
|
||||
decoder.json.decodeFromJsonElement<OpeningSchedule.Hours>(
|
||||
jsonElement
|
||||
)
|
||||
} catch (e: SerializationException) {
|
||||
Log.e("MM20", "Failed to deserialize OpeningSchedule.Hours", e)
|
||||
return OpeningSchedule.Hours(openingHours = emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
// fallback in case we receive data which was serialized before introducing OpeningScheduleSerializer.
|
||||
// here openingHours is empty, in which case it has to be 24/7, else it would indicate corrupted data.
|
||||
return OpeningSchedule.TwentyFourSeven
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package de.mm20.launcher2.serialization
|
||||
|
||||
import android.net.Uri
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
object UriSerializer : KSerializer<Uri> {
|
||||
override val descriptor = PrimitiveSerialDescriptor(javaClass.canonicalName!!, PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Uri) {
|
||||
// We use millis here for backwards compatibility in LocationSerializer
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Uri {
|
||||
return Uri.parse(decoder.decodeString())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package de.mm20.launcher2.serialization
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
object ZonedDateTimeSerializer : KSerializer<ZonedDateTime> {
|
||||
override val descriptor = PrimitiveSerialDescriptor(javaClass.canonicalName!!, PrimitiveKind.LONG)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: ZonedDateTime) {
|
||||
encoder.encodeLong(
|
||||
value.toEpochSecond()
|
||||
)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): ZonedDateTime {
|
||||
return ZonedDateTime.ofInstant(
|
||||
Instant.ofEpochSecond(decoder.decodeLong()),
|
||||
ZoneId.systemDefault()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package de.mm20.launcher2.weather
|
||||
|
||||
enum class WeatherIcon {
|
||||
Unknown,
|
||||
Clear,
|
||||
Cloudy,
|
||||
Cold,
|
||||
Drizzle,
|
||||
Haze,
|
||||
Fog,
|
||||
Hail,
|
||||
HeavyThunderstorm,
|
||||
HeavyThunderstormWithRain,
|
||||
Hot,
|
||||
MostlyCloudy,
|
||||
PartlyCloudy,
|
||||
Showers,
|
||||
Sleet,
|
||||
Snow,
|
||||
Storm,
|
||||
Thunderstorm,
|
||||
ThunderstormWithRain,
|
||||
Wind,
|
||||
BrokenClouds,
|
||||
}
|
||||
Reference in New Issue
Block a user