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": "禁用长度限制",
"Only valid for this run": "仅对本次运行有效",
"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": {
"Home": "首頁",
@@ -815,6 +818,9 @@
"Disable Length Limitation": "禁用長度限制",
"Only valid for this run": "僅對本次運行有效",
"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;
double k = (_futurePosition! - currentLocation).abs() / 1600 + 1;
_futurePosition = _futurePosition! + pointerSignal.scrollDelta.dy * k;
var beforeOffset = (_futurePosition! - currentLocation).abs();
_futurePosition = _futurePosition!.clamp(
_controller.position.minScrollExtent,
_controller.position.maxScrollExtent,
);
var afterOffset = (_futurePosition! - currentLocation).abs();
if (_futurePosition == old) return;
var target = _futurePosition!;
var duration = _fastAnimationDuration;
if (afterOffset < beforeOffset) {
duration = duration * (afterOffset / beforeOffset);
if (duration < Duration(milliseconds: 10)) {
duration = Duration(milliseconds: 10);
}
}
_controller
.animateTo(
_futurePosition!,
duration: _fastAnimationDuration,
duration: duration,
curve: Curves.linear,
)
.then((_) {

View File

@@ -26,8 +26,7 @@ class Appdata with Init {
var data = jsonEncode(toJson());
var file = File(FilePath.join(App.dataPath, 'appdata.json'));
await file.writeAsString(data);
}
finally {
} finally {
_isSavingData = false;
}
if (sync) {
@@ -57,10 +56,7 @@ class Appdata with Init {
}
Map<String, dynamic> toJson() {
return {
'settings': settings._data,
'searchHistory': searchHistory,
};
return {'settings': settings._data, 'searchHistory': searchHistory};
}
/// Following fields are related to device-specific data and should not be synced.
@@ -95,8 +91,7 @@ class Appdata with Init {
try {
var file = File(FilePath.join(App.dataPath, 'implicitData.json'));
await file.writeAsString(jsonEncode(implicitData));
}
finally {
} finally {
_isSavingData = false;
}
}
@@ -104,10 +99,7 @@ class Appdata with Init {
@override
Future<void> doInit() async {
var dataPath = (await getApplicationSupportDirectory()).path;
var file = File(FilePath.join(
dataPath,
'appdata.json',
));
var file = File(FilePath.join(dataPath, 'appdata.json'));
if (!await file.exists()) {
return;
}
@@ -119,8 +111,7 @@ class Appdata with Init {
}
}
searchHistory = List.from(json['searchHistory']);
}
catch(e) {
} catch (e) {
Log.error("Appdata", "Failed to load appdata", e);
Log.info("Appdata", "Resetting appdata");
file.deleteIgnoreError();
@@ -130,8 +121,7 @@ class Appdata with Init {
if (await implicitDataFile.exists()) {
implicitData = jsonDecode(await implicitDataFile.readAsString());
}
}
catch (e) {
} catch (e) {
Log.error("Appdata", "Failed to load implicit data", e);
Log.info("Appdata", "Resetting implicit data");
var implicitDataFile = File(FilePath.join(dataPath, 'implicitData.json'));
@@ -162,6 +152,7 @@ class Settings with ChangeNotifier {
'blockedWords': [],
'defaultSearchTarget': null,
'autoPageTurningInterval': 5, // in seconds
'enableComicSpecificSettings': false,
'readerMode': 'galleryLeftToRight', // values of [ReaderMode]
'readerScreenPicNumberForLandscape': 1, // 1 - 5
'readerScreenPicNumberForPortrait': 1, // 1 - 5
@@ -199,6 +190,7 @@ class Settings with ChangeNotifier {
'enableDoubleTapToZoom': true,
'reverseChapterOrder': false,
'showSystemStatusBar': false,
'comicSpecificSettings': <String, Map<String, dynamic>>{},
};
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
String 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) {
/// 如果无法检测到状态栏高度设定指定高度
/// 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);
if (App.isDesktop) {
widget = Shortcuts(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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