diff --git a/README.md b/README.md index e826031..dd258f1 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,10 @@ Compatible with Karoo 2 and Karoo 3 devices. Buy Me A Coffee -![Settings](preview0.png) -![Field setup](preview1.png) -![Data page](preview2.png) +![Page](preview0.png) +![Field](preview1.png) +![Overview](preview2.png) +![Setup](preview3.png) ## Installation @@ -37,7 +38,8 @@ After installing this app on your Karoo and opening it once from the main menu, - Headwind (graphical, 1x1 field): Shows the headwind direction and speed as a circle with a triangular direction indicator. The speed is shown at the center in your set unit of measurement (default is kilometers per hour if you have set up metric units in your Karoo, otherwise miles per hour). Both direction and speed are relative to the current riding direction by default, i. e., riding directly into a wind of 20 km/h will show a headwind speed of 20 km/h, while riding in the same direction will show -20 km/h. You can change this behavior in the app settings to show the absolute wind direction and speed instead. - 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. -- 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. +- 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. - 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. The app will automatically attempt to download weather data for your current approximate location from the [open-meteo.com](https://open-meteo.com) API once your device has acquired a GPS fix. The API service is free for non-commercial use. Your location is rounded to approximately two kilometers to maintain privacy. The data is updated when you ride more than two kilometers from the location where the weather data was downloaded or after one hour at the latest. If the app cannot connect to the weather service, it will retry the download every minute. Downloading weather data should work on Karoo 2 if you have a SIM card inserted or on Karoo 3 via your phone's internet connection if you have the Karoo companion app installed. diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/HeadwindSettings.kt b/app/src/main/kotlin/de/timklge/karooheadwind/HeadwindSettings.kt index 8e378c0..fbf826f 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/HeadwindSettings.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/HeadwindSettings.kt @@ -68,6 +68,7 @@ data class HeadwindSettings( val roundLocationTo: RoundLocationSetting = RoundLocationSetting.KM_3, val forecastedKmPerHour: Int = 20, val forecastedMilesPerHour: Int = 12, + val lastUpdateRequested: Long? = null, ){ companion object { val defaultSettings = Json.encodeToString(HeadwindSettings()) diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/screens/MainScreen.kt b/app/src/main/kotlin/de/timklge/karooheadwind/screens/MainScreen.kt index e957389..588973f 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/screens/MainScreen.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/screens/MainScreen.kt @@ -3,7 +3,6 @@ package de.timklge.karooheadwind.screens import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -14,10 +13,13 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Tab import androidx.compose.material3.TabRow import androidx.compose.material3.Text +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -37,8 +39,11 @@ import de.timklge.karooheadwind.R import de.timklge.karooheadwind.saveSettings import de.timklge.karooheadwind.streamSettings import io.hammerhead.karooext.KarooSystemService +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +@OptIn(ExperimentalMaterial3Api::class) @Composable fun MainScreen(close: () -> Unit) { var karooConnected by remember { mutableStateOf(false) } @@ -49,8 +54,29 @@ fun MainScreen(close: () -> Unit) { var welcomeDialogVisible by remember { mutableStateOf(false) } var tabIndex by remember { mutableIntStateOf(0) } + var isRefreshing by remember { mutableStateOf(false) } + val swipeRefreshState = rememberPullToRefreshState() + val tabs = listOf("Weather", "Settings") + fun refreshData() { + coroutineScope.launch { + isRefreshing = true + // Set the lastUpdateRequested value to trigger a weather update in the KarooHeadwindExtension + val settings = ctx.streamSettings(karooSystem).first() + saveSettings(ctx, settings.copy(lastUpdateRequested = System.currentTimeMillis())) + delay(1000) // Give some time to show the refreshing indicator + isRefreshing = false + } + } + + LaunchedEffect(isRefreshing) { + if (isRefreshing) { + delay(2000) // Timeout after 2 seconds if the refresh doesn't complete + isRefreshing = false + } + } + fun onFinish() { if (tabIndex > 0){ tabIndex-- @@ -77,7 +103,7 @@ fun MainScreen(close: () -> Unit) { } } - Box(modifier = Modifier.fillMaxSize()) { + PullToRefreshBox(modifier = Modifier.fillMaxSize(), isRefreshing = isRefreshing, onRefresh = { refreshData() }) { Column(modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background)) { diff --git a/preview2.png b/preview2.png index 1231cb3..cb08103 100644 Binary files a/preview2.png and b/preview2.png differ diff --git a/preview3.png b/preview3.png new file mode 100644 index 0000000..2f29839 Binary files /dev/null and b/preview3.png differ