feat: per-event time zones (#31) #82

Merged
makiolaj merged 11 commits from feat/timezone-support into release/v2.16.0 2026-07-19 09:49:13 +00:00
2 changed files with 15 additions and 20 deletions
Showing only changes of commit ca01f6e729 - Show all commits

View File

@@ -453,16 +453,20 @@ private fun EventDetailContent(state: EventDetailUiState.Success, modifier: Modi
} }
// Time zone — only when the event is timed and pinned to a zone other // Time zone — only when the event is timed and pinned to a zone other
// than the device's, so cross-zone events read unambiguously. The card // than the device's, so cross-zone events read unambiguously. It answers
// above already answers "when is this for me"; this one answers "when // "when was it set", which the zone's name alone never did: an 8 AM New
// was it set", which the zone's name alone never did — an 8 AM New York // York call showing as 2 PM here should still say 8 AM somewhere.
// call showing as 2 PM here should still say 8 AM somewhere. //
// Same hierarchy as the When card above — the label carries the card,
// the time sits small beneath it. The local time is the one the reader
// acts on, so the original must stay quieter than it, not compete.
foreignTimeZoneLabel(detail.eventTimezone, instance.isAllDay, locale)?.let { tzLabel -> foreignTimeZoneLabel(detail.eventTimezone, instance.isAllDay, locale)?.let { tzLabel ->
Spacer(Modifier.height(gap)) Spacer(Modifier.height(gap))
DetailCard( DetailCard(
icon = Icons.Default.Public, icon = Icons.Default.Public,
iconContentDescription = stringResource(R.string.event_detail_timezone), iconContentDescription = stringResource(R.string.event_detail_timezone),
) { ) {
Text(text = tzLabel, style = MaterialTheme.typography.titleMedium)
val originalZone = detail.eventTimezone?.let { tz -> val originalZone = detail.eventTimezone?.let { tz ->
runCatching { TimeZone.of(tz) }.getOrNull() runCatching { TimeZone.of(tz) }.getOrNull()
} }
@@ -473,23 +477,17 @@ private fun EventDetailContent(state: EventDetailUiState.Success, modifier: Modi
// the primary instead, so fall back to it rather than // the primary instead, so fall back to it rather than
// dropping the original time entirely. // dropping the original time entirely.
val (primary, secondary) = formatWhen(instance, originalZone, locale) val (primary, secondary) = formatWhen(instance, originalZone, locale)
Text(
text = stringResource(
R.string.event_detail_timezone_original,
secondary ?: primary,
originalZone.id.substringAfterLast('/').replace('_', ' '),
),
style = MaterialTheme.typography.titleMedium,
)
Spacer(Modifier.height(2.dp)) Spacer(Modifier.height(2.dp))
}
Text( Text(
text = tzLabel, // The label above already names the zone, so the range
// needs no "in New York" tail to be unambiguous here.
text = secondary ?: primary,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
) )
} }
} }
}
// Calendar — icon tinted in the calendar colour conveys identity, so no // Calendar — icon tinted in the calendar colour conveys identity, so no
// separate colour dot is needed. // separate colour dot is needed.

View File

@@ -111,9 +111,6 @@
the same event expressed where the user actually is. %1$s is a time the same event expressed where the user actually is. %1$s is a time
range, e.g. "2:00 PM 3:00 PM". --> range, e.g. "2:00 PM 3:00 PM". -->
<string name="event_edit_timezone_local_time">%1$s your time</string> <string name="event_edit_timezone_local_time">%1$s your time</string>
<!-- The event's own zone times on the detail screen. %1$s is a time range,
%2$s the zone's city, e.g. "8:00 AM 9:00 AM in New York". -->
<string name="event_detail_timezone_original">%1$s in %2$s</string>
<!-- Event form — per-event color --> <!-- Event form — per-event color -->
<string name="event_edit_color">Color</string> <string name="event_edit_color">Color</string>