diff --git a/changelog/v7.4.8+147.md b/changelog/v7.4.8+147.md index 54e268ca..d5aeb9a5 100644 --- a/changelog/v7.4.8+147.md +++ b/changelog/v7.4.8+147.md @@ -1,6 +1,6 @@ - 移动端支持自定义阅读页亮度 - 现在所有平台和布局模式下均可以自定义鼠标滚轮滚动速度 -- 优化取消收藏时的文案 +- 阅读页在从上至下布局模式下,现在可以支持自定义图片区域宽度 - 优化保存图片时的图片命名规则 - 优化解压归档时的icon显示 - 收藏页和关注页搜索不再继承关键字 @@ -16,6 +16,7 @@ - Support customizing the brightness of the reading page on the mobile terminal - Now you can customize the mouse wheel scrolling speed in all platforms and layout modes +- In the top to bottom layout mode, the reading page can now support customizing the width of the image area - Optimize the toast message when canceling favorites - Optimize the image naming rules when saving images - Optimize the icon display when decompressing archives diff --git a/lib/src/config/ui_config.dart b/lib/src/config/ui_config.dart index b9cd86b0..05e76b04 100644 --- a/lib/src/config/ui_config.dart +++ b/lib/src/config/ui_config.dart @@ -290,6 +290,7 @@ class UIConfig { static const int searchPageAnimationDuration = 250; /// Read page + static const Color readPageBackGroundColor = Colors.black; static const Color readPageForeGroundColor = Colors.white; static Color get readPageMenuColor => Colors.black.withOpacity(0.85); diff --git a/lib/src/l18n/en_US.dart b/lib/src/l18n/en_US.dart index cc58f59e..09ac3f84 100644 --- a/lib/src/l18n/en_US.dart +++ b/lib/src/l18n/en_US.dart @@ -405,6 +405,7 @@ class en_US { 'landscape': 'Landscape', 'portrait': 'Portrait', 'readDirection': 'Read Direction', + 'imageRegionWidthRatio': 'Image Region Width Ratio', 'useThirdPartyViewer': 'Use Custom Viewer', 'thirdPartyViewerPath': 'Custom Viewer Path(Executable file)', 'showThumbnails': 'Show Thumbnails', diff --git a/lib/src/l18n/ko_KR.dart b/lib/src/l18n/ko_KR.dart index 7c503cb0..5ed086e8 100644 --- a/lib/src/l18n/ko_KR.dart +++ b/lib/src/l18n/ko_KR.dart @@ -405,6 +405,7 @@ class ko_KR { 'landscape': 'Landscape', 'portrait': 'Portrait', 'readDirection': '읽는 방향', + 'imageRegionWidthRatio': 'Image Region Width Ratio', 'useThirdPartyViewer': '커스텀 뷰어 사용', 'thirdPartyViewerPath': '커스텀 뷰어 경로(실행 파일)', 'showThumbnails': '섬네일 보기', diff --git a/lib/src/l18n/pt_BR.dart b/lib/src/l18n/pt_BR.dart index 070cf044..b4a30c60 100644 --- a/lib/src/l18n/pt_BR.dart +++ b/lib/src/l18n/pt_BR.dart @@ -406,6 +406,7 @@ class pt_BR { 'landscape': 'Landscape', 'portrait': 'Portrait', 'readDirection': 'Direção da leitura', + 'imageRegionWidthRatio': 'Image Region Width Ratio', 'useThirdPartyViewer': 'Usar visualizador personaliado', 'thirdPartyViewerPath': 'Localização do visualizador personalizado(Arquivo executável)', 'showThumbnails': 'Mostrar miniaturas', diff --git a/lib/src/l18n/zh_CN.dart b/lib/src/l18n/zh_CN.dart index ae267e02..bea45901 100644 --- a/lib/src/l18n/zh_CN.dart +++ b/lib/src/l18n/zh_CN.dart @@ -405,6 +405,7 @@ class zh_CN { 'landscape': '横屏', 'portrait': '竖屏', 'readDirection': '阅读方向', + 'imageRegionWidthRatio': '图片区域宽度比例', 'useThirdPartyViewer': '使用第三方阅读器', 'thirdPartyViewerPath': '第三方阅读器路径(可执行文件)', 'showThumbnails': '显示缩略图', diff --git a/lib/src/l18n/zh_TW.dart b/lib/src/l18n/zh_TW.dart index 581d9649..7e4316c9 100644 --- a/lib/src/l18n/zh_TW.dart +++ b/lib/src/l18n/zh_TW.dart @@ -404,6 +404,7 @@ class zh_TW { 'landscape': '橫屏', 'portrait': '豎屏', 'readDirection': '閱讀方向', + 'imageRegionWidthRatio': '圖片區域寬度比例', 'useThirdPartyViewer': '使用第三方閱讀器', 'thirdPartyViewerPath': '第三方閱讀器路徑(可執行文件)', 'showThumbnails': '顯示快取圖', diff --git a/lib/src/pages/read/layout/base/base_layout_logic.dart b/lib/src/pages/read/layout/base/base_layout_logic.dart index e10f7bbe..623aebba 100644 --- a/lib/src/pages/read/layout/base/base_layout_logic.dart +++ b/lib/src/pages/read/layout/base/base_layout_logic.dart @@ -197,7 +197,7 @@ abstract class BaseLayoutLogic extends GetxController with GetTickerProviderStat (_) => Share.shareFiles( [path], text: '$index${extension(readPageState.images[index]!.url)}', - sharePositionOrigin: Rect.fromLTWH(0, 0, fullScreenWidth, readPageState.imageRegionSize.height * 2 / 3), + sharePositionOrigin: Rect.fromLTWH(0, 0, fullScreenWidth, readPageState.displayRegionSize.height * 2 / 3), ), ); } @@ -214,7 +214,7 @@ abstract class BaseLayoutLogic extends GetxController with GetTickerProviderStat galleryDownloadService.galleryDownloadInfos[readPageState.readPageInfo.gid!]!.images[index]!.path!), ], text: basename(galleryDownloadService.galleryDownloadInfos[readPageState.readPageInfo.gid!]!.images[index]!.path!), - sharePositionOrigin: Rect.fromLTWH(0, 0, fullScreenWidth, readPageState.imageRegionSize.height * 2 / 3), + sharePositionOrigin: Rect.fromLTWH(0, 0, fullScreenWidth, readPageState.displayRegionSize.height * 2 / 3), ); } @@ -290,7 +290,7 @@ abstract class BaseLayoutLogic extends GetxController with GetTickerProviderStat if (readPageState.imageContainerSizes[imageIndex] != null) { return readPageState.imageContainerSizes[imageIndex]!; } - return Size(double.infinity, readPageState.imageRegionSize.height / 2); + return Size(double.infinity, readPageState.displayRegionSize.height / 2); } /// Compute image container size @@ -298,7 +298,7 @@ abstract class BaseLayoutLogic extends GetxController with GetTickerProviderStat return applyBoxFit( BoxFit.contain, Size(imageSize.width, imageSize.height), - Size(readPageState.imageRegionSize.width, double.infinity), + Size(readPageState.displayRegionSize.width, double.infinity), ); } diff --git a/lib/src/pages/read/layout/horizontal_double_column/horizontal_double_column_layout_logic.dart b/lib/src/pages/read/layout/horizontal_double_column/horizontal_double_column_layout_logic.dart index 31f2a5a3..ef1b43e1 100644 --- a/lib/src/pages/read/layout/horizontal_double_column/horizontal_double_column_layout_logic.dart +++ b/lib/src/pages/read/layout/horizontal_double_column/horizontal_double_column_layout_logic.dart @@ -150,8 +150,8 @@ class HorizontalDoubleColumnLayoutLogic extends BaseLayoutLogic { BoxFit.contain, Size(imageSize.width, imageSize.height), Size( - isSpreadPage ? readPageState.imageRegionSize.width : (readPageState.imageRegionSize.width - ReadSetting.imageSpace.value) / 2, - readPageState.imageRegionSize.height, + isSpreadPage ? readPageState.displayRegionSize.width : (readPageState.displayRegionSize.width - ReadSetting.imageSpace.value) / 2, + readPageState.displayRegionSize.height, ), ); } diff --git a/lib/src/pages/read/layout/horizontal_list/horizontal_list_layout_logic.dart b/lib/src/pages/read/layout/horizontal_list/horizontal_list_layout_logic.dart index 49a99258..13e33892 100644 --- a/lib/src/pages/read/layout/horizontal_list/horizontal_list_layout_logic.dart +++ b/lib/src/pages/read/layout/horizontal_list/horizontal_list_layout_logic.dart @@ -232,7 +232,7 @@ class HorizontalListLayoutLogic extends BaseLayoutLogic { return applyBoxFit( BoxFit.contain, Size(imageSize.width, imageSize.height), - Size(double.infinity, readPageState.imageRegionSize.height), + Size(double.infinity, readPageState.displayRegionSize.height), ); } } diff --git a/lib/src/pages/read/layout/vertical_list/vertical_list_layout.dart b/lib/src/pages/read/layout/vertical_list/vertical_list_layout.dart index 21faf484..4ba815d5 100644 --- a/lib/src/pages/read/layout/vertical_list/vertical_list_layout.dart +++ b/lib/src/pages/read/layout/vertical_list/vertical_list_layout.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:jhentai/src/config/ui_config.dart'; import 'package:jhentai/src/model/read_page_info.dart'; import 'package:jhentai/src/pages/read/layout/vertical_list/vertical_list_layout_state.dart'; import 'package:photo_view/photo_view_gallery.dart'; @@ -20,34 +21,47 @@ class VerticalListLayout extends BaseLayout { @override Widget buildBody(BuildContext context) { - /// user PhotoViewGallery to scale up the whole gallery list, so set itemCount to 1 - return PhotoViewGallery.builder( - itemCount: 1, - builder: (_, __) => PhotoViewGalleryPageOptions.customChild( - controller: state.photoViewController, - initialScale: 1.0, - minScale: 1.0, - maxScale: 2.5, - scaleStateCycle: ReadSetting.enableDoubleTapToScaleUp.isTrue ? logic.scaleStateCycle : null, - enableTapDragZoom: ReadSetting.enableTapDragToScaleUp.isTrue, - child: EHWheelSpeedControllerForReadPage( - scrollController: state.itemScrollController, - child: EHScrollablePositionedList.separated( - physics: const ClampingScrollPhysics(), - minCacheExtent: readPageState.readPageInfo.mode == ReadMode.online - ? ReadSetting.preloadDistance * screenHeight * 1 - : GetPlatform.isIOS - ? 3 * screenHeight - : 8 * screenHeight, - initialScrollIndex: readPageState.readPageInfo.initialIndex, - itemCount: readPageState.readPageInfo.pageCount, - itemScrollController: state.itemScrollController, - itemPositionsListener: state.itemPositionsListener, - itemBuilder: (context, index) => - readPageState.readPageInfo.mode == ReadMode.online ? buildItemInOnlineMode(context, index) : buildItemInLocalMode(context, index), - separatorBuilder: (_, __) => Obx(() => SizedBox(height: ReadSetting.imageSpace.value.toDouble())), + return GetBuilder( + id: logic.verticalLayoutId, + builder: (_) => Row( + children: [ + Expanded(flex: 100 - ReadSetting.imageRegionWidthRatio.value, child: Container(color: UIConfig.readPageBackGroundColor)), + + /// user PhotoViewGallery to scale up the whole gallery list, so set itemCount to 1 + Expanded( + flex: ReadSetting.imageRegionWidthRatio.value * 2, + child: PhotoViewGallery.builder( + itemCount: 1, + builder: (_, __) => PhotoViewGalleryPageOptions.customChild( + controller: state.photoViewController, + initialScale: 1.0, + minScale: 1.0, + maxScale: 2.5, + scaleStateCycle: ReadSetting.enableDoubleTapToScaleUp.isTrue ? logic.scaleStateCycle : null, + enableTapDragZoom: ReadSetting.enableTapDragToScaleUp.isTrue, + child: EHWheelSpeedControllerForReadPage( + scrollController: state.itemScrollController, + child: EHScrollablePositionedList.separated( + physics: const ClampingScrollPhysics(), + minCacheExtent: readPageState.readPageInfo.mode == ReadMode.online + ? ReadSetting.preloadDistance * screenHeight * 1 + : GetPlatform.isIOS + ? 3 * screenHeight + : 8 * screenHeight, + initialScrollIndex: readPageState.readPageInfo.initialIndex, + itemCount: readPageState.readPageInfo.pageCount, + itemScrollController: state.itemScrollController, + itemPositionsListener: state.itemPositionsListener, + itemBuilder: (context, index) => + readPageState.readPageInfo.mode == ReadMode.online ? buildItemInOnlineMode(context, index) : buildItemInLocalMode(context, index), + separatorBuilder: (_, __) => Obx(() => SizedBox(height: ReadSetting.imageSpace.value.toDouble())), + ), + ), + ), + ), ), - ), + Expanded(flex: 100 - ReadSetting.imageRegionWidthRatio.value, child: Container(color: UIConfig.readPageBackGroundColor)), + ], ), ); } diff --git a/lib/src/pages/read/layout/vertical_list/vertical_list_layout_logic.dart b/lib/src/pages/read/layout/vertical_list/vertical_list_layout_logic.dart index bb4912ab..fe84b1f6 100644 --- a/lib/src/pages/read/layout/vertical_list/vertical_list_layout_logic.dart +++ b/lib/src/pages/read/layout/vertical_list/vertical_list_layout_logic.dart @@ -2,7 +2,9 @@ import 'dart:async'; import 'dart:math'; import 'package:collection/collection.dart'; +import 'package:flutter/widgets.dart'; import 'package:get/get.dart'; +import 'package:jhentai/src/extension/get_logic_extension.dart'; import 'package:jhentai/src/pages/read/layout/vertical_list/vertical_list_layout_state.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; @@ -11,8 +13,12 @@ import '../../../../utils/screen_size_util.dart'; import '../base/base_layout_logic.dart'; class VerticalListLayoutLogic extends BaseLayoutLogic { + final String verticalLayoutId = 'verticalLayoutId'; + VerticalListLayoutState state = VerticalListLayoutState(); + late Worker imageRegionWidthRatioListener; + @override void onInit() { super.onInit(); @@ -21,6 +27,16 @@ class VerticalListLayoutLogic extends BaseLayoutLogic { state.itemPositionsListener.itemPositions.addListener(_readProgressListener); } + @override + void onReady() { + super.onReady(); + + imageRegionWidthRatioListener = ever(ReadSetting.imageRegionWidthRatio, (int value) { + readPageLogic.clearImageContainerSized(); + updateSafely([verticalLayoutId]); + }); + } + @override void toLeft() { toPrev(); @@ -213,4 +229,23 @@ class VerticalListLayoutLogic extends BaseLayoutLogic { double _getVisibleHeight() { return screenHeight - Get.mediaQuery.padding.bottom - (ReadSetting.enableImmersiveMode.isTrue ? 0 : Get.mediaQuery.padding.top); } + + /// Compute image container size when we haven't parsed image's size + @override + Size getPlaceHolderSize(int imageIndex) { + if (readPageState.imageContainerSizes[imageIndex] != null) { + return readPageState.imageContainerSizes[imageIndex]!; + } + return Size(readPageState.displayRegionSize.width * ReadSetting.imageRegionWidthRatio.value / 100, readPageState.displayRegionSize.height / 2); + } + + /// Compute image container size + @override + FittedSizes getImageFittedSize(Size imageSize) { + return applyBoxFit( + BoxFit.contain, + Size(imageSize.width, imageSize.height), + Size(readPageState.displayRegionSize.width * ReadSetting.imageRegionWidthRatio.value / 100, double.infinity), + ); + } } diff --git a/lib/src/pages/read/read_page.dart b/lib/src/pages/read/read_page.dart index 775282e9..3dc1abd9 100644 --- a/lib/src/pages/read/read_page.dart +++ b/lib/src/pages/read/read_page.dart @@ -140,7 +140,7 @@ class _ReadPageState extends State with ScrollStatusListener, WindowLi return LayoutBuilder( builder: (context, constraints) { logic.clearImageContainerSized(); - state.imageRegionSize = Size(constraints.maxWidth, constraints.maxHeight); + state.displayRegionSize = Size(constraints.maxWidth, constraints.maxHeight); if (ReadSetting.readDirection.value == ReadDirection.top2bottomList) { return VerticalListLayout(); diff --git a/lib/src/pages/read/read_page_state.dart b/lib/src/pages/read/read_page_state.dart index 6dbcc25a..5d1ef318 100644 --- a/lib/src/pages/read/read_page_state.dart +++ b/lib/src/pages/read/read_page_state.dart @@ -36,7 +36,7 @@ class ReadPageState with ScrollStatusListerState { bool displayFirstPageAlone = ReadSetting.displayFirstPageAlone.value; FocusNode focusNode = FocusNode(); - late Size imageRegionSize; + late Size displayRegionSize; final EHItemScrollController thumbnailsScrollController = EHItemScrollController(); final ItemPositionsListener thumbnailPositionsListener = ItemPositionsListener.create(); diff --git a/lib/src/pages/setting/eh/tagsets/tag_sets_page.dart b/lib/src/pages/setting/eh/tagsets/tag_sets_page.dart index 4c380c99..8bdfe7a1 100644 --- a/lib/src/pages/setting/eh/tagsets/tag_sets_page.dart +++ b/lib/src/pages/setting/eh/tagsets/tag_sets_page.dart @@ -12,6 +12,7 @@ import 'package:jhentai/src/utils/search_util.dart'; import 'package:jhentai/src/widget/eh_wheel_speed_controller.dart'; import '../../../../utils/route_util.dart'; +import '../../../../utils/text_input_formatter.dart'; import '../../../../widget/loading_state_indicator.dart'; class TagSetsPage extends StatelessWidget { @@ -181,7 +182,7 @@ class _Tag extends StatelessWidget { textAlign: TextAlign.center, inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[\d-]')), - NumberRangeTextInputFormatter(minValue: -99, maxValue: 99), + IntRangeTextInputFormatter(minValue: -99, maxValue: 99), ], onSubmitted: onWeightUpdated, ), @@ -191,31 +192,6 @@ class _Tag extends StatelessWidget { enum TagSetStatus { watched, hidden, nope } -class NumberRangeTextInputFormatter extends TextInputFormatter { - double? minValue; - double? maxValue; - - NumberRangeTextInputFormatter({this.minValue, this.maxValue}); - - @override - TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) { - if (newValue.text.isEmpty || (minValue != null && minValue! < 0 && newValue.text == '-')) { - return newValue; - } - - double newNum = double.tryParse(newValue.text) ?? -100; - - if (minValue != null && newNum < minValue!) { - return oldValue; - } - if (maxValue != null && newNum > maxValue!) { - return oldValue; - } - - return newValue; - } -} - class _ColorSettingDialog extends StatefulWidget { final Color initialColor; diff --git a/lib/src/pages/setting/mousewheel/setting_mouse_wheel_page.dart b/lib/src/pages/setting/mousewheel/setting_mouse_wheel_page.dart index d71ad834..aff69324 100644 --- a/lib/src/pages/setting/mousewheel/setting_mouse_wheel_page.dart +++ b/lib/src/pages/setting/mousewheel/setting_mouse_wheel_page.dart @@ -5,8 +5,8 @@ import 'package:jhentai/src/config/ui_config.dart'; import 'package:jhentai/src/extension/widget_extension.dart'; import 'package:jhentai/src/setting/mouse_setting.dart'; +import '../../../utils/text_input_formatter.dart'; import '../../../utils/toast_util.dart'; -import '../eh/tagsets/tag_sets_page.dart'; class SettingMouseWheelPage extends StatelessWidget { const SettingMouseWheelPage({Key? key}) : super(key: key); @@ -32,7 +32,7 @@ class SettingMouseWheelPage extends StatelessWidget { controller: wheelScrollSpeedController, decoration: const InputDecoration(isDense: true, labelStyle: TextStyle(fontSize: 12)), textAlign: TextAlign.center, - inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'\d|\.')), NumberRangeTextInputFormatter(minValue: 0)], + inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'\d|\.')), DoubleRangeTextInputFormatter(minValue: 0)], onSubmitted: (_) { double? value = double.tryParse(wheelScrollSpeedController.value.text); if (value == null) { diff --git a/lib/src/pages/setting/network/setting_network_page.dart b/lib/src/pages/setting/network/setting_network_page.dart index 6dc8c2df..fb9f6a8e 100644 --- a/lib/src/pages/setting/network/setting_network_page.dart +++ b/lib/src/pages/setting/network/setting_network_page.dart @@ -7,8 +7,8 @@ import 'package:jhentai/src/setting/network_setting.dart'; import '../../../routes/routes.dart'; import '../../../utils/route_util.dart'; +import '../../../utils/text_input_formatter.dart'; import '../../../utils/toast_util.dart'; -import '../eh/tagsets/tag_sets_page.dart'; class SettingNetworkPage extends StatelessWidget { final TextEditingController proxyAddressController = TextEditingController(text: NetworkSetting.proxyAddress.value); @@ -93,11 +93,12 @@ class SettingNetworkPage extends StatelessWidget { width: 50, child: TextField( controller: connectTimeoutController, + keyboardType: TextInputType.number, decoration: const InputDecoration(isDense: true, labelStyle: TextStyle(fontSize: 12)), textAlign: TextAlign.center, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, - NumberRangeTextInputFormatter(minValue: 0), + IntRangeTextInputFormatter(minValue: 0), ], ), ), @@ -133,7 +134,7 @@ class SettingNetworkPage extends StatelessWidget { textAlign: TextAlign.center, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, - NumberRangeTextInputFormatter(minValue: 0), + IntRangeTextInputFormatter(minValue: 0), ], ), ), diff --git a/lib/src/pages/setting/read/setting_read_page.dart b/lib/src/pages/setting/read/setting_read_page.dart index 51633bc4..3ea0200b 100644 --- a/lib/src/pages/setting/read/setting_read_page.dart +++ b/lib/src/pages/setting/read/setting_read_page.dart @@ -1,14 +1,19 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:jhentai/src/config/ui_config.dart'; import 'package:jhentai/src/extension/widget_extension.dart'; import 'package:jhentai/src/setting/read_setting.dart'; import '../../../utils/log.dart'; +import '../../../utils/text_input_formatter.dart'; +import '../../../utils/toast_util.dart'; class SettingReadPage extends StatelessWidget { - const SettingReadPage({Key? key}) : super(key: key); + final TextEditingController imageRegionWidthRatioController = TextEditingController(text: ReadSetting.imageRegionWidthRatio.value.toString()); + + SettingReadPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -36,6 +41,7 @@ class SettingReadPage extends StatelessWidget { if (GetPlatform.isDesktop) _buildThirdPartyViewerPath().center(), if (GetPlatform.isMobile) _buildDeviceDirection().center(), _buildReadDirection().center(), + if (ReadSetting.readDirection.value == ReadDirection.top2bottomList) _buildImageRegionWidthRatio(context).center(), if (ReadSetting.isInListReadDirection) _buildPreloadDistanceInOnlineMode(context).fadeIn(const Key('preloadDistanceInOnlineMode')).center(), if (!ReadSetting.isInListReadDirection) _buildPreloadPageCount().fadeIn(const Key('preloadPageCount')).center(), if (ReadSetting.isInDoubleColumnReadDirection) _buildDisplayFirstPageAlone().fadeIn(const Key('displayFirstPageAloneGlobally')).center(), @@ -127,7 +133,7 @@ class SettingReadPage extends StatelessWidget { ), ], ), - ); + ).marginOnly(right: 12); } Widget _buildShowThumbnails() { @@ -221,6 +227,47 @@ class SettingReadPage extends StatelessWidget { ); } + Widget _buildImageRegionWidthRatio(BuildContext context) { + return ListTile( + title: Text('imageRegionWidthRatio'.tr), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 50, + child: TextField( + controller: imageRegionWidthRatioController, + keyboardType: TextInputType.number, + decoration: const InputDecoration(isDense: true, labelStyle: TextStyle(fontSize: 12)), + textAlign: TextAlign.center, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + IntRangeTextInputFormatter(minValue: 1, maxValue: 100), + ], + onSubmitted: (_) { + _saveImageRegionWidthRatio(); + }, + ), + ), + const Text('%'), + IconButton( + onPressed: _saveImageRegionWidthRatio, + icon: Icon(Icons.check, color: UIConfig.resumePauseButtonColor(context)), + ), + ], + ), + ).marginOnly(right: 12); + } + + void _saveImageRegionWidthRatio() { + int? value = int.tryParse(imageRegionWidthRatioController.value.text); + if (value == null) { + return; + } + ReadSetting.saveImageRegionWidthRatio(value); + toast('saveSuccess'.tr); + } + Widget _buildUseThirdPartyViewer() { return SwitchListTile( title: Text('useThirdPartyViewer'.tr), diff --git a/lib/src/routes/routes.dart b/lib/src/routes/routes.dart index 4c41c33d..f7bd6aab 100644 --- a/lib/src/routes/routes.dart +++ b/lib/src/routes/routes.dart @@ -248,7 +248,7 @@ class Routes { ), EHPage( name: settingRead, - page: () => const SettingReadPage(), + page: () => SettingReadPage(), transition: defaultTransition, ), EHPage( diff --git a/lib/src/setting/preference_setting.dart b/lib/src/setting/preference_setting.dart index 1f235c41..98d0bb01 100644 --- a/lib/src/setting/preference_setting.dart +++ b/lib/src/setting/preference_setting.dart @@ -22,7 +22,7 @@ class PreferenceSetting { static RxBool enableSwipeBackGesture = true.obs; static RxBool enableLeftMenuDrawerGesture = true.obs; static RxBool enableQuickSearchDrawerGesture = true.obs; - static RxInt drawerGestureEdgeWidth = 20.obs; + static RxInt drawerGestureEdgeWidth = 40.obs; static RxBool showComments = true.obs; static RxBool showAllComments = false.obs; static RxBool enableDefaultFavorite = false.obs; diff --git a/lib/src/setting/read_setting.dart b/lib/src/setting/read_setting.dart index a3455a88..05787a48 100644 --- a/lib/src/setting/read_setting.dart +++ b/lib/src/setting/read_setting.dart @@ -46,6 +46,7 @@ class ReadSetting { static RxBool enableBottomMenu = false.obs; static Rx deviceDirection = DeviceDirection.followSystem.obs; static Rx readDirection = GetPlatform.isMobile ? ReadDirection.top2bottomList.obs : ReadDirection.left2rightList.obs; + static RxInt imageRegionWidthRatio = 100.obs; static RxBool useThirdPartyViewer = false.obs; static RxnString thirdPartyViewerPath = RxnString(); static RxDouble autoModeInterval = 2.0.obs; @@ -165,6 +166,12 @@ class ReadSetting { readDirection.value = value; _save(); } + + static saveImageRegionWidthRatio(int value) { + Log.debug('saveImageRegionWidthRatio:$value'); + imageRegionWidthRatio.value = value; + _save(); + } static saveUseThirdPartyViewer(bool value) { Log.debug('saveUseThirdPartyViewer:$value'); @@ -266,6 +273,7 @@ class ReadSetting { 'autoModeStyle': autoModeStyle.value.index, 'deviceDirection': deviceDirection.value.index, 'readDirection': readDirection.value.index, + 'imageRegionWidthRatio': imageRegionWidthRatio.value, 'useThirdPartyViewer': useThirdPartyViewer.value, 'thirdPartyViewerPath': thirdPartyViewerPath.value, 'turnPageMode': turnPageMode.value.index, @@ -294,6 +302,7 @@ class ReadSetting { autoModeStyle.value = AutoModeStyle.values[map['autoModeStyle'] ?? AutoModeStyle.scroll.index]; deviceDirection.value = DeviceDirection.values[map['deviceDirection'] ?? DeviceDirection.followSystem.index]; readDirection.value = ReadDirection.values[map['readDirection']]; + imageRegionWidthRatio.value = map['imageRegionWidthRatio'] ?? imageRegionWidthRatio.value; useThirdPartyViewer.value = map['useThirdPartyViewer'] ?? useThirdPartyViewer.value; thirdPartyViewerPath.value = map['thirdPartyViewerPath']; turnPageMode.value = TurnPageMode.values[map['turnPageMode']]; diff --git a/lib/src/utils/text_input_formatter.dart b/lib/src/utils/text_input_formatter.dart new file mode 100644 index 00000000..374a9282 --- /dev/null +++ b/lib/src/utils/text_input_formatter.dart @@ -0,0 +1,51 @@ +import 'package:flutter/services.dart'; + +class DoubleRangeTextInputFormatter extends TextInputFormatter { + double? minValue; + double? maxValue; + + DoubleRangeTextInputFormatter({this.minValue, this.maxValue}) : assert(minValue != null || maxValue != null); + + @override + TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) { + if (newValue.text.isEmpty || (minValue != null && minValue! < 0 && newValue.text == '-')) { + return newValue; + } + + double newNum = double.tryParse(newValue.text) ?? minValue ?? maxValue ?? -100; + + if (minValue != null && newNum < minValue!) { + return oldValue; + } + if (maxValue != null && newNum > maxValue!) { + return oldValue; + } + + return newValue; + } +} + +class IntRangeTextInputFormatter extends TextInputFormatter { + int? minValue; + int? maxValue; + + IntRangeTextInputFormatter({this.minValue, this.maxValue}) : assert(minValue != null || maxValue != null); + + @override + TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) { + if (newValue.text.isEmpty || (minValue != null && minValue! < 0 && newValue.text == '-')) { + return newValue; + } + + int newNum = int.tryParse(newValue.text) ?? -100; + + if (minValue != null && newNum < minValue!) { + return TextEditingValue(text: minValue.toString()); + } + if (maxValue != null && newNum > maxValue!) { + return TextEditingValue(text: maxValue.toString()); + } + + return newValue; + } +}