15 Commits

Author SHA1 Message Date
nyne
8eda8adcc8 Merge pull request #410 from venera-app/v1.4.5-dev
V1.4.5
2025-06-18 16:52:59 +08:00
defd4b8624 Update version code. 2025-06-18 16:39:02 +08:00
b2a164e066 Remove the config file repository url from app. 2025-06-18 16:34:49 +08:00
a46ceebf19 Fixed the issue where the update dialog was not showed on startup. 2025-06-18 16:07:36 +08:00
cc08445f13 Set initial chapter to first downloaded chapter if there is no history when starting to read a local comic. Close #405 2025-06-17 17:36:13 +08:00
93f7f72d07 Fixed some issues when using custom download path on Android. Close #400 2025-06-17 17:15:35 +08:00
20f7ab4866 Clear folder value if it does not exist in local favorites. Close #389 2025-06-15 15:02:45 +08:00
54363919cd Fixed RangeError when translating tags. Close #356 2025-06-15 14:58:15 +08:00
182a821fc5 Fixed the issue where the download task would stop after exiting the reader. Close #387 2025-06-15 14:58:15 +08:00
8868c6edb3 Update Flutter SDK version to 3.32.4 2025-06-15 14:58:15 +08:00
角砂糖
fffbb4ed23 Only add closeListener when app is desktop (#397) 2025-06-04 12:11:45 +08:00
角砂糖
b057be0311 Fix abnormal history recording when not flipping pages. Close #392 (#395) 2025-06-03 17:36:20 +08:00
角砂糖
fc5fed1707 Fix history of page when show single image on first page (#393) 2025-06-03 17:35:45 +08:00
角砂糖
8525f5318f Fix page calculate when showSingleImageOnFirstPage is enabled (#391) 2025-06-03 17:35:17 +08:00
角砂糖
d58cafc4a0 Fix abnormal single image height when imagesPerPage > 1. Close #379 2025-05-31 10:50:17 +08:00
18 changed files with 256 additions and 162 deletions

View File

@@ -389,7 +389,7 @@
"Do not report any issues related to sources to App repo.": "请不要向App仓库报告任何与源相关的问题",
"Show single image on first page": "在首页显示单张图片",
"Click to select an image": "点击选择一张图片",
"Source URL": "地址",
"Repo URL": "仓库地址",
"The URL should point to a 'index.json' file": "该URL应指向一个'index.json'文件",
"Double tap to zoom": "双击缩放",
"Clear Unfavorited": "清除未收藏",
@@ -786,7 +786,7 @@
"Do not report any issues related to sources to App repo.": "請不要向App倉庫報告任何與源相關的問題",
"Show single image on first page": "在首頁顯示單張圖片",
"Click to select an image": "點擊選擇一張圖片",
"Source URL": "地址",
"Repo URL": "倉庫地址",
"The URL should point to a 'index.json' file": "該URL應指向一個'index.json'文件",
"Double tap to zoom": "雙擊縮放",
"Clear Unfavorited": "清除未收藏",

View File

@@ -9,13 +9,45 @@ Venera uses [flutter_qjs](https://github.com/wgh136/flutter_qjs) as js engine wh
This document will describe how to write a comic source for Venera.
## Preparation
## Comic Source List
Venera can display a list of comic sources in the app.
You should provide a repository url to let the app load the comic source list.
The url should point to a JSON file that contains the list of comic sources.
The JSON file should have the following format:
```json
[
{
"name": "Source Name",
"url": "https://example.com/source.js",
"filename": "Relative path to the source file",
"version": "1.0.0",
"description": "A brief description of the source"
}
]
```
Only one of `url` and `filename` should be provided.
The description field is optional.
Currently, you can use the following repo url:
```
https://cdn.jsdelivr.net/gh/venera-app/venera-configs@main/index.json
```
The repo is maintained by the Venera team, and you can submit a pull request to add your comic source.
## Create a Comic Source
### Preparation
- Install Venera. Using flutter to run the project is recommended since it's easier to debug.
- An editor that supports javascript.
- Download template and venera javascript api from [here](https://github.com/venera-app/venera-configs).
## Start Writing
### Start Writing
The template contains detailed comments and examples. You can refer to it when writing your own comic source.
@@ -23,7 +55,7 @@ Here is a brief introduction to the template:
> Note: Javascript api document is [here](js_api.md).
### Write basic information
#### Write basic information
```javascript
class NewComicSource extends ComicSource {
@@ -49,7 +81,7 @@ In this part, you need to do the following:
- Change the class name to your source name.
- Fill in the name, key, version, minAppVersion, and url fields.
### init function
#### init function
```javascript
/**
@@ -64,7 +96,7 @@ The function will be called when the source is initialized. You can do some init
Remove this function if not used.
### Account
#### Account
```javascript
// [Optional] account related
@@ -140,7 +172,7 @@ In this part, you can implement login, logout, and register functions.
Remove this part if not used.
### Explore page
#### Explore page
```javascript
// explore page list
@@ -185,7 +217,7 @@ There are three types of explore pages:
- multiPageComicList: An explore page contains multiple comics, the comics are loaded page by page.
- mixed: An explore page contains multiple parts, each part can be a list of comics or a block of comics which have a title and a view more button.
### Category Page
#### Category Page
```javascript
// categories
@@ -227,7 +259,7 @@ Category page is a static page that contains multiple parts, each part contains
A comic source can only have one category page.
### Category Comics Page
#### Category Comics Page
```javascript
/// category comic loading related
@@ -280,7 +312,7 @@ When user clicks on a category, the category comics page will be displayed.
This part is used to load comics of a category.
### Search
#### Search
```javascript
/// search related
@@ -339,7 +371,7 @@ This part is used to load search results.
`load` and `loadNext` functions are used to load search results.
If `load` function is implemented, `loadNext` function will be ignored.
### Favorites
#### Favorites
```javascript
// favorite related
@@ -411,7 +443,7 @@ This part is used to manage network favorites of the source.
`load` and `loadNext` functions are used to load search results.
If `load` function is implemented, `loadNext` function will be ignored.
### Comic Details
#### Comic Details
```javascript
/// single comic related
@@ -576,7 +608,7 @@ If `load` function is implemented, `loadNext` function will be ignored.
This part is used to load comic details.
### Settings
#### Settings
```javascript
/*
@@ -635,7 +667,7 @@ This part is used to load comic details.
This part is used to provide settings for the source.
### Translations
#### Translations
```javascript
// [Optional] translations for the strings in this config

View File

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

View File

@@ -189,7 +189,7 @@ class Settings with ChangeNotifier {
'customImageProcessing': defaultCustomImageProcessing,
'sni': true,
'autoAddLanguageFilter': 'none', // none, chinese, english, japanese
'comicSourceListUrl': defaultComicSourceUrl,
'comicSourceListUrl': '',
'preloadImageCount': 4,
'followUpdatesFolder': null,
'initialPage': '0',
@@ -233,5 +233,3 @@ function processImage(image, cid, eid, page, sourceKey) {
return futureImage;
}
''';
const defaultComicSourceUrl = "https://cdn.jsdelivr.net/gh/venera-app/venera-configs@latest/index.json";

View File

@@ -2,6 +2,7 @@ import 'dart:convert';
import 'dart:isolate';
import 'package:flutter/widgets.dart' show ChangeNotifier;
import 'package:flutter_saf/flutter_saf.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqlite3/sqlite3.dart';
import 'package:venera/foundation/comic_source/comic_source.dart';
@@ -108,15 +109,42 @@ class LocalComic with HistoryMixin implements Comic {
void read() {
var history = HistoryManager().find(id, comicType);
int? firstDownloadedChapter;
int? firstDownloadedChapterGroup;
if (downloadedChapters.isNotEmpty && chapters != null) {
final chapters = this.chapters!;
if (chapters.isGrouped) {
for (int i=0; i<chapters.groupCount; i++) {
var group = chapters.getGroupByIndex(i);
var keys = group.keys.toList();
for (int j=0; j<keys.length; j++) {
var chapterId = keys[j];
if (downloadedChapters.contains(chapterId)) {
firstDownloadedChapter = j + 1;
firstDownloadedChapterGroup = i + 1;
break;
}
}
}
} else {
var keys = chapters.allChapters.keys;
for (int i = 0; i < keys.length; i++) {
if (downloadedChapters.contains(keys.elementAt(i))) {
firstDownloadedChapter = i + 1;
break;
}
}
}
}
App.rootContext.to(
() => Reader(
type: comicType,
cid: id,
name: title,
chapters: chapters,
initialChapter: history?.ep,
initialChapter: history?.ep ?? firstDownloadedChapter,
initialPage: history?.page,
initialChapterGroup: history?.group,
initialChapterGroup: history?.group ?? firstDownloadedChapterGroup,
history: history ??
History.fromModel(
model: this,
@@ -625,6 +653,7 @@ class LocalManager with ChangeNotifier {
/// Deletes the directories in a separate isolate to avoid blocking the UI thread.
static void _deleteDirectories(List<Directory> directories) {
Isolate.run(() async {
await SAFTaskWorker().init();
for (var dir in directories) {
try {
if (dir.existsSync()) {

View File

@@ -95,8 +95,7 @@ Future<void> _checkAppUpdates() async {
appdata.writeImplicitData();
ComicSourcePage.checkComicSourceUpdate();
if (appdata.settings['checkUpdateOnStart']) {
await Future.delayed(const Duration(milliseconds: 300));
await checkUpdateUi(false);
await checkUpdateUi(false, true);
}
}

View File

@@ -552,7 +552,7 @@ class _ImageDownloadWrapper {
void start() async {
int lastBytes = 0;
try {
await for (var p in ImageDownloader.loadComicImage(
await for (var p in ImageDownloader.loadComicImageUnwrapped(
image, task.source.key, task.comicId, chapter)) {
if (isCancelled) {
return;

View File

@@ -111,6 +111,11 @@ abstract class ImageDownloader {
return stream.stream;
}
static Stream<ImageDownloadProgress> loadComicImageUnwrapped(
String imageKey, String? sourceKey, String cid, String eid) {
return _loadComicImage(imageKey, sourceKey, cid, eid);
}
static Stream<ImageDownloadProgress> _loadComicImage(
String imageKey, String? sourceKey, String cid, String eid) async* {
final cacheKey = "$imageKey@$sourceKey@$cid@$eid";

View File

@@ -51,9 +51,7 @@ class ComicSourcePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: const _Body(),
);
return Scaffold(body: const _Body());
}
}
@@ -87,10 +85,7 @@ class _BodyState extends State<_Body> {
Widget build(BuildContext context) {
return SmoothCustomScrollView(
slivers: [
SliverAppbar(
title: Text('Comic Source'.tl),
style: AppbarStyle.shadow,
),
SliverAppbar(title: Text('Comic Source'.tl), style: AppbarStyle.shadow),
buildCard(context),
for (var source in ComicSource.all())
_SliverComicSource(
@@ -109,9 +104,7 @@ class _BodyState extends State<_Body> {
showConfirmDialog(
context: App.rootContext,
title: "Delete".tl,
content: "Delete comic source '@n' ?".tlParams({
"n": source.name,
}),
content: "Delete comic source '@n' ?".tlParams({"n": source.name}),
btnColor: context.colorScheme.error,
onConfirm: () {
var file = File(source.filePath);
@@ -134,13 +127,15 @@ class _BodyState extends State<_Body> {
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("cancel")),
child: const Text("cancel"),
),
TextButton(
onPressed: () async {
await ComicSourceManager().reload();
App.forceRebuild();
},
child: const Text("continue")),
child: const Text("continue"),
),
],
),
);
@@ -157,8 +152,10 @@ class _BodyState extends State<_Body> {
);
}
static Future<void> update(ComicSource source,
[bool showLoading = true]) async {
static Future<void> update(
ComicSource source, [
bool showLoading = true,
]) async {
if (!source.url.isURL) {
App.rootContext.showMessage(message: "Invalid url config");
return;
@@ -174,8 +171,10 @@ class _BodyState extends State<_Body> {
);
}
try {
var res = await AppDio().get<String>(source.url,
options: Options(responseType: ResponseType.plain));
var res = await AppDio().get<String>(
source.url,
options: Options(responseType: ResponseType.plain),
);
if (cancel) return;
controller?.close();
await ComicSourceParser().parse(res.data!, source.filePath);
@@ -192,12 +191,11 @@ class _BodyState extends State<_Body> {
}
Widget buildCard(BuildContext context) {
Widget buildButton(
{required Widget child, required VoidCallback onPressed}) {
return Button.normal(
onPressed: onPressed,
child: child,
).fixHeight(32);
Widget buildButton({
required Widget child,
required VoidCallback onPressed,
}) {
return Button.normal(onPressed: onPressed, child: child).fixHeight(32);
}
return SliverToBoxAdapter(
@@ -218,7 +216,9 @@ class _BodyState extends State<_Body> {
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
suffix: IconButton(
onPressed: () => handleAddSource(url),
icon: const Icon(Icons.check))),
icon: const Icon(Icons.check),
),
),
onChanged: (value) {
url = value;
},
@@ -245,10 +245,7 @@ class _BodyState extends State<_Body> {
),
ListTile(
title: Text("Help".tl),
trailing: buildButton(
onPressed: help,
child: Text("Open".tl),
),
trailing: buildButton(onPressed: help, child: Text("Open".tl)),
),
ListTile(
title: Text("Check updates".tl),
@@ -277,7 +274,8 @@ class _BodyState extends State<_Body> {
void help() {
launchUrlString(
"https://github.com/venera-app/venera/blob/master/doc/comic_source.md");
"https://github.com/venera-app/venera/blob/master/doc/comic_source.md",
);
}
Future<void> handleAddSource(String url) async {
@@ -288,11 +286,16 @@ class _BodyState extends State<_Body> {
splits.removeWhere((element) => element == "");
var fileName = splits.last;
bool cancel = false;
var controller = showLoadingDialog(App.rootContext,
onCancel: () => cancel = true, barrierDismissible: false);
var controller = showLoadingDialog(
App.rootContext,
onCancel: () => cancel = true,
barrierDismissible: false,
);
try {
var res = await AppDio()
.get<String>(url, options: Options(responseType: ResponseType.plain));
var res = await AppDio().get<String>(
url,
options: Options(responseType: ResponseType.plain),
);
if (cancel) return;
controller.close();
await addSource(res.data!, fileName);
@@ -332,6 +335,12 @@ class _ComicSourceListState extends State<_ComicSourceList> {
json = null;
});
}
if (controller.text.isEmpty) {
setState(() {
json = [];
});
return;
}
var dio = AppDio();
try {
var res = await dio.get<String>(controller.text);
@@ -343,8 +352,7 @@ class _ComicSourceListState extends State<_ComicSourceList> {
json = jsonDecode(res.data!);
});
}
}
catch(e) {
} catch (e) {
context.showMessage(message: "Network error".tl);
if (mounted) {
setState(() {
@@ -372,10 +380,7 @@ class _ComicSourceListState extends State<_ComicSourceList> {
@override
Widget build(BuildContext context) {
return PopUpWidgetScaffold(
title: "Comic Source".tl,
body: buildBody(),
);
return PopUpWidgetScaffold(title: "Comic Source".tl, body: buildBody());
}
Widget buildBody() {
@@ -399,32 +404,36 @@ class _ComicSourceListState extends State<_ComicSourceList> {
children: [
ListTile(
leading: Icon(Icons.source_outlined),
title: Text("Source URL".tl),
title: Text("Repo URL".tl),
),
TextField(
controller: controller,
decoration: InputDecoration(
hintText: "URL",
border: const UnderlineInputBorder(),
contentPadding:
const EdgeInsets.symmetric(horizontal: 12),
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
),
onChanged: (value) {
changed = true;
},
).paddingHorizontal(16).paddingBottom(8),
Text("The URL should point to a 'index.json' file".tl).paddingLeft(16),
Text("Do not report any issues related to sources to App repo.".tl).paddingLeft(16),
Text(
"The URL should point to a 'index.json' file".tl,
).paddingLeft(16),
Text(
"Do not report any issues related to sources to App repo.".tl,
).paddingLeft(16),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
controller.text = defaultComicSourceUrl;
changed = true;
launchUrlString(
"https://github.com/venera-app/venera/blob/master/doc/comic_source.md",
);
},
child: Text("Reset".tl),
child: Text("Help".tl),
),
FilledButton.tonal(
onPressed: load,
@@ -440,7 +449,11 @@ class _ComicSourceListState extends State<_ComicSourceList> {
}
if (index == 1 && json == null) {
return Center(child: CircularProgressIndicator());
return Center(
child: CircularProgressIndicator(
strokeWidth: 2,
).fixWidth(24).fixHeight(24),
);
}
index--;
@@ -551,8 +564,7 @@ void _addAllPagesWithComicSource(ComicSource source) {
!networkFavorites.contains(source.favoriteData!.key)) {
networkFavorites.add(source.favoriteData!.key);
}
if (source.searchPageData != null &&
!searchPages.contains(source.key)) {
if (source.searchPageData != null && !searchPages.contains(source.key)) {
searchPages.add(source.key);
}
@@ -594,15 +606,10 @@ class __EditFilePageState extends State<_EditFilePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: Appbar(
title: Text("Edit".tl),
),
appBar: Appbar(title: Text("Edit".tl)),
body: Column(
children: [
Container(
height: 0.6,
color: context.colorScheme.outlineVariant,
),
Container(height: 0.6, color: context.colorScheme.outlineVariant),
Expanded(
child: CodeEditor(
initialValue: current,
@@ -643,9 +650,11 @@ class _CheckUpdatesButtonState extends State<_CheckUpdatesButton> {
}
void showUpdateDialog() async {
var text = ComicSourceManager().availableUpdates.entries.map((e) {
var text = ComicSourceManager().availableUpdates.entries
.map((e) {
return "${ComicSource.find(e.key)!.name}: ${e.value}";
}).join("\n");
})
.join("\n");
bool doUpdate = false;
await showDialog(
context: App.rootContext,
@@ -783,10 +792,7 @@ class _SliverComicSourceState extends State<_SliverComicSource> {
child: ListTile(
title: Row(
children: [
Text(
source.name,
style: ts.s18,
),
Text(source.name, style: ts.s18),
const SizedBox(width: 6),
Container(
padding: const EdgeInsets.symmetric(
@@ -819,7 +825,7 @@ class _SliverComicSourceState extends State<_SliverComicSource> {
style: const TextStyle(fontSize: 13),
),
),
).paddingLeft(4)
).paddingLeft(4),
],
),
trailing: Row(
@@ -864,15 +870,9 @@ class _SliverComicSourceState extends State<_SliverComicSource> {
),
),
SliverToBoxAdapter(
child: Column(
children: buildSourceSettings().toList(),
),
),
SliverToBoxAdapter(
child: Column(
children: _buildAccount().toList(),
),
child: Column(children: buildSourceSettings().toList()),
),
SliverToBoxAdapter(child: Column(children: _buildAccount().toList())),
],
);
}
@@ -898,8 +898,10 @@ class _SliverComicSourceState extends State<_SliverComicSource> {
}
}
} else {
current = item.value['options']
.firstWhere((e) => e['value'] == current)['text'] ??
current =
item.value['options'].firstWhere(
(e) => e['value'] == current,
)['text'] ??
current;
}
yield ListTile(
@@ -907,8 +909,9 @@ class _SliverComicSourceState extends State<_SliverComicSource> {
trailing: Select(
current: (current as String).ts(source.key),
values: (item.value['options'] as List)
.map<String>((e) =>
((e['text'] ?? e['value']) as String).ts(source.key))
.map<String>(
(e) => ((e['text'] ?? e['value']) as String).ts(source.key),
)
.toList(),
onTap: (i) {
source.data['settings'][key] =
@@ -936,8 +939,11 @@ class _SliverComicSourceState extends State<_SliverComicSource> {
source.data['settings'][key] ?? item.value['default'] ?? '';
yield ListTile(
title: Text((item.value['title'] as String).ts(source.key)),
subtitle:
Text(current, maxLines: 1, overflow: TextOverflow.ellipsis),
subtitle: Text(
current,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
trailing: IconButton(
icon: const Icon(Icons.edit),
onPressed: () {
@@ -978,10 +984,7 @@ class _SliverComicSourceState extends State<_SliverComicSource> {
trailing: const Icon(Icons.arrow_right),
onTap: () async {
await context.to(
() => _LoginPage(
config: source.account!,
source: source,
),
() => _LoginPage(config: source.account!, source: source),
);
source.saveData();
setState(() {});
@@ -1027,9 +1030,7 @@ class _SliverComicSourceState extends State<_SliverComicSource> {
trailing: loading
? const SizedBox.square(
dimension: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
),
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.refresh),
);
@@ -1070,9 +1071,7 @@ class _LoginPageState extends State<_LoginPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const Appbar(
title: Text(''),
),
appBar: const Appbar(title: Text('')),
body: Center(
child: Container(
padding: const EdgeInsets.all(16),
@@ -1200,8 +1199,9 @@ class _LoginPageState extends State<_LoginPage> {
setState(() {
loading = true;
});
var cookies =
widget.config.cookieFields!.map((e) => _cookies[e] ?? '').toList();
var cookies = widget.config.cookieFields!
.map((e) => _cookies[e] ?? '')
.toList();
widget.config.validateCookies!(cookies).then((value) {
if (value) {
widget.source.data['account'] = 'ok';

View File

@@ -66,6 +66,11 @@ class _FavoritesPageState extends State<FavoritesPage> {
folder = data['name'];
isNetwork = data['isNetwork'] ?? false;
}
if (folder != null
&& !isNetwork
&& !LocalFavoritesManager().existsFolder(folder!)) {
folder = null;
}
super.initState();
}

View File

@@ -40,6 +40,9 @@ class _ReaderImagesState extends State<_ReaderImages> {
reader.images = images;
reader.isLoading = false;
inProgress = false;
Future.microtask(() {
reader.updateHistory();
});
});
} catch (e) {
setState(() {
@@ -65,6 +68,9 @@ class _ReaderImagesState extends State<_ReaderImages> {
reader.images = res.data;
reader.isLoading = false;
inProgress = false;
Future.microtask(() {
reader.updateHistory();
});
});
}
}
@@ -233,7 +239,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,
controller: photoViewControllers[index],

View File

@@ -164,9 +164,6 @@ class _ReaderState extends State<Reader>
}
mode = ReaderMode.fromKey(appdata.settings['readerMode']);
history = widget.history;
Future.microtask(() {
updateHistory();
});
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
if (appdata.settings['enableTurnPageByVolumeKey']) {
handleVolumeEvent();
@@ -267,7 +264,15 @@ 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;
} else {
if (page == 1) {
history!.page = 1;
} else {
history!.page = (page - 2) * imagesPerPage + 2;
}
}
}
history!.maxPage = images?.length ?? 1;
if (widget.chapters?.isGrouped ?? false) {
@@ -349,9 +354,13 @@ abstract mixin class _ImagePerPageHandler {
void initImagesPerPage(int initialPage) {
_lastImagesPerPage = imagesPerPage;
if (imagesPerPage != 1) {
if (showSingleImageOnFirstPage) {
page = ((initialPage - 1) / imagesPerPage).ceil() + 1;
} else {
page = (initialPage / imagesPerPage).ceil();
}
}
}
bool get showSingleImageOnFirstPage =>
appdata.settings["showSingleImageOnFirstPage"];

View File

@@ -96,10 +96,13 @@ Future<bool> checkUpdate() async {
return false;
}
Future<void> checkUpdateUi([bool showMessageIfNoUpdate = true]) async {
Future<void> checkUpdateUi([bool showMessageIfNoUpdate = true, bool delay = false]) async {
try {
var value = await checkUpdate();
if (value) {
if (delay) {
await Future.delayed(const Duration(seconds: 2));
}
showDialog(
context: App.rootContext,
builder: (context) {

View File

@@ -22,11 +22,13 @@ class DataSync with ChangeNotifier {
}
LocalFavoritesManager().addListener(onDataChanged);
ComicSourceManager().addListener(onDataChanged);
if (App.isDesktop) {
Future.delayed(const Duration(seconds: 1), () {
var controller = WindowFrame.of(App.rootContext);
controller.addCloseListener(_handleWindowClose);
});
}
}
void onDataChanged() {
if (isEnabled) {

View File

@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:isolate';
import 'package:flutter_saf/flutter_saf.dart';
import 'package:venera/foundation/app.dart';
import 'package:venera/foundation/local.dart';
import 'package:venera/utils/image.dart';
@@ -74,6 +75,9 @@ Future<Isolate> _runIsolate(
return Isolate.spawn<SendPort>(
(sendPort) => overrideIO(
() async {
if (App.isAndroid) {
await SAFTaskWorker().init();
}
var receivePort = ReceivePort();
sendPort.send(receivePort.sendPort);

View File

@@ -36,7 +36,9 @@ extension TagsTranslation on String{
static String _translateTags(String tag){
if (tag.contains('|')) {
var splits = tag.split('|');
return enTagsTranslations[splits[0]]??enTagsTranslations[splits[1]]??tag;
return enTagsTranslations[splits[0].trim()]
?? enTagsTranslations[splits[1].trim()]
?? tag;
} else if(tag.contains(':')) {
var splits = tag.split(':');
if(_haveNamespace(splits[0])) {

View File

@@ -1108,4 +1108,4 @@ packages:
version: "0.0.12"
sdks:
dart: ">=3.8.0 <4.0.0"
flutter: ">=3.32.0"
flutter: ">=3.32.4"

View File

@@ -2,11 +2,11 @@ name: venera
description: "A comic app."
publish_to: 'none'
version: 1.4.4+144
version: 1.4.5+145
environment:
sdk: '>=3.8.0 <4.0.0'
flutter: 3.32.0
flutter: 3.32.4
dependencies:
flutter: