diff --git a/.github/workflows/update_alt_store.yml b/.github/workflows/update_alt_store.yml index c42a5cf..d896199 100644 --- a/.github/workflows/update_alt_store.yml +++ b/.github/workflows/update_alt_store.yml @@ -31,19 +31,30 @@ jobs: - name: Update AltStore source id: update_source env: - GITHUB_TOKEN: ${{ secrets.ACTION_GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - python update_alt_store.py - git config --global user.name 'GitHub Action' - git config --global user.email 'action@github.com' - git add alt_store.json - if git diff --staged --quiet; then - echo "changes=false" >> $GITHUB_OUTPUT - else - git commit -m "Updated source with latest release" - git push - echo "changes=true" >> $GITHUB_OUTPUT - fi + python update_alt_store.py + git config --global user.name 'GitHub Action' + git config --global user.email 'action@github.com' + git add alt_store.json + if git diff --staged --quiet; then + echo "changes=false" >> $GITHUB_OUTPUT + else + # Create a new branch for the PR + branch_name="update-altstore-$(date +%Y%m%d-%H%M%S)" + git checkout -b "$branch_name" + git commit -m "Updated source with latest release" + git push -u origin "$branch_name" + + # Create PR using GitHub CLI + gh pr create \ + --title "Update AltStore source with latest release" \ + --body "This PR updates the alt_store.json file with the latest release information." \ + --head "$branch_name" \ + --base main + + echo "changes=true" >> $GITHUB_OUTPUT + fi - name: Calculate job duration id: duration diff --git a/analysis_options.yaml b/analysis_options.yaml index d9ed12b..d79c394 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -23,7 +23,7 @@ linter: rules: collection_methods_unrelated_type: false use_build_context_synchronously: false - # avoid_print: false # Uncomment to disable the `avoid_print` rule + avoid_print: false # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # Additional information about this file can be found at diff --git a/android/app/build.gradle b/android/app/build.gradle index f6742d8..d233c5a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -84,9 +84,8 @@ android { buildTypes { release { - // Temporarily solution to fix crash - minifyEnabled false - shrinkResources false + minifyEnabled true + shrinkResources true ndk { abiFilters "armeabi-v7a", "arm64-v8a", "x86_64" } diff --git a/lib/foundation/app.dart b/lib/foundation/app.dart index 9678791..0687793 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.5.2"; + final version = "1.5.3"; bool get isAndroid => Platform.isAndroid; diff --git a/lib/foundation/app_page_route.dart b/lib/foundation/app_page_route.dart index 76f61cd..bbb8135 100644 --- a/lib/foundation/app_page_route.dart +++ b/lib/foundation/app_page_route.dart @@ -128,7 +128,7 @@ mixin _AppRouteTransitionMixin on PageRoute { context, animation, secondaryAnimation, - enableIOSGesture + enableIOSGesture && App.isIOS ? IOSBackGestureDetector( gestureWidth: _kBackGestureWidth, enabledCallback: () => _isPopGestureEnabled(this), @@ -302,7 +302,7 @@ class _IOSBackGestureDetectorState extends State { assert(mounted); assert(_backGestureController != null); _backGestureController!.dragUpdate( - _convertToLogical(details.primaryDelta! / context.size!.width)); + _convertToLogical(details.primaryDelta! / context.size!.width)); } } diff --git a/lib/pages/categories_page.dart b/lib/pages/categories_page.dart index 15ed457..953326a 100644 --- a/lib/pages/categories_page.dart +++ b/lib/pages/categories_page.dart @@ -17,39 +17,50 @@ class CategoriesPage extends StatefulWidget { State createState() => _CategoriesPageState(); } -class _CategoriesPageState extends State { +class _CategoriesPageState extends State + with + TickerProviderStateMixin, + AutomaticKeepAliveClientMixin { var categories = []; + late TabController controller; + void onSettingsChanged() { - var categories = - List.from(appdata.settings["categories"]).whereType().toList(); + var categories = List.from( + appdata.settings["categories"], + ).whereType().toList(); var allCategories = ComicSource.all() .map((e) => e.categoryData?.key) .where((element) => element != null) .map((e) => e!) .toList(); - categories = - categories.where((element) => allCategories.contains(element)).toList(); + categories = categories + .where((element) => allCategories.contains(element)) + .toList(); if (!categories.isEqualTo(this.categories)) { setState(() { this.categories = categories; }); + controller = TabController(length: categories.length, vsync: this); } } @override void initState() { super.initState(); - var categories = - List.from(appdata.settings["categories"]).whereType().toList(); + var categories = List.from( + appdata.settings["categories"], + ).whereType().toList(); var allCategories = ComicSource.all() .map((e) => e.categoryData?.key) .where((element) => element != null) .map((e) => e!) .toList(); - this.categories = - categories.where((element) => allCategories.contains(element)).toList(); + this.categories = categories + .where((element) => allCategories.contains(element)) + .toList(); appdata.settings.addListener(onSettingsChanged); + controller = TabController(length: categories.length, vsync: this); } void addPage() { @@ -59,6 +70,7 @@ class _CategoriesPageState extends State { @override void dispose() { super.dispose(); + controller.dispose(); appdata.settings.removeListener(onSettingsChanged); } @@ -85,46 +97,45 @@ class _CategoriesPageState extends State { @override Widget build(BuildContext context) { + super.build(context); if (categories.isEmpty) { return buildEmpty(); } return Material( - child: DefaultTabController( - length: categories.length, - key: Key(categories.toString()), - child: Column( - children: [ - AppTabBar( - key: PageStorageKey(categories.toString()), - tabs: categories.map((e) { - String title = e; - try { - title = getCategoryDataWithKey(e).title; - } catch (e) { - // - } - return Tab( - text: title, - key: Key(e), - ); - }).toList(), - actionButton: TabActionButton( - icon: const Icon(Icons.add), - text: "Add".tl, - onPressed: addPage, - ), - ).paddingTop(context.padding.top), - Expanded( - child: TabBarView( - children: categories.map((e) => _CategoryPage(e)).toList(), - ), - ) - ], - ), + child: Column( + children: [ + AppTabBar( + controller: controller, + key: PageStorageKey(categories.toString()), + tabs: categories.map((e) { + String title = e; + try { + title = getCategoryDataWithKey(e).title; + } catch (e) { + // + } + return Tab(text: title, key: Key(e)); + }).toList(), + actionButton: TabActionButton( + icon: const Icon(Icons.add), + text: "Add".tl, + onPressed: addPage, + ), + ).paddingTop(context.padding.top), + Expanded( + child: TabBarView( + controller: controller, + children: categories.map((e) => _CategoryPage(e)).toList(), + ), + ), + ], ), ); } + + @override + bool get wantKeepAlive => true; } typedef ClickTagCallback = void Function(String, String?); @@ -150,38 +161,42 @@ class _CategoryPage extends StatelessWidget { var children = []; if (data.enableRankingPage || data.buttons.isNotEmpty) { children.add(buildTitle(data.title)); - children.add(Padding( - padding: const EdgeInsets.fromLTRB(10, 0, 10, 16), - child: Wrap( - children: [ - if (data.enableRankingPage) - buildTag("Ranking".tl, () { - context.to(() => RankingPage(categoryKey: data.key)); - }), - for (var buttonData in data.buttons) - buildTag(buttonData.label.tl, buttonData.onTap) - ], + children.add( + Padding( + padding: const EdgeInsets.fromLTRB(10, 0, 10, 16), + child: Wrap( + children: [ + if (data.enableRankingPage) + buildTag("Ranking".tl, () { + context.to(() => RankingPage(categoryKey: data.key)); + }), + for (var buttonData in data.buttons) + buildTag(buttonData.label.tl, buttonData.onTap), + ], + ), ), - )); + ); } for (var part in data.categories) { if (part.enableRandom) { - children.add(StatefulBuilder(builder: (context, updater) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - buildTitleWithRefresh(part.title, () => updater(() {})), - buildTags(part.categories) - ], - ); - })); + children.add( + StatefulBuilder( + builder: (context, updater) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildTitleWithRefresh(part.title, () => updater(() {})), + buildTags(part.categories), + ], + ); + }, + ), + ); } else { children.add(buildTitle(part.title)); - children.add( - buildTags(part.categories), - ); + children.add(buildTags(part.categories)); } } return SingleChildScrollView( @@ -195,8 +210,10 @@ class _CategoryPage extends StatelessWidget { Widget buildTitle(String title) { return Padding( padding: const EdgeInsets.fromLTRB(16, 10, 5, 10), - child: Text(title.tl, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500)), + child: Text( + title.tl, + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500), + ), ); } @@ -207,21 +224,16 @@ class _CategoryPage extends StatelessWidget { children: [ Text( title.tl, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - ), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500), ), const Spacer(), - IconButton(onPressed: onRefresh, icon: const Icon(Icons.refresh)) + IconButton(onPressed: onRefresh, icon: const Icon(Icons.refresh)), ], ), ); } - Widget buildTags( - List categories, - ) { + Widget buildTags(List categories) { return Padding( padding: const EdgeInsets.fromLTRB(10, 0, 10, 16), child: Wrap( diff --git a/lib/pages/comic_details_page/actions.dart b/lib/pages/comic_details_page/actions.dart index fdb579c..4360e08 100644 --- a/lib/pages/comic_details_page/actions.dart +++ b/lib/pages/comic_details_page/actions.dart @@ -155,64 +155,60 @@ abstract mixin class _ComicPageActions { builder: (context, setState) { return ContentDialog( title: "Download".tl, - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - RadioListTile( - value: -1, - groupValue: selected, - title: Text("Normal".tl), - onChanged: (v) { - setState(() { - selected = v!; - }); - }, - ), - ExpansionTile( - title: Text("Archive".tl), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.zero, + content: RadioGroup( + groupValue: selected, + onChanged: (v) { + setState(() { + selected = v ?? selected; + }); + }, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + RadioListTile( + value: -1, + title: Text("Normal".tl), ), - collapsedShape: const RoundedRectangleBorder( - borderRadius: BorderRadius.zero, - ), - onExpansionChanged: (b) { - if (!isLoading && b && archives == null) { - isLoading = true; - comicSource.archiveDownloader! - .getArchives(comic.id) - .then((value) { - if (value.success) { - archives = value.data; - } else { - App.rootContext - .showMessage(message: value.errorMessage!); - } - setState(() { - isLoading = false; + ExpansionTile( + title: Text("Archive".tl), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.zero, + ), + collapsedShape: const RoundedRectangleBorder( + borderRadius: BorderRadius.zero, + ), + onExpansionChanged: (b) { + if (!isLoading && b && archives == null) { + isLoading = true; + comicSource.archiveDownloader! + .getArchives(comic.id) + .then((value) { + if (value.success) { + archives = value.data; + } else { + App.rootContext + .showMessage(message: value.errorMessage!); + } + setState(() { + isLoading = false; + }); }); - }); - } - }, - children: [ - if (archives == null) - const ListLoadingIndicator().toCenter() - else - for (int i = 0; i < archives!.length; i++) - RadioListTile( - value: i, - groupValue: selected, - onChanged: (v) { - setState(() { - selected = v!; - }); - }, - title: Text(archives![i].title), - subtitle: Text(archives![i].description), - ) - ], - ) - ], + } + }, + children: [ + if (archives == null) + const ListLoadingIndicator().toCenter() + else + for (int i = 0; i < archives!.length; i++) + RadioListTile( + value: i, + title: Text(archives![i].title), + subtitle: Text(archives![i].description), + ) + ], + ) + ], + ), ), actions: [ Button.filled( @@ -237,10 +233,12 @@ abstract mixin class _ComicPageActions { isGettingLink = false; }); } else if (context.mounted) { - LocalManager() + if (res.data.isNotEmpty) { + LocalManager() .addTask(ArchiveDownloadTask(res.data, comic)); - App.rootContext + App.rootContext .showMessage(message: "Download started".tl); + } context.pop(); } }, diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 4b0b2d7..c28c1bb 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -514,51 +514,53 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> { child: CircularProgressIndicator(), ), ) - : Column( - key: key, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(width: 600), - ...List.generate(importMethods.length, (index) { - return RadioListTile( - title: Text(importMethods[index]), - value: index, - groupValue: type, - onChanged: (value) { - setState(() { - type = value as int; - }); - }, - ); - }), - if (type != 4) - ListTile( - title: Text("Add to favorites".tl), - trailing: Select( - current: selectedFolder, - values: folders, - minWidth: 112, - onTap: (v) { - setState(() { - selectedFolder = folders[v]; - }); - }, - ), - ).paddingHorizontal(8), - if (!App.isIOS && !App.isMacOS && type != 2 && type != 3) - CheckboxListTile( - enabled: true, - title: Text("Copy to app local path".tl), - value: copyToLocalFolder, - onChanged: (v) { - setState(() { - copyToLocalFolder = !copyToLocalFolder; - }); - }).paddingHorizontal(8), - const SizedBox(height: 8), - Text(info).paddingHorizontal(24), - ], - ), + : RadioGroup( + groupValue: type, + onChanged: (value) { + setState(() { + type = value ?? type; + }); + }, + child: Column( + key: key, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(width: 600), + ...List.generate(importMethods.length, (index) { + return RadioListTile( + title: Text(importMethods[index]), + value: index, + ); + }), + if (type != 4) + ListTile( + title: Text("Add to favorites".tl), + trailing: Select( + current: selectedFolder, + values: folders, + minWidth: 112, + onTap: (v) { + setState(() { + selectedFolder = folders[v]; + }); + }, + ), + ).paddingHorizontal(8), + if (!App.isIOS && !App.isMacOS && type != 2 && type != 3) + CheckboxListTile( + enabled: true, + title: Text("Copy to app local path".tl), + value: copyToLocalFolder, + onChanged: (v) { + setState(() { + copyToLocalFolder = !copyToLocalFolder; + }); + }).paddingHorizontal(8), + const SizedBox(height: 8), + Text(info).paddingHorizontal(24), + ], + ), + ), actions: [ Button.text( child: Row( diff --git a/lib/pages/image_favorites_page/image_favorites_page.dart b/lib/pages/image_favorites_page/image_favorites_page.dart index ca0c4f1..5b32997 100644 --- a/lib/pages/image_favorites_page/image_favorites_page.dart +++ b/lib/pages/image_favorites_page/image_favorites_page.dart @@ -404,21 +404,23 @@ class _ImageFavoritesDialogState extends State<_ImageFavoritesDialog> { children: [ tabBar, TabViewBody(children: [ - Column( - children: ImageFavoriteSortType.values - .map( - (e) => RadioListTile( - title: Text(e.value.tl), - value: e, - groupValue: sortType, - onChanged: (v) { - setState(() { - sortType = v!; - }); - }, - ), - ) - .toList(), + RadioGroup( + groupValue: sortType, + onChanged: (v) { + setState(() { + sortType = v ?? sortType; + }); + }, + child: Column( + children: ImageFavoriteSortType.values + .map( + (e) => RadioListTile( + title: Text(e.value.tl), + value: e, + ), + ) + .toList(), + ), ), Column( children: [ diff --git a/lib/pages/local_comics_page.dart b/lib/pages/local_comics_page.dart index 8c8ab24..e4af827 100644 --- a/lib/pages/local_comics_page.dart +++ b/lib/pages/local_comics_page.dart @@ -70,39 +70,29 @@ class _LocalComicsPageState extends State { 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!; - }); - }, - ), - ], + content: RadioGroup( + groupValue: sortType, + onChanged: (v) { + setState(() { + sortType = v ?? sortType; + }); + }, + child: Column( + children: [ + RadioListTile( + title: Text("Name".tl), + value: LocalSortType.name, + ), + RadioListTile( + title: Text("Date".tl), + value: LocalSortType.timeAsc, + ), + RadioListTile( + title: Text("Date Desc".tl), + value: LocalSortType.timeDesc, + ), + ], + ), ), actions: [ FilledButton( diff --git a/lib/pages/settings/app.dart b/lib/pages/settings/app.dart index c057eb3..217edc3 100644 --- a/lib/pages/settings/app.dart +++ b/lib/pages/settings/app.dart @@ -428,30 +428,26 @@ class _WebdavSettingState extends State<_WebdavSetting> { ), ), const SizedBox(height: 12), - Row( - children: [ - Text("Operation".tl), - Radio( - groupValue: upload, - value: true, - onChanged: (value) { - setState(() { - upload = value!; - }); - }, - ), - Text("Upload".tl), - Radio( - groupValue: upload, - value: false, - onChanged: (value) { - setState(() { - upload = value!; - }); - }, - ), - Text("Download".tl), - ], + RadioGroup( + groupValue: upload, + onChanged: (value) { + setState(() { + upload = value ?? upload; + }); + }, + child: Row( + children: [ + Text("Operation".tl), + Radio( + value: true, + ), + Text("Upload".tl), + Radio( + value: false, + ), + Text("Download".tl), + ], + ), ), const SizedBox(height: 16), AnimatedSize( diff --git a/lib/pages/settings/network.dart b/lib/pages/settings/network.dart index 9f80afd..a97d02e 100644 --- a/lib/pages/settings/network.dart +++ b/lib/pages/settings/network.dart @@ -111,44 +111,34 @@ class _ProxySettingViewState extends State<_ProxySettingView> { return PopUpWidgetScaffold( title: "Proxy".tl, body: SingleChildScrollView( - child: Column( - children: [ - RadioListTile( - title: Text("Direct".tl), - value: 'direct', - groupValue: type, - onChanged: (v) { - setState(() { - type = v!; - }); - appdata.settings['proxy'] = toProxyStr(); - appdata.saveData(); - }, - ), - RadioListTile( - title: Text("System".tl), - value: 'system', - groupValue: type, - onChanged: (v) { - setState(() { - type = v!; - }); - appdata.settings['proxy'] = toProxyStr(); - appdata.saveData(); - }, - ), - RadioListTile( - title: Text("Manual".tl), - value: 'manual', - groupValue: type, - onChanged: (v) { - setState(() { - type = v!; - }); - }, - ), - if (type == 'manual') buildManualProxy(), - ], + child: RadioGroup( + groupValue: type, + onChanged: (v) { + setState(() { + type = v ?? type; + }); + if (type != 'manual') { + appdata.settings['proxy'] = toProxyStr(); + appdata.saveData(); + } + }, + child: Column( + children: [ + RadioListTile( + title: Text("Direct".tl), + value: 'direct', + ), + RadioListTile( + title: Text("System".tl), + value: 'system', + ), + RadioListTile( + title: Text("Manual".tl), + value: 'manual', + ), + if (type == 'manual') buildManualProxy(), + ], + ), ), ), ); diff --git a/pubspec.lock b/pubspec.lock index 5c713bf..63df0ee 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -416,10 +416,10 @@ packages: dependency: "direct main" description: name: flutter_memory_info - sha256: "1f112f1d7503aa1681fc8e923f6cd0e847bb2fbeec3753ed021cf1e5f7e9cd74" + sha256: eacfd0dd01ff596b4e5bf022442769a1807a73f2af43d62802436f0a5de99137 url: "https://pub.dev" source: hosted - version: "0.0.1" + version: "0.0.3" flutter_plugin_android_lifecycle: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a7cccbf..3f32294 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: venera description: "A comic app." publish_to: 'none' -version: 1.5.2+152 +version: 1.5.3+153 environment: sdk: '>=3.8.0 <4.0.0' @@ -75,7 +75,7 @@ dependencies: ref: fe182cdf40e5fa6230f451bc1d643b860f610d13 dynamic_color: ^1.7.0 shimmer_animation: ^2.1.0 - flutter_memory_info: ^0.0.1 + flutter_memory_info: ^0.0.3 syntax_highlight: ^0.4.0 flutter_7zip: git: