diff --git a/lib/i18n/de.i18n.json b/lib/i18n/de.i18n.json index c920ee62..e15abdaf 100644 --- a/lib/i18n/de.i18n.json +++ b/lib/i18n/de.i18n.json @@ -558,7 +558,15 @@ "premiere": "NEU", "reloadGuide": "Programmführer neu laden", "guideReloaded": "Programmdaten neu geladen", - "allChannels": "Alle Kanäle" + "allChannels": "Alle Kanäle", + "now": "Jetzt", + "today": "Heute", + "midnight": "Mitternacht", + "overnight": "Nacht", + "morning": "Morgen", + "daytime": "Tagsüber", + "evening": "Abend", + "lateNight": "Spätnacht" }, "downloads": { "title": "Downloads", diff --git a/lib/i18n/es.i18n.json b/lib/i18n/es.i18n.json index f24048b4..6de2ccc7 100644 --- a/lib/i18n/es.i18n.json +++ b/lib/i18n/es.i18n.json @@ -558,7 +558,15 @@ "premiere": "NUEVO", "reloadGuide": "Recargar guía", "guideReloaded": "Datos de la guía recargados", - "allChannels": "Todos los canales" + "allChannels": "Todos los canales", + "now": "Ahora", + "today": "Hoy", + "midnight": "Medianoche", + "overnight": "Madrugada", + "morning": "Mañana", + "daytime": "Día", + "evening": "Noche", + "lateNight": "Trasnoche" }, "collections": { "title": "Colecciones", diff --git a/lib/i18n/fr.i18n.json b/lib/i18n/fr.i18n.json index 4af2e7dc..4f160c23 100644 --- a/lib/i18n/fr.i18n.json +++ b/lib/i18n/fr.i18n.json @@ -558,7 +558,15 @@ "premiere": "NOUVEAU", "reloadGuide": "Recharger le guide", "guideReloaded": "Données du guide rechargées", - "allChannels": "Toutes les chaînes" + "allChannels": "Toutes les chaînes", + "now": "Maintenant", + "today": "Aujourd'hui", + "midnight": "Minuit", + "overnight": "Nuit", + "morning": "Matin", + "daytime": "Journée", + "evening": "Soirée", + "lateNight": "Nuit tardive" }, "collections": { "title": "Collections", diff --git a/lib/i18n/it.i18n.json b/lib/i18n/it.i18n.json index cca62e33..ed731924 100644 --- a/lib/i18n/it.i18n.json +++ b/lib/i18n/it.i18n.json @@ -558,7 +558,15 @@ "premiere": "NUOVO", "reloadGuide": "Ricarica guida", "guideReloaded": "Dati della guida ricaricati", - "allChannels": "Tutti i canali" + "allChannels": "Tutti i canali", + "now": "Ora", + "today": "Oggi", + "midnight": "Mezzanotte", + "overnight": "Notte", + "morning": "Mattina", + "daytime": "Giorno", + "evening": "Sera", + "lateNight": "Notte tarda" }, "downloads": { "title": "Download", diff --git a/lib/i18n/ko.i18n.json b/lib/i18n/ko.i18n.json index afc4def8..4e4aa519 100644 --- a/lib/i18n/ko.i18n.json +++ b/lib/i18n/ko.i18n.json @@ -558,7 +558,15 @@ "premiere": "신규", "reloadGuide": "편성표 새로고침", "guideReloaded": "편성표 데이터가 새로고침되었습니다", - "allChannels": "전체 채널" + "allChannels": "전체 채널", + "now": "지금", + "today": "오늘", + "midnight": "자정", + "overnight": "심야", + "morning": "아침", + "daytime": "낮", + "evening": "저녁", + "lateNight": "심야 방송" }, "collections": { "title": "컬렉션", diff --git a/lib/i18n/nl.i18n.json b/lib/i18n/nl.i18n.json index 403d24e5..3f692d37 100644 --- a/lib/i18n/nl.i18n.json +++ b/lib/i18n/nl.i18n.json @@ -558,7 +558,15 @@ "premiere": "NIEUW", "reloadGuide": "Gids herladen", "guideReloaded": "Gidsgegevens herladen", - "allChannels": "Alle zenders" + "allChannels": "Alle zenders", + "now": "Nu", + "today": "Vandaag", + "midnight": "Middernacht", + "overnight": "Nacht", + "morning": "Ochtend", + "daytime": "Overdag", + "evening": "Avond", + "lateNight": "Late avond" }, "downloads": { "title": "Downloads", diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 7431dbde..2886d958 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 9 -/// Strings: 6488 (720 per locale) +/// Strings: 6552 (728 per locale) /// -/// Built on 2026-02-12 at 12:55 UTC +/// Built on 2026-02-12 at 13:29 UTC // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/lib/i18n/sv.i18n.json b/lib/i18n/sv.i18n.json index bfd47379..6440424c 100644 --- a/lib/i18n/sv.i18n.json +++ b/lib/i18n/sv.i18n.json @@ -558,7 +558,15 @@ "premiere": "NY", "reloadGuide": "Ladda om programguide", "guideReloaded": "Programdata omladdad", - "allChannels": "Alla kanaler" + "allChannels": "Alla kanaler", + "now": "Nu", + "today": "Idag", + "midnight": "Midnatt", + "overnight": "Natt", + "morning": "Morgon", + "daytime": "Dagtid", + "evening": "Kväll", + "lateNight": "Sen kväll" }, "downloads": { "title": "Nedladdningar", diff --git a/lib/i18n/zh.i18n.json b/lib/i18n/zh.i18n.json index 705bc4ea..338a42bc 100644 --- a/lib/i18n/zh.i18n.json +++ b/lib/i18n/zh.i18n.json @@ -558,7 +558,15 @@ "premiere": "新", "reloadGuide": "重新加载节目指南", "guideReloaded": "节目指南已重新加载", - "allChannels": "所有频道" + "allChannels": "所有频道", + "now": "现在", + "today": "今天", + "midnight": "午夜", + "overnight": "凌晨", + "morning": "上午", + "daytime": "白天", + "evening": "晚上", + "lateNight": "深夜" }, "downloads": { "title": "下载", diff --git a/lib/screens/livetv/live_tv_screen.dart b/lib/screens/livetv/live_tv_screen.dart index 2e6edb35..a0f1750d 100644 --- a/lib/screens/livetv/live_tv_screen.dart +++ b/lib/screens/livetv/live_tv_screen.dart @@ -724,7 +724,7 @@ class _LiveTvScreenState extends State { width: width.clamp(2.0, double.infinity), top: 0, bottom: 0, - child: _buildProgramBlock(channel, program, theme), + child: _buildProgramBlock(channel, program, theme, isLast: program == programs.last), ), ); } @@ -746,7 +746,7 @@ class _LiveTvScreenState extends State { } Widget _buildProgramBlock( - LiveTvChannel channel, LiveTvProgram program, ThemeData theme) { + LiveTvChannel channel, LiveTvProgram program, ThemeData theme, {bool isLast = false}) { final isCurrentlyAiring = program.isCurrentlyAiring; final isPast = program.endsAt != null && program.endsAt! < DateTime.now().millisecondsSinceEpoch ~/ 1000; @@ -763,7 +763,10 @@ class _LiveTvScreenState extends State { onTap: () => _showProgramDetails(channel, program), child: Container( decoration: BoxDecoration( - border: Border(left: BorderSide(color: theme.dividerColor.withValues(alpha: 0.3))), + border: Border( + left: BorderSide(color: theme.dividerColor.withValues(alpha: 0.3)), + right: isLast ? BorderSide(color: theme.dividerColor.withValues(alpha: 0.3)) : BorderSide.none, + ), ), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), child: Column( @@ -816,6 +819,20 @@ class _LiveTvScreenState extends State { void _showProgramDetails(LiveTvChannel channel, LiveTvProgram program) { final theme = Theme.of(context); + final multiServer = context.read(); + final client = multiServer.getClientForServer(channel.serverId ?? ''); + String? posterUrl; + if (program.thumb != null && client != null) { + posterUrl = PlexImageHelper.getOptimizedImageUrl( + client: client, + thumbPath: program.thumb, + maxWidth: 80, + maxHeight: 120, + devicePixelRatio: PlexImageHelper.effectiveDevicePixelRatio(context), + imageType: ImageType.poster, + ); + } + showModalBottomSheet( context: context, builder: (sheetContext) { @@ -826,47 +843,72 @@ class _LiveTvScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ + if (posterUrl != null) ...[ + ClipRRect( + borderRadius: BorderRadius.circular(6), + child: Image.network( + posterUrl, + width: 80, + height: 120, + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => const SizedBox.shrink(), + ), + ), + const SizedBox(width: 14), + ], Expanded( - child: Text( - program.displayTitle, - style: theme.textTheme.titleMedium, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + program.displayTitle, + style: theme.textTheme.titleMedium, + ), + ), + if (program.isCurrentlyAiring) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.red, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + t.liveTv.live, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 11), + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + '${channel.displayName} · ${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')} · ${formatDurationTextual(program.durationMinutes * 60000)}', + style: theme.textTheme.bodySmall + ?.copyWith(color: theme.colorScheme.onSurfaceVariant), + ), + if (program.summary != null && + program.summary!.isNotEmpty) ...[ + const SizedBox(height: 12), + Text( + program.summary!, + style: theme.textTheme.bodyMedium, + maxLines: 4, + overflow: TextOverflow.ellipsis, + ), + ], + ], ), ), - if (program.isCurrentlyAiring) - Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: Colors.red, - borderRadius: BorderRadius.circular(4), - ), - child: Text( - t.liveTv.live, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 11), - ), - ), ], ), - const SizedBox(height: 4), - Text( - '${channel.displayName} · ${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')} · ${formatDurationTextual(program.durationMinutes * 60000)}', - style: theme.textTheme.bodySmall - ?.copyWith(color: theme.colorScheme.onSurfaceVariant), - ), - if (program.summary != null && - program.summary!.isNotEmpty) ...[ - const SizedBox(height: 12), - Text( - program.summary!, - style: theme.textTheme.bodyMedium, - maxLines: 4, - overflow: TextOverflow.ellipsis, - ), - ], const SizedBox(height: 16), Row( children: [