mirror of
https://github.com/venera-app/venera.git
synced 2025-12-15 14:41:15 +00:00
Compare commits
13 Commits
v1.5.2-dev
...
v1.5.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c234a53518 | ||
| 49fd64358c | |||
| 3426d707fe | |||
| ebc106d45b | |||
| 0cda9a2921 | |||
| 0eb5d76687 | |||
| 29d25f7fcd | |||
| 7d60e78f27 | |||
|
|
e93b56a008 | ||
|
|
d10873a903 | ||
|
|
2d27f7d650 | ||
|
|
5a76a10fb2 | ||
|
|
f09e766a8a |
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -116,6 +116,8 @@ jobs:
|
||||
run: |
|
||||
choco install yq -y
|
||||
pip install httpx
|
||||
- name: Install Inno Setup
|
||||
run: choco install innosetup --no-progress
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
|
||||
35
.github/workflows/update_alt_store.yml
vendored
35
.github/workflows/update_alt_store.yml
vendored
@@ -31,19 +31,30 @@ jobs:
|
||||
- name: Update AltStore source
|
||||
id: update_source
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ mixin _AppRouteTransitionMixin<T> on PageRoute<T> {
|
||||
context,
|
||||
animation,
|
||||
secondaryAnimation,
|
||||
enableIOSGesture
|
||||
enableIOSGesture && App.isIOS
|
||||
? IOSBackGestureDetector(
|
||||
gestureWidth: _kBackGestureWidth,
|
||||
enabledCallback: () => _isPopGestureEnabled<T>(this),
|
||||
@@ -302,7 +302,7 @@ class _IOSBackGestureDetectorState extends State<IOSBackGestureDetector> {
|
||||
assert(mounted);
|
||||
assert(_backGestureController != null);
|
||||
_backGestureController!.dragUpdate(
|
||||
_convertToLogical(details.primaryDelta! / context.size!.width));
|
||||
_convertToLogical(details.primaryDelta! / context.size!.width));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,39 +17,50 @@ class CategoriesPage extends StatefulWidget {
|
||||
State<CategoriesPage> createState() => _CategoriesPageState();
|
||||
}
|
||||
|
||||
class _CategoriesPageState extends State<CategoriesPage> {
|
||||
class _CategoriesPageState extends State<CategoriesPage>
|
||||
with
|
||||
TickerProviderStateMixin,
|
||||
AutomaticKeepAliveClientMixin<CategoriesPage> {
|
||||
var categories = <String>[];
|
||||
|
||||
late TabController controller;
|
||||
|
||||
void onSettingsChanged() {
|
||||
var categories =
|
||||
List.from(appdata.settings["categories"]).whereType<String>().toList();
|
||||
var categories = List.from(
|
||||
appdata.settings["categories"],
|
||||
).whereType<String>().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<String>().toList();
|
||||
var categories = List.from(
|
||||
appdata.settings["categories"],
|
||||
).whereType<String>().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<CategoriesPage> {
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
controller.dispose();
|
||||
appdata.settings.removeListener(onSettingsChanged);
|
||||
}
|
||||
|
||||
@@ -85,46 +97,45 @@ class _CategoriesPageState extends State<CategoriesPage> {
|
||||
|
||||
@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 = <Widget>[];
|
||||
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<CategoryItem> categories,
|
||||
) {
|
||||
Widget buildTags(List<CategoryItem> categories) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 0, 10, 16),
|
||||
child: Wrap(
|
||||
|
||||
@@ -155,64 +155,60 @@ abstract mixin class _ComicPageActions {
|
||||
builder: (context, setState) {
|
||||
return ContentDialog(
|
||||
title: "Download".tl,
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
RadioListTile<int>(
|
||||
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<int>(
|
||||
groupValue: selected,
|
||||
onChanged: (v) {
|
||||
setState(() {
|
||||
selected = v ?? selected;
|
||||
});
|
||||
},
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
RadioListTile<int>(
|
||||
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<int>(
|
||||
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<int>(
|
||||
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();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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<int>(
|
||||
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<int>(
|
||||
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(
|
||||
|
||||
@@ -404,21 +404,23 @@ class _ImageFavoritesDialogState extends State<_ImageFavoritesDialog> {
|
||||
children: [
|
||||
tabBar,
|
||||
TabViewBody(children: [
|
||||
Column(
|
||||
children: ImageFavoriteSortType.values
|
||||
.map(
|
||||
(e) => RadioListTile<ImageFavoriteSortType>(
|
||||
title: Text(e.value.tl),
|
||||
value: e,
|
||||
groupValue: sortType,
|
||||
onChanged: (v) {
|
||||
setState(() {
|
||||
sortType = v!;
|
||||
});
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
RadioGroup<ImageFavoriteSortType>(
|
||||
groupValue: sortType,
|
||||
onChanged: (v) {
|
||||
setState(() {
|
||||
sortType = v ?? sortType;
|
||||
});
|
||||
},
|
||||
child: Column(
|
||||
children: ImageFavoriteSortType.values
|
||||
.map(
|
||||
(e) => RadioListTile<ImageFavoriteSortType>(
|
||||
title: Text(e.value.tl),
|
||||
value: e,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
|
||||
@@ -70,39 +70,29 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
|
||||
return StatefulBuilder(builder: (context, setState) {
|
||||
return ContentDialog(
|
||||
title: "Sort".tl,
|
||||
content: Column(
|
||||
children: [
|
||||
RadioListTile<LocalSortType>(
|
||||
title: Text("Name".tl),
|
||||
value: LocalSortType.name,
|
||||
groupValue: sortType,
|
||||
onChanged: (v) {
|
||||
setState(() {
|
||||
sortType = v!;
|
||||
});
|
||||
},
|
||||
),
|
||||
RadioListTile<LocalSortType>(
|
||||
title: Text("Date".tl),
|
||||
value: LocalSortType.timeAsc,
|
||||
groupValue: sortType,
|
||||
onChanged: (v) {
|
||||
setState(() {
|
||||
sortType = v!;
|
||||
});
|
||||
},
|
||||
),
|
||||
RadioListTile<LocalSortType>(
|
||||
title: Text("Date Desc".tl),
|
||||
value: LocalSortType.timeDesc,
|
||||
groupValue: sortType,
|
||||
onChanged: (v) {
|
||||
setState(() {
|
||||
sortType = v!;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
content: RadioGroup<LocalSortType>(
|
||||
groupValue: sortType,
|
||||
onChanged: (v) {
|
||||
setState(() {
|
||||
sortType = v ?? sortType;
|
||||
});
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
RadioListTile<LocalSortType>(
|
||||
title: Text("Name".tl),
|
||||
value: LocalSortType.name,
|
||||
),
|
||||
RadioListTile<LocalSortType>(
|
||||
title: Text("Date".tl),
|
||||
value: LocalSortType.timeAsc,
|
||||
),
|
||||
RadioListTile<LocalSortType>(
|
||||
title: Text("Date Desc".tl),
|
||||
value: LocalSortType.timeDesc,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
FilledButton(
|
||||
|
||||
@@ -140,12 +140,12 @@ class _GalleryModeState extends State<_GalleryMode>
|
||||
int get totalPages {
|
||||
if (!reader.showSingleImageOnFirstPage()) {
|
||||
return (reader.images!.length /
|
||||
reader.imagesPerPage())
|
||||
reader.imagesPerPage)
|
||||
.ceil();
|
||||
} else {
|
||||
return 1 +
|
||||
((reader.images!.length - 1) /
|
||||
reader.imagesPerPage())
|
||||
reader.imagesPerPage)
|
||||
.ceil();
|
||||
}
|
||||
}
|
||||
@@ -169,7 +169,7 @@ class _GalleryModeState extends State<_GalleryMode>
|
||||
|
||||
/// Get the range of images for the given page. [page] is 1-based.
|
||||
(int start, int end) getPageImagesRange(int page) {
|
||||
var imagesPerPage = reader.imagesPerPage();
|
||||
var imagesPerPage = reader.imagesPerPage;
|
||||
if (reader.showSingleImageOnFirstPage()) {
|
||||
if (page == 1) {
|
||||
return (0, 1);
|
||||
@@ -191,6 +191,16 @@ class _GalleryModeState extends State<_GalleryMode>
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the image indices for current page. Returns null if no images.
|
||||
/// Returns a single index if only one image, or a range if multiple images.
|
||||
(int, int)? getCurrentPageImageRange() {
|
||||
if (reader.images == null || reader.images!.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
var (startIndex, endIndex) = getPageImagesRange(reader.page);
|
||||
return (startIndex, endIndex);
|
||||
}
|
||||
|
||||
/// [cache] is used to cache the images.
|
||||
/// The count of images to cache is determined by the [preCacheCount] setting.
|
||||
/// For previous page and next page, it will do a memory cache.
|
||||
@@ -259,7 +269,7 @@ class _GalleryModeState extends State<_GalleryMode>
|
||||
|
||||
photoViewControllers[index] ??= PhotoViewController();
|
||||
|
||||
if (reader.imagesPerPage() == 1 ||
|
||||
if (reader.imagesPerPage == 1 ||
|
||||
pageImages.length == 1) {
|
||||
return PhotoViewGalleryPageOptions(
|
||||
filterQuality: FilterQuality.medium,
|
||||
@@ -533,17 +543,27 @@ class _GalleryModeState extends State<_GalleryMode>
|
||||
|
||||
@override
|
||||
String? getImageKeyByOffset(Offset offset) {
|
||||
String? imageKey;
|
||||
if (reader.imagesPerPage() == 1) {
|
||||
imageKey = reader.images![reader.page - 1];
|
||||
} else {
|
||||
for (var imageState in imageStates) {
|
||||
if ((imageState as _ComicImageState).containsPoint(offset)) {
|
||||
imageKey = (imageState.widget.image as ReaderImageProvider).imageKey;
|
||||
var range = getCurrentPageImageRange();
|
||||
if (range == null) return null;
|
||||
|
||||
var (startIndex, endIndex) = range;
|
||||
int actualImageCount = endIndex - startIndex;
|
||||
|
||||
if (actualImageCount == 1) {
|
||||
return reader.images![startIndex];
|
||||
}
|
||||
|
||||
for (var imageState in imageStates) {
|
||||
if ((imageState as _ComicImageState).containsPoint(offset)) {
|
||||
var imageKey = (imageState.widget.image as ReaderImageProvider).imageKey;
|
||||
int index = reader.images!.indexOf(imageKey);
|
||||
if (index >= startIndex && index < endIndex) {
|
||||
return imageKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
return imageKey;
|
||||
|
||||
return reader.images![startIndex];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -116,9 +116,9 @@ class _ReaderState extends State<Reader>
|
||||
return 1;
|
||||
}
|
||||
if (!showSingleImageOnFirstPage()) {
|
||||
return (images!.length / imagesPerPage()).ceil();
|
||||
return (images!.length / imagesPerPage).ceil();
|
||||
} else {
|
||||
return 1 + ((images!.length - 1) / imagesPerPage()).ceil();
|
||||
return 1 + ((images!.length - 1) / imagesPerPage).ceil();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,13 +277,13 @@ class _ReaderState extends State<Reader>
|
||||
history!.page = images?.length ?? 1;
|
||||
} else {
|
||||
/// Record the first image of the page
|
||||
if (!showSingleImageOnFirstPage() || imagesPerPage() == 1) {
|
||||
history!.page = (page - 1) * imagesPerPage() + 1;
|
||||
if (!showSingleImageOnFirstPage() || imagesPerPage == 1) {
|
||||
history!.page = (page - 1) * imagesPerPage + 1;
|
||||
} else {
|
||||
if (page == 1) {
|
||||
history!.page = 1;
|
||||
} else {
|
||||
history!.page = (page - 2) * imagesPerPage() + 2;
|
||||
history!.page = (page - 2) * imagesPerPage + 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,13 +371,13 @@ abstract mixin class _ImagePerPageHandler {
|
||||
ComicType get type;
|
||||
|
||||
void initImagesPerPage(int initialPage) {
|
||||
_lastImagesPerPage = imagesPerPage();
|
||||
_lastImagesPerPage = imagesPerPage;
|
||||
_lastOrientation = isPortrait;
|
||||
if (imagesPerPage() != 1) {
|
||||
if (imagesPerPage != 1) {
|
||||
if (showSingleImageOnFirstPage()) {
|
||||
page = ((initialPage - 1) / imagesPerPage()).ceil() + 1;
|
||||
page = ((initialPage - 1) / imagesPerPage).ceil() + 1;
|
||||
} else {
|
||||
page = (initialPage / imagesPerPage()).ceil();
|
||||
page = (initialPage / imagesPerPage).ceil();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -386,7 +386,7 @@ abstract mixin class _ImagePerPageHandler {
|
||||
appdata.settings.getReaderSetting(cid, type.sourceKey, 'showSingleImageOnFirstPage');
|
||||
|
||||
/// The number of images displayed on one screen
|
||||
int imagesPerPage() {
|
||||
int get imagesPerPage {
|
||||
if (mode.isContinuous) return 1;
|
||||
if (isPortrait) {
|
||||
return appdata.settings.getReaderSetting(cid, type.sourceKey, 'readerScreenPicNumberForPortrait') ?? 1;
|
||||
@@ -397,7 +397,7 @@ abstract mixin class _ImagePerPageHandler {
|
||||
|
||||
/// Check if the number of images per page has changed
|
||||
void _checkImagesPerPageChange() {
|
||||
int currentImagesPerPage = imagesPerPage();
|
||||
int currentImagesPerPage = imagesPerPage;
|
||||
bool currentOrientation = isPortrait;
|
||||
|
||||
if (_lastImagesPerPage != currentImagesPerPage || _lastOrientation != currentOrientation) {
|
||||
|
||||
@@ -599,22 +599,24 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
||||
}
|
||||
|
||||
void saveCurrentImage() async {
|
||||
var data = await selectImageToData();
|
||||
if (data == null) {
|
||||
var result = await selectImageToData();
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
var (imageIndex, data) = result;
|
||||
var fileType = detectFileType(data);
|
||||
var filename = "${context.reader.page}${fileType.ext}";
|
||||
var filename = "${context.reader.widget.name}_${imageIndex + 1}${fileType.ext}";
|
||||
saveFile(data: data, filename: filename);
|
||||
}
|
||||
|
||||
void share() async {
|
||||
var data = await selectImageToData();
|
||||
if (data == null) {
|
||||
var result = await selectImageToData();
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
var (imageIndex, data) = result;
|
||||
var fileType = detectFileType(data);
|
||||
var filename = "${context.reader.page}${fileType.ext}";
|
||||
var filename = "${context.reader.widget.name}_${imageIndex + 1}${fileType.ext}";
|
||||
Share.shareFile(data: data, filename: filename, mime: fileType.mime);
|
||||
}
|
||||
|
||||
@@ -719,8 +721,29 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
||||
Future<int?> selectImage() async {
|
||||
var reader = context.reader;
|
||||
var imageViewController = context.reader._imageViewController;
|
||||
if (imageViewController is _GalleryModeState && reader.imagesPerPage == 1) {
|
||||
return reader.page - 1;
|
||||
|
||||
bool needsSelection = false;
|
||||
int? singleImageIndex;
|
||||
|
||||
if (imageViewController is _GalleryModeState) {
|
||||
var range = imageViewController.getCurrentPageImageRange();
|
||||
if (range != null) {
|
||||
var (startIndex, endIndex) = range;
|
||||
int actualImageCount = endIndex - startIndex;
|
||||
if (actualImageCount == 1) {
|
||||
needsSelection = false;
|
||||
singleImageIndex = startIndex;
|
||||
} else {
|
||||
needsSelection = true;
|
||||
}
|
||||
}
|
||||
} else if (imageViewController is _ContinuousModeState) {
|
||||
needsSelection = false;
|
||||
singleImageIndex = reader.page - 1;
|
||||
}
|
||||
|
||||
if (!needsSelection && singleImageIndex != null) {
|
||||
return singleImageIndex;
|
||||
} else {
|
||||
var location = await _showSelectImageOverlay();
|
||||
if (location == null) {
|
||||
@@ -734,20 +757,23 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as [selectImage], but return the image data.
|
||||
Future<Uint8List?> selectImageToData() async {
|
||||
/// Same as [selectImage], but return the image data with its index.
|
||||
/// Returns (imageIndex, imageData) or null if cancelled.
|
||||
Future<(int, Uint8List)?> selectImageToData() async {
|
||||
var i = await selectImage();
|
||||
if (i == null) {
|
||||
return null;
|
||||
}
|
||||
var imageKey = context.reader.images![i];
|
||||
Uint8List data;
|
||||
if (imageKey.startsWith("file://")) {
|
||||
return await File(imageKey.substring(7)).readAsBytes();
|
||||
data = await File(imageKey.substring(7)).readAsBytes();
|
||||
} else {
|
||||
return (await CacheManager().findCache(
|
||||
data = await (await CacheManager().findCache(
|
||||
"$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}",
|
||||
))!.readAsBytes();
|
||||
}
|
||||
return (i, data);
|
||||
}
|
||||
|
||||
Future<Offset?> _showSelectImageOverlay() {
|
||||
|
||||
@@ -428,30 +428,26 @@ class _WebdavSettingState extends State<_WebdavSetting> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Text("Operation".tl),
|
||||
Radio<bool>(
|
||||
groupValue: upload,
|
||||
value: true,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
upload = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
Text("Upload".tl),
|
||||
Radio<bool>(
|
||||
groupValue: upload,
|
||||
value: false,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
upload = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
Text("Download".tl),
|
||||
],
|
||||
RadioGroup<bool>(
|
||||
groupValue: upload,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
upload = value ?? upload;
|
||||
});
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Text("Operation".tl),
|
||||
Radio<bool>(
|
||||
value: true,
|
||||
),
|
||||
Text("Upload".tl),
|
||||
Radio<bool>(
|
||||
value: false,
|
||||
),
|
||||
Text("Download".tl),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
AnimatedSize(
|
||||
|
||||
@@ -111,44 +111,34 @@ class _ProxySettingViewState extends State<_ProxySettingView> {
|
||||
return PopUpWidgetScaffold(
|
||||
title: "Proxy".tl,
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
RadioListTile<String>(
|
||||
title: Text("Direct".tl),
|
||||
value: 'direct',
|
||||
groupValue: type,
|
||||
onChanged: (v) {
|
||||
setState(() {
|
||||
type = v!;
|
||||
});
|
||||
appdata.settings['proxy'] = toProxyStr();
|
||||
appdata.saveData();
|
||||
},
|
||||
),
|
||||
RadioListTile<String>(
|
||||
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<String>(
|
||||
groupValue: type,
|
||||
onChanged: (v) {
|
||||
setState(() {
|
||||
type = v ?? type;
|
||||
});
|
||||
if (type != 'manual') {
|
||||
appdata.settings['proxy'] = toProxyStr();
|
||||
appdata.saveData();
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
RadioListTile<String>(
|
||||
title: Text("Direct".tl),
|
||||
value: 'direct',
|
||||
),
|
||||
RadioListTile<String>(
|
||||
title: Text("System".tl),
|
||||
value: 'system',
|
||||
),
|
||||
RadioListTile(
|
||||
title: Text("Manual".tl),
|
||||
value: 'manual',
|
||||
),
|
||||
if (type == 'manual') buildManualProxy(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user