Compare commits

..

10 Commits

Author SHA1 Message Date
nyne
c234a53518 Merge pull request #557 from venera-app/v1.5.3-dev
V1.5.3
2025-10-13 20:42:31 +08:00
49fd64358c Improve categories page. 2025-10-13 20:28:03 +08:00
3426d707fe Refactor radio button implementations to use RadioGroup. 2025-10-13 20:12:47 +08:00
ebc106d45b enable minify. Close #547 2025-10-13 19:51:54 +08:00
0cda9a2921 Fix alt_store workflow 2025-10-13 18:39:39 +08:00
0eb5d76687 fix android back gesture. Close #544 2025-10-12 19:49:33 +08:00
29d25f7fcd Update version code 2025-10-12 16:47:08 +08:00
7d60e78f27 ignore empty archive link 2025-10-12 16:44:13 +08:00
nyne
e93b56a008 Add Inno Setup installation to workflow 2025-10-09 22:06:21 +08:00
nyne
d10873a903 Update update_alt_store.yml 2025-10-09 21:39:42 +08:00
15 changed files with 314 additions and 312 deletions

View File

@@ -116,6 +116,8 @@ jobs:
run: | run: |
choco install yq -y choco install yq -y
pip install httpx pip install httpx
- name: Install Inno Setup
run: choco install innosetup --no-progress
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
with: with:
channel: "stable" channel: "stable"

View File

@@ -31,19 +31,30 @@ jobs:
- name: Update AltStore source - name: Update AltStore source
id: update_source id: update_source
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
python update_alt_store.py python update_alt_store.py
git config --global user.name 'GitHub Action' git config --global user.name 'GitHub Action'
git config --global user.email 'action@github.com' git config --global user.email 'action@github.com'
git add alt_store.json git add alt_store.json
if git diff --staged --quiet; then if git diff --staged --quiet; then
echo "changes=false" >> $GITHUB_OUTPUT echo "changes=false" >> $GITHUB_OUTPUT
else else
git commit -m "Updated source with latest release" # Create a new branch for the PR
git push branch_name="update-altstore-$(date +%Y%m%d-%H%M%S)"
echo "changes=true" >> $GITHUB_OUTPUT git checkout -b "$branch_name"
fi 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 - name: Calculate job duration
id: duration id: duration

View File

@@ -23,7 +23,7 @@ linter:
rules: rules:
collection_methods_unrelated_type: false collection_methods_unrelated_type: false
use_build_context_synchronously: 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 # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at # Additional information about this file can be found at

View File

@@ -84,9 +84,8 @@ android {
buildTypes { buildTypes {
release { release {
// Temporarily solution to fix crash minifyEnabled true
minifyEnabled false shrinkResources true
shrinkResources false
ndk { ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86_64" abiFilters "armeabi-v7a", "arm64-v8a", "x86_64"
} }

View File

@@ -13,7 +13,7 @@ export "widget_utils.dart";
export "context.dart"; export "context.dart";
class _App { class _App {
final version = "1.5.2"; final version = "1.5.3";
bool get isAndroid => Platform.isAndroid; bool get isAndroid => Platform.isAndroid;

View File

@@ -128,7 +128,7 @@ mixin _AppRouteTransitionMixin<T> on PageRoute<T> {
context, context,
animation, animation,
secondaryAnimation, secondaryAnimation,
enableIOSGesture enableIOSGesture && App.isIOS
? IOSBackGestureDetector( ? IOSBackGestureDetector(
gestureWidth: _kBackGestureWidth, gestureWidth: _kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(this), enabledCallback: () => _isPopGestureEnabled<T>(this),
@@ -302,7 +302,7 @@ class _IOSBackGestureDetectorState extends State<IOSBackGestureDetector> {
assert(mounted); assert(mounted);
assert(_backGestureController != null); assert(_backGestureController != null);
_backGestureController!.dragUpdate( _backGestureController!.dragUpdate(
_convertToLogical(details.primaryDelta! / context.size!.width)); _convertToLogical(details.primaryDelta! / context.size!.width));
} }
} }

View File

@@ -17,39 +17,50 @@ class CategoriesPage extends StatefulWidget {
State<CategoriesPage> createState() => _CategoriesPageState(); State<CategoriesPage> createState() => _CategoriesPageState();
} }
class _CategoriesPageState extends State<CategoriesPage> { class _CategoriesPageState extends State<CategoriesPage>
with
TickerProviderStateMixin,
AutomaticKeepAliveClientMixin<CategoriesPage> {
var categories = <String>[]; var categories = <String>[];
late TabController controller;
void onSettingsChanged() { void onSettingsChanged() {
var categories = var categories = List.from(
List.from(appdata.settings["categories"]).whereType<String>().toList(); appdata.settings["categories"],
).whereType<String>().toList();
var allCategories = ComicSource.all() var allCategories = ComicSource.all()
.map((e) => e.categoryData?.key) .map((e) => e.categoryData?.key)
.where((element) => element != null) .where((element) => element != null)
.map((e) => e!) .map((e) => e!)
.toList(); .toList();
categories = categories = categories
categories.where((element) => allCategories.contains(element)).toList(); .where((element) => allCategories.contains(element))
.toList();
if (!categories.isEqualTo(this.categories)) { if (!categories.isEqualTo(this.categories)) {
setState(() { setState(() {
this.categories = categories; this.categories = categories;
}); });
controller = TabController(length: categories.length, vsync: this);
} }
} }
@override @override
void initState() { void initState() {
super.initState(); super.initState();
var categories = var categories = List.from(
List.from(appdata.settings["categories"]).whereType<String>().toList(); appdata.settings["categories"],
).whereType<String>().toList();
var allCategories = ComicSource.all() var allCategories = ComicSource.all()
.map((e) => e.categoryData?.key) .map((e) => e.categoryData?.key)
.where((element) => element != null) .where((element) => element != null)
.map((e) => e!) .map((e) => e!)
.toList(); .toList();
this.categories = this.categories = categories
categories.where((element) => allCategories.contains(element)).toList(); .where((element) => allCategories.contains(element))
.toList();
appdata.settings.addListener(onSettingsChanged); appdata.settings.addListener(onSettingsChanged);
controller = TabController(length: categories.length, vsync: this);
} }
void addPage() { void addPage() {
@@ -59,6 +70,7 @@ class _CategoriesPageState extends State<CategoriesPage> {
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
controller.dispose();
appdata.settings.removeListener(onSettingsChanged); appdata.settings.removeListener(onSettingsChanged);
} }
@@ -85,46 +97,45 @@ class _CategoriesPageState extends State<CategoriesPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context);
if (categories.isEmpty) { if (categories.isEmpty) {
return buildEmpty(); return buildEmpty();
} }
return Material( return Material(
child: DefaultTabController( child: Column(
length: categories.length, children: [
key: Key(categories.toString()), AppTabBar(
child: Column( controller: controller,
children: [ key: PageStorageKey(categories.toString()),
AppTabBar( tabs: categories.map((e) {
key: PageStorageKey(categories.toString()), String title = e;
tabs: categories.map((e) { try {
String title = e; title = getCategoryDataWithKey(e).title;
try { } catch (e) {
title = getCategoryDataWithKey(e).title; //
} catch (e) { }
// return Tab(text: title, key: Key(e));
} }).toList(),
return Tab( actionButton: TabActionButton(
text: title, icon: const Icon(Icons.add),
key: Key(e), text: "Add".tl,
); onPressed: addPage,
}).toList(), ),
actionButton: TabActionButton( ).paddingTop(context.padding.top),
icon: const Icon(Icons.add), Expanded(
text: "Add".tl, child: TabBarView(
onPressed: addPage, controller: controller,
), children: categories.map((e) => _CategoryPage(e)).toList(),
).paddingTop(context.padding.top), ),
Expanded( ),
child: TabBarView( ],
children: categories.map((e) => _CategoryPage(e)).toList(),
),
)
],
),
), ),
); );
} }
@override
bool get wantKeepAlive => true;
} }
typedef ClickTagCallback = void Function(String, String?); typedef ClickTagCallback = void Function(String, String?);
@@ -150,38 +161,42 @@ class _CategoryPage extends StatelessWidget {
var children = <Widget>[]; var children = <Widget>[];
if (data.enableRankingPage || data.buttons.isNotEmpty) { if (data.enableRankingPage || data.buttons.isNotEmpty) {
children.add(buildTitle(data.title)); children.add(buildTitle(data.title));
children.add(Padding( children.add(
padding: const EdgeInsets.fromLTRB(10, 0, 10, 16), Padding(
child: Wrap( padding: const EdgeInsets.fromLTRB(10, 0, 10, 16),
children: [ child: Wrap(
if (data.enableRankingPage) children: [
buildTag("Ranking".tl, () { if (data.enableRankingPage)
context.to(() => RankingPage(categoryKey: data.key)); buildTag("Ranking".tl, () {
}), context.to(() => RankingPage(categoryKey: data.key));
for (var buttonData in data.buttons) }),
buildTag(buttonData.label.tl, buttonData.onTap) for (var buttonData in data.buttons)
], buildTag(buttonData.label.tl, buttonData.onTap),
],
),
), ),
)); );
} }
for (var part in data.categories) { for (var part in data.categories) {
if (part.enableRandom) { if (part.enableRandom) {
children.add(StatefulBuilder(builder: (context, updater) { children.add(
return Column( StatefulBuilder(
mainAxisSize: MainAxisSize.min, builder: (context, updater) {
crossAxisAlignment: CrossAxisAlignment.start, return Column(
children: [ mainAxisSize: MainAxisSize.min,
buildTitleWithRefresh(part.title, () => updater(() {})), crossAxisAlignment: CrossAxisAlignment.start,
buildTags(part.categories) children: [
], buildTitleWithRefresh(part.title, () => updater(() {})),
); buildTags(part.categories),
})); ],
);
},
),
);
} else { } else {
children.add(buildTitle(part.title)); children.add(buildTitle(part.title));
children.add( children.add(buildTags(part.categories));
buildTags(part.categories),
);
} }
} }
return SingleChildScrollView( return SingleChildScrollView(
@@ -195,8 +210,10 @@ class _CategoryPage extends StatelessWidget {
Widget buildTitle(String title) { Widget buildTitle(String title) {
return Padding( return Padding(
padding: const EdgeInsets.fromLTRB(16, 10, 5, 10), padding: const EdgeInsets.fromLTRB(16, 10, 5, 10),
child: Text(title.tl, child: Text(
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500)), title.tl,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
),
); );
} }
@@ -207,21 +224,16 @@ class _CategoryPage extends StatelessWidget {
children: [ children: [
Text( Text(
title.tl, title.tl,
style: const TextStyle( style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
fontSize: 20,
fontWeight: FontWeight.w500,
),
), ),
const Spacer(), const Spacer(),
IconButton(onPressed: onRefresh, icon: const Icon(Icons.refresh)) IconButton(onPressed: onRefresh, icon: const Icon(Icons.refresh)),
], ],
), ),
); );
} }
Widget buildTags( Widget buildTags(List<CategoryItem> categories) {
List<CategoryItem> categories,
) {
return Padding( return Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 10, 16), padding: const EdgeInsets.fromLTRB(10, 0, 10, 16),
child: Wrap( child: Wrap(

View File

@@ -155,64 +155,60 @@ abstract mixin class _ComicPageActions {
builder: (context, setState) { builder: (context, setState) {
return ContentDialog( return ContentDialog(
title: "Download".tl, title: "Download".tl,
content: Column( content: RadioGroup<int>(
mainAxisSize: MainAxisSize.min, groupValue: selected,
children: [ onChanged: (v) {
RadioListTile<int>( setState(() {
value: -1, selected = v ?? selected;
groupValue: selected, });
title: Text("Normal".tl), },
onChanged: (v) { child: Column(
setState(() { mainAxisSize: MainAxisSize.min,
selected = v!; children: [
}); RadioListTile<int>(
}, value: -1,
), title: Text("Normal".tl),
ExpansionTile(
title: Text("Archive".tl),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
), ),
collapsedShape: const RoundedRectangleBorder( ExpansionTile(
borderRadius: BorderRadius.zero, title: Text("Archive".tl),
), shape: const RoundedRectangleBorder(
onExpansionChanged: (b) { borderRadius: BorderRadius.zero,
if (!isLoading && b && archives == null) { ),
isLoading = true; collapsedShape: const RoundedRectangleBorder(
comicSource.archiveDownloader! borderRadius: BorderRadius.zero,
.getArchives(comic.id) ),
.then((value) { onExpansionChanged: (b) {
if (value.success) { if (!isLoading && b && archives == null) {
archives = value.data; isLoading = true;
} else { comicSource.archiveDownloader!
App.rootContext .getArchives(comic.id)
.showMessage(message: value.errorMessage!); .then((value) {
} if (value.success) {
setState(() { archives = value.data;
isLoading = false; } else {
App.rootContext
.showMessage(message: value.errorMessage!);
}
setState(() {
isLoading = false;
});
}); });
}); }
} },
}, children: [
children: [ if (archives == null)
if (archives == null) const ListLoadingIndicator().toCenter()
const ListLoadingIndicator().toCenter() else
else for (int i = 0; i < archives!.length; i++)
for (int i = 0; i < archives!.length; i++) RadioListTile<int>(
RadioListTile<int>( value: i,
value: i, title: Text(archives![i].title),
groupValue: selected, subtitle: Text(archives![i].description),
onChanged: (v) { )
setState(() { ],
selected = v!; )
}); ],
}, ),
title: Text(archives![i].title),
subtitle: Text(archives![i].description),
)
],
)
],
), ),
actions: [ actions: [
Button.filled( Button.filled(
@@ -237,10 +233,12 @@ abstract mixin class _ComicPageActions {
isGettingLink = false; isGettingLink = false;
}); });
} else if (context.mounted) { } else if (context.mounted) {
LocalManager() if (res.data.isNotEmpty) {
LocalManager()
.addTask(ArchiveDownloadTask(res.data, comic)); .addTask(ArchiveDownloadTask(res.data, comic));
App.rootContext App.rootContext
.showMessage(message: "Download started".tl); .showMessage(message: "Download started".tl);
}
context.pop(); context.pop();
} }
}, },

View File

@@ -514,51 +514,53 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> {
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
) )
: Column( : RadioGroup<int>(
key: key, groupValue: type,
crossAxisAlignment: CrossAxisAlignment.start, onChanged: (value) {
children: [ setState(() {
const SizedBox(width: 600), type = value ?? type;
...List.generate(importMethods.length, (index) { });
return RadioListTile( },
title: Text(importMethods[index]), child: Column(
value: index, key: key,
groupValue: type, crossAxisAlignment: CrossAxisAlignment.start,
onChanged: (value) { children: [
setState(() { const SizedBox(width: 600),
type = value as int; ...List.generate(importMethods.length, (index) {
}); return RadioListTile<int>(
}, title: Text(importMethods[index]),
); value: index,
}), );
if (type != 4) }),
ListTile( if (type != 4)
title: Text("Add to favorites".tl), ListTile(
trailing: Select( title: Text("Add to favorites".tl),
current: selectedFolder, trailing: Select(
values: folders, current: selectedFolder,
minWidth: 112, values: folders,
onTap: (v) { minWidth: 112,
setState(() { onTap: (v) {
selectedFolder = folders[v]; setState(() {
}); selectedFolder = folders[v];
}, });
), },
).paddingHorizontal(8), ),
if (!App.isIOS && !App.isMacOS && type != 2 && type != 3) ).paddingHorizontal(8),
CheckboxListTile( if (!App.isIOS && !App.isMacOS && type != 2 && type != 3)
enabled: true, CheckboxListTile(
title: Text("Copy to app local path".tl), enabled: true,
value: copyToLocalFolder, title: Text("Copy to app local path".tl),
onChanged: (v) { value: copyToLocalFolder,
setState(() { onChanged: (v) {
copyToLocalFolder = !copyToLocalFolder; setState(() {
}); copyToLocalFolder = !copyToLocalFolder;
}).paddingHorizontal(8), });
const SizedBox(height: 8), }).paddingHorizontal(8),
Text(info).paddingHorizontal(24), const SizedBox(height: 8),
], Text(info).paddingHorizontal(24),
), ],
),
),
actions: [ actions: [
Button.text( Button.text(
child: Row( child: Row(

View File

@@ -404,21 +404,23 @@ class _ImageFavoritesDialogState extends State<_ImageFavoritesDialog> {
children: [ children: [
tabBar, tabBar,
TabViewBody(children: [ TabViewBody(children: [
Column( RadioGroup<ImageFavoriteSortType>(
children: ImageFavoriteSortType.values groupValue: sortType,
.map( onChanged: (v) {
(e) => RadioListTile<ImageFavoriteSortType>( setState(() {
title: Text(e.value.tl), sortType = v ?? sortType;
value: e, });
groupValue: sortType, },
onChanged: (v) { child: Column(
setState(() { children: ImageFavoriteSortType.values
sortType = v!; .map(
}); (e) => RadioListTile<ImageFavoriteSortType>(
}, title: Text(e.value.tl),
), value: e,
) ),
.toList(), )
.toList(),
),
), ),
Column( Column(
children: [ children: [

View File

@@ -70,39 +70,29 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
return StatefulBuilder(builder: (context, setState) { return StatefulBuilder(builder: (context, setState) {
return ContentDialog( return ContentDialog(
title: "Sort".tl, title: "Sort".tl,
content: Column( content: RadioGroup<LocalSortType>(
children: [ groupValue: sortType,
RadioListTile<LocalSortType>( onChanged: (v) {
title: Text("Name".tl), setState(() {
value: LocalSortType.name, sortType = v ?? sortType;
groupValue: sortType, });
onChanged: (v) { },
setState(() { child: Column(
sortType = v!; children: [
}); RadioListTile<LocalSortType>(
}, title: Text("Name".tl),
), value: LocalSortType.name,
RadioListTile<LocalSortType>( ),
title: Text("Date".tl), RadioListTile<LocalSortType>(
value: LocalSortType.timeAsc, title: Text("Date".tl),
groupValue: sortType, value: LocalSortType.timeAsc,
onChanged: (v) { ),
setState(() { RadioListTile<LocalSortType>(
sortType = v!; title: Text("Date Desc".tl),
}); value: LocalSortType.timeDesc,
}, ),
), ],
RadioListTile<LocalSortType>( ),
title: Text("Date Desc".tl),
value: LocalSortType.timeDesc,
groupValue: sortType,
onChanged: (v) {
setState(() {
sortType = v!;
});
},
),
],
), ),
actions: [ actions: [
FilledButton( FilledButton(

View File

@@ -428,30 +428,26 @@ class _WebdavSettingState extends State<_WebdavSetting> {
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Row( RadioGroup<bool>(
children: [ groupValue: upload,
Text("Operation".tl), onChanged: (value) {
Radio<bool>( setState(() {
groupValue: upload, upload = value ?? upload;
value: true, });
onChanged: (value) { },
setState(() { child: Row(
upload = value!; children: [
}); Text("Operation".tl),
}, Radio<bool>(
), value: true,
Text("Upload".tl), ),
Radio<bool>( Text("Upload".tl),
groupValue: upload, Radio<bool>(
value: false, value: false,
onChanged: (value) { ),
setState(() { Text("Download".tl),
upload = value!; ],
}); ),
},
),
Text("Download".tl),
],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
AnimatedSize( AnimatedSize(

View File

@@ -111,44 +111,34 @@ class _ProxySettingViewState extends State<_ProxySettingView> {
return PopUpWidgetScaffold( return PopUpWidgetScaffold(
title: "Proxy".tl, title: "Proxy".tl,
body: SingleChildScrollView( body: SingleChildScrollView(
child: Column( child: RadioGroup<String>(
children: [ groupValue: type,
RadioListTile<String>( onChanged: (v) {
title: Text("Direct".tl), setState(() {
value: 'direct', type = v ?? type;
groupValue: type, });
onChanged: (v) { if (type != 'manual') {
setState(() { appdata.settings['proxy'] = toProxyStr();
type = v!; appdata.saveData();
}); }
appdata.settings['proxy'] = toProxyStr(); },
appdata.saveData(); child: Column(
}, children: [
), RadioListTile<String>(
RadioListTile<String>( title: Text("Direct".tl),
title: Text("System".tl), value: 'direct',
value: 'system', ),
groupValue: type, RadioListTile<String>(
onChanged: (v) { title: Text("System".tl),
setState(() { value: 'system',
type = v!; ),
}); RadioListTile(
appdata.settings['proxy'] = toProxyStr(); title: Text("Manual".tl),
appdata.saveData(); value: 'manual',
}, ),
), if (type == 'manual') buildManualProxy(),
RadioListTile( ],
title: Text("Manual".tl), ),
value: 'manual',
groupValue: type,
onChanged: (v) {
setState(() {
type = v!;
});
},
),
if (type == 'manual') buildManualProxy(),
],
), ),
), ),
); );

View File

@@ -416,10 +416,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_memory_info name: flutter_memory_info
sha256: "1f112f1d7503aa1681fc8e923f6cd0e847bb2fbeec3753ed021cf1e5f7e9cd74" sha256: eacfd0dd01ff596b4e5bf022442769a1807a73f2af43d62802436f0a5de99137
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.0.1" version: "0.0.3"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:

View File

@@ -2,7 +2,7 @@ name: venera
description: "A comic app." description: "A comic app."
publish_to: 'none' publish_to: 'none'
version: 1.5.2+152 version: 1.5.3+153
environment: environment:
sdk: '>=3.8.0 <4.0.0' sdk: '>=3.8.0 <4.0.0'
@@ -75,7 +75,7 @@ dependencies:
ref: fe182cdf40e5fa6230f451bc1d643b860f610d13 ref: fe182cdf40e5fa6230f451bc1d643b860f610d13
dynamic_color: ^1.7.0 dynamic_color: ^1.7.0
shimmer_animation: ^2.1.0 shimmer_animation: ^2.1.0
flutter_memory_info: ^0.0.1 flutter_memory_info: ^0.0.3
syntax_highlight: ^0.4.0 syntax_highlight: ^0.4.0
flutter_7zip: flutter_7zip:
git: git: