diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/HeadingFlow.kt b/app/src/main/kotlin/de/timklge/karooheadwind/HeadingFlow.kt index 4e9bf6b..a7f3268 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/HeadingFlow.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/HeadingFlow.kt @@ -5,6 +5,7 @@ import android.util.Log import de.timklge.karooheadwind.datatypes.GpsCoordinates import de.timklge.karooheadwind.util.signedAngleDifference import io.hammerhead.karooext.KarooSystemService +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -90,7 +91,8 @@ suspend fun KarooSystemService.updateLastKnownGps(context: Context) { fun KarooSystemService.getGpsCoordinateFlow(context: Context): Flow { /* return flow { - emit(GpsCoordinates(52.5164069,13.3784)) + // emit(GpsCoordinates(52.5164069,13.3784)) + emit(GpsCoordinates(32.46,-111.524)) awaitCancellation() } */ diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/CycleHoursAction.kt b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/CycleHoursAction.kt index 47f3103..2e18bc7 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/CycleHoursAction.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/CycleHoursAction.kt @@ -25,9 +25,9 @@ class CycleHoursAction : ActionCallback { var hourOffset = currentSettings.currentForecastHourOffset + 3 val requestedPositions = forecastData?.data?.size - val requestedHours = forecastData?.data?.firstOrNull()?.forecasts?.size + val requestedHours = forecastData?.data?.firstOrNull()?.forecasts?.size?.coerceAtMost(6) - if (forecastData == null || requestedHours == null || requestedPositions == null || hourOffset >= requestedHours || (requestedPositions in 2..hourOffset)) { + if (forecastData == null || requestedHours == null || requestedPositions == null || (requestedPositions == 1 && hourOffset >= requestedHours) || (requestedPositions in 2..hourOffset)) { hourOffset = 0 } diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/ForecastDataType.kt b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/ForecastDataType.kt index 83025d0..49077b2 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/ForecastDataType.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/ForecastDataType.kt @@ -38,6 +38,7 @@ 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.getTimeFormatter import de.timklge.karooheadwind.util.millimetersInUserUnit import de.timklge.karooheadwind.util.msInUserUnit import de.timklge.karooheadwind.weatherprovider.WeatherData @@ -89,10 +90,6 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ @OptIn(ExperimentalGlanceRemoteViewsApi::class) private val glance = GlanceRemoteViews() - companion object { - val timeFormatter = DateTimeFormatter.ofPattern("HH:mm").withZone(ZoneId.systemDefault()) - } - data class StreamData(val data: WeatherDataResponse?, val settings: SettingsAndProfile, val widgetSettings: HeadwindWidgetSettings? = null, val headingResponse: HeadingResponse? = null, val upcomingRoute: UpcomingRoute? = null, val isVisible: Boolean) @@ -304,13 +301,22 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ val isCurrent = baseIndex == 0 && positionIndex == 0 + val time = if (isCurrent && data?.current != null) { + Instant.ofEpochSecond(data.current.time) + } else { + Instant.ofEpochSecond(data?.forecasts?.getOrNull(baseIndex)?.time ?: 0) + } + + if (time.isBefore(Instant.now().minus(1, ChronoUnit.HOURS)) || (upcomingRoute == null && time.isAfter(Instant.now().plus(6, ChronoUnit.HOURS)))) { + Log.d(KarooHeadwindExtension.TAG, "Skipping forecast data for time $time as it is in the past or too close to now") + continue + } + if (isCurrent && data?.current != null) { val interpretation = WeatherInterpretation.fromWeatherCode(data.current.weatherCode) val unixTime = data.current.time - val formattedTime = - timeFormatter.format(Instant.ofEpochSecond(unixTime)) - val formattedDate = - getShortDateFormatter().format(Instant.ofEpochSecond(unixTime)) + val formattedTime = getTimeFormatter(context).format(Instant.ofEpochSecond(unixTime).atZone(ZoneId.systemDefault()).toLocalTime()) + val formattedDate = getShortDateFormatter().format(Instant.ofEpochSecond(unixTime).atZone(ZoneId.systemDefault())) val hasNewDate = formattedDate != previousDate || baseIndex == 0 RenderWidget( @@ -335,7 +341,7 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ val weatherData = data?.forecasts?.getOrNull(baseIndex) val interpretation = WeatherInterpretation.fromWeatherCode(weatherData?.weatherCode ?: 0) val unixTime = data?.forecasts?.getOrNull(baseIndex)?.time ?: 0 - val formattedTime = timeFormatter.format(Instant.ofEpochSecond(unixTime)) + val formattedTime = getTimeFormatter(context).format(Instant.ofEpochSecond(unixTime).atZone(ZoneId.systemDefault()).toLocalTime()) val formattedDate = getShortDateFormatter().format(Instant.ofEpochSecond(unixTime)) val hasNewDate = formattedDate != previousDate || baseIndex == 0 diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/LineGraphForecastDataType.kt b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/LineGraphForecastDataType.kt index 31bb692..746d470 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/LineGraphForecastDataType.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/LineGraphForecastDataType.kt @@ -25,6 +25,7 @@ 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.getTimeFormatter import de.timklge.karooheadwind.weatherprovider.WeatherData import de.timklge.karooheadwind.weatherprovider.WeatherDataForLocation import de.timklge.karooheadwind.weatherprovider.WeatherDataResponse @@ -49,7 +50,6 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch import java.time.Instant import java.time.ZoneId -import java.time.format.DateTimeFormatter import java.time.temporal.ChronoUnit import kotlin.math.abs import kotlin.math.ceil @@ -59,10 +59,6 @@ abstract class LineGraphForecastDataType(private val karooSystem: KarooSystemSer @OptIn(ExperimentalGlanceRemoteViewsApi::class) private val glance = GlanceRemoteViews() - companion object { - val timeFormatter = DateTimeFormatter.ofPattern("HH:mm").withZone(ZoneId.of("UTC")) - } - data class StreamData(val data: WeatherDataResponse?, val settings: SettingsAndProfile, val widgetSettings: HeadwindWidgetSettings? = null, val headingResponse: HeadingResponse? = null, val upcomingRoute: UpcomingRoute? = null, val isVisible: Boolean) @@ -252,6 +248,11 @@ abstract class LineGraphForecastDataType(private val karooSystem: KarooSystemSer val time = Instant.ofEpochSecond(data.time) + if (time.isBefore(Instant.now().minus(1, ChronoUnit.HOURS)) || (locationData?.coords?.distanceAlongRoute == null && time.isAfter(Instant.now().plus(6, ChronoUnit.HOURS)))) { + Log.d(KarooHeadwindExtension.TAG, "Skipping forecast data for time $time as it is in the past or too close to now") + continue + } + add(LineData( time = time, distance = locationData?.coords?.distanceAlongRoute?.toFloat(), @@ -264,7 +265,7 @@ abstract class LineGraphForecastDataType(private val karooSystem: KarooSystemSer val bitmap = LineGraphBuilder(context).drawLineGraph(config.viewSize.first, config.viewSize.second, config.gridSize.first, config.gridSize.second, pointData) { x -> val startTime = data.firstOrNull()?.time val time = startTime?.plus(floor(x).toLong(), ChronoUnit.HOURS) - val timeLabel = timeFormatter.format(time) + val timeLabel = getTimeFormatter(context).format(time?.atZone(ZoneId.systemDefault())?.toLocalTime()) val beforeData = data.getOrNull(floor(x).toInt().coerceAtLeast(0)) val afterData = data.getOrNull(ceil(x).toInt().coerceAtMost(data.size - 1)) diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/screens/LineGraph.kt b/app/src/main/kotlin/de/timklge/karooheadwind/screens/LineGraph.kt index 1d7e88f..26c6823 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/screens/LineGraph.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/screens/LineGraph.kt @@ -11,6 +11,7 @@ import android.graphics.Path import androidx.annotation.ColorInt import kotlin.math.abs import androidx.core.graphics.createBitmap +import kotlin.math.roundToInt class LineGraphBuilder(val context: Context) { enum class YAxis { @@ -120,26 +121,18 @@ class LineGraphBuilder(val context: Context) { val yLabelStringsLeft = mutableListOf() val numYTicksForCalc = 2 // As used later for drawing Y-axis ticks + val minRange = numYTicksForCalc.toFloat() + if (dataMaxYLeft - dataMinYLeft < minRange) { + dataMaxYLeft += minRange - (dataMaxYLeft - dataMinYLeft) + } + // Determine Y-axis label strings (mirrors logic from where labels are drawn) if (abs(dataMaxYLeft - dataMinYLeft) < 0.0001f) { - yLabelStringsLeft.add( - String.format( - java.util.Locale.getDefault(), - "%.0f", - dataMinYLeft - ) - ) + yLabelStringsLeft.add(dataMinYLeft.roundToInt().toString()) } else { for (i in 0..numYTicksForCalc) { - val value = - dataMinYLeft + ((dataMaxYLeft - dataMinYLeft) / numYTicksForCalc) * i - yLabelStringsLeft.add( - String.format( - java.util.Locale.getDefault(), - "%.0f", - value - ) - ) + val value = dataMinYLeft + ((dataMaxYLeft - dataMinYLeft) / numYTicksForCalc) * i + yLabelStringsLeft.add(value.roundToInt().toString()) } } @@ -161,25 +154,18 @@ class LineGraphBuilder(val context: Context) { val yLabelStringsRight = mutableListOf() val numYTicksForCalc = 2 // As used later for drawing Y-axis ticks + // Adjust Y-axis range based on numYTicksForCalc. + val minRange = numYTicksForCalc.toFloat() + if (dataMaxYRight - dataMinYRight < minRange) { + dataMaxYRight += minRange - (dataMaxYRight - dataMinYRight) + } + if (abs(dataMaxYRight - dataMinYRight) < 0.0001f) { - yLabelStringsRight.add( - String.format( - java.util.Locale.getDefault(), - "%.0f", - dataMinYRight - ) - ) + yLabelStringsRight.add(dataMinYRight.roundToInt().toString()) } else { for (i in 0..numYTicksForCalc) { - val value = - dataMinYRight + ((dataMaxYRight - dataMinYRight) / numYTicksForCalc) * i - yLabelStringsRight.add( - String.format( - java.util.Locale.getDefault(), - "%.0f", - value - ) - ) + val value = dataMinYRight + ((dataMaxYRight - dataMinYRight) / numYTicksForCalc) * i + yLabelStringsRight.add(value.roundToInt().toString()) } } @@ -405,7 +391,7 @@ class LineGraphBuilder(val context: Context) { // Draw faint horizontal grid line canvas.drawLine(graphLeft, yPos, graphRight, yPos, gridLinePaint) canvas.drawText( - String.format(java.util.Locale.getDefault(), "%.0f", value), + value.roundToInt().toString(), graphLeft - 15f, yPos + (textPaint.textSize / 3), textPaint @@ -418,7 +404,7 @@ class LineGraphBuilder(val context: Context) { // Draw faint horizontal grid line canvas.drawLine(graphLeft, yPos, graphRight, yPos, gridLinePaint) canvas.drawText( - String.format(java.util.Locale.getDefault(), "%.0f", dataMinYLeft), + dataMinYLeft.roundToInt().toString(), graphLeft - 15f, yPos + (textPaint.textSize / 3), textPaint @@ -445,7 +431,7 @@ class LineGraphBuilder(val context: Context) { // Draw faint horizontal grid line canvas.drawLine(graphLeft, yPos, graphRight, yPos, gridLinePaint) canvas.drawText( - String.format(java.util.Locale.getDefault(), "%.0f", value), + value.roundToInt().toString(), graphRight + 15f, yPos + (textPaint.textSize / 3), textPaint @@ -464,7 +450,7 @@ class LineGraphBuilder(val context: Context) { // Draw faint horizontal grid line canvas.drawLine(graphLeft, yPos, graphRight, yPos, gridLinePaint) canvas.drawText( - String.format(java.util.Locale.getDefault(), "%.0f", dataMinYRight), + dataMinYRight.roundToInt().toString(), graphRight + 15f, yPos + (textPaint.textSize / 3), textPaint @@ -500,7 +486,7 @@ class LineGraphBuilder(val context: Context) { } textPaint.textAlign = Align.CENTER - val numXTicks = if (gridHeight > 15) 3 else 1 + val numXTicks = if (gridHeight > 15) 2 else 1 if (abs(dataMaxX - dataMinX) > 0.0001f) { for (i in 0..numXTicks) { val value = dataMinX + ((dataMaxX - dataMinX) / numXTicks) * i diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/screens/WeatherScreen.kt b/app/src/main/kotlin/de/timklge/karooheadwind/screens/WeatherScreen.kt index d48c6bd..65c3d31 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/screens/WeatherScreen.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/screens/WeatherScreen.kt @@ -27,7 +27,6 @@ import de.timklge.karooheadwind.HeadwindStats import de.timklge.karooheadwind.R import de.timklge.karooheadwind.ServiceStatusSingleton import de.timklge.karooheadwind.TemperatureUnit -import de.timklge.karooheadwind.datatypes.ForecastDataType import de.timklge.karooheadwind.datatypes.getShortDateFormatter import de.timklge.karooheadwind.getGpsCoordinateFlow import de.timklge.karooheadwind.streamCurrentForecastWeatherData @@ -36,6 +35,7 @@ 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.getTimeFormatter import de.timklge.karooheadwind.util.millimetersInUserUnit import de.timklge.karooheadwind.util.msInUserUnit import de.timklge.karooheadwind.weatherprovider.WeatherInterpretation @@ -43,6 +43,7 @@ import io.hammerhead.karooext.KarooSystemService import io.hammerhead.karooext.models.UserProfile import java.time.Instant import java.time.LocalDateTime +import java.time.ZoneId import java.time.ZoneOffset import java.time.temporal.ChronoUnit import kotlin.math.roundToInt @@ -104,7 +105,7 @@ fun WeatherScreen(onFinish: () -> Unit) { val requestedWeatherPosition = forecastData?.data?.firstOrNull()?.coords - val formattedTime = currentWeatherData?.let { ForecastDataType.timeFormatter.format(Instant.ofEpochSecond(it.time)) } + val formattedTime = currentWeatherData?.let { getTimeFormatter(ctx).format(Instant.ofEpochSecond(it.time).atZone(ZoneId.systemDefault()).toLocalTime()) } val formattedDate = currentWeatherData?.let { getShortDateFormatter().format(Instant.ofEpochSecond(it.time)) } if (karooConnected == true && currentWeatherData != null) { @@ -225,7 +226,7 @@ fun WeatherScreen(onFinish: () -> Unit) { val weatherData = data?.forecasts?.getOrNull(index) val interpretation = WeatherInterpretation.fromWeatherCode(weatherData?.weatherCode ?: 0) val unixTime = weatherData?.time ?: 0 - val formattedForecastTime = ForecastDataType.timeFormatter.format(Instant.ofEpochSecond(unixTime)) + val formattedForecastTime = getTimeFormatter(ctx).format(Instant.ofEpochSecond(unixTime).atZone(ZoneId.systemDefault()).toLocalTime()) val formattedForecastDate = getShortDateFormatter().format(Instant.ofEpochSecond(unixTime)) WeatherWidget( diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/util/TimeFormat.kt b/app/src/main/kotlin/de/timklge/karooheadwind/util/TimeFormat.kt new file mode 100644 index 0000000..a7c76c6 --- /dev/null +++ b/app/src/main/kotlin/de/timklge/karooheadwind/util/TimeFormat.kt @@ -0,0 +1,15 @@ +package de.timklge.karooheadwind.util + +import android.content.Context +import android.text.format.DateFormat +import java.time.format.DateTimeFormatter + +fun getTimeFormatter(context: Context): DateTimeFormatter { + val is24HourFormat = DateFormat.is24HourFormat(context) + + return if (is24HourFormat) { + DateTimeFormatter.ofPattern("HH:mm") + } else { + DateTimeFormatter.ofPattern("h a") + } +} \ No newline at end of file