Compare commits

..

No commits in common. "522364dd848ba2770157b154da559c087fbcc3d6" and "c4a23ce4565336e0a55a43766b985207d5b82e50" have entirely different histories.

65 changed files with 348 additions and 1092 deletions

View File

@ -25,6 +25,7 @@ jobs:
echo "KEY_PASSWORD=${{ secrets.KEY_PASSWORD }}" >> $GITHUB_ENV
echo "KEYSTORE_PASSWORD=${{ secrets.KEYSTORE_PASSWORD }}" >> $GITHUB_ENV
echo "KEYSTORE_BASE64=${{ secrets.KEYSTORE_BASE64 }}" >> $GITHUB_ENV
echo "GOOGLE_SERVICES_JSON_BASE64=${{ secrets.GOOGLE_SERVICES_JSON_BASE64 }}" >> $GITHUB_ENV
echo "BUILD_NUMBER=${{ github.run_number }}" >> $GITHUB_ENV
- uses: actions/checkout@v4
- name: set up JDK 17

View File

@ -25,7 +25,7 @@ After installing this app on your Karoo and opening it once from the main menu,
- Tailwind with riding speed (graphical, 1x1 field): Shows an arrow indicating the current headwind direction next to a label reading your current speed and the speed of the tailwind. If you ride against a headwind of 5 mph, it will show "-5". If you ride in the same direction of a 5 mph wind, it will read "+5". Text and arrow are colored based on the tailwind speed, with red indicating a strong headwind and green indicating a strong tailwind.
- Tailwind (graphical, 1x1 field): Similar to the tailwind and riding speed field, but shows tailwind speed, wind speed and wind gust speed instead of riding speed.
- Weather forecast (graphical, 2x1 field): Shows three columns indicating the current weather conditions (sunny, cloudy, ...), wind direction, precipitation and temperature forecasted for the next three hours. Tap on this widget to cycle through the 12 hour forecast. If you have a route loaded, the forecast widget will show the forecasted weather along points of the route, with an estimated traveled distance per hour of 20 km / 12 miles by default.
- Current weather (graphical, 1x1 field): Shows current weather conditions (same as forecast widget, but only for the current time).
- Current weather (graphical, 1x1 field): Shows current weather conditions (same as forecast widget, but only for the current time). Tap on this widget to open the headwind app with a forecast overview.
- Relative grade (numerical): Shows the relative grade. The relative grade is calculated by estimating the force of the headwind, and then calculating the gradient you would need to ride at to experience this resistance if there was no wind. Example: If you are riding on an actual gradient of 2 %, face a headwind of 18 km/h while riding at 29 km/h, the relative grade will be shown as 5.2 % (with 3.2 % added to the actual grade due to the headwind).
- Relative elevation gain (numerical): Shows the relative elegation gain. The relative elevation gain is calculated using the relative grade and is an estimation of how much climbing would have been equivalent to the headwind you faced during the ride.
- Additionally, data fields that only show the current data value for headwind speed, humidity, cloud cover, absolute wind speed, absolute wind gust speed, absolute wind direction, rainfall and surface pressure can be added if desired.
@ -41,7 +41,7 @@ If the app cannot connect to the weather service, it will retry the download eve
## Credits
- Icons are from [boxicons.com](https://boxicons.com) ([MIT-licensed](icon_credits.txt)) and the [Google Noto Color Emoji font](https://fonts.google.com/noto/specimen/Noto+Color+Emoji) (SIL Open Font License 1.1)
- Icons are from [boxicons.com](https://boxicons.com) ([MIT-licensed](icon_credits.txt))
- Made possible by the generous usage terms of [open-meteo.com](https://open-meteo.com)
- Interfaces with [openweathermap.org](https://openweathermap.org)
- Uses [karoo-ext](https://github.com/hammerheadnav/karoo-ext) (Apache2-licensed)

1
app/.gitignore vendored
View File

@ -1 +1,2 @@
/build
/google-services.json

View File

@ -5,6 +5,8 @@ plugins {
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.compose.compiler)
kotlin("plugin.serialization") version "2.0.20"
alias(libs.plugins.google.gms.google.services)
alias(libs.plugins.google.firebase.crashlytics)
}
android {
@ -35,6 +37,9 @@ android {
buildTypes {
debug {
isMinifyEnabled = false
firebaseCrashlytics {
mappingFileUploadEnabled = false
}
}
release {
signingConfig = signingConfigs.getByName("release")
@ -55,6 +60,24 @@ android {
}
}
tasks.register("addGoogleServicesJson") {
description = "Adds google-services.json to the project"
group = "build"
doLast {
val googleServicesJson = System.getenv("GOOGLE_SERVICES_JSON_BASE64")
?.let { Base64.getDecoder().decode(it) }
?.let { String(it) }
if (googleServicesJson != null) {
val jsonFile = file("$projectDir/google-services.json")
jsonFile.writeText(googleServicesJson)
println("Added google-services.json to the project")
} else {
println("No GOOGLE_SERVICES_JSON_BASE64 environment variable found, skipping...")
}
}
}
tasks.register("generateManifest") {
description = "Generates manifest.json with current version information"
group = "build"
@ -70,8 +93,11 @@ tasks.register("generateManifest") {
"latestVersionCode" to android.defaultConfig.versionCode,
"developer" to "github.com/timklge",
"description" to "Open-source extension that provides headwind direction, wind speed, forecast and other weather data fields.",
"releaseNotes" to "* Remove crashlytics\n" +
"* Reduce refresh rate on K2, add refresh rate setting\n" +
"releaseNotes" to "* Reduce refresh rate on K2, add refresh rate setting\n" +
"* Fix weather data download from Open-Meteo via iOS companion app (thx @keefar!)\n" +
"* Remove custom wind speed unit setting and always use imperial / metric as set in profile\n" +
"* Add relative grade, relative elevation gain data fields\n" +
"* Add OpenWeatherMap support contributed by lockevod\n",
"screenshotUrls" to listOf(
"https://github.com/timklge/karoo-headwind/releases/latest/download/preview1.png",
"https://github.com/timklge/karoo-headwind/releases/latest/download/preview3.png",
@ -88,6 +114,7 @@ tasks.register("generateManifest") {
tasks.named("assemble") {
dependsOn("generateManifest")
dependsOn("addGoogleServicesJson")
}
dependencies {
@ -102,5 +129,6 @@ dependencies {
implementation(libs.androidx.glance.appwidget)
implementation(libs.androidx.glance.appwidget.preview)
implementation(libs.androidx.glance.preview)
implementation(libs.firebase.crashlytics)
testImplementation(kotlin("test"))
}

View File

@ -210,22 +210,6 @@ fun Context.streamCurrentForecastWeatherData(): Flow<WeatherDataResponse?> {
}.distinctUntilChanged()
}
fun lerp(
start: Double,
end: Double,
factor: Double
): Double {
return start + (end - start) * factor
}
fun lerp(
start: Int,
end: Int,
factor: Double
): Int {
return (start + (end - start) * factor).toInt()
}
fun lerpNullable(
start: Double?,
end: Double?,
@ -282,18 +266,17 @@ fun lerpWeather(
return WeatherData(
time = (start.time + (end.time - start.time) * factor).toLong(),
temperature = start.temperature + (end.temperature - start.temperature) * factor,
relativeHumidity = lerp(start.relativeHumidity, end.relativeHumidity, factor),
relativeHumidity = lerpNullable(start.relativeHumidity, end.relativeHumidity, factor),
precipitation = start.precipitation + (end.precipitation - start.precipitation) * factor,
precipitationProbability = lerpNullable(start.precipitationProbability, end.precipitationProbability, factor),
cloudCover = lerp(start.cloudCover, end.cloudCover, factor),
surfacePressure = lerp(start.surfacePressure, end.surfacePressure, factor),
sealevelPressure = lerp(start.sealevelPressure, end.sealevelPressure, factor),
cloudCover = lerpNullable(start.cloudCover, end.cloudCover, factor),
surfacePressure = lerpNullable(start.surfacePressure, end.surfacePressure, factor),
sealevelPressure = lerpNullable(start.sealevelPressure, end.sealevelPressure, factor),
windSpeed = start.windSpeed + (end.windSpeed - start.windSpeed) * factor,
windDirection = lerpAngle(start.windDirection, end.windDirection, factor),
windGusts = start.windGusts + (end.windGusts - start.windGusts) * factor,
weatherCode = closestWeatherData.weatherCode,
isForecast = closestWeatherData.isForecast,
isNight = closestWeatherData.isNight,
isForecast = closestWeatherData.isForecast
)
}

View File

@ -1,7 +1,6 @@
package de.timklge.karooheadwind
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.models.ActiveRidePage
import io.hammerhead.karooext.models.OnLocationChanged
import io.hammerhead.karooext.models.OnNavigationState
import io.hammerhead.karooext.models.OnStreamState
@ -13,7 +12,6 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.flow.transform
@ -67,22 +65,3 @@ fun<T> Flow<T>.throttle(timeout: Long): Flow<T> = this
emit(it)
delay(timeout)
}
fun KarooSystemService.streamActiveRidePage(): Flow<ActiveRidePage> {
return callbackFlow {
val listenerId = addConsumer { activeRidePage: ActiveRidePage ->
trySendBlocking(activeRidePage)
}
awaitClose {
removeConsumer(listenerId)
}
}
}
fun KarooSystemService.streamDatatypeIsVisible(
datatype: String,
): Flow<Boolean> {
return streamActiveRidePage().map { page ->
page.page.elements.any { it.dataTypeId == datatype }
}
}

View File

@ -50,6 +50,7 @@ data class HeadwindWidgetSettings(
}
}
//Moded with openweahtermap.org
@Serializable
data class HeadwindStats(
val lastSuccessfulWeatherRequest: Long? = null,
@ -69,8 +70,8 @@ enum class RefreshRate(val id: String, val k2Ms: Long, val k3Ms: Long) {
SLOW("slow", 5_000L, 3_000L),
MINIMUM("minimum", 10_000L, 10_000L);
fun getDescription(isOnK2: Boolean): String {
return if (isOnK2) {
fun getDescription(karooSystemService: KarooSystemService): String {
return if (karooSystemService.hardwareType == HardwareType.K2) {
when (this) {
FAST -> "Fast (1s)"
STANDARD -> "Standard (2s)"

View File

@ -3,24 +3,16 @@ package de.timklge.karooheadwind.datatypes
import android.content.Context
import android.util.Log
import de.timklge.karooheadwind.KarooHeadwindExtension
import de.timklge.karooheadwind.streamCurrentWeatherData
import de.timklge.karooheadwind.streamUserProfile
import de.timklge.karooheadwind.throttle
import de.timklge.karooheadwind.weatherprovider.WeatherData
import de.timklge.karooheadwind.streamCurrentWeatherData
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.extension.DataTypeImpl
import io.hammerhead.karooext.internal.Emitter
import io.hammerhead.karooext.internal.ViewEmitter
import io.hammerhead.karooext.models.DataPoint
import io.hammerhead.karooext.models.DataType
import io.hammerhead.karooext.models.StreamState
import io.hammerhead.karooext.models.UpdateGraphicConfig
import io.hammerhead.karooext.models.UserProfile
import io.hammerhead.karooext.models.ViewConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
@ -29,25 +21,17 @@ abstract class BaseDataType(
private val applicationContext: Context,
dataTypeId: String
) : DataTypeImpl("karoo-headwind", dataTypeId) {
abstract fun getValue(data: WeatherData, userProfile: UserProfile): Double?
open fun getFormatDataType(): String? = null
abstract fun getValue(data: WeatherData): Double?
override fun startStream(emitter: Emitter<StreamState>) {
Log.d(KarooHeadwindExtension.TAG, "start $dataTypeId stream")
val job = CoroutineScope(Dispatchers.IO).launch {
data class StreamData(val weatherData: WeatherData, val userProfile: UserProfile)
val currentWeatherData = applicationContext.streamCurrentWeatherData(karooSystemService)
val currentWeatherData = combine(applicationContext.streamCurrentWeatherData(karooSystemService).filterNotNull(), karooSystemService.streamUserProfile()) { weatherData, userProfile ->
StreamData(weatherData, userProfile)
}
val refreshRate = karooSystemService.getRefreshRateInMilliseconds(applicationContext)
currentWeatherData.filterNotNull()
.throttle(refreshRate)
.collect { (data, userProfile) ->
val value = getValue(data, userProfile)
currentWeatherData
.filterNotNull()
.collect { data ->
val value = getValue(data)
Log.d(KarooHeadwindExtension.TAG, "$dataTypeId: $value")
if (value != null) {
@ -62,12 +46,4 @@ abstract class BaseDataType(
job.cancel()
}
}
override fun startView(context: Context, config: ViewConfig, emitter: ViewEmitter) {
Log.d(KarooHeadwindExtension.TAG, "Starting $dataTypeId view with $emitter")
if (getFormatDataType() != null){
emitter.onNext(UpdateGraphicConfig(formatDataTypeId = getFormatDataType()))
}
}
}

View File

@ -3,10 +3,9 @@ package de.timklge.karooheadwind.datatypes
import android.content.Context
import de.timklge.karooheadwind.weatherprovider.WeatherData
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.models.UserProfile
class CloudCoverDataType(karooSystemService: KarooSystemService, context: Context) : BaseDataType(karooSystemService, context, "cloudCover"){
override fun getValue(data: WeatherData, userProfile: UserProfile): Double? {
override fun getValue(data: WeatherData): Double? {
return data.cloudCover
}
}

View File

@ -28,22 +28,18 @@ import de.timklge.karooheadwind.KarooHeadwindExtension
import de.timklge.karooheadwind.R
import de.timklge.karooheadwind.TemperatureUnit
import de.timklge.karooheadwind.UpcomingRoute
import de.timklge.karooheadwind.weatherprovider.WeatherData
import de.timklge.karooheadwind.weatherprovider.WeatherDataForLocation
import de.timklge.karooheadwind.WeatherDataProvider
import de.timklge.karooheadwind.weatherprovider.WeatherDataResponse
import de.timklge.karooheadwind.weatherprovider.WeatherInterpretation
import de.timklge.karooheadwind.getHeadingFlow
import de.timklge.karooheadwind.streamCurrentForecastWeatherData
import de.timklge.karooheadwind.streamDatatypeIsVisible
import de.timklge.karooheadwind.streamSettings
import de.timklge.karooheadwind.streamUpcomingRoute
import de.timklge.karooheadwind.streamUserProfile
import de.timklge.karooheadwind.streamWidgetSettings
import de.timklge.karooheadwind.throttle
import de.timklge.karooheadwind.util.celciusInUserUnit
import de.timklge.karooheadwind.util.millimetersInUserUnit
import de.timklge.karooheadwind.util.msInUserUnit
import de.timklge.karooheadwind.weatherprovider.WeatherData
import de.timklge.karooheadwind.weatherprovider.WeatherDataForLocation
import de.timklge.karooheadwind.weatherprovider.WeatherDataResponse
import de.timklge.karooheadwind.weatherprovider.WeatherInterpretation
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.extension.DataTypeImpl
import io.hammerhead.karooext.internal.ViewEmitter
@ -58,7 +54,6 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
@ -83,8 +78,7 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
timeLabel: String,
dateLabel: String?,
distance: Double?,
isImperial: Boolean,
isNight: Boolean)
isImperial: Boolean)
@OptIn(ExperimentalGlanceRemoteViewsApi::class)
private val glance = GlanceRemoteViews()
@ -95,7 +89,7 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
data class StreamData(val data: WeatherDataResponse?, val settings: SettingsAndProfile,
val widgetSettings: HeadwindWidgetSettings? = null,
val headingResponse: HeadingResponse? = null, val upcomingRoute: UpcomingRoute? = null, val isVisible: Boolean)
val headingResponse: HeadingResponse? = null, val upcomingRoute: UpcomingRoute? = null)
data class SettingsAndProfile(val settings: HeadwindSettings, val isImperial: Boolean, val isImperialTemperature: Boolean)
@ -119,7 +113,7 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
WeatherData(
time = forecastTime,
temperature = forecastTemperature,
relativeHumidity = 20,
relativeHumidity = 20.0,
precipitation = forecastPrecipitation,
cloudCover = 3.0,
sealevelPressure = 1013.25,
@ -129,8 +123,7 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
windDirection = forecastWindDirection,
windGusts = forecastWindGusts,
weatherCode = forecastWeatherCode,
isForecast = true,
isNight = it < 2
isForecast = true
)
}
@ -142,7 +135,7 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
current = WeatherData(
time = timeAtFullHour,
temperature = 20.0,
relativeHumidity = 20,
relativeHumidity = 20.0,
precipitation = 0.0,
cloudCover = 3.0,
sealevelPressure = 1013.25,
@ -151,8 +144,7 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
windDirection = 180.0,
windGusts = 10.0,
weatherCode = WeatherInterpretation.getKnownWeatherCodes().random(),
isForecast = false,
isNight = false
isForecast = false
),
coords = GpsCoordinates(0.0, 0.0, distanceAlongRoute = index * distancePerHour),
timezone = "UTC",
@ -169,8 +161,7 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
HeadwindSettings(),
settingsAndProfile?.isImperial == true,
settingsAndProfile?.isImperialTemperature == true
),
isVisible = true
)
)
)
@ -212,23 +203,14 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
if (oldDistance == null || newDistance == null) return@distinctUntilChanged false
abs(oldDistance - newDistance) < 1_000
},
karooSystem.streamDatatypeIsVisible(dataTypeId)
) { data ->
val weatherData = data[0] as WeatherDataResponse?
val settings = data[1] as SettingsAndProfile
val widgetSettings = data[2] as HeadwindWidgetSettings?
val heading = data[3] as HeadingResponse?
val upcomingRoute = data[4] as UpcomingRoute?
val isVisible = data[5] as Boolean
}
) { weatherData, settings, widgetSettings, heading, upcomingRoute ->
StreamData(
data = weatherData,
settings = settings,
widgetSettings = widgetSettings,
headingResponse = heading,
upcomingRoute = upcomingRoute,
isVisible = isVisible
upcomingRoute = upcomingRoute
)
}
}
@ -236,7 +218,7 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
val viewJob = CoroutineScope(Dispatchers.IO).launch {
emitter.onNext(ShowCustomStreamState("", null))
dataFlow.filter { it.isVisible }.collect { (allData, settingsAndProfile, widgetSettings, headingResponse, upcomingRoute) ->
dataFlow.collect { (allData, settingsAndProfile, widgetSettings, headingResponse, upcomingRoute) ->
Log.d(KarooHeadwindExtension.TAG, "Updating weather forecast view")
if (allData?.data.isNullOrEmpty()){
@ -314,17 +296,16 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
arrowBitmap = baseBitmap,
current = interpretation,
windBearing = data.current.windDirection.roundToInt(),
windSpeed = msInUserUnit(data.current.windSpeed, settingsAndProfile.isImperial).roundToInt(),
windGusts = msInUserUnit(data.current.windGusts, settingsAndProfile.isImperial).roundToInt(),
precipitation = millimetersInUserUnit(data.current.precipitation, settingsAndProfile.isImperial),
windSpeed = data.current.windSpeed.roundToInt(),
windGusts = data.current.windGusts.roundToInt(),
precipitation = data.current.precipitation,
precipitationProbability = null,
temperature = celciusInUserUnit(data.current.temperature, settingsAndProfile.isImperialTemperature).roundToInt(),
temperature = data.current.temperature.roundToInt(),
temperatureUnit = if (settingsAndProfile.isImperialTemperature) TemperatureUnit.FAHRENHEIT else TemperatureUnit.CELSIUS,
timeLabel = formattedTime,
dateLabel = if (hasNewDate) formattedDate else null,
distance = null,
isImperial = settingsAndProfile.isImperial,
isNight = data.current.isNight
isImperial = settingsAndProfile.isImperial
)
previousDate = formattedDate
@ -340,17 +321,16 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
arrowBitmap = baseBitmap,
current = interpretation,
windBearing = weatherData?.windDirection?.roundToInt() ?: 0,
windSpeed = msInUserUnit(weatherData?.windSpeed ?: 0.0, settingsAndProfile.isImperial).roundToInt(),
windGusts = msInUserUnit(weatherData?.windGusts ?: 0.0, settingsAndProfile.isImperial).roundToInt(),
precipitation = millimetersInUserUnit(weatherData?.precipitation ?: 0.0, settingsAndProfile.isImperial),
windSpeed = weatherData?.windSpeed?.roundToInt() ?: 0,
windGusts = weatherData?.windGusts?.roundToInt() ?: 0,
precipitation = weatherData?.precipitation ?: 0.0,
precipitationProbability = weatherData?.precipitationProbability?.toInt(),
temperature = celciusInUserUnit(weatherData?.temperature ?: 0.0, settingsAndProfile.isImperialTemperature).roundToInt(),
temperature = weatherData?.temperature?.roundToInt() ?: 0,
temperatureUnit = if (settingsAndProfile.isImperialTemperature) TemperatureUnit.FAHRENHEIT else TemperatureUnit.CELSIUS,
timeLabel = formattedTime,
dateLabel = if (hasNewDate) formattedDate else null,
distance = if (settingsAndProfile.settings.showDistanceInForecast) distanceFromCurrent else null,
isImperial = settingsAndProfile.isImperial,
isNight = weatherData?.isNight == true
isImperial = settingsAndProfile.isImperial
)
previousDate = formattedDate

View File

@ -34,16 +34,16 @@ fun GraphicalForecast(
distance: Double? = null,
timeLabel: String? = null,
rowAlignment: Alignment.Horizontal = Alignment.Horizontal.CenterHorizontally,
isImperial: Boolean?,
isNight: Boolean,
isImperial: Boolean?
) {
Column(modifier = GlanceModifier.fillMaxHeight().padding(1.dp).width(86.dp), horizontalAlignment = rowAlignment) {
Row(modifier = GlanceModifier.defaultWeight().wrapContentWidth(), horizontalAlignment = rowAlignment, verticalAlignment = Alignment.CenterVertically) {
Image(
modifier = GlanceModifier.defaultWeight().wrapContentWidth().padding(1.dp),
provider = ImageProvider(getWeatherIcon(current, isNight)),
provider = ImageProvider(getWeatherIcon(current)),
contentDescription = "Current weather information",
contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White))
)
}
@ -96,15 +96,13 @@ class GraphicalForecastDataType(karooSystem: KarooSystemService) : ForecastDataT
timeLabel: String,
dateLabel: String?,
distance: Double?,
isImperial: Boolean,
isNight: Boolean,
isImperial: Boolean
) {
GraphicalForecast(
current = current,
distance = distance,
timeLabel = timeLabel,
isImperial = isImperial,
isNight = isNight
isImperial = isImperial
)
}
}

View File

@ -12,11 +12,8 @@ import de.timklge.karooheadwind.KarooHeadwindExtension
import de.timklge.karooheadwind.WindDirectionIndicatorSetting
import de.timklge.karooheadwind.getRelativeHeadingFlow
import de.timklge.karooheadwind.streamCurrentWeatherData
import de.timklge.karooheadwind.streamDatatypeIsVisible
import de.timklge.karooheadwind.streamSettings
import de.timklge.karooheadwind.streamUserProfile
import de.timklge.karooheadwind.throttle
import de.timklge.karooheadwind.util.msInUserUnit
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.extension.DataTypeImpl
import io.hammerhead.karooext.internal.Emitter
@ -26,7 +23,6 @@ import io.hammerhead.karooext.models.DataType
import io.hammerhead.karooext.models.HardwareType
import io.hammerhead.karooext.models.StreamState
import io.hammerhead.karooext.models.UpdateGraphicConfig
import io.hammerhead.karooext.models.UserProfile
import io.hammerhead.karooext.models.ViewConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -35,7 +31,6 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
@ -48,25 +43,17 @@ class HeadwindDirectionDataType(
) : DataTypeImpl("karoo-headwind", "headwind") {
private val glance = GlanceRemoteViews()
data class StreamData(val headingResponse: HeadingResponse, val absoluteWindDirection: Double?, val windSpeed: Double?, val settings: HeadwindSettings)
private fun streamValues(): Flow<Double> = flow {
combine(
karooSystem.getRelativeHeadingFlow(applicationContext),
applicationContext.streamCurrentWeatherData(karooSystem),
applicationContext.streamSettings(karooSystem),
) { headingResponse, currentWeather, settings ->
StreamData(
headingResponse,
currentWeather?.windDirection,
currentWeather?.windSpeed,
settings,
)
}.collect { streamData ->
karooSystem.getRelativeHeadingFlow(applicationContext)
.combine(applicationContext.streamCurrentWeatherData(karooSystem)) { headingResponse, data ->
StreamData(headingResponse, data?.windDirection, data?.windSpeed)
}
.combine(applicationContext.streamSettings(karooSystem)) { data, settings -> data.copy(settings = settings) }
.collect { streamData ->
val value = (streamData.headingResponse as? HeadingResponse.Value)?.diff
var returnValue = 0.0
if (value == null || streamData.absoluteWindDirection == null || streamData.windSpeed == null){
if (value == null || streamData.absoluteWindDirection == null || streamData.settings == null || streamData.windSpeed == null){
var errorCode = 1.0
var headingResponse = streamData.headingResponse
@ -74,7 +61,7 @@ class HeadwindDirectionDataType(
headingResponse = HeadingResponse.NoWeatherData
}
if (streamData.settings.welcomeDialogAccepted == false){
if (streamData.settings?.welcomeDialogAccepted == false){
errorCode = ERROR_APP_NOT_SET_UP.toDouble()
} else if (headingResponse is HeadingResponse.NoGps){
errorCode = ERROR_NO_GPS.toDouble()
@ -109,12 +96,9 @@ class HeadwindDirectionDataType(
}
}
data class DirectionAndSpeed(
val bearing: Double,
val speed: Double?,
val isVisible: Boolean,
val isImperial: Boolean
)
data class StreamData(val headingResponse: HeadingResponse?, val absoluteWindDirection: Double?, val windSpeed: Double?, val settings: HeadwindSettings? = null)
data class DirectionAndSpeed(val bearing: Double, val speed: Double?)
private fun previewFlow(): Flow<DirectionAndSpeed> {
return flow {
@ -122,12 +106,7 @@ class HeadwindDirectionDataType(
val bearing = (0..360).random().toDouble()
val windSpeed = (0..20).random()
emit(DirectionAndSpeed(
bearing,
windSpeed.toDouble(),
true,
true
))
emit(DirectionAndSpeed(bearing, windSpeed.toDouble()))
delay(2_000)
}
@ -157,15 +136,15 @@ class HeadwindDirectionDataType(
emitAll(UserWindSpeedDataType.streamValues(context, karooSystem))
}
combine(directionFlow, speedFlow, karooSystem.streamDatatypeIsVisible(dataTypeId), karooSystem.streamUserProfile()) { direction, speed, isVisible, profile ->
DirectionAndSpeed(direction, speed, isVisible, profile.preferredUnit.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL)
combine(directionFlow, speedFlow) { direction, speed ->
DirectionAndSpeed(direction, speed)
}
}
val viewJob = CoroutineScope(Dispatchers.IO).launch {
val refreshRate = karooSystem.getRefreshRateInMilliseconds(context)
flow.filter { it.isVisible }.throttle(refreshRate).collect { streamData ->
flow.throttle(refreshRate).collect { streamData ->
Log.d(KarooHeadwindExtension.TAG, "Updating headwind direction view")
val errorCode = streamData.bearing.let { if(it < 0) it.toInt() else null }
@ -175,15 +154,14 @@ class HeadwindDirectionDataType(
}
val windDirection = streamData.bearing
val windSpeed = streamData.speed ?: 0.0
val windSpeedUserUnit = msInUserUnit(windSpeed, streamData.isImperial)
val windSpeed = streamData.speed
val result = glance.compose(context, DpSize.Unspecified) {
HeadwindDirection(
baseBitmap,
windDirection.roundToInt(),
config.textSize,
windSpeedUserUnit.roundToInt().toString(),
windSpeed?.toInt()?.toString() ?: "",
preview = config.preview,
wideMode = false
)

View File

@ -67,7 +67,7 @@ fun HeadwindDirection(
val baseModifier = GlanceModifier.fillMaxSize().padding(5.dp).background(dayColor, nightColor).cornerRadius(10.dp)
Box(
modifier = baseModifier, // TODO if (!preview) baseModifier.clickable(actionStartActivity<MainActivity>()) else baseModifier,
modifier = if (!preview) baseModifier.clickable(actionStartActivity<MainActivity>()) else baseModifier,
contentAlignment = Alignment(
vertical = Alignment.Vertical.CenterVertically,
horizontal = Alignment.Horizontal.CenterHorizontally,

View File

@ -7,16 +7,12 @@ import de.timklge.karooheadwind.weatherprovider.WeatherData
import de.timklge.karooheadwind.getRelativeHeadingFlow
import de.timklge.karooheadwind.streamCurrentWeatherData
import de.timklge.karooheadwind.streamSettings
import de.timklge.karooheadwind.throttle
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.extension.DataTypeImpl
import io.hammerhead.karooext.internal.Emitter
import io.hammerhead.karooext.internal.ViewEmitter
import io.hammerhead.karooext.models.DataPoint
import io.hammerhead.karooext.models.DataType
import io.hammerhead.karooext.models.StreamState
import io.hammerhead.karooext.models.UpdateGraphicConfig
import io.hammerhead.karooext.models.ViewConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
@ -31,14 +27,11 @@ class HeadwindSpeedDataType(
override fun startStream(emitter: Emitter<StreamState>) {
val job = CoroutineScope(Dispatchers.IO).launch {
val refreshRate = karooSystem.getRefreshRateInMilliseconds(context)
karooSystem.getRelativeHeadingFlow(context)
.combine(context.streamCurrentWeatherData(karooSystem)) { value, data -> value to data }
.combine(context.streamSettings(karooSystem)) { (value, data), settings ->
StreamData(value, data, settings)
}
.throttle(refreshRate)
.collect { streamData ->
val windSpeed = streamData.weatherData?.windSpeed ?: 0.0
val windDirection = (streamData.headingResponse as? HeadingResponse.Value)?.diff ?: 0.0
@ -52,9 +45,5 @@ class HeadwindSpeedDataType(
job.cancel()
}
}
override fun startView(context: Context, config: ViewConfig, emitter: ViewEmitter) {
emitter.onNext(UpdateGraphicConfig(formatDataTypeId = DataType.Type.SPEED))
}
}

View File

@ -1,13 +1,11 @@
package de.timklge.karooheadwind.datatypes
import android.content.Context
import de.timklge.karooheadwind.util.millimetersInUserUnit
import de.timklge.karooheadwind.weatherprovider.WeatherData
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.models.UserProfile
class PrecipitationDataType(karooSystemService: KarooSystemService, context: Context) : BaseDataType(karooSystemService, context, "precipitation"){
override fun getValue(data: WeatherData, userProfile: UserProfile): Double {
return millimetersInUserUnit(data.precipitation, userProfile.preferredUnit.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL)
override fun getValue(data: WeatherData): Double {
return data.precipitation
}
}

View File

@ -25,6 +25,7 @@ import de.timklge.karooheadwind.weatherprovider.WeatherInterpretation
import io.hammerhead.karooext.KarooSystemService
import kotlin.math.absoluteValue
import kotlin.math.ceil
import kotlin.math.roundToInt
@Composable
fun PrecipitationForecast(
@ -33,7 +34,7 @@ fun PrecipitationForecast(
distance: Double? = null,
timeLabel: String? = null,
rowAlignment: Alignment.Horizontal = Alignment.Horizontal.CenterHorizontally,
isImperial: Boolean?,
isImperial: Boolean?
) {
Column(modifier = GlanceModifier.fillMaxHeight().padding(1.dp).width(86.dp), horizontalAlignment = rowAlignment) {
Row(modifier = GlanceModifier.defaultWeight().fillMaxWidth(), horizontalAlignment = rowAlignment, verticalAlignment = Alignment.CenterVertically) {
@ -94,15 +95,14 @@ class PrecipitationForecastDataType(karooSystem: KarooSystemService) : ForecastD
timeLabel: String,
dateLabel: String?,
distance: Double?,
isImperial: Boolean,
isNight: Boolean,
isImperial: Boolean
) {
PrecipitationForecast(
precipitation = ceil(precipitation).toInt(),
precipitationProbability = precipitationProbability,
distance = distance,
timeLabel = timeLabel,
isImperial = isImperial,
isImperial = isImperial
)
}
}

View File

@ -10,7 +10,6 @@ import de.timklge.karooheadwind.streamCurrentWeatherData
import de.timklge.karooheadwind.streamDataFlow
import de.timklge.karooheadwind.streamSettings
import de.timklge.karooheadwind.streamUserProfile
import de.timklge.karooheadwind.throttle
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.extension.DataTypeImpl
import io.hammerhead.karooext.internal.Emitter
@ -114,7 +113,7 @@ class RelativeGradeDataType(private val karooSystemService: KarooSystemService,
return relativeGrade
}
suspend fun streamRelativeGrade(karooSystemService: KarooSystemService, context: Context): Flow<RelativeGradeResponse> {
fun streamRelativeGrade(karooSystemService: KarooSystemService, context: Context): Flow<RelativeGradeResponse> {
val relativeWindDirectionFlow = karooSystemService.getRelativeHeadingFlow(context).filterIsInstance<HeadingResponse.Value>().map { it.diff + 180 }
val speedFlow = karooSystemService.streamDataFlow(DataType.Type.SPEED).filterIsInstance<StreamState.Streaming>().map { it.dataPoint.singleValue ?: 0.0 }
val actualGradeFlow = karooSystemService.streamDataFlow(DataType.Type.ELEVATION_GRADE).filterIsInstance<StreamState.Streaming>().map { it.dataPoint.singleValue }.filterNotNull().map { it / 100.0 } // Convert to decimal grade
@ -127,11 +126,30 @@ class RelativeGradeDataType(private val karooSystemService: KarooSystemService,
} + DEFAULT_BIKE_WEIGHT
}
val refreshRate = karooSystemService.getRefreshRateInMilliseconds(context)
val windSpeedFlow = combine(context.streamSettings(karooSystemService), karooSystemService.streamUserProfile(), context.streamCurrentWeatherData(karooSystemService).filterNotNull()) { settings, profile, weatherData ->
val isOpenMeteo = settings.weatherProvider == WeatherDataProvider.OPEN_METEO
val profileIsImperial = profile.preferredUnit.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL
val windSpeedFlow = context.streamCurrentWeatherData(karooSystemService).filterNotNull().map { weatherData ->
if (isOpenMeteo) {
if (profileIsImperial) { // OpenMeteo returns wind speed in mph
val windSpeedInMilesPerHour = weatherData.windSpeed
windSpeedInMilesPerHour * 0.44704
} else { // Wind speed reported by openmeteo is in km/h
val windSpeedInKmh = weatherData.windSpeed
windSpeedInKmh * 0.277778
}
} else {
if (profileIsImperial) { // OpenWeatherMap returns wind speed in mph
val windSpeedInMilesPerHour = weatherData.windSpeed
windSpeedInMilesPerHour * 0.44704
} else { // Wind speed reported by openweathermap is in m/s
weatherData.windSpeed
}
}
}
data class StreamValues(
val relativeWindDirection: Double,
@ -143,7 +161,7 @@ class RelativeGradeDataType(private val karooSystemService: KarooSystemService,
return combine(relativeWindDirectionFlow, speedFlow, windSpeedFlow, actualGradeFlow, totalMassFlow) { windDirection, speed, windSpeed, actualGrade, totalMass ->
StreamValues(windDirection, speed, windSpeed, actualGrade, totalMass)
}.distinctUntilChanged().throttle(refreshRate).map { (windDirection, speed, windSpeed, actualGrade, totalMass) ->
}.distinctUntilChanged().map { (windDirection, speed, windSpeed, actualGrade, totalMass) ->
val relativeGrade = estimateRelativeGrade(actualGrade, speed, windSpeed, windDirection, totalMass)
Log.d(KarooHeadwindExtension.TAG, "Relative grade: $relativeGrade - Wind Direction: $windDirection - Speed: $speed - Wind Speed: $windSpeed - Actual Grade: $actualGrade - Total Mass: $totalMass")

View File

@ -3,10 +3,9 @@ package de.timklge.karooheadwind.datatypes
import android.content.Context
import de.timklge.karooheadwind.weatherprovider.WeatherData
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.models.UserProfile
class RelativeHumidityDataType(karooSystemService: KarooSystemService, context: Context) : BaseDataType(karooSystemService, context, "relativeHumidity"){
override fun getValue(data: WeatherData, userProfile: UserProfile): Double? {
return data.relativeHumidity.toDouble()
override fun getValue(data: WeatherData): Double? {
return data.relativeHumidity
}
}

View File

@ -3,10 +3,9 @@ package de.timklge.karooheadwind.datatypes
import android.content.Context
import de.timklge.karooheadwind.weatherprovider.WeatherData
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.models.UserProfile
class SealevelPressureDataType(karooSystemService: KarooSystemService, context: Context) : BaseDataType(karooSystemService, context, "sealevelPressure"){
override fun getValue(data: WeatherData, userProfile: UserProfile): Double? {
override fun getValue(data: WeatherData): Double? {
return data.sealevelPressure
}
}

View File

@ -3,10 +3,9 @@ package de.timklge.karooheadwind.datatypes
import android.content.Context
import de.timklge.karooheadwind.weatherprovider.WeatherData
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.models.UserProfile
class SurfacePressureDataType(karooSystemService: KarooSystemService, context: Context) : BaseDataType(karooSystemService, context, "surfacePressure"){
override fun getValue(data: WeatherData, userProfile: UserProfile): Double? {
override fun getValue(data: WeatherData): Double? {
return data.surfacePressure
}
}

View File

@ -21,12 +21,9 @@ import de.timklge.karooheadwind.datatypes.TailwindDataType.StreamData
import de.timklge.karooheadwind.getRelativeHeadingFlow
import de.timklge.karooheadwind.streamCurrentWeatherData
import de.timklge.karooheadwind.streamDataFlow
import de.timklge.karooheadwind.streamDatatypeIsVisible
import de.timklge.karooheadwind.streamSettings
import de.timklge.karooheadwind.streamUserProfile
import de.timklge.karooheadwind.throttle
import de.timklge.karooheadwind.util.msInUserUnit
import de.timklge.karooheadwind.weatherprovider.WeatherData
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.extension.DataTypeImpl
import io.hammerhead.karooext.internal.ViewEmitter
@ -88,7 +85,7 @@ class TailwindAndRideSpeedDataType(
val gustSpeed = windSpeed * ((10..40).random().toDouble() / 10)
val isImperial = profile.preferredUnit.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL
emit(StreamData(HeadingResponse.Value(bearing), bearing, windSpeed.toDouble(), HeadwindSettings(), rideSpeed, gustSpeed = gustSpeed, isImperial = isImperial, isVisible = true))
emit(StreamData(HeadingResponse.Value(bearing), bearing, windSpeed.toDouble(), HeadwindSettings(), rideSpeed, gustSpeed = gustSpeed, isImperial = isImperial))
delay(2_000)
}
@ -116,27 +113,18 @@ class TailwindAndRideSpeedDataType(
val flow = if (config.preview) {
previewFlow(karooSystem.streamUserProfile())
} else {
combine(karooSystem.getRelativeHeadingFlow(context),
context.streamCurrentWeatherData(karooSystem),
context.streamSettings(karooSystem),
karooSystem.streamUserProfile(),
streamSpeedInMs(),
karooSystem.streamDatatypeIsVisible(dataTypeId)
) { data ->
val headingResponse = data[0] as HeadingResponse
val weatherData = data[1] as? WeatherData
val settings = data[2] as HeadwindSettings
val userProfile = data[3] as UserProfile
val rideSpeedInMs = data[4] as Double
val isVisible = data[5] as Boolean
combine(karooSystem.getRelativeHeadingFlow(context), context.streamCurrentWeatherData(karooSystem), context.streamSettings(karooSystem), karooSystem.streamUserProfile(), streamSpeedInMs()) { headingResponse, weatherData, settings, userProfile, rideSpeedInMs ->
val isImperial = userProfile.preferredUnit.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL
val absoluteWindDirection = weatherData?.windDirection
val windSpeed = weatherData?.windSpeed
val gustSpeed = weatherData?.windGusts
val rideSpeed = rideSpeedInMs
val rideSpeed = if (isImperial){
rideSpeedInMs * 2.23694
} else {
rideSpeedInMs * 3.6
}
StreamData(headingResponse, absoluteWindDirection, windSpeed, settings, rideSpeed = rideSpeed, isImperial = isImperial, gustSpeed = gustSpeed, isVisible = isVisible)
StreamData(headingResponse, absoluteWindDirection, windSpeed, settings, rideSpeed = rideSpeed, isImperial = isImperial, gustSpeed = gustSpeed)
}
}
@ -166,21 +154,15 @@ class TailwindAndRideSpeedDataType(
WindDirectionIndicatorSetting.WIND_DIRECTION -> streamData.absoluteWindDirection + 180
}
val rideSpeedInUserUnit = msInUserUnit(streamData.rideSpeed ?: 0.0, streamData.isImperial)
val text = String.format(Locale.current.platformLocale, "%.1f", rideSpeedInUserUnit)
val text = streamData.rideSpeed?.let { String.format(Locale.current.platformLocale, "%.1f", it) } ?: ""
val wideMode = config.gridSize.first == 60
val gustSpeedInUserUnit = msInUserUnit(streamData.gustSpeed ?: 0.0, streamData.isImperial)
val gustSpeedAddon = if (wideMode) {
"-${gustSpeedInUserUnit.roundToInt()}"
"-${streamData.gustSpeed?.roundToInt() ?: 0}"
} else {
""
}
val windSpeedUserUnit = msInUserUnit(windSpeed, streamData.isImperial)
val subtextWithSign = when (streamData.settings.windDirectionIndicatorTextSetting) {
WindDirectionIndicatorTextSetting.HEADWIND_SPEED -> {
val headwindSpeed = cos( (windDirection + 180) * Math.PI / 180.0) * windSpeed
@ -189,12 +171,9 @@ class TailwindAndRideSpeedDataType(
val sign = if (headwindSpeed < 0) "+" else {
if (headwindSpeed > 0) "-" else ""
}
val headwindSpeedUserUnit = msInUserUnit(headwindSpeed, streamData.isImperial)
"$sign${headwindSpeedUserUnit.roundToInt().absoluteValue} ${windSpeedUserUnit.roundToInt()}${gustSpeedAddon}"
"$sign${headwindSpeed.roundToInt().absoluteValue} ${windSpeed.roundToInt()}${gustSpeedAddon}"
}
WindDirectionIndicatorTextSetting.WIND_SPEED -> "${windSpeedUserUnit.roundToInt()}${gustSpeedAddon}"
WindDirectionIndicatorTextSetting.WIND_SPEED -> "${windSpeed.roundToInt()}${gustSpeedAddon}"
WindDirectionIndicatorTextSetting.NONE -> ""
}
@ -203,7 +182,11 @@ class TailwindAndRideSpeedDataType(
if (streamData.settings.windDirectionIndicatorSetting == WindDirectionIndicatorSetting.HEADWIND_DIRECTION) {
val headwindSpeed = cos( (windDirection + 180) * Math.PI / 180.0) * windSpeed
val windSpeedInKmh = headwindSpeed * 3.6
val windSpeedInKmh = if (streamData.isImperial == true){
headwindSpeed / 2.23694 * 3.6
} else {
headwindSpeed
}
dayColor = interpolateWindColor(windSpeedInKmh, false, context)
nightColor = interpolateWindColor(windSpeedInKmh, true, context)
}

View File

@ -17,12 +17,9 @@ import de.timklge.karooheadwind.WindDirectionIndicatorTextSetting
import de.timklge.karooheadwind.getRelativeHeadingFlow
import de.timklge.karooheadwind.streamCurrentWeatherData
import de.timklge.karooheadwind.streamDataFlow
import de.timklge.karooheadwind.streamDatatypeIsVisible
import de.timklge.karooheadwind.streamSettings
import de.timklge.karooheadwind.streamUserProfile
import de.timklge.karooheadwind.throttle
import de.timklge.karooheadwind.util.msInUserUnit
import de.timklge.karooheadwind.weatherprovider.WeatherData
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.extension.DataTypeImpl
import io.hammerhead.karooext.internal.ViewEmitter
@ -38,7 +35,6 @@ import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
@ -60,8 +56,7 @@ class TailwindDataType(
val settings: HeadwindSettings,
val rideSpeed: Double?,
val gustSpeed: Double?,
val isImperial: Boolean,
val isVisible: Boolean)
val isImperial: Boolean)
private fun previewFlow(profileFlow: Flow<UserProfile>): Flow<StreamData> {
return flow {
@ -74,7 +69,7 @@ class TailwindDataType(
val gustSpeed = windSpeed * ((10..40).random().toDouble() / 10)
val isImperial = profile.preferredUnit.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL
emit(StreamData(HeadingResponse.Value(bearing), bearing, windSpeed.toDouble(), HeadwindSettings(), rideSpeed, gustSpeed = gustSpeed, isImperial = isImperial, isVisible = true))
emit(StreamData(HeadingResponse.Value(bearing), bearing, windSpeed.toDouble(), HeadwindSettings(), rideSpeed, gustSpeed = gustSpeed, isImperial = isImperial))
delay(2_000)
}
@ -103,26 +98,18 @@ class TailwindDataType(
val flow = if (config.preview) {
previewFlow(karooSystem.streamUserProfile())
} else {
combine(karooSystem.getRelativeHeadingFlow(context),
context.streamCurrentWeatherData(karooSystem),
context.streamSettings(karooSystem),
karooSystem.streamUserProfile(),
streamSpeedInMs(),
karooSystem.streamDatatypeIsVisible(dataTypeId)
) { data ->
val headingResponse = data[0] as HeadingResponse
val weatherData = data[1] as? WeatherData
val settings = data[2] as HeadwindSettings
val userProfile = data[3] as UserProfile
val rideSpeedInMs = data[4] as Double
val isVisible = data[5] as Boolean
combine(karooSystem.getRelativeHeadingFlow(context), context.streamCurrentWeatherData(karooSystem), context.streamSettings(karooSystem), karooSystem.streamUserProfile(), streamSpeedInMs()) { headingResponse, weatherData, settings, userProfile, rideSpeedInMs ->
val isImperial = userProfile.preferredUnit.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL
val absoluteWindDirection = weatherData?.windDirection
val windSpeed = weatherData?.windSpeed
val gustSpeed = weatherData?.windGusts
val rideSpeed = if (isImperial){
rideSpeedInMs * 2.23694
} else {
rideSpeedInMs * 3.6
}
StreamData(headingResponse, absoluteWindDirection, windSpeed, settings, rideSpeed = rideSpeedInMs, isImperial = isImperial, gustSpeed = gustSpeed, isVisible = isVisible)
StreamData(headingResponse, absoluteWindDirection, windSpeed, settings, rideSpeed = rideSpeed, isImperial = isImperial, gustSpeed = gustSpeed)
}
}
@ -130,7 +117,7 @@ class TailwindDataType(
emitter.onNext(ShowCustomStreamState("", null))
val refreshRate = karooSystem.getRefreshRateInMilliseconds(context)
flow.filter { it.isVisible }.throttle(refreshRate).collect { streamData ->
flow.throttle(refreshRate).collect { streamData ->
Log.d(KarooHeadwindExtension.TAG, "Updating tailwind direction view")
val value = (streamData.headingResponse as? HeadingResponse.Value)?.diff
@ -160,26 +147,24 @@ class TailwindDataType(
val sign = if (headwindSpeed < 0) "+" else {
if (headwindSpeed > 0) "-" else ""
}
val headwindSpeedUserUnit = msInUserUnit(headwindSpeed, streamData.isImperial)
"$sign${headwindSpeedUserUnit.roundToInt().absoluteValue}"
"$sign${headwindSpeed.roundToInt().absoluteValue}"
}
WindDirectionIndicatorTextSetting.WIND_SPEED -> msInUserUnit(windSpeed, streamData.isImperial).roundToInt().toString()
WindDirectionIndicatorTextSetting.WIND_SPEED -> windSpeed.roundToInt().toString()
WindDirectionIndicatorTextSetting.NONE -> ""
}
val windSpeedUserUnit = msInUserUnit(windSpeed, streamData.isImperial)
val gustSpeedUserUnit = msInUserUnit(streamData.gustSpeed ?: 0.0, streamData.isImperial)
val subtext = "${windSpeedUserUnit.roundToInt()}-${gustSpeedUserUnit.roundToInt()}"
val subtext = "${windSpeed.roundToInt()}-${streamData.gustSpeed?.roundToInt()}"
var dayColor = Color(ContextCompat.getColor(context, R.color.black))
var nightColor = Color(ContextCompat.getColor(context, R.color.white))
if (streamData.settings.windDirectionIndicatorSetting == WindDirectionIndicatorSetting.HEADWIND_DIRECTION) {
val headwindSpeed = cos( (windDirection + 180) * Math.PI / 180.0) * windSpeed
val windSpeedInKmh = headwindSpeed * 3.6
val windSpeedInKmh = if (streamData.isImperial){
headwindSpeed / 2.23694 * 3.6
} else {
headwindSpeed
}
dayColor = interpolateWindColor(windSpeedInKmh, false, context)
nightColor = interpolateWindColor(windSpeedInKmh, true, context)
}

View File

@ -3,15 +3,9 @@ package de.timklge.karooheadwind.datatypes
import android.content.Context
import de.timklge.karooheadwind.weatherprovider.WeatherData
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.models.DataType
import io.hammerhead.karooext.models.UserProfile
class TemperatureDataType(karooSystemService: KarooSystemService, context: Context) : BaseDataType(karooSystemService, context, "temperature"){
override fun getValue(data: WeatherData, userProfile: UserProfile): Double {
override fun getValue(data: WeatherData): Double {
return data.temperature
}
override fun getFormatDataType(): String? {
return DataType.Type.TEMPERATURE
}
}

View File

@ -91,15 +91,14 @@ class TemperatureForecastDataType(karooSystem: KarooSystemService) : ForecastDat
timeLabel: String,
dateLabel: String?,
distance: Double?,
isImperial: Boolean,
isNight: Boolean
isImperial: Boolean
) {
TemperatureForecast(
temperature = temperature,
temperatureUnit = temperatureUnit,
distance = distance,
timeLabel = timeLabel,
isImperial = isImperial,
isImperial = isImperial
)
}
}

View File

@ -16,19 +16,13 @@ import de.timklge.karooheadwind.HeadingResponse
import de.timklge.karooheadwind.HeadwindSettings
import de.timklge.karooheadwind.KarooHeadwindExtension
import de.timklge.karooheadwind.MainActivity
import de.timklge.karooheadwind.R
import de.timklge.karooheadwind.TemperatureUnit
import de.timklge.karooheadwind.getHeadingFlow
import de.timklge.karooheadwind.streamCurrentWeatherData
import de.timklge.karooheadwind.streamDatatypeIsVisible
import de.timklge.karooheadwind.streamSettings
import de.timklge.karooheadwind.streamUserProfile
import de.timklge.karooheadwind.throttle
import de.timklge.karooheadwind.util.celciusInUserUnit
import de.timklge.karooheadwind.util.millimetersInUserUnit
import de.timklge.karooheadwind.util.msInUserUnit
import de.timklge.karooheadwind.weatherprovider.WeatherData
import de.timklge.karooheadwind.weatherprovider.WeatherInterpretation
import de.timklge.karooheadwind.getHeadingFlow
import de.timklge.karooheadwind.streamCurrentWeatherData
import de.timklge.karooheadwind.streamSettings
import de.timklge.karooheadwind.streamUserProfile
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.extension.DataTypeImpl
import io.hammerhead.karooext.internal.Emitter
@ -46,7 +40,6 @@ import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import java.time.Instant
@ -69,7 +62,8 @@ class WeatherDataType(
val job = CoroutineScope(Dispatchers.IO).launch {
val currentWeatherData = applicationContext.streamCurrentWeatherData(karooSystem)
currentWeatherData.collect { data ->
currentWeatherData
.collect { data ->
Log.d(KarooHeadwindExtension.TAG, "Wind code: ${data?.weatherCode}")
emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to (data?.weatherCode?.toDouble() ?: 0.0)))))
}
@ -80,18 +74,14 @@ class WeatherDataType(
}
data class StreamData(val data: WeatherData?, val settings: HeadwindSettings,
val profile: UserProfile? = null, val headingResponse: HeadingResponse? = null,
val isVisible: Boolean)
val profile: UserProfile? = null, val headingResponse: HeadingResponse? = null)
private fun previewFlow(): Flow<StreamData> = flow {
while (true){
emit(StreamData(
WeatherData(
Instant.now().epochSecond, 0.0,
20, 50.0, 3.0, 0.0, 1013.25, 980.0, 15.0, 30.0, 30.0,
WeatherInterpretation.getKnownWeatherCodes().random(), isForecast = false,
isNight = listOf(true, false).random()
), HeadwindSettings(), isVisible = true))
WeatherData(Instant.now().epochSecond, 0.0,
20.0, 50.0, 3.0, 0.0, 1013.25, 980.0, 15.0, 30.0, 30.0,
WeatherInterpretation.getKnownWeatherCodes().random(), isForecast = false), HeadwindSettings()))
delay(5_000)
}
@ -106,29 +96,21 @@ class WeatherDataType(
val baseBitmap = BitmapFactory.decodeResource(
context.resources,
R.drawable.arrow_0
de.timklge.karooheadwind.R.drawable.arrow_0
)
val dataFlow = if (config.preview){
previewFlow()
} else {
combine(
context.streamCurrentWeatherData(karooSystem),
context.streamSettings(karooSystem),
karooSystem.streamUserProfile(),
karooSystem.getHeadingFlow(context),
karooSystem.streamDatatypeIsVisible(dataTypeId)
) { data, settings, profile, heading, isVisible ->
StreamData(data, settings, profile, heading, isVisible)
combine(context.streamCurrentWeatherData(karooSystem), context.streamSettings(karooSystem), karooSystem.streamUserProfile(), karooSystem.getHeadingFlow(context)) { data, settings, profile, heading ->
StreamData(data, settings, profile, heading)
}
}
val viewJob = CoroutineScope(Dispatchers.IO).launch {
emitter.onNext(ShowCustomStreamState("", null))
val refreshRate = karooSystem.getRefreshRateInMilliseconds(context)
dataFlow.filter { it.isVisible }.throttle(refreshRate).collect { (data, settings, userProfile, headingResponse) ->
dataFlow.collect { (data, settings, userProfile, headingResponse) ->
Log.d(KarooHeadwindExtension.TAG, "Updating weather view")
if (data == null){
@ -143,18 +125,18 @@ class WeatherDataType(
val result = glance.compose(context, DpSize.Unspecified) {
var modifier = GlanceModifier.fillMaxSize()
// TODO reenable once swipes are no longer interpreted as clicks if (!config.preview) modifier = modifier.clickable(onClick = actionStartActivity<MainActivity>())
if (!config.preview) modifier = modifier.clickable(onClick = actionStartActivity<MainActivity>())
Box(modifier = modifier, contentAlignment = Alignment.CenterEnd) {
Weather(
baseBitmap,
current = interpretation,
windBearing = data.windDirection.roundToInt(),
windSpeed = msInUserUnit(data.windSpeed, userProfile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL).roundToInt(),
windGusts = msInUserUnit(data.windGusts, userProfile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL).roundToInt(),
precipitation = millimetersInUserUnit(data.precipitation, userProfile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL),
windSpeed = data.windSpeed.roundToInt(),
windGusts = data.windGusts.roundToInt(),
precipitation = data.precipitation,
precipitationProbability = null,
temperature = celciusInUserUnit(data.temperature, userProfile?.preferredUnit?.temperature == UserProfile.PreferredUnit.UnitType.IMPERIAL).roundToInt(),
temperature = data.temperature.roundToInt(),
temperatureUnit = if (userProfile?.preferredUnit?.temperature != UserProfile.PreferredUnit.UnitType.IMPERIAL) TemperatureUnit.CELSIUS else TemperatureUnit.FAHRENHEIT,
timeLabel = formattedTime,
rowAlignment = when (config.alignment){
@ -164,8 +146,7 @@ class WeatherDataType(
},
dateLabel = formattedDate,
singleDisplay = true,
isImperial = userProfile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL,
isNight = data.isNight,
isImperial = userProfile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL
)
}
}

View File

@ -21,8 +21,7 @@ class WeatherForecastDataType(karooSystem: KarooSystemService) : ForecastDataTyp
timeLabel: String,
dateLabel: String?,
distance: Double?,
isImperial: Boolean,
isNight: Boolean,
isImperial: Boolean
) {
Weather(
arrowBitmap = arrowBitmap,
@ -37,8 +36,7 @@ class WeatherForecastDataType(karooSystem: KarooSystemService) : ForecastDataTyp
timeLabel = timeLabel,
dateLabel = dateLabel,
distance = distance,
isImperial = isImperial,
isNight = isNight,
isImperial = isImperial
)
}

View File

@ -45,14 +45,14 @@ fun getShortDateFormatter(): DateTimeFormatter = DateTimeFormatter.ofPattern(
}
).withZone(ZoneId.systemDefault())
fun getWeatherIcon(interpretation: WeatherInterpretation, isNight: Boolean): Int {
fun getWeatherIcon(interpretation: WeatherInterpretation): Int {
return when (interpretation){
WeatherInterpretation.CLEAR -> if (isNight) R.drawable.crescent_moon else R.drawable.sun
WeatherInterpretation.CLOUDY -> R.drawable.cloud
WeatherInterpretation.RAINY -> R.drawable.cloud_with_rain
WeatherInterpretation.SNOWY -> R.drawable.cloud_with_snow
WeatherInterpretation.DRIZZLE -> R.drawable.cloud_with_light_rain
WeatherInterpretation.THUNDERSTORM -> R.drawable.cloud_with_lightning_and_rain
WeatherInterpretation.CLEAR -> R.drawable.bx_clear
WeatherInterpretation.CLOUDY -> R.drawable.bx_cloud
WeatherInterpretation.RAINY -> R.drawable.bx_cloud_rain
WeatherInterpretation.SNOWY -> R.drawable.bx_cloud_snow
WeatherInterpretation.DRIZZLE -> R.drawable.bx_cloud_drizzle
WeatherInterpretation.THUNDERSTORM -> R.drawable.bx_cloud_lightning
WeatherInterpretation.UNKNOWN -> R.drawable.question_mark_regular_240
}
}
@ -74,8 +74,7 @@ fun Weather(
rowAlignment: Alignment.Horizontal = Alignment.Horizontal.CenterHorizontally,
dateLabel: String? = null,
singleDisplay: Boolean = false,
isImperial: Boolean?,
isNight: Boolean
isImperial: Boolean?
) {
val fontSize = if (singleDisplay) 19f else 14f
@ -84,9 +83,10 @@ fun Weather(
Row(modifier = GlanceModifier.defaultWeight().wrapContentWidth(), horizontalAlignment = rowAlignment, verticalAlignment = Alignment.CenterVertically) {
Image(
modifier = GlanceModifier.defaultWeight().wrapContentWidth().padding(1.dp),
provider = ImageProvider(getWeatherIcon(current, isNight)),
provider = ImageProvider(getWeatherIcon(current)),
contentDescription = "Current weather information",
contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White))
)
}
@ -142,6 +142,7 @@ fun Weather(
provider = ImageProvider(R.drawable.thermometer),
contentDescription = "Temperature",
contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White))
)
Text(

View File

@ -17,25 +17,21 @@ import androidx.glance.text.FontFamily
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import de.timklge.karooheadwind.KarooHeadwindExtension
import de.timklge.karooheadwind.streamDataFlow
import de.timklge.karooheadwind.streamDatatypeIsVisible
import de.timklge.karooheadwind.throttle
import de.timklge.karooheadwind.weatherprovider.WeatherData
import de.timklge.karooheadwind.streamDataFlow
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.internal.ViewEmitter
import io.hammerhead.karooext.models.ShowCustomStreamState
import io.hammerhead.karooext.models.StreamState
import io.hammerhead.karooext.models.UpdateGraphicConfig
import io.hammerhead.karooext.models.UserProfile
import io.hammerhead.karooext.models.ViewConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
@ -52,22 +48,14 @@ class WindDirectionDataType(val karooSystem: KarooSystemService, context: Contex
)
}
override fun getValue(data: WeatherData, userProfile: UserProfile): Double {
override fun getValue(data: WeatherData): Double {
return data.windDirection
}
data class StreamData(
val windBearing: Double,
val isVisible: Boolean
)
private fun previewFlow(): Flow<StreamData> {
private fun previewFlow(): Flow<Double> {
return flow {
while (true) {
emit(StreamData(
(0..360).random().toDouble(),
true
))
emit((0..360).random().toDouble())
delay(1_000)
}
}
@ -86,20 +74,12 @@ class WindDirectionDataType(val karooSystem: KarooSystemService, context: Contex
val flow = if (config.preview){
previewFlow()
} else {
combine(
karooSystem.streamDataFlow(dataTypeId),
karooSystem.streamDatatypeIsVisible(dataTypeId)
) { windBearing, isVisible ->
StreamData(
windBearing = (windBearing as? StreamState.Streaming)?.dataPoint?.singleValue ?: 0.0,
isVisible = isVisible
)
}
karooSystem.streamDataFlow(dataTypeId)
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue ?: 0.0 }
}
val refreshRate = karooSystem.getRefreshRateInMilliseconds(context)
flow.filter { it.isVisible }.throttle(refreshRate).collect { (windBearing, isVisible) ->
flow
.collect { windBearing ->
val windCardinalDirectionIndex = ((windBearing % 360) / 22.5).roundToInt() % 16
val text = windDirections[windCardinalDirectionIndex]

View File

@ -103,8 +103,7 @@ class WindForecastDataType(karooSystem: KarooSystemService) : ForecastDataType(k
timeLabel: String,
dateLabel: String?,
distance: Double?,
isImperial: Boolean,
isNight: Boolean
isImperial: Boolean
) {
WindForecast(
arrowBitmap = arrowBitmap,
@ -113,7 +112,7 @@ class WindForecastDataType(karooSystem: KarooSystemService) : ForecastDataType(k
gustSpeed = windGusts,
distance = distance,
timeLabel = timeLabel,
isImperial = isImperial,
isImperial = isImperial
)
}
}

View File

@ -3,10 +3,9 @@ package de.timklge.karooheadwind.datatypes
import android.content.Context
import de.timklge.karooheadwind.weatherprovider.WeatherData
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.models.UserProfile
class WindGustsDataType(karooSystemService: KarooSystemService, context: Context) : BaseDataType(karooSystemService, context, "windGusts"){
override fun getValue(data: WeatherData, userProfile: UserProfile): Double {
override fun getValue(data: WeatherData): Double {
return data.windGusts
}
}

View File

@ -3,10 +3,9 @@ package de.timklge.karooheadwind.datatypes
import android.content.Context
import de.timklge.karooheadwind.weatherprovider.WeatherData
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.models.UserProfile
class WindSpeedDataType(karooSystemService: KarooSystemService, context: Context) : BaseDataType(karooSystemService, context, "windSpeed"){
override fun getValue(data: WeatherData, userProfile: UserProfile): Double {
override fun getValue(data: WeatherData): Double {
return data.windSpeed
}
}

View File

@ -84,7 +84,6 @@ fun SettingsScreen(onFinish: () -> Unit) {
var selectedWeatherProvider by remember { mutableStateOf(WeatherDataProvider.OPEN_METEO) }
var openWeatherMapApiKey by remember { mutableStateOf("") }
var isK2 by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
ctx.streamSettings(karooSystem).collect { settings ->
@ -103,7 +102,6 @@ fun SettingsScreen(onFinish: () -> Unit) {
LaunchedEffect(Unit) {
karooSystem.connect { connected ->
karooConnected = connected
isK2 = karooSystem.hardwareType == io.hammerhead.karooext.models.HardwareType.K2
}
}
@ -153,8 +151,8 @@ fun SettingsScreen(onFinish: () -> Unit) {
.verticalScroll(rememberScrollState())
.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(10.dp)
) {
val refreshRateDropdownOptions = remember(isK2) { RefreshRate.entries.toList().map { unit -> DropdownOption(unit.id, unit.getDescription(isK2)) } }
val refreshRateSelection by remember(refreshRateSetting, isK2) {
val refreshRateDropdownOptions = RefreshRate.entries.toList().map { unit -> DropdownOption(unit.id, unit.getDescription(karooSystem)) }
val refreshRateSelection by remember(refreshRateSetting) {
mutableStateOf(refreshRateDropdownOptions.find { option -> option.id == refreshRateSetting.id }!!)
}
Dropdown(
@ -162,7 +160,8 @@ fun SettingsScreen(onFinish: () -> Unit) {
options = refreshRateDropdownOptions,
selected = refreshRateSelection
) { selectedOption ->
refreshRateSetting = RefreshRate.entries.find { unit -> unit.id == selectedOption.id }!!
refreshRateSetting =
RefreshRate.entries.find { unit -> unit.id == selectedOption.id }!!
}
val windDirectionIndicatorSettingDropdownOptions =

View File

@ -37,9 +37,6 @@ import de.timklge.karooheadwind.streamCurrentWeatherData
import de.timklge.karooheadwind.streamStats
import de.timklge.karooheadwind.streamUpcomingRoute
import de.timklge.karooheadwind.streamUserProfile
import de.timklge.karooheadwind.util.celciusInUserUnit
import de.timklge.karooheadwind.util.millimetersInUserUnit
import de.timklge.karooheadwind.util.msInUserUnit
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.models.UserProfile
import java.time.Instant
@ -113,17 +110,16 @@ fun WeatherScreen(onFinish: () -> Unit) {
baseBitmap = baseBitmap,
current = WeatherInterpretation.fromWeatherCode(currentWeatherData?.weatherCode),
windBearing = currentWeatherData?.windDirection?.roundToInt() ?: 0,
windSpeed = msInUserUnit(currentWeatherData?.windSpeed ?: 0.0, profile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL).roundToInt(),
windGusts = msInUserUnit(currentWeatherData?.windGusts ?: 0.0, profile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL).roundToInt(),
precipitation = millimetersInUserUnit(currentWeatherData?.precipitation ?: 0.0, profile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL),
temperature = celciusInUserUnit(currentWeatherData?.temperature ?: 0.0, profile?.preferredUnit?.temperature == UserProfile.PreferredUnit.UnitType.IMPERIAL).roundToInt(),
windSpeed = currentWeatherData?.windSpeed?.roundToInt() ?: 0,
windGusts = currentWeatherData?.windGusts?.roundToInt() ?: 0,
precipitation = currentWeatherData?.precipitation ?: 0.0,
temperature = currentWeatherData?.temperature?.toInt() ?: 0,
temperatureUnit = if(profile?.preferredUnit?.temperature == UserProfile.PreferredUnit.UnitType.METRIC) TemperatureUnit.CELSIUS else TemperatureUnit.FAHRENHEIT,
timeLabel = formattedTime,
dateLabel = formattedDate,
distance = requestedWeatherPosition?.let { l -> location?.distanceTo(l)?.times(1000) },
includeDistanceLabel = false,
isImperial = profile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL,
isNight = currentWeatherData?.isNight == true
)
}
@ -233,18 +229,17 @@ fun WeatherScreen(onFinish: () -> Unit) {
baseBitmap,
current = interpretation,
windBearing = weatherData?.windDirection?.roundToInt() ?: 0,
windSpeed = msInUserUnit(currentWeatherData?.windSpeed ?: 0.0, profile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL).roundToInt(),
windGusts = msInUserUnit(currentWeatherData?.windGusts ?: 0.0, profile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL).roundToInt(),
precipitation = millimetersInUserUnit(weatherData?.precipitation ?: 0.0, profile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL),
temperature = celciusInUserUnit(weatherData?.temperature ?: 0.0, profile?.preferredUnit?.temperature == UserProfile.PreferredUnit.UnitType.IMPERIAL).roundToInt(),
windSpeed = weatherData?.windSpeed?.roundToInt() ?: 0,
windGusts = weatherData?.windGusts?.roundToInt() ?: 0,
precipitation = weatherData?.precipitation ?: 0.0,
temperature = weatherData?.temperature?.toInt() ?: 0,
temperatureUnit = if (profile?.preferredUnit?.temperature != UserProfile.PreferredUnit.UnitType.IMPERIAL) TemperatureUnit.CELSIUS else TemperatureUnit.FAHRENHEIT,
timeLabel = formattedForecastTime,
dateLabel = formattedForecastDate,
distance = distanceFromCurrent,
includeDistanceLabel = true,
precipitationProbability = weatherData?.precipitationProbability?.toInt() ?: 0,
isImperial = profile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL,
isNight = weatherData?.isNight == true,
isImperial = profile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL
)
}

View File

@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -47,10 +46,9 @@ fun WeatherWidget(
distance: Double? = null,
includeDistanceLabel: Boolean = false,
precipitationProbability: Int? = null,
isImperial: Boolean,
isNight: Boolean
isImperial: Boolean
) {
val fontSize = 18.sp
val fontSize = 20.sp
Row(
modifier = Modifier.fillMaxWidth().padding(5.dp),
@ -99,20 +97,22 @@ fun WeatherWidget(
}
}
Image(
painter = painterResource(id = getWeatherIcon(current, isNight)),
// Weather icon (larger)
Icon(
painter = painterResource(id = getWeatherIcon(current)),
contentDescription = "Current weather",
modifier = Modifier.size(72.dp)
)
Column(horizontalAlignment = Alignment.End) {
// Temperature (larger)
Row(
verticalAlignment = Alignment.CenterVertically
) {
Image(
Icon(
painter = painterResource(id = R.drawable.thermometer),
contentDescription = "Temperature",
modifier = Modifier.size(18.dp),
modifier = Modifier.size(18.dp)
)
Spacer(modifier = Modifier.width(4.dp))
@ -134,8 +134,8 @@ fun WeatherWidget(
val precipitationProbabilityLabel =
if (precipitationProbability != null) "${precipitationProbability}% " else ""
Image(
painter = painterResource(id = R.drawable.droplet),
Icon(
painter = painterResource(id = R.drawable.droplet_regular),
contentDescription = "Precipitation",
modifier = Modifier.size(18.dp)
)

View File

@ -1,26 +0,0 @@
package de.timklge.karooheadwind.util
fun celciusInUserUnit(celcius: Double, isImperial: Boolean): Double {
return if (isImperial) {
celcius * 9.0 / 5 + 32.0
} else {
celcius
}
}
fun millimetersInUserUnit(millimeters: Double, isImperial: Boolean): Double {
return if (isImperial) {
millimeters / 25.4
} else {
millimeters
}
}
// Returns the given speed value (m / s) in user unit (km/h or mph)
fun msInUserUnit(ms: Double, isImperial: Boolean): Double {
return if (isImperial) {
ms * 2.2369362920544
} else {
ms * 3.6
}
}

View File

@ -6,17 +6,16 @@ import kotlinx.serialization.Serializable
data class WeatherData(
val time: Long,
val temperature: Double,
val relativeHumidity: Int,
val relativeHumidity: Double? = null,
val precipitation: Double,
val precipitationProbability: Double? = null,
val cloudCover: Double,
val sealevelPressure: Double,
val surfacePressure: Double,
val cloudCover: Double? = null,
val sealevelPressure: Double? = null,
val surfacePressure: Double? = null,
val windSpeed: Double,
val windDirection: Double,
val windGusts: Double,
val weatherCode: Int,
val isForecast: Boolean,
val isNight: Boolean
val isForecast: Boolean
)

View File

@ -12,16 +12,15 @@ data class OpenMeteoWeatherData(
@SerialName("precipitation") val precipitation: Double,
@SerialName("cloud_cover") val cloudCover: Int,
@SerialName("surface_pressure") val surfacePressure: Double,
@SerialName("pressure_msl") val sealevelPressure: Double,
@SerialName("pressure_msl") val sealevelPressure: Double? = null,
@SerialName("wind_speed_10m") val windSpeed: Double,
@SerialName("wind_direction_10m") val windDirection: Double,
@SerialName("wind_gusts_10m") val windGusts: Double,
@SerialName("weather_code") val weatherCode: Int,
@SerialName("is_day") val isDay: Int,
) {
fun toWeatherData(): WeatherData = WeatherData(
temperature = temperature,
relativeHumidity = relativeHumidity,
relativeHumidity = relativeHumidity.toDouble(),
precipitation = precipitation,
cloudCover = cloudCover.toDouble(),
surfacePressure = surfacePressure,
@ -32,7 +31,6 @@ data class OpenMeteoWeatherData(
weatherCode = weatherCode,
time = time,
isForecast = false,
isNight = isDay == 0,
)
}

View File

@ -14,11 +14,6 @@ data class OpenMeteoWeatherForecastData(
@SerialName("wind_speed_10m") val windSpeed: List<Double>,
@SerialName("wind_direction_10m") val windDirection: List<Double>,
@SerialName("wind_gusts_10m") val windGusts: List<Double>,
@SerialName("cloud_cover") val cloudCover: List<Double>,
@SerialName("surface_pressure") val surfacePressure: List<Double>,
@SerialName("pressure_msl") val sealevelPressure: List<Double>,
@SerialName("is_day") val isDay: List<Int>,
@SerialName("relative_humidity_2m") val relativeHumidity: List<Int>,
) {
fun toWeatherData(): List<WeatherData> {
return time.mapIndexed { index, t ->
@ -30,13 +25,8 @@ data class OpenMeteoWeatherForecastData(
windDirection = windDirection[index],
windGusts = windGusts[index],
weatherCode = weatherCode[index],
isNight = isDay[index] == 0,
time = t,
isForecast = true,
cloudCover = cloudCover[index],
surfacePressure = surfacePressure[index],
sealevelPressure = sealevelPressure[index],
relativeHumidity = relativeHumidity[index],
)
}
}

View File

@ -3,7 +3,10 @@ package de.timklge.karooheadwind.weatherprovider.openmeteo
import android.util.Log
import de.timklge.karooheadwind.HeadwindSettings
import de.timklge.karooheadwind.KarooHeadwindExtension
import de.timklge.karooheadwind.PrecipitationUnit
import de.timklge.karooheadwind.TemperatureUnit
import de.timklge.karooheadwind.WeatherDataProvider
import de.timklge.karooheadwind.WindUnit
import de.timklge.karooheadwind.datatypes.GpsCoordinates
import de.timklge.karooheadwind.jsonWithUnknownKeys
import de.timklge.karooheadwind.weatherprovider.WeatherDataResponse
@ -25,12 +28,16 @@ import kotlin.time.Duration.Companion.seconds
class OpenMeteoWeatherProvider : WeatherProvider {
@OptIn(FlowPreview::class)
private suspend fun makeOpenMeteoWeatherRequest(karooSystemService: KarooSystemService, gpsCoordinates: List<GpsCoordinates>): HttpResponseState.Complete {
private suspend fun makeOpenMeteoWeatherRequest(karooSystemService: KarooSystemService, gpsCoordinates: List<GpsCoordinates>, settings: HeadwindSettings, profile: UserProfile?): HttpResponseState.Complete {
val precipitationUnit = if (profile?.preferredUnit?.distance != UserProfile.PreferredUnit.UnitType.IMPERIAL) PrecipitationUnit.MILLIMETERS else PrecipitationUnit.INCH
val temperatureUnit = if (profile?.preferredUnit?.temperature != UserProfile.PreferredUnit.UnitType.IMPERIAL) TemperatureUnit.CELSIUS else TemperatureUnit.FAHRENHEIT
val windUnit = if (profile?.preferredUnit?.distance != UserProfile.PreferredUnit.UnitType.IMPERIAL) WindUnit.KILOMETERS_PER_HOUR else WindUnit.MILES_PER_HOUR
val response = callbackFlow {
// https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&current=is_day,surface_pressure,pressure_msl,temperature_2m,relative_humidity_2m,precipitation,weather_code,cloud_cover,wind_speed_10m,wind_direction_10m,wind_gusts_10m&hourly=temperature_2m,precipitation_probability,precipitation,weather_code,wind_speed_10m,wind_direction_10m,wind_gusts_10m&timeformat=unixtime&past_hours=1&forecast_days=1&forecast_hours=12
// https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&current=surface_pressure,pressure_msl,temperature_2m,relative_humidity_2m,precipitation,weather_code,cloud_cover,wind_speed_10m,wind_direction_10m,wind_gusts_10m&hourly=temperature_2m,precipitation_probability,precipitation,weather_code,wind_speed_10m,wind_direction_10m,wind_gusts_10m&timeformat=unixtime&past_hours=1&forecast_days=1&forecast_hours=12
val lats = gpsCoordinates.joinToString(",") { String.format(Locale.US, "%.6f", it.lat) }
val lons = gpsCoordinates.joinToString(",") { String.format(Locale.US, "%.6f", it.lon) }
val url = "https://api.open-meteo.com/v1/forecast?latitude=${lats}&longitude=${lons}&current=is_day,surface_pressure,pressure_msl,temperature_2m,relative_humidity_2m,precipitation,weather_code,cloud_cover,wind_speed_10m,wind_direction_10m,wind_gusts_10m&hourly=temperature_2m,precipitation_probability,precipitation,weather_code,wind_speed_10m,wind_direction_10m,wind_gusts_10m,is_day,surface_pressure,pressure_msl,relative_humidity_2m,cloud_cover&timeformat=unixtime&past_hours=0&forecast_days=1&forecast_hours=12&wind_speed_unit=ms"
val url = "https://api.open-meteo.com/v1/forecast?latitude=${lats}&longitude=${lons}&current=surface_pressure,pressure_msl,temperature_2m,relative_humidity_2m,precipitation,weather_code,cloud_cover,wind_speed_10m,wind_direction_10m,wind_gusts_10m&hourly=temperature_2m,precipitation_probability,precipitation,weather_code,wind_speed_10m,wind_direction_10m,wind_gusts_10m&timeformat=unixtime&past_hours=0&forecast_days=1&forecast_hours=12&wind_speed_unit=${windUnit.id}&precipitation_unit=${precipitationUnit.id}&temperature_unit=${temperatureUnit.id}"
Log.d(KarooHeadwindExtension.TAG, "Http request to ${url}...")
@ -77,7 +84,7 @@ class OpenMeteoWeatherProvider : WeatherProvider {
settings: HeadwindSettings,
profile: UserProfile?
): WeatherDataResponse {
val openMeteoResponse = makeOpenMeteoWeatherRequest(karooSystem, coordinates)
val openMeteoResponse = makeOpenMeteoWeatherRequest(karooSystem, coordinates, settings, profile)
val responseBody = openMeteoResponse.body?.let { String(it) } ?: throw WeatherProviderException(500, "Null response from OpenMeteo")
val weatherData = if (coordinates.size == 1) {

View File

@ -2,8 +2,6 @@ package de.timklge.karooheadwind.weatherprovider.openweathermap
import de.timklge.karooheadwind.weatherprovider.WeatherData
import kotlinx.serialization.Serializable
import java.time.Instant
import java.time.ZoneOffset
@Serializable
data class OpenWeatherMapForecastData(
@ -22,18 +20,9 @@ data class OpenWeatherMapForecastData(
val snow: Snow? = null,
val weather: List<Weather>
) {
fun toWeatherData(currentWeatherData: OpenWeatherMapWeatherData): WeatherData {
val dtInstant = Instant.ofEpochSecond(dt)
val sunriseInstant = Instant.ofEpochSecond(currentWeatherData.sunrise)
val sunsetInstant = Instant.ofEpochSecond(currentWeatherData.sunset)
val dtTime = dtInstant.atZone(ZoneOffset.UTC).toLocalTime()
val sunriseTime = sunriseInstant.atZone(ZoneOffset.UTC).toLocalTime()
val sunsetTime = sunsetInstant.atZone(ZoneOffset.UTC).toLocalTime()
return WeatherData(
fun toWeatherData(): WeatherData = WeatherData(
temperature = temp,
relativeHumidity = humidity,
relativeHumidity = humidity.toDouble(),
precipitation = rain?.h1 ?: 0.0,
cloudCover = clouds.toDouble(),
surfacePressure = pressure.toDouble(),
@ -45,8 +34,6 @@ data class OpenWeatherMapForecastData(
weather.firstOrNull()?.id ?: 800
),
time = dt,
isForecast = true,
isNight = dtTime.isBefore(sunriseTime) || dtTime.isAfter(sunsetTime)
isForecast = true
)
}
}

View File

@ -23,7 +23,7 @@ data class OpenWeatherMapWeatherData(
fun toWeatherData(): WeatherData = WeatherData(
temperature = temp,
relativeHumidity = humidity,
relativeHumidity = humidity.toDouble(),
precipitation = rain?.h1 ?: 0.0,
cloudCover = clouds.toDouble(),
surfacePressure = pressure.toDouble(),
@ -35,9 +35,6 @@ data class OpenWeatherMapWeatherData(
weather.firstOrNull()?.id ?: 800
),
time = dt,
isNight = let {
dt !in sunrise..<sunset
},
isForecast = false
)
}

View File

@ -14,17 +14,10 @@ data class OpenWeatherMapWeatherDataForLocation(
val current: OpenWeatherMapWeatherData,
val hourly: List<OpenWeatherMapForecastData>
){
fun toWeatherDataForLocation(distanceAlongRoute: Double?): WeatherDataForLocation {
return WeatherDataForLocation(
fun toWeatherDataForLocation(distanceAlongRoute: Double?): WeatherDataForLocation = WeatherDataForLocation(
current = current.toWeatherData(),
coords = GpsCoordinates(
lat,
lon,
bearing = null,
distanceAlongRoute = distanceAlongRoute
),
coords = GpsCoordinates(lat, lon, bearing = null, distanceAlongRoute = distanceAlongRoute),
timezone = timezone,
forecasts = hourly.map { it.toWeatherData(current) }
forecasts = hourly.map { it.toWeatherData() }
)
}
}

View File

@ -69,7 +69,7 @@ class OpenWeatherMapWeatherProvider(private val apiKey: String) : WeatherProvide
profile: UserProfile?
): WeatherDataResponse {
val response = makeOpenWeatherMapRequest(karooSystem, coordinates, apiKey)
val response = makeOpenWeatherMapRequest(karooSystem, coordinates, apiKey, profile)
val responseBody = response.body?.let { String(it) } ?: throw Exception("Null response from OpenWeatherMap")
val responses = mutableListOf<WeatherDataForLocation>()
@ -89,15 +89,21 @@ class OpenWeatherMapWeatherProvider(private val apiKey: String) : WeatherProvide
private suspend fun makeOpenWeatherMapRequest(
service: KarooSystemService,
coordinates: List<GpsCoordinates>,
apiKey: String
apiKey: String,
profile: UserProfile?
): HttpResponseState.Complete {
val response = callbackFlow {
// OpenWeatherMap only supports setting imperial or metric units for all measurements, not individually for distance / temperature
val unitsString = if (profile?.preferredUnit?.temperature == UserProfile.PreferredUnit.UnitType.IMPERIAL || profile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL) {
"imperial"
} else {
"metric"
}
val coordinate = coordinates.first()
// URL API 3.0 with onecall endpoint
val url = "https://api.openweathermap.org/data/3.0/onecall?lat=${coordinate.lat}&lon=${coordinate.lon}" +
"&appid=$apiKey&exclude=minutely,daily,alerts&units=metric"
"&appid=$apiKey&exclude=minutely,daily,alerts&units=${unitsString}"
Log.d(KarooHeadwindExtension.TAG, "Http request to OpenWeatherMap API 3.0: $url")

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -1,40 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="m106.54,56.4c-0.68,0 -1.35,0.05 -2.01,0.13 0.52,-1.75 0.81,-3.6 0.81,-5.52 0,-10.7 -8.67,-19.37 -19.37,-19.37 -4.55,0 -8.73,1.58 -12.04,4.21 -4.45,-8.44 -13.3,-14.2 -23.51,-14.2 -14.67,0 -26.56,11.89 -26.56,26.56 0,0.53 0.02,1.05 0.05,1.57 -11.06,1.36 -19.63,10.78 -19.63,22.2 0,12.22 9.81,22.15 21.98,22.36 4.08,7.08 12.2,11.94 21.59,12.01 9.08,0.07 17.03,-4.36 21.33,-11 3.47,3.79 8.44,6.19 13.99,6.19 7.94,0 14.74,-4.89 17.56,-11.81 1.82,0.65 3.76,1.03 5.8,1.03 9.49,0 17.18,-7.69 17.18,-17.18 0.01,-9.49 -7.68,-17.18 -17.17,-17.18z">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="36.79"
android:centerY="18.87"
android:gradientRadius="114.63"
android:type="radial">
<item android:offset="0.14" android:color="#FFE3F2FD"/>
<item android:offset="1" android:color="#FF90CAF9"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m47.86,105c-8.55,0 -16.33,-4.46 -20.3,-11.34 -0.26,-0.46 -0.75,-0.78 -1.27,-0.79 -11.31,-0.2 -20.51,-9.57 -20.51,-20.87 0,-10.52 7.87,-19.42 18.31,-20.7 0.79,-0.1 1.36,-0.79 1.31,-1.58 -0.03,-0.5 -0.05,-1 -0.05,-1.5 0,-13.82 11.24,-25.06 25.06,-25.06 9.33,0 17.83,5.13 22.18,13.4 0.21,0.4 0.58,0.68 1.02,0.77 0.1,0.02 0.21,0.03 0.31,0.03 0.34,0 0.67,-0.11 0.93,-0.33 3.2,-2.54 7.04,-3.89 11.11,-3.89 9.86,0 17.87,8.02 17.87,17.87 0,1.71 -0.25,3.42 -0.75,5.09 -0.14,0.48 -0.03,1.01 0.29,1.39 0.29,0.34 0.71,0.53 1.15,0.53 0.06,0 0.12,0 0.17,-0.01 0.68,-0.08 1.28,-0.12 1.83,-0.12 8.64,0 15.68,7.03 15.68,15.68s-7.03,15.68 -15.68,15.68c-1.78,0 -3.56,-0.32 -5.3,-0.94 -0.17,-0.06 -0.34,-0.09 -0.51,-0.09 -0.59,0 -1.15,0.35 -1.39,0.93 -2.7,6.61 -9.05,10.88 -16.18,10.88 -4.88,0 -9.58,-2.08 -12.89,-5.71 -0.29,-0.31 -0.69,-0.49 -1.11,-0.49h-0.12c-0.46,0.04 -0.88,0.36 -1.13,0.76 -4.13,6.37 -11.73,10.4 -19.85,10.4h-0.18z"
android:strokeAlpha="0.2"
android:fillAlpha="0.2">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="37.48"
android:centerY="20.46"
android:gradientRadius="111.26"
android:type="radial">
<item android:offset="0.14" android:color="#FFE3F2FD"/>
<item android:offset="1" android:color="#FF90CAF9"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m50.42,24.65c8.77,0 16.76,4.83 20.85,12.6 0.42,0.79 1.16,1.35 2.04,1.54 0.2,0.04 0.41,0.06 0.62,0.06 0.67,0 1.33,-0.23 1.87,-0.65 2.93,-2.33 6.45,-3.56 10.17,-3.56 9.03,0 16.37,7.34 16.37,16.37 0,1.57 -0.23,3.14 -0.68,4.67 -0.29,0.97 -0.07,2.01 0.58,2.78 0.57,0.68 1.42,1.07 2.3,1.07 0.12,0 0.23,-0.01 0.35,-0.02 0.62,-0.07 1.16,-0.11 1.66,-0.11 7.82,0 14.18,6.36 14.18,14.18s-6.36,14.18 -14.18,14.18c-1.61,0 -3.22,-0.29 -4.79,-0.85 -0.33,-0.12 -0.68,-0.18 -1.01,-0.18 -1.19,0 -2.3,0.71 -2.78,1.87 -2.47,6.04 -8.27,9.95 -14.79,9.95 -4.46,0 -8.75,-1.9 -11.78,-5.22 -0.57,-0.63 -1.38,-0.98 -2.22,-0.98 -0.08,0 -0.16,0 -0.25,0.01 -0.93,0.08 -1.77,0.4 -2.27,1.18 -3.86,5.94 -10.98,9.46 -18.6,9.46h-0.18c-8.02,0 -15.31,-3.92 -19.01,-10.34 -0.53,-0.91 -1.49,-1.4 -2.55,-1.42 -10.5,-0.18 -19.04,-8.81 -19.04,-19.3 0,-9.76 7.3,-18 16.99,-19.18 1.57,-0.19 2.72,-1.56 2.63,-3.14 -0.03,-0.53 -0.05,-0.98 -0.05,-1.4 0.01,-13 10.58,-23.57 23.57,-23.57m0,-3c-14.67,0 -26.56,11.89 -26.56,26.56 0,0.53 0.02,1.06 0.05,1.58 -11.06,1.36 -19.63,10.77 -19.63,22.19 0,12.22 9.81,21.97 21.98,22.17 4.08,7.08 12.2,11.84 21.59,11.84h0.21c8.99,0 16.85,-4.25 21.12,-10.84 3.47,3.8 8.45,6.29 14,6.29 7.94,0 14.74,-4.84 17.56,-11.77 1.82,0.65 3.76,1.05 5.8,1.05 9.49,0 17.18,-7.68 17.18,-17.16s-7.69,-17.17 -17.18,-17.17c-0.68,0 -1.35,0.05 -2.01,0.13 0.52,-1.75 0.81,-3.6 0.81,-5.52 0,-10.7 -8.67,-19.37 -19.37,-19.37 -4.55,0 -8.73,1.58 -12.04,4.21 -4.45,-8.43 -13.31,-14.19 -23.51,-14.19z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
</vector>

View File

@ -1,49 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="m106.97,39.56c-0.68,0 -1.35,0.05 -2,0.13 0.52,-1.75 0.81,-3.59 0.81,-5.51 0,-10.68 -8.66,-19.34 -19.34,-19.34 -4.55,0 -8.72,1.58 -12.02,4.21 -4.45,-8.43 -13.29,-14.18 -23.48,-14.18 -14.64,0 -26.52,11.87 -26.52,26.52 0,0.53 0.02,1.05 0.05,1.57 -11.03,1.35 -19.58,10.75 -19.58,22.15 0,12.2 9.79,22.11 21.94,22.32 4.07,7.07 12.18,11.92 21.55,11.99 9.06,0.07 17,-4.35 21.3,-10.98 3.46,3.79 8.43,6.18 13.96,6.18 7.93,0 14.71,-4.88 17.53,-11.79 1.81,0.65 3.76,1.02 5.79,1.02 9.47,0 17.15,-7.68 17.15,-17.15 0,-9.46 -7.68,-17.14 -17.14,-17.14z">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="59.47"
android:centerY="-5.18"
android:gradientRadius="120.22"
android:type="radial">
<item android:offset="0.26" android:color="#FFE3F2FD"/>
<item android:offset="0.92" android:color="#FF90CAF9"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m50.94,7.87c8.75,0 16.73,4.82 20.81,12.57 0.42,0.79 1.16,1.35 2.04,1.54 0.2,0.04 0.41,0.06 0.62,0.06 0.67,0 1.33,-0.23 1.87,-0.65 2.92,-2.32 6.43,-3.55 10.15,-3.55 9.01,0 16.34,7.33 16.34,16.34 0,1.56 -0.23,3.13 -0.68,4.66 -0.29,0.97 -0.07,2.01 0.58,2.78 0.57,0.68 1.42,1.07 2.3,1.07 0.12,0 0.23,-0.01 0.35,-0.02 0.62,-0.07 1.16,-0.11 1.66,-0.11 7.8,0 14.15,6.35 14.15,14.15s-6.35,14.15 -14.15,14.15c-1.61,0 -3.21,-0.29 -4.78,-0.85 -0.33,-0.12 -0.68,-0.18 -1.01,-0.18 -1.19,0 -2.3,0.71 -2.78,1.87 -2.46,6.03 -8.25,9.92 -14.76,9.92 -4.45,0 -8.74,-1.9 -11.76,-5.21 -0.57,-0.63 -1.38,-0.98 -2.22,-0.98 -0.08,0 -0.16,0 -0.25,0.01 -0.93,0.08 -1.77,0.37 -2.27,1.15 -3.85,5.93 -10.96,9.41 -18.56,9.41h-0.19c-8.01,0 -15.28,-3.87 -18.97,-10.28 -0.53,-0.91 -1.49,-1.38 -2.55,-1.4 -10.47,-0.18 -18.99,-8.79 -18.99,-19.26 0,-9.74 7.29,-17.95 16.95,-19.14 1.57,-0.19 2.72,-1.56 2.63,-3.14 -0.03,-0.52 -0.05,-0.98 -0.05,-1.4 0.01,-12.96 10.56,-23.51 23.52,-23.51m0,-3c-14.64,0 -26.52,11.87 -26.52,26.52 0,0.53 0.02,1.06 0.05,1.58 -11.03,1.35 -19.58,10.74 -19.58,22.14 0,12.2 9.79,21.89 21.94,22.1 4.07,7.07 12.17,11.79 21.55,11.79h0.21c8.98,0 16.82,-4.2 21.08,-10.78 3.46,3.79 8.43,6.29 13.98,6.29 7.93,0 14.71,-4.82 17.53,-11.74 1.81,0.65 3.76,1.05 5.79,1.05 9.47,0 17.15,-7.66 17.15,-17.13s-7.68,-17.14 -17.15,-17.14c-0.68,0 -1.35,0.05 -2,0.13 0.52,-1.75 0.81,-3.59 0.81,-5.51 0,-10.68 -8.66,-19.34 -19.34,-19.34 -4.55,0 -8.72,1.58 -12.02,4.21 -4.45,-8.42 -13.29,-14.17 -23.48,-14.17z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
<path
android:pathData="m91.26,111.28c-1.78,3.88 -6.36,5.58 -10.24,3.8 -3.88,-1.78 -5.58,-6.36 -3.8,-10.24 1.78,-3.88 13.36,-11.57 13.36,-11.57 0,0 2.46,14.13 0.68,18.01z"
android:fillColor="#64b5f6"/>
<path
android:pathData="m91.26,111.28c-1.78,3.88 -6.36,5.58 -10.24,3.8 -3.88,-1.78 -5.58,-6.36 -3.8,-10.24 1.78,-3.88 13.36,-11.57 13.36,-11.57 0,0 2.46,14.13 0.68,18.01z"
android:fillColor="#90caf9"/>
<path
android:pathData="m50.84,113.15c-1.78,3.88 -6.36,5.58 -10.24,3.8 -3.88,-1.78 -5.58,-6.36 -3.8,-10.24 1.78,-3.88 13.36,-11.57 13.36,-11.57 0,0 2.46,14.13 0.68,18.01z"
android:fillColor="#90caf9"/>
<path
android:pathData="m48.68,98.59c0.74,5.36 1.22,11.83 0.35,13.72 -0.93,2.03 -2.97,3.34 -5.21,3.34 -0.82,0 -1.62,-0.18 -2.38,-0.52 -2.87,-1.32 -4.13,-4.72 -2.82,-7.59 0.88,-1.91 5.78,-5.89 10.06,-8.95m1.48,-3.45c0,0 -11.58,7.7 -13.36,11.57 -1.78,3.88 -0.08,8.46 3.8,10.24 1.04,0.48 2.14,0.71 3.21,0.71 2.93,0 5.72,-1.67 7.02,-4.5 1.79,-3.89 -0.67,-18.02 -0.67,-18.02z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
<path
android:pathData="m89.1,96.72c0.74,5.36 1.22,11.83 0.35,13.72 -0.93,2.03 -2.97,3.34 -5.21,3.34 -0.82,0 -1.62,-0.18 -2.38,-0.52 -1.39,-0.64 -2.45,-1.78 -2.98,-3.21 -0.53,-1.43 -0.47,-2.99 0.16,-4.38 0.88,-1.91 5.78,-5.89 10.06,-8.95m1.48,-3.45c0,0 -11.58,7.7 -13.36,11.57 -1.78,3.88 -0.08,8.46 3.8,10.24 1.04,0.48 2.14,0.71 3.21,0.71 2.93,0 5.72,-1.67 7.02,-4.5 1.79,-3.89 -0.67,-18.02 -0.67,-18.02z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
<path
android:pathData="m89.1,96.72c0.74,5.36 1.22,11.83 0.35,13.72 -0.93,2.03 -2.97,3.34 -5.21,3.34 -0.82,0 -1.62,-0.18 -2.38,-0.52 -1.39,-0.64 -2.45,-1.78 -2.98,-3.21 -0.53,-1.43 -0.47,-2.99 0.16,-4.38 0.88,-1.91 5.78,-5.89 10.06,-8.95m1.48,-3.45c0,0 -11.58,7.7 -13.36,11.57 -1.78,3.88 -0.08,8.46 3.8,10.24 1.04,0.48 2.14,0.71 3.21,0.71 2.93,0 5.72,-1.67 7.02,-4.5 1.79,-3.89 -0.67,-18.02 -0.67,-18.02z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
</vector>

View File

@ -1,44 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="m51.04,121.76 l13.8,-24.27c0.39,-0.68 -0.1,-1.53 -0.89,-1.54l-26.19,-0.1c-0.95,0 -1.39,-1.17 -0.68,-1.8l34.24,-30.28c0.85,-0.75 2.12,0.23 1.6,1.24l-10.37,20.07c-0.35,0.68 0.14,1.5 0.91,1.5l26.78,0.17c0.94,0.01 1.38,1.16 0.68,1.79l-38.3,34.49c-0.87,0.79 -2.16,-0.25 -1.58,-1.27z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="59.01"
android:startY="73.87"
android:endX="71.67"
android:endY="118.12"
android:type="linear">
<item android:offset="0" android:color="#FFFBC02D"/>
<item android:offset="1" android:color="#FFFFEB3B"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m65.41,73.01 l-5.52,10.69c-0.65,1.25 -0.6,2.72 0.13,3.93s2.01,1.94 3.42,1.94l21.69,0.14 -25.47,22.93 7.78,-13.68c0.71,-1.24 0.7,-2.78 -0.02,-4.01 -0.71,-1.23 -2.05,-2 -3.47,-2.01l-21.01,-0.08zM71.99,63.51c-0.23,0 -0.46,0.08 -0.67,0.27l-34.24,30.28c-0.71,0.63 -0.27,1.8 0.68,1.8l26.19,0.1c0.79,0 1.28,0.85 0.89,1.54l-13.8,24.27c-0.44,0.77 0.19,1.55 0.9,1.55 0.23,0 0.47,-0.08 0.68,-0.27l38.3,-34.49c0.7,-0.63 0.26,-1.79 -0.68,-1.79l-26.78,-0.17c-0.77,-0.01 -1.26,-0.82 -0.91,-1.5l10.36,-20.06c0.4,-0.79 -0.22,-1.53 -0.92,-1.53z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
<path
android:pathData="m103.18,36.62c-0.63,0 -1.24,0.05 -1.85,0.12 0.48,-1.61 0.74,-3.32 0.74,-5.08 0,-9.85 -7.99,-17.84 -17.84,-17.84 -4.19,0 -8.04,1.46 -11.09,3.88 -4.09,-7.78 -12.25,-13.08 -21.65,-13.08 -13.51,0 -24.46,10.95 -24.46,24.46 0,0.49 0.02,0.97 0.05,1.45 -10.19,1.24 -18.07,9.91 -18.07,20.43 0,11.26 9.03,20.4 20.24,20.59 3.76,6.52 11.23,10.99 19.88,11.06 8.36,0.06 15.69,-4.01 19.65,-10.13 3.19,3.49 7.78,5.7 12.88,5.7 7.31,0 13.57,-4.5 16.18,-10.88 1.67,0.6 3.47,0.95 5.34,0.95 8.74,0 15.82,-7.08 15.82,-15.82 -0.01,-8.73 -7.09,-15.81 -15.82,-15.81z">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="59.36"
android:centerY="-4.66"
android:gradientRadius="110.91"
android:type="radial">
<item android:offset="0.26" android:color="#FFE3F2FD"/>
<item android:offset="0.92" android:color="#FF90CAF9"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m51.49,7.62c7.99,0 15.27,4.4 19,11.47 0.42,0.79 1.16,1.35 2.04,1.54 0.2,0.04 0.41,0.06 0.62,0.06 0.67,0 1.33,-0.23 1.87,-0.65 2.65,-2.11 5.84,-3.23 9.22,-3.23 8.18,0 14.84,6.66 14.84,14.84 0,1.42 -0.21,2.84 -0.62,4.23 -0.29,0.97 -0.07,2.01 0.58,2.78 0.57,0.68 1.42,1.07 2.3,1.07 0.12,0 0.23,-0.01 0.35,-0.02 0.56,-0.07 1.05,-0.1 1.5,-0.1 7.07,0 12.82,5.75 12.82,12.82s-5.75,12.82 -12.82,12.82c-1.45,0 -2.91,-0.26 -4.33,-0.77 -0.33,-0.12 -0.68,-0.18 -1.01,-0.18 -1.19,0 -2.3,0.71 -2.78,1.87 -2.23,5.47 -7.49,9.01 -13.4,9.01 -4.04,0 -7.93,-1.72 -10.68,-4.73 -0.57,-0.63 -1.38,-0.98 -2.22,-0.98 -0.08,0 -0.16,0 -0.25,0.01 -0.93,0.08 -1.77,0.77 -2.27,1.55 -3.51,5.42 -9.99,8.97 -16.93,8.97h-0.17c-7.31,0 -13.94,-3.91 -17.3,-9.76 -0.53,-0.91 -1.49,-1.58 -2.55,-1.6 -9.54,-0.16 -17.29,-8.1 -17.29,-17.63 0,-8.87 6.64,-16.4 15.44,-17.48 1.57,-0.19 2.72,-1.59 2.63,-3.17 -0.03,-0.48 -0.04,-0.9 -0.04,-1.29 -0.01,-11.82 9.62,-21.45 21.45,-21.45m0,-3c-13.51,0 -24.46,10.95 -24.46,24.46 0,0.49 0.02,0.98 0.05,1.46 -10.18,1.24 -18.07,9.91 -18.07,20.42 0,11.26 9.03,20.58 20.24,20.77 3.75,6.53 11.23,11.27 19.88,11.27h0.19c8.28,0 15.51,-4.26 19.45,-10.33 3.2,3.5 7.78,5.61 12.89,5.61 7.31,0 13.57,-4.55 16.18,-10.93 1.67,0.6 3.47,0.92 5.34,0.92 8.74,0 15.82,-7.09 15.82,-15.83s-7.08,-15.82 -15.82,-15.82c-0.63,0 -1.24,0.04 -1.85,0.11 0.48,-1.61 0.74,-3.32 0.74,-5.08 0,-9.85 -7.99,-17.84 -17.84,-17.84 -4.19,0 -8.04,1.46 -11.09,3.88 -4.09,-7.77 -12.25,-13.07 -21.65,-13.07z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
</vector>

View File

@ -1,92 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="m51.04,121.76 l13.8,-24.27c0.39,-0.68 -0.1,-1.53 -0.89,-1.54l-26.19,-0.1c-0.95,0 -1.39,-1.17 -0.68,-1.8l34.24,-30.28c0.85,-0.75 2.12,0.23 1.6,1.24l-10.37,20.07c-0.35,0.68 0.14,1.5 0.91,1.5l26.78,0.17c0.94,0.01 1.38,1.16 0.68,1.79l-38.3,34.49c-0.87,0.79 -2.16,-0.25 -1.58,-1.27z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="59.01"
android:startY="73.87"
android:endX="71.67"
android:endY="118.12"
android:type="linear">
<item android:offset="0" android:color="#FFFBC02D"/>
<item android:offset="1" android:color="#FFFFEB3B"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m65.41,73.01 l-5.52,10.69c-0.65,1.25 -0.6,2.72 0.13,3.93s2.01,1.94 3.42,1.94l21.69,0.14 -25.47,22.93 7.78,-13.68c0.71,-1.24 0.7,-2.78 -0.02,-4.01 -0.71,-1.23 -2.05,-2 -3.47,-2.01l-21.01,-0.08zM71.99,63.51c-0.23,0 -0.46,0.08 -0.67,0.27l-34.24,30.28c-0.71,0.63 -0.27,1.8 0.68,1.8l26.19,0.1c0.79,0 1.28,0.85 0.89,1.54l-13.8,24.27c-0.44,0.77 0.19,1.55 0.9,1.55 0.23,0 0.47,-0.08 0.68,-0.27l38.3,-34.49c0.7,-0.63 0.26,-1.79 -0.68,-1.79l-26.78,-0.17c-0.77,-0.01 -1.26,-0.82 -0.91,-1.5l10.36,-20.06c0.4,-0.79 -0.22,-1.53 -0.92,-1.53z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
<path
android:pathData="m20.02,85.12c-1.44,3.13 -5.14,4.51 -8.28,3.07 -3.13,-1.44 -4.51,-5.14 -3.07,-8.28s10.8,-9.36 10.8,-9.36 1.99,11.43 0.55,14.57z"
android:fillColor="#64b5f6"/>
<path
android:pathData="m23.71,107.43c-1.44,3.13 -5.14,4.51 -8.28,3.07 -3.13,-1.44 -4.51,-5.14 -3.07,-8.28 1.44,-3.13 10.8,-9.36 10.8,-9.36s1.99,11.43 0.55,14.57z"
android:fillColor="#90caf9"/>
<path
android:pathData="m44.6,119.67c-1.44,3.13 -5.14,4.51 -8.28,3.07 -3.13,-1.44 -4.51,-5.14 -3.07,-8.28s10.8,-9.36 10.8,-9.36 1.99,11.43 0.55,14.57z"
android:fillColor="#90caf9"/>
<path
android:pathData="m112.91,85.12c-1.44,3.13 -5.14,4.51 -8.28,3.07 -3.13,-1.44 -4.51,-5.14 -3.07,-8.28 1.44,-3.13 10.8,-9.36 10.8,-9.36s1.98,11.43 0.55,14.57z"
android:fillColor="#64b5f6"/>
<path
android:pathData="m88.84,119.67c-1.44,3.13 -5.14,4.51 -8.28,3.07 -3.13,-1.44 -4.51,-5.14 -3.07,-8.28 1.44,-3.13 10.8,-9.36 10.8,-9.36s1.99,11.43 0.55,14.57z"
android:fillColor="#90caf9"/>
<path
android:pathData="m116.01,107.46c-1.44,3.13 -5.14,4.51 -8.28,3.07 -3.13,-1.44 -4.51,-5.14 -3.07,-8.28 1.44,-3.13 10.8,-9.36 10.8,-9.36s1.99,11.44 0.55,14.57z"
android:fillColor="#90caf9"/>
<path
android:pathData="m17.97,74.03c0.58,4.44 0.81,8.97 0.23,10.25 -0.69,1.5 -2.21,2.48 -3.86,2.48 -0.61,0 -1.2,-0.13 -1.77,-0.39 -1.03,-0.47 -1.81,-1.32 -2.21,-2.38 -0.39,-1.06 -0.35,-2.22 0.12,-3.25 0.65,-1.41 4.21,-4.33 7.49,-6.71m1.5,-3.47s-9.36,6.22 -10.8,9.36c-1.44,3.13 -0.06,6.84 3.07,8.28 0.84,0.39 1.73,0.57 2.6,0.57 2.37,0 4.63,-1.35 5.68,-3.64 1.44,-3.15 -0.55,-14.57 -0.55,-14.57z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
<path
android:pathData="m21.66,96.34c0.58,4.44 0.81,8.97 0.23,10.25 -0.69,1.5 -2.21,2.48 -3.86,2.48 -0.61,0 -1.2,-0.13 -1.77,-0.39 -2.13,-0.98 -3.06,-3.5 -2.09,-5.63 0.65,-1.41 4.21,-4.33 7.49,-6.71m1.5,-3.48s-9.36,6.22 -10.8,9.36c-1.44,3.13 -0.06,6.84 3.07,8.28 0.84,0.39 1.73,0.57 2.6,0.57 2.37,0 4.63,-1.35 5.68,-3.64 1.44,-3.14 -0.55,-14.57 -0.55,-14.57z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
<path
android:pathData="m42.56,108.58c0.58,4.44 0.81,8.98 0.23,10.25 -0.69,1.5 -2.21,2.48 -3.86,2.48 -0.61,0 -1.2,-0.13 -1.77,-0.39 -1.03,-0.47 -1.81,-1.32 -2.21,-2.38 -0.39,-1.06 -0.35,-2.22 0.12,-3.25 0.65,-1.41 4.2,-4.33 7.49,-6.71m1.49,-3.48s-9.36,6.22 -10.8,9.36 -0.06,6.84 3.07,8.28c0.84,0.39 1.73,0.57 2.6,0.57 2.37,0 4.63,-1.35 5.68,-3.64 1.44,-3.14 -0.55,-14.57 -0.55,-14.57z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
<path
android:pathData="m110.86,74.03c0.58,4.44 0.81,8.97 0.23,10.25 -0.69,1.5 -2.21,2.48 -3.86,2.48 -0.61,0 -1.2,-0.13 -1.77,-0.39 -1.03,-0.47 -1.81,-1.32 -2.21,-2.38 -0.39,-1.06 -0.35,-2.22 0.12,-3.25 0.65,-1.41 4.21,-4.33 7.49,-6.71m1.5,-3.47s-9.36,6.22 -10.8,9.36c-1.44,3.13 -0.06,6.84 3.07,8.28 0.84,0.39 1.73,0.57 2.6,0.57 2.37,0 4.63,-1.35 5.68,-3.64 1.43,-3.15 -0.55,-14.57 -0.55,-14.57z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
<path
android:pathData="m86.79,108.58c0.58,4.44 0.81,8.97 0.23,10.25 -0.69,1.5 -2.21,2.48 -3.86,2.48 -0.61,0 -1.2,-0.13 -1.77,-0.39 -1.03,-0.47 -1.81,-1.32 -2.21,-2.38 -0.39,-1.06 -0.35,-2.22 0.12,-3.25 0.65,-1.41 4.21,-4.33 7.49,-6.71m1.5,-3.48s-9.36,6.22 -10.8,9.36c-1.44,3.13 -0.06,6.84 3.07,8.28 0.84,0.39 1.73,0.57 2.6,0.57 2.37,0 4.63,-1.35 5.68,-3.64 1.44,-3.14 -0.55,-14.57 -0.55,-14.57z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
<path
android:pathData="m113.96,96.37c0.58,4.44 0.81,8.98 0.23,10.25 -0.69,1.5 -2.21,2.48 -3.86,2.48 -0.61,0 -1.2,-0.13 -1.77,-0.39 -2.13,-0.98 -3.06,-3.5 -2.09,-5.63 0.65,-1.4 4.21,-4.33 7.49,-6.71m1.5,-3.47s-9.36,6.22 -10.8,9.36c-1.44,3.13 -0.06,6.84 3.07,8.28 0.84,0.39 1.73,0.57 2.6,0.57 2.37,0 4.63,-1.35 5.68,-3.64 1.44,-3.14 -0.55,-14.57 -0.55,-14.57z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
<path
android:pathData="m103.18,36.62c-0.63,0 -1.24,0.05 -1.85,0.12 0.48,-1.61 0.74,-3.32 0.74,-5.08 0,-9.85 -7.99,-17.84 -17.84,-17.84 -4.19,0 -8.04,1.46 -11.09,3.88 -4.09,-7.78 -12.25,-13.08 -21.65,-13.08 -13.51,0 -24.46,10.95 -24.46,24.46 0,0.49 0.02,0.97 0.05,1.45 -10.19,1.24 -18.07,9.91 -18.07,20.43 0,11.26 9.03,20.4 20.24,20.59 3.76,6.52 11.23,10.99 19.88,11.06 8.36,0.06 15.69,-4.01 19.65,-10.13 3.19,3.49 7.78,5.7 12.88,5.7 7.31,0 13.57,-4.5 16.18,-10.88 1.67,0.6 3.47,0.95 5.34,0.95 8.74,0 15.82,-7.08 15.82,-15.82 -0.01,-8.73 -7.09,-15.81 -15.82,-15.81z">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="59.36"
android:centerY="-4.66"
android:gradientRadius="110.91"
android:type="radial">
<item android:offset="0.26" android:color="#FFE3F2FD"/>
<item android:offset="0.92" android:color="#FF90CAF9"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m51.49,7.62c7.99,0 15.27,4.4 19,11.47 0.42,0.79 1.16,1.35 2.04,1.54 0.2,0.04 0.41,0.06 0.62,0.06 0.67,0 1.33,-0.23 1.87,-0.65 2.65,-2.11 5.84,-3.23 9.22,-3.23 8.18,0 14.84,6.66 14.84,14.84 0,1.42 -0.21,2.84 -0.62,4.23 -0.29,0.97 -0.07,2.01 0.58,2.78 0.57,0.68 1.42,1.07 2.3,1.07 0.12,0 0.23,-0.01 0.35,-0.02 0.56,-0.07 1.05,-0.1 1.5,-0.1 7.07,0 12.82,5.75 12.82,12.82s-5.75,12.82 -12.82,12.82c-1.45,0 -2.91,-0.26 -4.33,-0.77 -0.33,-0.12 -0.68,-0.18 -1.01,-0.18 -1.19,0 -2.3,0.71 -2.78,1.87 -2.23,5.47 -7.49,9.01 -13.4,9.01 -4.04,0 -7.93,-1.72 -10.68,-4.73 -0.57,-0.63 -1.38,-0.98 -2.22,-0.98 -0.08,0 -0.16,0 -0.25,0.01 -0.93,0.08 -1.77,0.77 -2.27,1.55 -3.51,5.42 -9.99,8.97 -16.93,8.97h-0.17c-7.31,0 -13.94,-3.91 -17.3,-9.76 -0.53,-0.91 -1.49,-1.58 -2.55,-1.6 -9.54,-0.16 -17.29,-8.1 -17.29,-17.63 0,-8.87 6.64,-16.4 15.44,-17.48 1.57,-0.19 2.72,-1.59 2.63,-3.17 -0.03,-0.48 -0.04,-0.9 -0.04,-1.29 -0.01,-11.82 9.62,-21.45 21.45,-21.45m0,-3c-13.51,0 -24.46,10.95 -24.46,24.46 0,0.49 0.02,0.98 0.05,1.46 -10.18,1.24 -18.07,9.91 -18.07,20.42 0,11.26 9.03,20.58 20.24,20.77 3.75,6.53 11.23,11.27 19.88,11.27h0.19c8.28,0 15.51,-4.26 19.45,-10.33 3.2,3.5 7.78,5.61 12.89,5.61 7.31,0 13.57,-4.55 16.18,-10.93 1.67,0.6 3.47,0.92 5.34,0.92 8.74,0 15.82,-7.09 15.82,-15.83s-7.08,-15.82 -15.82,-15.82c-0.63,0 -1.24,0.04 -1.85,0.11 0.48,-1.61 0.74,-3.32 0.74,-5.08 0,-9.85 -7.99,-17.84 -17.84,-17.84 -4.19,0 -8.04,1.46 -11.09,3.88 -4.09,-7.77 -12.25,-13.07 -21.65,-13.07z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
</vector>

View File

@ -1,73 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="m106.97,39.56c-0.68,0 -1.35,0.05 -2,0.13 0.52,-1.75 0.81,-3.59 0.81,-5.51 0,-10.68 -8.66,-19.34 -19.34,-19.34 -4.55,0 -8.72,1.58 -12.02,4.21 -4.45,-8.43 -13.29,-14.18 -23.48,-14.18 -14.64,0 -26.52,11.87 -26.52,26.52 0,0.53 0.02,1.05 0.05,1.57 -11.03,1.35 -19.58,10.75 -19.58,22.15 0,12.2 9.79,22.11 21.94,22.32 4.07,7.07 12.18,11.92 21.55,11.99 9.06,0.07 17,-4.35 21.3,-10.98 3.46,3.79 8.43,6.18 13.96,6.18 7.93,0 14.71,-4.88 17.53,-11.79 1.81,0.65 3.76,1.02 5.79,1.02 9.47,0 17.15,-7.68 17.15,-17.15 0,-9.46 -7.68,-17.14 -17.14,-17.14z">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="59.47"
android:centerY="-5.18"
android:gradientRadius="120.22"
android:type="radial">
<item android:offset="0.26" android:color="#FFE3F2FD"/>
<item android:offset="0.92" android:color="#FF90CAF9"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m50.94,7.87c8.75,0 16.73,4.82 20.81,12.57 0.42,0.79 1.16,1.35 2.04,1.54 0.2,0.04 0.41,0.06 0.62,0.06 0.67,0 1.33,-0.23 1.87,-0.65 2.92,-2.32 6.43,-3.55 10.15,-3.55 9.01,0 16.34,7.33 16.34,16.34 0,1.56 -0.23,3.13 -0.68,4.66 -0.29,0.97 -0.07,2.01 0.58,2.78 0.57,0.68 1.42,1.07 2.3,1.07 0.12,0 0.23,-0.01 0.35,-0.02 0.62,-0.07 1.16,-0.11 1.66,-0.11 7.8,0 14.15,6.35 14.15,14.15s-6.35,14.15 -14.15,14.15c-1.61,0 -3.21,-0.29 -4.78,-0.85 -0.33,-0.12 -0.68,-0.18 -1.01,-0.18 -1.19,0 -2.3,0.71 -2.78,1.87 -2.46,6.03 -8.25,9.92 -14.76,9.92 -4.45,0 -8.74,-1.9 -11.76,-5.21 -0.57,-0.63 -1.38,-0.98 -2.22,-0.98 -0.08,0 -0.16,0 -0.25,0.01 -0.93,0.08 -1.77,0.37 -2.27,1.15 -3.85,5.93 -10.96,9.41 -18.56,9.41h-0.19c-8.01,0 -15.28,-3.87 -18.97,-10.28 -0.53,-0.91 -1.49,-1.38 -2.55,-1.4 -10.47,-0.18 -18.99,-8.79 -18.99,-19.26 0,-9.74 7.29,-17.95 16.95,-19.14 1.57,-0.19 2.72,-1.56 2.63,-3.14 -0.03,-0.52 -0.05,-0.98 -0.05,-1.4 0.01,-12.96 10.56,-23.51 23.52,-23.51m0,-3c-14.64,0 -26.52,11.87 -26.52,26.52 0,0.53 0.02,1.06 0.05,1.58 -11.03,1.35 -19.58,10.74 -19.58,22.14 0,12.2 9.79,21.89 21.94,22.1 4.07,7.07 12.17,11.79 21.55,11.79h0.21c8.98,0 16.82,-4.2 21.08,-10.78 3.46,3.79 8.43,6.29 13.98,6.29 7.93,0 14.71,-4.82 17.53,-11.74 1.81,0.65 3.76,1.05 5.79,1.05 9.47,0 17.15,-7.66 17.15,-17.13s-7.68,-17.14 -17.15,-17.14c-0.68,0 -1.35,0.05 -2,0.13 0.52,-1.75 0.81,-3.59 0.81,-5.51 0,-10.68 -8.66,-19.34 -19.34,-19.34 -4.55,0 -8.72,1.58 -12.02,4.21 -4.45,-8.42 -13.29,-14.17 -23.48,-14.17z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
<path
android:pathData="m97.36,119.15c-1.78,3.88 -6.36,5.58 -10.24,3.8s-5.58,-6.36 -3.8,-10.24 13.36,-11.57 13.36,-11.57 2.46,14.13 0.68,18.01z"
android:fillColor="#64b5f6"/>
<path
android:pathData="m97.36,119.15c-1.78,3.88 -6.36,5.58 -10.24,3.8s-5.58,-6.36 -3.8,-10.24 13.36,-11.57 13.36,-11.57 2.46,14.13 0.68,18.01z"
android:fillColor="#90caf9"/>
<path
android:pathData="m25.59,96.63c-1.78,3.88 -6.36,5.58 -10.24,3.8s-5.58,-6.36 -3.8,-10.24 13.36,-11.57 13.36,-11.57 2.46,14.13 0.68,18.01z"
android:fillColor="#64b5f6"/>
<path
android:pathData="m23.42,82.08c0.74,5.36 1.22,11.83 0.35,13.72 -0.93,2.03 -2.97,3.34 -5.21,3.34 -0.82,0 -1.62,-0.18 -2.38,-0.52 -1.39,-0.64 -2.45,-1.78 -2.98,-3.21s-0.47,-2.99 0.16,-4.38c0.89,-1.91 5.79,-5.9 10.06,-8.95m1.49,-3.46s-11.58,7.7 -13.36,11.57c-1.78,3.88 -0.08,8.46 3.8,10.24 1.04,0.48 2.14,0.71 3.21,0.71 2.93,0 5.72,-1.67 7.02,-4.5 1.79,-3.89 -0.67,-18.02 -0.67,-18.02z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
<path
android:pathData="m39.66,119.15c-1.78,3.88 -6.36,5.58 -10.24,3.8s-5.58,-6.36 -3.8,-10.24 13.36,-11.57 13.36,-11.57 2.46,14.13 0.68,18.01z"
android:fillColor="#90caf9"/>
<path
android:pathData="m37.5,104.59c0.74,5.36 1.22,11.83 0.35,13.72 -0.93,2.03 -2.97,3.34 -5.21,3.34 -0.82,0 -1.62,-0.18 -2.38,-0.52 -2.87,-1.32 -4.13,-4.72 -2.82,-7.59 0.88,-1.91 5.78,-5.89 10.06,-8.95m1.48,-3.45s-11.58,7.7 -13.36,11.57c-1.78,3.88 -0.08,8.46 3.8,10.24 1.04,0.48 2.14,0.71 3.21,0.71 2.93,0 5.72,-1.67 7.02,-4.5 1.79,-3.89 -0.67,-18.02 -0.67,-18.02z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
<path
android:pathData="m69.22,107.89c-1.78,3.88 -6.36,5.58 -10.24,3.8s-5.58,-6.36 -3.8,-10.24 13.36,-11.57 13.36,-11.57 2.46,14.13 0.68,18.01z"
android:fillColor="#64b5f6"/>
<path
android:pathData="m67.05,93.33c0.74,5.36 1.22,11.83 0.35,13.72 -0.93,2.03 -2.97,3.34 -5.21,3.34 -0.82,0 -1.62,-0.18 -2.38,-0.52 -2.87,-1.32 -4.13,-4.72 -2.82,-7.59 0.88,-1.91 5.78,-5.89 10.06,-8.95m1.49,-3.45s-11.58,7.7 -13.36,11.57c-1.78,3.88 -0.08,8.46 3.8,10.24 1.04,0.48 2.14,0.71 3.22,0.71 2.93,0 5.72,-1.67 7.02,-4.5 1.78,-3.89 -0.68,-18.02 -0.68,-18.02z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
<path
android:pathData="m115.66,96.63c-1.78,3.88 -6.36,5.58 -10.24,3.8s-5.58,-6.36 -3.8,-10.24 13.36,-11.57 13.36,-11.57 2.46,14.13 0.68,18.01z"
android:fillColor="#64b5f6"/>
<path
android:pathData="m113.49,82.08c0.74,5.36 1.22,11.83 0.35,13.72 -0.93,2.03 -2.97,3.34 -5.21,3.34 -0.82,0 -1.62,-0.18 -2.38,-0.52 -2.87,-1.32 -4.13,-4.72 -2.82,-7.59 0.88,-1.91 5.78,-5.9 10.06,-8.95m1.49,-3.46s-11.58,7.7 -13.36,11.57c-1.78,3.88 -0.08,8.46 3.8,10.24 1.04,0.48 2.14,0.71 3.21,0.71 2.93,0 5.72,-1.67 7.02,-4.5 1.79,-3.89 -0.67,-18.02 -0.67,-18.02z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
<path
android:pathData="m95.2,104.59c0.74,5.36 1.22,11.83 0.35,13.72 -0.93,2.03 -2.97,3.34 -5.21,3.34 -0.82,0 -1.62,-0.18 -2.38,-0.52 -1.39,-0.64 -2.45,-1.78 -2.98,-3.21s-0.47,-2.99 0.16,-4.38c0.88,-1.91 5.78,-5.89 10.06,-8.95m1.48,-3.45s-11.58,7.7 -13.36,11.57c-1.78,3.88 -0.08,8.46 3.8,10.24 1.04,0.48 2.14,0.71 3.21,0.71 2.93,0 5.72,-1.67 7.02,-4.5 1.79,-3.89 -0.67,-18.02 -0.67,-18.02z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
<path
android:pathData="m95.2,104.59c0.74,5.36 1.22,11.83 0.35,13.72 -0.93,2.03 -2.97,3.34 -5.21,3.34 -0.82,0 -1.62,-0.18 -2.38,-0.52 -1.39,-0.64 -2.45,-1.78 -2.98,-3.21s-0.47,-2.99 0.16,-4.38c0.88,-1.91 5.78,-5.89 10.06,-8.95m1.48,-3.45s-11.58,7.7 -13.36,11.57c-1.78,3.88 -0.08,8.46 3.8,10.24 1.04,0.48 2.14,0.71 3.21,0.71 2.93,0 5.72,-1.67 7.02,-4.5 1.79,-3.89 -0.67,-18.02 -0.67,-18.02z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
</vector>

View File

@ -1,55 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="m106.97,37.87c-0.68,0 -1.35,0.05 -2,0.12 0.52,-1.71 0.81,-3.51 0.81,-5.38 0,-10.43 -8.66,-18.88 -19.34,-18.88 -4.55,0 -8.72,1.54 -12.02,4.11 -4.45,-8.23 -13.29,-13.84 -23.48,-13.84 -14.64,0 -26.51,11.59 -26.51,25.89 0,0.51 0.02,1.02 0.05,1.53 -11.04,1.32 -19.59,10.5 -19.59,21.63 0,11.91 9.79,21.59 21.94,21.79 4.07,6.9 12.18,11.63 21.55,11.7 9.06,0.07 17,-4.25 21.3,-10.72 3.46,3.7 8.43,6.03 13.96,6.03 7.93,0 14.71,-4.76 17.53,-11.51 1.81,0.64 3.76,1 5.79,1 9.47,0 17.15,-7.49 17.15,-16.74 0,-9.24 -7.68,-16.73 -17.14,-16.73z">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="59.47"
android:centerY="-5.81"
android:gradientRadius="120.22"
android:type="radial">
<item android:offset="0.26" android:color="#FFE3F2FD"/>
<item android:offset="0.92" android:color="#FF90CAF9"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m50.94,7c8.76,0 16.74,4.7 20.83,12.26 0.42,0.78 1.16,1.33 2.02,1.51 0.21,0.04 0.41,0.06 0.62,0.06 0.66,0 1.31,-0.22 1.84,-0.63 2.93,-2.27 6.45,-3.48 10.18,-3.48 9.01,0 16.34,7.12 16.34,15.88 0,1.51 -0.23,3.03 -0.68,4.51 -0.29,0.97 -0.08,2.02 0.56,2.79 0.57,0.69 1.42,1.08 2.3,1.08 0.11,0 0.23,-0.01 0.34,-0.02 0.62,-0.07 1.16,-0.1 1.66,-0.1 7.8,0 14.15,6.16 14.15,13.74s-6.35,13.74 -14.15,13.74c-1.61,0 -3.23,-0.28 -4.8,-0.83 -0.33,-0.12 -0.66,-0.17 -0.99,-0.17 -1.18,0 -2.29,0.7 -2.77,1.84 -2.46,5.87 -8.25,9.67 -14.77,9.67 -4.46,0 -8.76,-1.86 -11.78,-5.09 -0.57,-0.61 -1.36,-0.95 -2.19,-0.95 -0.08,0 -0.17,0 -0.25,0.01 -0.91,0.08 -1.74,0.79 -2.25,1.56 -3.83,5.8 -10.95,9.62 -18.56,9.62h-0.19c-8.02,-1 -15.3,-4.21 -18.99,-10.46 -0.53,-0.9 -1.49,-1.57 -2.53,-1.59 -10.47,-0.18 -18.99,-8.66 -18.99,-18.84 0,-9.47 7.29,-17.51 16.95,-18.67 1.58,-0.19 2.73,-1.59 2.64,-3.17 -0.03,-0.51 -0.05,-0.96 -0.05,-1.37 0,-12.63 10.55,-22.9 23.51,-22.9m0,-3c-14.64,0 -26.51,11.59 -26.51,25.89 0,0.52 0.02,1.03 0.05,1.54 -11.04,1.32 -19.59,10.49 -19.59,21.62 0,11.91 9.79,21.8 21.94,22.01 4.07,6.9 12.17,10.94 21.55,11.94h0.21c8.98,0 16.82,-4.53 21.08,-10.96 3.46,3.7 8.43,5.93 13.98,5.93 7.93,0 14.71,-4.82 17.53,-11.57 1.81,0.64 3.76,0.97 5.79,0.97 9.47,0 17.15,-7.51 17.15,-16.75s-7.68,-16.75 -17.15,-16.75c-0.68,0 -1.35,0.05 -2,0.12 0.52,-1.71 0.81,-3.51 0.81,-5.38 0,-10.43 -8.66,-18.88 -19.34,-18.88 -4.55,0 -8.72,1.54 -12.02,4.11 -4.45,-8.23 -13.29,-13.84 -23.48,-13.84z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
<path
android:pathData="m21.61,104.5 l-0.63,0.02c-0.83,0.03 -1.52,-0.62 -1.55,-1.45l-0.67,-21.17c-0.03,-0.83 0.62,-1.52 1.45,-1.55l0.63,-0.02c0.83,-0.03 1.52,0.62 1.55,1.45l0.68,21.17c0.02,0.83 -0.63,1.52 -1.46,1.55z"
android:fillColor="#64b5f6"/>
<path
android:pathData="m30.95,95.56 l-6.15,-3.26 5.93,-3.64c0.87,-0.53 1.17,-1.73 0.67,-2.67s-1.61,-1.28 -2.48,-0.74l-8.09,4.97 -8.39,-4.44c-0.9,-0.48 -1.99,-0.07 -2.42,0.9 -0.44,0.97 -0.06,2.15 0.84,2.63l6.16,3.25 -5.93,3.64c-0.87,0.53 -1.17,1.73 -0.67,2.67s1.61,1.28 2.48,0.74l8.09,-4.97 8.39,4.44c0.9,0.48 1.99,0.07 2.42,-0.9s0.05,-2.15 -0.85,-2.62z"
android:fillColor="#64b5f6"/>
<path
android:pathData="m97.88,82.66 l0.36,-0.51c0.48,-0.67 1.42,-0.83 2.09,-0.35l17.25,12.29c0.67,0.48 0.83,1.42 0.35,2.09l-0.36,0.51c-0.48,0.67 -1.42,0.83 -2.09,0.35l-17.25,-12.28c-0.67,-0.49 -0.83,-1.42 -0.35,-2.1z"
android:fillColor="#64b5f6"/>
<path
android:pathData="m99.33,95.51 l6.32,-2.92 -0.7,6.93c-0.1,1.01 0.67,1.98 1.72,2.16s1.99,-0.5 2.09,-1.52l0.95,-9.44 8.62,-3.98c0.93,-0.43 1.26,-1.53 0.75,-2.47s-1.67,-1.35 -2.6,-0.92l-6.32,2.92 0.7,-6.93c0.1,-1.02 -0.67,-1.98 -1.72,-2.16s-1.99,0.5 -2.09,1.52l-0.95,9.44 -8.62,3.98c-0.93,0.43 -1.26,1.53 -0.75,2.47s1.68,1.35 2.6,0.92z"
android:fillColor="#64b5f6"/>
<path
android:pathData="m62.17,112.31 l-0.61,-0.13c-0.81,-0.17 -1.33,-0.97 -1.15,-1.78l4.46,-20.71c0.17,-0.81 0.97,-1.33 1.78,-1.15l0.61,0.13c0.81,0.17 1.33,0.97 1.15,1.78l-4.46,20.71c-0.17,0.81 -0.97,1.33 -1.78,1.15z"
android:fillColor="#64b5f6"/>
<path
android:pathData="m73.4,105.89 l-5.19,-4.65 6.64,-2.1c0.97,-0.31 1.55,-1.4 1.3,-2.43 -0.26,-1.04 -1.25,-1.63 -2.22,-1.32l-9.05,2.86 -7.07,-6.33c-0.76,-0.68 -1.91,-0.55 -2.57,0.29s-0.58,2.07 0.18,2.75l5.19,4.65 -6.64,2.1c-0.97,0.31 -1.55,1.4 -1.3,2.43 0.25,1.04 1.25,1.63 2.22,1.32l9.05,-2.86 7.07,6.33c0.76,0.68 1.91,0.55 2.57,-0.29s0.58,-2.07 -0.18,-2.75z"
android:fillColor="#64b5f6"/>
<path
android:pathData="m32.23,117.52c-0.45,-0.45 -0.45,-1.18 0,-1.63l9.23,-9.23c0.45,-0.45 1.18,-0.45 1.63,0s0.45,1.18 0,1.63l-9.23,9.23c-0.45,0.45 -1.18,0.45 -1.63,0z"
android:fillColor="#90caf9"/>
<path
android:pathData="m40.57,118.1 l-1.17,-4.27 4.27,1.17c0.62,0.17 1.31,-0.21 1.53,-0.85s-0.11,-1.3 -0.73,-1.47l-5.82,-1.59 -1.59,-5.82c-0.17,-0.62 -0.83,-0.95 -1.47,-0.73s-1.02,0.91 -0.85,1.53l1.17,4.27 -4.27,-1.17c-0.62,-0.17 -1.31,0.21 -1.53,0.85s0.11,1.3 0.73,1.47l5.82,1.59 1.59,5.82c0.17,0.62 0.83,0.95 1.47,0.73s1.02,-0.9 0.85,-1.53z"
android:fillColor="#90caf9"/>
<path
android:pathData="m88.94,117.06c-0.58,-0.27 -0.83,-0.95 -0.56,-1.53l5.48,-11.85c0.27,-0.58 0.95,-0.83 1.53,-0.56s0.83,0.95 0.56,1.53l-5.48,11.85c-0.27,0.58 -0.95,0.83 -1.53,0.56z"
android:fillColor="#90caf9"/>
<path
android:pathData="m96.97,114.73 l-2.57,-3.6 4.41,-0.38c0.65,-0.06 1.16,-0.65 1.14,-1.33 -0.01,-0.68 -0.55,-1.18 -1.19,-1.13l-6.01,0.52 -3.5,-4.91c-0.38,-0.53 -1.11,-0.61 -1.63,-0.18s-0.65,1.2 -0.27,1.73l2.57,3.6 -4.41,0.38c-0.65,0.06 -1.16,0.65 -1.14,1.33 0.01,0.68 0.55,1.18 1.19,1.13l6.01,-0.52 3.5,4.91c0.38,0.53 1.11,0.61 1.63,0.18s0.64,-1.21 0.27,-1.73z"
android:fillColor="#90caf9"/>
</vector>

View File

@ -1,84 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="m89.47,28.93c-2.46,-9.66 -7.29,-18.09 -13.69,-24.84 20.75,5.42 38.01,21.66 43.67,43.91 8.29,32.59 -11.41,65.72 -43.99,74.01 -27.79,7.07 -55.96,-6.22 -68.8,-30.49 12.29,7.12 27.26,9.64 42.12,5.86 30.14,-7.65 48.36,-38.31 40.69,-68.45z">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="67.56"
android:centerY="24.47"
android:gradientRadius="96.59"
android:type="radial">
<item android:offset="0.28" android:color="#FFFFF157"/>
<item android:offset="0.52" android:color="#FFFEEE54"/>
<item android:offset="0.72" android:color="#FFFAE44A"/>
<item android:offset="0.9" android:color="#FFF4D538"/>
<item android:offset="1" android:color="#FFF0C92C"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m91.78,80.3c0.61,6.13 6.41,10.57 12.96,9.92 6.54,-0.65 11.35,-6.15 10.74,-12.28s-6.41,-10.57 -12.96,-9.92c-6.54,0.65 -11.35,6.15 -10.74,12.28z">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="103.31"
android:centerY="75.44"
android:gradientRadius="14.31"
android:type="radial">
<item android:offset="0.01" android:color="#FFE0A800"/>
<item android:offset="0.61" android:color="#1BE0A800"/>
<item android:offset="0.68" android:color="#00E0A800"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m89.42,103.89c1.77,3.82 6.29,5.48 10.09,3.72 3.8,-1.77 5.44,-6.29 3.67,-10.11s-6.29,-5.48 -10.09,-3.72c-3.8,1.77 -5.44,6.3 -3.67,10.11z">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="95.12"
android:centerY="92.32"
android:gradientRadius="13.45"
android:type="radial">
<item android:offset="0.01" android:color="#FFE0A800"/>
<item android:offset="0.61" android:color="#1BE0A800"/>
<item android:offset="0.68" android:color="#00E0A800"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m107.44,62.08c1.21,2.61 4.31,3.75 6.91,2.54s3.73,-4.31 2.51,-6.92c-1.21,-2.61 -4.31,-3.75 -6.91,-2.54 -2.6,1.2 -3.73,4.3 -2.51,6.92z">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="111.34"
android:centerY="54.15"
android:gradientRadius="9.2"
android:type="radial">
<item android:offset="0.01" android:color="#FFE0A800"/>
<item android:offset="0.61" android:color="#1BE0A800"/>
<item android:offset="0.68" android:color="#00E0A800"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M73.24,109.06m-8.97,0a8.97,8.97 0,1 1,17.94 0a8.97,8.97 0,1 1,-17.94 0">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="73.01"
android:centerY="105.52"
android:gradientRadius="15.24"
android:type="radial">
<item android:offset="0.01" android:color="#FFE0A800"/>
<item android:offset="0.61" android:color="#1BE0A800"/>
<item android:offset="0.68" android:color="#00E0A800"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m84.57,10.4c15.75,7.23 27.62,21.23 31.98,38.34 7.87,30.93 -10.89,62.5 -41.83,70.37 -4.7,1.19 -9.51,1.8 -14.31,1.8 -18.06,0 -34.78,-8.35 -45.62,-22.3 6.43,2.32 13.23,3.53 20.08,3.53 4.91,0 9.84,-0.62 14.65,-1.84 31.7,-8.06 50.93,-40.41 42.86,-72.11 -1.61,-6.33 -4.27,-12.35 -7.81,-17.79m-8.79,-6.31c6.41,6.75 11.23,15.18 13.69,24.84 7.67,30.14 -10.55,60.79 -40.69,68.46 -4.65,1.18 -9.32,1.75 -13.91,1.75 -10.06,0 -19.77,-2.72 -28.21,-7.61 10.51,19.87 31.31,32.38 53.75,32.38 4.97,0 10.01,-0.61 15.05,-1.89 32.59,-8.29 52.28,-41.43 43.99,-74.01 -5.66,-22.26 -22.92,-38.5 -43.67,-43.92z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
</vector>

View File

@ -1,23 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="m64.6,124c-20.1,0 -35.56,-19.48 -35.56,-36.18 0,-11.75 5.26,-25.36 12.68,-44.22 0.93,-2.78 2.16,-5.57 3.4,-8.66 3.54,-8.84 6.66,-18.42 11.41,-26.71 3.24,-5.66 11.46,-5.65 14.58,0.08 4.43,8.14 7.45,16.96 12.05,27.25 12.99,29.07 16.7,40.82 16.7,52.57 0.3,16.39 -15.47,35.87 -35.26,35.87z">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="67.14"
android:centerY="19.26"
android:gradientRadius="79.34"
android:type="radial">
<item android:offset="0.46" android:color="#FF29B6F6"/>
<item android:offset="1" android:color="#FF1E88E5"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m86.34,101.71c-4.55,7.02 -14.84,5.69 -14.84,-5.97 0,-7.45 1.52,-45.74 7.92,-40.39 10.41,8.72 13.38,36.43 6.92,46.36z"
android:fillColor="#81d4fa"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -1,43 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="m81.56,35.12 l23.3,-13.38c1.48,-0.85 3.12,0.83 2.24,2.29l-13.86,23.01c-2.07,3.44 -0.28,7.91 3.6,8.95l25.94,7.01c1.65,0.45 1.62,2.79 -0.03,3.2l-26.08,6.47c-3.9,0.97 -5.79,5.4 -3.79,8.88l13.38,23.3c0.85,1.48 -0.83,3.12 -2.29,2.24l-23.01,-13.85c-3.44,-2.07 -7.91,-0.28 -8.95,3.6l-7.01,25.94c-0.45,1.65 -2.79,1.62 -3.2,-0.03l-6.47,-26.08c-0.97,-3.9 -5.4,-5.79 -8.88,-3.79l-23.3,13.38c-1.48,0.85 -3.12,-0.83 -2.24,-2.29l13.86,-23.02c2.07,-3.44 0.28,-7.91 -3.6,-8.95l-25.95,-7.01c-1.65,-0.45 -1.62,-2.79 0.03,-3.2l26.08,-6.47c3.9,-0.97 5.79,-5.4 3.79,-8.88l-13.38,-23.3c-0.85,-1.48 0.83,-3.12 2.29,-2.24l23.02,13.86c3.44,2.07 7.91,0.28 8.95,-3.6l7.01,-25.94c0.45,-1.65 2.79,-1.62 3.2,0.03l6.47,26.08c0.97,3.9 5.4,5.79 8.88,3.79z">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="64.01"
android:centerY="64"
android:gradientRadius="55.57"
android:type="radial">
<item android:offset="0.39" android:color="#FFFF8F00"/>
<item android:offset="0.82" android:color="#FFFFB300"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M64.07,63.97m-30.03,0a30.03,30.03 0,1 1,60.06 0a30.03,30.03 0,1 1,-60.06 0">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="64.07"
android:centerY="63.97"
android:gradientRadius="37.05"
android:type="radial">
<item android:offset="0.58" android:color="#FFFFEB3B"/>
<item android:offset="0.84" android:color="#FFFBC02D"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m64.11,35.99c3.74,0 7.4,0.74 10.87,2.21 14.21,6.01 20.88,22.47 14.87,36.68 -4.39,10.38 -14.52,17.08 -25.81,17.08 -3.74,0 -7.4,-0.74 -10.87,-2.21 -14.21,-6.02 -20.88,-22.47 -14.87,-36.69 4.39,-10.37 14.52,-17.07 25.81,-17.07m0,-2c-11.69,0 -22.82,6.88 -27.66,18.3 -6.44,15.23 0.7,32.86 15.93,39.3 3.8,1.61 7.75,2.37 11.65,2.37 11.69,0 22.82,-6.88 27.66,-18.3 6.44,-15.23 -0.7,-32.86 -15.93,-39.3 -3.8,-1.61 -7.76,-2.37 -11.65,-2.37z"
android:strokeAlpha="0.2"
android:fillColor="#eee"
android:fillAlpha="0.2"/>
<path
android:pathData="m64.55,11.02 l5.22,21.04c1,4.04 4.62,6.87 8.79,6.87 1.57,0 3.12,-0.42 4.5,-1.21l18.8,-10.8 -11.19,18.57c-1.48,2.45 -1.71,5.38 -0.64,8.04s3.27,4.61 6.03,5.36l20.92,5.66 -21.04,5.22c-2.78,0.69 -5.02,2.6 -6.14,5.23s-0.95,5.57 0.48,8.05l10.8,18.8 -18.57,-11.18c-1.41,-0.85 -3.02,-1.3 -4.66,-1.3 -4.08,0 -7.68,2.75 -8.74,6.69l-5.66,20.92 -5.22,-21.04c-1,-4.04 -4.62,-6.87 -8.79,-6.87 -1.57,0 -3.12,0.42 -4.5,1.21l-18.8,10.8 11.18,-18.57c1.48,-2.45 1.71,-5.38 0.64,-8.04s-3.27,-4.61 -6.03,-5.36l-20.92,-5.66 21.04,-5.22c2.78,-0.69 5.02,-2.6 6.14,-5.23s0.95,-5.57 -0.48,-8.05l-10.8,-18.8 18.57,11.18c1.41,0.85 3.02,1.3 4.66,1.3 4.08,0 7.68,-2.75 8.74,-6.69zM64.61,4c-0.69,0 -1.37,0.41 -1.59,1.22l-7.02,25.94c-0.75,2.76 -3.23,4.47 -5.85,4.47 -1.05,0 -2.12,-0.28 -3.11,-0.87l-23.02,-13.86c-0.28,-0.17 -0.58,-0.25 -0.86,-0.25 -1.16,0 -2.11,1.3 -1.43,2.49l13.38,23.3c2,3.48 0.11,7.91 -3.79,8.88l-26.07,6.47c-1.66,0.41 -1.68,2.75 -0.03,3.2l25.94,7.01c3.88,1.05 5.67,5.51 3.6,8.95l-13.86,23.03c-0.72,1.19 0.23,2.52 1.41,2.52 0.27,0 0.55,-0.07 0.83,-0.23l23.3,-13.38c0.96,-0.55 1.99,-0.81 3,-0.81 2.66,0 5.18,1.77 5.88,4.59l6.47,26.08c0.21,0.83 0.91,1.25 1.6,1.25s1.37,-0.41 1.59,-1.22l7.02,-25.94c0.75,-2.76 3.23,-4.47 5.85,-4.47 1.05,0 2.12,0.28 3.11,0.87l23.02,13.86c0.28,0.17 0.58,0.25 0.86,0.25 1.16,0 2.11,-1.3 1.43,-2.49l-13.38,-23.3c-2,-3.48 -0.11,-7.91 3.79,-8.88l26.08,-6.47c1.66,-0.41 1.68,-2.75 0.03,-3.2l-25.95,-7.01c-3.88,-1.05 -5.67,-5.51 -3.6,-8.95l13.86,-23.02c0.72,-1.19 -0.23,-2.52 -1.41,-2.52 -0.27,0 -0.55,0.07 -0.83,0.23l-23.3,13.38c-0.96,0.55 -1.99,0.81 -3,0.81 -2.66,0 -5.18,-1.77 -5.88,-4.59l-6.47,-26.09c-0.21,-0.83 -0.91,-1.25 -1.6,-1.25z"
android:strokeAlpha="0.2"
android:fillColor="#eee"
android:fillAlpha="0.2"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -1,38 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="M76,80.34V16.29C76,9.77 70.82,4 64.3,4h-1.55C56.24,4 51,9.77 51,16.29v64.04c-7,4.2 -11.24,11.68 -11.24,20.2c0,13.14 10.64,23.8 23.79,23.8s23.74,-10.65 23.74,-23.8C87.28,92.01 83,84.54 76,80.34z"
android:fillColor="#E1F5FE"/>
<path
android:pathData="m69.92,85.89v-62.07c0,-3.53 -2.86,-6.39 -6.39,-6.39s-6.39,2.86 -6.39,6.39v62.08c-5.82,2.54 -9.83,8.44 -9.58,15.25 0.3,8.19 6.99,14.96 15.18,15.36 9.19,0.44 16.78,-6.87 16.78,-15.96 -0.01,-6.57 -3.95,-12.19 -9.6,-14.66z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="67.01"
android:startY="38.59"
android:endX="63.28"
android:endY="104.92"
android:type="linear">
<item android:offset="0.6" android:color="#FFEF5350"/>
<item android:offset="1" android:color="#FFE53935"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m62.87,39.76h-11.92v-6.39h11.92c1.66,0 3,1.34 3,3v0.39c0,1.66 -1.34,3 -3,3z"
android:fillColor="#616161"/>
<path
android:pathData="m62.87,58.32h-11.92v-6.39h11.92c1.66,0 3,1.34 3,3v0.39c0,1.65 -1.34,3 -3,3z"
android:fillColor="#616161"/>
<path
android:pathData="m62.87,76.88h-11.92v-6.39h11.92c1.66,0 3,1.34 3,3v0.39c0,1.65 -1.34,3 -3,3z"
android:fillColor="#616161"/>
<path
android:pathData="M64.15,7C68.87,7 73,11.26 73,16.29v64.04c0,1.05 0.55,2.03 1.46,2.57c6.15,3.69 9.82,10.28 9.82,17.63c0,11.47 -9.3,20.8 -20.74,20.8c-11.46,0 -20.79,-9.33 -20.79,-20.8c0,-7.36 3.66,-13.95 9.79,-17.63c0.9,-0.54 1.46,-1.52 1.46,-2.57V16.29C54,11.26 58.01,7 62.76,7H64M64.3,4h-1.55C56.24,4 51,9.77 51,16.29v64.04c-7,4.2 -11.24,11.68 -11.24,20.2c0,13.14 10.64,23.8 23.79,23.8s23.74,-10.65 23.74,-23.8c0,-8.52 -4.28,-16 -11.28,-20.2V16.29C76,9.77 70.82,4 64.3,4L64.3,4z"
android:strokeAlpha="0.2"
android:fillColor="#424242"
android:fillAlpha="0.2"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -3,4 +3,6 @@ plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false
alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.google.gms.google.services) apply false
alias(libs.plugins.google.firebase.crashlytics) apply false
}

View File

@ -11,15 +11,20 @@ androidxComposeMaterial = "1.3.1"
glance = "1.1.1"
kotlinxSerializationJson = "1.8.0"
mapboxSdkTurf = "7.3.1"
firebaseCrashlytics = "19.4.2"
googleGmsGoogleServices = "4.4.2"
googleFirebaseCrashlytics = "3.0.3"
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
google-gms-google-services = { id = "com.google.gms.google-services", version.ref = "googleGmsGoogleServices" }
google-firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "googleFirebaseCrashlytics" }
[libraries]
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
hammerhead-karoo-ext = { group = "io.hammerhead", name = "karoo-ext", version = "1.1.5" }
hammerhead-karoo-ext = { group = "io.hammerhead", name = "karoo-ext", version = "1.1.3" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }
# compose
@ -38,6 +43,7 @@ androidx-glance-appwidget-preview = { group = "androidx.glance", name = "glance-
androidx-glance-preview = { group = "androidx.glance", name = "glance-preview", version.ref = "glance" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
mapbox-sdk-turf = { module = "com.mapbox.mapboxsdk:mapbox-sdk-turf", version.ref = "mapboxSdkTurf" }
firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics", version.ref = "firebaseCrashlytics" }
[bundles]
androidx-lifeycle = ["androidx-lifecycle-runtime-compose", "androidx-lifecycle-viewmodel-compose"]