diff --git a/assets/translation.json b/assets/translation.json index 0f666b6..309c9d1 100644 --- a/assets/translation.json +++ b/assets/translation.json @@ -147,7 +147,11 @@ "A cbz file" : "一个cbz文件", "Fullscreen": "全屏", "Exit": "退出", - "View more": "查看更多" + "View more": "查看更多", + "Sort": "排序", + "Name": "名称", + "Date": "日期", + "Date Desc": "日期降序" }, "zh_TW": { "Home": "首頁", @@ -297,6 +301,10 @@ "A cbz file" : "一個cbz文件", "Fullscreen": "全螢幕", "Exit": "退出", - "View more": "查看更多" + "View more": "查看更多", + "Sort": "排序", + "Name": "名稱", + "Date": "日期", + "Date Desc": "日期降序" } } \ No newline at end of file diff --git a/lib/foundation/local.dart b/lib/foundation/local.dart index c3ccc61..29c46fb 100644 --- a/lib/foundation/local.dart +++ b/lib/foundation/local.dart @@ -261,8 +261,14 @@ class LocalManager with ChangeNotifier { notifyListeners(); } - List getComics() { - final res = _db.select('SELECT * FROM comics;'); + List getComics(LocalSortType sortType) { + var res = _db.select(''' + SELECT * FROM comics + ORDER BY + ${sortType.value == 'name' ? 'title' : 'created_at'} + ${sortType.value == 'time_asc' ? 'ASC' : 'DESC'} + ; + '''); return res.map((row) => LocalComic.fromRow(row)).toList(); } @@ -310,6 +316,15 @@ class LocalManager with ChangeNotifier { return LocalComic.fromRow(res.first); } + List search(String keyword) { + final res = _db.select(''' + SELECT * FROM comics + WHERE title LIKE ? OR tags LIKE ? OR subtitle LIKE ? + ORDER BY created_at DESC; + ''', ['%$keyword%', '%$keyword%', '%$keyword%']); + return res.map((row) => LocalComic.fromRow(row)).toList(); + } + Future> getImages(String id, ComicType type, Object ep) async { if(ep is! String && ep is! int) { throw "Invalid ep"; @@ -429,3 +444,22 @@ class LocalManager with ChangeNotifier { notifyListeners(); } } + +enum LocalSortType { + name("name"), + timeAsc("time_asc"), + timeDesc("time_desc"); + + final String value; + + const LocalSortType(this.value); + + static LocalSortType fromString(String value) { + for (var type in values) { + if (type.value == value) { + return type; + } + } + return name; + } +} \ No newline at end of file diff --git a/lib/pages/local_comics_page.dart b/lib/pages/local_comics_page.dart index 6252a81..eee469b 100644 --- a/lib/pages/local_comics_page.dart +++ b/lib/pages/local_comics_page.dart @@ -1,6 +1,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/local.dart'; import 'package:venera/pages/downloading_page.dart'; import 'package:venera/utils/cbz.dart'; @@ -17,15 +18,29 @@ class LocalComicsPage extends StatefulWidget { class _LocalComicsPageState extends State { late List comics; + late LocalSortType sortType; + + String keyword = ""; + + bool searchMode = false; + void update() { - setState(() { - comics = LocalManager().getComics(); - }); + if(keyword.isEmpty) { + setState(() { + comics = LocalManager().getComics(sortType); + }); + } else { + setState(() { + comics = LocalManager().search(keyword); + }); + } } @override void initState() { - comics = LocalManager().getComics(); + var sort = appdata.implicitData["local_sort"] ?? "name"; + sortType = LocalSortType.fromString(sort); + comics = LocalManager().getComics(sortType); LocalManager().addListener(update); super.initState(); } @@ -36,25 +51,129 @@ class _LocalComicsPageState extends State { super.dispose(); } + void sort() { + showDialog( + context: context, + builder: (context) { + return StatefulBuilder(builder: (context, setState) { + return ContentDialog( + title: "Sort".tl, + content: Column( + children: [ + RadioListTile( + title: Text("Name".tl), + value: LocalSortType.name, + groupValue: sortType, + onChanged: (v) { + setState(() { + sortType = v!; + }); + }, + ), + RadioListTile( + title: Text("Date".tl), + value: LocalSortType.timeAsc, + groupValue: sortType, + onChanged: (v) { + setState(() { + sortType = v!; + }); + }, + ), + RadioListTile( + title: Text("Date Desc".tl), + value: LocalSortType.timeDesc, + groupValue: sortType, + onChanged: (v) { + setState(() { + sortType = v!; + }); + }, + ), + ], + ), + actions: [ + FilledButton( + onPressed: () { + appdata.implicitData["local_sort"] = + sortType.value; + appdata.writeImplicitData(); + Navigator.pop(context); + update(); + }, + child: Text("Confirm".tl), + ), + ], + ); + }); + }, + ); + } + @override Widget build(BuildContext context) { return Scaffold( body: SmoothCustomScrollView( slivers: [ - SliverAppbar( - title: Text("Local".tl), - actions: [ - Tooltip( - message: "Downloading".tl, - child: IconButton( - icon: const Icon(Icons.download), + if(!searchMode) + SliverAppbar( + title: Text("Local".tl), + actions: [ + Tooltip( + message: "Search".tl, + child: IconButton( + icon: const Icon(Icons.search), + onPressed: () { + setState(() { + searchMode = true; + }); + }, + ), + ), + Tooltip( + message: "Sort".tl, + child: IconButton( + icon: const Icon(Icons.sort), + onPressed: sort, + ), + ), + Tooltip( + message: "Downloading".tl, + child: IconButton( + icon: const Icon(Icons.download), + onPressed: () { + showPopUpWidget(context, const DownloadingPage()); + }, + ), + ) + ], + ) + else + SliverAppbar( + title: TextField( + autofocus: true, + decoration: InputDecoration( + hintText: "Search".tl, + border: InputBorder.none, + ), + onChanged: (v) { + keyword = v; + update(); + }, + ), + actions: [ + IconButton( + icon: const Icon(Icons.close), onPressed: () { - showPopUpWidget(context, const DownloadingPage()); + setState(() { + searchMode = false; + keyword = ""; + update(); + }); }, ), - ) - ], - ), + ], + ), SliverGridComics( comics: comics, onTap: (c) { @@ -80,8 +199,7 @@ class _LocalComicsPageState extends State { var file = await CBZ.export(c as LocalComic); await saveFile(filename: file.name, file: file); await file.delete(); - } - catch (e) { + } catch (e) { context.showMessage(message: e.toString()); } controller.close();