diff --git a/lib/components/comic.dart b/lib/components/comic.dart index 525bcaf..430d96f 100644 --- a/lib/components/comic.dart +++ b/lib/components/comic.dart @@ -581,10 +581,13 @@ class SliverGridComics extends StatefulWidget { this.badgeBuilder, this.menuBuilder, this.onTap, + this.selections }); final List comics; + final Map? selections; + final void Function()? onLastItemBuild; final String? Function(Comic)? badgeBuilder; @@ -638,6 +641,7 @@ class _SliverGridComicsState extends State { Widget build(BuildContext context) { return _SliverGridComics( comics: comics, + selection: widget.selections, onLastItemBuild: widget.onLastItemBuild, badgeBuilder: widget.badgeBuilder, menuBuilder: widget.menuBuilder, @@ -653,10 +657,13 @@ class _SliverGridComics extends StatelessWidget { this.badgeBuilder, this.menuBuilder, this.onTap, + this.selection, }); final List comics; + final Map? selection; + final void Function()? onLastItemBuild; final String? Function(Comic)? badgeBuilder; @@ -674,11 +681,37 @@ class _SliverGridComics extends StatelessWidget { onLastItemBuild?.call(); } var badge = badgeBuilder?.call(comics[index]); - return ComicTile( - comic: comics[index], - badge: badge, - menuOptions: menuBuilder?.call(comics[index]), - onTap: onTap != null ? () => onTap!(comics[index]) : null, + return Stack( + children: [ + ComicTile( + comic: comics[index], + badge: badge, + menuOptions: menuBuilder?.call(comics[index]), + 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, diff --git a/lib/pages/local_comics_page.dart b/lib/pages/local_comics_page.dart index 29e4d38..151f0db 100644 --- a/lib/pages/local_comics_page.dart +++ b/lib/pages/local_comics_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:venera/components/components.dart'; import 'package:venera/foundation/app.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/pages/downloading_page.dart'; import 'package:venera/utils/cbz.dart'; @@ -26,7 +27,7 @@ class _LocalComicsPageState extends State { bool multiSelectMode = false; - Map selectedComics = {}; + Map selectedComics = {}; void update() { if (keyword.isEmpty) { @@ -166,10 +167,66 @@ class _LocalComicsPageState extends State { ) else if (multiSelectMode) SliverAppbar( - title: Text("Selected ${selectedComics.length} comics"), + title: Text("Selected @c comics".tlParams({"c": selectedComics.length})), 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 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( icon: const Icon(Icons.close), + tooltip: "Exit Multi-Select".tl, onPressed: () { setState(() { multiSelectMode = false; @@ -177,6 +234,7 @@ class _LocalComicsPageState extends State { }); }, ), + ], ) else if (searchMode) @@ -207,13 +265,14 @@ class _LocalComicsPageState extends State { ), SliverGridComics( comics: comics, + selections: selectedComics, onTap: multiSelectMode ? (c) { setState(() { if (selectedComics.containsKey(c as LocalComic)) { - selectedComics.remove(c as LocalComic); + selectedComics.remove(c); } else { - selectedComics[c as LocalComic] = true; + selectedComics[c] = true; } }); } @@ -226,23 +285,54 @@ class _LocalComicsPageState extends State { icon: Icons.delete, text: "Delete".tl, onClick: () { - if (multiSelectMode) { - showConfirmDialog( - context: context, - title: "Delete".tl, - content: "Delete selected comics?".tl, - onConfirm: () { - for (var comic in selectedComics.keys) { - LocalManager().deleteComic(comic); + showDialog( + context: context, + builder: (context) { + bool removeComicFile = true; + return StatefulBuilder( + builder: (context, state) { + return ContentDialog( + title: "Delete".tl, + content: Column( + 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) { + LocalManager().deleteComic(comic as LocalComic, removeComicFile); + } + setState(() { + selectedComics.clear(); + }); + } else { + LocalManager().deleteComic(c as LocalComic, removeComicFile); + } + }, + child: Text("Confirm".tl), + ), + ], + ); } - setState(() { - selectedComics.clear(); - }); - }, - ); - } else { - LocalManager().deleteComic(c as LocalComic); - } + ); + } + ); }), MenuEntry( icon: Icons.outbox_outlined, @@ -255,7 +345,7 @@ class _LocalComicsPageState extends State { try { if (multiSelectMode) { 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 file.delete(); }