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:
Christoph
2024-06-14 11:57:03 +02:00
committed by GitHub
co-authored by MM20
parent cfe80ff3e5
commit 65a9c8c1fe
145 changed files with 5804 additions and 2199 deletions
@@ -14,6 +14,9 @@ import de.mm20.launcher2.plugin.PluginApi
import de.mm20.launcher2.plugin.config.WeatherPluginConfig
import de.mm20.launcher2.plugin.contracts.PluginContract
import de.mm20.launcher2.plugin.contracts.WeatherPluginContract
import de.mm20.launcher2.plugin.contracts.WeatherPluginContract.ForecastColumns
import de.mm20.launcher2.plugin.contracts.WeatherPluginContract.LocationColumns
import de.mm20.launcher2.plugin.data.withColumns
import de.mm20.launcher2.preferences.weather.WeatherLocation
import de.mm20.launcher2.weather.Forecast
import de.mm20.launcher2.weather.WeatherProvider
@@ -107,99 +110,30 @@ internal class PluginWeatherProvider(
return cursor.use {
val results = mutableListOf<Forecast>()
val timestampIndex =
cursor.getColumnIndex(WeatherPluginContract.ForecastColumns.Timestamp)
.takeIf { it >= 0 } ?: return null
val createdAtIndex =
cursor.getColumnIndex(WeatherPluginContract.ForecastColumns.CreatedAt)
.takeIf { it >= 0 } ?: return null
val temperatureIndex =
cursor.getColumnIndex(WeatherPluginContract.ForecastColumns.Temperature)
.takeIf { it >= 0 } ?: return null
val conditionIndex =
cursor.getColumnIndex(WeatherPluginContract.ForecastColumns.Condition)
.takeIf { it >= 0 } ?: return null
val iconIndex =
cursor.getColumnIndex(WeatherPluginContract.ForecastColumns.Icon).takeIf { it >= 0 }
?: return null
cursor.withColumns(ForecastColumns) {
val locationIndex =
cursor.getColumnIndex(WeatherPluginContract.ForecastColumns.Location)
.takeIf { it >= 0 }
?: return null
val providerIndex =
cursor.getColumnIndex(WeatherPluginContract.ForecastColumns.Provider)
.takeIf { it >= 0 }
?: return null
val providerUrlIndex =
cursor.getColumnIndex(WeatherPluginContract.ForecastColumns.ProviderUrl)
.takeIf { it >= 0 }
val precipitationIndex =
cursor.getColumnIndex(WeatherPluginContract.ForecastColumns.Precipitation)
.takeIf { it >= 0 }
val precipProbabilityIndex =
cursor.getColumnIndex(WeatherPluginContract.ForecastColumns.RainProbability)
.takeIf { it >= 0 }
val cloudsIndex =
cursor.getColumnIndex(WeatherPluginContract.ForecastColumns.Clouds)
.takeIf { it >= 0 }
val humidityIndex =
cursor.getColumnIndex(WeatherPluginContract.ForecastColumns.Humidity)
.takeIf { it >= 0 }
val windSpeedIndex =
cursor.getColumnIndex(WeatherPluginContract.ForecastColumns.WindSpeed)
.takeIf { it >= 0 }
val windDirectionIndex =
cursor.getColumnIndex(WeatherPluginContract.ForecastColumns.WindDirection)
.takeIf { it >= 0 }
val pressureIndex =
cursor.getColumnIndex(WeatherPluginContract.ForecastColumns.Pressure)
.takeIf { it >= 0 }
val nightIndex =
cursor.getColumnIndex(WeatherPluginContract.ForecastColumns.Night)
.takeIf { it >= 0 }
val minTempIndex =
cursor.getColumnIndex(WeatherPluginContract.ForecastColumns.TemperatureMin)
.takeIf { it >= 0 }
val maxTempIndex =
cursor.getColumnIndex(WeatherPluginContract.ForecastColumns.TemperatureMax)
.takeIf { it >= 0 }
while (cursor.moveToNext()) {
results += Forecast(
timestamp = cursor.getLongOrNull(timestampIndex) ?: continue,
temperature = cursor.getDoubleOrNull(temperatureIndex) ?: continue,
updateTime = cursor.getLongOrNull(createdAtIndex) ?: continue,
condition = cursor.getStringOrNull(conditionIndex) ?: continue,
icon = getIcon(cursor.getStringOrNull(iconIndex) ?: continue),
location = cursor.getStringOrNull(locationIndex) ?: continue,
provider = cursor.getStringOrNull(providerIndex) ?: continue,
providerUrl = providerUrlIndex?.let { cursor.getStringOrNull(it) } ?: "",
clouds = cloudsIndex?.let { cursor.getIntOrNull(it) },
humidity = humidityIndex?.let { cursor.getDoubleOrNull(it) },
precipitation = precipitationIndex?.let { cursor.getDoubleOrNull(it) },
precipProbability = precipProbabilityIndex?.let { cursor.getIntOrNull(it) },
windSpeed = windSpeedIndex?.let { cursor.getDoubleOrNull(it) },
windDirection = windDirectionIndex?.let { cursor.getDoubleOrNull(it) },
pressure = pressureIndex?.let { cursor.getDoubleOrNull(it) },
night = nightIndex?.let { cursor.getIntOrNull(it) } == 1,
minTemp = minTempIndex?.let { cursor.getDoubleOrNull(it) },
maxTemp = maxTempIndex?.let { cursor.getDoubleOrNull(it) },
)
while (cursor.moveToNext()) {
results += Forecast(
timestamp = cursor[ForecastColumns.Timestamp] ?: continue,
temperature = cursor[ForecastColumns.Temperature] ?: continue,
updateTime = cursor[ForecastColumns.CreatedAt] ?: continue,
condition = cursor[ForecastColumns.Condition] ?: continue,
icon = getIcon(cursor[ForecastColumns.Icon]?.name ?: continue),
location = cursor[ForecastColumns.Location] ?: continue,
provider = cursor[ForecastColumns.Provider] ?: continue,
providerUrl = cursor[ForecastColumns.ProviderUrl] ?: "",
clouds = cursor[ForecastColumns.Clouds],
humidity = cursor[ForecastColumns.Humidity]?.toDouble(),
precipitation = cursor[ForecastColumns.Precipitation],
precipProbability = cursor[ForecastColumns.RainProbability],
windSpeed = cursor[ForecastColumns.WindSpeed],
windDirection = cursor[ForecastColumns.WindDirection],
pressure = cursor[ForecastColumns.Pressure],
night = cursor[ForecastColumns.Night] ?: false,
minTemp = cursor[ForecastColumns.TemperatureMin],
maxTemp = cursor[ForecastColumns.TemperatureMax],
)
}
}
results
}
@@ -278,29 +212,18 @@ internal class PluginWeatherProvider(
return cursor.use {
val results = mutableListOf<WeatherLocation>()
val nameIndex =
cursor.getColumnIndex(WeatherPluginContract.LocationColumns.Name)
.takeIf { it >= 0 } ?: return emptyList()
val latIndex =
cursor.getColumnIndex(WeatherPluginContract.LocationColumns.Lat)
.takeIf { it >= 0 }
val lonIndex =
cursor.getColumnIndex(WeatherPluginContract.LocationColumns.Lon)
.takeIf { it >= 0 }
val locationIdIndex =
cursor.getColumnIndex(WeatherPluginContract.LocationColumns.Id)
.takeIf { it >= 0 }
cursor.withColumns(LocationColumns) {
while (cursor.moveToNext()) {
val lat = cursor[LocationColumns.Lat]
val lon = cursor[LocationColumns.Lon]
val locationId = cursor[LocationColumns.Id]
val name = cursor[LocationColumns.Name] ?: continue
while (cursor.moveToNext()) {
val lat = latIndex?.let { cursor.getDoubleOrNull(it) }
val lon = lonIndex?.let { cursor.getDoubleOrNull(it) }
val locationId = locationIdIndex?.let { cursor.getStringOrNull(it) }
val name = cursor.getStringOrNull(nameIndex) ?: continue
if (lat != null && lon != null) {
results += WeatherLocation.LatLon(lat = lat, lon = lon, name = name)
} else if (locationId != null) {
results += WeatherLocation.Id(locationId = locationId, name = name)
if (lat != null && lon != null) {
results += WeatherLocation.LatLon(lat = lat, lon = lon, name = name)
} else if (locationId != null) {
results += WeatherLocation.Id(locationId = locationId, name = name)
}
}
}
results