feat: respect system 24h time format setting

Closes #605
This commit is contained in:
edde746
2026-03-02 18:19:52 +01:00
parent 34252b9ba7
commit 5ab6af1b02
6 changed files with 24 additions and 15 deletions
@@ -8,6 +8,7 @@ import '../../models/livetv_channel.dart';
import '../../models/livetv_program.dart';
import '../../providers/multi_server_provider.dart';
import '../../theme/mono_tokens.dart';
import '../../utils/formatters.dart';
import '../../utils/live_tv_player_navigation.dart';
import '../../utils/plex_image_helper.dart';
import '../../widgets/app_icon.dart';
@@ -173,7 +174,7 @@ class _ScheduleListTile extends StatelessWidget {
const _ScheduleListTile({required this.program, required this.channel, required this.onTap});
String _formatTimeInfo() {
String _formatTimeInfo({required bool is24Hour}) {
final now = DateTime.now();
final start = program.startTime;
final end = program.endTime;
@@ -187,16 +188,16 @@ class _ScheduleListTile extends StatelessWidget {
final minutesUntil = start.difference(now).inMinutes;
if (minutesUntil <= 0) {
// Just started
return _formatAbsoluteTime(start, now);
return _formatAbsoluteTime(start, now, is24Hour: is24Hour);
} else if (minutesUntil < 90) {
return 'Starting in ${minutesUntil}min';
} else {
return _formatAbsoluteTime(start, now);
return _formatAbsoluteTime(start, now, is24Hour: is24Hour);
}
}
String _formatAbsoluteTime(DateTime start, DateTime now) {
final time = '${start.hour.toString().padLeft(2, '0')}:${start.minute.toString().padLeft(2, '0')}';
String _formatAbsoluteTime(DateTime start, DateTime now, {required bool is24Hour}) {
final time = formatClockTime(start, is24Hour: is24Hour);
final today = DateTime(now.year, now.month, now.day);
final startDay = DateTime(start.year, start.month, start.day);
final diff = startDay.difference(today).inDays;
@@ -217,7 +218,7 @@ class _ScheduleListTile extends StatelessWidget {
? 'S${program.parentIndex} · E${program.index}${program.title}'
: program.title;
final timeInfo = _formatTimeInfo();
final timeInfo = _formatTimeInfo(is24Hour: MediaQuery.alwaysUse24HourFormatOf(context));
final subtitle = [
timeInfo,
if (program.summary != null && program.summary!.isNotEmpty) program.summary!,
@@ -194,7 +194,7 @@ class _ProgramDetailsSheetContentState extends State<_ProgramDetailsSheetContent
[
if (channel != null) channel.displayName,
if (program.startTime != null && program.endTime != null)
'${program.startTime!.hour.toString().padLeft(2, '0')}:${program.startTime!.minute.toString().padLeft(2, '0')} - ${program.endTime!.hour.toString().padLeft(2, '0')}:${program.endTime!.minute.toString().padLeft(2, '0')}',
'${formatClockTime(program.startTime!, is24Hour: MediaQuery.alwaysUse24HourFormatOf(context))} - ${formatClockTime(program.endTime!, is24Hour: MediaQuery.alwaysUse24HourFormatOf(context))}',
if (program.durationMinutes > 0) formatDurationTextual(program.durationMinutes * 60000),
].join(' · '),
style: theme.textTheme.bodySmall?.copyWith(color: theme.colorScheme.onSurfaceVariant),
+3 -2
View File
@@ -845,11 +845,12 @@ class GuideTabState extends State<GuideTab> {
// ---------------------------------------------------------------------------
Widget _buildTimeHeader(ThemeData theme) {
final is24Hour = MediaQuery.alwaysUse24HourFormatOf(context);
final slots = <Widget>[];
var current = _gridStart;
while (current.isBefore(_gridEnd)) {
final timeStr = '${current.hour.toString().padLeft(2, '0')}:${current.minute.toString().padLeft(2, '0')}';
final timeStr = formatClockTime(current, is24Hour: is24Hour);
slots.add(
SizedBox(
width: _slotWidth,
@@ -1064,7 +1065,7 @@ class GuideTabState extends State<GuideTab> {
),
if (program.startTime != null)
Text(
'${program.startTime!.hour.toString().padLeft(2, '0')}:${program.startTime!.minute.toString().padLeft(2, '0')} · ${formatDurationTextual(program.durationMinutes * 60000)}',
'${formatClockTime(program.startTime!, is24Hour: MediaQuery.alwaysUse24HourFormatOf(context))} · ${formatDurationTextual(program.durationMinutes * 60000)}',
style: theme.textTheme.labelSmall?.copyWith(color: subtitleColor),
maxLines: 1,
),
+11 -4
View File
@@ -176,13 +176,20 @@ DurationLocale _getDurationLocale() {
}
}
/// Formats a [DateTime] as a clock time string, respecting the system 24-hour preference.
/// Uses `DateFormat.Hm` for 24-hour format and `DateFormat.jm` for 12-hour format.
String formatClockTime(DateTime time, {required bool is24Hour}) {
final locale = LocaleSettings.currentLocale.languageCode;
final formatter = is24Hour ? DateFormat.Hm(locale) : DateFormat.jm(locale);
return formatter.format(time);
}
/// Formats the clock time at which media will finish playing, given the remaining duration.
/// Returns a localized time string like "6:12 PM" or "18:12" depending on locale.
String formatFinishTime(Duration remaining, {double rate = 1.0}) {
/// Returns a localized time string like "6:12 PM" or "18:12" depending on system setting.
String formatFinishTime(Duration remaining, {double rate = 1.0, required bool is24Hour}) {
final adjustedRemaining = remaining * (1.0 / rate);
final finishTime = DateTime.now().add(adjustedRemaining);
final formatter = DateFormat.jm(LocaleSettings.currentLocale.languageCode);
return formatter.format(finishTime);
return formatClockTime(finishTime, is24Hour: is24Hour);
}
/// Takes a list of strings and returns one long string with each item in the list concatenated by a bullet
@@ -633,7 +633,7 @@ class DesktopVideoControlsState extends State<DesktopVideoControls> {
final rate = rateSnap.data ?? 1.0;
if (remaining.inSeconds <= 0) return const SizedBox.shrink();
final text = t.videoControls.endsAt(time: formatFinishTime(remaining, rate: rate));
final text = t.videoControls.endsAt(time: formatFinishTime(remaining, rate: rate, is24Hour: MediaQuery.alwaysUse24HourFormatOf(context)));
const style = TextStyle(color: Colors.white70, fontSize: 13);
return LayoutBuilder(
@@ -127,7 +127,7 @@ class VideoTimelineBar extends StatelessWidget {
initialData: player.state.rate,
builder: (context, rateSnap) {
final rate = rateSnap.data ?? 1.0;
final text = '${formatDurationTimestamp(remaining)} · ${formatFinishTime(remaining.abs(), rate: rate)}';
final text = '${formatDurationTimestamp(remaining)} · ${formatFinishTime(remaining.abs(), rate: rate, is24Hour: MediaQuery.alwaysUse24HourFormatOf(context))}';
return Text(text, style: const TextStyle(color: Colors.white, fontSize: 14));
},
);