From e70e3ce5dd6c95a2bc09ceeed63ae2d78a395926 Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Sat, 29 Nov 2025 23:16:43 +0100 Subject: [PATCH] Use 12h/24h preference in weather widget --- .../launcher/widgets/weather/WeatherWidget.kt | 40 ++++++++++++++---- .../widgets/weather/WeatherWidgetVM.kt | 41 +++++++++++-------- .../preferences/weather/WeatherSettings.kt | 4 ++ 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidget.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidget.kt index b4195c17e..418c6fee3 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidget.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidget.kt @@ -4,6 +4,7 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.net.Uri +import android.text.format.DateFormat import android.text.format.DateUtils import androidx.appcompat.app.AppCompatActivity import androidx.compose.animation.AnimatedVisibility @@ -71,6 +72,7 @@ import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.ktx.tryStartActivity import de.mm20.launcher2.preferences.MeasurementSystem +import de.mm20.launcher2.preferences.TimeFormat import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.common.WeatherLocationSearchDialog import de.mm20.launcher2.ui.component.Banner @@ -82,9 +84,10 @@ import de.mm20.launcher2.ui.theme.transparency.transparency import de.mm20.launcher2.weather.DailyForecast import de.mm20.launcher2.weather.Forecast import de.mm20.launcher2.widgets.WeatherWidget -import java.text.DateFormat +import kotlinx.coroutines.flow.map import java.text.DecimalFormat import java.text.SimpleDateFormat +import java.util.Locale import kotlin.math.roundToInt @Composable @@ -103,6 +106,15 @@ fun WeatherWidget(widget: WeatherWidget) { val selectedForecast by viewModel.currentForecast val measurementSystem by viewModel.measurementSystem.collectAsState() + val timeFormat by remember(context) { + viewModel.timeFormat.map { + if (it == TimeFormat.System) { + if (android.text.format.DateFormat.is24HourFormat(context)) TimeFormat.TwentyFourHour else TimeFormat.TwelveHour + } else { + it + } + } + }.collectAsState(TimeFormat.TwentyFourHour) val compactMode = !widget.config.showForecast val isProviderAvailable by viewModel.isProviderAvailable.collectAsStateWithLifecycle(true) @@ -170,7 +182,7 @@ fun WeatherWidget(widget: WeatherWidget) { } - CurrentWeather(forecast, measurementSystem) + CurrentWeather(forecast, measurementSystem, timeFormat) if (!compactMode) { @@ -189,6 +201,7 @@ fun WeatherWidget(widget: WeatherWidget) { forecasts = currentDayForecasts, selectedForecast = forecast, measurementSystem = measurementSystem, + timeFormat = timeFormat, onTimeSelected = { viewModel.selectForecast(it) }, @@ -211,7 +224,11 @@ fun WeatherWidget(widget: WeatherWidget) { } @Composable -fun CurrentWeather(forecast: Forecast, measurementSystem: MeasurementSystem) { +fun CurrentWeather( + forecast: Forecast, + measurementSystem: MeasurementSystem, + timeFormat: TimeFormat, +) { val context = LocalContext.current val weatherApp = remember { context.packageManager.resolveActivity( @@ -223,7 +240,8 @@ fun CurrentWeather(forecast: Forecast, measurementSystem: MeasurementSystem) { val useFahrenheit = measurementSystem == MeasurementSystem.UnitedStates val useInches = measurementSystem == MeasurementSystem.UnitedStates - val useMph = measurementSystem == MeasurementSystem.UnitedStates || measurementSystem == MeasurementSystem.UnitedKingdom + val useMph = + measurementSystem == MeasurementSystem.UnitedStates || measurementSystem == MeasurementSystem.UnitedKingdom var bounds by remember { mutableStateOf(Rect.Zero) } @@ -295,7 +313,8 @@ fun CurrentWeather(forecast: Forecast, measurementSystem: MeasurementSystem) { text = "${forecast.provider} (${ formatTime( LocalContext.current, - forecast.updateTime + forecast.updateTime, + timeFormat, ) })", style = MaterialTheme.typography.bodySmall.copy(fontSize = 8.sp), @@ -436,9 +455,10 @@ fun WeatherTimeSelector( forecasts: List, selectedForecast: Forecast, measurementSystem: MeasurementSystem, + timeFormat: TimeFormat, onTimeSelected: (Int) -> Unit ) { - val dateFormat = remember { DateFormat.getTimeInstance(DateFormat.SHORT) } + val context = LocalContext.current val useFahrenheit = measurementSystem == MeasurementSystem.UnitedStates @@ -490,7 +510,7 @@ fun WeatherTimeSelector( night = fc.night ) Text( - text = dateFormat.format(fc.timestamp), + text = formatTime(context, fc.timestamp, timeFormat), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, softWrap = false, @@ -597,8 +617,10 @@ fun WeatherDaySelector( } } -private fun formatTime(context: Context, timestamp: Long): String { - return DateUtils.formatDateTime(context, timestamp, DateUtils.FORMAT_SHOW_TIME) +private fun formatTime(context: Context, timestamp: Long, timeFormat: TimeFormat): String { + val skeleton = if (timeFormat == TimeFormat.TwelveHour) "h:mm a" else "H:mm" + val dateFormat = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton), Locale.getDefault()) + return dateFormat.format(timestamp) } private fun convertTemperature(useFahrenheit: Boolean, temp: Double): Int { diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidgetVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidgetVM.kt index 2b4caa751..7e6900bfa 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidgetVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidgetVM.kt @@ -6,11 +6,13 @@ import android.icu.util.LocaleData import android.icu.util.ULocale import androidx.appcompat.app.AppCompatActivity import androidx.compose.runtime.mutableStateOf -import androidx.lifecycle.* +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import de.mm20.launcher2.ktx.isAtLeastApiLevel import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.preferences.MeasurementSystem +import de.mm20.launcher2.preferences.TimeFormat import de.mm20.launcher2.preferences.weather.WeatherSettings import de.mm20.launcher2.ui.settings.SettingsActivity import de.mm20.launcher2.weather.DailyForecast @@ -56,14 +58,14 @@ class WeatherWidgetVM : ViewModel(), KoinComponent { * Index of the currently selected forecast in [currentDayForecasts] */ private var selectedForecastIndex = 0 - set(value) { - if (selectedDayIndex < 0) { - currentForecast.value = null - return + set(value) { + if (selectedDayIndex < 0) { + currentForecast.value = null + return + } + field = min(value, forecasts[selectedDayIndex].hourlyForecasts.lastIndex) + currentForecast.value = getCurrentlySelectedForecast() } - field = min(value, forecasts[selectedDayIndex].hourlyForecasts.lastIndex) - currentForecast.value = getCurrentlySelectedForecast() - } private val forecastsFlow = weatherRepository.getDailyForecasts() @@ -71,12 +73,12 @@ class WeatherWidgetVM : ViewModel(), KoinComponent { * All available forecasts, grouped by day */ private var forecasts: List = emptyList() - set(value) { - field = value - selectedDayIndex = 0 - selectedForecastIndex = 0 - dailyForecasts.value = value - } + set(value) { + field = value + selectedDayIndex = 0 + selectedForecastIndex = 0 + dailyForecasts.value = value + } /** * Currently selected forecast, one of [currentDayForecasts] @@ -110,9 +112,11 @@ class WeatherWidgetVM : ViewModel(), KoinComponent { val hasLocationPermission = permissionsManager.hasPermission(PermissionGroup.Location) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) + fun requestLocationPermission(context: AppCompatActivity) { permissionsManager.requestPermission(context, PermissionGroup.Location) } + val autoLocation = weatherSettings.autoLocation .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) @@ -121,7 +125,7 @@ class WeatherWidgetVM : ViewModel(), KoinComponent { if (ms == MeasurementSystem.System) { return@map if (isAtLeastApiLevel(28)) { val systemMs = LocaleData.getMeasurementSystem(ULocale.getDefault()) - when(systemMs) { + when (systemMs) { LocaleData.MeasurementSystem.UK -> MeasurementSystem.UnitedKingdom LocaleData.MeasurementSystem.US -> MeasurementSystem.UnitedStates else -> MeasurementSystem.Metric @@ -134,6 +138,9 @@ class WeatherWidgetVM : ViewModel(), KoinComponent { } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), MeasurementSystem.Metric) + val timeFormat = weatherSettings.timeFormat + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), TimeFormat.TwentyFourHour) + fun selectDay(index: Int) { selectedDayIndex = min(index, forecasts.lastIndex) } @@ -143,7 +150,9 @@ class WeatherWidgetVM : ViewModel(), KoinComponent { } private fun getCurrentlySelectedForecast(): Forecast? { - return forecasts.getOrNull(selectedDayIndex)?.hourlyForecasts?.getOrNull(selectedForecastIndex) + return forecasts.getOrNull(selectedDayIndex)?.hourlyForecasts?.getOrNull( + selectedForecastIndex + ) } fun selectNow() { diff --git a/core/preferences/src/main/java/de/mm20/launcher2/preferences/weather/WeatherSettings.kt b/core/preferences/src/main/java/de/mm20/launcher2/preferences/weather/WeatherSettings.kt index f9f489793..52da5fb26 100644 --- a/core/preferences/src/main/java/de/mm20/launcher2/preferences/weather/WeatherSettings.kt +++ b/core/preferences/src/main/java/de/mm20/launcher2/preferences/weather/WeatherSettings.kt @@ -188,4 +188,8 @@ class WeatherSettings internal constructor( val measurementSystem = launcherDataStore.data.map { it.localeMeasurementSystem }.distinctUntilChanged() + + val timeFormat = launcherDataStore.data.map { + it.localeTimeFormat + }.distinctUntilChanged() } \ No newline at end of file