mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 07:47:24 +00:00
Add Favorite multiple selections (#66)
This commit is contained in:
@@ -235,7 +235,9 @@
|
||||
"Also remove files on disk": "同时删除磁盘上的文件",
|
||||
"New version available": "有新版本可用",
|
||||
"A new version is available. Do you want to update now?" : "有新版本可用。您要现在更新吗?",
|
||||
"No new version available": "没有新版本可用"
|
||||
"No new version available": "没有新版本可用",
|
||||
"Move": "移动",
|
||||
"Move to favorites": "移动至收藏"
|
||||
},
|
||||
"zh_TW": {
|
||||
"Home": "首頁",
|
||||
@@ -473,6 +475,8 @@
|
||||
"Also remove files on disk": "同時刪除磁盤上的文件",
|
||||
"New version available": "有新版本可用",
|
||||
"A new version is available. Do you want to update now?" : "有新版本可用。您要現在更新嗎?",
|
||||
"No new version available": "沒有新版本可用"
|
||||
"No new version available": "沒有新版本可用",
|
||||
"Move": "移動",
|
||||
"Move to favorites": "移動至收藏"
|
||||
}
|
||||
}
|
@@ -1,14 +1,14 @@
|
||||
part of 'components.dart';
|
||||
|
||||
class ComicTile extends StatelessWidget {
|
||||
const ComicTile({
|
||||
super.key,
|
||||
required this.comic,
|
||||
this.enableLongPressed = true,
|
||||
this.badge,
|
||||
this.menuOptions,
|
||||
this.onTap,
|
||||
});
|
||||
const ComicTile(
|
||||
{super.key,
|
||||
required this.comic,
|
||||
this.enableLongPressed = true,
|
||||
this.badge,
|
||||
this.menuOptions,
|
||||
this.onTap,
|
||||
this.onLongPressed});
|
||||
|
||||
final Comic comic;
|
||||
|
||||
@@ -20,6 +20,8 @@ class ComicTile extends StatelessWidget {
|
||||
|
||||
final VoidCallback? onTap;
|
||||
|
||||
final VoidCallback? onLongPressed;
|
||||
|
||||
void _onTap() {
|
||||
if (onTap != null) {
|
||||
onTap!();
|
||||
@@ -29,6 +31,14 @@ class ComicTile extends StatelessWidget {
|
||||
?.to(() => ComicPage(id: comic.id, sourceKey: comic.sourceKey));
|
||||
}
|
||||
|
||||
void _onLongPressed(context) {
|
||||
if (onLongPressed != null) {
|
||||
onLongPressed!();
|
||||
return;
|
||||
}
|
||||
onLongPress(context);
|
||||
}
|
||||
|
||||
void onLongPress(BuildContext context) {
|
||||
var renderBox = context.findRenderObject() as RenderBox;
|
||||
var size = renderBox.size;
|
||||
@@ -181,7 +191,7 @@ class ComicTile extends StatelessWidget {
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: _onTap,
|
||||
onLongPress: enableLongPressed ? () => onLongPress(context) : null,
|
||||
onLongPress: enableLongPressed ? () => _onLongPressed(context) : null,
|
||||
onSecondaryTapDown: (detail) => onSecondaryTap(detail, context),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 24, 8),
|
||||
@@ -231,7 +241,7 @@ class ComicTile extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
onTap: _onTap,
|
||||
onLongPress:
|
||||
enableLongPressed ? () => onLongPress(context) : null,
|
||||
enableLongPressed ? () => _onLongPressed(context) : null,
|
||||
onSecondaryTapDown: (detail) => onSecondaryTap(detail, context),
|
||||
child: Column(
|
||||
children: [
|
||||
@@ -644,6 +654,7 @@ class SliverGridComics extends StatefulWidget {
|
||||
this.badgeBuilder,
|
||||
this.menuBuilder,
|
||||
this.onTap,
|
||||
this.onLongPressed,
|
||||
this.selections});
|
||||
|
||||
final List<Comic> comics;
|
||||
@@ -658,6 +669,8 @@ class SliverGridComics extends StatefulWidget {
|
||||
|
||||
final void Function(Comic)? onTap;
|
||||
|
||||
final void Function(Comic)? onLongPressed;
|
||||
|
||||
@override
|
||||
State<SliverGridComics> createState() => _SliverGridComicsState();
|
||||
}
|
||||
@@ -708,6 +721,7 @@ class _SliverGridComicsState extends State<SliverGridComics> {
|
||||
badgeBuilder: widget.badgeBuilder,
|
||||
menuBuilder: widget.menuBuilder,
|
||||
onTap: widget.onTap,
|
||||
onLongPressed: widget.onLongPressed,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -719,6 +733,7 @@ class _SliverGridComics extends StatelessWidget {
|
||||
this.badgeBuilder,
|
||||
this.menuBuilder,
|
||||
this.onTap,
|
||||
this.onLongPressed,
|
||||
this.selection,
|
||||
});
|
||||
|
||||
@@ -734,6 +749,8 @@ class _SliverGridComics extends StatelessWidget {
|
||||
|
||||
final void Function(Comic)? onTap;
|
||||
|
||||
final void Function(Comic)? onLongPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverGrid(
|
||||
@@ -750,14 +767,18 @@ class _SliverGridComics extends StatelessWidget {
|
||||
badge: badge,
|
||||
menuOptions: menuBuilder?.call(comics[index]),
|
||||
onTap: onTap != null ? () => onTap!(comics[index]) : null,
|
||||
onLongPressed: onLongPressed != null
|
||||
? () => onLongPressed!(comics[index])
|
||||
: null,
|
||||
);
|
||||
if(selection == null) {
|
||||
if (selection == null) {
|
||||
return comic;
|
||||
}
|
||||
return Container(
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? Theme.of(context).colorScheme.surfaceContainer
|
||||
? Theme.of(context).colorScheme.secondaryContainer
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
|
@@ -12,10 +12,7 @@ import 'comic_source/comic_source.dart';
|
||||
import 'comic_type.dart';
|
||||
|
||||
String _getTimeString(DateTime time) {
|
||||
return time
|
||||
.toIso8601String()
|
||||
.replaceFirst("T", " ")
|
||||
.substring(0, 19);
|
||||
return time.toIso8601String().replaceFirst("T", " ").substring(0, 19);
|
||||
}
|
||||
|
||||
class FavoriteItem implements Comic {
|
||||
@@ -29,15 +26,14 @@ class FavoriteItem implements Comic {
|
||||
String coverPath;
|
||||
late String time;
|
||||
|
||||
FavoriteItem({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.coverPath,
|
||||
required this.author,
|
||||
required this.type,
|
||||
required this.tags,
|
||||
DateTime? favoriteTime
|
||||
}) {
|
||||
FavoriteItem(
|
||||
{required this.id,
|
||||
required this.name,
|
||||
required this.coverPath,
|
||||
required this.author,
|
||||
required this.type,
|
||||
required this.tags,
|
||||
DateTime? favoriteTime}) {
|
||||
var t = favoriteTime ?? DateTime.now();
|
||||
time = _getTimeString(t);
|
||||
}
|
||||
@@ -355,7 +351,8 @@ class LocalFavoritesManager with ChangeNotifier {
|
||||
""", [folder, source, networkFolder]);
|
||||
}
|
||||
|
||||
bool isLinkedToNetworkFolder(String folder, String source, String networkFolder) {
|
||||
bool isLinkedToNetworkFolder(
|
||||
String folder, String source, String networkFolder) {
|
||||
var res = _db.select("""
|
||||
select * from folder_sync
|
||||
where folder_name == ? and source_key == ? and source_folder == ?;
|
||||
@@ -436,6 +433,41 @@ class LocalFavoritesManager with ChangeNotifier {
|
||||
return true;
|
||||
}
|
||||
|
||||
void moveFavorite(
|
||||
String sourceFolder, String targetFolder, String id, ComicType type) {
|
||||
_modifiedAfterLastCache = true;
|
||||
|
||||
if (!existsFolder(sourceFolder)) {
|
||||
throw Exception("Source folder does not exist");
|
||||
}
|
||||
if (!existsFolder(targetFolder)) {
|
||||
throw Exception("Target folder does not exist");
|
||||
}
|
||||
|
||||
var res = _db.select("""
|
||||
select * from "$targetFolder"
|
||||
where id == ? and type == ?;
|
||||
""", [id, type.value]);
|
||||
|
||||
if (res.isNotEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
_db.execute("""
|
||||
insert into "$targetFolder" (id, name, author, type, tags, cover_path, time, display_order)
|
||||
select id, name, author, type, tags, cover_path, time, ?
|
||||
from "$sourceFolder"
|
||||
where id == ? and type == ?;
|
||||
""", [minValue(targetFolder) - 1, id, type.value]);
|
||||
|
||||
_db.execute("""
|
||||
delete from "$sourceFolder"
|
||||
where id == ? and type == ?;
|
||||
""", [id, type.value]);
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// delete a folder
|
||||
void deleteFolder(String name) {
|
||||
_modifiedAfterLastCache = true;
|
||||
|
@@ -13,6 +13,7 @@ import 'package:venera/foundation/favorites.dart';
|
||||
import 'package:venera/foundation/local.dart';
|
||||
import 'package:venera/foundation/res.dart';
|
||||
import 'package:venera/network/download.dart';
|
||||
import 'package:venera/pages/comic_page.dart';
|
||||
import 'package:venera/utils/io.dart';
|
||||
import 'package:venera/utils/translations.dart';
|
||||
|
||||
|
@@ -17,10 +17,30 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
|
||||
String? networkSource;
|
||||
String? networkFolder;
|
||||
|
||||
Map<Comic, bool> selectedComics = {};
|
||||
|
||||
var selectedLocalFolders = <String>{};
|
||||
|
||||
late List<String> added = [];
|
||||
|
||||
String keyword = "";
|
||||
|
||||
bool searchMode = false;
|
||||
|
||||
bool multiSelectMode = false;
|
||||
|
||||
int? lastSelectedIndex;
|
||||
|
||||
void updateComics() {
|
||||
setState(() {
|
||||
comics = LocalFavoritesManager().getAllComics(widget.folder);
|
||||
});
|
||||
if (keyword.isEmpty) {
|
||||
setState(() {
|
||||
comics = LocalFavoritesManager().getAllComics(widget.folder);
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
comics = LocalFavoritesManager().search(keyword);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -35,216 +55,533 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SmoothCustomScrollView(
|
||||
slivers: [
|
||||
SliverAppbar(
|
||||
leading: Tooltip(
|
||||
message: "Folders".tl,
|
||||
child: context.width <= _kTwoPanelChangeWidth
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.menu),
|
||||
color: context.colorScheme.primary,
|
||||
onPressed: favPage.showFolderSelector,
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
title: GestureDetector(
|
||||
onTap: context.width < _kTwoPanelChangeWidth
|
||||
? favPage.showFolderSelector
|
||||
: null,
|
||||
child: Text(favPage.folder ?? "Unselected".tl),
|
||||
),
|
||||
actions: [
|
||||
if (networkSource != null)
|
||||
void selectAll() {
|
||||
setState(() {
|
||||
selectedComics = comics.asMap().map((k, v) => MapEntry(v, true));
|
||||
});
|
||||
}
|
||||
|
||||
void invertSelection() {
|
||||
setState(() {
|
||||
comics.asMap().forEach((k, v) {
|
||||
selectedComics[v] = !selectedComics.putIfAbsent(v, () => false);
|
||||
});
|
||||
selectedComics.removeWhere((k, v) => !v);
|
||||
});
|
||||
}
|
||||
|
||||
var body = Scaffold(
|
||||
body: SmoothCustomScrollView(slivers: [
|
||||
if (!searchMode && !multiSelectMode)
|
||||
SliverAppbar(
|
||||
leading: Tooltip(
|
||||
message: "Folders".tl,
|
||||
child: context.width <= _kTwoPanelChangeWidth
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.menu),
|
||||
color: context.colorScheme.primary,
|
||||
onPressed: favPage.showFolderSelector,
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
title: GestureDetector(
|
||||
onTap: context.width < _kTwoPanelChangeWidth
|
||||
? favPage.showFolderSelector
|
||||
: null,
|
||||
child: Text(favPage.folder ?? "Unselected".tl),
|
||||
),
|
||||
actions: [
|
||||
if (networkSource != null)
|
||||
Tooltip(
|
||||
message: "Sync".tl,
|
||||
child: Flyout(
|
||||
flyoutBuilder: (context) {
|
||||
var sourceName = ComicSource.find(networkSource!)?.name ??
|
||||
networkSource!;
|
||||
var text = "The folder is Linked to @source".tlParams({
|
||||
"source": sourceName,
|
||||
});
|
||||
if (networkFolder != null && networkFolder!.isNotEmpty) {
|
||||
text += "\n${"Source Folder".tl}: $networkFolder";
|
||||
}
|
||||
return FlyoutContent(
|
||||
title: "Sync".tl,
|
||||
content: Text(text),
|
||||
actions: [
|
||||
Button.filled(
|
||||
child: Text("Update".tl),
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
importNetworkFolder(
|
||||
networkSource!,
|
||||
widget.folder,
|
||||
networkFolder!,
|
||||
).then(
|
||||
(value) {
|
||||
updateComics();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Builder(builder: (context) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.sync),
|
||||
onPressed: () {
|
||||
Flyout.of(context).show();
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
message: "Sync".tl,
|
||||
child: Flyout(
|
||||
flyoutBuilder: (context) {
|
||||
var sourceName = ComicSource.find(networkSource!)?.name ??
|
||||
networkSource!;
|
||||
var text = "The folder is Linked to @source".tlParams({
|
||||
"source": sourceName,
|
||||
message: "Search".tl,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.search),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
searchMode = true;
|
||||
});
|
||||
if(networkFolder != null && networkFolder!.isNotEmpty) {
|
||||
text += "\n${"Source Folder".tl}: $networkFolder";
|
||||
}
|
||||
return FlyoutContent(
|
||||
title: "Sync".tl,
|
||||
content: Text(text),
|
||||
actions: [
|
||||
Button.filled(
|
||||
child: Text("Update".tl),
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
importNetworkFolder(
|
||||
networkSource!,
|
||||
},
|
||||
),
|
||||
),
|
||||
MenuButton(
|
||||
entries: [
|
||||
MenuEntry(
|
||||
icon: Icons.delete_outline,
|
||||
text: "Delete Folder".tl,
|
||||
onClick: () {
|
||||
showConfirmDialog(
|
||||
context: App.rootContext,
|
||||
title: "Delete".tl,
|
||||
content:
|
||||
"Are you sure you want to delete this folder?".tl,
|
||||
onConfirm: () {
|
||||
favPage.setFolder(false, null);
|
||||
LocalFavoritesManager().deleteFolder(widget.folder);
|
||||
favPage.folderList?.updateFolders();
|
||||
},
|
||||
);
|
||||
}),
|
||||
MenuEntry(
|
||||
icon: Icons.edit_outlined,
|
||||
text: "Rename".tl,
|
||||
onClick: () {
|
||||
showInputDialog(
|
||||
context: App.rootContext,
|
||||
title: "Rename".tl,
|
||||
hintText: "New Name".tl,
|
||||
onConfirm: (value) {
|
||||
var err = validateFolderName(value.toString());
|
||||
if (err != null) {
|
||||
return err;
|
||||
}
|
||||
LocalFavoritesManager().rename(
|
||||
widget.folder,
|
||||
networkFolder!,
|
||||
).then(
|
||||
(value) {
|
||||
updateComics();
|
||||
value.toString(),
|
||||
);
|
||||
favPage.folderList?.updateFolders();
|
||||
favPage.setFolder(false, value.toString());
|
||||
return null;
|
||||
},
|
||||
);
|
||||
}),
|
||||
MenuEntry(
|
||||
icon: Icons.reorder,
|
||||
text: "Reorder".tl,
|
||||
onClick: () {
|
||||
context.to(
|
||||
() {
|
||||
return _ReorderComicsPage(
|
||||
widget.folder,
|
||||
(comics) {
|
||||
this.comics = comics;
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Builder(builder: (context) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.sync),
|
||||
onPressed: () {
|
||||
Flyout.of(context).show();
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
).then(
|
||||
(value) {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
);
|
||||
}),
|
||||
MenuEntry(
|
||||
icon: Icons.upload_file,
|
||||
text: "Export".tl,
|
||||
onClick: () {
|
||||
var json = LocalFavoritesManager().folderToJson(
|
||||
widget.folder,
|
||||
);
|
||||
saveFile(
|
||||
data: utf8.encode(json),
|
||||
filename: "${widget.folder}.json",
|
||||
);
|
||||
}),
|
||||
MenuEntry(
|
||||
icon: Icons.update,
|
||||
text: "Update Comics Info".tl,
|
||||
onClick: () {
|
||||
updateComicsInfo(widget.folder).then((newComics) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
comics = newComics;
|
||||
});
|
||||
}
|
||||
});
|
||||
}),
|
||||
],
|
||||
),
|
||||
MenuButton(
|
||||
entries: [
|
||||
],
|
||||
)
|
||||
else if (multiSelectMode)
|
||||
SliverAppbar(
|
||||
leading: Tooltip(
|
||||
message: "Cancel".tl,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
multiSelectMode = false;
|
||||
selectedComics.clear();
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
"Selected @c comics".tlParams({"c": selectedComics.length})),
|
||||
actions: [
|
||||
MenuButton(entries: [
|
||||
MenuEntry(
|
||||
icon: Icons.star,
|
||||
text: "Add to favorites".tl,
|
||||
onClick: () => favoriteOption('move')),
|
||||
MenuEntry(
|
||||
icon: Icons.drive_file_move,
|
||||
text: "Move to favorites".tl,
|
||||
onClick: () => favoriteOption('add')),
|
||||
MenuEntry(
|
||||
icon: Icons.select_all,
|
||||
text: "Select All".tl,
|
||||
onClick: selectAll),
|
||||
MenuEntry(
|
||||
icon: Icons.deselect,
|
||||
text: "Deselect".tl,
|
||||
onClick: _cancel),
|
||||
MenuEntry(
|
||||
icon: Icons.flip,
|
||||
text: "Invert Selection".tl,
|
||||
onClick: invertSelection),
|
||||
MenuEntry(
|
||||
icon: Icons.delete_outline,
|
||||
text: "Delete Folder".tl,
|
||||
onClick: () {
|
||||
showConfirmDialog(
|
||||
context: App.rootContext,
|
||||
context: context,
|
||||
title: "Delete".tl,
|
||||
content:
|
||||
"Are you sure you want to delete this folder?".tl,
|
||||
"Are you sure you want to delete this comic?".tl,
|
||||
onConfirm: () {
|
||||
favPage.setFolder(false, null);
|
||||
LocalFavoritesManager().deleteFolder(widget.folder);
|
||||
favPage.folderList?.updateFolders();
|
||||
_deleteComicWithId();
|
||||
},
|
||||
);
|
||||
}),
|
||||
MenuEntry(
|
||||
icon: Icons.edit_outlined,
|
||||
text: "Rename".tl,
|
||||
onClick: () {
|
||||
showInputDialog(
|
||||
context: App.rootContext,
|
||||
title: "Rename".tl,
|
||||
hintText: "New Name".tl,
|
||||
onConfirm: (value) {
|
||||
var err = validateFolderName(value.toString());
|
||||
if (err != null) {
|
||||
return err;
|
||||
}
|
||||
LocalFavoritesManager().rename(
|
||||
widget.folder,
|
||||
value.toString(),
|
||||
);
|
||||
favPage.folderList?.updateFolders();
|
||||
favPage.setFolder(false, value.toString());
|
||||
return null;
|
||||
},
|
||||
);
|
||||
}),
|
||||
MenuEntry(
|
||||
icon: Icons.reorder,
|
||||
text: "Reorder".tl,
|
||||
onClick: () {
|
||||
context.to(
|
||||
() {
|
||||
return _ReorderComicsPage(
|
||||
widget.folder,
|
||||
(comics) {
|
||||
this.comics = comics;
|
||||
},
|
||||
);
|
||||
},
|
||||
).then(
|
||||
(value) {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
);
|
||||
}),
|
||||
MenuEntry(
|
||||
icon: Icons.upload_file,
|
||||
text: "Export".tl,
|
||||
onClick: () {
|
||||
var json = LocalFavoritesManager().folderToJson(
|
||||
widget.folder,
|
||||
);
|
||||
saveFile(
|
||||
data: utf8.encode(json),
|
||||
filename: "${widget.folder}.json",
|
||||
);
|
||||
}),
|
||||
MenuEntry(
|
||||
icon: Icons.update,
|
||||
text: "Update Comics Info".tl,
|
||||
onClick: () {
|
||||
updateComicsInfo(widget.folder).then((newComics) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
comics = newComics;
|
||||
});
|
||||
}
|
||||
});
|
||||
}),
|
||||
MenuEntry(
|
||||
icon: Icons.download,
|
||||
text: "Download All".tl,
|
||||
onClick: () async {
|
||||
int count = 0;
|
||||
for (var c in comics) {
|
||||
if (await LocalManager().isDownloaded(c.id, c.type)) {
|
||||
continue;
|
||||
}
|
||||
var comicSource = c.type.comicSource;
|
||||
if (comicSource == null) {
|
||||
continue;
|
||||
}
|
||||
LocalManager().addTask(ImagesDownloadTask(
|
||||
source: comicSource,
|
||||
comicId: c.id,
|
||||
comic: null,
|
||||
comicTitle: c.name,
|
||||
));
|
||||
count++;
|
||||
}
|
||||
context.showMessage(
|
||||
message: "Added @count comics to download queue."
|
||||
.tlParams({
|
||||
"count": count.toString(),
|
||||
}));
|
||||
}),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
SliverGridComics(
|
||||
comics: comics,
|
||||
menuBuilder: (c) {
|
||||
return [
|
||||
MenuEntry(
|
||||
icon: Icons.delete_outline,
|
||||
text: "Delete".tl,
|
||||
onClick: () {
|
||||
showConfirmDialog(
|
||||
context: context,
|
||||
title: "Delete".tl,
|
||||
content: "Are you sure you want to delete this comic?".tl,
|
||||
onConfirm: () {
|
||||
LocalFavoritesManager().deleteComicWithId(
|
||||
widget.folder,
|
||||
c.id,
|
||||
(c as FavoriteItem).type,
|
||||
);
|
||||
updateComics();
|
||||
},
|
||||
);
|
||||
]),
|
||||
],
|
||||
)
|
||||
else if (searchMode)
|
||||
SliverAppbar(
|
||||
leading: Tooltip(
|
||||
message: "Cancel".tl,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
searchMode = false;
|
||||
keyword = "";
|
||||
updateComics();
|
||||
});
|
||||
},
|
||||
),
|
||||
];
|
||||
),
|
||||
title: TextField(
|
||||
autofocus: true,
|
||||
decoration: InputDecoration(
|
||||
hintText: "Search".tl,
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onChanged: (v) {
|
||||
keyword = v;
|
||||
updateComics();
|
||||
},
|
||||
),
|
||||
),
|
||||
SliverGridComics(
|
||||
comics: comics,
|
||||
selections: selectedComics,
|
||||
onTap: multiSelectMode
|
||||
? (c) {
|
||||
setState(() {
|
||||
if (selectedComics.containsKey(c as FavoriteItem)) {
|
||||
selectedComics.remove(c);
|
||||
_checkExitSelectMode();
|
||||
} else {
|
||||
selectedComics[c] = true;
|
||||
}
|
||||
lastSelectedIndex = comics.indexOf(c);
|
||||
});
|
||||
}
|
||||
: (c) {
|
||||
App.mainNavigatorKey?.currentContext
|
||||
?.to(() => ComicPage(id: c.id, sourceKey: c.sourceKey));
|
||||
},
|
||||
onLongPressed: (c) {
|
||||
setState(() {
|
||||
if (!multiSelectMode) {
|
||||
multiSelectMode = true;
|
||||
if (!selectedComics.containsKey(c as FavoriteItem)) {
|
||||
selectedComics[c] = true;
|
||||
}
|
||||
lastSelectedIndex = comics.indexOf(c);
|
||||
} else {
|
||||
if (lastSelectedIndex != null) {
|
||||
int start = lastSelectedIndex!;
|
||||
int end = comics.indexOf(c as FavoriteItem);
|
||||
if (start > end) {
|
||||
int temp = start;
|
||||
start = end;
|
||||
end = temp;
|
||||
}
|
||||
|
||||
for (int i = start; i <= end; i++) {
|
||||
if (i == lastSelectedIndex) continue;
|
||||
|
||||
var comic = comics[i];
|
||||
if (selectedComics.containsKey(comic)) {
|
||||
selectedComics.remove(comic);
|
||||
} else {
|
||||
selectedComics[comic] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
lastSelectedIndex = comics.indexOf(c as FavoriteItem);
|
||||
}
|
||||
_checkExitSelectMode();
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
]),
|
||||
);
|
||||
return PopScope(
|
||||
canPop: !multiSelectMode && !searchMode,
|
||||
onPopInvokedWithResult: (didPop, result) {
|
||||
if (multiSelectMode) {
|
||||
setState(() {
|
||||
multiSelectMode = false;
|
||||
selectedComics.clear();
|
||||
});
|
||||
} else if (searchMode) {
|
||||
setState(() {
|
||||
searchMode = false;
|
||||
keyword = "";
|
||||
updateComics();
|
||||
});
|
||||
}
|
||||
},
|
||||
child: body,
|
||||
);
|
||||
}
|
||||
|
||||
void favoriteOption(String option) {
|
||||
var targetFolders = LocalFavoritesManager()
|
||||
.folderNames
|
||||
.where((folder) => folder != favPage.folder)
|
||||
.toList();
|
||||
|
||||
showDialog(
|
||||
context: App.rootContext,
|
||||
builder: (context) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 50),
|
||||
child: Container(
|
||||
constraints:
|
||||
const BoxConstraints(maxHeight: 700, maxWidth: 500),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Center(
|
||||
child: Text(
|
||||
favPage.folder ?? "Unselected".tl,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: targetFolders.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == targetFolders.length) {
|
||||
return SizedBox(
|
||||
height: 36,
|
||||
child: Center(
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
newFolder().then((v) {
|
||||
setState(() {
|
||||
targetFolders =
|
||||
LocalFavoritesManager()
|
||||
.folderNames
|
||||
.where((folder) =>
|
||||
folder !=
|
||||
favPage.folder)
|
||||
.toList();
|
||||
});
|
||||
});
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.add, size: 20),
|
||||
const SizedBox(width: 4),
|
||||
Text("New Folder".tl),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
var folder = targetFolders[index];
|
||||
var disabled = false;
|
||||
if (selectedLocalFolders.isNotEmpty) {
|
||||
if (added.contains(folder) &&
|
||||
!added
|
||||
.contains(selectedLocalFolders.first)) {
|
||||
disabled = true;
|
||||
} else if (!added.contains(folder) &&
|
||||
added
|
||||
.contains(selectedLocalFolders.first)) {
|
||||
disabled = true;
|
||||
}
|
||||
}
|
||||
return CheckboxListTile(
|
||||
title: Row(
|
||||
children: [
|
||||
Text(folder),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
value: selectedLocalFolders.contains(folder),
|
||||
onChanged: disabled
|
||||
? null
|
||||
: (v) {
|
||||
setState(() {
|
||||
if (v!) {
|
||||
selectedLocalFolders.add(folder);
|
||||
} else {
|
||||
selectedLocalFolders.remove(folder);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: FilledButton(
|
||||
onPressed: () {
|
||||
if (selectedLocalFolders.isEmpty) {
|
||||
return;
|
||||
}
|
||||
if (option == 'move') {
|
||||
for (var c in selectedComics.keys) {
|
||||
for (var s in selectedLocalFolders) {
|
||||
LocalFavoritesManager().moveFavorite(
|
||||
favPage.folder as String,
|
||||
s,
|
||||
c.id,
|
||||
(c as FavoriteItem).type);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var c in selectedComics.keys) {
|
||||
for (var s in selectedLocalFolders) {
|
||||
LocalFavoritesManager().addComic(
|
||||
s,
|
||||
FavoriteItem(
|
||||
id: c.id,
|
||||
name: c.title,
|
||||
coverPath: c.cover,
|
||||
author: c.subtitle ?? '',
|
||||
type: ComicType((c.sourceKey == 'local'
|
||||
? 0
|
||||
: c.sourceKey.hashCode)),
|
||||
tags: c.tags ?? [],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
context.pop();
|
||||
updateComics();
|
||||
_cancel();
|
||||
},
|
||||
child:
|
||||
Text(option == 'move' ? "Move".tl : "Add".tl),
|
||||
).paddingVertical(16),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _checkExitSelectMode() {
|
||||
if (selectedComics.isEmpty) {
|
||||
setState(() {
|
||||
multiSelectMode = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _cancel() {
|
||||
setState(() {
|
||||
selectedComics.clear();
|
||||
multiSelectMode = false;
|
||||
});
|
||||
}
|
||||
|
||||
void _deleteComicWithId() {
|
||||
for (var c in selectedComics.keys) {
|
||||
LocalFavoritesManager().deleteComicWithId(
|
||||
widget.folder,
|
||||
c.id,
|
||||
(c as FavoriteItem).type,
|
||||
);
|
||||
}
|
||||
updateComics();
|
||||
_cancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user