Add Serie
parent
1ce512b1c4
commit
72b4f4e30c
@ -0,0 +1,205 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../shared/providers.dart';
|
||||
import '../data/series_repository.dart';
|
||||
|
||||
class SeriesAddScreen extends ConsumerStatefulWidget {
|
||||
const SeriesAddScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<SeriesAddScreen> createState() => _SeriesAddScreenState();
|
||||
}
|
||||
|
||||
class _SeriesAddScreenState extends ConsumerState<SeriesAddScreen> {
|
||||
final _queryCtrl = TextEditingController();
|
||||
bool _loading = false;
|
||||
List<Map<String, dynamic>> _results = const [];
|
||||
final Set<int> _selected = {};
|
||||
Set<int> _existing = {};
|
||||
|
||||
Future<void> _loadExisting() async {
|
||||
// Query backend for existing shows to filter out (tmdb_id)
|
||||
final backend = ref.read(backendApiProvider);
|
||||
final rows = await backend.listShows();
|
||||
setState(() {
|
||||
_existing = rows
|
||||
.map((m) => (m['tmdb_id'] as num?))
|
||||
.whereType<num>()
|
||||
.map((n) => n.toInt())
|
||||
.toSet();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _search() async {
|
||||
final q = _queryCtrl.text.trim();
|
||||
if (q.isEmpty) return;
|
||||
setState(() {
|
||||
_loading = true;
|
||||
_results = const [];
|
||||
_selected.clear();
|
||||
});
|
||||
try {
|
||||
await _loadExisting();
|
||||
final tmdb = ref.read(tmdbApiProvider);
|
||||
final items = await tmdb.searchShows(q);
|
||||
final filtered = items
|
||||
.where((m) => !_existing.contains((m['id'] as num).toInt()))
|
||||
.toList();
|
||||
setState(() => _results = filtered);
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(SnackBar(content: Text('Suche fehlgeschlagen: $e')));
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _loading = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _addSelected() async {
|
||||
if (_selected.isEmpty) return;
|
||||
setState(() => _loading = true);
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
try {
|
||||
final tmdb = ref.read(tmdbApiProvider);
|
||||
final backend = ref.read(backendApiProvider);
|
||||
int ok = 0;
|
||||
final ids = List<int>.from(_selected);
|
||||
for (final id in ids) {
|
||||
try {
|
||||
final showJson = await tmdb.getShow(id);
|
||||
final dbShowId = await backend.upsertShow(showJson);
|
||||
final seasons = (showJson['seasons'] as List? ?? const [])
|
||||
.where((s) => (s['season_number'] ?? -1) is num)
|
||||
.map((s) => (s as Map<String, dynamic>)['season_number'] as int)
|
||||
.toList();
|
||||
for (final sNo in seasons) {
|
||||
// Include all seasons; frontend handles hiding S0
|
||||
final seasonJson = await tmdb.getSeason(id, sNo);
|
||||
final seasonId = await backend.upsertSeason(dbShowId, seasonJson);
|
||||
final eps = (seasonJson['episodes'] as List? ?? const [])
|
||||
.cast<Map<String, dynamic>>();
|
||||
for (final e in eps) {
|
||||
await backend.upsertEpisode(seasonId, e);
|
||||
}
|
||||
}
|
||||
ok++;
|
||||
if (mounted) setState(() {});
|
||||
} catch (_) {}
|
||||
}
|
||||
// Refresh series list/table
|
||||
// ignore: unused_result
|
||||
ref.invalidate(seriesGroupedProvider);
|
||||
messenger.showSnackBar(
|
||||
SnackBar(content: Text('$ok Serie(n) hinzugefügt')));
|
||||
if (mounted) Navigator.of(context).pop();
|
||||
} catch (e) {
|
||||
messenger
|
||||
.showSnackBar(SnackBar(content: Text('Hinzufügen fehlgeschlagen: $e')));
|
||||
} finally {
|
||||
if (mounted) setState(() => _loading = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Neue Serien hinzufügen (TMDB)'),
|
||||
actions: [
|
||||
if (_selected.isNotEmpty)
|
||||
TextButton.icon(
|
||||
onPressed: _loading ? null : _addSelected,
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text('Hinzufügen (${_selected.length})'),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _queryCtrl,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Serien suchen…',
|
||||
prefixIcon: Icon(Icons.search),
|
||||
),
|
||||
onSubmitted: (_) => _search(),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
FilledButton.icon(
|
||||
onPressed: _loading ? null : _search,
|
||||
icon: const Icon(Icons.search),
|
||||
label: const Text('Suchen'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (_loading) const LinearProgressIndicator(),
|
||||
const SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: _results.isEmpty
|
||||
? const Center(child: Text('Keine Ergebnisse'))
|
||||
: ListView.separated(
|
||||
itemCount: _results.length,
|
||||
separatorBuilder: (_, __) => const Divider(height: 1),
|
||||
itemBuilder: (_, i) {
|
||||
final m = _results[i];
|
||||
final id = (m['id'] as num).toInt();
|
||||
final title = (m['name'] ?? m['original_name'] ?? '') as String;
|
||||
final firstAir = (m['first_air_date'] as String?) ?? '';
|
||||
final year = firstAir.length >= 4 ? firstAir.substring(0, 4) : '';
|
||||
final poster = m['poster_path'] as String?;
|
||||
final sel = _selected.contains(id);
|
||||
return ListTile(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
if (sel) {
|
||||
_selected.remove(id);
|
||||
} else {
|
||||
_selected.add(id);
|
||||
}
|
||||
});
|
||||
},
|
||||
leading: poster != null
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: 'https://image.tmdb.org/t/p/w154$poster',
|
||||
width: 50,
|
||||
height: 75,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
)
|
||||
: const SizedBox(width: 50, height: 75),
|
||||
title:
|
||||
Text(year.isNotEmpty ? '$title ($year)' : title),
|
||||
trailing: Checkbox(
|
||||
value: sel,
|
||||
onChanged: (v) {
|
||||
setState(() {
|
||||
if (v == true) {
|
||||
_selected.add(id);
|
||||
} else {
|
||||
_selected.remove(id);
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue