4 Commits

Author SHA1 Message Date
d308c2ac60 Add mouse scroll speed setting. Close #471 2025-08-24 19:52:24 +08:00
ac13807ef4 Add option to ignore certificate errors. Close #485 2025-08-24 19:19:40 +08:00
38a5b2b8cf refactor: comic specific settings 2025-08-24 19:04:42 +08:00
3a7c8d5e38 Fix toolbar overflow. 2025-08-24 17:57:35 +08:00
9 changed files with 343 additions and 321 deletions

View File

@@ -409,7 +409,9 @@
"Export logs": "导出日志", "Export logs": "导出日志",
"Clear specific reader settings for all comics": "清除所有漫画的特殊阅读设置", "Clear specific reader settings for all comics": "清除所有漫画的特殊阅读设置",
"Clear specific reader settings for this comic": "清除该漫画的特殊阅读设置", "Clear specific reader settings for this comic": "清除该漫画的特殊阅读设置",
"Enable comic specific settings": "为每本漫画保存特定设置" "Enable comic specific settings": "启用此漫画特定设置",
"Ignore Certificate Errors": "忽略证书错误",
"Mouse scroll speed": "鼠标滚动速度"
}, },
"zh_TW": { "zh_TW": {
"Home": "首頁", "Home": "首頁",
@@ -821,6 +823,8 @@
"Export logs": "匯出日誌", "Export logs": "匯出日誌",
"Clear specific reader settings for all comics": "清除所有漫畫的特殊閱讀設定", "Clear specific reader settings for all comics": "清除所有漫畫的特殊閱讀設定",
"Clear specific reader settings for this comic": "清除該漫畫的特殊閱讀設定", "Clear specific reader settings for this comic": "清除該漫畫的特殊閱讀設定",
"Enable comic specific settings": "為每本漫畫保存特定設定" "Enable comic specific settings": "啟用此漫畫特定設定",
"Ignore Certificate Errors": "忽略證書錯誤",
"Mouse scroll speed": "滑鼠滾動速度"
} }
} }

View File

@@ -152,7 +152,6 @@ class Settings with ChangeNotifier {
'blockedWords': [], 'blockedWords': [],
'defaultSearchTarget': null, 'defaultSearchTarget': null,
'autoPageTurningInterval': 5, // in seconds 'autoPageTurningInterval': 5, // in seconds
'enableComicSpecificSettings': false,
'readerMode': 'galleryLeftToRight', // values of [ReaderMode] 'readerMode': 'galleryLeftToRight', // values of [ReaderMode]
'readerScreenPicNumberForLandscape': 1, // 1 - 5 'readerScreenPicNumberForLandscape': 1, // 1 - 5
'readerScreenPicNumberForPortrait': 1, // 1 - 5 'readerScreenPicNumberForPortrait': 1, // 1 - 5
@@ -191,6 +190,8 @@ class Settings with ChangeNotifier {
'reverseChapterOrder': false, 'reverseChapterOrder': false,
'showSystemStatusBar': false, 'showSystemStatusBar': false,
'comicSpecificSettings': <String, Map<String, dynamic>>{}, 'comicSpecificSettings': <String, Map<String, dynamic>>{},
'ignoreBadCertificate': false,
'readerScrollSpeed': 1.0, // 0.5 - 3.0
}; };
operator [](String key) { operator [](String key) {
@@ -204,18 +205,19 @@ class Settings with ChangeNotifier {
} }
} }
bool haveComicSpecificSettings(String comicId, String sourceKey, String key) { void setEnabledComicSpecificSettings(String comicId, String sourceKey, bool enabled) {
return _data['comicSpecificSettings']?["$comicId@$sourceKey"]?.containsKey( setReaderSetting(comicId, sourceKey, "enabled", enabled);
key, }
) ??
false; bool isComicSpecificSettingsEnabled(String? comicId, String? sourceKey) {
if (comicId == null || sourceKey == null) {
return false;
}
return _data['comicSpecificSettings']["$comicId@$sourceKey"]?["enabled"] == true;
} }
dynamic getReaderSetting(String comicId, String sourceKey, String key) { dynamic getReaderSetting(String comicId, String sourceKey, String key) {
if (key == 'enableComicSpecificSettings') { if (!isComicSpecificSettingsEnabled(comicId, sourceKey)) {
return _data['enableComicSpecificSettings'];
}
if (_data['enableComicSpecificSettings'] == false) {
return _data[key]; return _data[key];
} }
return _data['comicSpecificSettings']["$comicId@$sourceKey"]?[key] ?? return _data['comicSpecificSettings']["$comicId@$sourceKey"]?[key] ??
@@ -228,16 +230,6 @@ class Settings with ChangeNotifier {
String key, String key,
dynamic value, dynamic value,
) { ) {
if (key == 'enableComicSpecificSettings') {
_data['enableComicSpecificSettings'] = value;
notifyListeners();
return;
}
if (_data['enableComicSpecificSettings'] == false) {
_data[key] = value;
notifyListeners();
return;
}
(_data['comicSpecificSettings'] as Map<String, dynamic>).putIfAbsent( (_data['comicSpecificSettings'] as Map<String, dynamic>).putIfAbsent(
"$comicId@$sourceKey", "$comicId@$sourceKey",
() => <String, dynamic>{}, () => <String, dynamic>{},
@@ -245,16 +237,8 @@ class Settings with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
void resetComicReaderSettings(String comicId, String sourceKey) { void resetComicReaderSettings(String key) {
final allComicSettings = _data['comicSpecificSettings'] as Map; (_data['comicSpecificSettings'] as Map).remove(key);
if (allComicSettings.containsKey("$comicId@$sourceKey")) {
allComicSettings.remove("$comicId@$sourceKey");
}
notifyListeners();
}
void resetAllComicReaderSettings() {
_data['comicSpecificSettings'] = <String, Map<String, dynamic>>{};
notifyListeners(); notifyListeners();
} }

View File

@@ -173,6 +173,7 @@ class RHttpAdapter implements HttpClientAdapter {
dnsSettings: rhttp.DnsSettings.static(overrides: _getOverrides()), dnsSettings: rhttp.DnsSettings.static(overrides: _getOverrides()),
tlsSettings: rhttp.TlsSettings( tlsSettings: rhttp.TlsSettings(
sni: appdata.settings['sni'] != false, sni: appdata.settings['sni'] != false,
verifyCertificates: appdata.settings['ignoreBadCertificate'] != true,
), ),
); );
} }

View File

@@ -131,11 +131,11 @@ class _ReaderGestureDetectorState extends AutomaticGlobalState<_ReaderGestureDet
} }
if (context.reader.mode.key.startsWith('gallery')) { if (context.reader.mode.key.startsWith('gallery')) {
if (forward) { if (forward) {
if (!context.reader.toNextPage(reader.cid, reader.type) && !context.reader.isLastChapterOfGroup) { if (!context.reader.toNextPage() && !context.reader.isLastChapterOfGroup) {
context.reader.toNextChapter(); context.reader.toNextChapter();
} }
} else { } else {
if (!context.reader.toPrevPage(reader.cid, reader.type) && !context.reader.isFirstChapterOfGroup) { if (!context.reader.toPrevPage() && !context.reader.isFirstChapterOfGroup) {
context.reader.toPrevChapter(); context.reader.toPrevChapter();
} }
} }
@@ -209,12 +209,12 @@ class _ReaderGestureDetectorState extends AutomaticGlobalState<_ReaderGestureDet
isBottom = true; isBottom = true;
} }
bool isCenter = false; bool isCenter = false;
var prev = () => context.reader.toPrevPage(context.reader.cid, context.reader.type); var prev = () => context.reader.toPrevPage();
var next = () => context.reader.toNextPage(context.reader.cid, context.reader.type); var next = () => context.reader.toNextPage();
if (appdata.settings.getReaderSetting( if (appdata.settings.getReaderSetting(
reader.cid, reader.type.sourceKey, 'reverseTapToTurnPages')) { reader.cid, reader.type.sourceKey, 'reverseTapToTurnPages')) {
prev = () => context.reader.toNextPage(context.reader.cid, context.reader.type); prev = () => context.reader.toNextPage();
next = () => context.reader.toPrevPage(context.reader.cid, context.reader.type); next = () => context.reader.toPrevPage();
} }
switch (context.reader.mode) { switch (context.reader.mode) {
case ReaderMode.galleryLeftToRight: case ReaderMode.galleryLeftToRight:

View File

@@ -138,14 +138,14 @@ class _GalleryModeState extends State<_GalleryMode>
/// [totalPages] is the total number of pages in the current chapter. /// [totalPages] is the total number of pages in the current chapter.
/// More than one images can be displayed on one page. /// More than one images can be displayed on one page.
int get totalPages { int get totalPages {
if (!reader.showSingleImageOnFirstPage(reader.cid, reader.type)) { if (!reader.showSingleImageOnFirstPage()) {
return (reader.images!.length / return (reader.images!.length /
reader.imagesPerPage(reader.cid, reader.type)) reader.imagesPerPage())
.ceil(); .ceil();
} else { } else {
return 1 + return 1 +
((reader.images!.length - 1) / ((reader.images!.length - 1) /
reader.imagesPerPage(reader.cid, reader.type)) reader.imagesPerPage())
.ceil(); .ceil();
} }
} }
@@ -169,8 +169,8 @@ class _GalleryModeState extends State<_GalleryMode>
/// Get the range of images for the given page. [page] is 1-based. /// Get the range of images for the given page. [page] is 1-based.
(int start, int end) getPageImagesRange(int page) { (int start, int end) getPageImagesRange(int page) {
var imagesPerPage = reader.imagesPerPage(reader.cid, reader.type); var imagesPerPage = reader.imagesPerPage();
if (reader.showSingleImageOnFirstPage(reader.cid, reader.type)) { if (reader.showSingleImageOnFirstPage()) {
if (page == 1) { if (page == 1) {
return (0, 1); return (0, 1);
} else { } else {
@@ -259,7 +259,7 @@ class _GalleryModeState extends State<_GalleryMode>
photoViewControllers[index] ??= PhotoViewController(); photoViewControllers[index] ??= PhotoViewController();
if (reader.imagesPerPage(reader.cid, reader.type) == 1 || if (reader.imagesPerPage() == 1 ||
pageImages.length == 1) { pageImages.length == 1) {
return PhotoViewGalleryPageOptions( return PhotoViewGalleryPageOptions(
filterQuality: FilterQuality.medium, filterQuality: FilterQuality.medium,
@@ -301,11 +301,11 @@ class _GalleryModeState extends State<_GalleryMode>
onPageChanged: (i) { onPageChanged: (i) {
if (i == 0) { if (i == 0) {
if (reader.isFirstChapterOfGroup || !reader.toPrevChapter()) { if (reader.isFirstChapterOfGroup || !reader.toPrevChapter()) {
reader.toPage(reader.cid, reader.type, 1); reader.toPage(1);
} }
} else if (i == totalPages + 1) { } else if (i == totalPages + 1) {
if (reader.isLastChapterOfGroup || !reader.toNextChapter()) { if (reader.isLastChapterOfGroup || !reader.toNextChapter()) {
reader.toPage(reader.cid, reader.type, totalPages); reader.toPage(totalPages);
} }
} else { } else {
reader.setPage(i); reader.setPage(i);
@@ -485,9 +485,9 @@ class _GalleryModeState extends State<_GalleryMode>
keyRepeatTimer = null; keyRepeatTimer = null;
} }
if (forward == true) { if (forward == true) {
reader.toPage(reader.cid, reader.type, reader.page + 1); reader.toPage(reader.page + 1);
} else if (forward == false) { } else if (forward == false) {
reader.toPage(reader.cid, reader.type, reader.page - 1); reader.toPage(reader.page - 1);
} }
} }
if (event is KeyRepeatEvent && keyRepeatTimer == null) { if (event is KeyRepeatEvent && keyRepeatTimer == null) {
@@ -500,9 +500,9 @@ class _GalleryModeState extends State<_GalleryMode>
timer.cancel(); timer.cancel();
return; return;
} else if (forward == true) { } else if (forward == true) {
reader.toPage(reader.cid, reader.type, reader.page + 1); reader.toPage(reader.page + 1);
} else if (forward == false) { } else if (forward == false) {
reader.toPage(reader.cid, reader.type, reader.page - 1); reader.toPage(reader.page - 1);
} }
}, },
); );
@@ -534,7 +534,7 @@ class _GalleryModeState extends State<_GalleryMode>
@override @override
String? getImageKeyByOffset(Offset offset) { String? getImageKeyByOffset(Offset offset) {
String? imageKey; String? imageKey;
if (reader.imagesPerPage(reader.cid, reader.type) == 1) { if (reader.imagesPerPage() == 1) {
imageKey = reader.images![reader.page - 1]; imageKey = reader.images![reader.page - 1];
} else { } else {
for (var imageState in imageStates) { for (var imageState in imageStates) {
@@ -638,27 +638,52 @@ class _ContinuousModeState extends State<_ContinuousMode>
cacheImages(page); cacheImages(page);
} }
double? futurePosition; double? _futurePosition;
void smoothTo(double offset) { void smoothTo(double offset) {
futurePosition ??= scrollController.offset; if (HardwareKeyboard.instance.isShiftPressed) {
if (futurePosition! > scrollController.position.maxScrollExtent &&
offset > 0) {
return;
} else if (futurePosition! < scrollController.position.minScrollExtent &&
offset < 0) {
return; return;
} }
futurePosition = futurePosition! + offset * 1.2; var currentLocation = scrollController.position.pixels;
futurePosition = futurePosition!.clamp( var old = _futurePosition;
_futurePosition ??= currentLocation;
double k = (_futurePosition! - currentLocation).abs() / 1600 + 1;
final customSpeed = appdata.settings.getReaderSetting(
context.reader.cid,
context.reader.type.sourceKey,
"readerScrollSpeed",
);
if (customSpeed is num) {
k *= customSpeed;
}
_futurePosition = _futurePosition! + offset * k;
var beforeOffset = (_futurePosition! - currentLocation).abs();
_futurePosition = _futurePosition!.clamp(
scrollController.position.minScrollExtent, scrollController.position.minScrollExtent,
scrollController.position.maxScrollExtent, scrollController.position.maxScrollExtent,
); );
scrollController.animateTo( var afterOffset = (_futurePosition! - currentLocation).abs();
futurePosition!, if (_futurePosition == old) return;
duration: const Duration(milliseconds: 200), var target = _futurePosition!;
var duration = const Duration(milliseconds: 160);
if (afterOffset < beforeOffset) {
duration = duration * (afterOffset / beforeOffset);
if (duration < Duration(milliseconds: 10)) {
duration = Duration(milliseconds: 10);
}
}
scrollController
.animateTo(
_futurePosition!,
duration: duration,
curve: Curves.linear, curve: Curves.linear,
); )
.then((_) {
var current = scrollController.position.pixels;
if (current == target && current == _futurePosition) {
_futurePosition = null;
}
});
} }
void onPointerSignal(PointerSignalEvent event) { void onPointerSignal(PointerSignalEvent event) {
@@ -787,7 +812,7 @@ class _ContinuousModeState extends State<_ContinuousMode>
disableScroll = true; disableScroll = true;
}); });
} }
futurePosition = null; _futurePosition = null;
if (_isMouseScrolling) { if (_isMouseScrolling) {
setState(() { setState(() {
_isMouseScrolling = false; _isMouseScrolling = false;
@@ -1009,7 +1034,7 @@ class _ContinuousModeState extends State<_ContinuousMode>
@override @override
void toPage(int page) { void toPage(int page) {
itemScrollController.jumpTo(index: page); itemScrollController.jumpTo(index: page);
futurePosition = null; _futurePosition = null;
} }
@override @override

View File

@@ -115,15 +115,17 @@ class _ReaderState extends State<Reader>
if (images == null) { if (images == null) {
return 1; return 1;
} }
if (!showSingleImageOnFirstPage(cid, type)) { if (!showSingleImageOnFirstPage()) {
return (images!.length / imagesPerPage(cid, type)).ceil(); return (images!.length / imagesPerPage()).ceil();
} else { } else {
return 1 + ((images!.length - 1) / imagesPerPage(cid, type)).ceil(); return 1 + ((images!.length - 1) / imagesPerPage()).ceil();
} }
} }
@override
ComicType get type => widget.type; ComicType get type => widget.type;
@override
String get cid => widget.cid; String get cid => widget.cid;
String get eid => widget.chapters?.ids.elementAtOrNull(chapter - 1) ?? '0'; String get eid => widget.chapters?.ids.elementAtOrNull(chapter - 1) ?? '0';
@@ -169,7 +171,7 @@ class _ReaderState extends State<Reader>
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
} }
if (appdata.settings.getReaderSetting(cid, type.sourceKey, 'enableTurnPageByVolumeKey')) { if (appdata.settings.getReaderSetting(cid, type.sourceKey, 'enableTurnPageByVolumeKey')) {
handleVolumeEvent(cid, type); handleVolumeEvent();
} }
setImageCacheSize(); setImageCacheSize();
Future.delayed(const Duration(milliseconds: 200), () { Future.delayed(const Duration(milliseconds: 200), () {
@@ -184,11 +186,11 @@ class _ReaderState extends State<Reader>
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
if (!_isInitialized) { if (!_isInitialized) {
initImagesPerPage(cid, type, widget.initialPage ?? 1); initImagesPerPage(widget.initialPage ?? 1);
_isInitialized = true; _isInitialized = true;
} else { } else {
// For orientation changed // For orientation changed
_checkImagesPerPageChange(cid, type); _checkImagesPerPageChange();
} }
initReaderWindow(); initReaderWindow();
} }
@@ -230,7 +232,7 @@ class _ReaderState extends State<Reader>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_checkImagesPerPageChange(cid, type); _checkImagesPerPageChange();
return KeyboardListener( return KeyboardListener(
focusNode: focusNode, focusNode: focusNode,
autofocus: true, autofocus: true,
@@ -275,13 +277,13 @@ class _ReaderState extends State<Reader>
history!.page = images?.length ?? 1; history!.page = images?.length ?? 1;
} else { } else {
/// Record the first image of the page /// Record the first image of the page
if (!showSingleImageOnFirstPage(cid, type) || imagesPerPage(cid, type) == 1) { if (!showSingleImageOnFirstPage() || imagesPerPage() == 1) {
history!.page = (page - 1) * imagesPerPage(cid, type) + 1; history!.page = (page - 1) * imagesPerPage() + 1;
} else { } else {
if (page == 1) { if (page == 1) {
history!.page = 1; history!.page = 1;
} else { } else {
history!.page = (page - 2) * imagesPerPage(cid, type) + 2; history!.page = (page - 2) * imagesPerPage() + 2;
} }
} }
} }
@@ -364,23 +366,27 @@ abstract mixin class _ImagePerPageHandler {
ReaderMode get mode; ReaderMode get mode;
void initImagesPerPage(String cid, ComicType type, int initialPage) { String get cid;
_lastImagesPerPage = imagesPerPage(cid, type);
ComicType get type;
void initImagesPerPage(int initialPage) {
_lastImagesPerPage = imagesPerPage();
_lastOrientation = isPortrait; _lastOrientation = isPortrait;
if (imagesPerPage(cid, type) != 1) { if (imagesPerPage() != 1) {
if (showSingleImageOnFirstPage(cid, type)) { if (showSingleImageOnFirstPage()) {
page = ((initialPage - 1) / imagesPerPage(cid, type)).ceil() + 1; page = ((initialPage - 1) / imagesPerPage()).ceil() + 1;
} else { } else {
page = (initialPage / imagesPerPage(cid, type)).ceil(); page = (initialPage / imagesPerPage()).ceil();
} }
} }
} }
bool showSingleImageOnFirstPage(String cid, ComicType type) => bool showSingleImageOnFirstPage() =>
appdata.settings.getReaderSetting(cid, type.sourceKey, 'showSingleImageOnFirstPage'); appdata.settings.getReaderSetting(cid, type.sourceKey, 'showSingleImageOnFirstPage');
/// The number of images displayed on one screen /// The number of images displayed on one screen
int imagesPerPage(String cid, ComicType type) { int imagesPerPage() {
if (mode.isContinuous) return 1; if (mode.isContinuous) return 1;
if (isPortrait) { if (isPortrait) {
return appdata.settings.getReaderSetting(cid, type.sourceKey, 'readerScreenPicNumberForPortrait') ?? 1; return appdata.settings.getReaderSetting(cid, type.sourceKey, 'readerScreenPicNumberForPortrait') ?? 1;
@@ -390,23 +396,21 @@ abstract mixin class _ImagePerPageHandler {
} }
/// Check if the number of images per page has changed /// Check if the number of images per page has changed
void _checkImagesPerPageChange(String cid, ComicType type) { void _checkImagesPerPageChange() {
int currentImagesPerPage = imagesPerPage(cid, type); int currentImagesPerPage = imagesPerPage();
bool currentOrientation = isPortrait; bool currentOrientation = isPortrait;
if (_lastImagesPerPage != currentImagesPerPage || _lastOrientation != currentOrientation) { if (_lastImagesPerPage != currentImagesPerPage || _lastOrientation != currentOrientation) {
_adjustPageForImagesPerPageChange( _adjustPageForImagesPerPageChange(_lastImagesPerPage, currentImagesPerPage);
cid, type, _lastImagesPerPage, currentImagesPerPage);
_lastImagesPerPage = currentImagesPerPage; _lastImagesPerPage = currentImagesPerPage;
_lastOrientation = currentOrientation; _lastOrientation = currentOrientation;
} }
} }
/// Adjust the page number when the number of images per page changes /// Adjust the page number when the number of images per page changes
void _adjustPageForImagesPerPageChange( void _adjustPageForImagesPerPageChange(int oldImagesPerPage, int newImagesPerPage) {
String cid, ComicType type, int oldImagesPerPage, int newImagesPerPage) {
int previousImageIndex = 1; int previousImageIndex = 1;
if (!showSingleImageOnFirstPage(cid, type) || oldImagesPerPage == 1) { if (!showSingleImageOnFirstPage() || oldImagesPerPage == 1) {
previousImageIndex = (page - 1) * oldImagesPerPage + 1; previousImageIndex = (page - 1) * oldImagesPerPage + 1;
} else { } else {
if (page == 1) { if (page == 1) {
@@ -418,7 +422,7 @@ abstract mixin class _ImagePerPageHandler {
int newPage; int newPage;
if (newImagesPerPage != 1) { if (newImagesPerPage != 1) {
if (showSingleImageOnFirstPage(cid, type)) { if (showSingleImageOnFirstPage()) {
newPage = ((previousImageIndex - 1) / newImagesPerPage).ceil() + 1; newPage = ((previousImageIndex - 1) / newImagesPerPage).ceil() + 1;
} else { } else {
newPage = (previousImageIndex / newImagesPerPage).ceil(); newPage = (previousImageIndex / newImagesPerPage).ceil();
@@ -432,9 +436,9 @@ abstract mixin class _ImagePerPageHandler {
} }
abstract mixin class _VolumeListener { abstract mixin class _VolumeListener {
bool toNextPage(String cid, ComicType type); bool toNextPage();
bool toPrevPage(String cid, ComicType type); bool toPrevPage();
bool toNextChapter(); bool toNextChapter();
@@ -442,19 +446,19 @@ abstract mixin class _VolumeListener {
VolumeListener? volumeListener; VolumeListener? volumeListener;
void onDown(String cid, ComicType type) { void onDown() {
if (!toNextPage(cid, type)) { if (!toNextPage()) {
toNextChapter(); toNextChapter();
} }
} }
void onUp(String cid, ComicType type) { void onUp() {
if (!toPrevPage(cid, type)) { if (!toPrevPage()) {
toPrevChapter(); toPrevChapter();
} }
} }
void handleVolumeEvent(String cid, ComicType type) { void handleVolumeEvent() {
if (!App.isAndroid) { if (!App.isAndroid) {
// Currently only support Android // Currently only support Android
return; return;
@@ -463,8 +467,8 @@ abstract mixin class _VolumeListener {
volumeListener?.cancel(); volumeListener?.cancel();
} }
volumeListener = VolumeListener( volumeListener = VolumeListener(
onDown: () => onDown(cid, type), onDown: onDown,
onUp: () => onUp(cid, type), onUp: onUp,
)..listen(); )..listen();
} }
@@ -494,6 +498,10 @@ abstract mixin class _ReaderLocation {
bool get isLoading; bool get isLoading;
String get cid;
ComicType get type;
void update(); void update();
bool enablePageAnimation(String cid, ComicType type) => appdata.settings.getReaderSetting(cid, type.sourceKey, 'enablePageAnimation'); bool enablePageAnimation(String cid, ComicType type) => appdata.settings.getReaderSetting(cid, type.sourceKey, 'enablePageAnimation');
@@ -515,18 +523,18 @@ abstract mixin class _ReaderLocation {
} }
/// Returns true if the page is changed /// Returns true if the page is changed
bool toNextPage(String cid, ComicType type) { bool toNextPage() {
return toPage(cid, type, page + 1); return toPage(page + 1);
} }
/// Returns true if the page is changed /// Returns true if the page is changed
bool toPrevPage(String cid, ComicType type) { bool toPrevPage() {
return toPage(cid, type, page - 1); return toPage(page - 1);
} }
int _animationCount = 0; int _animationCount = 0;
bool toPage(String cid, ComicType type, int page) { bool toPage(int page) {
if (_validatePage(page)) { if (_validatePage(page)) {
if (page == this.page && page != 1 && page != maxPage) { if (page == this.page && page != 1 && page != maxPage) {
return false; return false;
@@ -582,7 +590,7 @@ abstract mixin class _ReaderLocation {
if (page == maxPage) { if (page == maxPage) {
autoPageTurningTimer!.cancel(); autoPageTurningTimer!.cancel();
} }
toNextPage(cid, type); toNextPage();
}); });
} }
} }

View File

@@ -348,73 +348,11 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
text = "P${context.reader.page}"; text = "P${context.reader.page}";
} }
Widget child = SizedBox( final buttons = [
height: kBottomBarHeight,
child: Column(
children: [
const SizedBox(height: 8),
Row(
children: [
const SizedBox(width: 8),
IconButton.filledTonal(
onPressed: () => !isReversed
? context.reader.chapter > 1
? context.reader.toPrevChapter()
: context.reader.toPage(
context.reader.cid,
context.reader.type,
1,
)
: context.reader.chapter < context.reader.maxChapter
? context.reader.toNextChapter()
: context.reader.toPage(
context.reader.cid,
context.reader.type,
context.reader.maxPage,
),
icon: const Icon(Icons.first_page),
),
Expanded(child: buildSlider()),
IconButton.filledTonal(
onPressed: () => !isReversed
? context.reader.chapter < context.reader.maxChapter
? context.reader.toNextChapter()
: context.reader.toPage(
context.reader.cid,
context.reader.type,
context.reader.maxPage,
)
: context.reader.chapter > 1
? context.reader.toPrevChapter()
: context.reader.toPage(
context.reader.cid,
context.reader.type,
1,
),
icon: const Icon(Icons.last_page),
),
const SizedBox(width: 8),
],
),
Row(
children: [
const SizedBox(width: 16),
Container(
height: 24,
padding: const EdgeInsets.fromLTRB(6, 2, 6, 0),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.tertiaryContainer,
borderRadius: BorderRadius.circular(8),
),
child: Center(child: Text(text)),
),
const Spacer(),
Tooltip( Tooltip(
message: "Collect the image".tl, message: "Collect the image".tl,
child: IconButton( child: IconButton(
icon: Icon( icon: Icon(isLiked() ? Icons.favorite : Icons.favorite_border),
isLiked() ? Icons.favorite : Icons.favorite_border,
),
onPressed: addImageFavorite, onPressed: addImageFavorite,
), ),
), ),
@@ -462,9 +400,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
setState(() { setState(() {
rotation = null; rotation = null;
}); });
SystemChrome.setPreferredOrientations( SystemChrome.setPreferredOrientations(DeviceOrientation.values);
DeviceOrientation.values,
);
} }
}, },
), ),
@@ -501,13 +437,62 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
), ),
Tooltip( Tooltip(
message: "Share".tl, message: "Share".tl,
child: IconButton( child: IconButton(icon: const Icon(Icons.share), onPressed: share),
icon: const Icon(Icons.share),
onPressed: share,
), ),
];
Widget child = SizedBox(
height: kBottomBarHeight,
child: Column(
children: [
const SizedBox(height: 8),
Row(
children: [
const SizedBox(width: 8),
IconButton.filledTonal(
onPressed: () => !isReversed
? context.reader.chapter > 1
? context.reader.toPrevChapter()
: context.reader.toPage(1)
: context.reader.chapter < context.reader.maxChapter
? context.reader.toNextChapter()
: context.reader.toPage(context.reader.maxPage),
icon: const Icon(Icons.first_page),
), ),
Expanded(child: buildSlider()),
IconButton.filledTonal(
onPressed: () => !isReversed
? context.reader.chapter < context.reader.maxChapter
? context.reader.toNextChapter()
: context.reader.toPage(context.reader.maxPage)
: context.reader.chapter > 1
? context.reader.toPrevChapter()
: context.reader.toPage(1),
icon: const Icon(Icons.last_page),
),
const SizedBox(width: 8),
],
),
LayoutBuilder(
builder: (context, constrains) {
return Row(
children: [
if ((constrains.maxWidth - buttons.length * 42) > 80)
Container(
height: 24,
padding: const EdgeInsets.fromLTRB(6, 2, 6, 0),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.tertiaryContainer,
borderRadius: BorderRadius.circular(8),
),
child: Center(child: Text(text)),
).paddingLeft(16),
const Spacer(),
...buttons,
const SizedBox(width: 4), const SizedBox(width: 4),
], ],
);
},
), ),
], ],
), ),
@@ -545,11 +530,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
reversed: isReversed, reversed: isReversed,
divisions: (context.reader.maxPage - 1).clamp(2, 1 << 16), divisions: (context.reader.maxPage - 1).clamp(2, 1 << 16),
onChanged: (i) { onChanged: (i) {
context.reader.toPage( context.reader.toPage(i.toInt());
context.reader.cid,
context.reader.type,
i.toInt(),
);
}, },
); );
} }
@@ -659,10 +640,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
context.reader.type.sourceKey, context.reader.type.sourceKey,
key, key,
)) { )) {
context.reader.handleVolumeEvent( context.reader.handleVolumeEvent();
context.reader.cid,
context.reader.type,
);
} else { } else {
context.reader.stopVolumeEvent(); context.reader.stopVolumeEvent();
} }

View File

@@ -31,6 +31,10 @@ class DebugPageState extends State<DebugPage> {
}, },
actionTitle: 'Open'.tl, actionTitle: 'Open'.tl,
).toSliver(), ).toSliver(),
_SwitchSetting(
title: "Ignore Certificate Errors".tl,
settingKey: "ignoreBadCertificate",
).toSliver(),
SliverToBoxAdapter( SliverToBoxAdapter(
child: Column( child: Column(
children: [ children: [

View File

@@ -19,17 +19,57 @@ class ReaderSettings extends StatefulWidget {
class _ReaderSettingsState extends State<ReaderSettings> { class _ReaderSettingsState extends State<ReaderSettings> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final comicId = widget.comicId;
final sourceKey = widget.comicSource;
final key = "$comicId@$sourceKey";
bool isEnabledSpecificSettings =
comicId != null &&
appdata.settings.isComicSpecificSettingsEnabled(comicId, sourceKey);
return SmoothCustomScrollView( return SmoothCustomScrollView(
slivers: [ slivers: [
SliverAppbar(title: Text("Reading".tl)), SliverAppbar(title: Text("Reading".tl)),
if (comicId != null && sourceKey != null)
SliverMainAxisGroup(
slivers: [
SwitchListTile(
title: Text("Enable comic specific settings".tl),
value: isEnabledSpecificSettings,
onChanged: (b) {
setState(() {
appdata.settings.setEnabledComicSpecificSettings(
comicId,
sourceKey,
b,
);
});
},
).toSliver(),
if (isEnabledSpecificSettings)
Center(
child: TextButton(
onPressed: () {
setState(() {
appdata.settings.resetComicReaderSettings(key);
});
},
child: Text(
"Clear specific reader settings for this comic".tl,
),
),
).toSliver(),
Divider().toSliver(),
],
),
_SwitchSetting( _SwitchSetting(
title: "Tap to turn Pages".tl, title: "Tap to turn Pages".tl,
settingKey: "enableTapToTurnPages", settingKey: "enableTapToTurnPages",
onChanged: () { onChanged: () {
widget.onChanged?.call("enableTapToTurnPages"); widget.onChanged?.call("enableTapToTurnPages");
}, },
comicId: widget.comicId, comicId: isEnabledSpecificSettings ? widget.comicId : null,
comicSource: widget.comicSource, comicSource: isEnabledSpecificSettings ? widget.comicSource : null,
).toSliver(), ).toSliver(),
_SwitchSetting( _SwitchSetting(
title: "Reverse tap to turn Pages".tl, title: "Reverse tap to turn Pages".tl,
@@ -37,8 +77,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
onChanged: () { onChanged: () {
widget.onChanged?.call("reverseTapToTurnPages"); widget.onChanged?.call("reverseTapToTurnPages");
}, },
comicId: widget.comicId, comicId: isEnabledSpecificSettings ? widget.comicId : null,
comicSource: widget.comicSource, comicSource: isEnabledSpecificSettings ? widget.comicSource : null,
).toSliver(), ).toSliver(),
_SwitchSetting( _SwitchSetting(
title: "Page animation".tl, title: "Page animation".tl,
@@ -46,15 +86,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
onChanged: () { onChanged: () {
widget.onChanged?.call("enablePageAnimation"); widget.onChanged?.call("enablePageAnimation");
}, },
comicId: widget.comicId, comicId: isEnabledSpecificSettings ? widget.comicId : null,
comicSource: widget.comicSource, comicSource: isEnabledSpecificSettings ? widget.comicSource : null,
).toSliver(),
_SwitchSetting(
title: "Enable comic specific settings".tl,
settingKey: "enableComicSpecificSettings",
onChanged: () {
widget.onChanged?.call("enableComicSpecificSettings");
},
).toSliver(), ).toSliver(),
SelectSetting( SelectSetting(
title: "Reading mode".tl, title: "Reading mode".tl,
@@ -78,8 +111,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
} }
widget.onChanged?.call("readerMode"); widget.onChanged?.call("readerMode");
}, },
comicId: widget.comicId, comicId: isEnabledSpecificSettings ? widget.comicId : null,
comicSource: widget.comicSource, comicSource: isEnabledSpecificSettings ? widget.comicSource : null,
).toSliver(), ).toSliver(),
_SliderSetting( _SliderSetting(
title: "Auto page turning interval".tl, title: "Auto page turning interval".tl,
@@ -91,8 +124,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
setState(() {}); setState(() {});
widget.onChanged?.call("autoPageTurningInterval"); widget.onChanged?.call("autoPageTurningInterval");
}, },
comicId: widget.comicId, comicId: isEnabledSpecificSettings ? widget.comicId : null,
comicSource: widget.comicSource, comicSource: isEnabledSpecificSettings ? widget.comicSource : null,
).toSliver(), ).toSliver(),
SliverAnimatedVisibility( SliverAnimatedVisibility(
visible: appdata.settings['readerMode']!.startsWith('gallery'), visible: appdata.settings['readerMode']!.startsWith('gallery'),
@@ -108,8 +141,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
setState(() {}); setState(() {});
widget.onChanged?.call("readerScreenPicNumberForLandscape"); widget.onChanged?.call("readerScreenPicNumberForLandscape");
}, },
comicId: widget.comicId, comicId: isEnabledSpecificSettings ? widget.comicId : null,
comicSource: widget.comicSource, comicSource: isEnabledSpecificSettings ? widget.comicSource : null,
), ),
), ),
SliverAnimatedVisibility( SliverAnimatedVisibility(
@@ -125,8 +158,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
onChanged: () { onChanged: () {
widget.onChanged?.call("readerScreenPicNumberForPortrait"); widget.onChanged?.call("readerScreenPicNumberForPortrait");
}, },
comicId: widget.comicId, comicId: isEnabledSpecificSettings ? widget.comicId : null,
comicSource: widget.comicSource, comicSource: isEnabledSpecificSettings ? widget.comicSource : null,
), ),
), ),
SliverAnimatedVisibility( SliverAnimatedVisibility(
@@ -140,8 +173,23 @@ class _ReaderSettingsState extends State<ReaderSettings> {
onChanged: () { onChanged: () {
widget.onChanged?.call("showSingleImageOnFirstPage"); widget.onChanged?.call("showSingleImageOnFirstPage");
}, },
comicId: widget.comicId, comicId: isEnabledSpecificSettings ? widget.comicId : null,
comicSource: widget.comicSource, comicSource: isEnabledSpecificSettings ? widget.comicSource : null,
),
),
SliverAnimatedVisibility(
visible: appdata.settings['readerMode']!.startsWith('continuous'),
child: _SliderSetting(
title: "Mouse scroll speed".tl,
settingsIndex: "readerScrollSpeed",
interval: 0.1,
min: 0.5,
max: 3,
onChanged: () {
widget.onChanged?.call("readerScrollSpeed");
},
comicId: isEnabledSpecificSettings ? widget.comicId : null,
comicSource: isEnabledSpecificSettings ? widget.comicSource : null,
), ),
), ),
_SwitchSetting( _SwitchSetting(
@@ -151,8 +199,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
setState(() {}); setState(() {});
widget.onChanged?.call('enableDoubleTapToZoom'); widget.onChanged?.call('enableDoubleTapToZoom');
}, },
comicId: widget.comicId, comicId: isEnabledSpecificSettings ? widget.comicId : null,
comicSource: widget.comicSource, comicSource: isEnabledSpecificSettings ? widget.comicSource : null,
).toSliver(), ).toSliver(),
_SwitchSetting( _SwitchSetting(
title: 'Long press to zoom'.tl, title: 'Long press to zoom'.tl,
@@ -161,8 +209,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
setState(() {}); setState(() {});
widget.onChanged?.call('enableLongPressToZoom'); widget.onChanged?.call('enableLongPressToZoom');
}, },
comicId: widget.comicId, comicId: isEnabledSpecificSettings ? widget.comicId : null,
comicSource: widget.comicSource, comicSource: isEnabledSpecificSettings ? widget.comicSource : null,
).toSliver(), ).toSliver(),
SliverAnimatedVisibility( SliverAnimatedVisibility(
visible: appdata.settings['enableLongPressToZoom'] == true, visible: appdata.settings['enableLongPressToZoom'] == true,
@@ -173,8 +221,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
"press": "Press position".tl, "press": "Press position".tl,
"center": "Screen center".tl, "center": "Screen center".tl,
}, },
comicId: widget.comicId, comicId: isEnabledSpecificSettings ? widget.comicId : null,
comicSource: widget.comicSource, comicSource: isEnabledSpecificSettings ? widget.comicSource : null,
), ),
), ),
_SwitchSetting( _SwitchSetting(
@@ -184,8 +232,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
onChanged: () { onChanged: () {
widget.onChanged?.call('limitImageWidth'); widget.onChanged?.call('limitImageWidth');
}, },
comicId: widget.comicId, comicId: isEnabledSpecificSettings ? widget.comicId : null,
comicSource: widget.comicSource, comicSource: isEnabledSpecificSettings ? widget.comicSource : null,
).toSliver(), ).toSliver(),
if (App.isAndroid) if (App.isAndroid)
_SwitchSetting( _SwitchSetting(
@@ -194,8 +242,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
onChanged: () { onChanged: () {
widget.onChanged?.call('enableTurnPageByVolumeKey'); widget.onChanged?.call('enableTurnPageByVolumeKey');
}, },
comicId: widget.comicId, comicId: isEnabledSpecificSettings ? widget.comicId : null,
comicSource: widget.comicSource, comicSource: isEnabledSpecificSettings ? widget.comicSource : null,
).toSliver(), ).toSliver(),
_SwitchSetting( _SwitchSetting(
title: "Display time & battery info in reader".tl, title: "Display time & battery info in reader".tl,
@@ -203,8 +251,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
onChanged: () { onChanged: () {
widget.onChanged?.call("enableClockAndBatteryInfoInReader"); widget.onChanged?.call("enableClockAndBatteryInfoInReader");
}, },
comicId: widget.comicId, comicId: isEnabledSpecificSettings ? widget.comicId : null,
comicSource: widget.comicSource, comicSource: isEnabledSpecificSettings ? widget.comicSource : null,
).toSliver(), ).toSliver(),
_SwitchSetting( _SwitchSetting(
title: "Show system status bar".tl, title: "Show system status bar".tl,
@@ -212,8 +260,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
onChanged: () { onChanged: () {
widget.onChanged?.call("showSystemStatusBar"); widget.onChanged?.call("showSystemStatusBar");
}, },
comicId: widget.comicId, comicId: isEnabledSpecificSettings ? widget.comicId : null,
comicSource: widget.comicSource, comicSource: isEnabledSpecificSettings ? widget.comicSource : null,
).toSliver(), ).toSliver(),
SelectSetting( SelectSetting(
title: "Quick collect image".tl, title: "Quick collect image".tl,
@@ -229,8 +277,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
help: help:
"On the image browsing page, you can quickly collect images by sliding horizontally or vertically according to your reading mode" "On the image browsing page, you can quickly collect images by sliding horizontally or vertically according to your reading mode"
.tl, .tl,
comicId: widget.comicId, comicId: isEnabledSpecificSettings ? widget.comicId : null,
comicSource: widget.comicSource, comicSource: isEnabledSpecificSettings ? widget.comicSource : null,
).toSliver(), ).toSliver(),
_CallbackSetting( _CallbackSetting(
title: "Custom Image Processing".tl, title: "Custom Image Processing".tl,
@@ -243,8 +291,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
interval: 1, interval: 1,
min: 1, min: 1,
max: 16, max: 16,
comicId: widget.comicId, comicId: isEnabledSpecificSettings ? widget.comicId : null,
comicSource: widget.comicSource, comicSource: isEnabledSpecificSettings ? widget.comicSource : null,
).toSliver(), ).toSliver(),
_SwitchSetting( _SwitchSetting(
title: "Show Page Number".tl, title: "Show Page Number".tl,
@@ -252,39 +300,9 @@ class _ReaderSettingsState extends State<ReaderSettings> {
onChanged: () { onChanged: () {
widget.onChanged?.call("showPageNumberInReader"); widget.onChanged?.call("showPageNumberInReader");
}, },
comicId: widget.comicId, comicId: isEnabledSpecificSettings ? widget.comicId : null,
comicSource: widget.comicSource, comicSource: isEnabledSpecificSettings ? widget.comicSource : null,
).toSliver(), ).toSliver(),
// reset button
SliverToBoxAdapter(
child: TextButton(
onPressed: () {
if (widget.comicId == null) {
appdata.settings.resetAllComicReaderSettings();
} else {
var keys = appdata
.settings['comicSpecificSettings']["${widget.comicId}@${widget.comicSource}"]
?.keys;
appdata.settings.resetComicReaderSettings(
widget.comicId!,
widget.comicSource!,
);
if (keys != null) {
setState(() {});
for (var key in keys) {
widget.onChanged?.call(key);
}
}
}
},
child: Text(
(widget.comicId == null
? "Clear specific reader settings for all comics"
: "Clear specific reader settings for this comic")
.tl,
),
),
),
], ],
); );
} }