diff --git a/lib/foundation/comic_source/category.dart b/lib/foundation/comic_source/category.dart index 22d1756..3cb8e7c 100644 --- a/lib/foundation/comic_source/category.dart +++ b/lib/foundation/comic_source/category.dart @@ -144,7 +144,7 @@ class RandomCategoryPartWithRuntimeData extends BaseCategoryPart { } CategoryData getCategoryDataWithKey(String key) { - for (var source in ComicSource.sources) { + for (var source in ComicSource._sources) { if (source.categoryData?.key == key) { return source.categoryData!; } diff --git a/lib/foundation/comic_source/comic_source.dart b/lib/foundation/comic_source/comic_source.dart index 652acac..ee951d9 100644 --- a/lib/foundation/comic_source/comic_source.dart +++ b/lib/foundation/comic_source/comic_source.dart @@ -43,13 +43,31 @@ typedef GetThumbnailLoadingConfigFunc = Map Function( String imageKey)?; class ComicSource { - static List sources = []; + static final List _sources = []; + + static final List _listeners = []; + + static void addListener(Function listener) { + _listeners.add(listener); + } + + static void removeListener(Function listener) { + _listeners.remove(listener); + } + + static void notifyListeners() { + for (var listener in _listeners) { + listener(); + } + } + + static List all() => List.from(_sources); static ComicSource? find(String key) => - sources.firstWhereOrNull((element) => element.key == key); + _sources.firstWhereOrNull((element) => element.key == key); static ComicSource? fromIntKey(int key) => - sources.firstWhereOrNull((element) => element.key.hashCode == key); + _sources.firstWhereOrNull((element) => element.key.hashCode == key); static Future init() async { final path = "${App.dataPath}/comic_source"; @@ -62,7 +80,7 @@ class ComicSource { try { var source = await ComicSourceParser() .parse(await entity.readAsString(), entity.absolute.path); - sources.add(source); + _sources.add(source); } catch (e, s) { Log.error("ComicSource", "$e\n$s"); } @@ -71,9 +89,20 @@ class ComicSource { } static Future reload() async { - sources.clear(); + _sources.clear(); JsEngine().runCode("ComicSource.sources = {};"); await init(); + notifyListeners(); + } + + static void add(ComicSource source) { + _sources.add(source); + notifyListeners(); + } + + static void remove(String key) { + _sources.removeWhere((element) => element.key == key); + notifyListeners(); } /// Name of this source. @@ -123,7 +152,7 @@ class ComicSource { var data = {}; - bool get isLogin => data["account"] != null; + bool get isLogged => data["account"] != null; final String filePath; diff --git a/lib/foundation/comic_source/parser.dart b/lib/foundation/comic_source/parser.dart index edc2059..456d63c 100644 --- a/lib/foundation/comic_source/parser.dart +++ b/lib/foundation/comic_source/parser.dart @@ -105,7 +105,7 @@ class ComicSourceParser { throw ComicSourceParseException("minAppVersion $minAppVersion is required"); } } - for(var source in ComicSource.sources){ + for(var source in ComicSource.all()){ if(source.key == key){ throw ComicSourceParseException("key($key) already exists"); } @@ -188,8 +188,7 @@ class ComicSourceParser { ComicSource.sources.$_key.account.login(${jsonEncode(account)}, ${jsonEncode(pwd)}) """); - var source = ComicSource.sources - .firstWhere((element) => element.key == _key); + var source = ComicSource.find(_key!)!; source.data["account"] = [account, pwd]; source.saveData(); return const Res(true); @@ -461,7 +460,7 @@ class ComicSourceParser { final bool multiFolder = _getValue("favorites.multiFolder"); Future> retryZone(Future> Function() func) async{ - if(!ComicSource.find(_key!)!.isLogin){ + if(!ComicSource.find(_key!)!.isLogged){ return const Res.error("Not login"); } var res = await func(); diff --git a/lib/foundation/comic_type.dart b/lib/foundation/comic_type.dart index 0120186..fedd6d3 100644 --- a/lib/foundation/comic_type.dart +++ b/lib/foundation/comic_type.dart @@ -15,7 +15,7 @@ class ComicType { if(this == local) { return null; } else { - return ComicSource.sources.firstWhere((element) => element.intKey == value); + return ComicSource.fromIntKey(value); } } diff --git a/lib/foundation/js_engine.dart b/lib/foundation/js_engine.dart index 5732bda..028d741 100644 --- a/lib/foundation/js_engine.dart +++ b/lib/foundation/js_engine.dart @@ -22,7 +22,6 @@ import 'package:pointycastle/block/modes/ecb.dart'; import 'package:pointycastle/block/modes/ofb.dart'; import 'package:venera/network/app_dio.dart'; import 'package:venera/network/cookie_jar.dart'; -import 'package:venera/utils/ext.dart'; import 'comic_source/comic_source.dart'; import 'consts.dart'; @@ -107,8 +106,7 @@ class JsEngine with _JSEngineApi{ { String key = message["key"]; String dataKey = message["data_key"]; - return ComicSource.sources - .firstWhereOrNull((element) => element.key == key) + return ComicSource.find(key) ?.data[dataKey]; } case 'save_data': @@ -116,8 +114,7 @@ class JsEngine with _JSEngineApi{ String key = message["key"]; String dataKey = message["data_key"]; var data = message["data"]; - var source = ComicSource.sources - .firstWhere((element) => element.key == key); + var source = ComicSource.find(key)!; source.data[dataKey] = data; source.saveData(); } @@ -125,8 +122,7 @@ class JsEngine with _JSEngineApi{ { String key = message["key"]; String dataKey = message["data_key"]; - var source = ComicSource.sources - .firstWhereOrNull((element) => element.key == key); + var source = ComicSource.find(key); source?.data.remove(dataKey); source?.saveData(); } diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index e8cca1a..1c91bbb 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:venera/components/components.dart'; import 'package:venera/foundation/app.dart'; +import 'package:venera/foundation/comic_source/comic_source.dart'; import 'package:venera/foundation/comic_type.dart'; import 'package:venera/foundation/history.dart'; import 'package:venera/foundation/image_provider/cached_image.dart'; @@ -21,19 +22,47 @@ class HomePage extends StatelessWidget { slivers: [ _History(), _Local(), + _ComicSourceWidget(), + _AccountsWidget(), ], ); } } -class _History extends StatelessWidget { +class _History extends StatefulWidget { const _History(); @override - Widget build(BuildContext context) { - final history = HistoryManager().getRecent(); - final count = HistoryManager().count(); + State<_History> createState() => _HistoryState(); +} +class _HistoryState extends State<_History> { + late List history; + late int count; + + void onHistoryChange() { + setState(() { + history = HistoryManager().getRecent(); + count = HistoryManager().count(); + }); + } + + @override + void initState() { + history = HistoryManager().getRecent(); + count = HistoryManager().count(); + HistoryManager().addListener(onHistoryChange); + super.initState(); + } + + @override + void dispose() { + HistoryManager().removeListener(onHistoryChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { return SliverToBoxAdapter( child: InkWell( onTap: () {}, @@ -117,14 +146,40 @@ class _History extends StatelessWidget { } } -class _Local extends StatelessWidget { +class _Local extends StatefulWidget { const _Local(); @override - Widget build(BuildContext context) { - final local = LocalManager().getRecent(); - final count = LocalManager().count; + State<_Local> createState() => _LocalState(); +} +class _LocalState extends State<_Local> { + late List local; + late int count; + + void onLocalComicsChange() { + setState(() { + local = LocalManager().getRecent(); + count = LocalManager().count; + }); + } + + @override + void initState() { + local = LocalManager().getRecent(); + count = LocalManager().count; + LocalManager().addListener(onLocalComicsChange); + super.initState(); + } + + @override + void dispose() { + LocalManager().removeListener(onLocalComicsChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { return SliverToBoxAdapter( child: InkWell( onTap: () {}, @@ -495,3 +550,198 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> { ); } } + +class _ComicSourceWidget extends StatefulWidget { + const _ComicSourceWidget(); + + @override + State<_ComicSourceWidget> createState() => _ComicSourceWidgetState(); +} + +class _ComicSourceWidgetState extends State<_ComicSourceWidget> { + late List comicSources; + + void onComicSourceChange() { + setState(() { + comicSources = ComicSource.all().map((e) => e.name).toList(); + }); + } + + @override + void initState() { + comicSources = ComicSource.all().map((e) => e.name).toList(); + ComicSource.addListener(onComicSourceChange); + super.initState(); + } + + @override + void dispose() { + ComicSource.removeListener(onComicSourceChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SliverToBoxAdapter( + child: InkWell( + onTap: () {}, + child: Container( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Theme.of(context).colorScheme.outlineVariant, + width: 0.6, + ), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 56, + child: Row( + children: [ + Center( + child: Text('Comic Source'.tl, style: ts.s18), + ), + Container( + margin: const EdgeInsets.symmetric(horizontal: 8), + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.secondaryContainer, + borderRadius: BorderRadius.circular(8), + ), + child: Text(comicSources.length.toString(), style: ts.s12), + ), + const Spacer(), + const Icon(Icons.arrow_right), + ], + ), + ).paddingHorizontal(16), + SizedBox( + width: double.infinity, + child: Wrap( + children: comicSources.map((e) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 8), + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.secondaryContainer, + borderRadius: BorderRadius.circular(8), + ), + child: Text(e), + ); + }).toList(), + ), + ), + ], + ), + ), + ), + ); + } +} + +class _AccountsWidget extends StatefulWidget { + const _AccountsWidget(); + + @override + State<_AccountsWidget> createState() => _AccountsWidgetState(); +} + +class _AccountsWidgetState extends State<_AccountsWidget> { + late List accounts; + + void onComicSourceChange() { + setState(() { + for(var c in ComicSource.all()) { + if(c.isLogged) { + accounts.add(c.name); + } + } + }); + } + + @override + void initState() { + accounts = []; + for(var c in ComicSource.all()) { + if(c.isLogged) { + accounts.add(c.name); + } + } + ComicSource.addListener(onComicSourceChange); + super.initState(); + } + + @override + void dispose() { + ComicSource.removeListener(onComicSourceChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SliverToBoxAdapter( + child: InkWell( + onTap: () {}, + child: Container( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Theme.of(context).colorScheme.outlineVariant, + width: 0.6, + ), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 56, + child: Row( + children: [ + Center( + child: Text('Accounts'.tl, style: ts.s18), + ), + Container( + margin: const EdgeInsets.symmetric(horizontal: 8), + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.secondaryContainer, + borderRadius: BorderRadius.circular(8), + ), + child: Text(accounts.length.toString(), style: ts.s12), + ), + const Spacer(), + const Icon(Icons.arrow_right), + ], + ), + ).paddingHorizontal(16), + SizedBox( + width: double.infinity, + child: Wrap( + children: accounts.map((e) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 8), + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.secondaryContainer, + borderRadius: BorderRadius.circular(8), + ), + child: Text(e), + ); + }).toList(), + ), + ), + ], + ), + ), + ), + ); + } +}