diff --git a/assets/translation.json b/assets/translation.json index be1e7b4..45079e2 100644 --- a/assets/translation.json +++ b/assets/translation.json @@ -391,7 +391,10 @@ "Click to select an image": "点击选择一张图片", "Source URL": "源地址", "The URL should point to a 'index.json' file": "该URL应指向一个'index.json'文件", - "Double tap to zoom": "双击缩放" + "Double tap to zoom": "双击缩放", + "Clear Unfavorited": "清除未收藏", + "Reverse": "反转", + "Delete Chapters": "删除章节" }, "zh_TW": { "Home": "首頁", @@ -785,6 +788,9 @@ "Click to select an image": "點擊選擇一張圖片", "Source URL": "源地址", "The URL should point to a 'index.json' file": "該URL應指向一個'index.json'文件", - "Double tap to zoom": "雙擊縮放" + "Double tap to zoom": "雙擊縮放", + "Clear Unfavorited": "清除未收藏", + "Reverse": "反轉", + "Delete Chapters": "刪除章節" } -} +} \ No newline at end of file diff --git a/lib/components/message.dart b/lib/components/message.dart index 1a67f61..9c27e24 100644 --- a/lib/components/message.dart +++ b/lib/components/message.dart @@ -290,28 +290,30 @@ class ContentDialog extends StatelessWidget { @override Widget build(BuildContext context) { - var content = Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - title != null - ? Appbar( - leading: IconButton( - icon: const Icon(Icons.close), - onPressed: dismissible ? context.pop : null, - ), - title: Text(title!), - backgroundColor: Colors.transparent, - ) - : const SizedBox.shrink(), - this.content, - const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: actions, - ).paddingRight(12), - const SizedBox(height: 16), - ], + var content = SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title != null + ? Appbar( + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: dismissible ? context.pop : null, + ), + title: Text(title!), + backgroundColor: Colors.transparent, + ) + : const SizedBox.shrink(), + this.content, + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: actions, + ).paddingRight(12), + const SizedBox(height: 16), + ], + ), ); return Dialog( shape: RoundedRectangleBorder( diff --git a/lib/foundation/app.dart b/lib/foundation/app.dart index 1257778..13ed776 100644 --- a/lib/foundation/app.dart +++ b/lib/foundation/app.dart @@ -13,7 +13,7 @@ export "widget_utils.dart"; export "context.dart"; class _App { - final version = "1.4.3"; + final version = "1.4.4"; bool get isAndroid => Platform.isAndroid; diff --git a/lib/foundation/appdata.dart b/lib/foundation/appdata.dart index 9c1f210..5600664 100644 --- a/lib/foundation/appdata.dart +++ b/lib/foundation/appdata.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:path_provider/path_provider.dart'; import 'package:venera/foundation/app.dart'; +import 'package:venera/foundation/log.dart'; import 'package:venera/utils/data_sync.dart'; import 'package:venera/utils/init.dart'; import 'package:venera/utils/io.dart'; @@ -110,21 +111,31 @@ class Appdata with Init { if (!await file.exists()) { return; } - var json = jsonDecode(await file.readAsString()); - for (var key in (json['settings'] as Map).keys) { - if (json['settings'][key] != null) { - settings[key] = json['settings'][key]; + try { + var json = jsonDecode(await file.readAsString()); + for (var key in (json['settings'] as Map).keys) { + if (json['settings'][key] != null) { + settings[key] = json['settings'][key]; + } } + searchHistory = List.from(json['searchHistory']); } - searchHistory = List.from(json['searchHistory']); - var implicitDataFile = File(FilePath.join(dataPath, 'implicitData.json')); - if (await implicitDataFile.exists()) { - try { + catch(e) { + Log.error("Appdata", "Failed to load appdata", e); + Log.info("Appdata", "Resetting appdata"); + file.deleteIgnoreError(); + } + try { + var implicitDataFile = File(FilePath.join(dataPath, 'implicitData.json')); + if (await implicitDataFile.exists()) { implicitData = jsonDecode(await implicitDataFile.readAsString()); } - catch(_) { - // ignore - } + } + catch (e) { + Log.error("Appdata", "Failed to load implicit data", e); + Log.info("Appdata", "Resetting implicit data"); + var implicitDataFile = File(FilePath.join(dataPath, 'implicitData.json')); + implicitDataFile.deleteIgnoreError(); } } } @@ -194,7 +205,9 @@ class Settings with ChangeNotifier { operator []=(String key, dynamic value) { _data[key] = value; - notifyListeners(); + if (key != "dataVersion") { + notifyListeners(); + } } @override diff --git a/lib/foundation/comic_source/models.dart b/lib/foundation/comic_source/models.dart index 5e2bc62..52cf276 100644 --- a/lib/foundation/comic_source/models.dart +++ b/lib/foundation/comic_source/models.dart @@ -116,6 +116,26 @@ class Comic { toString() => "$sourceKey@$id"; } +class ComicID { + final ComicType type; + + final String id; + + const ComicID(this.type, this.id); + + @override + bool operator ==(Object other) { + if (other is! ComicID) return false; + return other.type == type && other.id == id; + } + + @override + int get hashCode => type.hashCode ^ id.hashCode; + + @override + String toString() => "$type@$id"; +} + class ComicDetails with HistoryMixin { @override final String title; diff --git a/lib/foundation/favorites.dart b/lib/foundation/favorites.dart index 2fce8f5..d198ec3 100644 --- a/lib/foundation/favorites.dart +++ b/lib/foundation/favorites.dart @@ -653,6 +653,102 @@ class LocalFavoritesManager with ChangeNotifier { notifyListeners(); } + void batchMoveFavorites( + String sourceFolder, String targetFolder, List items) { + _modifiedAfterLastCache = true; + + if (!existsFolder(sourceFolder)) { + throw Exception("Source folder does not exist"); + } + if (!existsFolder(targetFolder)) { + throw Exception("Target folder does not exist"); + } + + _db.execute("BEGIN TRANSACTION"); + var displayOrder = maxValue(targetFolder) + 1; + try { + for (var item in items) { + _db.execute(""" + insert or ignore 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 == ?; + """, [displayOrder, item.id, item.type.value]); + + _db.execute(""" + delete from "$sourceFolder" + where id == ? and type == ?; + """, [item.id, item.type.value]); + + displayOrder++; + } + notifyListeners(); + } catch (e) { + Log.error("Batch Move Favorites", e.toString()); + _db.execute("ROLLBACK"); + return; + } + _db.execute("COMMIT"); + + // Update counts + if (counts[targetFolder] == null) { + counts[targetFolder] = count(targetFolder); + } else { + counts[targetFolder] = counts[targetFolder]! + items.length; + } + + if (counts[sourceFolder] != null) { + counts[sourceFolder] = counts[sourceFolder]! - items.length; + } else { + counts[sourceFolder] = count(sourceFolder); + } + + notifyListeners(); + } + + void batchCopyFavorites( + String sourceFolder, String targetFolder, List items) { + _modifiedAfterLastCache = true; + + if (!existsFolder(sourceFolder)) { + throw Exception("Source folder does not exist"); + } + if (!existsFolder(targetFolder)) { + throw Exception("Target folder does not exist"); + } + + _db.execute("BEGIN TRANSACTION"); + var displayOrder = maxValue(targetFolder) + 1; + try { + for (var item in items) { + _db.execute(""" + insert or ignore 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 == ?; + """, [displayOrder, item.id, item.type.value]); + + displayOrder++; + } + notifyListeners(); + } catch (e) { + Log.error("Batch Copy Favorites", e.toString()); + _db.execute("ROLLBACK"); + return; + } + + _db.execute("COMMIT"); + + // Update counts + if (counts[targetFolder] == null) { + counts[targetFolder] = count(targetFolder); + } else { + counts[targetFolder] = counts[targetFolder]! + items.length; + } + + notifyListeners(); + } + /// delete a folder void deleteFolder(String name) { _modifiedAfterLastCache = true; @@ -667,11 +763,6 @@ class LocalFavoritesManager with ChangeNotifier { notifyListeners(); } - void deleteComic(String folder, FavoriteItem comic) { - _modifiedAfterLastCache = true; - deleteComicWithId(folder, comic.id, comic.type); - } - void deleteComicWithId(String folder, String id, ComicType type) { _modifiedAfterLastCache = true; LocalFavoriteImageProvider.delete(id, type.value); @@ -687,6 +778,55 @@ class LocalFavoritesManager with ChangeNotifier { notifyListeners(); } + void batchDeleteComics(String folder, List comics) { + _modifiedAfterLastCache = true; + _db.execute("BEGIN TRANSACTION"); + try { + for (var comic in comics) { + LocalFavoriteImageProvider.delete(comic.id, comic.type.value); + _db.execute(""" + delete from "$folder" + where id == ? and type == ?; + """, [comic.id, comic.type.value]); + } + if (counts[folder] != null) { + counts[folder] = counts[folder]! - comics.length; + } else { + counts[folder] = count(folder); + } + } catch (e) { + Log.error("Batch Delete Comics", e.toString()); + _db.execute("ROLLBACK"); + return; + } + _db.execute("COMMIT"); + notifyListeners(); + } + + void batchDeleteComicsInAllFolders(List comics) { + _modifiedAfterLastCache = true; + _db.execute("BEGIN TRANSACTION"); + var folderNames = _getFolderNamesWithDB(); + try { + for (var comic in comics) { + LocalFavoriteImageProvider.delete(comic.id, comic.type.value); + for (var folder in folderNames) { + _db.execute(""" + delete from "$folder" + where id == ? and type == ?; + """, [comic.id, comic.type.value]); + } + } + } catch (e) { + Log.error("Batch Delete Comics in All Folders", e.toString()); + _db.execute("ROLLBACK"); + return; + } + initCounts(); + _db.execute("COMMIT"); + notifyListeners(); + } + Future removeInvalid() async { int count = 0; await Future.microtask(() { @@ -714,11 +854,26 @@ class LocalFavoritesManager with ChangeNotifier { if (!existsFolder(folder)) { throw Exception("Failed to reorder: folder not found"); } - deleteFolder(folder); - createFolder(folder); - for (int i = 0; i < newFolder.length; i++) { - addComic(folder, newFolder[i], i); + _db.execute("BEGIN TRANSACTION"); + try { + for (int i = 0; i < newFolder.length; i++) { + _db.execute(""" + update "$folder" + set display_order = ? + where id == ? and type == ?; + """, [ + i, + newFolder[i].id, + newFolder[i].type.value + ]); + } } + catch (e) { + Log.error("Reorder", e.toString()); + _db.execute("ROLLBACK"); + return; + } + _db.execute("COMMIT"); notifyListeners(); } @@ -743,6 +898,8 @@ class LocalFavoritesManager with ChangeNotifier { set folder_name = ? where folder_name == ?; """, [after, before]); + counts[after] = counts[before] ?? 0; + counts.remove(before); notifyListeners(); } diff --git a/lib/foundation/history.dart b/lib/foundation/history.dart index 0c017cf..4ea0892 100644 --- a/lib/foundation/history.dart +++ b/lib/foundation/history.dart @@ -10,6 +10,7 @@ import 'package:flutter/widgets.dart' show ChangeNotifier; import 'package:sqlite3/sqlite3.dart'; import 'package:venera/foundation/comic_source/comic_source.dart'; import 'package:venera/foundation/comic_type.dart'; +import 'package:venera/foundation/favorites.dart'; import 'package:venera/foundation/image_provider/image_favorites_provider.dart'; import 'package:venera/foundation/log.dart'; import 'package:venera/utils/ext.dart'; @@ -305,6 +306,31 @@ class HistoryManager with ChangeNotifier { notifyListeners(); } +void clearUnfavoritedHistory() { + _db.execute('BEGIN TRANSACTION;'); + try { + final idAndTypes = _db.select(""" + select id, type from history; + """); + for (var element in idAndTypes) { + final id = element["id"] as String; + final type = ComicType(element["type"] as int); + if (!LocalFavoritesManager().isExist(id, type)) { + _db.execute(""" + delete from history + where id == ? and type == ?; + """, [id, type.value]); + } + } + _db.execute('COMMIT;'); + } catch (e) { + _db.execute('ROLLBACK;'); + rethrow; + } + updateCache(); + notifyListeners(); +} + void remove(String id, ComicType type) async { _db.execute(""" delete from history @@ -380,4 +406,23 @@ class HistoryManager with ChangeNotifier { isInitialized = false; _db.dispose(); } + + void batchDeleteHistories(List histories) { + if (histories.isEmpty) return; + _db.execute('BEGIN TRANSACTION;'); + try { + for (var history in histories) { + _db.execute(""" + delete from history + where id == ? and type == ?; + """, [history.id, history.type.value]); + } + _db.execute('COMMIT;'); + } catch (e) { + _db.execute('ROLLBACK;'); + rethrow; + } + updateCache(); + notifyListeners(); + } } diff --git a/lib/foundation/local.dart b/lib/foundation/local.dart index b52524f..812d6eb 100644 --- a/lib/foundation/local.dart +++ b/lib/foundation/local.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:isolate'; import 'package:flutter/widgets.dart' show ChangeNotifier; import 'package:path_provider/path_provider.dart'; @@ -461,7 +462,7 @@ class LocalManager with ChangeNotifier { if (comic != null) { return Directory(FilePath.join(path, comic.directory)); } - const comicDirectoryMaxLength = 128; + const comicDirectoryMaxLength = 80; if (name.length > comicDirectoryMaxLength) { name = name.substring(0, comicDirectoryMaxLength); } @@ -546,6 +547,95 @@ class LocalManager with ChangeNotifier { remove(c.id, c.comicType); notifyListeners(); } + + void deleteComicChapters(LocalComic c, List chapters) { + if (chapters.isEmpty) { + return; + } + var newDownloadedChapters = c.downloadedChapters + .where((e) => !chapters.contains(e)) + .toList(); + if (newDownloadedChapters.isNotEmpty) { + _db.execute( + 'UPDATE comics SET downloadedChapters = ? WHERE id = ? AND comic_type = ?;', + [ + jsonEncode(newDownloadedChapters), + c.id, + c.comicType.value, + ], + ); + } else { + _db.execute( + 'DELETE FROM comics WHERE id = ? AND comic_type = ?;', + [c.id, c.comicType.value], + ); + } + var shouldRemovedDirs = []; + for (var chapter in chapters) { + var dir = Directory(FilePath.join(c.baseDir, chapter)); + if (dir.existsSync()) { + shouldRemovedDirs.add(dir); + } + } + if (shouldRemovedDirs.isNotEmpty) { + _deleteDirectories(shouldRemovedDirs); + } + notifyListeners(); + } + + void batchDeleteComics(List comics, [bool removeFileOnDisk = true]) { + if (comics.isEmpty) { + return; + } + + var shouldRemovedDirs = []; + _db.execute('BEGIN TRANSACTION;'); + try { + for (var c in comics) { + if (removeFileOnDisk) { + var dir = Directory(FilePath.join(path, c.directory)); + if (dir.existsSync()) { + shouldRemovedDirs.add(dir); + } + } + _db.execute( + 'DELETE FROM comics WHERE id = ? AND comic_type = ?;', + [c.id, c.comicType.value], + ); + } + } + catch(e, s) { + Log.error("LocalManager", "Failed to batch delete comics: $e", s); + _db.execute('ROLLBACK;'); + return; + } + _db.execute('COMMIT;'); + + var comicIDs = comics.map((e) => ComicID(e.comicType, e.id)).toList(); + LocalFavoritesManager().batchDeleteComicsInAllFolders(comicIDs); + HistoryManager().batchDeleteHistories(comicIDs); + + notifyListeners(); + + if (removeFileOnDisk) { + _deleteDirectories(shouldRemovedDirs); + } + } + + /// Deletes the directories in a separate isolate to avoid blocking the UI thread. + static void _deleteDirectories(List directories) { + Isolate.run(() async { + for (var dir in directories) { + try { + if (dir.existsSync()) { + await dir.delete(recursive: true); + } + } catch (e) { + continue; + } + } + }); + } } enum LocalSortType { diff --git a/lib/pages/favorites/local_favorites_page.dart b/lib/pages/favorites/local_favorites_page.dart index 7dfe925..12f258c 100644 --- a/lib/pages/favorites/local_favorites_page.dart +++ b/lib/pages/favorites/local_favorites_page.dart @@ -155,16 +155,33 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> { void selectAll() { setState(() { - selectedComics = comics.asMap().map((k, v) => MapEntry(v, true)); + if (searchMode) { + selectedComics = searchResults.asMap().map((k, v) => MapEntry(v, true)); + } else { + 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); + if (searchMode) { + for (var c in searchResults) { + if (selectedComics.containsKey(c)) { + selectedComics.remove(c); + } else { + selectedComics[c] = true; + } + } + } else { + for (var c in comics) { + if (selectedComics.containsKey(c)) { + selectedComics.remove(c); + } else { + selectedComics[c] = true; + } + } + } }); } @@ -416,10 +433,12 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> { "Selected @c comics".tlParams({"c": selectedComics.length})), actions: [ MenuButton(entries: [ + if (!isAllFolder) MenuEntry( icon: Icons.drive_file_move, text: "Move to folder".tl, onClick: () => favoriteOption('move')), + if (!isAllFolder) MenuEntry( icon: Icons.copy, text: "Copy to folder".tl, @@ -756,32 +775,26 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> { 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); - } + var comics = selectedComics.keys + .map((e) => e as FavoriteItem) + .toList(); + for (var f in selectedLocalFolders) { + LocalFavoritesManager().batchMoveFavorites( + favPage.folder as String, + f, + comics, + ); } } 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 ?? [], - ), - ); - } + var comics = selectedComics.keys + .map((e) => e as FavoriteItem) + .toList(); + for (var f in selectedLocalFolders) { + LocalFavoritesManager().batchCopyFavorites( + favPage.folder as String, + f, + comics, + ); } } App.rootContext.pop(); @@ -817,13 +830,8 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> { } void _deleteComicWithId() { - for (var c in selectedComics.keys) { - LocalFavoritesManager().deleteComicWithId( - widget.folder, - c.id, - (c as FavoriteItem).type, - ); - } + var toBeDeleted = selectedComics.keys.map((e) => e as FavoriteItem).toList(); + LocalFavoritesManager().batchDeleteComics(widget.folder, toBeDeleted); _cancel(); } } @@ -864,7 +872,10 @@ class _ReorderComicsPageState extends State<_ReorderComicsPage> { @override void dispose() { if (changed) { - LocalFavoritesManager().reorder(comics, widget.name); + // Delay to ensure navigation is completed + Future.delayed(const Duration(milliseconds: 200), () { + LocalFavoritesManager().reorder(comics, widget.name); + }); } super.dispose(); } @@ -899,27 +910,31 @@ class _ReorderComicsPageState extends State<_ReorderComicsPage> { appBar: Appbar( title: Text("Reorder".tl), actions: [ - IconButton( - icon: const Icon(Icons.info_outline), - onPressed: () { - showInfoDialog( - context: context, - title: "Reorder".tl, - content: "Long press and drag to reorder.".tl, - ); - }, - ), - IconButton( - icon: const Icon(Icons.swap_vert), - onPressed: () { - setState(() { - comics = comics.reversed.toList(); - changed = true; - showToast( - message: "Reversed successfully".tl, context: context); - }); - }, + Tooltip( + message: "Information".tl, + child: IconButton( + icon: const Icon(Icons.info_outline), + onPressed: () { + showInfoDialog( + context: context, + title: "Reorder".tl, + content: "Long press and drag to reorder.".tl, + ); + }, + ), ), + Tooltip( + message: "Reverse".tl, + child: IconButton( + icon: const Icon(Icons.swap_vert), + onPressed: () { + setState(() { + comics = comics.reversed.toList(); + changed = true; + }); + }, + ), + ) ], ), body: ReorderableBuilder( diff --git a/lib/pages/favorites/side_bar.dart b/lib/pages/favorites/side_bar.dart index 464ad75..1125b0a 100644 --- a/lib/pages/favorites/side_bar.dart +++ b/lib/pages/favorites/side_bar.dart @@ -42,6 +42,7 @@ class _LeftBarState extends State<_LeftBar> implements FolderList { folders = LocalFavoritesManager().folderNames; findNetworkFolders(); appdata.settings.addListener(updateFolders); + LocalFavoritesManager().addListener(updateFolders); super.initState(); } @@ -49,6 +50,7 @@ class _LeftBarState extends State<_LeftBar> implements FolderList { void dispose() { super.dispose(); appdata.settings.removeListener(updateFolders); + LocalFavoritesManager().removeListener(updateFolders); } @override diff --git a/lib/pages/history_page.dart b/lib/pages/history_page.dart index 049021e..bd65ce7 100644 --- a/lib/pages/history_page.dart +++ b/lib/pages/history_page.dart @@ -140,6 +140,14 @@ class _HistoryPageState extends State { title: 'Clear History'.tl, content: Text('Are you sure you want to clear your history?'.tl), actions: [ + Button.outlined( + onPressed: () { + HistoryManager().clearUnfavoritedHistory(); + context.pop(); + }, + child: Text('Clear Unfavorited'.tl), + ), + const SizedBox(width: 4), Button.filled( color: context.colorScheme.error, onPressed: () { diff --git a/lib/pages/local_comics_page.dart b/lib/pages/local_comics_page.dart index 22b4dd9..6904054 100644 --- a/lib/pages/local_comics_page.dart +++ b/lib/pages/local_comics_page.dart @@ -374,15 +374,21 @@ class _LocalComicsPageState extends State { }, ), actions: [ + if (comics.length == 1 && comics.first.hasChapters) + TextButton( + child: Text("Delete Chapters".tl), + onPressed: () { + context.pop(); + showDeleteChaptersPopWindow(context, comics.first); + }, + ), FilledButton( onPressed: () { context.pop(); - for (var comic in comics) { - LocalManager().deleteComic( - comic, - removeComicFile, - ); - } + LocalManager().batchDeleteComics( + comics, + removeComicFile, + ); isDeleted = true; }, child: Text("Confirm".tl), @@ -497,3 +503,59 @@ class _LocalComicsPageState extends State { typedef ExportComicFunc = Future Function( LocalComic comic, String outFilePath); + +void showDeleteChaptersPopWindow(BuildContext context, LocalComic comic) { + var chapters = []; + + showPopUpWidget( + context, + PopUpWidgetScaffold( + title: "Delete Chapters".tl, + body: StatefulBuilder(builder: (context, setState) { + return Column( + children: [ + Expanded( + child: ListView.builder( + itemCount: comic.downloadedChapters.length, + itemBuilder: (context, index) { + var id = comic.downloadedChapters[index]; + var chapter = comic.chapters![id] ?? "Unknown Chapter"; + return CheckboxListTile( + title: Text(chapter), + value: chapters.contains(id), + onChanged: (v) { + setState(() { + if (v == true) { + chapters.add(id); + } else { + chapters.remove(id); + } + }); + }, + ); + }, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + FilledButton( + onPressed: () { + Future.delayed(const Duration(milliseconds: 200), () { + LocalManager().deleteComicChapters(comic, chapters); + }); + App.rootContext.pop(); + }, + child: Text("Submit".tl), + ) + ], + ), + ) + ], + ); + }), + ), + ); +} diff --git a/pubspec.lock b/pubspec.lock index 1d4a5da..e96ad55 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" battery_plus: dependency: "direct main" description: @@ -190,10 +190,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" ffi: dependency: transitive description: @@ -524,10 +524,10 @@ packages: dependency: "direct main" description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "0.20.2" io: dependency: transitive description: @@ -548,10 +548,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: @@ -1037,10 +1037,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" web: dependency: transitive description: @@ -1107,5 +1107,5 @@ packages: source: hosted version: "0.0.12" sdks: - dart: ">=3.7.0 <4.0.0" - flutter: ">=3.29.3" + dart: ">=3.8.0 <4.0.0" + flutter: ">=3.32.0" diff --git a/pubspec.yaml b/pubspec.yaml index 8731d77..6fc8078 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,11 +2,11 @@ name: venera description: "A comic app." publish_to: 'none' -version: 1.4.3+143 +version: 1.4.4+144 environment: - sdk: '>=3.6.0 <4.0.0' - flutter: 3.29.3 + sdk: '>=3.8.0 <4.0.0' + flutter: 3.32.0 dependencies: flutter: