diff --git a/lib/basic/config/Address.dart b/lib/basic/config/Address.dart index 034ef5f..fb1cd10 100644 --- a/lib/basic/config/Address.dart +++ b/lib/basic/config/Address.dart @@ -21,9 +21,7 @@ Future initAddress() async { _currentAddress = await method.getSwitchAddress(); } -String currentAddressName() { - return _addresses[_currentAddress] ?? ""; -} +String currentAddressName() => _addresses[_currentAddress] ?? ""; Future chooseAddress(BuildContext context) async { String? choose = await showDialog( diff --git a/lib/basic/config/ImageAddress.dart b/lib/basic/config/ImageAddress.dart index 0769fd0..e492d24 100644 --- a/lib/basic/config/ImageAddress.dart +++ b/lib/basic/config/ImageAddress.dart @@ -21,9 +21,7 @@ int currentImageAddress() { return int.parse(_currentImageAddress); } -String currentImageAddressName() { - return _imageAddresses[_currentImageAddress] ?? ""; -} +String currentImageAddressName() => _imageAddresses[_currentImageAddress] ?? ""; Future chooseImageAddress(BuildContext context) async { String? choose = await showDialog( diff --git a/lib/basic/config/ReaderSliderPosition.dart b/lib/basic/config/ReaderSliderPosition.dart new file mode 100644 index 0000000..412bc88 --- /dev/null +++ b/lib/basic/config/ReaderSliderPosition.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:pikapika/basic/Method.dart'; + +import '../Common.dart'; + +enum ReaderSliderPosition { BOTTOM, RIGHT, LEFT } + +const _positionNames = { + ReaderSliderPosition.BOTTOM: '下方', + ReaderSliderPosition.RIGHT: '右侧', + ReaderSliderPosition.LEFT: '左侧', +}; + +const _propertyName = "readerSliderPosition"; +late ReaderSliderPosition _readerSliderPosition; + +Future initReaderSliderPosition() async { + _readerSliderPosition = _readerSliderPositionFromString( + await method.loadProperty(_propertyName, ""), + ); +} + +ReaderSliderPosition _readerSliderPositionFromString(String str) { + for (var value in ReaderSliderPosition.values) { + if (str == value.toString()) return value; + } + return ReaderSliderPosition.BOTTOM; +} + +ReaderSliderPosition currentReaderSliderPosition() => _readerSliderPosition; + +String currentReaderSliderPositionName() => + _positionNames[_readerSliderPosition] ?? ""; + +Future chooseReaderSliderPosition(BuildContext context) async { + Map map = {}; + _positionNames.forEach((key, value) { + map[value] = key; + }); + ReaderSliderPosition? result = + await chooseMapDialog(context, map, "选择操控方式"); + if (result != null) { + await method.saveProperty(_propertyName, result.toString()); + _readerSliderPosition = result; + } +} + +Widget readerSliderPositionSetting() { + return StatefulBuilder( + builder: (BuildContext context, void Function(void Function()) setState) { + return ListTile( + title: Text("滚动条的位置"), + subtitle: Text(currentReaderSliderPositionName()), + onTap: () async { + await chooseReaderSliderPosition(context); + setState(() {}); + }, + ); + }, + ); +} diff --git a/lib/screens/InitScreen.dart b/lib/screens/InitScreen.dart index 4b7897b..1f125e1 100644 --- a/lib/screens/InitScreen.dart +++ b/lib/screens/InitScreen.dart @@ -19,6 +19,7 @@ import 'package:pikapika/basic/config/Platform.dart'; import 'package:pikapika/basic/config/Proxy.dart'; import 'package:pikapika/basic/config/Quality.dart'; import 'package:pikapika/basic/config/ReaderDirection.dart'; +import 'package:pikapika/basic/config/ReaderSliderPosition.dart'; import 'package:pikapika/basic/config/ReaderType.dart'; import 'package:pikapika/basic/config/ShadowCategories.dart'; import 'package:pikapika/basic/config/Themes.dart'; @@ -58,6 +59,7 @@ class _InitScreenState extends State { await initListLayout(); await initReaderType(); await initReaderDirection(); + await initReaderSliderPosition(); await initAutoFullScreen(); await initFullScreenAction(); await initPagerAction(); diff --git a/lib/screens/SettingsScreen.dart b/lib/screens/SettingsScreen.dart index 138056a..9e0ee21 100644 --- a/lib/screens/SettingsScreen.dart +++ b/lib/screens/SettingsScreen.dart @@ -17,6 +17,7 @@ import 'package:pikapika/basic/config/KeyboardController.dart'; import 'package:pikapika/basic/config/NoAnimation.dart'; import 'package:pikapika/basic/config/PagerAction.dart'; import 'package:pikapika/basic/config/ReaderDirection.dart'; +import 'package:pikapika/basic/config/ReaderSliderPosition.dart'; import 'package:pikapika/basic/config/ReaderType.dart'; import 'package:pikapika/basic/config/Quality.dart'; import 'package:pikapika/basic/config/ShadowCategories.dart'; @@ -43,6 +44,7 @@ class SettingsScreen extends StatelessWidget { convertToPNGSetting(), readerTypeSetting(), readerDirectionSetting(), + readerSliderPositionSetting(), autoFullScreenSetting(), fullScreenActionSetting(), volumeControllerSetting(), diff --git a/lib/screens/components/ImageReader.dart b/lib/screens/components/ImageReader.dart index 0a606cd..a36d45a 100644 --- a/lib/screens/components/ImageReader.dart +++ b/lib/screens/components/ImageReader.dart @@ -19,6 +19,7 @@ import 'package:pikapika/basic/config/KeyboardController.dart'; import 'package:pikapika/basic/config/NoAnimation.dart'; import 'package:pikapika/basic/config/Quality.dart'; import 'package:pikapika/basic/config/ReaderDirection.dart'; +import 'package:pikapika/basic/config/ReaderSliderPosition.dart'; import 'package:pikapika/basic/config/ReaderType.dart'; import 'package:pikapika/basic/config/VolumeController.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; @@ -152,20 +153,20 @@ class _ImageReaderState extends State { // 记录初始阅读器类型 final ReaderType _pagerType = currentReaderType(); - // 记录开始的画质 - final _currentQuality = currentQualityCode(); - // 记录了控制器 late FullScreenAction _fullScreenAction = currentFullScreenAction(); + late ReaderSliderPosition _readerSliderPosition = + currentReaderSliderPosition(); + @override Widget build(BuildContext context) { return _ImageReaderContent( widget.struct, _pagerDirection, _pagerType, - _currentQuality, _fullScreenAction, + _readerSliderPosition, ); } } @@ -179,18 +180,18 @@ class _ImageReaderContent extends StatefulWidget { // 记录初始阅读器类型 final ReaderType pagerType; - // 记录开始的画质 - final String currentQuality; final FullScreenAction fullScreenAction; + final ReaderSliderPosition readerSliderPosition; + final ImageReaderStruct struct; const _ImageReaderContent( this.struct, this.pagerDirection, this.pagerType, - this.currentQuality, this.fullScreenAction, + this.readerSliderPosition, ); @override @@ -298,121 +299,208 @@ abstract class _ImageReaderContentState extends State<_ImageReaderContent> { } Widget _buildBar() { - return Column( - children: [ - widget.struct.fullScreen - ? Container() - : AppBar( - title: Text( - "${widget.struct.epNameMap[widget.struct.epOrder] ?? ""} - ${widget.struct.comicTitle}"), - actions: [ - IconButton( - onPressed: _onChooseEp, - icon: Icon(Icons.menu_open), - ), - IconButton( - onPressed: _onMoreSetting, - icon: Icon(Icons.more_horiz), + switch (widget.readerSliderPosition) { + case ReaderSliderPosition.BOTTOM: + return Column( + children: [ + _buildAppBar(), + Expanded(child: _buildController()), + widget.struct.fullScreen + ? Container() + : Container( + height: 45, + color: Color(0x88000000), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container(width: 15), + IconButton( + icon: Icon(Icons.fullscreen), + color: Colors.white, + onPressed: () { + widget.struct + .onFullScreenChange(!widget.struct.fullScreen); + }, + ), + Container(width: 10), + Expanded( + child: + widget.pagerType != ReaderType.WEB_TOON_FREE_ZOOM + ? _buildSliderBottom() + : Container(), + ), + Container(width: 10), + IconButton( + icon: Icon(Icons.skip_next_outlined), + color: Colors.white, + onPressed: _onNextAction, + ), + Container(width: 15), + ], + ), ), + ], + ); + case ReaderSliderPosition.RIGHT: + return Column( + children: [ + _buildAppBar(), + Expanded( + child: Stack( + children: [ + _buildController(), + _buildSliderRight(), ], ), - Expanded(child: _buildController()), - widget.struct.fullScreen - ? Container() - : Container( - height: 45, - color: Color(0x88000000), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container(width: 15), - IconButton( - icon: Icon(Icons.fullscreen), - color: Colors.white, - onPressed: () { - widget.struct - .onFullScreenChange(!widget.struct.fullScreen); - }, - ), - Container(width: 10), - Expanded( - child: widget.pagerType != ReaderType.WEB_TOON_FREE_ZOOM - ? _buildSlider() - : Container(), - ), - Container(width: 10), - IconButton( - icon: Icon(Icons.skip_next_outlined), - color: Colors.white, - onPressed: _onNextAction, - ), - Container(width: 15), - ], - ), + ), + ], + ); + case ReaderSliderPosition.LEFT: + return Column( + children: [ + _buildAppBar(), + Expanded( + child: Stack( + children: [ + _buildController(), + _buildSliderLeft(), + ], ), - ], - ); + ), + ], + ); + } + return Container(); } - Widget _buildSlider() { + Widget _buildAppBar() => widget.struct.fullScreen + ? Container() + : AppBar( + title: Text( + "${widget.struct.epNameMap[widget.struct.epOrder] ?? ""} - ${widget.struct.comicTitle}"), + actions: [ + IconButton( + onPressed: _onChooseEp, + icon: Icon(Icons.menu_open), + ), + IconButton( + onPressed: _onMoreSetting, + icon: Icon(Icons.more_horiz), + ), + ], + ); + + Widget _buildSliderBottom() { return Column( children: [ Expanded(child: Container()), Container( height: 25, - child: FlutterSlider( - axis: Axis.horizontal, - values: [_slider.toDouble()], - min: 0, - max: (widget.struct.images.length - 1).toDouble(), - onDragging: (handlerIndex, lowerValue, upperValue) { - _slider = (lowerValue.toInt()); - }, - onDragCompleted: (handlerIndex, lowerValue, upperValue) { - _slider = (lowerValue.toInt()); - if (_slider != _current) { - _needJumpTo(_slider, false); - } - }, - trackBar: FlutterSliderTrackBar( - inactiveTrackBar: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.grey.shade300, - ), - activeTrackBar: BoxDecoration( - borderRadius: BorderRadius.circular(4), - color: Theme.of(context).colorScheme.secondary, - ), - ), - step: FlutterSliderStep( - step: 1, - isPercentRange: false, - ), - tooltip: FlutterSliderTooltip(custom: (value) { - double a = value + 1; - return Container( - padding: EdgeInsets.all(8), - decoration: ShapeDecoration( - color: Colors.black.withAlpha(0xCC), - shape: RoundedRectangleBorder( - borderRadius: BorderRadiusDirectional.circular(3)), - ), - child: Text( - '${a.toInt()}', - style: TextStyle( - color: Colors.white, - fontSize: 18, - ), - ), - ); - }), - ), + child: _buildSliderWidget(Axis.horizontal), ), Expanded(child: Container()), ], ); } + Widget _buildSliderLeft() => widget.struct.fullScreen + ? Container() + : Align( + alignment: Alignment.centerLeft, + child: Material( + color: Color(0x0), + child: Container( + width: 35, + height: 300, + decoration: BoxDecoration( + color: Color(0x66000000), + borderRadius: BorderRadius.only( + topRight: Radius.circular(10), + bottomRight: Radius.circular(10), + ), + ), + padding: EdgeInsets.only(top: 10, bottom: 10, left: 6, right: 5), + child: Center( + child: _buildSliderWidget(Axis.vertical), + ), + ), + ), + ); + + Widget _buildSliderRight() => widget.struct.fullScreen + ? Container() + : Align( + alignment: Alignment.centerRight, + child: Material( + color: Color(0x0), + child: Container( + width: 35, + height: 300, + decoration: BoxDecoration( + color: Color(0x66000000), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10), + bottomLeft: Radius.circular(10), + ), + ), + padding: EdgeInsets.only(top: 10, bottom: 10, left: 5, right: 6), + child: Center( + child: _buildSliderWidget(Axis.vertical), + ), + ), + ), + ); + + Widget _buildSliderWidget(Axis axis) { + return FlutterSlider( + axis: axis, + values: [_slider.toDouble()], + min: 0, + max: (widget.struct.images.length - 1).toDouble(), + onDragging: (handlerIndex, lowerValue, upperValue) { + _slider = (lowerValue.toInt()); + }, + onDragCompleted: (handlerIndex, lowerValue, upperValue) { + _slider = (lowerValue.toInt()); + if (_slider != _current) { + _needJumpTo(_slider, false); + } + }, + trackBar: FlutterSliderTrackBar( + inactiveTrackBar: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.grey.shade300, + ), + activeTrackBar: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: Theme.of(context).colorScheme.secondary, + ), + ), + step: FlutterSliderStep( + step: 1, + isPercentRange: false, + ), + tooltip: FlutterSliderTooltip(custom: (value) { + double a = value + 1; + return Container( + padding: EdgeInsets.all(8), + decoration: ShapeDecoration( + color: Colors.black.withAlpha(0xCC), + shape: RoundedRectangleBorder( + borderRadius: BorderRadiusDirectional.circular(3)), + ), + child: Text( + '${a.toInt()}', + style: TextStyle( + color: Colors.white, + fontSize: 18, + ), + ), + ); + }), + ); + } + Widget _buildController() { switch (currentFullScreenAction()) { case FullScreenAction.CONTROLLER: @@ -427,7 +515,8 @@ abstract class _ImageReaderContentState extends State<_ImageReaderContent> { } Widget _buildFullScreenController() { - if (!widget.struct.fullScreen) { + if (widget.readerSliderPosition == ReaderSliderPosition.BOTTOM && + !widget.struct.fullScreen) { return Container(); } return Align( @@ -526,22 +615,10 @@ abstract class _ImageReaderContentState extends State<_ImageReaderContent> { ]); break; } - return Container( + return SizedBox( width: constraints.maxWidth, height: constraints.maxHeight, - child: Column( - children: [ - Container( - height: widget.struct.fullScreen - ? 0 - : Scaffold.of(context).appBarMaxHeight ?? 0, - ), - Expanded(child: child), - Container( - height: widget.struct.fullScreen ? 0 : 45, - ), - ], - ), + child: child, ); }, ); @@ -565,6 +642,10 @@ abstract class _ImageReaderContentState extends State<_ImageReaderContent> { } Future _onMoreSetting() async { + // 记录开始的画质 + final currentQuality = currentQualityCode(); + final cReaderSliderPosition = currentReaderSliderPosition(); + // await showMaterialModalBottomSheet( context: context, backgroundColor: Color(0xAA000000), @@ -580,8 +661,9 @@ abstract class _ImageReaderContentState extends State<_ImageReaderContent> { ); if (widget.pagerDirection != gReaderDirection || widget.pagerType != currentReaderType() || - widget.currentQuality != currentQualityCode() || - widget.fullScreenAction != currentFullScreenAction()) { + currentQuality != currentQualityCode() || + widget.fullScreenAction != currentFullScreenAction() || + cReaderSliderPosition != currentReaderSliderPosition()) { widget.struct.onReloadEp(); } } @@ -599,13 +681,10 @@ abstract class _ImageReaderContentState extends State<_ImageReaderContent> { bool _hasNextEp() => widget.struct.epNameMap.containsKey(widget.struct.epOrder + 1); - double _topBarHeight() { - return Scaffold.of(context).appBarMaxHeight ?? 0; - } + double _topBarHeight() => Scaffold.of(context).appBarMaxHeight ?? 0; - double _bottomBarHeight() { - return 45; - } + double _bottomBarHeight() => + widget.readerSliderPosition == ReaderSliderPosition.BOTTOM ? 45 : 0; } class _EpChooser extends StatefulWidget { @@ -740,6 +819,20 @@ class _SettingPanelState extends State<_SettingPanel> { ], ), ), + Row( + children: [ + _bottomIcon( + icon: Icons.straighten_sharp, + title: currentReaderSliderPositionName(), + onPressed: () async { + await chooseReaderSliderPosition(context); + }, + ), + Expanded(child: Container()), + Expanded(child: Container()), + Expanded(child: Container()), + ], + ), ], ); } @@ -1415,7 +1508,44 @@ class _GalleryReaderState extends _ImageReaderContentState { ), child: gallery, ); - return gallery; + return Stack( + children: [ + gallery, + _buildNextEpController(), + ], + ); + } + + Widget _buildNextEpController() { + if (_current < widget.struct.images.length - 1) return Container(); + return Align( + alignment: Alignment.bottomRight, + child: Material( + color: Color(0x0), + child: Container( + margin: EdgeInsets.only(bottom: 10), + padding: EdgeInsets.only(left: 10, right: 10, top: 4, bottom: 4), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10), + bottomLeft: Radius.circular(10), + ), + color: Color(0x88000000), + ), + child: GestureDetector( + onTap: () { + if (_hasNextEp()) { + _onNextAction(); + } else { + Navigator.of(context).pop(); + } + }, + child: Text(_hasNextEp() ? '下一章' : '结束阅读', + style: TextStyle(color: Colors.white)), + ), + ), + ), + ); } }