feat(tv): add poster to program details, translate EPG strings

This commit is contained in:
edde746
2026-02-12 14:57:43 +01:00
parent eb644c0194
commit fbb9982f44
10 changed files with 154 additions and 48 deletions
+9 -1
View File
@@ -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",
+9 -1
View File
@@ -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",
+9 -1
View File
@@ -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",
+9 -1
View File
@@ -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",
+9 -1
View File
@@ -558,7 +558,15 @@
"premiere": "신규",
"reloadGuide": "편성표 새로고침",
"guideReloaded": "편성표 데이터가 새로고침되었습니다",
"allChannels": "전체 채널"
"allChannels": "전체 채널",
"now": "지금",
"today": "오늘",
"midnight": "자정",
"overnight": "심야",
"morning": "아침",
"daytime": "낮",
"evening": "저녁",
"lateNight": "심야 방송"
},
"collections": {
"title": "컬렉션",
+9 -1
View File
@@ -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",
+2 -2
View File
@@ -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
+9 -1
View File
@@ -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",
+9 -1
View File
@@ -558,7 +558,15 @@
"premiere": "新",
"reloadGuide": "重新加载节目指南",
"guideReloaded": "节目指南已重新加载",
"allChannels": "所有频道"
"allChannels": "所有频道",
"now": "现在",
"today": "今天",
"midnight": "午夜",
"overnight": "凌晨",
"morning": "上午",
"daytime": "白天",
"evening": "晚上",
"lateNight": "深夜"
},
"downloads": {
"title": "下载",
+80 -38
View File
@@ -724,7 +724,7 @@ class _LiveTvScreenState extends State<LiveTvScreen> {
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<LiveTvScreen> {
}
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<LiveTvScreen> {
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<LiveTvScreen> {
void _showProgramDetails(LiveTvChannel channel, LiveTvProgram program) {
final theme = Theme.of(context);
final multiServer = context.read<MultiServerProvider>();
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<LiveTvScreen> {
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: [