16 Commits

24 changed files with 342 additions and 192 deletions

View File

@@ -67,7 +67,6 @@ android {
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.github.wgh136.venera"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
@@ -125,6 +124,6 @@ flutter {
}
dependencies {
implementation "androidx.activity:activity-ktx:1.9.2"
implementation "androidx.activity:activity-ktx:1.10.1"
implementation 'androidx.documentfile:documentfile:1.0.1'
}

View File

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip

View File

@@ -18,7 +18,7 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version '8.3.2' apply false
id "com.android.application" version '8.9.0' apply false
id "org.jetbrains.kotlin.android" version "1.8.10" apply false
}

View File

@@ -140,18 +140,18 @@
"Block": "屏蔽",
"Add new favorite to": "添加新收藏到",
"Move favorite after reading": "阅读后移动收藏",
"Delete folder?" : "删除文件夹?",
"Delete folder '@f' ?" : "删除文件夹 '@f' ?",
"Delete folder?": "删除文件夹?",
"Delete folder '@f' ?": "删除文件夹 '@f' ?",
"Import from file": "从文件导入",
"Failed to import": "导入失败",
"Cache Limit": "缓存限制",
"Set Cache Limit": "设置缓存限制",
"Size in MB": "大小MB",
"Select a directory which contains the comic directories." : "选择一个包含漫画文件夹的目录",
"Select a directory which contains the comic directories.": "选择一个包含漫画文件夹的目录",
"Help": "帮助",
"Export as cbz": "导出为cbz",
"Select an archive file (cbz, zip, 7z, cb7)" : "选择一个归档文件 (cbz, zip, 7z, cb7)",
"An archive file" : "一个归档文件",
"Select an archive file (cbz, zip, 7z, cb7)": "选择一个归档文件 (cbz, zip, 7z, cb7)",
"An archive file": "一个归档文件",
"Fullscreen": "全屏",
"Exit": "退出",
"View more": "查看更多",
@@ -198,9 +198,9 @@
"Long press on the favorite button to quickly add to this folder": "长按收藏按钮快速添加到这个文件夹",
"Added": "已添加",
"Turn page by volume keys": "使用音量键翻页",
"Display time & battery info in reader":"在阅读器中显示时间和电量信息",
"EhViewer downloads":"EhViewer下载",
"Select an EhViewer database and a download folder.":"选择EhViewer的下载数据导出的db文件与存放下载内容的目录",
"Display time & battery info in reader": "在阅读器中显示时间和电量信息",
"EhViewer downloads": "EhViewer下载",
"Select an EhViewer database and a download folder.": "选择EhViewer的下载数据导出的db文件与存放下载内容的目录",
"(EhViewer)Default": "(EhViewer)默认",
"If you import an EhViewer's database, program will automatically create folders according to the download label in that database.": "若通过EhViewer数据库导入漫画程序将会按其中的下载标签自动创建收藏文件夹。",
"Multi-Select": "进入多选模式",
@@ -241,7 +241,7 @@
"Delete all unavailable local favorite items": "删除所有无效的本地收藏",
"Deleted @a favorite items.": "已删除 @a 条无效收藏",
"New version available": "有新版本可用",
"A new version is available. Do you want to update now?" : "有新版本可用。您要现在更新吗?",
"A new version is available. Do you want to update now?": "有新版本可用。您要现在更新吗?",
"No new version available": "没有新版本可用",
"Export as pdf": "导出为pdf",
"Export as epub": "导出为epub",
@@ -288,15 +288,15 @@
"Copy the title successfully": "复制标题成功",
"The comic is invalid, please long press to delete, you can double click the title to copy": "该漫画已失效, 请长按删除, 可以双击标题进行复制",
"No search results found": "未找到搜索结果",
"Added @c comics to download queue." : "已添加 @c 本漫画到下载队列",
"Added @c comics to download queue.": "已添加 @c 本漫画到下载队列",
"Download started": "下载已开始",
"Click favorite": "点击收藏",
"End": "末尾",
"None": "无",
"View Detail": "查看详情",
"Select a directory which contains multiple archive files." : "选择一个包含多个归档文件的目录",
"Multiple archive files" : "多个归档文件",
"No valid comics found" : "未找到有效的漫画",
"Select a directory which contains multiple archive files.": "选择一个包含多个归档文件的目录",
"Multiple archive files": "多个归档文件",
"No valid comics found": "未找到有效的漫画",
"Enable DNS Overrides": "启用DNS覆写",
"DNS Overrides": "DNS覆写",
"Custom Image Processing": "自定义图片处理",
@@ -342,12 +342,12 @@
"Replies": "回复",
"Follow Updates": "追更",
"Not Configured": "未配置",
"Choose a folder to follow updates." : "选择一个文件夹以追更",
"Choose a folder to follow updates.": "选择一个文件夹以追更",
"Choose Folder": "选择文件夹",
"No folders available": "没有可用的文件夹",
"Updating comics...": "更新漫画中...",
"Automatic update checking enabled." : "已启用自动更新检查",
"The app will check for updates at most once a day." : "APP将每天最多检查一次更新",
"Automatic update checking enabled.": "已启用自动更新检查",
"The app will check for updates at most once a day.": "APP将每天最多检查一次更新",
"Change Folder": "更改文件夹",
"Check Now": "立即检查",
"Updates": "更新",
@@ -360,7 +360,7 @@
"Disabled": "已禁用",
"Auto Sync Data": "自动同步数据",
"Mark all as read": "全部标记为已读",
"Do you want to mark all as read?" : "您要全部标记为已读吗?",
"Do you want to mark all as read?": "您要全部标记为已读吗?",
"Swipe down for previous chapter": "向下滑动查看上一章",
"Swipe up for next chapter": "向上滑动查看下一章",
"Initial Page": "初始页面",
@@ -378,7 +378,13 @@
"Page": "页面",
"Jump": "跳转",
"Copy Image": "复制图片",
"A valid WebDav directory URL": "有效的WebDav目录URL"
"A valid WebDav directory URL": "有效的WebDav目录URL",
"Shut Down": "关闭",
"Uploading data...": "正在上传数据...",
"Pages": "页数",
"Long press zoom position": "长按缩放位置",
"Press position": "按压位置",
"Screen center": "屏幕中心"
},
"zh_TW": {
"Home": "首頁",
@@ -520,18 +526,18 @@
"Block": "封鎖",
"Add new favorite to": "添加新收藏到",
"Move favorite after reading": "閱讀後移動收藏",
"Delete folder?" : "刪除資料夾?",
"Delete folder '@f' ?" : "刪除資料夾 '@f' ",
"Delete folder?": "刪除資料夾?",
"Delete folder '@f' ?": "刪除資料夾 '@f' ",
"Import from file": "從文件匯入",
"Failed to import": "匯入失敗",
"Cache Limit": "快取限制",
"Set Cache Limit": "設定快取限制",
"Size in MB": "大小MB",
"Select a directory which contains the comic directories." : "選擇一個包含漫畫資料夾的目錄",
"Select a directory which contains the comic directories.": "選擇一個包含漫畫資料夾的目錄",
"Help": "幫助",
"Export as cbz": "匯出為cbz",
"Select an archive file (cbz, zip, 7z, cb7)" : "選擇一個歸檔文件 (cbz, zip, 7z, cb7)",
"An archive file" : "一個歸檔文件",
"Select an archive file (cbz, zip, 7z, cb7)": "選擇一個歸檔文件 (cbz, zip, 7z, cb7)",
"An archive file": "一個歸檔文件",
"Fullscreen": "全螢幕",
"Exit": "退出",
"View more": "查看更多",
@@ -622,13 +628,13 @@
"Delete all unavailable local favorite items": "刪除所有無效的本機收藏",
"Deleted @a favorite items.": "已刪除 @a 條無效收藏",
"New version available": "有新版本可用",
"A new version is available. Do you want to update now?" : "有新版本可用。您要現在更新嗎?",
"A new version is available. Do you want to update now?": "有新版本可用。您要現在更新嗎?",
"No new version available": "沒有新版本可用",
"Export as pdf": "匯出為pdf",
"Export as epub": "匯出為epub",
"Aggregated Search": "聚合搜尋",
"No search results found": "未找到搜尋結果",
"Added @c comics to download queue." : "已添加 @c 本漫畫到下載佇列",
"Added @c comics to download queue.": "已添加 @c 本漫畫到下載佇列",
"Download started": "下載已開始",
"Click favorite": "點擊收藏",
"Local comic collection is not supported at present": "本機收藏暫不支援",
@@ -675,9 +681,9 @@
"End": "末尾",
"None": "無",
"View Detail": "查看詳情",
"Select a directory which contains multiple archive files." : "選擇一個包含多個歸檔文件的目錄",
"Multiple archive files" : "多個歸檔文件",
"No valid comics found" : "未找到有效的漫畫",
"Select a directory which contains multiple archive files.": "選擇一個包含多個歸檔文件的目錄",
"Multiple archive files": "多個歸檔文件",
"No valid comics found": "未找到有效的漫畫",
"Enable DNS Overrides": "啟用DNS覆寫",
"DNS Overrides": "DNS覆寫",
"Custom Image Processing": "自訂圖片處理",
@@ -723,12 +729,12 @@
"Replies": "回覆",
"Follow Updates": "追更",
"Not Configured": "未配置",
"Choose a folder to follow updates." : "選擇一個資料夾以追更",
"Choose a folder to follow updates.": "選擇一個資料夾以追更",
"Choose Folder": "選擇資料夾",
"No folders available": "沒有可用的資料夾",
"Updating comics...": "更新漫畫中...",
"Automatic update checking enabled." : "已啟用自動更新檢查",
"The app will check for updates at most once a day." : "APP將每天最多檢查一次更新",
"Automatic update checking enabled.": "已啟用自動更新檢查",
"The app will check for updates at most once a day.": "APP將每天最多檢查一次更新",
"Change Folder": "更改資料夾",
"Check Now": "立即檢查",
"Updates": "更新",
@@ -741,7 +747,7 @@
"Disabled": "已停用",
"Auto Sync Data": "自動同步資料",
"Mark all as read": "全部標記為已讀",
"Do you want to mark all as read?" : "您要全部標記為已讀嗎?",
"Do you want to mark all as read?": "您要全部標記為已讀嗎?",
"Swipe down for previous chapter": "向下滑動查看上一章",
"Swipe up for next chapter": "向上滑動查看下一章",
"Initial Page": "初始頁面",
@@ -759,6 +765,12 @@
"Page": "頁面",
"Jump": "跳轉",
"Copy Image": "複製圖片",
"A valid WebDav directory URL": "有效的WebDav目錄URL"
"A valid WebDav directory URL": "有效的WebDav目錄URL",
"Shut Down": "關閉",
"Uploading data...": "正在上傳數據...",
"Pages": "頁數",
"Long press zoom position": "長按縮放位置",
"Press position": "按壓位置",
"Screen center": "螢幕中心"
}
}

BIN
debian/gui/venera.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

@@ -163,3 +163,29 @@ class SliverLazyToBoxAdapter extends StatelessWidget {
]);
}
}
class SliverAnimatedVisibility extends StatelessWidget {
const SliverAnimatedVisibility({
super.key,
required this.visible,
required this.child,
});
final bool visible;
final Widget child;
@override
Widget build(BuildContext context) {
var child = visible ? this.child : const SizedBox.shrink();
return SliverToBoxAdapter(
child: AnimatedSize(
duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut,
alignment: Alignment.topCenter,
child: child,
),
);
}
}

View File

@@ -82,10 +82,7 @@ class _WindowFrameState extends State<WindowFrame> {
return;
}
}
windowManager.close().then((_) {
// Make sure the app exits when the window is closed.
exit(0);
});
exit(0);
}
@override
@@ -564,20 +561,19 @@ class _VirtualWindowFrameState extends State<VirtualWindowFrame>
Widget _buildVirtualWindowFrame(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(_isMaximized ? 0 : 8),
color: Colors.transparent,
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black.toOpacity(_isFocused ? 0.4 : 0.2),
offset: Offset(0.0, 2),
blurRadius: 4,
)
],
),
clipBehavior: Clip.antiAlias,
child: widget.child,
);
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(_isMaximized ? 0 : 8),
color: Colors.transparent,
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black.toOpacity(_isFocused ? 0.4 : 0.2),
blurRadius: 4,
)
],
),
clipBehavior: Clip.antiAlias,
child: widget.child,
);
}
@override

View File

@@ -13,7 +13,7 @@ export "widget_utils.dart";
export "context.dart";
class _App {
final version = "1.3.4";
final version = "1.3.5";
bool get isAndroid => Platform.isAndroid;
@@ -47,6 +47,7 @@ class _App {
late String dataPath;
late String cachePath;
String? externalStoragePath;
final rootNavigatorKey = GlobalKey<NavigatorState>();
@@ -77,6 +78,9 @@ class _App {
Future<void> init() async {
cachePath = (await getApplicationCacheDirectory()).path;
dataPath = (await getApplicationSupportDirectory()).path;
if (isAndroid) {
externalStoragePath = (await getExternalStorageDirectory())!.path;
}
}
Future<void> initComponents() async {

View File

@@ -161,6 +161,7 @@ class Settings with ChangeNotifier {
'cacheSize': 2048, // in MB
'downloadThreads': 5,
'enableLongPressToZoom': true,
'longPressZoomPosition': "press", // press, center
'checkUpdateOnStart': false,
'limitImageWidth': true,
'webdav': [], // empty means not configured

View File

@@ -342,7 +342,8 @@ class ComicChapters {
} else if (groupedChapters.isNotEmpty) {
return ComicChapters.grouped(groupedChapters);
} else {
throw ArgumentError("Empty chapter list");
// return a empty list.
return ComicChapters(chapters);
}
}

View File

@@ -461,6 +461,10 @@ class LocalManager with ChangeNotifier {
if (comic != null) {
return Directory(FilePath.join(path, comic.directory));
}
const comicDirectoryMaxLength = 128;
if (name.length > comicDirectoryMaxLength) {
name = name.substring(0, comicDirectoryMaxLength);
}
var dir = findValidDirectoryName(path, name);
return Directory(FilePath.join(path, dir)).create().then((value) => value);
}

View File

@@ -1,7 +1,9 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:venera/foundation/app.dart';
import 'package:venera/utils/ext.dart';
import 'package:venera/utils/io.dart';
class LogItem {
final LogLevel level;
@@ -28,9 +30,6 @@ class Log {
static bool ignoreLimitation = false;
/// only for debug
static const String? logFile = null;
static void printWarning(String text) {
debugPrint('\x1B[33m$text\x1B[0m');
}
@@ -39,7 +38,20 @@ class Log {
debugPrint('\x1B[31m$text\x1B[0m');
}
static IOSink? _file;
static void addLog(LogLevel level, String title, String content) {
if (_file == null) {
Directory dir;
if (App.isAndroid) {
dir = Directory(App.externalStoragePath!);
} else {
dir = Directory(App.dataPath);
}
var file = dir.joinFile("logs.txt");
_file = file.openWrite();
}
if (!ignoreLimitation && content.length > maxLogLength) {
content = "${content.substring(0, maxLogLength)}...";
}
@@ -62,8 +74,8 @@ class Log {
}
_logs.add(newLog);
if(logFile != null) {
File(logFile!).writeAsString(newLog.toString(), mode: FileMode.append);
if(_file != null) {
_file!.write(newLog.toString());
}
if (_logs.length > maxLogNumber) {
var res = _logs.remove(

View File

@@ -35,8 +35,14 @@ void main(List<String> args) {
}
await windowManager.setMinimumSize(const Size(500, 600));
var placement = await WindowPlacement.loadFromFile();
await placement.applyToWindow();
await windowManager.show();
if (App.isLinux) {
await windowManager.show();
await placement.applyToWindow();
} else {
await placement.applyToWindow();
await windowManager.show();
}
WindowPlacement.loop();
});
}

View File

@@ -461,7 +461,8 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
if (comic.tags.isEmpty &&
comic.uploader == null &&
comic.uploadTime == null &&
comic.uploadTime == null) {
comic.uploadTime == null &&
comic.maxPage == null) {
return const SliverPadding(padding: EdgeInsets.zero);
}
@@ -625,6 +626,13 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
buildTag(text: formatTime(comic.updateTime!)),
],
),
if (comic.maxPage != null)
buildWrap(
children: [
buildTag(text: 'Pages'.tl, isTitle: true),
buildTag(text: comic.maxPage.toString()),
],
),
const SizedBox(height: 12),
const Divider(),
],

View File

@@ -99,61 +99,67 @@ class _CommentsPageState extends State<CommentsPage> {
return Column(
children: [
Expanded(
child: ListView.builder(
primary: false,
padding: EdgeInsets.zero,
itemCount: _comments!.length + 2,
itemBuilder: (context, index) {
if (index == 0) {
if (widget.replyComment != null) {
return Column(
children: [
_CommentTile(
comment: widget.replyComment!,
source: widget.source,
comic: widget.data,
showAvatar: showAvatar,
showActions: false,
),
const SizedBox(height: 8),
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border(
top: BorderSide(
color: context.colorScheme.outlineVariant,
width: 0.6,
child: SmoothScrollProvider(
builder: (context, controller, physics) {
return ListView.builder(
controller: controller,
physics: physics,
primary: false,
padding: EdgeInsets.zero,
itemCount: _comments!.length + 2,
itemBuilder: (context, index) {
if (index == 0) {
if (widget.replyComment != null) {
return Column(
children: [
_CommentTile(
comment: widget.replyComment!,
source: widget.source,
comic: widget.data,
showAvatar: showAvatar,
showActions: false,
),
const SizedBox(height: 8),
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border(
top: BorderSide(
color: context.colorScheme.outlineVariant,
width: 0.6,
),
),
),
child: Text(
"Replies".tl,
style: ts.s18,
),
),
),
child: Text(
"Replies".tl,
style: ts.s18,
),
),
],
],
);
} else {
return const SizedBox();
}
}
index--;
if (index == _comments!.length) {
if (_page < (maxPage ?? _page + 1)) {
loadMore();
return const ListLoadingIndicator();
} else {
return const SizedBox();
}
}
return _CommentTile(
comment: _comments![index],
source: widget.source,
comic: widget.data,
showAvatar: showAvatar,
);
} else {
return const SizedBox();
}
}
index--;
if (index == _comments!.length) {
if (_page < (maxPage ?? _page + 1)) {
loadMore();
return const ListLoadingIndicator();
} else {
return const SizedBox();
}
}
return _CommentTile(
comment: _comments![index],
source: widget.source,
comic: widget.data,
showAvatar: showAvatar,
},
);
},
),

View File

@@ -52,7 +52,7 @@ class _SearchBar extends StatelessWidget {
Widget build(BuildContext context) {
return SliverToBoxAdapter(
child: Container(
height: 52,
height: App.isMobile ? 52 : 46,
width: double.infinity,
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
child: Material(

View File

@@ -343,10 +343,19 @@ class _GalleryModeState extends State<_GalleryMode>
}
var photoViewController = photoViewControllers[reader.page]!;
double target = photoViewController.getInitialScale!.call()! * 1.75;
var size = MediaQuery.of(context).size;
var size = reader.size;
Offset zoomPosition;
if (appdata.settings['longPressZoomPosition'] != 'center') {
zoomPosition = Offset(
size.width / 2 - location.dx,
size.height / 2 - location.dy,
);
} else {
zoomPosition = Offset(0, 0);
}
photoViewController.animateScale?.call(
target,
Offset(size.width / 2 - location.dx, size.height / 2 - location.dy),
zoomPosition,
);
isLongPressing = true;
}
@@ -608,6 +617,13 @@ class _ContinuousModeState extends State<_ContinuousMode>
}
bool onScaleUpdate([double? scale]) {
if (prepareToNextChapter || prepareToPrevChapter) {
setState(() {
prepareToPrevChapter = false;
prepareToNextChapter = false;
});
context.readerScaffold.setFloatingButton(0);
}
var isZoomedIn = (scale ?? photoViewController.scale) != 1.0;
if (isZoomedIn != this.isZoomedIn) {
setState(() {
@@ -731,7 +747,7 @@ class _ContinuousModeState extends State<_ContinuousMode>
}
Offset offset;
var sp = scrollController.position;
if (sp.pixels < sp.minScrollExtent || sp.pixels > sp.maxScrollExtent) {
if (sp.pixels <= sp.minScrollExtent || sp.pixels >= sp.maxScrollExtent) {
offset = Offset(value.dx, value.dy);
} else {
if (reader.mode == ReaderMode.continuousTopToBottom) {
@@ -759,7 +775,10 @@ class _ContinuousModeState extends State<_ContinuousMode>
delayedSetIsScrolling(false);
}
if (notification is ScrollUpdateNotification) {
var scale = photoViewController.scale ?? 1.0;
if (notification is ScrollUpdateNotification &&
(scale - 1).abs() < 0.05) {
if (!scrollController.hasClients) return false;
if (scrollController.position.pixels <=
scrollController.position.minScrollExtent &&
@@ -800,8 +819,8 @@ class _ContinuousModeState extends State<_ContinuousMode>
},
child: widget,
);
var width = MediaQuery.of(context).size.width;
var height = MediaQuery.of(context).size.height;
var width = reader.size.width;
var height = reader.size.height;
if (appdata.settings['limitImageWidth'] &&
width / height > 0.7 &&
reader.mode == ReaderMode.continuousTopToBottom) {
@@ -882,9 +901,19 @@ class _ContinuousModeState extends State<_ContinuousMode>
return;
}
double target = photoViewController.getInitialScale!.call()! * 1.75;
var size = reader.size;
Offset zoomPosition;
if (appdata.settings['longPressZoomPosition'] != 'center') {
zoomPosition = Offset(
size.width / 2 - location.dx,
size.height / 2 - location.dy,
);
} else {
zoomPosition = Offset(0, 0);
}
photoViewController.animateScale?.call(
target,
Offset(0, 0),
zoomPosition,
);
onScaleUpdate(target);
isLongPressing = true;

View File

@@ -309,6 +309,13 @@ class _ReaderState extends State<Reader>
}
return chapter == maxChapter;
}
/// Get the size of the reader.
/// The size is not always the same as the size of the screen.
Size get size {
var renderBox = context.findRenderObject() as RenderBox;
return renderBox.size;
}
}
abstract mixin class _ImagePerPageHandler {
@@ -363,8 +370,24 @@ abstract mixin class _VolumeListener {
bool toPrevPage();
bool toNextChapter();
bool toPrevChapter();
VolumeListener? volumeListener;
void onDown() {
if (!toNextPage()) {
toNextChapter();
}
}
void onUp() {
if (!toPrevPage()) {
toPrevChapter();
}
}
void handleVolumeEvent() {
if (!App.isAndroid) {
// Currently only support Android
@@ -374,8 +397,8 @@ abstract mixin class _VolumeListener {
volumeListener?.cancel();
}
volumeListener = VolumeListener(
onDown: toNextPage,
onUp: toPrevPage,
onDown: onDown,
onUp: onUp,
)..listen();
}

View File

@@ -48,6 +48,7 @@ class _ReaderSettingsState extends State<ReaderSettings> {
"continuousTopToBottom": "Continuous (Top to Bottom)".tl,
},
onChanged: () {
setState(() {});
var readerMode = appdata.settings['readerMode'];
if (readerMode?.toLowerCase().startsWith('continuous') ?? false) {
appdata.settings['readerScreenPicNumberForLandscape'] = 1;
@@ -68,67 +69,55 @@ class _ReaderSettingsState extends State<ReaderSettings> {
widget.onChanged?.call("autoPageTurningInterval");
},
).toSliver(),
SliverToBoxAdapter(
child: AbsorbPointer(
absorbing: (appdata.settings['readerMode']
?.toLowerCase()
.startsWith('continuous') ??
false),
child: AnimatedOpacity(
opacity: (appdata.settings['readerMode']
?.toLowerCase()
.startsWith('continuous') ??
false)
? 0.5
: 1.0,
duration: Duration(milliseconds: 300),
child: _SliderSetting(
title: "The number of pic in screen for landscape (Only Gallery Mode)".tl,
settingsIndex: "readerScreenPicNumberForLandscape",
interval: 1,
min: 1,
max: 5,
onChanged: () {
widget.onChanged?.call("readerScreenPicNumberForLandscape");
},
),
),
SliverAnimatedVisibility(
visible: appdata.settings['readerMode']!.startsWith('gallery'),
child: _SliderSetting(
title:
"The number of pic in screen for landscape (Only Gallery Mode)"
.tl,
settingsIndex: "readerScreenPicNumberForLandscape",
interval: 1,
min: 1,
max: 5,
onChanged: () {
widget.onChanged?.call("readerScreenPicNumberForLandscape");
},
),
),
SliverToBoxAdapter(
child: AbsorbPointer(
absorbing: (appdata.settings['readerMode']
?.toLowerCase()
.startsWith('continuous') ??
false),
child: AnimatedOpacity(
opacity: (appdata.settings['readerMode']
?.toLowerCase()
.startsWith('continuous') ??
false)
? 0.5
: 1.0,
duration: Duration(milliseconds: 300),
child: _SliderSetting(
title: "The number of pic in screen for portrait (Only Gallery Mode)".tl,
settingsIndex: "readerScreenPicNumberForPortrait",
interval: 1,
min: 1,
max: 5,
onChanged: () {
widget.onChanged?.call("readerScreenPicNumberForPortrait");
},
),
),
SliverAnimatedVisibility(
visible: appdata.settings['readerMode']!.startsWith('gallery'),
child: _SliderSetting(
title:
"The number of pic in screen for portrait (Only Gallery Mode)"
.tl,
settingsIndex: "readerScreenPicNumberForPortrait",
interval: 1,
min: 1,
max: 5,
onChanged: () {
widget.onChanged?.call("readerScreenPicNumberForPortrait");
},
),
),
_SwitchSetting(
title: 'Long press to zoom'.tl,
settingKey: 'enableLongPressToZoom',
onChanged: () {
setState(() {});
widget.onChanged?.call('enableLongPressToZoom');
},
).toSliver(),
SliverAnimatedVisibility(
visible: appdata.settings['enableLongPressToZoom'] == true,
child: SelectSetting(
title: "Long press zoom position".tl,
settingKey: "longPressZoomPosition",
optionTranslation: {
"press": "Press position".tl,
"center": "Screen center".tl,
},
),
),
_SwitchSetting(
title: 'Limit image width'.tl,
subtitle: 'When using Continuous(Top to Bottom) mode'.tl,

View File

@@ -1,4 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:venera/components/components.dart';
import 'package:venera/components/window_frame.dart';
import 'package:venera/foundation/app.dart';
import 'package:venera/foundation/appdata.dart';
import 'package:venera/foundation/comic_source/comic_source.dart';
@@ -10,6 +12,7 @@ import 'package:venera/utils/data.dart';
import 'package:venera/utils/ext.dart';
import 'package:webdav_client/webdav_client.dart' hide File;
import 'package:rhttp/rhttp.dart' as rhttp;
import 'package:venera/utils/translations.dart';
import 'io.dart';
@@ -20,6 +23,10 @@ class DataSync with ChangeNotifier {
}
LocalFavoritesManager().addListener(onDataChanged);
ComicSourceManager().addListener(onDataChanged);
Future.delayed(const Duration(seconds: 1), () {
var controller = WindowFrame.of(App.rootContext);
controller.addCloseListener(_handleWindowClose);
});
}
void onDataChanged() {
@@ -28,6 +35,28 @@ class DataSync with ChangeNotifier {
}
}
bool _handleWindowClose() {
if (_isUploading) {
_showWindowCloseDialog();
return false;
}
return true;
}
void _showWindowCloseDialog() async {
showLoadingDialog(
App.rootContext,
cancelButtonText: "Shut Down".tl,
onCancel: () => exit(0),
barrierDismissible: false,
message: "Uploading data...".tl,
);
while (_isUploading) {
await Future.delayed(const Duration(milliseconds: 50));
}
exit(0);
}
static DataSync? instance;
factory DataSync() => instance ?? (instance = DataSync._());
@@ -100,6 +129,7 @@ class DataSync with ChangeNotifier {
rhttp.ClientSettings(
proxySettings:
proxy == null ? null : rhttp.ProxySettings.proxy(proxy),
userAgent: "venera v${App.version}",
),
),
);
@@ -172,6 +202,7 @@ class DataSync with ChangeNotifier {
rhttp.ClientSettings(
proxySettings:
proxy == null ? null : rhttp.ProxySettings.proxy(proxy),
userAgent: "venera v${App.version}",
),
),
);

View File

@@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
@@ -132,25 +131,28 @@ extension DirectoryExtension on Directory {
}
/// Sanitize the file name. Remove invalid characters and trim the file name.
String sanitizeFileName(String fileName) {
String sanitizeFileName(String fileName, {String? dir, int? maxLength}) {
if (fileName.endsWith('.')) {
fileName = fileName.substring(0, fileName.length - 1);
}
const maxLength = 255;
var maxLength = 255;
if (dir != null) {
if (!dir.endsWith('/') && !dir.endsWith('\\')) {
dir = "$dir/";
}
maxLength -= dir.length;
}
final invalidChars = RegExp(r'[<>:"/\\|?*]');
final sanitizedFileName = fileName.replaceAll(invalidChars, ' ');
var trimmedFileName = sanitizedFileName.trim();
if (trimmedFileName.isEmpty) {
throw Exception('Invalid File Name: Empty length.');
}
while (true) {
final bytes = utf8.encode(trimmedFileName);
if (bytes.length > maxLength) {
trimmedFileName =
trimmedFileName.substring(0, trimmedFileName.length - 1);
} else {
break;
}
if (maxLength <= 0) {
throw Exception('Invalid File Name: Max length is less than 0.');
}
if (trimmedFileName.length > maxLength) {
trimmedFileName = trimmedFileName.substring(0, maxLength);
}
return trimmedFileName;
}

View File

@@ -80,6 +80,7 @@ static void my_application_activate(GApplication* application) {
gtk_window_set_default_size(window, 1280, 720);
GdkVisual* visual;
gtk_widget_set_app_paintable(GTK_WIDGET(window), TRUE);
gtk_window_set_decorated(window, FALSE);
visual = gdk_screen_get_rgba_visual(screen);
if (visual != NULL && gdk_screen_is_composited(screen)) {
gtk_widget_set_visual(GTK_WIDGET(window), visual);

View File

@@ -433,8 +433,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "690a03a954f1603e0149cfd479c8961b88f21336"
resolved-ref: "690a03a954f1603e0149cfd479c8961b88f21336"
ref: fe182cdf40e5fa6230f451bc1d643b860f610d13
resolved-ref: fe182cdf40e5fa6230f451bc1d643b860f610d13
url: "https://github.com/venera-app/flutter_saf"
source: git
version: "0.0.1"

View File

@@ -2,7 +2,7 @@ name: venera
description: "A comic app."
publish_to: 'none'
version: 1.3.4+134
version: 1.3.5+135
environment:
sdk: '>=3.6.0 <4.0.0'
@@ -72,7 +72,7 @@ dependencies:
flutter_saf:
git:
url: https://github.com/venera-app/flutter_saf
ref: 690a03a954f1603e0149cfd479c8961b88f21336
ref: fe182cdf40e5fa6230f451bc1d643b860f610d13
dynamic_color: ^1.7.0
shimmer_animation: ^2.1.0
flutter_memory_info: ^0.0.1