Serie Context Menu
This commit is contained in:
+4
-4
@@ -58,11 +58,11 @@ class _HomeTabsState extends State<_HomeTabs>
|
||||
),
|
||||
body: TabBarView(
|
||||
controller: _controller,
|
||||
children: const [
|
||||
MovieListScreen(),
|
||||
children: [
|
||||
const MovieListScreen(),
|
||||
SeriesListScreen(),
|
||||
ImportScreen(),
|
||||
PingTestScreen(),
|
||||
const ImportScreen(),
|
||||
const PingTestScreen(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -86,11 +86,10 @@ class BackendApi {
|
||||
// ignore: avoid_print
|
||||
final sc = res?.statusCode;
|
||||
final data = res?.data;
|
||||
print('Backend HTTP ${sc}. URL: ${_dio.options.baseUrl}, payload: $payload, data: ${data}');
|
||||
if (map != null && map.containsKey('error')) {
|
||||
throw Exception('Backend HTTP ${sc}: ${map['error']}');
|
||||
throw Exception('Backend HTTP $sc: ${map['error']}');
|
||||
}
|
||||
throw Exception('Backend HTTP ${sc}: ${data}');
|
||||
throw Exception('Backend HTTP $sc: $data');
|
||||
}
|
||||
if (map == null) {
|
||||
throw Exception('Backend: Unexpected response format');
|
||||
@@ -224,5 +223,8 @@ class BackendApi {
|
||||
final map = await _post({'action': 'get_show_episodes', 'show_id': showId});
|
||||
return (map['items'] as List).cast<Map<String, dynamic>>();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteShow(int showId) async {
|
||||
await _post({'action': 'delete_show', 'show_id': showId});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/// Globale Konfiguration. Standard: von `--dart-define` lesen.
|
||||
/// Fallbacks sind hilfreich für lokale Tests.
|
||||
library;
|
||||
|
||||
class AppConfig {
|
||||
static const backendBaseUrl = String.fromEnvironment(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ignore: constant_identifier_names
|
||||
enum ItemStatus { Init, Progress, Done }
|
||||
|
||||
ItemStatus statusFromString(String? s) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import '../../core/api/tmdb_api.dart';
|
||||
import '../../core/api/backend_api.dart';
|
||||
import '../shared/providers.dart';
|
||||
|
||||
@@ -20,7 +19,7 @@ class _ImportScreenState extends ConsumerState<ImportScreen> {
|
||||
String _log = '';
|
||||
bool _busy = false;
|
||||
|
||||
void _append(String msg) => setState(() => _log += msg + '\n');
|
||||
void _append(String msg) => setState(() => _log += '$msg\n');
|
||||
|
||||
Future<void> _importMovies() async {
|
||||
setState(() => _busy = true);
|
||||
@@ -58,7 +57,7 @@ class _ImportScreenState extends ConsumerState<ImportScreen> {
|
||||
try {
|
||||
_append('Serie $showId: TMDB laden …');
|
||||
final showJson = await tmdb.getShow(showId);
|
||||
print('SHOW JSON: $showJson'); // Debug-Ausgabe
|
||||
// Debug-Ausgabe
|
||||
|
||||
await backend.upsertShow(showJson);
|
||||
_append('Serie $showId: Show OK ✓');
|
||||
@@ -88,10 +87,6 @@ class _ImportScreenState extends ConsumerState<ImportScreen> {
|
||||
} catch (e) {
|
||||
// 👇 Hier kommt der erweiterte Catch hin!
|
||||
if (e is DioException) {
|
||||
print('❗ TMDB DioException für $showId');
|
||||
print('➡️ Request: ${e.requestOptions.uri}');
|
||||
print('➡️ Response: ${e.response?.data}');
|
||||
print('➡️ Status: ${e.response?.statusCode}');
|
||||
}
|
||||
_append('Serie $showId: Fehler → $e');
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../core/status.dart';
|
||||
import '../../shared/providers.dart';
|
||||
import '../data/movie_repository.dart';
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ class MovieDetailScreen extends ConsumerStatefulWidget {
|
||||
class _MovieDetailScreenState extends ConsumerState<MovieDetailScreen> {
|
||||
late ItemStatus _status;
|
||||
Map<String, dynamic>? _tmdb;
|
||||
bool _loading = false;
|
||||
String? _resolution;
|
||||
late ItemStatus _origStatus;
|
||||
late String? _origResolution;
|
||||
@@ -34,7 +33,6 @@ class _MovieDetailScreenState extends ConsumerState<MovieDetailScreen> {
|
||||
}
|
||||
|
||||
Future<void> _loadTmdb() async {
|
||||
setState(() => _loading = true);
|
||||
try {
|
||||
final tmdb = ref.read(tmdbApiProvider);
|
||||
final data = await tmdb.getMovie(widget.movie.tmdbId);
|
||||
@@ -42,7 +40,9 @@ class _MovieDetailScreenState extends ConsumerState<MovieDetailScreen> {
|
||||
} catch (e) {
|
||||
// ignore error, keep UI functional
|
||||
} finally {
|
||||
if (mounted) setState(() => _loading = false);
|
||||
if (mounted){
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,26 +95,26 @@ class _MovieDetailScreenState extends ConsumerState<MovieDetailScreen> {
|
||||
tooltip: 'TMDB aktualisieren',
|
||||
icon: const Icon(Icons.sync),
|
||||
onPressed: () async {
|
||||
// Capture messenger before async gaps to avoid context use across awaits
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
try {
|
||||
final tmdb = ref.read(tmdbApiProvider);
|
||||
final json = await tmdb.getMovie(m.tmdbId);
|
||||
await ref.read(backendApiProvider).upsertMovie(json);
|
||||
if (!mounted) return;
|
||||
// Update local TMDB data for immediate UI refresh
|
||||
if (mounted) setState(() => _tmdb = json);
|
||||
setState(() => _tmdb = json);
|
||||
// Refresh movie list so the list reflects new metadata
|
||||
// ignore: unused_result
|
||||
ref.invalidate(moviesProvider);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('TMDB Daten aktualisiert')),
|
||||
);
|
||||
}
|
||||
messenger.showSnackBar(
|
||||
const SnackBar(content: Text('TMDB Daten aktualisiert')),
|
||||
);
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('TMDB Update fehlgeschlagen: $e')),
|
||||
);
|
||||
}
|
||||
// Even if the widget unmounted, showing a SnackBar via captured messenger is safe
|
||||
messenger.showSnackBar(
|
||||
SnackBar(content: Text('TMDB Update fehlgeschlagen: $e')),
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -202,16 +202,6 @@ class MovieListScreen extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
IconData _statusIcon(ItemStatus s) {
|
||||
switch (s) {
|
||||
case ItemStatus.Init:
|
||||
return Icons.hourglass_empty;
|
||||
case ItemStatus.Progress:
|
||||
return Icons.downloading;
|
||||
case ItemStatus.Done:
|
||||
return Icons.check_circle;
|
||||
}
|
||||
}
|
||||
|
||||
Color? _statusBg(ItemStatus s, BuildContext context) {
|
||||
switch (s) {
|
||||
|
||||
@@ -31,7 +31,11 @@ final seriesGroupedProvider =
|
||||
continue; // retry same offset with smaller page
|
||||
}
|
||||
// If we already have some data, return partial results to unblock UI
|
||||
if (items.isNotEmpty) break; else rethrow;
|
||||
if (items.isNotEmpty) {
|
||||
break;
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
consecutiveFailures = 0;
|
||||
if (page.isEmpty) break;
|
||||
|
||||
@@ -214,35 +214,6 @@ class _SeriesDetailScreenState extends ConsumerState<SeriesDetailScreen> {
|
||||
|
||||
bool _saved = false;
|
||||
|
||||
void _setEpisodeStatus(EpisodeItem ep, ItemStatus s) {
|
||||
setState(() {
|
||||
_pending[ep.id] = s;
|
||||
// reflect change in local list so radios don't jump back after save
|
||||
_seasons = {
|
||||
for (final e in _seasons!.entries)
|
||||
e.key: e.value
|
||||
.map((it) => it.id == ep.id
|
||||
? EpisodeItem(
|
||||
id: it.id,
|
||||
episodeNumber: it.episodeNumber,
|
||||
seasonNumber: it.seasonNumber,
|
||||
showName: it.showName,
|
||||
name: it.name,
|
||||
status: s,
|
||||
resolution: it.resolution,
|
||||
firstAirYear: it.firstAirYear,
|
||||
showJson: it.showJson,
|
||||
showStatus: it.showStatus,
|
||||
showCliffhanger: it.showCliffhanger,
|
||||
posterPath: it.posterPath,
|
||||
showId: it.showId,
|
||||
downloadPath: it.downloadPath,
|
||||
)
|
||||
: it)
|
||||
.toList(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _setSeasonAll(int season, ItemStatus s) async {
|
||||
final eps = _seasons?[season] ?? const <EpisodeItem>[];
|
||||
@@ -285,10 +256,13 @@ class _SeriesDetailScreenState extends ConsumerState<SeriesDetailScreen> {
|
||||
_resolution != _origResolution ||
|
||||
_downloadPath != _origDownloadPath ||
|
||||
_cliffhanger != _origCliffhanger;
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
Navigator.of(context).pop(_saved ? 'updated' : null);
|
||||
return false;
|
||||
return PopScope(
|
||||
canPop: true,
|
||||
onPopInvokedWithResult: (didPop, result) async {
|
||||
if (didPop) return;
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop(_saved ? 'updated' : null);
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
@@ -303,6 +277,7 @@ class _SeriesDetailScreenState extends ConsumerState<SeriesDetailScreen> {
|
||||
onPressed: _tmdbId == null
|
||||
? null
|
||||
: () async {
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
try {
|
||||
final tmdb = ref.read(tmdbApiProvider);
|
||||
final backend = ref.read(backendApiProvider);
|
||||
@@ -353,17 +328,13 @@ class _SeriesDetailScreenState extends ConsumerState<SeriesDetailScreen> {
|
||||
// ignore: unused_result
|
||||
ref.invalidate(seriesGroupedProvider);
|
||||
await _load();
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('TMDB Daten aktualisiert')),
|
||||
);
|
||||
}
|
||||
messenger.showSnackBar(
|
||||
const SnackBar(content: Text('TMDB Daten aktualisiert')),
|
||||
);
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('TMDB Update fehlgeschlagen: $e')),
|
||||
);
|
||||
}
|
||||
messenger.showSnackBar(
|
||||
SnackBar(content: Text('TMDB Update fehlgeschlagen: $e')),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -573,7 +544,7 @@ class _SeriesDetailScreenState extends ConsumerState<SeriesDetailScreen> {
|
||||
Radio<ItemStatus>(
|
||||
value: rowStatus,
|
||||
groupValue: (_pending[ep.id] ?? ep.status),
|
||||
fillColor: const MaterialStatePropertyAll(Colors.white),
|
||||
fillColor: const WidgetStatePropertyAll(Colors.white),
|
||||
onChanged: (v) {
|
||||
setState(() => _pending[ep.id] = rowStatus);
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user