From d8c66a4a255e278c61287452596a078024bcbf36 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 17 Jun 2026 03:08:43 +0000 Subject: [PATCH] feat: default workout distance unit to the phone's measurement system Read the phone's measurement preference (Android 14+ Regional preferences override via the locale's -u-ms- extension, else ICU's locale-derived US/UK = miles) and default the New Workout composer's km/mi selector to it. Health Connect distances (always metres) are converted into the chosen unit on pre-fill instead of being forced to km. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_015qgqQKHSewRHVM8vSCLt9P --- .../loggedIn/workouts/MeasurementSystem.kt | 47 +++++++++++++++++++ .../loggedIn/workouts/NewWorkoutViewModel.kt | 10 ++-- 2 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/MeasurementSystem.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/MeasurementSystem.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/MeasurementSystem.kt new file mode 100644 index 0000000000..50fce5c2ce --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/MeasurementSystem.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts + +import android.icu.util.LocaleData +import android.icu.util.ULocale +import java.util.Locale + +/** + * Whether the phone's measurement preference favours miles for distance. + * + * Honours the Android 14+ "Regional preferences → Measurement system" override, + * which the platform surfaces through the default locale's Unicode `-u-ms-` + * extension (`metric` / `ussystem` / `uksystem`). When no explicit override is + * set it falls back to ICU's locale-derived measurement system (US and UK both + * use miles for distance), available since API 24. + */ +fun phonePrefersMiles(): Boolean { + val locale = Locale.getDefault(Locale.Category.FORMAT) + + locale.getUnicodeLocaleType("ms")?.let { ms -> + return ms == "ussystem" || ms == "uksystem" + } + + return when (LocaleData.getMeasurementSystem(ULocale.forLocale(locale))) { + LocaleData.MeasurementSystem.US, LocaleData.MeasurementSystem.UK -> true + else -> false + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/NewWorkoutViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/NewWorkoutViewModel.kt index df5d5ce98f..9ae1cdf7a5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/NewWorkoutViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/NewWorkoutViewModel.kt @@ -54,7 +54,7 @@ class NewWorkoutViewModel : ViewModel() { var minutes by mutableStateOf("") var seconds by mutableStateOf("") var distance by mutableStateOf("") - var distanceUnit by mutableStateOf(DistanceTag.KILOMETERS) + var distanceUnit by mutableStateOf(if (phonePrefersMiles()) DistanceTag.MILES else DistanceTag.KILOMETERS) var calories by mutableStateOf("") var notes by mutableStateOf("") @@ -100,8 +100,10 @@ class NewWorkoutViewModel : ViewModel() { seconds = (route.durationSeconds % 60).toString() } if (route.distanceMeters > 0) { - distance = ((route.distanceMeters / 1000.0 * 100).roundToInt() / 100.0).toString() - distanceUnit = DistanceTag.KILOMETERS + val miles = phonePrefersMiles() + distanceUnit = if (miles) DistanceTag.MILES else DistanceTag.KILOMETERS + val value = if (miles) route.distanceMeters / DistanceTag.METERS_PER_MILE else route.distanceMeters / 1000.0 + distance = ((value * 100).roundToInt() / 100.0).toString() } if (route.calories > 0) calories = route.calories.toString() @@ -127,7 +129,7 @@ class NewWorkoutViewModel : ViewModel() { minutes = "" seconds = "" distance = "" - distanceUnit = DistanceTag.KILOMETERS + distanceUnit = if (phonePrefersMiles()) DistanceTag.MILES else DistanceTag.KILOMETERS calories = "" notes = "" source = SourceTag.MANUAL