4 Commits

Author SHA1 Message Date
enximi
ce0d10aeb2 Add a feature to allow saving custom reader settings for each comic. (#459)
* Add a feature to allow saving custom reader settings for each  comic.

* Comic-specific settings disabled by default
2025-08-10 16:02:44 +08:00
角砂糖
0ac857ef9a Temp solution for hyper os multi window display issue (#467)
Temp solution for hyper os multi window display
2025-08-10 16:02:00 +08:00
3928f5afe7 Improve smooth scroll. Close #462 2025-08-03 17:05:31 +08:00
8a61a4750b Add avif format. 2025-08-03 16:40:25 +08:00
11 changed files with 691 additions and 355 deletions

View File

@@ -406,7 +406,10 @@
"Disable Length Limitation": "禁用长度限制", "Disable Length Limitation": "禁用长度限制",
"Only valid for this run": "仅对本次运行有效", "Only valid for this run": "仅对本次运行有效",
"Logs": "日志", "Logs": "日志",
"Export logs": "导出日志" "Export logs": "导出日志",
"Clear specific reader settings for all comics": "清除所有漫画的特殊阅读设置",
"Clear specific reader settings for this comic": "清除该漫画的特殊阅读设置",
"Enable comic specific settings": "为每本漫画保存特定设置"
}, },
"zh_TW": { "zh_TW": {
"Home": "首頁", "Home": "首頁",
@@ -815,6 +818,9 @@
"Disable Length Limitation": "禁用長度限制", "Disable Length Limitation": "禁用長度限制",
"Only valid for this run": "僅對本次運行有效", "Only valid for this run": "僅對本次運行有效",
"Logs": "日誌", "Logs": "日誌",
"Export logs": "匯出日誌" "Export logs": "匯出日誌",
"Clear specific reader settings for all comics": "清除所有漫畫的特殊閱讀設定",
"Clear specific reader settings for this comic": "清除該漫畫的特殊閱讀設定",
"Enable comic specific settings": "為每本漫畫保存特定設定"
} }
} }

View File

@@ -117,16 +117,25 @@ class _SmoothScrollProviderState extends State<SmoothScrollProvider> {
_futurePosition ??= currentLocation; _futurePosition ??= currentLocation;
double k = (_futurePosition! - currentLocation).abs() / 1600 + 1; double k = (_futurePosition! - currentLocation).abs() / 1600 + 1;
_futurePosition = _futurePosition! + pointerSignal.scrollDelta.dy * k; _futurePosition = _futurePosition! + pointerSignal.scrollDelta.dy * k;
var beforeOffset = (_futurePosition! - currentLocation).abs();
_futurePosition = _futurePosition!.clamp( _futurePosition = _futurePosition!.clamp(
_controller.position.minScrollExtent, _controller.position.minScrollExtent,
_controller.position.maxScrollExtent, _controller.position.maxScrollExtent,
); );
var afterOffset = (_futurePosition! - currentLocation).abs();
if (_futurePosition == old) return; if (_futurePosition == old) return;
var target = _futurePosition!; var target = _futurePosition!;
var duration = _fastAnimationDuration;
if (afterOffset < beforeOffset) {
duration = duration * (afterOffset / beforeOffset);
if (duration < Duration(milliseconds: 10)) {
duration = Duration(milliseconds: 10);
}
}
_controller _controller
.animateTo( .animateTo(
_futurePosition!, _futurePosition!,
duration: _fastAnimationDuration, duration: duration,
curve: Curves.linear, curve: Curves.linear,
) )
.then((_) { .then((_) {

View File

@@ -26,8 +26,7 @@ class Appdata with Init {
var data = jsonEncode(toJson()); var data = jsonEncode(toJson());
var file = File(FilePath.join(App.dataPath, 'appdata.json')); var file = File(FilePath.join(App.dataPath, 'appdata.json'));
await file.writeAsString(data); await file.writeAsString(data);
} } finally {
finally {
_isSavingData = false; _isSavingData = false;
} }
if (sync) { if (sync) {
@@ -57,10 +56,7 @@ class Appdata with Init {
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {'settings': settings._data, 'searchHistory': searchHistory};
'settings': settings._data,
'searchHistory': searchHistory,
};
} }
/// Following fields are related to device-specific data and should not be synced. /// Following fields are related to device-specific data and should not be synced.
@@ -95,8 +91,7 @@ class Appdata with Init {
try { try {
var file = File(FilePath.join(App.dataPath, 'implicitData.json')); var file = File(FilePath.join(App.dataPath, 'implicitData.json'));
await file.writeAsString(jsonEncode(implicitData)); await file.writeAsString(jsonEncode(implicitData));
} } finally {
finally {
_isSavingData = false; _isSavingData = false;
} }
} }
@@ -104,10 +99,7 @@ class Appdata with Init {
@override @override
Future<void> doInit() async { Future<void> doInit() async {
var dataPath = (await getApplicationSupportDirectory()).path; var dataPath = (await getApplicationSupportDirectory()).path;
var file = File(FilePath.join( var file = File(FilePath.join(dataPath, 'appdata.json'));
dataPath,
'appdata.json',
));
if (!await file.exists()) { if (!await file.exists()) {
return; return;
} }
@@ -119,8 +111,7 @@ class Appdata with Init {
} }
} }
searchHistory = List.from(json['searchHistory']); searchHistory = List.from(json['searchHistory']);
} } catch (e) {
catch(e) {
Log.error("Appdata", "Failed to load appdata", e); Log.error("Appdata", "Failed to load appdata", e);
Log.info("Appdata", "Resetting appdata"); Log.info("Appdata", "Resetting appdata");
file.deleteIgnoreError(); file.deleteIgnoreError();
@@ -130,8 +121,7 @@ class Appdata with Init {
if (await implicitDataFile.exists()) { if (await implicitDataFile.exists()) {
implicitData = jsonDecode(await implicitDataFile.readAsString()); implicitData = jsonDecode(await implicitDataFile.readAsString());
} }
} } catch (e) {
catch (e) {
Log.error("Appdata", "Failed to load implicit data", e); Log.error("Appdata", "Failed to load implicit data", e);
Log.info("Appdata", "Resetting implicit data"); Log.info("Appdata", "Resetting implicit data");
var implicitDataFile = File(FilePath.join(dataPath, 'implicitData.json')); var implicitDataFile = File(FilePath.join(dataPath, 'implicitData.json'));
@@ -162,6 +152,7 @@ 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
@@ -199,6 +190,7 @@ class Settings with ChangeNotifier {
'enableDoubleTapToZoom': true, 'enableDoubleTapToZoom': true,
'reverseChapterOrder': false, 'reverseChapterOrder': false,
'showSystemStatusBar': false, 'showSystemStatusBar': false,
'comicSpecificSettings': <String, Map<String, dynamic>>{},
}; };
operator [](String key) { operator [](String key) {
@@ -212,6 +204,60 @@ class Settings with ChangeNotifier {
} }
} }
bool haveComicSpecificSettings(String comicId, String sourceKey, String key) {
return _data['comicSpecificSettings']?["$comicId@$sourceKey"]?.containsKey(
key,
) ??
false;
}
dynamic getReaderSetting(String comicId, String sourceKey, String key) {
if (key == 'enableComicSpecificSettings') {
return _data['enableComicSpecificSettings'];
}
if (_data['enableComicSpecificSettings'] == false) {
return _data[key];
}
return _data['comicSpecificSettings']["$comicId@$sourceKey"]?[key] ??
_data[key];
}
void setReaderSetting(
String comicId,
String sourceKey,
String key,
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(
"$comicId@$sourceKey",
() => <String, dynamic>{},
)[key] = value;
notifyListeners();
}
void resetComicReaderSettings(String comicId, String sourceKey) {
final allComicSettings = _data['comicSpecificSettings'] as Map;
if (allComicSettings.containsKey("$comicId@$sourceKey")) {
allComicSettings.remove("$comicId@$sourceKey");
}
notifyListeners();
}
void resetAllComicReaderSettings() {
_data['comicSpecificSettings'] = <String, Map<String, dynamic>>{};
notifyListeners();
}
@override @override
String toString() { String toString() {
return _data.toString(); return _data.toString();
@@ -236,4 +282,5 @@ function processImage(image, cid, eid, page, sourceKey) {
} }
'''; ''';
const _defaultSourceListUrl = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/index.json"; const _defaultSourceListUrl =
"https://git.nyne.dev/nyne/venera-configs/raw/branch/main/index.json";

View File

@@ -237,6 +237,27 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
); );
}; };
if (widget != null) { if (widget != null) {
/// 如果无法检测到状态栏高度设定指定高度
/// https://github.com/flutter/flutter/issues/161086
var isPaddingCheckError =
MediaQuery.of(context).viewPadding.top <= 0 ||
MediaQuery.of(context).viewPadding.top > 50;
if (isPaddingCheckError) {
widget = MediaQuery(
data: MediaQuery.of(context).copyWith(
viewPadding: const EdgeInsets.only(
top: 15,
bottom: 15,
),
padding: const EdgeInsets.only(
top: 15,
bottom: 15,
),
),
child: widget);
}
widget = OverlayWidget(widget); widget = OverlayWidget(widget);
if (App.isDesktop) { if (App.isDesktop) {
widget = Shortcuts( widget = Shortcuts(

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() && !context.reader.isLastChapterOfGroup) { if (!context.reader.toNextPage(reader.cid, reader.type) && !context.reader.isLastChapterOfGroup) {
context.reader.toNextChapter(); context.reader.toNextChapter();
} }
} else { } else {
if (!context.reader.toPrevPage() && !context.reader.isFirstChapterOfGroup) { if (!context.reader.toPrevPage(reader.cid, reader.type) && !context.reader.isFirstChapterOfGroup) {
context.reader.toPrevChapter(); context.reader.toPrevChapter();
} }
} }
@@ -152,7 +152,8 @@ class _ReaderGestureDetectorState extends AutomaticGlobalState<_ReaderGestureDet
bool _dragInProgress = false; bool _dragInProgress = false;
bool get _enableDoubleTapToZoom => appdata.settings["enableDoubleTapToZoom"]; bool get _enableDoubleTapToZoom =>
appdata.settings.getReaderSetting(reader.cid, reader.type.sourceKey, 'enableDoubleTapToZoom');
void onTapUp(TapUpDetails event) { void onTapUp(TapUpDetails event) {
if (_longPressInProgress) { if (_longPressInProgress) {
@@ -190,7 +191,8 @@ class _ReaderGestureDetectorState extends AutomaticGlobalState<_ReaderGestureDet
} else if (context.readerScaffold.isOpen) { } else if (context.readerScaffold.isOpen) {
context.readerScaffold.openOrClose(); context.readerScaffold.openOrClose();
} else { } else {
if (appdata.settings['enableTapToTurnPages']) { if (appdata.settings.getReaderSetting(
reader.cid, reader.type.sourceKey, 'enableTapToTurnPages')) {
bool isLeft = false, isRight = false, isTop = false, isBottom = false; bool isLeft = false, isRight = false, isTop = false, isBottom = false;
final width = context.width; final width = context.width;
final height = context.height; final height = context.height;
@@ -207,11 +209,12 @@ class _ReaderGestureDetectorState extends AutomaticGlobalState<_ReaderGestureDet
isBottom = true; isBottom = true;
} }
bool isCenter = false; bool isCenter = false;
var prev = context.reader.toPrevPage; var prev = () => context.reader.toPrevPage(context.reader.cid, context.reader.type);
var next = context.reader.toNextPage; var next = () => context.reader.toNextPage(context.reader.cid, context.reader.type);
if (appdata.settings['reverseTapToTurnPages']) { if (appdata.settings.getReaderSetting(
prev = context.reader.toNextPage; reader.cid, reader.type.sourceKey, 'reverseTapToTurnPages')) {
next = context.reader.toPrevPage; prev = () => context.reader.toNextPage(context.reader.cid, context.reader.type);
next = () => context.reader.toPrevPage(context.reader.cid, context.reader.type);
} }
switch (context.reader.mode) { switch (context.reader.mode) {
case ReaderMode.galleryLeftToRight: case ReaderMode.galleryLeftToRight:

View File

@@ -32,10 +32,17 @@ class _ReaderImagesState extends State<_ReaderImages> {
inProgress = true; inProgress = true;
if (reader.type == ComicType.local || if (reader.type == ComicType.local ||
(LocalManager().isDownloaded( (LocalManager().isDownloaded(
reader.cid, reader.type, reader.chapter, reader.widget.chapters))) { reader.cid,
reader.type,
reader.chapter,
reader.widget.chapters,
))) {
try { try {
var images = await LocalManager() var images = await LocalManager().getImages(
.getImages(reader.cid, reader.type, reader.chapter); reader.cid,
reader.type,
reader.chapter,
);
setState(() { setState(() {
reader.images = images; reader.images = images;
reader.isLoading = false; reader.isLoading = false;
@@ -81,9 +88,7 @@ class _ReaderImagesState extends State<_ReaderImages> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (reader.isLoading) { if (reader.isLoading) {
load(); load();
return const Center( return const Center(child: CircularProgressIndicator());
child: CircularProgressIndicator(),
);
} else if (error != null) { } else if (error != null) {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
@@ -104,7 +109,8 @@ class _ReaderImagesState extends State<_ReaderImages> {
} else { } else {
if (reader.mode.isGallery) { if (reader.mode.isGallery) {
return _GalleryMode( return _GalleryMode(
key: Key('${reader.mode.key}_${reader.imagesPerPage}')); key: Key('${reader.mode.key}_${reader.imagesPerPage}'),
);
} else { } else {
return _ContinuousMode(key: Key(reader.mode.key)); return _ContinuousMode(key: Key(reader.mode.key));
} }
@@ -132,11 +138,15 @@ 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) { if (!reader.showSingleImageOnFirstPage(reader.cid, reader.type)) {
return (reader.images!.length / reader.imagesPerPage).ceil(); return (reader.images!.length /
reader.imagesPerPage(reader.cid, reader.type))
.ceil();
} else { } else {
return 1 + return 1 +
((reader.images!.length - 1) / reader.imagesPerPage).ceil(); ((reader.images!.length - 1) /
reader.imagesPerPage(reader.cid, reader.type))
.ceil();
} }
} }
@@ -159,19 +169,24 @@ 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) {
if (reader.showSingleImageOnFirstPage) { var imagesPerPage = reader.imagesPerPage(reader.cid, reader.type);
if (reader.showSingleImageOnFirstPage(reader.cid, reader.type)) {
if (page == 1) { if (page == 1) {
return (0, 1); return (0, 1);
} else { } else {
int startIndex = (page - 2) * reader.imagesPerPage + 1; int startIndex = (page - 2) * imagesPerPage + 1;
int endIndex = math.min( int endIndex = math.min(
startIndex + reader.imagesPerPage, reader.images!.length); startIndex + imagesPerPage,
reader.images!.length,
);
return (startIndex, endIndex); return (startIndex, endIndex);
} }
} else { } else {
int startIndex = (page - 1) * reader.imagesPerPage; int startIndex = (page - 1) * imagesPerPage;
int endIndex = math.min( int endIndex = math.min(
startIndex + reader.imagesPerPage, reader.images!.length); startIndex + imagesPerPage,
reader.images!.length,
);
return (startIndex, endIndex); return (startIndex, endIndex);
} }
} }
@@ -193,9 +208,9 @@ class _GalleryModeState extends State<_GalleryMode>
var (startIndex, endIndex) = getPageImagesRange(page); var (startIndex, endIndex) = getPageImagesRange(page);
for (int i = startIndex; i < endIndex; i++) { for (int i = startIndex; i < endIndex; i++) {
if (shouldPreCache) { if (shouldPreCache) {
_precacheImage(i+1, context); _precacheImage(i + 1, context);
} else { } else {
_preDownloadImage(i+1, context); _preDownloadImage(i + 1, context);
} }
} }
} }
@@ -217,16 +232,12 @@ class _GalleryModeState extends State<_GalleryMode>
var controller = photoViewControllers[reader.page]!; var controller = photoViewControllers[reader.page]!;
Offset value = event.delta; Offset value = event.delta;
if (isLongPressing) { if (isLongPressing) {
controller.updateMultiple( controller.updateMultiple(position: controller.position + value);
position: controller.position + value,
);
} }
} }
}, },
child: PhotoViewGallery.builder( child: PhotoViewGallery.builder(
backgroundDecoration: BoxDecoration( backgroundDecoration: BoxDecoration(color: context.colorScheme.surface),
color: context.colorScheme.surface,
),
reverse: reader.mode == ReaderMode.galleryRightToLeft, reverse: reader.mode == ReaderMode.galleryRightToLeft,
scrollDirection: reader.mode == ReaderMode.galleryTopToBottom scrollDirection: reader.mode == ReaderMode.galleryTopToBottom
? Axis.vertical ? Axis.vertical
@@ -239,14 +250,17 @@ class _GalleryModeState extends State<_GalleryMode>
); );
} else { } else {
var (startIndex, endIndex) = getPageImagesRange(index); var (startIndex, endIndex) = getPageImagesRange(index);
List<String> pageImages = List<String> pageImages = reader.images!.sublist(
reader.images!.sublist(startIndex, endIndex); startIndex,
endIndex,
);
cache(index); cache(index);
photoViewControllers[index] ??= PhotoViewController(); photoViewControllers[index] ??= PhotoViewController();
if (reader.imagesPerPage == 1 || pageImages.length == 1) { if (reader.imagesPerPage(reader.cid, reader.type) == 1 ||
pageImages.length == 1) {
return PhotoViewGalleryPageOptions( return PhotoViewGalleryPageOptions(
filterQuality: FilterQuality.medium, filterQuality: FilterQuality.medium,
controller: photoViewControllers[index], controller: photoViewControllers[index],
@@ -287,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(1); reader.toPage(reader.cid, reader.type, 1);
} }
} else if (i == totalPages + 1) { } else if (i == totalPages + 1) {
if (reader.isLastChapterOfGroup || !reader.toNextChapter()) { if (reader.isLastChapterOfGroup || !reader.toNextChapter()) {
reader.toPage(totalPages); reader.toPage(reader.cid, reader.type, totalPages);
} }
} else { } else {
reader.setPage(i); reader.setPage(i);
@@ -356,13 +370,16 @@ class _GalleryModeState extends State<_GalleryMode>
onInit: (state) => imageStates.add(state), onInit: (state) => imageStates.add(state),
onDispose: (state) => imageStates.remove(state), onDispose: (state) => imageStates.remove(state),
), ),
) ),
]; ];
} else { } else {
imageWidgets = images.map((imageKey) { imageWidgets = images.map((imageKey) {
startIndex++; startIndex++;
ImageProvider imageProvider = ImageProvider imageProvider = _createImageProviderFromKey(
_createImageProviderFromKey(imageKey, context, startIndex); imageKey,
context,
startIndex,
);
return Expanded( return Expanded(
child: ComicImage( child: ComicImage(
image: imageProvider, image: imageProvider,
@@ -423,10 +440,7 @@ class _GalleryModeState extends State<_GalleryMode>
} else { } else {
zoomPosition = Offset(0, 0); zoomPosition = Offset(0, 0);
} }
photoViewController.animateScale?.call( photoViewController.animateScale?.call(target, zoomPosition);
target,
zoomPosition,
);
isLongPressing = true; isLongPressing = true;
} }
@@ -471,14 +485,14 @@ class _GalleryModeState extends State<_GalleryMode>
keyRepeatTimer = null; keyRepeatTimer = null;
} }
if (forward == true) { if (forward == true) {
reader.toPage(reader.page+1); reader.toPage(reader.cid, reader.type, reader.page + 1);
} else if (forward == false) { } else if (forward == false) {
reader.toPage(reader.page-1); reader.toPage(reader.cid, reader.type, reader.page - 1);
} }
} }
if (event is KeyRepeatEvent && keyRepeatTimer == null) { if (event is KeyRepeatEvent && keyRepeatTimer == null) {
keyRepeatTimer = Timer.periodic( keyRepeatTimer = Timer.periodic(
reader.enablePageAnimation reader.enablePageAnimation(reader.cid, reader.type)
? const Duration(milliseconds: 200) ? const Duration(milliseconds: 200)
: const Duration(milliseconds: 50), : const Duration(milliseconds: 50),
(timer) { (timer) {
@@ -486,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.page+1); reader.toPage(reader.cid, reader.type, reader.page + 1);
} else if (forward == false) { } else if (forward == false) {
reader.toPage(reader.page-1); reader.toPage(reader.cid, reader.type, reader.page - 1);
} }
}, },
); );
@@ -512,15 +526,15 @@ class _GalleryModeState extends State<_GalleryMode>
return await File(imageKey.substring(7)).readAsBytes(); return await File(imageKey.substring(7)).readAsBytes();
} else { } else {
return (await CacheManager().findCache( return (await CacheManager().findCache(
"$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}"))! "$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}",
.readAsBytes(); ))!.readAsBytes();
} }
} }
@override @override
String? getImageKeyByOffset(Offset offset) { String? getImageKeyByOffset(Offset offset) {
String? imageKey; String? imageKey;
if (reader.imagesPerPage == 1) { if (reader.imagesPerPage(reader.cid, reader.type) == 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) {
@@ -538,7 +552,7 @@ const Set<PointerDeviceKind> _kTouchLikeDeviceTypes = <PointerDeviceKind>{
PointerDeviceKind.mouse, PointerDeviceKind.mouse,
PointerDeviceKind.stylus, PointerDeviceKind.stylus,
PointerDeviceKind.invertedStylus, PointerDeviceKind.invertedStylus,
PointerDeviceKind.unknown PointerDeviceKind.unknown,
}; };
const double _kChangeChapterOffset = 160; const double _kChangeChapterOffset = 160;
@@ -673,10 +687,12 @@ class _ContinuousModeState extends State<_ContinuousMode>
void onScroll() { void onScroll() {
if (prepareToPrevChapter) { if (prepareToPrevChapter) {
jumpToNextChapter = false; jumpToNextChapter = false;
jumpToPrevChapter = scrollController.offset < jumpToPrevChapter =
scrollController.offset <
scrollController.position.minScrollExtent - _kChangeChapterOffset; scrollController.position.minScrollExtent - _kChangeChapterOffset;
} else if (prepareToNextChapter) { } else if (prepareToNextChapter) {
jumpToNextChapter = scrollController.offset > jumpToNextChapter =
scrollController.offset >
scrollController.position.maxScrollExtent + _kChangeChapterOffset; scrollController.position.maxScrollExtent + _kChangeChapterOffset;
jumpToPrevChapter = false; jumpToPrevChapter = false;
} }
@@ -750,8 +766,10 @@ class _ContinuousModeState extends State<_ContinuousMode>
), ),
); );
}, },
scrollBehavior: const MaterialScrollBehavior() scrollBehavior: const MaterialScrollBehavior().copyWith(
.copyWith(scrollbars: false, dragDevices: _kTouchLikeDeviceTypes), scrollbars: false,
dragDevices: _kTouchLikeDeviceTypes,
),
); );
widget = Stack( widget = Stack(
@@ -895,20 +913,14 @@ class _ContinuousModeState extends State<_ContinuousMode>
} }
return PhotoView.customChild( return PhotoView.customChild(
backgroundDecoration: BoxDecoration( backgroundDecoration: BoxDecoration(color: context.colorScheme.surface),
color: context.colorScheme.surface,
),
childSize: Size(width, height), childSize: Size(width, height),
minScale: 1.0, minScale: 1.0,
maxScale: 2.5, maxScale: 2.5,
strictScale: true, strictScale: true,
controller: photoViewController, controller: photoViewController,
onScaleUpdate: onScaleUpdate, onScaleUpdate: onScaleUpdate,
child: SizedBox( child: SizedBox(width: width, height: height, child: widget),
width: width,
height: height,
child: widget,
),
); );
} }
@@ -978,10 +990,7 @@ class _ContinuousModeState extends State<_ContinuousMode>
} else { } else {
zoomPosition = Offset(0, 0); zoomPosition = Offset(0, 0);
} }
photoViewController.animateScale?.call( photoViewController.animateScale?.call(target, zoomPosition);
target,
zoomPosition,
);
onScaleUpdate(target); onScaleUpdate(target);
isLongPressing = true; isLongPressing = true;
} }
@@ -1069,8 +1078,8 @@ class _ContinuousModeState extends State<_ContinuousMode>
return await File(imageKey.substring(7)).readAsBytes(); return await File(imageKey.substring(7)).readAsBytes();
} else { } else {
return (await CacheManager().findCache( return (await CacheManager().findCache(
"$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}"))! "$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}",
.readAsBytes(); ))!.readAsBytes();
} }
} }
@@ -1114,10 +1123,7 @@ void _precacheImage(int page, BuildContext context) {
if (page <= 0 || page > context.reader.images!.length) { if (page <= 0 || page > context.reader.images!.length) {
return; return;
} }
precacheImage( precacheImage(_createImageProvider(page, context), context);
_createImageProvider(page, context),
context,
);
} }
/// [_preDownloadImage] is used to download the image for the given page. /// [_preDownloadImage] is used to download the image for the given page.
@@ -1138,10 +1144,7 @@ void _preDownloadImage(int page, BuildContext context) {
} }
class _SwipeChangeChapterProgress extends StatefulWidget { class _SwipeChangeChapterProgress extends StatefulWidget {
const _SwipeChangeChapterProgress({ const _SwipeChangeChapterProgress({this.controller, required this.isPrev});
this.controller,
required this.isPrev,
});
final ScrollController? controller; final ScrollController? controller;
@@ -1258,7 +1261,12 @@ class _ProgressPainter extends CustomPainter {
paint.color = color; paint.color = color;
canvas.drawRRect( canvas.drawRRect(
RRect.fromLTRBR( RRect.fromLTRBR(
0, 0, size.width * value, size.height, Radius.circular(16)), 0,
0,
size.width * value,
size.height,
Radius.circular(16),
),
paint, paint,
); );
} }

View File

@@ -115,10 +115,10 @@ class _ReaderState extends State<Reader>
if (images == null) { if (images == null) {
return 1; return 1;
} }
if (!showSingleImageOnFirstPage) { if (!showSingleImageOnFirstPage(cid, type)) {
return (images!.length / imagesPerPage).ceil(); return (images!.length / imagesPerPage(cid, type)).ceil();
} else { } else {
return 1 + ((images!.length - 1) / imagesPerPage).ceil(); return 1 + ((images!.length - 1) / imagesPerPage(cid, type)).ceil();
} }
} }
@@ -162,13 +162,14 @@ class _ReaderState extends State<Reader>
if (widget.initialPage != null) { if (widget.initialPage != null) {
page = widget.initialPage!; page = widget.initialPage!;
} }
mode = ReaderMode.fromKey(appdata.settings['readerMode']); // mode = ReaderMode.fromKey(appdata.settings['readerMode']);
mode = ReaderMode.fromKey(appdata.settings.getReaderSetting(cid, type.sourceKey, 'readerMode'));
history = widget.history; history = widget.history;
if (!appdata.settings['showSystemStatusBar']) { if (!appdata.settings.getReaderSetting(cid, type.sourceKey, 'showSystemStatusBar')) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
} }
if (appdata.settings['enableTurnPageByVolumeKey']) { if (appdata.settings.getReaderSetting(cid, type.sourceKey, 'enableTurnPageByVolumeKey')) {
handleVolumeEvent(); handleVolumeEvent(cid, type);
} }
setImageCacheSize(); setImageCacheSize();
Future.delayed(const Duration(milliseconds: 200), () { Future.delayed(const Duration(milliseconds: 200), () {
@@ -183,11 +184,11 @@ class _ReaderState extends State<Reader>
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
if (!_isInitialized) { if (!_isInitialized) {
initImagesPerPage(widget.initialPage ?? 1); initImagesPerPage(cid, type, widget.initialPage ?? 1);
_isInitialized = true; _isInitialized = true;
} else { } else {
// For orientation changed // For orientation changed
_checkImagesPerPageChange(); _checkImagesPerPageChange(cid, type);
} }
initReaderWindow(); initReaderWindow();
} }
@@ -229,7 +230,7 @@ class _ReaderState extends State<Reader>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_checkImagesPerPageChange(); _checkImagesPerPageChange(cid, type);
return KeyboardListener( return KeyboardListener(
focusNode: focusNode, focusNode: focusNode,
autofocus: true, autofocus: true,
@@ -274,13 +275,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 || imagesPerPage == 1) { if (!showSingleImageOnFirstPage(cid, type) || imagesPerPage(cid, type) == 1) {
history!.page = (page - 1) * imagesPerPage + 1; history!.page = (page - 1) * imagesPerPage(cid, type) + 1;
} else { } else {
if (page == 1) { if (page == 1) {
history!.page = 1; history!.page = 1;
} else { } else {
history!.page = (page - 2) * imagesPerPage + 2; history!.page = (page - 2) * imagesPerPage(cid, type) + 2;
} }
} }
} }
@@ -363,39 +364,39 @@ abstract mixin class _ImagePerPageHandler {
ReaderMode get mode; ReaderMode get mode;
void initImagesPerPage(int initialPage) { void initImagesPerPage(String cid, ComicType type, int initialPage) {
_lastImagesPerPage = imagesPerPage; _lastImagesPerPage = imagesPerPage(cid, type);
_lastOrientation = isPortrait; _lastOrientation = isPortrait;
if (imagesPerPage != 1) { if (imagesPerPage(cid, type) != 1) {
if (showSingleImageOnFirstPage) { if (showSingleImageOnFirstPage(cid, type)) {
page = ((initialPage - 1) / imagesPerPage).ceil() + 1; page = ((initialPage - 1) / imagesPerPage(cid, type)).ceil() + 1;
} else { } else {
page = (initialPage / imagesPerPage).ceil(); page = (initialPage / imagesPerPage(cid, type)).ceil();
} }
} }
} }
bool get showSingleImageOnFirstPage => bool showSingleImageOnFirstPage(String cid, ComicType type) =>
appdata.settings["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 get imagesPerPage { int imagesPerPage(String cid, ComicType type) {
if (mode.isContinuous) return 1; if (mode.isContinuous) return 1;
if (isPortrait) { if (isPortrait) {
return appdata.settings['readerScreenPicNumberForPortrait'] ?? 1; return appdata.settings.getReaderSetting(cid, type.sourceKey, 'readerScreenPicNumberForPortrait') ?? 1;
} else { } else {
return appdata.settings['readerScreenPicNumberForLandscape'] ?? 1; return appdata.settings.getReaderSetting(cid, type.sourceKey, 'readerScreenPicNumberForLandscape') ?? 1;
} }
} }
/// Check if the number of images per page has changed /// Check if the number of images per page has changed
void _checkImagesPerPageChange() { void _checkImagesPerPageChange(String cid, ComicType type) {
int currentImagesPerPage = imagesPerPage; int currentImagesPerPage = imagesPerPage(cid, type);
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;
} }
@@ -403,9 +404,9 @@ abstract mixin class _ImagePerPageHandler {
/// 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 || oldImagesPerPage == 1) { if (!showSingleImageOnFirstPage(cid, type) || oldImagesPerPage == 1) {
previousImageIndex = (page - 1) * oldImagesPerPage + 1; previousImageIndex = (page - 1) * oldImagesPerPage + 1;
} else { } else {
if (page == 1) { if (page == 1) {
@@ -417,7 +418,7 @@ abstract mixin class _ImagePerPageHandler {
int newPage; int newPage;
if (newImagesPerPage != 1) { if (newImagesPerPage != 1) {
if (showSingleImageOnFirstPage) { if (showSingleImageOnFirstPage(cid, type)) {
newPage = ((previousImageIndex - 1) / newImagesPerPage).ceil() + 1; newPage = ((previousImageIndex - 1) / newImagesPerPage).ceil() + 1;
} else { } else {
newPage = (previousImageIndex / newImagesPerPage).ceil(); newPage = (previousImageIndex / newImagesPerPage).ceil();
@@ -431,9 +432,9 @@ abstract mixin class _ImagePerPageHandler {
} }
abstract mixin class _VolumeListener { abstract mixin class _VolumeListener {
bool toNextPage(); bool toNextPage(String cid, ComicType type);
bool toPrevPage(); bool toPrevPage(String cid, ComicType type);
bool toNextChapter(); bool toNextChapter();
@@ -441,19 +442,19 @@ abstract mixin class _VolumeListener {
VolumeListener? volumeListener; VolumeListener? volumeListener;
void onDown() { void onDown(String cid, ComicType type) {
if (!toNextPage()) { if (!toNextPage(cid, type)) {
toNextChapter(); toNextChapter();
} }
} }
void onUp() { void onUp(String cid, ComicType type) {
if (!toPrevPage()) { if (!toPrevPage(cid, type)) {
toPrevChapter(); toPrevChapter();
} }
} }
void handleVolumeEvent() { void handleVolumeEvent(String cid, ComicType type) {
if (!App.isAndroid) { if (!App.isAndroid) {
// Currently only support Android // Currently only support Android
return; return;
@@ -462,8 +463,8 @@ abstract mixin class _VolumeListener {
volumeListener?.cancel(); volumeListener?.cancel();
} }
volumeListener = VolumeListener( volumeListener = VolumeListener(
onDown: onDown, onDown: () => onDown(cid, type),
onUp: onUp, onUp: () => onUp(cid, type),
)..listen(); )..listen();
} }
@@ -495,7 +496,7 @@ abstract mixin class _ReaderLocation {
void update(); void update();
bool get enablePageAnimation => appdata.settings['enablePageAnimation']; bool enablePageAnimation(String cid, ComicType type) => appdata.settings.getReaderSetting(cid, type.sourceKey, 'enablePageAnimation');
_ImageViewController? _imageViewController; _ImageViewController? _imageViewController;
@@ -514,25 +515,25 @@ abstract mixin class _ReaderLocation {
} }
/// Returns true if the page is changed /// Returns true if the page is changed
bool toNextPage() { bool toNextPage(String cid, ComicType type) {
return toPage(page + 1); return toPage(cid, type, page + 1);
} }
/// Returns true if the page is changed /// Returns true if the page is changed
bool toPrevPage() { bool toPrevPage(String cid, ComicType type) {
return toPage(page - 1); return toPage(cid, type, page - 1);
} }
int _animationCount = 0; int _animationCount = 0;
bool toPage(int page) { bool toPage(String cid, ComicType type, 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;
} }
this.page = page; this.page = page;
update(); update();
if (enablePageAnimation) { if (enablePageAnimation(cid, type)) {
_animationCount++; _animationCount++;
_imageViewController!.animateToPage(page).then((_) { _imageViewController!.animateToPage(page).then((_) {
_animationCount--; _animationCount--;
@@ -571,17 +572,17 @@ abstract mixin class _ReaderLocation {
Timer? autoPageTurningTimer; Timer? autoPageTurningTimer;
void autoPageTurning() { void autoPageTurning(String cid, ComicType type) {
if (autoPageTurningTimer != null) { if (autoPageTurningTimer != null) {
autoPageTurningTimer!.cancel(); autoPageTurningTimer!.cancel();
autoPageTurningTimer = null; autoPageTurningTimer = null;
} else { } else {
int interval = appdata.settings['autoPageTurningInterval']; int interval = appdata.settings.getReaderSetting(cid, type.sourceKey, 'autoPageTurningInterval');
autoPageTurningTimer = Timer.periodic(Duration(seconds: interval), (_) { autoPageTurningTimer = Timer.periodic(Duration(seconds: interval), (_) {
if (page == maxPage) { if (page == maxPage) {
autoPageTurningTimer!.cancel(); autoPageTurningTimer!.cancel();
} }
toNextPage(); toNextPage(cid, type);
}); });
} }
} }

View File

@@ -128,9 +128,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Stack( return Stack(
children: [ children: [
Positioned.fill( Positioned.fill(child: widget.child),
child: widget.child,
),
if (appdata.settings['showPageNumberInReader'] == true) if (appdata.settings['showPageNumberInReader'] == true)
buildPageInfoText(), buildPageInfoText(),
buildStatusInfo(), buildStatusInfo(),
@@ -168,10 +166,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: context.colorScheme.surface.toOpacity(0.92), color: context.colorScheme.surface.toOpacity(0.92),
border: Border( border: Border(
bottom: BorderSide( bottom: BorderSide(color: Colors.grey.toOpacity(0.5), width: 0.5),
color: Colors.grey.toOpacity(0.5),
width: 0.5,
),
), ),
), ),
child: Row( child: Row(
@@ -217,7 +212,8 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
if (context.reader.images![0].contains('file://')) { if (context.reader.images![0].contains('file://')) {
showToast( showToast(
message: "Local comic collection is not supported at present".tl, message: "Local comic collection is not supported at present".tl,
context: context); context: context,
);
return; return;
} }
String id = context.reader.cid; String id = context.reader.cid;
@@ -234,8 +230,10 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
List<String> tags = context.reader.widget.tags; List<String> tags = context.reader.widget.tags;
String author = context.reader.widget.author; String author = context.reader.widget.author;
var epName = context.reader.widget.chapters?.titles var epName =
.elementAtOrNull(context.reader.chapter - 1) ?? context.reader.widget.chapters?.titles.elementAtOrNull(
context.reader.chapter - 1,
) ??
"E${context.reader.chapter}"; "E${context.reader.chapter}";
var translatedTags = tags.map((e) => e.translateTagsToCN).toList(); var translatedTags = tags.map((e) => e.translateTagsToCN).toList();
@@ -248,7 +246,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
return; return;
} }
ImageFavoriteManager().deleteImageFavorite([ ImageFavoriteManager().deleteImageFavorite([
ImageFavorite(page, imageKey, null, eid, id, ep, sourceKey, epName) ImageFavorite(page, imageKey, null, eid, id, ep, sourceKey, epName),
]); ]);
showToast( showToast(
message: "Uncollected the image".tl, message: "Uncollected the image".tl,
@@ -256,7 +254,8 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
seconds: 1, seconds: 1,
); );
} else { } else {
var imageFavoritesComic = ImageFavoriteManager().find(id, sourceKey) ?? var imageFavoritesComic =
ImageFavoriteManager().find(id, sourceKey) ??
ImageFavoritesComic( ImageFavoritesComic(
id, id,
[], [],
@@ -270,10 +269,19 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
subTitle, subTitle,
maxPage, maxPage,
); );
ImageFavorite imageFavorite = ImageFavorite imageFavorite = ImageFavorite(
ImageFavorite(page, imageKey, null, eid, id, ep, sourceKey, epName); page,
ImageFavoritesEp? imageFavoritesEp = imageKey,
imageFavoritesComic.imageFavoritesEp.firstWhereOrNull((e) { null,
eid,
id,
ep,
sourceKey,
epName,
);
ImageFavoritesEp? imageFavoritesEp = imageFavoritesComic
.imageFavoritesEp
.firstWhereOrNull((e) {
return e.ep == ep; return e.ep == ep;
}); });
if (imageFavoritesEp == null) { if (imageFavoritesEp == null) {
@@ -285,10 +293,20 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
); );
// 不是第一页的话, 自动塞一个封面进去 // 不是第一页的话, 自动塞一个封面进去
imageFavoritesEp = ImageFavoritesEp( imageFavoritesEp = ImageFavoritesEp(
eid, ep, [copy, imageFavorite], epName, maxPage); eid,
ep,
[copy, imageFavorite],
epName,
maxPage,
);
} else { } else {
imageFavoritesEp = imageFavoritesEp = ImageFavoritesEp(
ImageFavoritesEp(eid, ep, [imageFavorite], epName, maxPage); eid,
ep,
[imageFavorite],
epName,
maxPage,
);
} }
imageFavoritesComic.imageFavoritesEp.add(imageFavoritesEp); imageFavoritesComic.imageFavoritesEp.add(imageFavoritesEp);
} else { } else {
@@ -312,7 +330,10 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
ImageFavoriteManager().addOrUpdateOrDelete(imageFavoritesComic); ImageFavoriteManager().addOrUpdateOrDelete(imageFavoritesComic);
showToast( showToast(
message: "Successfully collected".tl, context: context, seconds: 1); message: "Successfully collected".tl,
context: context,
seconds: 1,
);
} }
update(); update();
} catch (e, stackTrace) { } catch (e, stackTrace) {
@@ -331,9 +352,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
height: kBottomBarHeight, height: kBottomBarHeight,
child: Column( child: Column(
children: [ children: [
const SizedBox( const SizedBox(height: 8),
height: 8,
),
Row( Row(
children: [ children: [
const SizedBox(width: 8), const SizedBox(width: 8),
@@ -341,34 +360,45 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
onPressed: () => !isReversed onPressed: () => !isReversed
? context.reader.chapter > 1 ? context.reader.chapter > 1
? context.reader.toPrevChapter() ? context.reader.toPrevChapter()
: context.reader.toPage(1) : context.reader.toPage(
context.reader.cid,
context.reader.type,
1,
)
: context.reader.chapter < context.reader.maxChapter : context.reader.chapter < context.reader.maxChapter
? context.reader.toNextChapter() ? context.reader.toNextChapter()
: context.reader.toPage(context.reader.maxPage), : context.reader.toPage(
context.reader.cid,
context.reader.type,
context.reader.maxPage,
),
icon: const Icon(Icons.first_page), icon: const Icon(Icons.first_page),
), ),
Expanded( Expanded(child: buildSlider()),
child: buildSlider(),
),
IconButton.filledTonal( IconButton.filledTonal(
onPressed: () => !isReversed onPressed: () => !isReversed
? context.reader.chapter < context.reader.maxChapter ? context.reader.chapter < context.reader.maxChapter
? context.reader.toNextChapter() ? context.reader.toNextChapter()
: context.reader.toPage(context.reader.maxPage) : context.reader.toPage(
context.reader.cid,
context.reader.type,
context.reader.maxPage,
)
: context.reader.chapter > 1 : context.reader.chapter > 1
? context.reader.toPrevChapter() ? context.reader.toPrevChapter()
: context.reader.toPage(1), : context.reader.toPage(
icon: const Icon(Icons.last_page)), context.reader.cid,
const SizedBox( context.reader.type,
width: 8, 1,
), ),
icon: const Icon(Icons.last_page),
),
const SizedBox(width: 8),
], ],
), ),
Row( Row(
children: [ children: [
const SizedBox( const SizedBox(width: 16),
width: 16,
),
Container( Container(
height: 24, height: 24,
padding: const EdgeInsets.fromLTRB(6, 2, 6, 0), padding: const EdgeInsets.fromLTRB(6, 2, 6, 0),
@@ -376,16 +406,15 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
color: Theme.of(context).colorScheme.tertiaryContainer, color: Theme.of(context).colorScheme.tertiaryContainer,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Center( child: Center(child: Text(text)),
child: Text(text),
),
), ),
const Spacer(), 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,
), ),
), ),
@@ -427,14 +456,15 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
}); });
SystemChrome.setPreferredOrientations([ SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight DeviceOrientation.landscapeRight,
]); ]);
} else { } else {
setState(() { setState(() {
rotation = null; rotation = null;
}); });
SystemChrome.setPreferredOrientations( SystemChrome.setPreferredOrientations(
DeviceOrientation.values); DeviceOrientation.values,
);
} }
}, },
), ),
@@ -446,7 +476,10 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
? const Icon(Icons.timer) ? const Icon(Icons.timer)
: const Icon(Icons.timer_sharp), : const Icon(Icons.timer_sharp),
onPressed: () { onPressed: () {
context.reader.autoPageTurning(); context.reader.autoPageTurning(
context.reader.cid,
context.reader.type,
);
update(); update();
}, },
), ),
@@ -473,9 +506,9 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
onPressed: share, onPressed: share,
), ),
), ),
const SizedBox(width: 4) const SizedBox(width: 4),
], ],
) ),
], ],
), ),
); );
@@ -506,19 +539,26 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
focusNode: sliderFocus, focusNode: sliderFocus,
value: context.reader.page.toDouble(), value: context.reader.page.toDouble(),
min: 1, min: 1,
max: max: context.reader.maxPage
context.reader.maxPage.clamp(context.reader.page, 1 << 16).toDouble(), .clamp(context.reader.page, 1 << 16)
.toDouble(),
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(i.toInt()); context.reader.toPage(
context.reader.cid,
context.reader.type,
i.toInt(),
);
}, },
); );
} }
Widget buildPageInfoText() { Widget buildPageInfoText() {
var epName = context.reader.widget.chapters?.titles var epName =
.elementAtOrNull(context.reader.chapter - 1) ?? context.reader.widget.chapters?.titles.elementAtOrNull(
context.reader.chapter - 1,
) ??
"E${context.reader.chapter}"; "E${context.reader.chapter}";
if (epName.length > 8) { if (epName.length > 8) {
epName = "${epName.substring(0, 8)}..."; epName = "${epName.substring(0, 8)}...";
@@ -594,24 +634,35 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
} }
var fileType = detectFileType(data); var fileType = detectFileType(data);
var filename = "${context.reader.page}${fileType.ext}"; var filename = "${context.reader.page}${fileType.ext}";
Share.shareFile( Share.shareFile(data: data, filename: filename, mime: fileType.mime);
data: data,
filename: filename,
mime: fileType.mime,
);
} }
void openSetting() { void openSetting() {
showSideBar( showSideBar(
context, context,
ReaderSettings( ReaderSettings(
comicId: context.reader.cid,
comicSource: context.reader.type.sourceKey,
onChanged: (key) { onChanged: (key) {
if (key == "readerMode") { if (key == "readerMode") {
context.reader.mode = ReaderMode.fromKey(appdata.settings[key]); context.reader.mode = ReaderMode.fromKey(
appdata.settings.getReaderSetting(
context.reader.cid,
context.reader.type.sourceKey,
key,
),
);
} }
if (key == "enableTurnPageByVolumeKey") { if (key == "enableTurnPageByVolumeKey") {
if (appdata.settings[key]) { if (appdata.settings.getReaderSetting(
context.reader.handleVolumeEvent(); context.reader.cid,
context.reader.type.sourceKey,
key,
)) {
context.reader.handleVolumeEvent(
context.reader.cid,
context.reader.type,
);
} else { } else {
context.reader.stopVolumeEvent(); context.reader.stopVolumeEvent();
} }
@@ -716,8 +767,8 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
return await File(imageKey.substring(7)).readAsBytes(); return await File(imageKey.substring(7)).readAsBytes();
} else { } else {
return (await CacheManager().findCache( return (await CacheManager().findCache(
"$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}"))! "$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}",
.readAsBytes(); ))!.readAsBytes();
} }
} }
@@ -733,14 +784,17 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
entry = OverlayEntry( entry = OverlayEntry(
builder: (context) { builder: (context) {
return Positioned.fill( return Positioned.fill(
child: _SelectImageOverlayContent(onTap: (offset) { child: _SelectImageOverlayContent(
onTap: (offset) {
completer.complete(offset); completer.complete(offset);
entry!.remove(); entry!.remove();
}, onDispose: () { },
onDispose: () {
if (!completer.isCompleted) { if (!completer.isCompleted) {
completer.complete(null); completer.complete(null);
} }
}), },
),
); );
}, },
); );
@@ -840,9 +894,7 @@ class _BatteryWidgetState extends State<_BatteryWidget> {
size: 16, size: 16,
color: batteryColor, color: batteryColor,
// Stroke // Stroke
shadows: List.generate( shadows: List.generate(9, (index) {
9,
(index) {
if (index == 4) { if (index == 4) {
return null; return null;
} }
@@ -852,8 +904,7 @@ class _BatteryWidgetState extends State<_BatteryWidget> {
color: context.colorScheme.onInverseSurface, color: context.colorScheme.onInverseSurface,
offset: Offset(offsetX, offsetY), offset: Offset(offsetX, offsetY),
); );
}, }).whereType<Shadow>().toList(),
).whereType<Shadow>().toList(),
), ),
Stack( Stack(
children: [ children: [
@@ -940,10 +991,12 @@ class _SelectImageOverlayContent extends StatefulWidget {
final void Function() onDispose; final void Function() onDispose;
@override @override
State<_SelectImageOverlayContent> createState() => _SelectImageOverlayContentState(); State<_SelectImageOverlayContent> createState() =>
_SelectImageOverlayContentState();
} }
class _SelectImageOverlayContentState extends State<_SelectImageOverlayContent> { class _SelectImageOverlayContentState
extends State<_SelectImageOverlayContent> {
@override @override
void dispose() { void dispose() {
widget.onDispose(); widget.onDispose();
@@ -960,19 +1013,14 @@ class _SelectImageOverlayContentState extends State<_SelectImageOverlayContent>
child: Container( child: Container(
color: Colors.black.withAlpha(50), color: Colors.black.withAlpha(50),
child: Align( child: Align(
alignment: Alignment( alignment: Alignment(0, -0.8),
0,
-0.8,
),
child: Container( child: Container(
width: 232, width: 232,
height: 42, height: 42,
decoration: BoxDecoration( decoration: BoxDecoration(
color: context.colorScheme.surface, color: context.colorScheme.surface,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all( border: Border.all(color: context.colorScheme.outlineVariant),
color: context.colorScheme.outlineVariant,
),
), ),
child: Row( child: Row(
children: [ children: [

View File

@@ -1,9 +1,16 @@
part of 'settings_page.dart'; part of 'settings_page.dart';
class ReaderSettings extends StatefulWidget { class ReaderSettings extends StatefulWidget {
const ReaderSettings({super.key, this.onChanged}); const ReaderSettings({
super.key,
this.onChanged,
this.comicId,
this.comicSource,
});
final void Function(String key)? onChanged; final void Function(String key)? onChanged;
final String? comicId;
final String? comicSource;
@override @override
State<ReaderSettings> createState() => _ReaderSettingsState(); State<ReaderSettings> createState() => _ReaderSettingsState();
@@ -21,6 +28,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
onChanged: () { onChanged: () {
widget.onChanged?.call("enableTapToTurnPages"); widget.onChanged?.call("enableTapToTurnPages");
}, },
comicId: widget.comicId,
comicSource: widget.comicSource,
).toSliver(), ).toSliver(),
_SwitchSetting( _SwitchSetting(
title: "Reverse tap to turn Pages".tl, title: "Reverse tap to turn Pages".tl,
@@ -28,6 +37,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
onChanged: () { onChanged: () {
widget.onChanged?.call("reverseTapToTurnPages"); widget.onChanged?.call("reverseTapToTurnPages");
}, },
comicId: widget.comicId,
comicSource: widget.comicSource,
).toSliver(), ).toSliver(),
_SwitchSetting( _SwitchSetting(
title: "Page animation".tl, title: "Page animation".tl,
@@ -35,6 +46,15 @@ class _ReaderSettingsState extends State<ReaderSettings> {
onChanged: () { onChanged: () {
widget.onChanged?.call("enablePageAnimation"); widget.onChanged?.call("enablePageAnimation");
}, },
comicId: widget.comicId,
comicSource: widget.comicSource,
).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,
@@ -58,6 +78,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
} }
widget.onChanged?.call("readerMode"); widget.onChanged?.call("readerMode");
}, },
comicId: widget.comicId,
comicSource: widget.comicSource,
).toSliver(), ).toSliver(),
_SliderSetting( _SliderSetting(
title: "Auto page turning interval".tl, title: "Auto page turning interval".tl,
@@ -69,6 +91,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
setState(() {}); setState(() {});
widget.onChanged?.call("autoPageTurningInterval"); widget.onChanged?.call("autoPageTurningInterval");
}, },
comicId: widget.comicId,
comicSource: widget.comicSource,
).toSliver(), ).toSliver(),
SliverAnimatedVisibility( SliverAnimatedVisibility(
visible: appdata.settings['readerMode']!.startsWith('gallery'), visible: appdata.settings['readerMode']!.startsWith('gallery'),
@@ -84,6 +108,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
setState(() {}); setState(() {});
widget.onChanged?.call("readerScreenPicNumberForLandscape"); widget.onChanged?.call("readerScreenPicNumberForLandscape");
}, },
comicId: widget.comicId,
comicSource: widget.comicSource,
), ),
), ),
SliverAnimatedVisibility( SliverAnimatedVisibility(
@@ -99,10 +125,13 @@ class _ReaderSettingsState extends State<ReaderSettings> {
onChanged: () { onChanged: () {
widget.onChanged?.call("readerScreenPicNumberForPortrait"); widget.onChanged?.call("readerScreenPicNumberForPortrait");
}, },
comicId: widget.comicId,
comicSource: widget.comicSource,
), ),
), ),
SliverAnimatedVisibility( SliverAnimatedVisibility(
visible: appdata.settings['readerMode']!.startsWith('gallery') && visible:
appdata.settings['readerMode']!.startsWith('gallery') &&
(appdata.settings['readerScreenPicNumberForLandscape'] > 1 || (appdata.settings['readerScreenPicNumberForLandscape'] > 1 ||
appdata.settings['readerScreenPicNumberForPortrait'] > 1), appdata.settings['readerScreenPicNumberForPortrait'] > 1),
child: _SwitchSetting( child: _SwitchSetting(
@@ -111,6 +140,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
onChanged: () { onChanged: () {
widget.onChanged?.call("showSingleImageOnFirstPage"); widget.onChanged?.call("showSingleImageOnFirstPage");
}, },
comicId: widget.comicId,
comicSource: widget.comicSource,
), ),
), ),
_SwitchSetting( _SwitchSetting(
@@ -120,6 +151,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
setState(() {}); setState(() {});
widget.onChanged?.call('enableDoubleTapToZoom'); widget.onChanged?.call('enableDoubleTapToZoom');
}, },
comicId: widget.comicId,
comicSource: widget.comicSource,
).toSliver(), ).toSliver(),
_SwitchSetting( _SwitchSetting(
title: 'Long press to zoom'.tl, title: 'Long press to zoom'.tl,
@@ -128,6 +161,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
setState(() {}); setState(() {});
widget.onChanged?.call('enableLongPressToZoom'); widget.onChanged?.call('enableLongPressToZoom');
}, },
comicId: widget.comicId,
comicSource: widget.comicSource,
).toSliver(), ).toSliver(),
SliverAnimatedVisibility( SliverAnimatedVisibility(
visible: appdata.settings['enableLongPressToZoom'] == true, visible: appdata.settings['enableLongPressToZoom'] == true,
@@ -138,6 +173,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,
comicSource: widget.comicSource,
), ),
), ),
_SwitchSetting( _SwitchSetting(
@@ -147,6 +184,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
onChanged: () { onChanged: () {
widget.onChanged?.call('limitImageWidth'); widget.onChanged?.call('limitImageWidth');
}, },
comicId: widget.comicId,
comicSource: widget.comicSource,
).toSliver(), ).toSliver(),
if (App.isAndroid) if (App.isAndroid)
_SwitchSetting( _SwitchSetting(
@@ -155,6 +194,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
onChanged: () { onChanged: () {
widget.onChanged?.call('enableTurnPageByVolumeKey'); widget.onChanged?.call('enableTurnPageByVolumeKey');
}, },
comicId: widget.comicId,
comicSource: widget.comicSource,
).toSliver(), ).toSliver(),
_SwitchSetting( _SwitchSetting(
title: "Display time & battery info in reader".tl, title: "Display time & battery info in reader".tl,
@@ -162,6 +203,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
onChanged: () { onChanged: () {
widget.onChanged?.call("enableClockAndBatteryInfoInReader"); widget.onChanged?.call("enableClockAndBatteryInfoInReader");
}, },
comicId: widget.comicId,
comicSource: widget.comicSource,
).toSliver(), ).toSliver(),
_SwitchSetting( _SwitchSetting(
title: "Show system status bar".tl, title: "Show system status bar".tl,
@@ -169,6 +212,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
onChanged: () { onChanged: () {
widget.onChanged?.call("showSystemStatusBar"); widget.onChanged?.call("showSystemStatusBar");
}, },
comicId: widget.comicId,
comicSource: widget.comicSource,
).toSliver(), ).toSliver(),
SelectSetting( SelectSetting(
title: "Quick collect image".tl, title: "Quick collect image".tl,
@@ -184,6 +229,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,
comicSource: widget.comicSource,
).toSliver(), ).toSliver(),
_CallbackSetting( _CallbackSetting(
title: "Custom Image Processing".tl, title: "Custom Image Processing".tl,
@@ -196,6 +243,8 @@ class _ReaderSettingsState extends State<ReaderSettings> {
interval: 1, interval: 1,
min: 1, min: 1,
max: 16, max: 16,
comicId: widget.comicId,
comicSource: widget.comicSource,
).toSliver(), ).toSliver(),
_SwitchSetting( _SwitchSetting(
title: "Show Page Number".tl, title: "Show Page Number".tl,
@@ -203,7 +252,39 @@ class _ReaderSettingsState extends State<ReaderSettings> {
onChanged: () { onChanged: () {
widget.onChanged?.call("showPageNumberInReader"); widget.onChanged?.call("showPageNumberInReader");
}, },
comicId: widget.comicId,
comicSource: widget.comicSource,
).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,
),
),
),
], ],
); );
} }
@@ -248,7 +329,7 @@ class __CustomImageProcessingState extends State<_CustomImageProcessing> {
setState(() {}); setState(() {});
}, },
child: Text("Reset".tl), child: Text("Reset".tl),
) ),
], ],
), ),
body: Column( body: Column(
@@ -274,7 +355,7 @@ class __CustomImageProcessingState extends State<_CustomImageProcessing> {
), ),
), ),
), ),
) ),
], ],
), ),
); );

View File

@@ -6,6 +6,8 @@ class _SwitchSetting extends StatefulWidget {
required this.settingKey, required this.settingKey,
this.onChanged, this.onChanged,
this.subtitle, this.subtitle,
this.comicId,
this.comicSource,
}); });
final String title; final String title;
@@ -16,6 +18,10 @@ class _SwitchSetting extends StatefulWidget {
final String? subtitle; final String? subtitle;
final String? comicId;
final String? comicSource;
@override @override
State<_SwitchSetting> createState() => _SwitchSettingState(); State<_SwitchSetting> createState() => _SwitchSettingState();
} }
@@ -23,16 +29,33 @@ class _SwitchSetting extends StatefulWidget {
class _SwitchSettingState extends State<_SwitchSetting> { class _SwitchSettingState extends State<_SwitchSetting> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(appdata.settings[widget.settingKey] is bool); var value = widget.comicId == null
? appdata.settings[widget.settingKey]
: appdata.settings.getReaderSetting(
widget.comicId!,
widget.comicSource!,
widget.settingKey,
);
assert(value is bool);
return ListTile( return ListTile(
title: Text(widget.title), title: Text(widget.title),
subtitle: widget.subtitle == null ? null : Text(widget.subtitle!), subtitle: widget.subtitle == null ? null : Text(widget.subtitle!),
trailing: Switch( trailing: Switch(
value: appdata.settings[widget.settingKey], value: value,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
if (widget.comicId == null) {
appdata.settings[widget.settingKey] = value; appdata.settings[widget.settingKey] = value;
} else {
appdata.settings.setReaderSetting(
widget.comicId!,
widget.comicSource!,
widget.settingKey,
value,
);
}
}); });
appdata.saveData().then((_) { appdata.saveData().then((_) {
widget.onChanged?.call(); widget.onChanged?.call();
@@ -51,6 +74,8 @@ class SelectSetting extends StatelessWidget {
required this.optionTranslation, required this.optionTranslation,
this.onChanged, this.onChanged,
this.help, this.help,
this.comicId,
this.comicSource,
}); });
final String title; final String title;
@@ -63,6 +88,10 @@ class SelectSetting extends StatelessWidget {
final String? help; final String? help;
final String? comicId;
final String? comicSource;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return SizedBox(
@@ -76,6 +105,8 @@ class SelectSetting extends StatelessWidget {
optionTranslation: optionTranslation, optionTranslation: optionTranslation,
onChanged: onChanged, onChanged: onChanged,
help: help, help: help,
comicId: comicId,
comicSource: comicSource,
); );
} else { } else {
return _EndSelectorSelectSetting( return _EndSelectorSelectSetting(
@@ -84,6 +115,8 @@ class SelectSetting extends StatelessWidget {
optionTranslation: optionTranslation, optionTranslation: optionTranslation,
onChanged: onChanged, onChanged: onChanged,
help: help, help: help,
comicId: comicId,
comicSource: comicSource,
); );
} }
}, },
@@ -99,6 +132,8 @@ class _DoubleLineSelectSettings extends StatefulWidget {
required this.optionTranslation, required this.optionTranslation,
this.onChanged, this.onChanged,
this.help, this.help,
this.comicId,
this.comicSource,
}); });
final String title; final String title;
@@ -111,6 +146,10 @@ class _DoubleLineSelectSettings extends StatefulWidget {
final String? help; final String? help;
final String? comicId;
final String? comicSource;
@override @override
State<_DoubleLineSelectSettings> createState() => State<_DoubleLineSelectSettings> createState() =>
_DoubleLineSelectSettingsState(); _DoubleLineSelectSettingsState();
@@ -119,6 +158,14 @@ class _DoubleLineSelectSettings extends StatefulWidget {
class _DoubleLineSelectSettingsState extends State<_DoubleLineSelectSettings> { class _DoubleLineSelectSettingsState extends State<_DoubleLineSelectSettings> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var value = widget.comicId == null
? appdata.settings[widget.settingKey]
: appdata.settings.getReaderSetting(
widget.comicId!,
widget.comicSource!,
widget.settingKey,
);
return ListTile( return ListTile(
title: Row( title: Row(
children: [ children: [
@@ -134,9 +181,9 @@ class _DoubleLineSelectSettingsState extends State<_DoubleLineSelectSettings> {
builder: (context) { builder: (context) {
return ContentDialog( return ContentDialog(
title: "Help".tl, title: "Help".tl,
content: Text(widget.help!) content: Text(
.paddingHorizontal(16) widget.help!,
.fixWidth(double.infinity), ).paddingHorizontal(16).fixWidth(double.infinity),
actions: [ actions: [
Button.filled( Button.filled(
onPressed: context.pop, onPressed: context.pop,
@@ -150,9 +197,7 @@ class _DoubleLineSelectSettingsState extends State<_DoubleLineSelectSettings> {
), ),
], ],
), ),
subtitle: Text( subtitle: Text(widget.optionTranslation[value] ?? "None"),
widget.optionTranslation[appdata.settings[widget.settingKey]] ??
"None"),
trailing: const Icon(Icons.arrow_drop_down), trailing: const Icon(Icons.arrow_drop_down),
onTap: () { onTap: () {
var renderBox = context.findRenderObject() as RenderBox; var renderBox = context.findRenderObject() as RenderBox;
@@ -170,16 +215,27 @@ class _DoubleLineSelectSettingsState extends State<_DoubleLineSelectSettings> {
Offset.zero & MediaQuery.of(context).size, Offset.zero & MediaQuery.of(context).size,
), ),
items: widget.optionTranslation.keys items: widget.optionTranslation.keys
.map((key) => PopupMenuItem( .map(
(key) => PopupMenuItem(
value: key, value: key,
height: App.isMobile ? 46 : 40, height: App.isMobile ? 46 : 40,
child: Text(widget.optionTranslation[key]!), child: Text(widget.optionTranslation[key]!),
)) ),
)
.toList(), .toList(),
).then((value) { ).then((value) {
if (value != null) { if (value != null) {
setState(() { setState(() {
if (widget.comicId == null) {
appdata.settings[widget.settingKey] = value; appdata.settings[widget.settingKey] = value;
} else {
appdata.settings.setReaderSetting(
widget.comicId!,
widget.comicSource!,
widget.settingKey,
value,
);
}
}); });
appdata.saveData(); appdata.saveData();
widget.onChanged?.call(); widget.onChanged?.call();
@@ -197,6 +253,8 @@ class _EndSelectorSelectSetting extends StatefulWidget {
required this.optionTranslation, required this.optionTranslation,
this.onChanged, this.onChanged,
this.help, this.help,
this.comicId,
this.comicSource,
}); });
final String title; final String title;
@@ -209,6 +267,10 @@ class _EndSelectorSelectSetting extends StatefulWidget {
final String? help; final String? help;
final String? comicId;
final String? comicSource;
@override @override
State<_EndSelectorSelectSetting> createState() => State<_EndSelectorSelectSetting> createState() =>
_EndSelectorSelectSettingState(); _EndSelectorSelectSettingState();
@@ -218,6 +280,13 @@ class _EndSelectorSelectSettingState extends State<_EndSelectorSelectSetting> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var options = widget.optionTranslation; var options = widget.optionTranslation;
var value = widget.comicId == null
? appdata.settings[widget.settingKey]
: appdata.settings.getReaderSetting(
widget.comicId!,
widget.comicSource!,
widget.settingKey,
);
return ListTile( return ListTile(
title: Row( title: Row(
children: [ children: [
@@ -233,9 +302,9 @@ class _EndSelectorSelectSettingState extends State<_EndSelectorSelectSetting> {
builder: (context) { builder: (context) {
return ContentDialog( return ContentDialog(
title: "Help".tl, title: "Help".tl,
content: Text(widget.help!) content: Text(
.paddingHorizontal(16) widget.help!,
.fixWidth(double.infinity), ).paddingHorizontal(16).fixWidth(double.infinity),
actions: [ actions: [
Button.filled( Button.filled(
onPressed: context.pop, onPressed: context.pop,
@@ -250,12 +319,22 @@ class _EndSelectorSelectSettingState extends State<_EndSelectorSelectSetting> {
], ],
), ),
trailing: Select( trailing: Select(
current: options[appdata.settings[widget.settingKey]], current: options[value],
values: options.values.toList(), values: options.values.toList(),
minWidth: 64, minWidth: 64,
onTap: (index) { onTap: (index) {
setState(() { setState(() {
appdata.settings[widget.settingKey] = options.keys.elementAt(index); var value = options.keys.elementAt(index);
if (widget.comicId == null) {
appdata.settings[widget.settingKey] = value;
} else {
appdata.settings.setReaderSetting(
widget.comicId!,
widget.comicSource!,
widget.settingKey,
value,
);
}
}); });
appdata.saveData(); appdata.saveData();
widget.onChanged?.call(); widget.onChanged?.call();
@@ -273,6 +352,8 @@ class _SliderSetting extends StatefulWidget {
required this.min, required this.min,
required this.max, required this.max,
this.onChanged, this.onChanged,
this.comicId,
this.comicSource,
}); });
final String title; final String title;
@@ -287,6 +368,10 @@ class _SliderSetting extends StatefulWidget {
final VoidCallback? onChanged; final VoidCallback? onChanged;
final String? comicId;
final String? comicSource;
@override @override
State<_SliderSetting> createState() => _SliderSettingState(); State<_SliderSetting> createState() => _SliderSettingState();
} }
@@ -294,28 +379,52 @@ class _SliderSetting extends StatefulWidget {
class _SliderSettingState extends State<_SliderSetting> { class _SliderSettingState extends State<_SliderSetting> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var value =
(widget.comicId == null
? appdata.settings[widget.settingsIndex]
: appdata.settings.getReaderSetting(
widget.comicId!,
widget.comicSource!,
widget.settingsIndex,
))
.toDouble();
return ListTile( return ListTile(
title: Row( title: Row(
children: [ children: [
Text(widget.title), Text(widget.title),
const Spacer(), const Spacer(),
Text( Text(value.toString(), style: ts.s12),
appdata.settings[widget.settingsIndex].toString(),
style: ts.s12,
),
], ],
), ),
subtitle: Slider( subtitle: Slider(
value: appdata.settings[widget.settingsIndex].toDouble(), value: value,
onChanged: (value) { onChanged: (value) {
if (value.toInt() == value) { if (value.toInt() == value) {
setState(() { setState(() {
if (widget.comicId == null) {
appdata.settings[widget.settingsIndex] = value.toInt(); appdata.settings[widget.settingsIndex] = value.toInt();
} else {
appdata.settings.setReaderSetting(
widget.comicId!,
widget.comicSource!,
widget.settingsIndex,
value.toInt(),
);
}
appdata.saveData(); appdata.saveData();
}); });
} else { } else {
setState(() { setState(() {
if (widget.comicId == null) {
appdata.settings[widget.settingsIndex] = value; appdata.settings[widget.settingsIndex] = value;
} else {
appdata.settings.setReaderSetting(
widget.comicId!,
widget.comicSource!,
widget.settingsIndex,
value,
);
}
appdata.saveData(); appdata.saveData();
}); });
} }
@@ -405,7 +514,8 @@ class _MultiPagesFilterState extends State<_MultiPagesFilter> {
color: Colors.black12, color: Colors.black12,
blurRadius: 5, blurRadius: 5,
offset: Offset(0, 2), offset: Offset(0, 2),
spreadRadius: 2) spreadRadius: 2,
),
], ],
), ),
onReorder: (reorderFunc) { onReorder: (reorderFunc) {
@@ -435,7 +545,7 @@ class _MultiPagesFilterState extends State<_MultiPagesFilter> {
label: Text("Add".tl), label: Text("Add".tl),
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
onPressed: showAddDialog, onPressed: showAddDialog,
) ),
], ],
body: view, body: view,
); );
@@ -450,7 +560,8 @@ class _MultiPagesFilterState extends State<_MultiPagesFilter> {
keys.remove(key); keys.remove(key);
}); });
}, },
icon: const Icon(Icons.delete_outline)), icon: const Icon(Icons.delete_outline),
),
); );
return ListTile( return ListTile(
@@ -458,10 +569,7 @@ class _MultiPagesFilterState extends State<_MultiPagesFilter> {
key: Key(key), key: Key(key),
trailing: Row( trailing: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [removeButton, const Icon(Icons.drag_handle)],
removeButton,
const Icon(Icons.drag_handle),
],
), ),
); );
} }
@@ -477,7 +585,8 @@ class _MultiPagesFilterState extends State<_MultiPagesFilter> {
showDialog( showDialog(
context: context, context: context,
builder: (context) { builder: (context) {
return StatefulBuilder(builder: (context, setState) { return StatefulBuilder(
builder: (context, setState) {
return ContentDialog( return ContentDialog(
title: "Add".tl, title: "Add".tl,
content: Column( content: Column(
@@ -534,7 +643,8 @@ class _MultiPagesFilterState extends State<_MultiPagesFilter> {
), ),
], ],
); );
}); },
);
}, },
); );
} }

View File

@@ -28,6 +28,8 @@ final _resolver = MimeTypeResolver()
..addMagicNumber([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C], 'application/x-7z-compressed') ..addMagicNumber([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C], 'application/x-7z-compressed')
// rar // rar
..addMagicNumber([0x52, 0x61, 0x72, 0x21, 0x1A, 0x07], 'application/vnd.rar') ..addMagicNumber([0x52, 0x61, 0x72, 0x21, 0x1A, 0x07], 'application/vnd.rar')
// avif
..addMagicNumber([0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66], 'image/avif')
; ;
FileType detectFileType(List<int> data) { FileType detectFileType(List<int> data) {