fix(worldclock): keep the local row's day label and legacy city names
The local row prepends "Local time" to the offset and day, so on a
phone a one-line summary cut off the day ("Tomorrow"), the part that
matters most on a long trip. It now wraps to two lines. Only Etc/
offsets and bare abbreviations are named by their offset; a legacy
place id such as US/Pacific keeps its place name. The picker's home
selection now derives from the same settings subscription the tab
uses.
This commit is contained in:
@@ -69,11 +69,14 @@ object ZoneCatalog {
|
||||
entries.sortedWith(compareBy({ ZoneSearch.fold(it.city) }, { it.zoneId }))
|
||||
|
||||
/**
|
||||
* True for an id named after a place (`Asia/Tokyo`) or for [UTC]; false for
|
||||
* the offset and abbreviation ids tzdata also lists (`Etc/GMT-9`, `CET`),
|
||||
* whose last segment is no city and, for `Etc/GMT±n`, has the sign reversed.
|
||||
* True when [cityFallbackFor] gives a fair name for [zoneId]: a place id,
|
||||
* current (`Asia/Tokyo`) or legacy (`US/Pacific`), or [UTC]. False for the
|
||||
* `Etc/` offsets and the bare abbreviations tzdata also lists (`Etc/GMT-9`,
|
||||
* `CET`), whose last segment is no place and, for `Etc/GMT±n`, has the sign
|
||||
* reversed.
|
||||
*/
|
||||
fun isCityId(zoneId: String): Boolean = zoneId == UTC || isRegionId(zoneId)
|
||||
fun hasPlaceName(zoneId: String): Boolean =
|
||||
zoneId == UTC || ('/' in zoneId && !zoneId.startsWith("Etc/"))
|
||||
|
||||
private fun isRegionId(zoneId: String): Boolean =
|
||||
zoneId.contains('/') && zoneId.substringBefore('/') in REGIONS
|
||||
|
||||
@@ -175,7 +175,9 @@ internal fun LocalClockRow(
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
RowText(name = row.name, summary = summary, time = time)
|
||||
// Two lines, not one: "Local time · " is prepended, and on a phone
|
||||
// the day ("Tomorrow") is what an ellipsis would otherwise eat.
|
||||
RowText(name = row.name, summary = summary, time = time, summaryMaxLines = 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,6 +189,7 @@ private fun RowScope.RowText(
|
||||
summary: String,
|
||||
time: String?,
|
||||
timeColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
summaryMaxLines: Int = 1,
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
@@ -200,7 +203,7 @@ private fun RowScope.RowText(
|
||||
text = summary,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
maxLines = summaryMaxLines,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ internal object WorldClockRows {
|
||||
entries: Map<String, ZoneEntry>,
|
||||
zoneNames: Map<String, String?>,
|
||||
): WorldClockRowState {
|
||||
val name = if (ZoneCatalog.isCityId(deviceZone.id)) {
|
||||
val name = if (ZoneCatalog.hasPlaceName(deviceZone.id)) {
|
||||
null
|
||||
} else {
|
||||
ZoneOffsetFormat.gmtLabel(ZoneComparisons.utcOffsetMinutes(deviceZone, now))
|
||||
|
||||
@@ -35,13 +35,13 @@ internal class WorldClockSource @Inject constructor(
|
||||
/** What the picker needs before it can draw a row; null while it is still being built. */
|
||||
private data class Catalog(val entries: List<ZoneEntry>, val deviceCity: String)
|
||||
|
||||
private val storedHomeZoneId: Flow<String?> =
|
||||
settings.defaults.map { it.homeZoneId }.distinctUntilChanged()
|
||||
|
||||
/** The two settings the tab reads, from one subscription to the defaults. */
|
||||
/** The two settings the tab reads: the stored home zone and the local-clock switch. */
|
||||
private val tabSettings: Flow<Pair<String?, Boolean>> =
|
||||
settings.defaults.map { it.homeZoneId to it.automaticLocalClock }.distinctUntilChanged()
|
||||
|
||||
private val storedHomeZoneId: Flow<String?> =
|
||||
tabSettings.map { it.first }.distinctUntilChanged()
|
||||
|
||||
/**
|
||||
* The tab's state. Both the instant and the device zone are re-read on
|
||||
* **every tick**, which is what makes `TIME_SET` and `TIMEZONE_CHANGED`
|
||||
|
||||
@@ -48,9 +48,8 @@ class LocalClockTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the offset is the one at the instant, so DST counts`() {
|
||||
// London is an hour behind Berlin all year, but Lisbon and London agree
|
||||
// in summer and in winter: the rule is the offset now, not the zone id.
|
||||
fun `the offset decides, not the zone id`() {
|
||||
// Lisbon and London are different ids on the same offset; Berlin is not.
|
||||
assertThat(show(storedHome = "Europe/Lisbon", device = london)).isNull()
|
||||
assertThat(show(storedHome = "Europe/Berlin", device = london)).isEqualTo(london)
|
||||
}
|
||||
|
||||
@@ -226,10 +226,11 @@ class ZoneCatalogTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a city id is a region id or UTC, never an offset or abbreviation`() {
|
||||
assertThat(ZoneCatalog.isCityId("Asia/Tokyo")).isTrue()
|
||||
assertThat(ZoneCatalog.isCityId("UTC")).isTrue()
|
||||
assertThat(ZoneCatalog.isCityId("Etc/GMT-9")).isFalse()
|
||||
assertThat(ZoneCatalog.isCityId("CET")).isFalse()
|
||||
fun `a place id or UTC has a place name, an offset or abbreviation does not`() {
|
||||
assertThat(ZoneCatalog.hasPlaceName("Asia/Tokyo")).isTrue()
|
||||
assertThat(ZoneCatalog.hasPlaceName("US/Pacific")).isTrue()
|
||||
assertThat(ZoneCatalog.hasPlaceName("UTC")).isTrue()
|
||||
assertThat(ZoneCatalog.hasPlaceName("Etc/GMT-9")).isFalse()
|
||||
assertThat(ZoneCatalog.hasPlaceName("CET")).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2407,7 +2407,7 @@ differ from it, so with no stored home the rule is always null, and Settings ›
|
||||
Clock says so under the switch rather than hiding it. A device set by UTC
|
||||
offset rather than by city reports an id like `Etc/GMT-9`, whose last segment
|
||||
has its sign reversed, so that row is named by its real offset ("GMT+9")
|
||||
instead (`ZoneCatalog.isCityId`).
|
||||
instead (`ZoneCatalog.hasPlaceName`).
|
||||
|
||||
The row is not stored. It is `WorldClockUiState.Loaded.local`, built per tick
|
||||
by the source from the same `zones.current()` read the face uses, so a
|
||||
|
||||
Reference in New Issue
Block a user