Compare commits

..

1 Commits

Author SHA1 Message Date
8ca0a7785f Fix alt_store workflow 2025-10-12 19:57:28 +08:00
16 changed files with 387 additions and 367 deletions

View File

@@ -31,30 +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.ACTION_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
# Create a new branch for the PR # Create a new branch for the PR
branch_name="update-altstore-$(date +%Y%m%d-%H%M%S)" branch_name="update-altstore-$(date +%Y%m%d-%H%M%S)"
git checkout -b "$branch_name" git checkout -b "$branch_name"
git commit -m "Updated source with latest release" git commit -m "Updated source with latest release"
git push -u origin "$branch_name" git push -u origin "$branch_name"
# Create PR using GitHub CLI # Create PR using GitHub CLI
gh pr create \ gh pr create \
--title "Update AltStore source with latest release" \ --title "Update AltStore source with latest release" \
--body "This PR updates the alt_store.json file with the latest release information." \ --body "This PR updates the alt_store.json file with the latest release information." \
--head "$branch_name" \ --head "$branch_name" \
--base master --base main
echo "changes=true" >> $GITHUB_OUTPUT echo "changes=true" >> $GITHUB_OUTPUT
fi fi
- name: Calculate job duration - name: Calculate job duration
id: duration id: duration

View File

@@ -13,15 +13,15 @@
"bundleIdentifier": "com.github.wgh136.venera", "bundleIdentifier": "com.github.wgh136.venera",
"developerName": "wgh136", "developerName": "wgh136",
"subtitle": "A comic reader that supports reading local and network comics", "subtitle": "A comic reader that supports reading local and network comics",
"version": "1.5.3", "version": "1.4.5",
"versionDate": "2025-10-13", "versionDate": "2025-06-18",
"versionDescription": "1. Fix an issue where the app freezes after swiping back on Android. 544\r\n2. Enable minification when building for Android. 547\r\n3. Prevent the app from creating an archive download task when the archive URL is an empty string.", "versionDescription": "1. Fixed an abnormal single image height issue when \"imagesPerPage > 1\". 379 \r\n2. Fixed an invalid page calculation issue when \"showSingleImageOnFirstPage\" is enabled. \r\n3. Fixed an issue with incorrect reading history when displaying a single image on the first page. \r\n4. Fixed abnormal history recording when pages are not flipped. 392 \r\n5. Fixed an issue where the download task would stop after exiting the reader. 387 \r\n6. Fixed a \"RangeError\" when translating tags. 356 \r\n7. Reset the current folder to null on the favorites page if the folder is invalid. 389 \r\n8. Fixed various issues when using a custom download path on Android. 400 \r\n9. Set the initial chapter to the first downloaded chapter if no history exists when starting to read a local comic. 405 \r\n10. Removed the config file repository URL from the app.",
"downloadURL": "https://github.com/venera-app/venera/releases/download/v1.5.3/venera-ios-1.5.3%2B153.ipa", "downloadURL": "https://github.com/venera-app/venera/releases/download/v1.4.5/venera-ios-1.4.5%2B145.ipa",
"localizedDescription": "A comic reader that supports reading local and network comics", "localizedDescription": "A comic reader that supports reading local and network comics",
"iconURL": "https://raw.githubusercontent.com/venera-app/venera/master/assets/app_icon.png", "iconURL": "https://raw.githubusercontent.com/venera-app/venera/master/assets/app_icon.png",
"tintColor": "#0784FC", "tintColor": "#0784FC",
"category": "utilities", "category": "utilities",
"size": 15047841, "size": 14960268,
"appPermissions": { "appPermissions": {
"entitlements": [ "entitlements": [
"application-identifier", "application-identifier",
@@ -39,13 +39,6 @@
} }
}, },
"versions": [ "versions": [
{
"version": "1.5.3",
"date": "2025-10-13",
"localizedDescription": "1. Fix an issue where the app freezes after swiping back on Android. 544\r\n2. Enable minification when building for Android. 547\r\n3. Prevent the app from creating an archive download task when the archive URL is an empty string.",
"downloadURL": "https://github.com/venera-app/venera/releases/download/v1.5.3/venera-ios-1.5.3%2B153.ipa",
"size": 15047841
},
{ {
"version": "1.4.5", "version": "1.4.5",
"date": "2025-06-18", "date": "2025-06-18",
@@ -66,16 +59,6 @@
"tintColor": "#0784FC", "tintColor": "#0784FC",
"title": "v1.4.5 - Venera 18/06/25", "title": "v1.4.5 - Venera 18/06/25",
"url": "https://github.com/venera-app/venera/releases/tag/v1.4.5" "url": "https://github.com/venera-app/venera/releases/tag/v1.4.5"
},
{
"appID": "com.github.wgh136.venera",
"caption": "Update of Venera just got released!",
"date": "2025-10-13T12:47:27Z",
"identifier": "release-v1.5.3",
"notify": true,
"tintColor": "#0784FC",
"title": "v1.5.3 - Venera 13/10/25",
"url": "https://github.com/venera-app/venera/releases/tag/v1.5.3"
} }
] ]
} }

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 # avoid_print: false # Uncomment to disable the `avoid_print` rule
# 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,8 +84,9 @@ android {
buildTypes { buildTypes {
release { release {
minifyEnabled true // Temporarily solution to fix crash
shrinkResources true minifyEnabled false
shrinkResources false
ndk { ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86_64" abiFilters "armeabi-v7a", "arm64-v8a", "x86_64"
} }

View File

@@ -181,15 +181,7 @@ abstract class ImageDownloader {
} }
if (configs['onResponse'] is JSInvokable) { if (configs['onResponse'] is JSInvokable) {
dynamic result = (configs['onResponse'] as JSInvokable)([Uint8List.fromList(buffer)]); buffer = (configs['onResponse'] as JSInvokable)([Uint8List.fromList(buffer)]);
if (result is Future) {
result = await result;
}
if (result is List<int>) {
buffer = result;
} else {
throw "Error: Invalid onResponse result.";
}
(configs['onResponse'] as JSInvokable).free(); (configs['onResponse'] as JSInvokable).free();
} }

View File

@@ -17,50 +17,39 @@ 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 = List.from( var categories =
appdata.settings["categories"], List.from(appdata.settings["categories"]).whereType<String>().toList();
).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 =
.where((element) => allCategories.contains(element)) categories.where((element) => allCategories.contains(element)).toList();
.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 = List.from( var categories =
appdata.settings["categories"], List.from(appdata.settings["categories"]).whereType<String>().toList();
).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 = categories this.categories =
.where((element) => allCategories.contains(element)) categories.where((element) => allCategories.contains(element)).toList();
.toList();
appdata.settings.addListener(onSettingsChanged); appdata.settings.addListener(onSettingsChanged);
controller = TabController(length: categories.length, vsync: this);
} }
void addPage() { void addPage() {
@@ -70,7 +59,6 @@ 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);
} }
@@ -97,45 +85,46 @@ 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: Column( child: DefaultTabController(
children: [ length: categories.length,
AppTabBar( key: Key(categories.toString()),
controller: controller, child: Column(
key: PageStorageKey(categories.toString()), children: [
tabs: categories.map((e) { AppTabBar(
String title = e; key: PageStorageKey(categories.toString()),
try { tabs: categories.map((e) {
title = getCategoryDataWithKey(e).title; String title = e;
} catch (e) { try {
// title = getCategoryDataWithKey(e).title;
} } catch (e) {
return Tab(text: title, key: Key(e)); //
}).toList(), }
actionButton: TabActionButton( return Tab(
icon: const Icon(Icons.add), text: title,
text: "Add".tl, key: Key(e),
onPressed: addPage, );
), }).toList(),
).paddingTop(context.padding.top), actionButton: TabActionButton(
Expanded( icon: const Icon(Icons.add),
child: TabBarView( text: "Add".tl,
controller: controller, onPressed: addPage,
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?);
@@ -161,42 +150,38 @@ 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( children.add(Padding(
Padding( padding: const EdgeInsets.fromLTRB(10, 0, 10, 16),
padding: const EdgeInsets.fromLTRB(10, 0, 10, 16), child: Wrap(
child: Wrap( children: [
children: [ if (data.enableRankingPage)
if (data.enableRankingPage) buildTag("Ranking".tl, () {
buildTag("Ranking".tl, () { context.to(() => RankingPage(categoryKey: data.key));
context.to(() => RankingPage(categoryKey: data.key)); }),
}), for (var buttonData in data.buttons)
for (var buttonData in data.buttons) buildTag(buttonData.label.tl, buttonData.onTap)
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( children.add(StatefulBuilder(builder: (context, updater) {
StatefulBuilder( return Column(
builder: (context, updater) { mainAxisSize: MainAxisSize.min,
return Column( crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, children: [
crossAxisAlignment: CrossAxisAlignment.start, buildTitleWithRefresh(part.title, () => updater(() {})),
children: [ buildTags(part.categories)
buildTitleWithRefresh(part.title, () => updater(() {})), ],
buildTags(part.categories), );
], }));
);
},
),
);
} else { } else {
children.add(buildTitle(part.title)); children.add(buildTitle(part.title));
children.add(buildTags(part.categories)); children.add(
buildTags(part.categories),
);
} }
} }
return SingleChildScrollView( return SingleChildScrollView(
@@ -210,10 +195,8 @@ 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( child: Text(title.tl,
title.tl, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500)),
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
),
); );
} }
@@ -224,16 +207,21 @@ class _CategoryPage extends StatelessWidget {
children: [ children: [
Text( Text(
title.tl, title.tl,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500), style: const TextStyle(
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(List<CategoryItem> categories) { Widget buildTags(
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,60 +155,64 @@ abstract mixin class _ComicPageActions {
builder: (context, setState) { builder: (context, setState) {
return ContentDialog( return ContentDialog(
title: "Download".tl, title: "Download".tl,
content: RadioGroup<int>( content: Column(
groupValue: selected, mainAxisSize: MainAxisSize.min,
onChanged: (v) { children: [
setState(() { RadioListTile<int>(
selected = v ?? selected; value: -1,
}); groupValue: selected,
}, title: Text("Normal".tl),
child: Column( onChanged: (v) {
mainAxisSize: MainAxisSize.min, setState(() {
children: [ selected = v!;
RadioListTile<int>( });
value: -1, },
title: Text("Normal".tl), ),
ExpansionTile(
title: Text("Archive".tl),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
), ),
ExpansionTile( collapsedShape: const RoundedRectangleBorder(
title: Text("Archive".tl), borderRadius: BorderRadius.zero,
shape: const RoundedRectangleBorder( ),
borderRadius: BorderRadius.zero, onExpansionChanged: (b) {
), if (!isLoading && b && archives == null) {
collapsedShape: const RoundedRectangleBorder( isLoading = true;
borderRadius: BorderRadius.zero, comicSource.archiveDownloader!
), .getArchives(comic.id)
onExpansionChanged: (b) { .then((value) {
if (!isLoading && b && archives == null) { if (value.success) {
isLoading = true; archives = value.data;
comicSource.archiveDownloader! } else {
.getArchives(comic.id) App.rootContext
.then((value) { .showMessage(message: value.errorMessage!);
if (value.success) { }
archives = value.data; setState(() {
} else { isLoading = false;
App.rootContext
.showMessage(message: value.errorMessage!);
}
setState(() {
isLoading = false;
});
}); });
} });
}, }
children: [ },
if (archives == null) children: [
const ListLoadingIndicator().toCenter() if (archives == null)
else const ListLoadingIndicator().toCenter()
for (int i = 0; i < archives!.length; i++) else
RadioListTile<int>( for (int i = 0; i < archives!.length; i++)
value: i, RadioListTile<int>(
title: Text(archives![i].title), value: i,
subtitle: Text(archives![i].description), groupValue: selected,
) onChanged: (v) {
], setState(() {
) selected = v!;
], });
), },
title: Text(archives![i].title),
subtitle: Text(archives![i].description),
)
],
)
],
), ),
actions: [ actions: [
Button.filled( Button.filled(

View File

@@ -197,12 +197,11 @@ class _NetworkSectionState extends State<_NetworkSection> {
if (res.subData is List) { if (res.subData is List) {
final list = List<String>.from(res.subData); final list = List<String>.from(res.subData);
if (list.isNotEmpty) { if (list.isNotEmpty) {
addedFolders = list.toSet(); addedFolders = {list.first};
localIsFavorite = true;
} else { } else {
addedFolders.clear(); addedFolders.clear();
localIsFavorite = false;
} }
localIsFavorite = addedFolders.isNotEmpty;
} else { } else {
addedFolders.clear(); addedFolders.clear();
localIsFavorite = false; localIsFavorite = false;
@@ -353,6 +352,62 @@ class _NetworkSectionState extends State<_NetworkSection> {
} }
Widget _buildMultiFolder() { Widget _buildMultiFolder() {
if (localIsFavorite == true &&
widget.comicSource.favoriteData!.singleFolderForSingleComic) {
return ListTile(
title: Row(
children: [
Text("Network Favorites".tl),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: context.colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(12),
),
child: Text("Added".tl, style: ts.s12),
),
],
),
trailing: isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: _HoverButton(
isFavorite: true,
onTap: () async {
setState(() {
isLoading = true;
});
var res = await widget
.comicSource
.favoriteData!
.addOrDelFavorite!(widget.cid, '', false, null);
if (res.success) {
// Invalidate network cache so subsequent loads see latest
NetworkCacheManager().clear();
setState(() {
localIsFavorite = false;
});
widget.onFavorite(false);
App.rootContext.showMessage(message: "Removed".tl);
if (appdata.settings['autoCloseFavoritePanel'] ?? false) {
context.pop();
}
} else {
context.showMessage(message: res.errorMessage!);
}
setState(() {
isLoading = false;
});
},
),
);
}
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -370,10 +425,8 @@ class _NetworkSectionState extends State<_NetworkSection> {
var name = entry.value; var name = entry.value;
var id = entry.key; var id = entry.key;
var isAdded = addedFolders.contains(id); var isAdded = addedFolders.contains(id);
// When `singleFolderForSingleComic` is `false`, all add and remove buttons are clickable. var hasSelection = addedFolders.isNotEmpty;
// When `singleFolderForSingleComic` is `true`, the remove button is always clickable, var enabled = !hasSelection || isAdded;
// while the add button is only clickable if the comic has not been added to any list.
var enabled = !(widget.comicSource.favoriteData!.singleFolderForSingleComic && addedFolders.isNotEmpty && !isAdded);
return ListTile( return ListTile(
title: Row( title: Row(
@@ -416,9 +469,11 @@ class _NetworkSectionState extends State<_NetworkSection> {
NetworkCacheManager().clear(); NetworkCacheManager().clear();
setState(() { setState(() {
if (isAdded) { if (isAdded) {
addedFolders.remove(id); addedFolders.clear();
} else { } else {
addedFolders.add(id); addedFolders
..clear()
..add(id);
} }
// sync local flag for single-folder-per-comic logic and parent // sync local flag for single-folder-per-comic logic and parent
localIsFavorite = addedFolders.isNotEmpty; localIsFavorite = addedFolders.isNotEmpty;

View File

@@ -1245,15 +1245,6 @@ class _LoginPageState extends State<_LoginPage> {
if (widget.config.checkLoginStatus != null && if (widget.config.checkLoginStatus != null &&
widget.config.checkLoginStatus!(url, title)) { widget.config.checkLoginStatus!(url, title)) {
var cookies = (await c.getCookies(url)) ?? []; var cookies = (await c.getCookies(url)) ?? [];
var localStorageItems = await c.webStorage.localStorage.getItems();
var mappedLocalStorage = <String, dynamic>{};
for (var item in localStorageItems) {
if (item.key != null) {
mappedLocalStorage[item.key!] = item.value;
}
}
widget.source.data['_localStorage'] = mappedLocalStorage;
await widget.source.saveData();
SingleInstanceCookieJar.instance?.saveFromResponse( SingleInstanceCookieJar.instance?.saveFromResponse(
Uri.parse(url), Uri.parse(url),
cookies, cookies,
@@ -1315,20 +1306,6 @@ class _LoginPageState extends State<_LoginPage> {
Uri.parse(url), Uri.parse(url),
cookies, cookies,
); );
var localStorageJson = await webview.evaluateJavascript(
"JSON.stringify(window.localStorage);",
);
var localStorage = <String, dynamic>{};
try {
var decoded = jsonDecode(localStorageJson ?? '');
if (decoded is Map<String, dynamic>) {
localStorage = decoded;
}
} catch (e) {
Log.error("ComicSourcePage", "Failed to parse localStorage JSON\n$e");
}
widget.source.data['_localStorage'] = localStorage;
await widget.source.saveData();
success = true; success = true;
widget.config.onLoginWithWebviewSuccess?.call(); widget.config.onLoginWithWebviewSuccess?.call();
webview.close(); webview.close();

View File

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

View File

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

View File

@@ -70,29 +70,39 @@ 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: RadioGroup<LocalSortType>( content: Column(
groupValue: sortType, children: [
onChanged: (v) { RadioListTile<LocalSortType>(
setState(() { title: Text("Name".tl),
sortType = v ?? sortType; value: LocalSortType.name,
}); groupValue: sortType,
}, onChanged: (v) {
child: Column( setState(() {
children: [ sortType = v!;
RadioListTile<LocalSortType>( });
title: Text("Name".tl), },
value: LocalSortType.name, ),
), RadioListTile<LocalSortType>(
RadioListTile<LocalSortType>( title: Text("Date".tl),
title: Text("Date".tl), value: LocalSortType.timeAsc,
value: LocalSortType.timeAsc, groupValue: sortType,
), onChanged: (v) {
RadioListTile<LocalSortType>( setState(() {
title: Text("Date Desc".tl), sortType = v!;
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,26 +428,30 @@ class _WebdavSettingState extends State<_WebdavSetting> {
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
RadioGroup<bool>( Row(
groupValue: upload, children: [
onChanged: (value) { Text("Operation".tl),
setState(() { Radio<bool>(
upload = value ?? upload; groupValue: upload,
}); value: true,
}, onChanged: (value) {
child: Row( setState(() {
children: [ upload = value!;
Text("Operation".tl), });
Radio<bool>( },
value: true, ),
), Text("Upload".tl),
Text("Upload".tl), Radio<bool>(
Radio<bool>( groupValue: upload,
value: false, value: false,
), onChanged: (value) {
Text("Download".tl), setState(() {
], upload = value!;
), });
},
),
Text("Download".tl),
],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
AnimatedSize( AnimatedSize(

View File

@@ -111,34 +111,44 @@ class _ProxySettingViewState extends State<_ProxySettingView> {
return PopUpWidgetScaffold( return PopUpWidgetScaffold(
title: "Proxy".tl, title: "Proxy".tl,
body: SingleChildScrollView( body: SingleChildScrollView(
child: RadioGroup<String>( child: Column(
groupValue: type, children: [
onChanged: (v) { RadioListTile<String>(
setState(() { title: Text("Direct".tl),
type = v ?? type; value: 'direct',
}); groupValue: type,
if (type != 'manual') { onChanged: (v) {
appdata.settings['proxy'] = toProxyStr(); setState(() {
appdata.saveData(); type = v!;
} });
}, appdata.settings['proxy'] = toProxyStr();
child: Column( appdata.saveData();
children: [ },
RadioListTile<String>( ),
title: Text("Direct".tl), RadioListTile<String>(
value: 'direct', title: Text("System".tl),
), value: 'system',
RadioListTile<String>( groupValue: type,
title: Text("System".tl), onChanged: (v) {
value: 'system', setState(() {
), type = v!;
RadioListTile( });
title: Text("Manual".tl), appdata.settings['proxy'] = toProxyStr();
value: 'manual', appdata.saveData();
), },
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: eacfd0dd01ff596b4e5bf022442769a1807a73f2af43d62802436f0a5de99137 sha256: "1f112f1d7503aa1681fc8e923f6cd0e847bb2fbeec3753ed021cf1e5f7e9cd74"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.0.3" version: "0.0.1"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:

View File

@@ -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.3 flutter_memory_info: ^0.0.1
syntax_highlight: ^0.4.0 syntax_highlight: ^0.4.0
flutter_7zip: flutter_7zip:
git: git: