From 7566170e0bc2446483734a9f4492355d00600c80 Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Sun, 30 Nov 2025 14:20:25 +0100 Subject: [PATCH] use 12h/24h preference in calendar results --- .../mm20/launcher2/ui/base/ProvideSettings.kt | 47 +++++++++++ .../launcher/search/calendar/CalendarItem.kt | 82 +++++++++++++------ .../ui/launcher/widgets/clock/ClockWidget.kt | 6 +- .../launcher/widgets/weather/WeatherWidget.kt | 37 ++++----- .../widgets/weather/WeatherWidgetVM.kt | 21 ----- .../launcher2/ui/locals/CompositionLocals.kt | 5 ++ .../ClockWidgetSettingsScreenVM.kt | 7 -- .../preferences/ui/ClockWidgetSettings.kt | 9 -- 8 files changed, 126 insertions(+), 88 deletions(-) diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/base/ProvideSettings.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/base/ProvideSettings.kt index 0974fa8eb..be9b671a2 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/base/ProvideSettings.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/base/ProvideSettings.kt @@ -1,30 +1,44 @@ package de.mm20.launcher2.ui.base +import android.icu.util.LocaleData +import android.icu.util.ULocale +import android.text.format.DateFormat import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import de.mm20.launcher2.ktx.isAtLeastApiLevel import de.mm20.launcher2.preferences.IconShape +import de.mm20.launcher2.preferences.MeasurementSystem +import de.mm20.launcher2.preferences.TimeFormat import de.mm20.launcher2.preferences.ui.CardStyle import de.mm20.launcher2.preferences.ui.GridSettings +import de.mm20.launcher2.preferences.ui.LocaleSettings import de.mm20.launcher2.preferences.ui.UiSettings import de.mm20.launcher2.ui.component.ProvideIconShape import de.mm20.launcher2.ui.locals.LocalCardStyle import de.mm20.launcher2.ui.locals.LocalFavoritesEnabled import de.mm20.launcher2.ui.locals.LocalGridSettings +import de.mm20.launcher2.ui.locals.LocalMeasurementSystem +import de.mm20.launcher2.ui.locals.LocalTimeFormat import de.mm20.launcher2.widgets.FavoritesWidget import de.mm20.launcher2.widgets.WidgetRepository import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import org.koin.compose.koinInject @Composable fun ProvideSettings( content: @Composable () -> Unit ) { + val context = LocalContext.current + val settings: UiSettings = koinInject() val widgetRepository: WidgetRepository = koinInject() + val localeSettings: LocaleSettings = koinInject() val iconShape by remember { settings.iconShape.distinctUntilChanged() @@ -41,9 +55,42 @@ fun ProvideSettings( settings.gridSettings.distinctUntilChanged() }.collectAsState(GridSettings()) + val timeFormat by remember(context) { + localeSettings.timeFormat + .map { + if (it == TimeFormat.System) { + if (DateFormat.is24HourFormat(context)) TimeFormat.TwentyFourHour else TimeFormat.TwelveHour + } else { + it + } + }.distinctUntilChanged() + }.collectAsState(null) + + val measurementSystem by remember { + localeSettings.measurementSystem.map { ms -> + if (ms == MeasurementSystem.System) { + return@map if (isAtLeastApiLevel(28)) { + val systemMs = LocaleData.getMeasurementSystem(ULocale.getDefault()) + when (systemMs) { + LocaleData.MeasurementSystem.UK -> MeasurementSystem.UnitedKingdom + LocaleData.MeasurementSystem.US -> MeasurementSystem.UnitedStates + else -> MeasurementSystem.Metric + } + } else { + MeasurementSystem.Metric + } + } + return@map ms + }.distinctUntilChanged() + }.collectAsState(null) + + if (timeFormat == null || measurementSystem == null) return + CompositionLocalProvider( LocalFavoritesEnabled provides favoritesEnabled, LocalGridSettings provides gridSettings, + LocalTimeFormat provides timeFormat!!, + LocalMeasurementSystem provides measurementSystem!! ) { ProvideIconShape(iconShape) { content() diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt index 161cba5d4..712bd7f1b 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt @@ -36,7 +36,7 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.roundToIntRect +import de.mm20.launcher2.preferences.TimeFormat import de.mm20.launcher2.search.CalendarEvent import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.DefaultToolbarAction @@ -49,6 +49,7 @@ import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager import de.mm20.launcher2.ui.locals.LocalDarkTheme import de.mm20.launcher2.ui.locals.LocalFavoritesEnabled import de.mm20.launcher2.ui.locals.LocalGridSettings +import de.mm20.launcher2.ui.locals.LocalTimeFormat import palettes.TonalPalette @Composable @@ -61,6 +62,7 @@ fun CalendarItem( val context = LocalContext.current val viewModel: SearchableItemVM = listItemViewModel(key = "search-${calendar.key}") val iconSize = LocalGridSettings.current.iconSize.dp.toPixels() + val timeFormat = LocalTimeFormat.current LaunchedEffect(calendar) { viewModel.init(calendar, iconSize.toInt()) @@ -85,11 +87,13 @@ fun CalendarItem( verticalAlignment = Alignment.CenterVertically, ) { Icon( - painterResource(when (calendar.isCompleted) { - true -> R.drawable.check_circle_24px_filled - false -> R.drawable.radio_button_unchecked_24px - null -> R.drawable.circle_24px_filled - }), + painterResource( + when (calendar.isCompleted) { + true -> R.drawable.check_circle_24px_filled + false -> R.drawable.radio_button_unchecked_24px + null -> R.drawable.circle_24px_filled + } + ), null, modifier = Modifier .padding(horizontal = 14.dp, vertical = 20.dp) @@ -149,7 +153,7 @@ fun CalendarItem( rememberSharedContentState("date"), this@AnimatedContent ), - text = calendar.formatTime(context), + text = calendar.formatTime(context, timeFormat), style = MaterialTheme.typography.bodySmall ) } @@ -166,7 +170,10 @@ fun CalendarItem( contentDescription = null ) Text( - text = Html.fromHtml(calendar.description!!, Html.FROM_HTML_MODE_COMPACT).toString(), + text = Html.fromHtml( + calendar.description!!, + Html.FROM_HTML_MODE_COMPACT + ).toString(), style = MaterialTheme.typography.bodySmall, maxLines = 8, overflow = TextOverflow.Ellipsis, @@ -249,7 +256,8 @@ fun CalendarItem( ) val sheetManager = LocalBottomSheetManager.current - toolbarActions.add(DefaultToolbarAction( + toolbarActions.add( + DefaultToolbarAction( label = stringResource(R.string.menu_customize), icon = R.drawable.tune_24px, action = { sheetManager.showCustomizeSearchableModal(calendar) } @@ -274,11 +282,13 @@ fun CalendarItem( .padding(16.dp) ) { Icon( - painterResource(when (calendar.isCompleted) { - true -> R.drawable.check_circle_24px_filled - false -> R.drawable.radio_button_unchecked_24px - null -> R.drawable.circle_24px_filled - }), + painterResource( + when (calendar.isCompleted) { + true -> R.drawable.check_circle_24px_filled + false -> R.drawable.radio_button_unchecked_24px + null -> R.drawable.circle_24px_filled + } + ), null, modifier = Modifier .padding(end = 16.dp) @@ -311,7 +321,7 @@ fun CalendarItem( rememberSharedContentState("date"), this@AnimatedContent ), - text = calendar.getSummary(context), + text = calendar.getSummary(context, timeFormat), style = MaterialTheme.typography.bodySmall, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -353,7 +363,16 @@ fun CalendarItemGridPopup( } } -private fun CalendarEvent.formatTime(context: Context): String { +private fun CalendarEvent.formatTime( + context: Context, + timeFormat: TimeFormat, +): String { + val timeFormatFlag = when (timeFormat) { + TimeFormat.System -> 0 + TimeFormat.TwelveHour -> DateUtils.FORMAT_12HOUR + TimeFormat.TwentyFourHour -> DateUtils.FORMAT_24HOUR + } + val startTime = startTime if (startTime == null || isTask) { if (allDay) { @@ -361,14 +380,14 @@ private fun CalendarEvent.formatTime(context: Context): String { context, endTime, endTime, - DateUtils.FORMAT_SHOW_DATE + DateUtils.FORMAT_SHOW_DATE or timeFormatFlag ) } return DateUtils.formatDateRange( context, endTime, endTime, - DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_TIME + DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_TIME or timeFormatFlag ) } @@ -376,18 +395,27 @@ private fun CalendarEvent.formatTime(context: Context): String { context, startTime, endTime, - DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_WEEKDAY + DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_WEEKDAY or timeFormatFlag ) return DateUtils.formatDateRange( context, startTime, endTime, - DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_WEEKDAY + DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_WEEKDAY or timeFormatFlag ) } -private fun CalendarEvent.getSummary(context: Context): String { +private fun CalendarEvent.getSummary( + context: Context, + timeFormat: TimeFormat, +): String { + val timeFormatFlag = when (timeFormat) { + TimeFormat.System -> 0 + TimeFormat.TwelveHour -> DateUtils.FORMAT_12HOUR + TimeFormat.TwentyFourHour -> DateUtils.FORMAT_24HOUR + } + val startTime = startTime if (isTask || startTime == null) { val isToday = DateUtils.isToday(endTime) @@ -399,7 +427,7 @@ private fun CalendarEvent.getSummary(context: Context): String { R.string.task_due_time, DateUtils.formatDateTime( context, endTime, - DateUtils.FORMAT_SHOW_TIME + DateUtils.FORMAT_SHOW_TIME or timeFormatFlag ) ) } @@ -408,7 +436,7 @@ private fun CalendarEvent.getSummary(context: Context): String { R.string.task_due_date, DateUtils.formatDateTime( context, endTime, - DateUtils.FORMAT_SHOW_DATE + DateUtils.FORMAT_SHOW_DATE or timeFormatFlag ) ) } @@ -416,7 +444,7 @@ private fun CalendarEvent.getSummary(context: Context): String { R.string.task_due_date, DateUtils.formatDateTime( context, endTime, - DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_TIME + DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_TIME or timeFormatFlag ) ) } @@ -431,7 +459,7 @@ private fun CalendarEvent.getSummary(context: Context): String { context, startTime, endTime, - DateUtils.FORMAT_SHOW_TIME + DateUtils.FORMAT_SHOW_TIME or timeFormatFlag ) } } else { @@ -440,14 +468,14 @@ private fun CalendarEvent.getSummary(context: Context): String { context, startTime, endTime, - DateUtils.FORMAT_SHOW_DATE + DateUtils.FORMAT_SHOW_DATE or timeFormatFlag ) } else { DateUtils.formatDateRange( context, startTime, endTime, - DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE + DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE or timeFormatFlag ) } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt index b94348655..edea5c7f6 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt @@ -66,6 +66,7 @@ import de.mm20.launcher2.ui.launcher.widgets.clock.clocks.OrbitClock import de.mm20.launcher2.ui.launcher.widgets.clock.clocks.SegmentClock import de.mm20.launcher2.ui.launcher.widgets.clock.parts.PartProvider import de.mm20.launcher2.ui.locals.LocalPreferDarkContentOverWallpaper +import de.mm20.launcher2.ui.locals.LocalTimeFormat import de.mm20.launcher2.ui.settings.clockwidget.ClockWidgetSettingsScreenVM import de.mm20.launcher2.ui.utils.isTwentyFourHours import org.koin.compose.koinInject @@ -251,16 +252,15 @@ fun Clock( darkColors: Boolean = false ) { val time = LocalTime.current + val timeFormat = LocalTimeFormat.current val context = LocalContext.current val clockSettings: ClockWidgetSettings = koinInject() val showSeconds by clockSettings.showSeconds.collectAsState(initial = false) val monospaced by clockSettings.monospaced.collectAsState(initial = false) val useThemeColor by clockSettings.useThemeColor.collectAsState(initial = false) - val timeFormat by clockSettings.timeFormat.collectAsState(null) - if (timeFormat == null) return - val isTwentyFourHours = timeFormat!!.isTwentyFourHours(context) + val isTwentyFourHours = timeFormat.isTwentyFourHours(context) when (style) { is ClockWidgetStyle.Digital1 -> DigitalClock1( 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 dc70aaa02..fc45f58e7 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,7 +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 import androidx.compose.animation.core.animateFloatAsState @@ -79,6 +79,8 @@ import de.mm20.launcher2.ui.component.MissingPermissionBanner import de.mm20.launcher2.ui.component.Tooltip import de.mm20.launcher2.ui.component.weather.AnimatedWeatherIcon import de.mm20.launcher2.ui.component.weather.WeatherIcon +import de.mm20.launcher2.ui.locals.LocalMeasurementSystem +import de.mm20.launcher2.ui.locals.LocalTimeFormat import de.mm20.launcher2.ui.theme.transparency.transparency import de.mm20.launcher2.ui.utils.formatPercent import de.mm20.launcher2.ui.utils.formatPrecipitation @@ -87,10 +89,7 @@ import de.mm20.launcher2.ui.utils.formatTemperature import de.mm20.launcher2.weather.DailyForecast import de.mm20.launcher2.weather.Forecast import de.mm20.launcher2.widgets.WeatherWidget -import kotlinx.coroutines.flow.map -import java.text.DecimalFormat import java.text.SimpleDateFormat -import java.util.Locale @Composable fun WeatherWidget(widget: WeatherWidget) { @@ -107,16 +106,9 @@ 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 measurementSystem = LocalMeasurementSystem.current + val timeFormat = LocalTimeFormat.current + val compactMode = !widget.config.showForecast val isProviderAvailable by viewModel.isProviderAvailable.collectAsStateWithLifecycle(true) @@ -475,8 +467,6 @@ fun WeatherTimeSelector( ) { val context = LocalContext.current - val useFahrenheit = measurementSystem == MeasurementSystem.UnitedStates - val listState = rememberLazyListState() LazyRow( state = listState, @@ -647,12 +637,17 @@ fun WeatherDaySelector( } 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() + val timeFormatFlag = when (timeFormat) { + TimeFormat.TwelveHour -> DateUtils.FORMAT_12HOUR + TimeFormat.TwentyFourHour -> DateUtils.FORMAT_24HOUR + else -> 0 + } + + return DateUtils.formatDateTime( + context, + timestamp, + DateUtils.FORMAT_SHOW_TIME or timeFormatFlag ) - return dateFormat.format(timestamp) } @Composable 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 7e6900bfa..897ca1e4a 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 @@ -120,27 +120,6 @@ class WeatherWidgetVM : ViewModel(), KoinComponent { val autoLocation = weatherSettings.autoLocation .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) - val measurementSystem = weatherSettings.measurementSystem - .map { ms -> - if (ms == MeasurementSystem.System) { - return@map if (isAtLeastApiLevel(28)) { - val systemMs = LocaleData.getMeasurementSystem(ULocale.getDefault()) - when (systemMs) { - LocaleData.MeasurementSystem.UK -> MeasurementSystem.UnitedKingdom - LocaleData.MeasurementSystem.US -> MeasurementSystem.UnitedStates - else -> MeasurementSystem.Metric - } - } else { - MeasurementSystem.Metric - } - } - return@map ms - } - .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) } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/locals/CompositionLocals.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/locals/CompositionLocals.kt index 0a8d9bf5c..0b6de5453 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/locals/CompositionLocals.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/locals/CompositionLocals.kt @@ -6,6 +6,8 @@ import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.geometry.Size import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey +import de.mm20.launcher2.preferences.MeasurementSystem +import de.mm20.launcher2.preferences.TimeFormat import de.mm20.launcher2.preferences.ui.CardStyle import de.mm20.launcher2.preferences.ui.GridSettings import de.mm20.launcher2.ui.theme.WallpaperColors @@ -20,6 +22,9 @@ val LocalFavoritesEnabled = compositionLocalOf { true } val LocalGridSettings = compositionLocalOf { GridSettings() } +val LocalTimeFormat = compositionLocalOf { TimeFormat.TwentyFourHour } +val LocalMeasurementSystem = compositionLocalOf { MeasurementSystem.Metric } + val LocalSnackbarHostState = compositionLocalOf { SnackbarHostState() } val LocalDarkTheme = compositionLocalOf { false } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/ClockWidgetSettingsScreenVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/ClockWidgetSettingsScreenVM.kt index dd5be4250..41699bb11 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/ClockWidgetSettingsScreenVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/ClockWidgetSettingsScreenVM.kt @@ -64,13 +64,6 @@ class ClockWidgetSettingsScreenVM : ViewModel(), KoinComponent { settings.setMonospaced(monospaced) } - val timeFormat = settings.timeFormat - .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), TimeFormat.System) - - fun setTimeFormat(timeFormat: TimeFormat) { - settings.setTimeFormat(timeFormat) - } - val useThemeColor = settings.useThemeColor .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false) diff --git a/core/preferences/src/main/java/de/mm20/launcher2/preferences/ui/ClockWidgetSettings.kt b/core/preferences/src/main/java/de/mm20/launcher2/preferences/ui/ClockWidgetSettings.kt index c9e2e926f..6a8c42c11 100644 --- a/core/preferences/src/main/java/de/mm20/launcher2/preferences/ui/ClockWidgetSettings.kt +++ b/core/preferences/src/main/java/de/mm20/launcher2/preferences/ui/ClockWidgetSettings.kt @@ -145,15 +145,6 @@ class ClockWidgetSettings internal constructor( } } - val timeFormat - get() = launcherDataStore.data.map { it.localeTimeFormat } - - fun setTimeFormat(timeFormat: TimeFormat) { - launcherDataStore.update { - it.copy(localeTimeFormat = timeFormat) - } - } - val useThemeColor get() = launcherDataStore.data.map { it.clockWidgetUseThemeColor }