Improve local comics selection logic.

This commit is contained in:
pkuislm
2024-11-12 19:54:34 +08:00
parent c94438d7c4
commit 601ef68ad3
2 changed files with 149 additions and 26 deletions

View File

@@ -581,10 +581,13 @@ class SliverGridComics extends StatefulWidget {
this.badgeBuilder, this.badgeBuilder,
this.menuBuilder, this.menuBuilder,
this.onTap, this.onTap,
this.selections
}); });
final List<Comic> comics; final List<Comic> comics;
final Map<Comic, bool>? selections;
final void Function()? onLastItemBuild; final void Function()? onLastItemBuild;
final String? Function(Comic)? badgeBuilder; final String? Function(Comic)? badgeBuilder;
@@ -638,6 +641,7 @@ class _SliverGridComicsState extends State<SliverGridComics> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return _SliverGridComics( return _SliverGridComics(
comics: comics, comics: comics,
selection: widget.selections,
onLastItemBuild: widget.onLastItemBuild, onLastItemBuild: widget.onLastItemBuild,
badgeBuilder: widget.badgeBuilder, badgeBuilder: widget.badgeBuilder,
menuBuilder: widget.menuBuilder, menuBuilder: widget.menuBuilder,
@@ -653,10 +657,13 @@ class _SliverGridComics extends StatelessWidget {
this.badgeBuilder, this.badgeBuilder,
this.menuBuilder, this.menuBuilder,
this.onTap, this.onTap,
this.selection,
}); });
final List<Comic> comics; final List<Comic> comics;
final Map<Comic, bool>? selection;
final void Function()? onLastItemBuild; final void Function()? onLastItemBuild;
final String? Function(Comic)? badgeBuilder; final String? Function(Comic)? badgeBuilder;
@@ -674,11 +681,37 @@ class _SliverGridComics extends StatelessWidget {
onLastItemBuild?.call(); onLastItemBuild?.call();
} }
var badge = badgeBuilder?.call(comics[index]); var badge = badgeBuilder?.call(comics[index]);
return ComicTile( return Stack(
children: [
ComicTile(
comic: comics[index], comic: comics[index],
badge: badge, badge: badge,
menuOptions: menuBuilder?.call(comics[index]), menuOptions: menuBuilder?.call(comics[index]),
onTap: onTap != null ? () => onTap!(comics[index]) : null, onTap: onTap != null ? () => onTap!(comics[index]) : null,
),
Positioned(
bottom: 10,
right: 8,
child: Visibility(
visible: selection == null ? false : selection![comics[index]] ?? false,
child: Stack(
children: [
Transform.scale(
scale: 0.9,
child: const Icon(
Icons.circle_rounded,
color: Colors.white,
)
),
const Icon(
Icons.check_circle_rounded,
color: Colors.green,
)
],
)
)
)
],
); );
}, },
childCount: comics.length, childCount: comics.length,

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:venera/components/components.dart'; import 'package:venera/components/components.dart';
import 'package:venera/foundation/app.dart'; import 'package:venera/foundation/app.dart';
import 'package:venera/foundation/appdata.dart'; import 'package:venera/foundation/appdata.dart';
import 'package:venera/foundation/comic_source/comic_source.dart';
import 'package:venera/foundation/local.dart'; import 'package:venera/foundation/local.dart';
import 'package:venera/pages/downloading_page.dart'; import 'package:venera/pages/downloading_page.dart';
import 'package:venera/utils/cbz.dart'; import 'package:venera/utils/cbz.dart';
@@ -26,7 +27,7 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
bool multiSelectMode = false; bool multiSelectMode = false;
Map<LocalComic, bool> selectedComics = {}; Map<Comic, bool> selectedComics = {};
void update() { void update() {
if (keyword.isEmpty) { if (keyword.isEmpty) {
@@ -166,10 +167,66 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
) )
else if (multiSelectMode) else if (multiSelectMode)
SliverAppbar( SliverAppbar(
title: Text("Selected ${selectedComics.length} comics"), title: Text("Selected @c comics".tlParams({"c": selectedComics.length})),
actions: [ actions: [
IconButton(
icon: const Icon(Icons.check_box_rounded),
tooltip: "Select All".tl,
onPressed: () {
setState(() {
selectedComics = comics.asMap().map((k, v) => MapEntry(v, true));
});
},
),
IconButton(
icon: const Icon(Icons.check_box_outline_blank_outlined),
tooltip: "Deselect".tl,
onPressed: () {
setState(() {
selectedComics.clear();
});
},
),
IconButton(
icon: const Icon(Icons.check_box_outlined),
tooltip: "Invert Selection".tl,
onPressed: () {
setState(() {
comics.asMap().forEach((k, v) {
selectedComics[v] = !selectedComics.putIfAbsent(v, () => false);
});
selectedComics.removeWhere((k, v) => !v);
});
},
),
IconButton(
icon: const Icon(Icons.indeterminate_check_box_rounded),
tooltip: "Select in range".tl,
onPressed: () {
setState(() {
List<int> l = [];
selectedComics.forEach((k, v) {
l.add(comics.indexOf(k as LocalComic));
});
if(l.isEmpty) {
return;
}
l.sort();
int start = l.first;
int end = l.last;
selectedComics.clear();
selectedComics.addEntries(
List.generate(end - start + 1, (i) {
return MapEntry(comics[start + i], true);
})
);
});
},
),
IconButton( IconButton(
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
tooltip: "Exit Multi-Select".tl,
onPressed: () { onPressed: () {
setState(() { setState(() {
multiSelectMode = false; multiSelectMode = false;
@@ -177,6 +234,7 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
}); });
}, },
), ),
], ],
) )
else if (searchMode) else if (searchMode)
@@ -207,13 +265,14 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
), ),
SliverGridComics( SliverGridComics(
comics: comics, comics: comics,
selections: selectedComics,
onTap: multiSelectMode onTap: multiSelectMode
? (c) { ? (c) {
setState(() { setState(() {
if (selectedComics.containsKey(c as LocalComic)) { if (selectedComics.containsKey(c as LocalComic)) {
selectedComics.remove(c as LocalComic); selectedComics.remove(c);
} else { } else {
selectedComics[c as LocalComic] = true; selectedComics[c] = true;
} }
}); });
} }
@@ -226,23 +285,54 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
icon: Icons.delete, icon: Icons.delete,
text: "Delete".tl, text: "Delete".tl,
onClick: () { onClick: () {
if (multiSelectMode) { showDialog(
showConfirmDialog(
context: context, context: context,
builder: (context) {
bool removeComicFile = true;
return StatefulBuilder(
builder: (context, state) {
return ContentDialog(
title: "Delete".tl, title: "Delete".tl,
content: "Delete selected comics?".tl, content: Column(
onConfirm: () { children: [
Text("Delete selected comics?".tl).paddingVertical(8),
Transform.scale(
scale: 0.9,
child: CheckboxListTile(
title: Text("Also remove files on disk".tl),
value: removeComicFile,
onChanged: (v) {
state(() {
removeComicFile = !removeComicFile;
});
}
)
),
],
).paddingHorizontal(16).paddingVertical(8),
actions: [
FilledButton(
onPressed: () {
context.pop();
if(multiSelectMode) {
for (var comic in selectedComics.keys) { for (var comic in selectedComics.keys) {
LocalManager().deleteComic(comic); LocalManager().deleteComic(comic as LocalComic, removeComicFile);
} }
setState(() { setState(() {
selectedComics.clear(); selectedComics.clear();
}); });
},
);
} else { } else {
LocalManager().deleteComic(c as LocalComic); LocalManager().deleteComic(c as LocalComic, removeComicFile);
} }
},
child: Text("Confirm".tl),
),
],
);
}
);
}
);
}), }),
MenuEntry( MenuEntry(
icon: Icons.outbox_outlined, icon: Icons.outbox_outlined,
@@ -255,7 +345,7 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
try { try {
if (multiSelectMode) { if (multiSelectMode) {
for (var comic in selectedComics.keys) { for (var comic in selectedComics.keys) {
var file = await CBZ.export(comic); var file = await CBZ.export(comic as LocalComic);
await saveFile(filename: file.name, file: file); await saveFile(filename: file.name, file: file);
await file.delete(); await file.delete();
} }