From a6bd7181570e650d21d07ae00dc433c61097abee Mon Sep 17 00:00:00 2001 From: Herwig Birke Date: Mon, 2 Feb 2026 14:46:05 +0100 Subject: [PATCH] added Information-Field to TV Shows --- lib/core/api/backend_api.dart | 2 + lib/features/series/data/episode_model.dart | 3 ++ .../presentation/series_detail_screen.dart | 43 ++++++++++++++++++- lib/php/multimedia.php | 34 +++++++++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) diff --git a/lib/core/api/backend_api.dart b/lib/core/api/backend_api.dart index ec1ff6d..134047e 100644 --- a/lib/core/api/backend_api.dart +++ b/lib/core/api/backend_api.dart @@ -254,6 +254,7 @@ class BackendApi { String? resolution, String? downloadPath, bool? cliffhanger, + String? information, }) async { final payload = { 'action': 'set_show_meta', @@ -261,6 +262,7 @@ class BackendApi { if (resolution != null) 'resolution': resolution, if (downloadPath != null) 'download_path': downloadPath, if (cliffhanger != null) 'cliffhanger': cliffhanger, + if (information != null) 'information': information, }; await _post(payload); } diff --git a/lib/features/series/data/episode_model.dart b/lib/features/series/data/episode_model.dart index a5218b3..9370a62 100644 --- a/lib/features/series/data/episode_model.dart +++ b/lib/features/series/data/episode_model.dart @@ -15,6 +15,7 @@ class EpisodeItem { final String? posterPath; final int? showId; final String? downloadPath; + final String? showInformation; EpisodeItem({ required this.id, @@ -31,6 +32,7 @@ class EpisodeItem { this.posterPath, this.showId, this.downloadPath, + this.showInformation, }); factory EpisodeItem.fromJson(Map j) => EpisodeItem( @@ -48,6 +50,7 @@ class EpisodeItem { posterPath: j['poster_path'] as String?, showId: (j['show_id'] as num?)?.toInt(), downloadPath: j['download_path'] as String?, + showInformation: j['show_information'] as String?, ); static bool? _parseBool(dynamic v) { diff --git a/lib/features/series/presentation/series_detail_screen.dart b/lib/features/series/presentation/series_detail_screen.dart index feb1b32..9a633b8 100644 --- a/lib/features/series/presentation/series_detail_screen.dart +++ b/lib/features/series/presentation/series_detail_screen.dart @@ -46,7 +46,10 @@ class _SeriesDetailScreenState extends ConsumerState { String? _origResolution; String? _origDownloadPath; bool? _origCliffhanger; + String? _information; + String? _origInformation; late final TextEditingController _downloadCtrl; + late final TextEditingController _infoCtrl; String? _overview; List> _cast = const []; List> _crew = const []; @@ -63,6 +66,13 @@ class _SeriesDetailScreenState extends ConsumerState { setState(() => _downloadPath = v); } }); + _infoCtrl = TextEditingController(text: _information ?? ''); + _infoCtrl.addListener(() { + final v = _infoCtrl.text; + if (v != (_information ?? '')) { + setState(() => _information = v); + } + }); // Quick-fill from seasonsEps for instant UI while fetching real data if (widget.seasonsEps != null && widget.seasonsEps!.isNotEmpty) { final map = >{}; @@ -93,6 +103,13 @@ class _SeriesDetailScreenState extends ConsumerState { _load(); } + @override + void dispose() { + _downloadCtrl.dispose(); + _infoCtrl.dispose(); + super.dispose(); + } + Future _load() async { try { int? showId = widget.showId; @@ -120,13 +137,18 @@ class _SeriesDetailScreenState extends ConsumerState { _resolution ??= first.resolution; _downloadPath ??= first.downloadPath; _cliffhanger ??= first.showCliffhanger; + _information ??= first.showInformation; _showId ??= first.showId ?? showId; _origResolution ??= _resolution; _origDownloadPath ??= _downloadPath; _origCliffhanger ??= _cliffhanger; + _origInformation ??= _information; if (_downloadCtrl.text != (_downloadPath ?? '')) { _downloadCtrl.text = _downloadPath ?? ''; } + if (_infoCtrl.text != (_information ?? '')) { + _infoCtrl.text = _information ?? ''; + } if (first.showJson != null && first.showJson!.isNotEmpty) { try { final m = jsonDecode(first.showJson!) as Map; @@ -178,12 +200,14 @@ class _SeriesDetailScreenState extends ConsumerState { futures.add(api.setStatus(type: 'episode', refId: epId, status: st.name)); }); // show meta - if (_showId != null && (_resolution != _origResolution || _downloadPath != _origDownloadPath || _cliffhanger != _origCliffhanger)) { + final infoChanged = _information != _origInformation; + if (_showId != null && (_resolution != _origResolution || _downloadPath != _origDownloadPath || _cliffhanger != _origCliffhanger || infoChanged)) { futures.add(api.setShowMeta( showId: _showId!, resolution: _resolution, downloadPath: _downloadPath, cliffhanger: _cliffhanger, + information: infoChanged ? _information : null, )); } await Future.wait(futures); @@ -202,6 +226,7 @@ class _SeriesDetailScreenState extends ConsumerState { _origResolution = _resolution; _origDownloadPath = _downloadPath; _origCliffhanger = _cliffhanger; + _origInformation = _information; }); ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Gespeichert'))); } @@ -240,6 +265,7 @@ class _SeriesDetailScreenState extends ConsumerState { posterPath: it.posterPath, showId: it.showId, downloadPath: it.downloadPath, + showInformation: it.showInformation, )) .toList() : e.value, @@ -257,7 +283,8 @@ class _SeriesDetailScreenState extends ConsumerState { final dirty = _pending.isNotEmpty || _resolution != _origResolution || _downloadPath != _origDownloadPath || - _cliffhanger != _origCliffhanger; + _cliffhanger != _origCliffhanger || + _information != _origInformation; return PopScope( canPop: true, onPopInvokedWithResult: (didPop, result) async { @@ -535,6 +562,18 @@ class _SeriesDetailScreenState extends ConsumerState { focusedBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.white)), ), ), + const SizedBox(height: 8), + TextField( + controller: _infoCtrl, + style: const TextStyle(color: Colors.white), + maxLines: 3, + decoration: const InputDecoration( + labelText: 'Information', + labelStyle: TextStyle(color: Colors.white70), + enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.white54)), + focusedBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.white)), + ), + ), Row( children: [ Checkbox( diff --git a/lib/php/multimedia.php b/lib/php/multimedia.php index c336f75..38b868f 100644 --- a/lib/php/multimedia.php +++ b/lib/php/multimedia.php @@ -154,6 +154,26 @@ function gameHasColumn(string $col): bool { return in_array($col, gameColumns(), true); } +function showColumns(): array { + static $cols = null; + if ($cols !== null) return $cols; + $cols = []; + try { + $pdo = getDb(); + $stmt = $pdo->query('DESCRIBE shows'); + foreach ($stmt as $row) { + if (isset($row['Field'])) $cols[] = $row['Field']; + } + } catch (Throwable $e) { + $cols = []; + } + return $cols; +} + +function showHasColumn(string $col): bool { + return in_array($col, showColumns(), true); +} + function getLocalization(int $igdbId, string $lang = 'de'): ?array { $pdo = getDb(); $stmt = $pdo->prepare('SELECT * FROM igdb_localizations WHERE igdb_id = :id AND lang = :lang LIMIT 1'); @@ -1007,6 +1027,8 @@ try { $s = strtolower($status); if ($s==='init') $statusVal = 0; elseif ($s==='progress') $statusVal = 1; elseif ($s==='done') $statusVal = 2; } + $hasInfo = showHasColumn('information'); + $infoSelect = $hasInfo ? 'sh.information AS show_information' : 'NULL AS show_information'; $base = "FROM episodes e JOIN seasons se ON se.id = e.season_id JOIN shows sh ON sh.id = se.show_id @@ -1018,6 +1040,7 @@ try { sh.poster_path AS poster_path, sh.id AS show_id, sh.download_path AS download_path, + ".$infoSelect.", JSON_UNQUOTE(JSON_EXTRACT(sh.json, '$.status')) AS show_status, sh.cliffhanger AS show_cliffhanger, sh.json AS show_json, @@ -1029,6 +1052,7 @@ try { sh.poster_path AS poster_path, sh.id AS show_id, sh.download_path AS download_path, + ".$infoSelect.", sh.cliffhanger AS show_cliffhanger, sh.json AS show_json, se.season_number, sh.name AS show_name ".$base; @@ -1176,6 +1200,8 @@ try { case 'get_show_episodes': { $showId = (int)($in['show_id'] ?? 0); if (!$showId) fail('bad params'); + $hasInfo = showHasColumn('information'); + $infoSelect = $hasInfo ? 'sh.information AS show_information' : 'NULL AS show_information'; $base = "FROM episodes e JOIN seasons se ON se.id = e.season_id JOIN shows sh ON sh.id = se.show_id @@ -1186,6 +1212,7 @@ try { sh.poster_path AS poster_path, sh.id AS show_id, sh.download_path AS download_path, + ".$infoSelect.", JSON_UNQUOTE(JSON_EXTRACT(sh.json, '$.status')) AS show_status, sh.cliffhanger AS show_cliffhanger, sh.json AS show_json, @@ -1197,6 +1224,7 @@ try { sh.poster_path AS poster_path, sh.id AS show_id, sh.download_path AS download_path, + ".$infoSelect.", sh.cliffhanger AS show_cliffhanger, sh.json AS show_json, se.season_number, sh.name AS show_name ".$base. @@ -1324,9 +1352,15 @@ try { if (!$showId) fail('bad params'); $fields = []; $params = []; + $hasInfo = showHasColumn('information'); if (isset($in['resolution'])) { $fields[] = 'resolution = ?'; $params[] = $in['resolution']; } if (array_key_exists('download_path', $in)) { $fields[] = 'download_path = ?'; $params[] = $in['download_path']; } if (isset($in['cliffhanger'])) { $fields[] = 'cliffhanger = ?'; $params[] = (int)!!$in['cliffhanger']; } + if (array_key_exists('information', $in)) { + if (!$hasInfo) fail('shows.information column missing'); + $fields[] = 'information = ?'; + $params[] = $in['information']; + } if (empty($fields)) fail('no fields'); $sql = 'UPDATE shows SET '.implode(', ', $fields).' WHERE id = ?'; $params[] = $showId;