Compare commits
10 Commits
c4a23ce456
...
522364dd84
| Author | SHA1 | Date | |
|---|---|---|---|
| 522364dd84 | |||
| 855ce46b99 | |||
| 0332e032d4 | |||
|
|
188863727f | ||
|
|
617a41c7c8 | ||
|
|
9772347a61 | ||
|
|
f7cd264e0c | ||
|
|
96bee1b55c | ||
|
|
cff4b07d7d | ||
|
|
a0a1ad6f7b |
1
.github/workflows/android.yml
vendored
@ -25,7 +25,6 @@ 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
|
||||
|
||||
@ -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). Tap on this widget to open the headwind app with a forecast overview.
|
||||
- Current weather (graphical, 1x1 field): Shows current weather conditions (same as forecast widget, but only for the current time).
|
||||
- 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))
|
||||
- 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)
|
||||
- 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
@ -1,2 +1 @@
|
||||
/build
|
||||
/google-services.json
|
||||
|
||||
@ -5,8 +5,6 @@ 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 {
|
||||
@ -37,9 +35,6 @@ android {
|
||||
buildTypes {
|
||||
debug {
|
||||
isMinifyEnabled = false
|
||||
firebaseCrashlytics {
|
||||
mappingFileUploadEnabled = false
|
||||
}
|
||||
}
|
||||
release {
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
@ -60,24 +55,6 @@ 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"
|
||||
@ -93,11 +70,8 @@ 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 "* 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",
|
||||
"releaseNotes" to "* Remove crashlytics\n" +
|
||||
"* Reduce refresh rate on K2, add refresh rate setting\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",
|
||||
@ -114,7 +88,6 @@ tasks.register("generateManifest") {
|
||||
|
||||
tasks.named("assemble") {
|
||||
dependsOn("generateManifest")
|
||||
dependsOn("addGoogleServicesJson")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@ -129,6 +102,5 @@ dependencies {
|
||||
implementation(libs.androidx.glance.appwidget)
|
||||
implementation(libs.androidx.glance.appwidget.preview)
|
||||
implementation(libs.androidx.glance.preview)
|
||||
implementation(libs.firebase.crashlytics)
|
||||
testImplementation(kotlin("test"))
|
||||
}
|
||||
|
||||
@ -210,6 +210,22 @@ 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?,
|
||||
@ -266,17 +282,18 @@ fun lerpWeather(
|
||||
return WeatherData(
|
||||
time = (start.time + (end.time - start.time) * factor).toLong(),
|
||||
temperature = start.temperature + (end.temperature - start.temperature) * factor,
|
||||
relativeHumidity = lerpNullable(start.relativeHumidity, end.relativeHumidity, factor),
|
||||
relativeHumidity = lerp(start.relativeHumidity, end.relativeHumidity, factor),
|
||||
precipitation = start.precipitation + (end.precipitation - start.precipitation) * factor,
|
||||
precipitationProbability = lerpNullable(start.precipitationProbability, end.precipitationProbability, factor),
|
||||
cloudCover = lerpNullable(start.cloudCover, end.cloudCover, factor),
|
||||
surfacePressure = lerpNullable(start.surfacePressure, end.surfacePressure, factor),
|
||||
sealevelPressure = lerpNullable(start.sealevelPressure, end.sealevelPressure, factor),
|
||||
cloudCover = lerp(start.cloudCover, end.cloudCover, factor),
|
||||
surfacePressure = lerp(start.surfacePressure, end.surfacePressure, factor),
|
||||
sealevelPressure = lerp(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
|
||||
isForecast = closestWeatherData.isForecast,
|
||||
isNight = closestWeatherData.isNight,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
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
|
||||
@ -12,6 +13,7 @@ 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
|
||||
|
||||
@ -65,3 +67,22 @@ 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 }
|
||||
}
|
||||
}
|
||||
@ -50,7 +50,6 @@ data class HeadwindWidgetSettings(
|
||||
}
|
||||
}
|
||||
|
||||
//Moded with openweahtermap.org
|
||||
@Serializable
|
||||
data class HeadwindStats(
|
||||
val lastSuccessfulWeatherRequest: Long? = null,
|
||||
@ -70,8 +69,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(karooSystemService: KarooSystemService): String {
|
||||
return if (karooSystemService.hardwareType == HardwareType.K2) {
|
||||
fun getDescription(isOnK2: Boolean): String {
|
||||
return if (isOnK2) {
|
||||
when (this) {
|
||||
FAST -> "Fast (1s)"
|
||||
STANDARD -> "Standard (2s)"
|
||||
|
||||
@ -3,16 +3,24 @@ package de.timklge.karooheadwind.datatypes
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import de.timklge.karooheadwind.KarooHeadwindExtension
|
||||
import de.timklge.karooheadwind.weatherprovider.WeatherData
|
||||
import de.timklge.karooheadwind.streamCurrentWeatherData
|
||||
import de.timklge.karooheadwind.streamUserProfile
|
||||
import de.timklge.karooheadwind.throttle
|
||||
import de.timklge.karooheadwind.weatherprovider.WeatherData
|
||||
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
|
||||
|
||||
@ -21,17 +29,25 @@ abstract class BaseDataType(
|
||||
private val applicationContext: Context,
|
||||
dataTypeId: String
|
||||
) : DataTypeImpl("karoo-headwind", dataTypeId) {
|
||||
abstract fun getValue(data: WeatherData): Double?
|
||||
abstract fun getValue(data: WeatherData, userProfile: UserProfile): Double?
|
||||
|
||||
open fun getFormatDataType(): String? = null
|
||||
|
||||
override fun startStream(emitter: Emitter<StreamState>) {
|
||||
Log.d(KarooHeadwindExtension.TAG, "start $dataTypeId stream")
|
||||
val job = CoroutineScope(Dispatchers.IO).launch {
|
||||
val currentWeatherData = applicationContext.streamCurrentWeatherData(karooSystemService)
|
||||
data class StreamData(val weatherData: WeatherData, val userProfile: UserProfile)
|
||||
|
||||
currentWeatherData
|
||||
.filterNotNull()
|
||||
.collect { data ->
|
||||
val value = getValue(data)
|
||||
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)
|
||||
Log.d(KarooHeadwindExtension.TAG, "$dataTypeId: $value")
|
||||
|
||||
if (value != null) {
|
||||
@ -46,4 +62,12 @@ 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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,9 +3,10 @@ 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): Double? {
|
||||
override fun getValue(data: WeatherData, userProfile: UserProfile): Double? {
|
||||
return data.cloudCover
|
||||
}
|
||||
}
|
||||
@ -28,18 +28,22 @@ 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
|
||||
@ -54,6 +58,7 @@ 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
|
||||
@ -78,7 +83,8 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
|
||||
timeLabel: String,
|
||||
dateLabel: String?,
|
||||
distance: Double?,
|
||||
isImperial: Boolean)
|
||||
isImperial: Boolean,
|
||||
isNight: Boolean)
|
||||
|
||||
@OptIn(ExperimentalGlanceRemoteViewsApi::class)
|
||||
private val glance = GlanceRemoteViews()
|
||||
@ -89,7 +95,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 headingResponse: HeadingResponse? = null, val upcomingRoute: UpcomingRoute? = null, val isVisible: Boolean)
|
||||
|
||||
data class SettingsAndProfile(val settings: HeadwindSettings, val isImperial: Boolean, val isImperialTemperature: Boolean)
|
||||
|
||||
@ -113,7 +119,7 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
|
||||
WeatherData(
|
||||
time = forecastTime,
|
||||
temperature = forecastTemperature,
|
||||
relativeHumidity = 20.0,
|
||||
relativeHumidity = 20,
|
||||
precipitation = forecastPrecipitation,
|
||||
cloudCover = 3.0,
|
||||
sealevelPressure = 1013.25,
|
||||
@ -123,7 +129,8 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
|
||||
windDirection = forecastWindDirection,
|
||||
windGusts = forecastWindGusts,
|
||||
weatherCode = forecastWeatherCode,
|
||||
isForecast = true
|
||||
isForecast = true,
|
||||
isNight = it < 2
|
||||
)
|
||||
}
|
||||
|
||||
@ -135,7 +142,7 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
|
||||
current = WeatherData(
|
||||
time = timeAtFullHour,
|
||||
temperature = 20.0,
|
||||
relativeHumidity = 20.0,
|
||||
relativeHumidity = 20,
|
||||
precipitation = 0.0,
|
||||
cloudCover = 3.0,
|
||||
sealevelPressure = 1013.25,
|
||||
@ -144,7 +151,8 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
|
||||
windDirection = 180.0,
|
||||
windGusts = 10.0,
|
||||
weatherCode = WeatherInterpretation.getKnownWeatherCodes().random(),
|
||||
isForecast = false
|
||||
isForecast = false,
|
||||
isNight = false
|
||||
),
|
||||
coords = GpsCoordinates(0.0, 0.0, distanceAlongRoute = index * distancePerHour),
|
||||
timezone = "UTC",
|
||||
@ -161,7 +169,8 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
|
||||
HeadwindSettings(),
|
||||
settingsAndProfile?.isImperial == true,
|
||||
settingsAndProfile?.isImperialTemperature == true
|
||||
)
|
||||
),
|
||||
isVisible = true
|
||||
)
|
||||
)
|
||||
|
||||
@ -203,14 +212,23 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
|
||||
if (oldDistance == null || newDistance == null) return@distinctUntilChanged false
|
||||
|
||||
abs(oldDistance - newDistance) < 1_000
|
||||
}
|
||||
) { weatherData, settings, widgetSettings, heading, upcomingRoute ->
|
||||
},
|
||||
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
|
||||
|
||||
StreamData(
|
||||
data = weatherData,
|
||||
settings = settings,
|
||||
widgetSettings = widgetSettings,
|
||||
headingResponse = heading,
|
||||
upcomingRoute = upcomingRoute
|
||||
upcomingRoute = upcomingRoute,
|
||||
isVisible = isVisible
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -218,7 +236,7 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
|
||||
val viewJob = CoroutineScope(Dispatchers.IO).launch {
|
||||
emitter.onNext(ShowCustomStreamState("", null))
|
||||
|
||||
dataFlow.collect { (allData, settingsAndProfile, widgetSettings, headingResponse, upcomingRoute) ->
|
||||
dataFlow.filter { it.isVisible }.collect { (allData, settingsAndProfile, widgetSettings, headingResponse, upcomingRoute) ->
|
||||
Log.d(KarooHeadwindExtension.TAG, "Updating weather forecast view")
|
||||
|
||||
if (allData?.data.isNullOrEmpty()){
|
||||
@ -296,16 +314,17 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
|
||||
arrowBitmap = baseBitmap,
|
||||
current = interpretation,
|
||||
windBearing = data.current.windDirection.roundToInt(),
|
||||
windSpeed = data.current.windSpeed.roundToInt(),
|
||||
windGusts = data.current.windGusts.roundToInt(),
|
||||
precipitation = data.current.precipitation,
|
||||
windSpeed = msInUserUnit(data.current.windSpeed, settingsAndProfile.isImperial).roundToInt(),
|
||||
windGusts = msInUserUnit(data.current.windGusts, settingsAndProfile.isImperial).roundToInt(),
|
||||
precipitation = millimetersInUserUnit(data.current.precipitation, settingsAndProfile.isImperial),
|
||||
precipitationProbability = null,
|
||||
temperature = data.current.temperature.roundToInt(),
|
||||
temperature = celciusInUserUnit(data.current.temperature, settingsAndProfile.isImperialTemperature).roundToInt(),
|
||||
temperatureUnit = if (settingsAndProfile.isImperialTemperature) TemperatureUnit.FAHRENHEIT else TemperatureUnit.CELSIUS,
|
||||
timeLabel = formattedTime,
|
||||
dateLabel = if (hasNewDate) formattedDate else null,
|
||||
distance = null,
|
||||
isImperial = settingsAndProfile.isImperial
|
||||
isImperial = settingsAndProfile.isImperial,
|
||||
isNight = data.current.isNight
|
||||
)
|
||||
|
||||
previousDate = formattedDate
|
||||
@ -321,16 +340,17 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
|
||||
arrowBitmap = baseBitmap,
|
||||
current = interpretation,
|
||||
windBearing = weatherData?.windDirection?.roundToInt() ?: 0,
|
||||
windSpeed = weatherData?.windSpeed?.roundToInt() ?: 0,
|
||||
windGusts = weatherData?.windGusts?.roundToInt() ?: 0,
|
||||
precipitation = weatherData?.precipitation ?: 0.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),
|
||||
precipitationProbability = weatherData?.precipitationProbability?.toInt(),
|
||||
temperature = weatherData?.temperature?.roundToInt() ?: 0,
|
||||
temperature = celciusInUserUnit(weatherData?.temperature ?: 0.0, settingsAndProfile.isImperialTemperature).roundToInt(),
|
||||
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
|
||||
isImperial = settingsAndProfile.isImperial,
|
||||
isNight = weatherData?.isNight == true
|
||||
)
|
||||
|
||||
previousDate = formattedDate
|
||||
|
||||
@ -34,16 +34,16 @@ fun GraphicalForecast(
|
||||
distance: Double? = null,
|
||||
timeLabel: String? = null,
|
||||
rowAlignment: Alignment.Horizontal = Alignment.Horizontal.CenterHorizontally,
|
||||
isImperial: Boolean?
|
||||
isImperial: Boolean?,
|
||||
isNight: 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)),
|
||||
provider = ImageProvider(getWeatherIcon(current, isNight)),
|
||||
contentDescription = "Current weather information",
|
||||
contentScale = ContentScale.Fit,
|
||||
colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White))
|
||||
)
|
||||
}
|
||||
|
||||
@ -96,13 +96,15 @@ class GraphicalForecastDataType(karooSystem: KarooSystemService) : ForecastDataT
|
||||
timeLabel: String,
|
||||
dateLabel: String?,
|
||||
distance: Double?,
|
||||
isImperial: Boolean
|
||||
isImperial: Boolean,
|
||||
isNight: Boolean,
|
||||
) {
|
||||
GraphicalForecast(
|
||||
current = current,
|
||||
distance = distance,
|
||||
timeLabel = timeLabel,
|
||||
isImperial = isImperial
|
||||
isImperial = isImperial,
|
||||
isNight = isNight
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -12,8 +12,11 @@ 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
|
||||
@ -23,6 +26,7 @@ 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
|
||||
@ -31,6 +35,7 @@ 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
|
||||
@ -43,17 +48,25 @@ 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 {
|
||||
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 ->
|
||||
combine(
|
||||
karooSystem.getRelativeHeadingFlow(applicationContext),
|
||||
applicationContext.streamCurrentWeatherData(karooSystem),
|
||||
applicationContext.streamSettings(karooSystem),
|
||||
) { headingResponse, currentWeather, settings ->
|
||||
StreamData(
|
||||
headingResponse,
|
||||
currentWeather?.windDirection,
|
||||
currentWeather?.windSpeed,
|
||||
settings,
|
||||
)
|
||||
}.collect { streamData ->
|
||||
val value = (streamData.headingResponse as? HeadingResponse.Value)?.diff
|
||||
|
||||
var returnValue = 0.0
|
||||
if (value == null || streamData.absoluteWindDirection == null || streamData.settings == null || streamData.windSpeed == null){
|
||||
if (value == null || streamData.absoluteWindDirection == null || streamData.windSpeed == null){
|
||||
var errorCode = 1.0
|
||||
var headingResponse = streamData.headingResponse
|
||||
|
||||
@ -61,7 +74,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()
|
||||
@ -96,9 +109,12 @@ class HeadwindDirectionDataType(
|
||||
}
|
||||
}
|
||||
|
||||
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?)
|
||||
data class DirectionAndSpeed(
|
||||
val bearing: Double,
|
||||
val speed: Double?,
|
||||
val isVisible: Boolean,
|
||||
val isImperial: Boolean
|
||||
)
|
||||
|
||||
private fun previewFlow(): Flow<DirectionAndSpeed> {
|
||||
return flow {
|
||||
@ -106,7 +122,12 @@ class HeadwindDirectionDataType(
|
||||
val bearing = (0..360).random().toDouble()
|
||||
val windSpeed = (0..20).random()
|
||||
|
||||
emit(DirectionAndSpeed(bearing, windSpeed.toDouble()))
|
||||
emit(DirectionAndSpeed(
|
||||
bearing,
|
||||
windSpeed.toDouble(),
|
||||
true,
|
||||
true
|
||||
))
|
||||
|
||||
delay(2_000)
|
||||
}
|
||||
@ -136,15 +157,15 @@ class HeadwindDirectionDataType(
|
||||
emitAll(UserWindSpeedDataType.streamValues(context, karooSystem))
|
||||
}
|
||||
|
||||
combine(directionFlow, speedFlow) { direction, speed ->
|
||||
DirectionAndSpeed(direction, speed)
|
||||
combine(directionFlow, speedFlow, karooSystem.streamDatatypeIsVisible(dataTypeId), karooSystem.streamUserProfile()) { direction, speed, isVisible, profile ->
|
||||
DirectionAndSpeed(direction, speed, isVisible, profile.preferredUnit.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL)
|
||||
}
|
||||
}
|
||||
|
||||
val viewJob = CoroutineScope(Dispatchers.IO).launch {
|
||||
val refreshRate = karooSystem.getRefreshRateInMilliseconds(context)
|
||||
|
||||
flow.throttle(refreshRate).collect { streamData ->
|
||||
flow.filter { it.isVisible }.throttle(refreshRate).collect { streamData ->
|
||||
Log.d(KarooHeadwindExtension.TAG, "Updating headwind direction view")
|
||||
|
||||
val errorCode = streamData.bearing.let { if(it < 0) it.toInt() else null }
|
||||
@ -154,14 +175,15 @@ class HeadwindDirectionDataType(
|
||||
}
|
||||
|
||||
val windDirection = streamData.bearing
|
||||
val windSpeed = streamData.speed
|
||||
val windSpeed = streamData.speed ?: 0.0
|
||||
val windSpeedUserUnit = msInUserUnit(windSpeed, streamData.isImperial)
|
||||
|
||||
val result = glance.compose(context, DpSize.Unspecified) {
|
||||
HeadwindDirection(
|
||||
baseBitmap,
|
||||
windDirection.roundToInt(),
|
||||
config.textSize,
|
||||
windSpeed?.toInt()?.toString() ?: "",
|
||||
windSpeedUserUnit.roundToInt().toString(),
|
||||
preview = config.preview,
|
||||
wideMode = false
|
||||
)
|
||||
|
||||
@ -67,7 +67,7 @@ fun HeadwindDirection(
|
||||
val baseModifier = GlanceModifier.fillMaxSize().padding(5.dp).background(dayColor, nightColor).cornerRadius(10.dp)
|
||||
|
||||
Box(
|
||||
modifier = if (!preview) baseModifier.clickable(actionStartActivity<MainActivity>()) else baseModifier,
|
||||
modifier = baseModifier, // TODO if (!preview) baseModifier.clickable(actionStartActivity<MainActivity>()) else baseModifier,
|
||||
contentAlignment = Alignment(
|
||||
vertical = Alignment.Vertical.CenterVertically,
|
||||
horizontal = Alignment.Horizontal.CenterHorizontally,
|
||||
|
||||
@ -7,12 +7,16 @@ 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
|
||||
@ -27,11 +31,14 @@ 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
|
||||
@ -45,5 +52,9 @@ class HeadwindSpeedDataType(
|
||||
job.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
override fun startView(context: Context, config: ViewConfig, emitter: ViewEmitter) {
|
||||
emitter.onNext(UpdateGraphicConfig(formatDataTypeId = DataType.Type.SPEED))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
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): Double {
|
||||
return data.precipitation
|
||||
override fun getValue(data: WeatherData, userProfile: UserProfile): Double {
|
||||
return millimetersInUserUnit(data.precipitation, userProfile.preferredUnit.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL)
|
||||
}
|
||||
}
|
||||
@ -25,7 +25,6 @@ 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(
|
||||
@ -34,7 +33,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) {
|
||||
@ -95,14 +94,15 @@ class PrecipitationForecastDataType(karooSystem: KarooSystemService) : ForecastD
|
||||
timeLabel: String,
|
||||
dateLabel: String?,
|
||||
distance: Double?,
|
||||
isImperial: Boolean
|
||||
isImperial: Boolean,
|
||||
isNight: Boolean,
|
||||
) {
|
||||
PrecipitationForecast(
|
||||
precipitation = ceil(precipitation).toInt(),
|
||||
precipitationProbability = precipitationProbability,
|
||||
distance = distance,
|
||||
timeLabel = timeLabel,
|
||||
isImperial = isImperial
|
||||
isImperial = isImperial,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,7 @@ 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
|
||||
@ -113,7 +114,7 @@ class RelativeGradeDataType(private val karooSystemService: KarooSystemService,
|
||||
return relativeGrade
|
||||
}
|
||||
|
||||
fun streamRelativeGrade(karooSystemService: KarooSystemService, context: Context): Flow<RelativeGradeResponse> {
|
||||
suspend 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
|
||||
@ -126,30 +127,11 @@ class RelativeGradeDataType(private val karooSystemService: KarooSystemService,
|
||||
} + DEFAULT_BIKE_WEIGHT
|
||||
}
|
||||
|
||||
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 refreshRate = karooSystemService.getRefreshRateInMilliseconds(context)
|
||||
|
||||
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
|
||||
val windSpeedFlow = context.streamCurrentWeatherData(karooSystemService).filterNotNull().map { weatherData ->
|
||||
weatherData.windSpeed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class StreamValues(
|
||||
val relativeWindDirection: Double,
|
||||
@ -161,7 +143,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().map { (windDirection, speed, windSpeed, actualGrade, totalMass) ->
|
||||
}.distinctUntilChanged().throttle(refreshRate).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")
|
||||
|
||||
@ -3,9 +3,10 @@ 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): Double? {
|
||||
return data.relativeHumidity
|
||||
override fun getValue(data: WeatherData, userProfile: UserProfile): Double? {
|
||||
return data.relativeHumidity.toDouble()
|
||||
}
|
||||
}
|
||||
@ -3,9 +3,10 @@ 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): Double? {
|
||||
override fun getValue(data: WeatherData, userProfile: UserProfile): Double? {
|
||||
return data.sealevelPressure
|
||||
}
|
||||
}
|
||||
@ -3,9 +3,10 @@ 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): Double? {
|
||||
override fun getValue(data: WeatherData, userProfile: UserProfile): Double? {
|
||||
return data.surfacePressure
|
||||
}
|
||||
}
|
||||
@ -21,9 +21,12 @@ 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
|
||||
@ -85,7 +88,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))
|
||||
emit(StreamData(HeadingResponse.Value(bearing), bearing, windSpeed.toDouble(), HeadwindSettings(), rideSpeed, gustSpeed = gustSpeed, isImperial = isImperial, isVisible = true))
|
||||
|
||||
delay(2_000)
|
||||
}
|
||||
@ -113,18 +116,27 @@ class TailwindAndRideSpeedDataType(
|
||||
val flow = if (config.preview) {
|
||||
previewFlow(karooSystem.streamUserProfile())
|
||||
} else {
|
||||
combine(karooSystem.getRelativeHeadingFlow(context), context.streamCurrentWeatherData(karooSystem), context.streamSettings(karooSystem), karooSystem.streamUserProfile(), streamSpeedInMs()) { headingResponse, weatherData, settings, userProfile, rideSpeedInMs ->
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
val rideSpeed = rideSpeedInMs
|
||||
|
||||
StreamData(headingResponse, absoluteWindDirection, windSpeed, settings, rideSpeed = rideSpeed, isImperial = isImperial, gustSpeed = gustSpeed)
|
||||
StreamData(headingResponse, absoluteWindDirection, windSpeed, settings, rideSpeed = rideSpeed, isImperial = isImperial, gustSpeed = gustSpeed, isVisible = isVisible)
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,15 +166,21 @@ class TailwindAndRideSpeedDataType(
|
||||
WindDirectionIndicatorSetting.WIND_DIRECTION -> streamData.absoluteWindDirection + 180
|
||||
}
|
||||
|
||||
val text = streamData.rideSpeed?.let { String.format(Locale.current.platformLocale, "%.1f", it) } ?: ""
|
||||
val rideSpeedInUserUnit = msInUserUnit(streamData.rideSpeed ?: 0.0, streamData.isImperial)
|
||||
val text = String.format(Locale.current.platformLocale, "%.1f", rideSpeedInUserUnit)
|
||||
|
||||
val wideMode = config.gridSize.first == 60
|
||||
|
||||
val gustSpeedInUserUnit = msInUserUnit(streamData.gustSpeed ?: 0.0, streamData.isImperial)
|
||||
|
||||
val gustSpeedAddon = if (wideMode) {
|
||||
"-${streamData.gustSpeed?.roundToInt() ?: 0}"
|
||||
"-${gustSpeedInUserUnit.roundToInt()}"
|
||||
} 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
|
||||
@ -171,9 +189,12 @@ class TailwindAndRideSpeedDataType(
|
||||
val sign = if (headwindSpeed < 0) "+" else {
|
||||
if (headwindSpeed > 0) "-" else ""
|
||||
}
|
||||
"$sign${headwindSpeed.roundToInt().absoluteValue} ${windSpeed.roundToInt()}${gustSpeedAddon}"
|
||||
|
||||
val headwindSpeedUserUnit = msInUserUnit(headwindSpeed, streamData.isImperial)
|
||||
|
||||
"$sign${headwindSpeedUserUnit.roundToInt().absoluteValue} ${windSpeedUserUnit.roundToInt()}${gustSpeedAddon}"
|
||||
}
|
||||
WindDirectionIndicatorTextSetting.WIND_SPEED -> "${windSpeed.roundToInt()}${gustSpeedAddon}"
|
||||
WindDirectionIndicatorTextSetting.WIND_SPEED -> "${windSpeedUserUnit.roundToInt()}${gustSpeedAddon}"
|
||||
WindDirectionIndicatorTextSetting.NONE -> ""
|
||||
}
|
||||
|
||||
@ -182,11 +203,7 @@ class TailwindAndRideSpeedDataType(
|
||||
|
||||
if (streamData.settings.windDirectionIndicatorSetting == WindDirectionIndicatorSetting.HEADWIND_DIRECTION) {
|
||||
val headwindSpeed = cos( (windDirection + 180) * Math.PI / 180.0) * windSpeed
|
||||
val windSpeedInKmh = if (streamData.isImperial == true){
|
||||
headwindSpeed / 2.23694 * 3.6
|
||||
} else {
|
||||
headwindSpeed
|
||||
}
|
||||
val windSpeedInKmh = headwindSpeed * 3.6
|
||||
dayColor = interpolateWindColor(windSpeedInKmh, false, context)
|
||||
nightColor = interpolateWindColor(windSpeedInKmh, true, context)
|
||||
}
|
||||
|
||||
@ -17,9 +17,12 @@ 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
|
||||
@ -35,6 +38,7 @@ 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
|
||||
@ -56,7 +60,8 @@ class TailwindDataType(
|
||||
val settings: HeadwindSettings,
|
||||
val rideSpeed: Double?,
|
||||
val gustSpeed: Double?,
|
||||
val isImperial: Boolean)
|
||||
val isImperial: Boolean,
|
||||
val isVisible: Boolean)
|
||||
|
||||
private fun previewFlow(profileFlow: Flow<UserProfile>): Flow<StreamData> {
|
||||
return flow {
|
||||
@ -69,7 +74,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))
|
||||
emit(StreamData(HeadingResponse.Value(bearing), bearing, windSpeed.toDouble(), HeadwindSettings(), rideSpeed, gustSpeed = gustSpeed, isImperial = isImperial, isVisible = true))
|
||||
|
||||
delay(2_000)
|
||||
}
|
||||
@ -98,18 +103,26 @@ class TailwindDataType(
|
||||
val flow = if (config.preview) {
|
||||
previewFlow(karooSystem.streamUserProfile())
|
||||
} else {
|
||||
combine(karooSystem.getRelativeHeadingFlow(context), context.streamCurrentWeatherData(karooSystem), context.streamSettings(karooSystem), karooSystem.streamUserProfile(), streamSpeedInMs()) { headingResponse, weatherData, settings, userProfile, rideSpeedInMs ->
|
||||
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
|
||||
|
||||
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 = rideSpeed, isImperial = isImperial, gustSpeed = gustSpeed)
|
||||
StreamData(headingResponse, absoluteWindDirection, windSpeed, settings, rideSpeed = rideSpeedInMs, isImperial = isImperial, gustSpeed = gustSpeed, isVisible = isVisible)
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,7 +130,7 @@ class TailwindDataType(
|
||||
emitter.onNext(ShowCustomStreamState("", null))
|
||||
|
||||
val refreshRate = karooSystem.getRefreshRateInMilliseconds(context)
|
||||
flow.throttle(refreshRate).collect { streamData ->
|
||||
flow.filter { it.isVisible }.throttle(refreshRate).collect { streamData ->
|
||||
Log.d(KarooHeadwindExtension.TAG, "Updating tailwind direction view")
|
||||
|
||||
val value = (streamData.headingResponse as? HeadingResponse.Value)?.diff
|
||||
@ -147,24 +160,26 @@ class TailwindDataType(
|
||||
val sign = if (headwindSpeed < 0) "+" else {
|
||||
if (headwindSpeed > 0) "-" else ""
|
||||
}
|
||||
"$sign${headwindSpeed.roundToInt().absoluteValue}"
|
||||
|
||||
val headwindSpeedUserUnit = msInUserUnit(headwindSpeed, streamData.isImperial)
|
||||
|
||||
"$sign${headwindSpeedUserUnit.roundToInt().absoluteValue}"
|
||||
}
|
||||
WindDirectionIndicatorTextSetting.WIND_SPEED -> windSpeed.roundToInt().toString()
|
||||
WindDirectionIndicatorTextSetting.WIND_SPEED -> msInUserUnit(windSpeed, streamData.isImperial).roundToInt().toString()
|
||||
WindDirectionIndicatorTextSetting.NONE -> ""
|
||||
}
|
||||
|
||||
val subtext = "${windSpeed.roundToInt()}-${streamData.gustSpeed?.roundToInt()}"
|
||||
val windSpeedUserUnit = msInUserUnit(windSpeed, streamData.isImperial)
|
||||
val gustSpeedUserUnit = msInUserUnit(streamData.gustSpeed ?: 0.0, streamData.isImperial)
|
||||
|
||||
val subtext = "${windSpeedUserUnit.roundToInt()}-${gustSpeedUserUnit.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 = if (streamData.isImperial){
|
||||
headwindSpeed / 2.23694 * 3.6
|
||||
} else {
|
||||
headwindSpeed
|
||||
}
|
||||
val windSpeedInKmh = headwindSpeed * 3.6
|
||||
dayColor = interpolateWindColor(windSpeedInKmh, false, context)
|
||||
nightColor = interpolateWindColor(windSpeedInKmh, true, context)
|
||||
}
|
||||
|
||||
@ -3,9 +3,15 @@ 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): Double {
|
||||
override fun getValue(data: WeatherData, userProfile: UserProfile): Double {
|
||||
return data.temperature
|
||||
}
|
||||
|
||||
override fun getFormatDataType(): String? {
|
||||
return DataType.Type.TEMPERATURE
|
||||
}
|
||||
}
|
||||
@ -91,14 +91,15 @@ class TemperatureForecastDataType(karooSystem: KarooSystemService) : ForecastDat
|
||||
timeLabel: String,
|
||||
dateLabel: String?,
|
||||
distance: Double?,
|
||||
isImperial: Boolean
|
||||
isImperial: Boolean,
|
||||
isNight: Boolean
|
||||
) {
|
||||
TemperatureForecast(
|
||||
temperature = temperature,
|
||||
temperatureUnit = temperatureUnit,
|
||||
distance = distance,
|
||||
timeLabel = timeLabel,
|
||||
isImperial = isImperial
|
||||
isImperial = isImperial,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,13 +16,19 @@ 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.weatherprovider.WeatherData
|
||||
import de.timklge.karooheadwind.weatherprovider.WeatherInterpretation
|
||||
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 io.hammerhead.karooext.KarooSystemService
|
||||
import io.hammerhead.karooext.extension.DataTypeImpl
|
||||
import io.hammerhead.karooext.internal.Emitter
|
||||
@ -40,6 +46,7 @@ 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
|
||||
@ -62,8 +69,7 @@ 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)))))
|
||||
}
|
||||
@ -74,14 +80,18 @@ class WeatherDataType(
|
||||
}
|
||||
|
||||
data class StreamData(val data: WeatherData?, val settings: HeadwindSettings,
|
||||
val profile: UserProfile? = null, val headingResponse: HeadingResponse? = null)
|
||||
val profile: UserProfile? = null, val headingResponse: HeadingResponse? = null,
|
||||
val isVisible: Boolean)
|
||||
|
||||
private fun previewFlow(): Flow<StreamData> = flow {
|
||||
while (true){
|
||||
emit(StreamData(
|
||||
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()))
|
||||
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))
|
||||
|
||||
delay(5_000)
|
||||
}
|
||||
@ -96,21 +106,29 @@ class WeatherDataType(
|
||||
|
||||
val baseBitmap = BitmapFactory.decodeResource(
|
||||
context.resources,
|
||||
de.timklge.karooheadwind.R.drawable.arrow_0
|
||||
R.drawable.arrow_0
|
||||
)
|
||||
|
||||
val dataFlow = if (config.preview){
|
||||
previewFlow()
|
||||
} else {
|
||||
combine(context.streamCurrentWeatherData(karooSystem), context.streamSettings(karooSystem), karooSystem.streamUserProfile(), karooSystem.getHeadingFlow(context)) { data, settings, profile, heading ->
|
||||
StreamData(data, settings, profile, heading)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
val viewJob = CoroutineScope(Dispatchers.IO).launch {
|
||||
emitter.onNext(ShowCustomStreamState("", null))
|
||||
|
||||
dataFlow.collect { (data, settings, userProfile, headingResponse) ->
|
||||
val refreshRate = karooSystem.getRefreshRateInMilliseconds(context)
|
||||
|
||||
dataFlow.filter { it.isVisible }.throttle(refreshRate).collect { (data, settings, userProfile, headingResponse) ->
|
||||
Log.d(KarooHeadwindExtension.TAG, "Updating weather view")
|
||||
|
||||
if (data == null){
|
||||
@ -125,18 +143,18 @@ class WeatherDataType(
|
||||
|
||||
val result = glance.compose(context, DpSize.Unspecified) {
|
||||
var modifier = GlanceModifier.fillMaxSize()
|
||||
if (!config.preview) modifier = modifier.clickable(onClick = actionStartActivity<MainActivity>())
|
||||
// TODO reenable once swipes are no longer interpreted as clicks if (!config.preview) modifier = modifier.clickable(onClick = actionStartActivity<MainActivity>())
|
||||
|
||||
Box(modifier = modifier, contentAlignment = Alignment.CenterEnd) {
|
||||
Weather(
|
||||
baseBitmap,
|
||||
current = interpretation,
|
||||
windBearing = data.windDirection.roundToInt(),
|
||||
windSpeed = data.windSpeed.roundToInt(),
|
||||
windGusts = data.windGusts.roundToInt(),
|
||||
precipitation = data.precipitation,
|
||||
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),
|
||||
precipitationProbability = null,
|
||||
temperature = data.temperature.roundToInt(),
|
||||
temperature = celciusInUserUnit(data.temperature, userProfile?.preferredUnit?.temperature == UserProfile.PreferredUnit.UnitType.IMPERIAL).roundToInt(),
|
||||
temperatureUnit = if (userProfile?.preferredUnit?.temperature != UserProfile.PreferredUnit.UnitType.IMPERIAL) TemperatureUnit.CELSIUS else TemperatureUnit.FAHRENHEIT,
|
||||
timeLabel = formattedTime,
|
||||
rowAlignment = when (config.alignment){
|
||||
@ -146,7 +164,8 @@ class WeatherDataType(
|
||||
},
|
||||
dateLabel = formattedDate,
|
||||
singleDisplay = true,
|
||||
isImperial = userProfile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL
|
||||
isImperial = userProfile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL,
|
||||
isNight = data.isNight,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,8 @@ class WeatherForecastDataType(karooSystem: KarooSystemService) : ForecastDataTyp
|
||||
timeLabel: String,
|
||||
dateLabel: String?,
|
||||
distance: Double?,
|
||||
isImperial: Boolean
|
||||
isImperial: Boolean,
|
||||
isNight: Boolean,
|
||||
) {
|
||||
Weather(
|
||||
arrowBitmap = arrowBitmap,
|
||||
@ -36,7 +37,8 @@ class WeatherForecastDataType(karooSystem: KarooSystemService) : ForecastDataTyp
|
||||
timeLabel = timeLabel,
|
||||
dateLabel = dateLabel,
|
||||
distance = distance,
|
||||
isImperial = isImperial
|
||||
isImperial = isImperial,
|
||||
isNight = isNight,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -45,14 +45,14 @@ fun getShortDateFormatter(): DateTimeFormatter = DateTimeFormatter.ofPattern(
|
||||
}
|
||||
).withZone(ZoneId.systemDefault())
|
||||
|
||||
fun getWeatherIcon(interpretation: WeatherInterpretation): Int {
|
||||
fun getWeatherIcon(interpretation: WeatherInterpretation, isNight: Boolean): Int {
|
||||
return when (interpretation){
|
||||
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.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.UNKNOWN -> R.drawable.question_mark_regular_240
|
||||
}
|
||||
}
|
||||
@ -74,7 +74,8 @@ fun Weather(
|
||||
rowAlignment: Alignment.Horizontal = Alignment.Horizontal.CenterHorizontally,
|
||||
dateLabel: String? = null,
|
||||
singleDisplay: Boolean = false,
|
||||
isImperial: Boolean?
|
||||
isImperial: Boolean?,
|
||||
isNight: Boolean
|
||||
) {
|
||||
|
||||
val fontSize = if (singleDisplay) 19f else 14f
|
||||
@ -83,10 +84,9 @@ fun Weather(
|
||||
Row(modifier = GlanceModifier.defaultWeight().wrapContentWidth(), horizontalAlignment = rowAlignment, verticalAlignment = Alignment.CenterVertically) {
|
||||
Image(
|
||||
modifier = GlanceModifier.defaultWeight().wrapContentWidth().padding(1.dp),
|
||||
provider = ImageProvider(getWeatherIcon(current)),
|
||||
provider = ImageProvider(getWeatherIcon(current, isNight)),
|
||||
contentDescription = "Current weather information",
|
||||
contentScale = ContentScale.Fit,
|
||||
colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White))
|
||||
)
|
||||
}
|
||||
|
||||
@ -142,7 +142,6 @@ fun Weather(
|
||||
provider = ImageProvider(R.drawable.thermometer),
|
||||
contentDescription = "Temperature",
|
||||
contentScale = ContentScale.Fit,
|
||||
colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White))
|
||||
)
|
||||
|
||||
Text(
|
||||
|
||||
@ -17,21 +17,25 @@ import androidx.glance.text.FontFamily
|
||||
import androidx.glance.text.Text
|
||||
import androidx.glance.text.TextStyle
|
||||
import de.timklge.karooheadwind.KarooHeadwindExtension
|
||||
import de.timklge.karooheadwind.weatherprovider.WeatherData
|
||||
import de.timklge.karooheadwind.streamDataFlow
|
||||
import de.timklge.karooheadwind.streamDatatypeIsVisible
|
||||
import de.timklge.karooheadwind.throttle
|
||||
import de.timklge.karooheadwind.weatherprovider.WeatherData
|
||||
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
|
||||
|
||||
@ -48,14 +52,22 @@ class WindDirectionDataType(val karooSystem: KarooSystemService, context: Contex
|
||||
)
|
||||
}
|
||||
|
||||
override fun getValue(data: WeatherData): Double {
|
||||
override fun getValue(data: WeatherData, userProfile: UserProfile): Double {
|
||||
return data.windDirection
|
||||
}
|
||||
|
||||
private fun previewFlow(): Flow<Double> {
|
||||
data class StreamData(
|
||||
val windBearing: Double,
|
||||
val isVisible: Boolean
|
||||
)
|
||||
|
||||
private fun previewFlow(): Flow<StreamData> {
|
||||
return flow {
|
||||
while (true) {
|
||||
emit((0..360).random().toDouble())
|
||||
emit(StreamData(
|
||||
(0..360).random().toDouble(),
|
||||
true
|
||||
))
|
||||
delay(1_000)
|
||||
}
|
||||
}
|
||||
@ -74,12 +86,20 @@ class WindDirectionDataType(val karooSystem: KarooSystemService, context: Contex
|
||||
val flow = if (config.preview){
|
||||
previewFlow()
|
||||
} else {
|
||||
karooSystem.streamDataFlow(dataTypeId)
|
||||
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue ?: 0.0 }
|
||||
combine(
|
||||
karooSystem.streamDataFlow(dataTypeId),
|
||||
karooSystem.streamDatatypeIsVisible(dataTypeId)
|
||||
) { windBearing, isVisible ->
|
||||
StreamData(
|
||||
windBearing = (windBearing as? StreamState.Streaming)?.dataPoint?.singleValue ?: 0.0,
|
||||
isVisible = isVisible
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
flow
|
||||
.collect { windBearing ->
|
||||
val refreshRate = karooSystem.getRefreshRateInMilliseconds(context)
|
||||
|
||||
flow.filter { it.isVisible }.throttle(refreshRate).collect { (windBearing, isVisible) ->
|
||||
val windCardinalDirectionIndex = ((windBearing % 360) / 22.5).roundToInt() % 16
|
||||
|
||||
val text = windDirections[windCardinalDirectionIndex]
|
||||
|
||||
@ -103,7 +103,8 @@ class WindForecastDataType(karooSystem: KarooSystemService) : ForecastDataType(k
|
||||
timeLabel: String,
|
||||
dateLabel: String?,
|
||||
distance: Double?,
|
||||
isImperial: Boolean
|
||||
isImperial: Boolean,
|
||||
isNight: Boolean
|
||||
) {
|
||||
WindForecast(
|
||||
arrowBitmap = arrowBitmap,
|
||||
@ -112,7 +113,7 @@ class WindForecastDataType(karooSystem: KarooSystemService) : ForecastDataType(k
|
||||
gustSpeed = windGusts,
|
||||
distance = distance,
|
||||
timeLabel = timeLabel,
|
||||
isImperial = isImperial
|
||||
isImperial = isImperial,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -3,9 +3,10 @@ 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): Double {
|
||||
override fun getValue(data: WeatherData, userProfile: UserProfile): Double {
|
||||
return data.windGusts
|
||||
}
|
||||
}
|
||||
@ -3,9 +3,10 @@ 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): Double {
|
||||
override fun getValue(data: WeatherData, userProfile: UserProfile): Double {
|
||||
return data.windSpeed
|
||||
}
|
||||
}
|
||||
@ -84,6 +84,7 @@ 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 ->
|
||||
@ -102,6 +103,7 @@ fun SettingsScreen(onFinish: () -> Unit) {
|
||||
LaunchedEffect(Unit) {
|
||||
karooSystem.connect { connected ->
|
||||
karooConnected = connected
|
||||
isK2 = karooSystem.hardwareType == io.hammerhead.karooext.models.HardwareType.K2
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,8 +153,8 @@ fun SettingsScreen(onFinish: () -> Unit) {
|
||||
.verticalScroll(rememberScrollState())
|
||||
.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
val refreshRateDropdownOptions = RefreshRate.entries.toList().map { unit -> DropdownOption(unit.id, unit.getDescription(karooSystem)) }
|
||||
val refreshRateSelection by remember(refreshRateSetting) {
|
||||
val refreshRateDropdownOptions = remember(isK2) { RefreshRate.entries.toList().map { unit -> DropdownOption(unit.id, unit.getDescription(isK2)) } }
|
||||
val refreshRateSelection by remember(refreshRateSetting, isK2) {
|
||||
mutableStateOf(refreshRateDropdownOptions.find { option -> option.id == refreshRateSetting.id }!!)
|
||||
}
|
||||
Dropdown(
|
||||
@ -160,8 +162,7 @@ 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 =
|
||||
|
||||
@ -37,6 +37,9 @@ 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
|
||||
@ -110,16 +113,17 @@ fun WeatherScreen(onFinish: () -> Unit) {
|
||||
baseBitmap = baseBitmap,
|
||||
current = WeatherInterpretation.fromWeatherCode(currentWeatherData?.weatherCode),
|
||||
windBearing = currentWeatherData?.windDirection?.roundToInt() ?: 0,
|
||||
windSpeed = currentWeatherData?.windSpeed?.roundToInt() ?: 0,
|
||||
windGusts = currentWeatherData?.windGusts?.roundToInt() ?: 0,
|
||||
precipitation = currentWeatherData?.precipitation ?: 0.0,
|
||||
temperature = currentWeatherData?.temperature?.toInt() ?: 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(),
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
@ -229,17 +233,18 @@ fun WeatherScreen(onFinish: () -> Unit) {
|
||||
baseBitmap,
|
||||
current = interpretation,
|
||||
windBearing = weatherData?.windDirection?.roundToInt() ?: 0,
|
||||
windSpeed = weatherData?.windSpeed?.roundToInt() ?: 0,
|
||||
windGusts = weatherData?.windGusts?.roundToInt() ?: 0,
|
||||
precipitation = weatherData?.precipitation ?: 0.0,
|
||||
temperature = weatherData?.temperature?.toInt() ?: 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(),
|
||||
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
|
||||
isImperial = profile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL,
|
||||
isNight = weatherData?.isNight == true,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ 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
|
||||
@ -46,9 +47,10 @@ fun WeatherWidget(
|
||||
distance: Double? = null,
|
||||
includeDistanceLabel: Boolean = false,
|
||||
precipitationProbability: Int? = null,
|
||||
isImperial: Boolean
|
||||
isImperial: Boolean,
|
||||
isNight: Boolean
|
||||
) {
|
||||
val fontSize = 20.sp
|
||||
val fontSize = 18.sp
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(5.dp),
|
||||
@ -97,22 +99,20 @@ fun WeatherWidget(
|
||||
}
|
||||
}
|
||||
|
||||
// Weather icon (larger)
|
||||
Icon(
|
||||
painter = painterResource(id = getWeatherIcon(current)),
|
||||
Image(
|
||||
painter = painterResource(id = getWeatherIcon(current, isNight)),
|
||||
contentDescription = "Current weather",
|
||||
modifier = Modifier.size(72.dp)
|
||||
)
|
||||
|
||||
Column(horizontalAlignment = Alignment.End) {
|
||||
// Temperature (larger)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
Image(
|
||||
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 ""
|
||||
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.droplet_regular),
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.droplet),
|
||||
contentDescription = "Precipitation",
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -6,16 +6,17 @@ import kotlinx.serialization.Serializable
|
||||
data class WeatherData(
|
||||
val time: Long,
|
||||
val temperature: Double,
|
||||
val relativeHumidity: Double? = null,
|
||||
val relativeHumidity: Int,
|
||||
val precipitation: Double,
|
||||
val precipitationProbability: Double? = null,
|
||||
val cloudCover: Double? = null,
|
||||
val sealevelPressure: Double? = null,
|
||||
val surfacePressure: Double? = null,
|
||||
val cloudCover: Double,
|
||||
val sealevelPressure: Double,
|
||||
val surfacePressure: Double,
|
||||
val windSpeed: Double,
|
||||
val windDirection: Double,
|
||||
val windGusts: Double,
|
||||
val weatherCode: Int,
|
||||
val isForecast: Boolean
|
||||
val isForecast: Boolean,
|
||||
val isNight: Boolean
|
||||
)
|
||||
|
||||
|
||||
@ -12,15 +12,16 @@ 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? = null,
|
||||
@SerialName("pressure_msl") val sealevelPressure: Double,
|
||||
@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.toDouble(),
|
||||
relativeHumidity = relativeHumidity,
|
||||
precipitation = precipitation,
|
||||
cloudCover = cloudCover.toDouble(),
|
||||
surfacePressure = surfacePressure,
|
||||
@ -31,6 +32,7 @@ data class OpenMeteoWeatherData(
|
||||
weatherCode = weatherCode,
|
||||
time = time,
|
||||
isForecast = false,
|
||||
isNight = isDay == 0,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -14,6 +14,11 @@ 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 ->
|
||||
@ -25,8 +30,13 @@ 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],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,10 +3,7 @@ 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
|
||||
@ -28,16 +25,12 @@ import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class OpenMeteoWeatherProvider : WeatherProvider {
|
||||
@OptIn(FlowPreview::class)
|
||||
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
|
||||
|
||||
private suspend fun makeOpenMeteoWeatherRequest(karooSystemService: KarooSystemService, gpsCoordinates: List<GpsCoordinates>): HttpResponseState.Complete {
|
||||
val response = callbackFlow {
|
||||
// https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41¤t=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¤t=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
|
||||
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}¤t=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}"
|
||||
val url = "https://api.open-meteo.com/v1/forecast?latitude=${lats}&longitude=${lons}¤t=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"
|
||||
|
||||
Log.d(KarooHeadwindExtension.TAG, "Http request to ${url}...")
|
||||
|
||||
@ -84,7 +77,7 @@ class OpenMeteoWeatherProvider : WeatherProvider {
|
||||
settings: HeadwindSettings,
|
||||
profile: UserProfile?
|
||||
): WeatherDataResponse {
|
||||
val openMeteoResponse = makeOpenMeteoWeatherRequest(karooSystem, coordinates, settings, profile)
|
||||
val openMeteoResponse = makeOpenMeteoWeatherRequest(karooSystem, coordinates)
|
||||
val responseBody = openMeteoResponse.body?.let { String(it) } ?: throw WeatherProviderException(500, "Null response from OpenMeteo")
|
||||
|
||||
val weatherData = if (coordinates.size == 1) {
|
||||
|
||||
@ -2,6 +2,8 @@ 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(
|
||||
@ -20,9 +22,18 @@ data class OpenWeatherMapForecastData(
|
||||
val snow: Snow? = null,
|
||||
val weather: List<Weather>
|
||||
) {
|
||||
fun toWeatherData(): WeatherData = WeatherData(
|
||||
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(
|
||||
temperature = temp,
|
||||
relativeHumidity = humidity.toDouble(),
|
||||
relativeHumidity = humidity,
|
||||
precipitation = rain?.h1 ?: 0.0,
|
||||
cloudCover = clouds.toDouble(),
|
||||
surfacePressure = pressure.toDouble(),
|
||||
@ -34,6 +45,8 @@ data class OpenWeatherMapForecastData(
|
||||
weather.firstOrNull()?.id ?: 800
|
||||
),
|
||||
time = dt,
|
||||
isForecast = true
|
||||
isForecast = true,
|
||||
isNight = dtTime.isBefore(sunriseTime) || dtTime.isAfter(sunsetTime)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ data class OpenWeatherMapWeatherData(
|
||||
|
||||
fun toWeatherData(): WeatherData = WeatherData(
|
||||
temperature = temp,
|
||||
relativeHumidity = humidity.toDouble(),
|
||||
relativeHumidity = humidity,
|
||||
precipitation = rain?.h1 ?: 0.0,
|
||||
cloudCover = clouds.toDouble(),
|
||||
surfacePressure = pressure.toDouble(),
|
||||
@ -35,6 +35,9 @@ data class OpenWeatherMapWeatherData(
|
||||
weather.firstOrNull()?.id ?: 800
|
||||
),
|
||||
time = dt,
|
||||
isNight = let {
|
||||
dt !in sunrise..<sunset
|
||||
},
|
||||
isForecast = false
|
||||
)
|
||||
}
|
||||
@ -14,10 +14,17 @@ data class OpenWeatherMapWeatherDataForLocation(
|
||||
val current: OpenWeatherMapWeatherData,
|
||||
val hourly: List<OpenWeatherMapForecastData>
|
||||
){
|
||||
fun toWeatherDataForLocation(distanceAlongRoute: Double?): WeatherDataForLocation = WeatherDataForLocation(
|
||||
fun toWeatherDataForLocation(distanceAlongRoute: Double?): WeatherDataForLocation {
|
||||
return 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() }
|
||||
forecasts = hourly.map { it.toWeatherData(current) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -69,7 +69,7 @@ class OpenWeatherMapWeatherProvider(private val apiKey: String) : WeatherProvide
|
||||
profile: UserProfile?
|
||||
): WeatherDataResponse {
|
||||
|
||||
val response = makeOpenWeatherMapRequest(karooSystem, coordinates, apiKey, profile)
|
||||
val response = makeOpenWeatherMapRequest(karooSystem, coordinates, apiKey)
|
||||
val responseBody = response.body?.let { String(it) } ?: throw Exception("Null response from OpenWeatherMap")
|
||||
|
||||
val responses = mutableListOf<WeatherDataForLocation>()
|
||||
@ -89,21 +89,15 @@ class OpenWeatherMapWeatherProvider(private val apiKey: String) : WeatherProvide
|
||||
private suspend fun makeOpenWeatherMapRequest(
|
||||
service: KarooSystemService,
|
||||
coordinates: List<GpsCoordinates>,
|
||||
apiKey: String,
|
||||
profile: UserProfile?
|
||||
apiKey: String
|
||||
): 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=${unitsString}"
|
||||
"&appid=$apiKey&exclude=minutely,daily,alerts&units=metric"
|
||||
|
||||
Log.d(KarooHeadwindExtension.TAG, "Http request to OpenWeatherMap API 3.0: $url")
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
40
app/src/main/res/drawable/cloud.xml
Normal file
@ -0,0 +1,40 @@
|
||||
<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>
|
||||
49
app/src/main/res/drawable/cloud_with_light_rain.xml
Normal file
@ -0,0 +1,49 @@
|
||||
<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>
|
||||
44
app/src/main/res/drawable/cloud_with_lightning.xml
Normal file
@ -0,0 +1,44 @@
|
||||
<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>
|
||||
92
app/src/main/res/drawable/cloud_with_lightning_and_rain.xml
Normal file
@ -0,0 +1,92 @@
|
||||
<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>
|
||||
73
app/src/main/res/drawable/cloud_with_rain.xml
Normal file
@ -0,0 +1,73 @@
|
||||
<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>
|
||||
55
app/src/main/res/drawable/cloud_with_snow.xml
Normal file
@ -0,0 +1,55 @@
|
||||
<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>
|
||||
84
app/src/main/res/drawable/crescent_moon.xml
Normal file
@ -0,0 +1,84 @@
|
||||
<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>
|
||||
23
app/src/main/res/drawable/droplet.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 5.9 KiB |
43
app/src/main/res/drawable/sun.xml
Normal file
@ -0,0 +1,43 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 5.6 KiB |
38
app/src/main/res/drawable/thermometer.xml
Normal file
@ -0,0 +1,38 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 4.9 KiB |
@ -3,6 +3,4 @@ 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
|
||||
}
|
||||
@ -11,20 +11,15 @@ 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.3" }
|
||||
hammerhead-karoo-ext = { group = "io.hammerhead", name = "karoo-ext", version = "1.1.5" }
|
||||
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }
|
||||
|
||||
# compose
|
||||
@ -43,7 +38,6 @@ 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"]
|
||||
|
||||