diff --git a/lib/screens/livetv/live_tv_show_schedule_screen.dart b/lib/screens/livetv/live_tv_show_schedule_screen.dart index 69386305..707e1c86 100644 --- a/lib/screens/livetv/live_tv_show_schedule_screen.dart +++ b/lib/screens/livetv/live_tv_show_schedule_screen.dart @@ -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!, diff --git a/lib/screens/livetv/program_details_sheet.dart b/lib/screens/livetv/program_details_sheet.dart index 0cc9ff24..66d4db6c 100644 --- a/lib/screens/livetv/program_details_sheet.dart +++ b/lib/screens/livetv/program_details_sheet.dart @@ -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), diff --git a/lib/screens/livetv/tabs/guide_tab.dart b/lib/screens/livetv/tabs/guide_tab.dart index 7ead5af7..d74d888c 100644 --- a/lib/screens/livetv/tabs/guide_tab.dart +++ b/lib/screens/livetv/tabs/guide_tab.dart @@ -845,11 +845,12 @@ class GuideTabState extends State { // --------------------------------------------------------------------------- Widget _buildTimeHeader(ThemeData theme) { + final is24Hour = MediaQuery.alwaysUse24HourFormatOf(context); final slots = []; 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 { ), 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, ), diff --git a/lib/utils/formatters.dart b/lib/utils/formatters.dart index 40cb1d63..201169c9 100644 --- a/lib/utils/formatters.dart +++ b/lib/utils/formatters.dart @@ -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 diff --git a/lib/widgets/video_controls/desktop_video_controls.dart b/lib/widgets/video_controls/desktop_video_controls.dart index b5ee9ef0..a76967a9 100644 --- a/lib/widgets/video_controls/desktop_video_controls.dart +++ b/lib/widgets/video_controls/desktop_video_controls.dart @@ -633,7 +633,7 @@ class DesktopVideoControlsState extends State { 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( diff --git a/lib/widgets/video_controls/widgets/video_timeline_bar.dart b/lib/widgets/video_controls/widgets/video_timeline_bar.dart index 64e17bd1..fa5697eb 100644 --- a/lib/widgets/video_controls/widgets/video_timeline_bar.dart +++ b/lib/widgets/video_controls/widgets/video_timeline_bar.dart @@ -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)); }, );