Use 12h/24h preference in weather widget
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user