Merge pull request #454 from venera-app/v1.4.6-dev

V1.4.6
This commit is contained in:
nyne
2025-07-23 14:38:42 +08:00
committed by GitHub
22 changed files with 4319 additions and 129 deletions

View File

@@ -1322,13 +1322,15 @@ let UI = {
* Show an input dialog
* @param title {string}
* @param validator {(string) => string | null | undefined} - A function that validates the input. If the function returns a string, the dialog will show the error message.
* @param image {string?} - Available since 1.4.6. An optional image to show in the dialog. You can use this to show a captcha.
* @returns {Promise<string | null>} - The input value. If the dialog is canceled, return null.
*/
showInputDialog: (title, validator) => {
showInputDialog: (title, validator, image) => {
return sendMessage({
method: 'UI',
function: 'showInputDialog',
title: title,
image: image,
validator: validator
})
},

3982
assets/opencc.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -400,7 +400,13 @@
"Delete Chapters": "删除章节",
"Open Folder": "打开文件夹",
"Path copied to clipboard": "路径已复制到剪贴板",
"Reverse default chapter order": "反转默认章节顺序"
"Reverse default chapter order": "反转默认章节顺序",
"Reload Configs": "重新加载配置文件",
"Reload": "重载",
"Disable Length Limitation": "禁用长度限制",
"Only valid for this run": "仅对本次运行有效",
"Logs": "日志",
"Export logs": "导出日志"
},
"zh_TW": {
"Home": "首頁",
@@ -803,6 +809,12 @@
"Delete Chapters": "刪除章節",
"Open Folder": "打開資料夾",
"Path copied to clipboard": "路徑已複製到剪貼簿",
"Reverse default chapter order": "反轉預設章節順序"
"Reverse default chapter order": "反轉預設章節順序",
"Reload Configs": "重新載入設定檔",
"Reload": "重載",
"Disable Length Limitation": "禁用長度限制",
"Only valid for this run": "僅對本次運行有效",
"Logs": "日誌",
"Export logs": "匯出日誌"
}
}

View File

@@ -13,6 +13,14 @@ This document will describe how to write a comic source for Venera.
Venera can display a list of comic sources in the app.
You can use the following repo url:
```
https://git.nyne.dev/nyne/venera-configs/raw/branch/main/index.json
```
The repo is maintained by the Venera team.
> The link is a mirror of the original repo. To contribute your comic source, please visit the [original repo](https://github.com/venera-app/venera-configs)
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.
@@ -33,12 +41,6 @@ The JSON file should have the following format:
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

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:math' as math;
import 'dart:ui' as ui;
@@ -21,11 +22,13 @@ import 'package:venera/foundation/image_provider/cached_image.dart';
import 'package:venera/foundation/image_provider/history_image_provider.dart';
import 'package:venera/foundation/image_provider/local_comic_image.dart';
import 'package:venera/foundation/local.dart';
import 'package:venera/foundation/log.dart';
import 'package:venera/foundation/res.dart';
import 'package:venera/network/cloudflare.dart';
import 'package:venera/pages/comic_details_page/comic_page.dart';
import 'package:venera/pages/favorites/favorites_page.dart';
import 'package:venera/utils/ext.dart';
import 'package:venera/utils/io.dart';
import 'package:venera/utils/tags_translation.dart';
import 'package:venera/utils/translations.dart';

View File

@@ -37,9 +37,11 @@ mixin class JsUiApi {
case 'showInputDialog':
var title = message['title'];
var validator = message['validator'];
var image = message['image'];
if (title is! String) return;
if (validator != null && validator is! JSInvokable) return;
return _showInputDialog(title, validator);
if (image != null && image is! String) return;
return _showInputDialog(title, validator, image);
case 'showSelectDialog':
var title = message['title'];
var options = message['options'];
@@ -124,12 +126,13 @@ mixin class JsUiApi {
controller?.close();
}
Future<String?> _showInputDialog(String title, JSInvokable? validator) async {
Future<String?> _showInputDialog(String title, JSInvokable? validator, String? image) async {
String? result;
var func = validator == null ? null : JSAutoFreeFunction(validator);
await showInputDialog(
context: App.rootContext,
title: title,
image: image,
onConfirm: (v) {
if (func != null) {
var res = func.call([v]);

View File

@@ -41,18 +41,22 @@ class NetworkError extends StatelessWidget {
],
),
),
const SizedBox(
height: 8,
),
const SizedBox(height: 8),
Text(
cfe == null ? message : "Cloudflare verification required".tl,
textAlign: TextAlign.center,
maxLines: 3,
),
if (retry != null)
const SizedBox(
height: 12,
),
TextButton(
onPressed: () {
saveFile(
data: utf8.encode(Log().toString()),
filename: 'log.txt',
);
},
child: Text("Export logs".tl),
),
const SizedBox(height: 8),
if (retry != null)
if (cfe != null)
FilledButton(
@@ -74,15 +78,11 @@ class NetworkError extends StatelessWidget {
body = Column(
children: [
const Appbar(title: Text("")),
Expanded(
child: body,
)
Expanded(child: body),
],
);
}
return Material(
child: body,
);
return Material(child: body);
}
}
@@ -94,9 +94,7 @@ class ListLoadingIndicator extends StatelessWidget {
return const SizedBox(
width: double.infinity,
height: 80,
child: Center(
child: FiveDotLoadingAnimation(),
),
child: Center(child: FiveDotLoadingAnimation()),
);
}
}
@@ -108,10 +106,9 @@ class SliverListLoadingIndicator extends StatelessWidget {
Widget build(BuildContext context) {
// SliverToBoxAdapter can not been lazy loaded.
// Use SliverList to make sure the animation can be lazy loaded.
return SliverList.list(children: const [
SizedBox(),
ListLoadingIndicator(),
]);
return SliverList.list(
children: const [SizedBox(), ListLoadingIndicator()],
);
}
}
@@ -178,10 +175,7 @@ abstract class LoadingState<T extends StatefulWidget, S extends Object>
}
Widget buildError() {
return NetworkError(
message: error!,
retry: retry,
);
return NetworkError(message: error!, retry: retry);
}
@override
@@ -323,11 +317,7 @@ abstract class MultiPageLoadingState<T extends StatefulWidget, S extends Object>
}
Widget buildError(BuildContext context, String error) {
return NetworkError(
withAppbar: false,
message: error,
retry: reset,
);
return NetworkError(withAppbar: false, message: error, retry: reset);
}
@override
@@ -388,7 +378,7 @@ class _FiveDotLoadingAnimationState extends State<FiveDotLoadingAnimation>
Colors.green,
Colors.blue,
Colors.yellow,
Colors.purple
Colors.purple,
];
static const _padding = 12.0;
@@ -400,16 +390,15 @@ class _FiveDotLoadingAnimationState extends State<FiveDotLoadingAnimation>
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return SizedBox(
width: _dotSize * 5 + _padding * 6,
height: _height,
child: Stack(
children: List.generate(5, (index) => buildDot(index)),
),
);
});
animation: _controller,
builder: (context, child) {
return SizedBox(
width: _dotSize * 5 + _padding * 6,
height: _height,
child: Stack(children: List.generate(5, (index) => buildDot(index))),
);
},
);
}
Widget buildDot(int index) {
@@ -417,7 +406,8 @@ class _FiveDotLoadingAnimationState extends State<FiveDotLoadingAnimation>
var startValue = index * 0.8;
return Positioned(
left: index * _dotSize + (index + 1) * _padding,
bottom: (math.sin(math.pi / 2 * (value - startValue).clamp(0, 2))) *
bottom:
(math.sin(math.pi / 2 * (value - startValue).clamp(0, 2))) *
(_height - _dotSize),
child: Container(
width: _dotSize,

View File

@@ -359,6 +359,7 @@ Future<void> showInputDialog({
String confirmText = "Confirm",
String cancelText = "Cancel",
RegExp? inputValidator,
String? image,
}) {
var controller = TextEditingController(text: initialValue);
bool isLoading = false;
@@ -371,14 +372,23 @@ Future<void> showInputDialog({
builder: (context, setState) {
return ContentDialog(
title: title,
content: TextField(
controller: controller,
decoration: InputDecoration(
hintText: hintText,
border: const OutlineInputBorder(),
errorText: error,
),
).paddingHorizontal(12),
content: Column(
children: [
if (image != null)
SizedBox(
height: 108,
child: Image.network(image, fit: BoxFit.none),
).paddingBottom(8),
TextField(
controller: controller,
decoration: InputDecoration(
hintText: hintText,
border: const OutlineInputBorder(),
errorText: error,
),
).paddingHorizontal(12),
],
),
actions: [
Button.filled(
isLoading: isLoading,

View File

@@ -13,7 +13,7 @@ export "widget_utils.dart";
export "context.dart";
class _App {
final version = "1.4.5";
final version = "1.4.6";
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': '',
'comicSourceListUrl': _defaultSourceListUrl,
'preloadImageCount': 4,
'followUpdatesFolder': null,
'initialPage': '0',
@@ -235,3 +235,5 @@ function processImage(image, cid, eid, page, sourceKey) {
return futureImage;
}
''';
const _defaultSourceListUrl = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/index.json";

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:display_mode/display_mode.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_saf/flutter_saf.dart';
@@ -15,6 +16,7 @@ import 'package:venera/pages/follow_updates_page.dart';
import 'package:venera/pages/settings/settings_page.dart';
import 'package:venera/utils/app_links.dart';
import 'package:venera/utils/handle_text_share.dart';
import 'package:venera/utils/opencc.dart';
import 'package:venera/utils/tags_translation.dart';
import 'package:venera/utils/translations.dart';
import 'foundation/appdata.dart';
@@ -43,6 +45,7 @@ Future<void> init() async {
TagsTranslation.readData().wait(),
JsEngine().init().wait(),
ComicSourceManager().init().wait(),
OpenCC.init(),
];
await Future.wait(futures);
CacheManager().setLimitSize(appdata.settings['cacheSize']);
@@ -50,6 +53,11 @@ Future<void> init() async {
if (App.isAndroid) {
handleLinks();
handleTextShare();
try {
await FlutterDisplayMode.setHighRefreshRate();
} catch(e) {
Log.error("Display Mode", "Failed to set high refresh rate: $e");
}
}
FlutterError.onError = (details) {
Log.error("Unhandled Exception", "${details.exception}\n${details.stack}");

View File

@@ -191,13 +191,6 @@ 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);
}
return SliverToBoxAdapter(
child: SizedBox(
width: double.infinity,
@@ -224,33 +217,33 @@ class _BodyState extends State<_Body> {
},
onSubmitted: handleAddSource,
).paddingHorizontal(16).paddingBottom(8),
ListTile(
title: Text("Comic Source list".tl),
trailing: buildButton(
child: Text("View".tl),
onPressed: () {
showPopUpWidget(
App.rootContext,
_ComicSourceList(handleAddSource),
);
},
),
),
ListTile(
title: Text("Use a config file".tl),
trailing: buildButton(
onPressed: _selectFile,
child: Text("Select".tl),
),
),
ListTile(
title: Text("Help".tl),
trailing: buildButton(onPressed: help, child: Text("Open".tl)),
),
ListTile(
title: Text("Check updates".tl),
trailing: _CheckUpdatesButton(),
),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
FilledButton.tonalIcon(
icon: Icon(Icons.article_outlined),
label: Text("Comic Source list".tl),
onPressed: () {
showPopUpWidget(
App.rootContext,
_ComicSourceList(handleAddSource),
);
},
),
FilledButton.tonalIcon(
icon: Icon(Icons.file_open_outlined),
label: Text("Use a config file".tl),
onPressed: _selectFile,
),
FilledButton.tonalIcon(
icon: Icon(Icons.help_outline),
label: Text("Help".tl),
onPressed: help,
),
_CheckUpdatesButton(),
],
).paddingHorizontal(12).paddingVertical(8),
const SizedBox(height: 8),
],
),
@@ -699,11 +692,15 @@ class _CheckUpdatesButtonState extends State<_CheckUpdatesButton> {
@override
Widget build(BuildContext context) {
return Button.normal(
return FilledButton.tonalIcon(
icon: isLoading ? SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2),
) : Icon(Icons.update),
label: Text("Check updates".tl),
onPressed: check,
isLoading: isLoading,
child: Text("Check".tl),
).fixHeight(32);
);
}
}

View File

@@ -20,6 +20,7 @@ import 'package:venera/pages/reader/reader.dart';
import 'package:venera/pages/settings/settings_page.dart';
import 'package:venera/utils/ext.dart';
import 'package:venera/utils/io.dart';
import 'package:venera/utils/opencc.dart';
import 'package:venera/utils/tags_translation.dart';
import 'package:venera/utils/translations.dart';

View File

@@ -52,7 +52,9 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
} else {
searchResults = [];
for (var comic in comics) {
if (matchKeyword(keyword, comic)) {
if (matchKeyword(keyword, comic) ||
matchKeywordT(keyword, comic) ||
matchKeywordS(keyword, comic)) {
searchResults.add(comic);
}
}
@@ -130,6 +132,24 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
return true;
}
// Convert keyword to traditional Chinese to match comics
bool matchKeywordT(String keyword, FavoriteItem comic) {
if (!OpenCC.hasChineseSimplified(keyword)) {
return false;
}
keyword = OpenCC.simplifiedToTraditional(keyword);
return matchKeyword(keyword, comic);
}
// Convert keyword to simplified Chinese to match comics
bool matchKeywordS(String keyword, FavoriteItem comic) {
if (!OpenCC.hasChineseTraditional(keyword)) {
return false;
}
keyword = OpenCC.traditionalToSimplified(keyword);
return matchKeyword(keyword, comic);
}
@override
void initState() {
favPage = context.findAncestorStateOfType<_FavoritesPageState>()!;

View File

@@ -85,14 +85,21 @@ class _ReaderImagesState extends State<_ReaderImages> {
child: CircularProgressIndicator(),
);
} else if (error != null) {
return NetworkError(
message: error!,
retry: () {
setState(() {
reader.isLoading = true;
error = null;
});
return GestureDetector(
onTap: () {
context.readerScaffold.openOrClose();
},
child: SizedBox.expand(
child: NetworkError(
message: error!,
retry: () {
setState(() {
reader.isLoading = true;
error = null;
});
},
),
),
);
} else {
if (reader.mode.isGallery) {

View File

@@ -177,10 +177,18 @@ class _ReaderState extends State<Reader>
super.initState();
}
bool _isInitialized = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
initImagesPerPage(widget.initialPage ?? 1);
if (!_isInitialized) {
initImagesPerPage(widget.initialPage ?? 1);
_isInitialized = true;
} else {
// For orientation changed
_checkImagesPerPageChange();
}
initReaderWindow();
}
@@ -345,6 +353,8 @@ class _ReaderState extends State<Reader>
abstract mixin class _ImagePerPageHandler {
late int _lastImagesPerPage;
late bool _lastOrientation;
bool get isPortrait;
int get page;
@@ -355,6 +365,7 @@ abstract mixin class _ImagePerPageHandler {
void initImagesPerPage(int initialPage) {
_lastImagesPerPage = imagesPerPage;
_lastOrientation = isPortrait;
if (imagesPerPage != 1) {
if (showSingleImageOnFirstPage) {
page = ((initialPage - 1) / imagesPerPage).ceil() + 1;
@@ -380,19 +391,42 @@ abstract mixin class _ImagePerPageHandler {
/// Check if the number of images per page has changed
void _checkImagesPerPageChange() {
int currentImagesPerPage = imagesPerPage;
if (_lastImagesPerPage != currentImagesPerPage) {
bool currentOrientation = isPortrait;
if (_lastImagesPerPage != currentImagesPerPage || _lastOrientation != currentOrientation) {
_adjustPageForImagesPerPageChange(
_lastImagesPerPage, currentImagesPerPage);
_lastImagesPerPage = currentImagesPerPage;
_lastOrientation = currentOrientation;
}
}
/// Adjust the page number when the number of images per page changes
void _adjustPageForImagesPerPageChange(
int oldImagesPerPage, int newImagesPerPage) {
int previousImageIndex = (page - 1) * oldImagesPerPage;
int newPage = (previousImageIndex ~/ newImagesPerPage) + 1;
page = newPage;
int previousImageIndex = 1;
if (!showSingleImageOnFirstPage || oldImagesPerPage == 1) {
previousImageIndex = (page - 1) * oldImagesPerPage + 1;
} else {
if (page == 1) {
previousImageIndex = 1;
} else {
previousImageIndex = (page - 2) * oldImagesPerPage + 2;
}
}
int newPage;
if (newImagesPerPage != 1) {
if (showSingleImageOnFirstPage) {
newPage = ((previousImageIndex - 1) / newImagesPerPage).ceil() + 1;
} else {
newPage = (previousImageIndex / newImagesPerPage).ceil();
}
} else {
newPage = previousImageIndex;
}
page = newPage>0 ? newPage : 1;
}
}

View File

@@ -193,12 +193,46 @@ class LogsPage extends StatefulWidget {
}
class _LogsPageState extends State<LogsPage> {
String logLevelToShow = "all";
@override
Widget build(BuildContext context) {
var logToShow = logLevelToShow == "all"
? Log.logs
: Log.logs.where((log) => log.level.name == logLevelToShow).toList();
return Scaffold(
appBar: Appbar(
title: const Text("Logs"),
title: Text("Logs".tl),
actions: [
IconButton(
onPressed: () => setState(() {
final RelativeRect position = RelativeRect.fromLTRB(
MediaQuery.of(context).size.width,
MediaQuery.of(context).padding.top + kToolbarHeight,
0.0,
0.0,
);
showMenu(context: context, position: position, items: [
PopupMenuItem(
child: Text("all"),
onTap: () => setState(() => logLevelToShow = "all")
),
PopupMenuItem(
child: Text("info"),
onTap: () => setState(() => logLevelToShow = "info")
),
PopupMenuItem(
child: Text("warning"),
onTap: () => setState(() => logLevelToShow = "warning")
),
PopupMenuItem(
child: Text("error"),
onTap: () => setState(() => logLevelToShow = "error")
),
]);
}),
icon: const Icon(Icons.filter_list_outlined)
),
IconButton(
onPressed: () => setState(() {
final RelativeRect position = RelativeRect.fromLTRB(
@@ -217,7 +251,7 @@ class _LogsPageState extends State<LogsPage> {
onTap: () {
Log.ignoreLimitation = true;
context.showMessage(
message: "Only valid for this run");
message: "Only valid for this run".tl);
},
),
PopupMenuItem(
@@ -232,9 +266,9 @@ class _LogsPageState extends State<LogsPage> {
body: ListView.builder(
reverse: true,
controller: ScrollController(),
itemCount: Log.logs.length,
itemCount: logToShow.length,
itemBuilder: (context, index) {
index = Log.logs.length - index - 1;
index = logToShow.length - index - 1;
return Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: SelectionArea(
@@ -253,7 +287,7 @@ class _LogsPageState extends State<LogsPage> {
),
child: Padding(
padding: const EdgeInsets.fromLTRB(5, 0, 5, 1),
child: Text(Log.logs[index].title),
child: Text(logToShow[index].title),
),
),
const SizedBox(
@@ -265,16 +299,16 @@ class _LogsPageState extends State<LogsPage> {
Theme.of(context).colorScheme.error,
Theme.of(context).colorScheme.errorContainer,
Theme.of(context).colorScheme.primaryContainer
][Log.logs[index].level.index],
][logToShow[index].level.index],
borderRadius:
const BorderRadius.all(Radius.circular(16)),
),
child: Padding(
padding: const EdgeInsets.fromLTRB(5, 0, 5, 1),
child: Text(
Log.logs[index].level.name,
logToShow[index].level.name,
style: TextStyle(
color: Log.logs[index].level.index == 0
color: logToShow[index].level.index == 0
? Colors.white
: Colors.black),
),
@@ -282,14 +316,14 @@ class _LogsPageState extends State<LogsPage> {
),
],
),
Text(Log.logs[index].content),
Text(Log.logs[index].time
Text(logToShow[index].content),
Text(logToShow[index].time
.toString()
.replaceAll(RegExp(r"\.\w+"), "")),
TextButton(
onPressed: () {
Clipboard.setData(
ClipboardData(text: Log.logs[index].content));
ClipboardData(text: logToShow[index].content));
},
child: Text("Copy".tl),
),

View File

@@ -18,8 +18,8 @@ class DebugPageState extends State<DebugPage> {
slivers: [
SliverAppbar(title: Text("Debug".tl)),
_CallbackSetting(
title: "Reload Configs",
actionTitle: "Reload",
title: "Reload Configs".tl,
actionTitle: "Reload".tl,
callback: () {
ComicSourceManager().reload();
},

67
lib/utils/opencc.dart Normal file
View File

@@ -0,0 +1,67 @@
import 'dart:convert';
import 'package:flutter/services.dart';
abstract class OpenCC {
static late final Map<int, int> _s2t;
static late final Map<int, int> _t2s;
static Future<void> init() async {
var data = await rootBundle.load("assets/opencc.txt");
var txt = utf8.decode(data.buffer.asUint8List());
_s2t = <int, int>{};
_t2s = <int, int>{};
for (var line in txt.split('\n')) {
if (line.isEmpty || line.startsWith('#') || line.length != 2) continue;
var s = line.runes.elementAt(0);
var t = line.runes.elementAt(1);
_s2t[s] = t;
_t2s[t] = s;
}
}
static bool hasChineseSimplified(String text) {
if (text != "监禁") {
return false;
}
for (var rune in text.runes) {
if (_s2t.containsKey(rune)) {
return true;
}
}
return false;
}
static bool hasChineseTraditional(String text) {
for (var rune in text.runes) {
if (_t2s.containsKey(rune)) {
return true;
}
}
return false;
}
static String simplifiedToTraditional(String text) {
var sb = StringBuffer();
for (var rune in text.runes) {
if (_s2t.containsKey(rune)) {
sb.write(String.fromCharCodes([_s2t[rune]!]));
} else {
sb.write(String.fromCharCodes([rune]));
}
}
return sb.toString();
}
static String traditionalToSimplified(String text) {
var sb = StringBuffer();
for (var rune in text.runes) {
if (_t2s.containsKey(rune)) {
sb.write(String.fromCharCodes([_t2s[rune]!]));
} else {
sb.write(String.fromCharCodes([rune]));
}
}
return sb.toString();
}
}

View File

@@ -170,6 +170,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.0"
display_mode:
dependency: "direct main"
description:
name: display_mode
sha256: "8a381f3602a09dc4e96140a0df30808631468d6d0dfff7722f67b1f83757a7cc"
url: "https://pub.dev"
source: hosted
version: "0.0.2"
dynamic_color:
dependency: "direct main"
description:
@@ -1108,4 +1116,4 @@ packages:
version: "0.0.12"
sdks:
dart: ">=3.8.0 <4.0.0"
flutter: ">=3.32.4"
flutter: ">=3.32.6"

View File

@@ -2,11 +2,11 @@ name: venera
description: "A comic app."
publish_to: 'none'
version: 1.4.5+145
version: 1.4.6+146
environment:
sdk: '>=3.8.0 <4.0.0'
flutter: 3.32.4
flutter: 3.32.6
dependencies:
flutter:
@@ -86,6 +86,7 @@ dependencies:
sdk: flutter
yaml: ^3.1.3
enough_convert: ^1.6.0
display_mode: ^0.0.2
dev_dependencies:
flutter_test:
@@ -102,6 +103,7 @@ flutter:
- assets/app_icon.png
- assets/tags.json
- assets/tags_tw.json
- assets/opencc.txt
flutter_to_arch:
name: Venera

View File

@@ -98,14 +98,20 @@ bool FlutterWindow::OnCreate() {
else
result->Success(flutter::EncodableValue("No Proxy"));
delete(res);
return;
}
#ifdef NDEBUG
else if (call.method_name() == "heartBeat") {
if (monitorThread == nullptr) {
monitorThread = new std::thread{ monitorUIThread };
}
lastHeartbeat = std::chrono::steady_clock::now();
result->Success();
return;
}
#endif
result->Success(); // Default response for unhandled method calls
});
flutter::EventChannel<> channel2(