Testfilter, Listenbreite
This commit is contained in:
@@ -11,11 +11,45 @@ import 'movie_add_screen.dart';
|
||||
|
||||
enum _ExportFormat { csv, xlsx }
|
||||
|
||||
class MovieListScreen extends ConsumerWidget {
|
||||
class MovieListScreen extends ConsumerStatefulWidget {
|
||||
const MovieListScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
ConsumerState<MovieListScreen> createState() => _MovieListScreenState();
|
||||
}
|
||||
|
||||
class _MovieListScreenState extends ConsumerState<MovieListScreen> {
|
||||
final TextEditingController _moviePrefixController = TextEditingController();
|
||||
String _moviePrefix = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_moviePrefixController.addListener(() {
|
||||
final next = _moviePrefixController.text;
|
||||
if (next == _moviePrefix) return;
|
||||
setState(() {
|
||||
_moviePrefix = next;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_moviePrefixController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
String get _normalizedMoviePrefix => _moviePrefix.trim().toLowerCase();
|
||||
|
||||
bool _matchesMoviePrefix(String title) {
|
||||
final prefix = _normalizedMoviePrefix;
|
||||
if (prefix.isEmpty) return true;
|
||||
return title.trim().toLowerCase().startsWith(prefix);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final filter = ref.watch(movieFilterProvider);
|
||||
final moviesAsync = ref.watch(moviesStreamProvider);
|
||||
|
||||
@@ -24,9 +58,43 @@ class MovieListScreen extends ConsumerWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
StatusChip(
|
||||
selected: filter,
|
||||
onChanged: (f) => ref.read(movieFilterProvider.notifier).state = f,
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final searchWidth = constraints.maxWidth >= 360
|
||||
? 280.0
|
||||
: constraints.maxWidth;
|
||||
|
||||
return Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 8,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
StatusChip(
|
||||
selected: filter,
|
||||
onChanged: (f) => ref.read(movieFilterProvider.notifier).state = f,
|
||||
),
|
||||
SizedBox(
|
||||
width: searchWidth,
|
||||
child: TextField(
|
||||
controller: _moviePrefixController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Filme filtern...',
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
isDense: true,
|
||||
border: const OutlineInputBorder(),
|
||||
suffixIcon: _moviePrefix.isEmpty
|
||||
? null
|
||||
: IconButton(
|
||||
onPressed: _moviePrefixController.clear,
|
||||
icon: const Icon(Icons.clear),
|
||||
tooltip: 'Filter loeschen',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Align(
|
||||
@@ -137,8 +205,15 @@ class MovieListScreen extends ConsumerWidget {
|
||||
Expanded(
|
||||
child: moviesAsync.when(
|
||||
data: (items) {
|
||||
final itemsSorted = [...items]
|
||||
final itemsSorted = items
|
||||
.where((m) => _matchesMoviePrefix(m.title))
|
||||
.toList()
|
||||
..sort((a, b) => a.title.toLowerCase().compareTo(b.title.toLowerCase()));
|
||||
if (itemsSorted.isEmpty) {
|
||||
return const Center(
|
||||
child: Text('Keine Filme fuer den aktuellen Filter.'),
|
||||
);
|
||||
}
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
// ignore: unused_result
|
||||
@@ -398,3 +473,4 @@ class MovieListScreen extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,20 @@ class _SeriesListScreenState extends ConsumerState<SeriesListScreen> {
|
||||
final ScrollController _summaryVController = ScrollController();
|
||||
final ScrollController _groupedHController = ScrollController();
|
||||
final ScrollController _summaryHController = ScrollController();
|
||||
final TextEditingController _seriesPrefixController = TextEditingController();
|
||||
String _seriesPrefix = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_seriesPrefixController.addListener(() {
|
||||
final next = _seriesPrefixController.text;
|
||||
if (next == _seriesPrefix) return;
|
||||
setState(() {
|
||||
_seriesPrefix = next;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -59,9 +72,18 @@ class _SeriesListScreenState extends ConsumerState<SeriesListScreen> {
|
||||
_summaryVController.dispose();
|
||||
_groupedHController.dispose();
|
||||
_summaryHController.dispose();
|
||||
_seriesPrefixController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
String get _normalizedSeriesPrefix => _seriesPrefix.trim().toLowerCase();
|
||||
|
||||
bool _matchesSeriesPrefix(String? name) {
|
||||
final prefix = _normalizedSeriesPrefix;
|
||||
if (prefix.isEmpty) return true;
|
||||
return (name ?? '').trim().toLowerCase().startsWith(prefix);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final filter = ref.watch(episodeFilterProvider);
|
||||
@@ -74,10 +96,44 @@ class _SeriesListScreenState extends ConsumerState<SeriesListScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
StatusChip(
|
||||
selected: filter,
|
||||
onChanged: (f) =>
|
||||
ref.read(episodeFilterProvider.notifier).state = f,
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final searchWidth = constraints.maxWidth >= 360
|
||||
? 280.0
|
||||
: constraints.maxWidth;
|
||||
|
||||
return Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 8,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
StatusChip(
|
||||
selected: filter,
|
||||
onChanged: (f) =>
|
||||
ref.read(episodeFilterProvider.notifier).state = f,
|
||||
),
|
||||
SizedBox(
|
||||
width: searchWidth,
|
||||
child: TextField(
|
||||
controller: _seriesPrefixController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Serien filtern...',
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
isDense: true,
|
||||
border: const OutlineInputBorder(),
|
||||
suffixIcon: _seriesPrefix.isEmpty
|
||||
? null
|
||||
: IconButton(
|
||||
onPressed: _seriesPrefixController.clear,
|
||||
icon: const Icon(Icons.clear),
|
||||
tooltip: 'Filter loeschen',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const widgets.SizedBox(height: 8),
|
||||
Align(
|
||||
@@ -413,6 +469,10 @@ class _SeriesListScreenState extends ConsumerState<SeriesListScreen> {
|
||||
bg = Colors.green;
|
||||
}
|
||||
|
||||
if (!_matchesSeriesPrefix(displayName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply series-level filter rules
|
||||
final f = filter;
|
||||
final include = () {
|
||||
@@ -857,6 +917,12 @@ class _SeriesListScreenState extends ConsumerState<SeriesListScreen> {
|
||||
));
|
||||
}
|
||||
|
||||
if (rows.isEmpty) {
|
||||
return const Center(
|
||||
child: Text('Keine Serien fuer den aktuellen Filter.'),
|
||||
);
|
||||
}
|
||||
|
||||
final table = DataTable(
|
||||
columns: buildColumns(),
|
||||
rows: rows,
|
||||
@@ -867,36 +933,45 @@ class _SeriesListScreenState extends ConsumerState<SeriesListScreen> {
|
||||
showCheckboxColumn: false,
|
||||
);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Scrollbar(
|
||||
controller: _groupedHController,
|
||||
child: SingleChildScrollView(
|
||||
controller: _groupedHController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Scrollbar(
|
||||
controller: _groupedVController,
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return Stack(
|
||||
children: [
|
||||
Scrollbar(
|
||||
controller: _groupedHController,
|
||||
child: SingleChildScrollView(
|
||||
controller: _groupedVController,
|
||||
child: table,
|
||||
controller: _groupedHController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minWidth: constraints.maxWidth,
|
||||
),
|
||||
child: Scrollbar(
|
||||
controller: _groupedVController,
|
||||
child: SingleChildScrollView(
|
||||
controller: _groupedVController,
|
||||
child: table,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: SizedBox(
|
||||
width: 10,
|
||||
child: AlwaysOnScrollbar(
|
||||
controller: _groupedVController,
|
||||
axis: Axis.vertical,
|
||||
thickness: 10,
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: SizedBox(
|
||||
width: 10,
|
||||
child: AlwaysOnScrollbar(
|
||||
controller: _groupedVController,
|
||||
axis: Axis.vertical,
|
||||
thickness: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -976,6 +1051,9 @@ class _SeriesListScreenState extends ConsumerState<SeriesListScreen> {
|
||||
|
||||
// Apply series-level filter
|
||||
items = items.where(includeByFilter).toList();
|
||||
items = items
|
||||
.where((m) => _matchesSeriesPrefix(m['name'] as String?))
|
||||
.toList();
|
||||
// Build and sort keys
|
||||
final keys = <String>[];
|
||||
final byKey = <String, Map<String, dynamic>>{};
|
||||
@@ -989,6 +1067,11 @@ class _SeriesListScreenState extends ConsumerState<SeriesListScreen> {
|
||||
String displayOf(String rowKey) => rowKey.split('##').first;
|
||||
keys.sort((a, b) =>
|
||||
displayOf(a).toLowerCase().compareTo(displayOf(b).toLowerCase()));
|
||||
if (keys.isEmpty) {
|
||||
return const Center(
|
||||
child: Text('Keine Serien fuer den aktuellen Filter.'),
|
||||
);
|
||||
}
|
||||
|
||||
// Determine all season columns to create a fixed horizontal grid
|
||||
final allSeasons = <int>{};
|
||||
@@ -1912,6 +1995,3 @@ Future<void> openDownload() async {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -34,7 +34,9 @@ class SummaryTable extends StatelessWidget {
|
||||
controller: summaryHController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: widgets.SizedBox(
|
||||
width: tableWidth,
|
||||
width: tableWidth < constraints.maxWidth
|
||||
? constraints.maxWidth
|
||||
: tableWidth,
|
||||
height: constraints.maxHeight,
|
||||
child: Column(
|
||||
children: [
|
||||
@@ -85,3 +87,4 @@ class SummaryTable extends StatelessWidget {
|
||||
return tableWidth;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user