Use 12h/24h preference in weather widget

This commit is contained in:
MM20
2025-11-29 23:16:43 +01:00
parent 6d1be70837
commit e70e3ce5dd
3 changed files with 60 additions and 25 deletions

View File

@@ -4,6 +4,7 @@ import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.text.format.DateFormat
import android.text.format.DateUtils import android.text.format.DateUtils
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
@@ -71,6 +72,7 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ktx.tryStartActivity import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.preferences.MeasurementSystem import de.mm20.launcher2.preferences.MeasurementSystem
import de.mm20.launcher2.preferences.TimeFormat
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.common.WeatherLocationSearchDialog import de.mm20.launcher2.ui.common.WeatherLocationSearchDialog
import de.mm20.launcher2.ui.component.Banner 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.DailyForecast
import de.mm20.launcher2.weather.Forecast import de.mm20.launcher2.weather.Forecast
import de.mm20.launcher2.widgets.WeatherWidget import de.mm20.launcher2.widgets.WeatherWidget
import java.text.DateFormat import kotlinx.coroutines.flow.map
import java.text.DecimalFormat import java.text.DecimalFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale
import kotlin.math.roundToInt import kotlin.math.roundToInt
@Composable @Composable
@@ -103,6 +106,15 @@ fun WeatherWidget(widget: WeatherWidget) {
val selectedForecast by viewModel.currentForecast val selectedForecast by viewModel.currentForecast
val measurementSystem by viewModel.measurementSystem.collectAsState() 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 compactMode = !widget.config.showForecast
val isProviderAvailable by viewModel.isProviderAvailable.collectAsStateWithLifecycle(true) val isProviderAvailable by viewModel.isProviderAvailable.collectAsStateWithLifecycle(true)
@@ -170,7 +182,7 @@ fun WeatherWidget(widget: WeatherWidget) {
} }
CurrentWeather(forecast, measurementSystem) CurrentWeather(forecast, measurementSystem, timeFormat)
if (!compactMode) { if (!compactMode) {
@@ -189,6 +201,7 @@ fun WeatherWidget(widget: WeatherWidget) {
forecasts = currentDayForecasts, forecasts = currentDayForecasts,
selectedForecast = forecast, selectedForecast = forecast,
measurementSystem = measurementSystem, measurementSystem = measurementSystem,
timeFormat = timeFormat,
onTimeSelected = { onTimeSelected = {
viewModel.selectForecast(it) viewModel.selectForecast(it)
}, },
@@ -211,7 +224,11 @@ fun WeatherWidget(widget: WeatherWidget) {
} }
@Composable @Composable
fun CurrentWeather(forecast: Forecast, measurementSystem: MeasurementSystem) { fun CurrentWeather(
forecast: Forecast,
measurementSystem: MeasurementSystem,
timeFormat: TimeFormat,
) {
val context = LocalContext.current val context = LocalContext.current
val weatherApp = remember { val weatherApp = remember {
context.packageManager.resolveActivity( context.packageManager.resolveActivity(
@@ -223,7 +240,8 @@ fun CurrentWeather(forecast: Forecast, measurementSystem: MeasurementSystem) {
val useFahrenheit = measurementSystem == MeasurementSystem.UnitedStates val useFahrenheit = measurementSystem == MeasurementSystem.UnitedStates
val useInches = 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) } var bounds by remember { mutableStateOf(Rect.Zero) }
@@ -295,7 +313,8 @@ fun CurrentWeather(forecast: Forecast, measurementSystem: MeasurementSystem) {
text = "${forecast.provider} (${ text = "${forecast.provider} (${
formatTime( formatTime(
LocalContext.current, LocalContext.current,
forecast.updateTime forecast.updateTime,
timeFormat,
) )
})", })",
style = MaterialTheme.typography.bodySmall.copy(fontSize = 8.sp), style = MaterialTheme.typography.bodySmall.copy(fontSize = 8.sp),
@@ -436,9 +455,10 @@ fun WeatherTimeSelector(
forecasts: List<Forecast>, forecasts: List<Forecast>,
selectedForecast: Forecast, selectedForecast: Forecast,
measurementSystem: MeasurementSystem, measurementSystem: MeasurementSystem,
timeFormat: TimeFormat,
onTimeSelected: (Int) -> Unit onTimeSelected: (Int) -> Unit
) { ) {
val dateFormat = remember { DateFormat.getTimeInstance(DateFormat.SHORT) } val context = LocalContext.current
val useFahrenheit = measurementSystem == MeasurementSystem.UnitedStates val useFahrenheit = measurementSystem == MeasurementSystem.UnitedStates
@@ -490,7 +510,7 @@ fun WeatherTimeSelector(
night = fc.night night = fc.night
) )
Text( Text(
text = dateFormat.format(fc.timestamp), text = formatTime(context, fc.timestamp, timeFormat),
style = MaterialTheme.typography.labelSmall, style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
softWrap = false, softWrap = false,
@@ -597,8 +617,10 @@ fun WeatherDaySelector(
} }
} }
private fun formatTime(context: Context, timestamp: Long): String { private fun formatTime(context: Context, timestamp: Long, timeFormat: TimeFormat): String {
return DateUtils.formatDateTime(context, timestamp, DateUtils.FORMAT_SHOW_TIME) 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 { private fun convertTemperature(useFahrenheit: Boolean, temp: Double): Int {

View File

@@ -6,11 +6,13 @@ import android.icu.util.LocaleData
import android.icu.util.ULocale import android.icu.util.ULocale
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.mutableStateOf 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.ktx.isAtLeastApiLevel
import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.MeasurementSystem import de.mm20.launcher2.preferences.MeasurementSystem
import de.mm20.launcher2.preferences.TimeFormat
import de.mm20.launcher2.preferences.weather.WeatherSettings import de.mm20.launcher2.preferences.weather.WeatherSettings
import de.mm20.launcher2.ui.settings.SettingsActivity import de.mm20.launcher2.ui.settings.SettingsActivity
import de.mm20.launcher2.weather.DailyForecast import de.mm20.launcher2.weather.DailyForecast
@@ -56,14 +58,14 @@ class WeatherWidgetVM : ViewModel(), KoinComponent {
* Index of the currently selected forecast in [currentDayForecasts] * Index of the currently selected forecast in [currentDayForecasts]
*/ */
private var selectedForecastIndex = 0 private var selectedForecastIndex = 0
set(value) { set(value) {
if (selectedDayIndex < 0) { if (selectedDayIndex < 0) {
currentForecast.value = null currentForecast.value = null
return 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() private val forecastsFlow = weatherRepository.getDailyForecasts()
@@ -71,12 +73,12 @@ class WeatherWidgetVM : ViewModel(), KoinComponent {
* All available forecasts, grouped by day * All available forecasts, grouped by day
*/ */
private var forecasts: List<DailyForecast> = emptyList() private var forecasts: List<DailyForecast> = emptyList()
set(value) { set(value) {
field = value field = value
selectedDayIndex = 0 selectedDayIndex = 0
selectedForecastIndex = 0 selectedForecastIndex = 0
dailyForecasts.value = value dailyForecasts.value = value
} }
/** /**
* Currently selected forecast, one of [currentDayForecasts] * Currently selected forecast, one of [currentDayForecasts]
@@ -110,9 +112,11 @@ class WeatherWidgetVM : ViewModel(), KoinComponent {
val hasLocationPermission = permissionsManager.hasPermission(PermissionGroup.Location) val hasLocationPermission = permissionsManager.hasPermission(PermissionGroup.Location)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
fun requestLocationPermission(context: AppCompatActivity) { fun requestLocationPermission(context: AppCompatActivity) {
permissionsManager.requestPermission(context, PermissionGroup.Location) permissionsManager.requestPermission(context, PermissionGroup.Location)
} }
val autoLocation = weatherSettings.autoLocation val autoLocation = weatherSettings.autoLocation
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
@@ -121,7 +125,7 @@ class WeatherWidgetVM : ViewModel(), KoinComponent {
if (ms == MeasurementSystem.System) { if (ms == MeasurementSystem.System) {
return@map if (isAtLeastApiLevel(28)) { return@map if (isAtLeastApiLevel(28)) {
val systemMs = LocaleData.getMeasurementSystem(ULocale.getDefault()) val systemMs = LocaleData.getMeasurementSystem(ULocale.getDefault())
when(systemMs) { when (systemMs) {
LocaleData.MeasurementSystem.UK -> MeasurementSystem.UnitedKingdom LocaleData.MeasurementSystem.UK -> MeasurementSystem.UnitedKingdom
LocaleData.MeasurementSystem.US -> MeasurementSystem.UnitedStates LocaleData.MeasurementSystem.US -> MeasurementSystem.UnitedStates
else -> MeasurementSystem.Metric else -> MeasurementSystem.Metric
@@ -134,6 +138,9 @@ class WeatherWidgetVM : ViewModel(), KoinComponent {
} }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), MeasurementSystem.Metric) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), MeasurementSystem.Metric)
val timeFormat = weatherSettings.timeFormat
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), TimeFormat.TwentyFourHour)
fun selectDay(index: Int) { fun selectDay(index: Int) {
selectedDayIndex = min(index, forecasts.lastIndex) selectedDayIndex = min(index, forecasts.lastIndex)
} }
@@ -143,7 +150,9 @@ class WeatherWidgetVM : ViewModel(), KoinComponent {
} }
private fun getCurrentlySelectedForecast(): Forecast? { private fun getCurrentlySelectedForecast(): Forecast? {
return forecasts.getOrNull(selectedDayIndex)?.hourlyForecasts?.getOrNull(selectedForecastIndex) return forecasts.getOrNull(selectedDayIndex)?.hourlyForecasts?.getOrNull(
selectedForecastIndex
)
} }
fun selectNow() { fun selectNow() {

View File

@@ -188,4 +188,8 @@ class WeatherSettings internal constructor(
val measurementSystem = launcherDataStore.data.map { val measurementSystem = launcherDataStore.data.map {
it.localeMeasurementSystem it.localeMeasurementSystem
}.distinctUntilChanged() }.distinctUntilChanged()
val timeFormat = launcherDataStore.data.map {
it.localeTimeFormat
}.distinctUntilChanged()
} }