new reader slider

This commit is contained in:
niuhuan 2021-11-30 18:49:51 +08:00
parent c95771e637
commit b5868f3fe9
8 changed files with 415 additions and 497 deletions

View File

@ -44,7 +44,7 @@ var switchAddresses = map[int]string{
} }
var switchAddress = 1 var switchAddress = 1
var switchAddressPattern, _ = regexp.Compile("^.+pica" + "comic\\.com:\\d+$") var switchAddressPattern, _ = regexp.Compile("^.+picacomic\\.com:\\d+$")
func switchAddressContext(ctx context.Context, network, addr string) (net.Conn, error) { func switchAddressContext(ctx context.Context, network, addr string) (net.Conn, error) {
if sAddr, ok := switchAddresses[switchAddress]; ok { if sAddr, ok := switchAddresses[switchAddress]; ok {

5
lib/basic/const.dart Normal file
View File

@ -0,0 +1,5 @@
import 'package:flutter/material.dart';
var readerAppbarColor = Color(0xff1e202c);
var readerAppbarColor2 = readerAppbarColor.withAlpha(225);

View File

@ -332,7 +332,7 @@ class _ComicInfoScreenState extends State<ComicInfoScreen> with RouteAware {
comicInfo: comicInfo, comicInfo: comicInfo,
epList: epList, epList: epList,
currentEpOrder: order, currentEpOrder: order,
initPictureRank: rank, initPicturePosition: rank,
), ),
), ),
); );

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:pikapika/basic/Common.dart';
import 'package:pikapika/basic/Entities.dart'; import 'package:pikapika/basic/Entities.dart';
import 'package:pikapika/basic/Method.dart'; import 'package:pikapika/basic/Method.dart';
import 'package:pikapika/basic/config/AutoFullScreen.dart'; import 'package:pikapika/basic/config/AutoFullScreen.dart';
@ -9,6 +10,7 @@ import 'package:pikapika/basic/config/FullScreenUI.dart';
import 'package:pikapika/basic/config/Quality.dart'; import 'package:pikapika/basic/config/Quality.dart';
import 'package:pikapika/basic/config/ReaderDirection.dart'; import 'package:pikapika/basic/config/ReaderDirection.dart';
import 'package:pikapika/basic/config/ReaderType.dart'; import 'package:pikapika/basic/config/ReaderType.dart';
import 'package:pikapika/basic/const.dart';
import 'package:pikapika/screens/components/ContentBuilder.dart'; import 'package:pikapika/screens/components/ContentBuilder.dart';
import 'components/ImageReader.dart'; import 'components/ImageReader.dart';
@ -17,7 +19,7 @@ class ComicReaderScreen extends StatefulWidget {
final ComicInfo comicInfo; final ComicInfo comicInfo;
final List<Ep> epList; final List<Ep> epList;
final currentEpOrder; final currentEpOrder;
final int? initPictureRank; final int? initPicturePosition;
final ReaderType pagerType = currentReaderType(); final ReaderType pagerType = currentReaderType();
final ReaderDirection pagerDirection = gReaderDirection; final ReaderDirection pagerDirection = gReaderDirection;
late final bool autoFullScreen; late final bool autoFullScreen;
@ -27,7 +29,7 @@ class ComicReaderScreen extends StatefulWidget {
required this.comicInfo, required this.comicInfo,
required this.epList, required this.epList,
required this.currentEpOrder, required this.currentEpOrder,
this.initPictureRank, this.initPicturePosition,
bool? autoFullScreen, bool? autoFullScreen,
}) : super(key: key) { }) : super(key: key) {
this.autoFullScreen = autoFullScreen ?? currentAutoFullScreen(); this.autoFullScreen = autoFullScreen ?? currentAutoFullScreen();
@ -45,8 +47,8 @@ class _ComicReaderScreenState extends State<ComicReaderScreen> {
bool _replacement = false; bool _replacement = false;
Future<List<RemoteImageInfo>> _load() async { Future<List<RemoteImageInfo>> _load() async {
if (widget.initPictureRank == null) { if (widget.initPicturePosition == null) {
await method.storeViewEp(widget.comicInfo.id, _ep.order, _ep.title, 1); await method.storeViewEp(widget.comicInfo.id, _ep.order, _ep.title, 0);
} }
List<RemoteImageInfo> list = []; List<RemoteImageInfo> list = [];
var _needLoadPage = 0; var _needLoadPage = 0;
@ -70,11 +72,13 @@ class _ComicReaderScreenState extends State<ComicReaderScreen> {
} }
Future _onPositionChange(int position) async { Future _onPositionChange(int position) async {
_lastChangeRank = position + 1; _lastChangeRank = position;
return method.storeViewEp( return method.storeViewEp(
widget.comicInfo.id, _ep.order, _ep.title, position + 1); widget.comicInfo.id, _ep.order, _ep.title, position);
} }
FutureOr<dynamic> Function() _previousAction = () => null;
String _nextText = ""; String _nextText = "";
FutureOr<dynamic> Function() _nextAction = () => null; FutureOr<dynamic> Function() _nextAction = () => null;
@ -85,6 +89,23 @@ class _ComicReaderScreenState extends State<ComicReaderScreen> {
widget.epList.forEach((element) { widget.epList.forEach((element) {
orderMap[element.order] = element; orderMap[element.order] = element;
}); });
if (orderMap.containsKey(widget.currentEpOrder - 1)) {
_previousAction = () {
_replacement = true;
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => ComicReaderScreen(
comicInfo: widget.comicInfo,
epList: widget.epList,
currentEpOrder: widget.currentEpOrder - 1,
autoFullScreen: _fullScreen,
),
),
);
};
} else {
_previousAction = () => defaultToast(context, "已经到头了");
}
if (orderMap.containsKey(widget.currentEpOrder + 1)) { if (orderMap.containsKey(widget.currentEpOrder + 1)) {
_nextText = "下一章"; _nextText = "下一章";
_nextAction = () { _nextAction = () {
@ -135,6 +156,7 @@ class _ComicReaderScreenState extends State<ComicReaderScreen> {
appBar: _fullScreen appBar: _fullScreen
? null ? null
: AppBar( : AppBar(
backgroundColor: readerAppbarColor,
title: Text("${_ep.title} - ${widget.comicInfo.title}"), title: Text("${_ep.title} - ${widget.comicInfo.title}"),
actions: [ actions: [
IconButton( IconButton(
@ -164,8 +186,8 @@ class _ComicReaderScreenState extends State<ComicReaderScreen> {
_future = _load(); _future = _load();
}); });
}, },
successBuilder: successBuilder: (BuildContext context,
(BuildContext context, AsyncSnapshot<List<RemoteImageInfo>> snapshot) { AsyncSnapshot<List<RemoteImageInfo>> snapshot) {
return ImageReader( return ImageReader(
ImageReaderStruct( ImageReaderStruct(
images: snapshot.data! images: snapshot.data!
@ -182,11 +204,10 @@ class _ComicReaderScreenState extends State<ComicReaderScreen> {
fullScreen: _fullScreen, fullScreen: _fullScreen,
onFullScreenChange: _onFullScreenChange, onFullScreenChange: _onFullScreenChange,
onNextText: _nextText, onNextText: _nextText,
onPreviousAction: _previousAction,
onNextAction: _nextAction, onNextAction: _nextAction,
onPositionChange: _onPositionChange, onPositionChange: _onPositionChange,
initPosition: widget.initPictureRank == null initPosition: widget.initPicturePosition,
? null
: widget.initPictureRank! - 1,
pagerType: widget.pagerType, pagerType: widget.pagerType,
pagerDirection: widget.pagerDirection, pagerDirection: widget.pagerDirection,
), ),
@ -212,7 +233,7 @@ class _ComicReaderScreenState extends State<ComicReaderScreen> {
comicInfo: widget.comicInfo, comicInfo: widget.comicInfo,
epList: widget.epList, epList: widget.epList,
currentEpOrder: widget.currentEpOrder, currentEpOrder: widget.currentEpOrder,
initPictureRank: _lastChangeRank ?? widget.initPictureRank, initPicturePosition: _lastChangeRank ?? widget.initPicturePosition,
// maybe null // maybe null
autoFullScreen: _fullScreen, autoFullScreen: _fullScreen,
), ),

View File

@ -176,7 +176,7 @@ class _DownloadInfoScreenState extends State<DownloadInfoScreen>
comicInfo: _task, comicInfo: _task,
epList: _epList, epList: _epList,
currentEpOrder: epOrder, currentEpOrder: epOrder,
initPictureRank: rank, initPicturePosition: rank,
), ),
), ),
); );

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:pikapika/basic/Common.dart';
import 'package:pikapika/basic/Entities.dart'; import 'package:pikapika/basic/Entities.dart';
import 'package:pikapika/basic/config/AutoFullScreen.dart'; import 'package:pikapika/basic/config/AutoFullScreen.dart';
import 'package:pikapika/basic/config/FullScreenUI.dart'; import 'package:pikapika/basic/config/FullScreenUI.dart';
@ -16,7 +17,7 @@ class DownloadReaderScreen extends StatefulWidget {
final DownloadComic comicInfo; final DownloadComic comicInfo;
final List<DownloadEp> epList; final List<DownloadEp> epList;
final int currentEpOrder; final int currentEpOrder;
final int? initPictureRank; final int? initPicturePosition;
final ReaderType pagerType = currentReaderType(); final ReaderType pagerType = currentReaderType();
final ReaderDirection pagerDirection = gReaderDirection; final ReaderDirection pagerDirection = gReaderDirection;
late final bool autoFullScreen; late final bool autoFullScreen;
@ -26,7 +27,7 @@ class DownloadReaderScreen extends StatefulWidget {
required this.comicInfo, required this.comicInfo,
required this.epList, required this.epList,
required this.currentEpOrder, required this.currentEpOrder,
this.initPictureRank, this.initPicturePosition,
bool? autoFullScreen, bool? autoFullScreen,
}) : super(key: key) { }) : super(key: key) {
this.autoFullScreen = autoFullScreen ?? currentAutoFullScreen(); this.autoFullScreen = autoFullScreen ?? currentAutoFullScreen();
@ -45,8 +46,8 @@ class _DownloadReaderScreenState extends State<DownloadReaderScreen> {
bool _replacement = false; bool _replacement = false;
Future _load() async { Future _load() async {
if (widget.initPictureRank == null) { if (widget.initPicturePosition == null) {
await method.storeViewEp(widget.comicInfo.id, _ep.epOrder, _ep.title, 1); await method.storeViewEp(widget.comicInfo.id, _ep.epOrder, _ep.title, 0);
} }
pictures.clear(); pictures.clear();
for (var ep in widget.epList) { for (var ep in widget.epList) {
@ -63,11 +64,13 @@ class _DownloadReaderScreenState extends State<DownloadReaderScreen> {
} }
Future _onPositionChange(int position) async { Future _onPositionChange(int position) async {
_lastChangeRank = position + 1; _lastChangeRank = position;
return method.storeViewEp( return method.storeViewEp(
widget.comicInfo.id, _ep.epOrder, _ep.title, position + 1); widget.comicInfo.id, _ep.epOrder, _ep.title, position);
} }
FutureOr<dynamic> Function() _previousAction = () => null;
String _nextText = ""; String _nextText = "";
FutureOr<dynamic> Function() _nextAction = () => null; FutureOr<dynamic> Function() _nextAction = () => null;
@ -78,6 +81,23 @@ class _DownloadReaderScreenState extends State<DownloadReaderScreen> {
widget.epList.forEach((element) { widget.epList.forEach((element) {
orderMap[element.epOrder] = element; orderMap[element.epOrder] = element;
}); });
if (orderMap.containsKey(widget.currentEpOrder - 1)) {
_previousAction = () {
_replacement = true;
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => DownloadReaderScreen(
comicInfo: widget.comicInfo,
epList: widget.epList,
currentEpOrder: widget.currentEpOrder - 1,
autoFullScreen: _fullScreen,
),
),
);
};
} else {
_previousAction = () => defaultToast(context, "已经到头了");
}
if (orderMap.containsKey(widget.currentEpOrder + 1)) { if (orderMap.containsKey(widget.currentEpOrder + 1)) {
_nextText = "下一章"; _nextText = "下一章";
_nextAction = () { _nextAction = () {
@ -168,11 +188,10 @@ class _DownloadReaderScreenState extends State<DownloadReaderScreen> {
fullScreen: _fullScreen, fullScreen: _fullScreen,
onFullScreenChange: _onFullScreenChange, onFullScreenChange: _onFullScreenChange,
onNextText: _nextText, onNextText: _nextText,
onPreviousAction: _previousAction,
onNextAction: _nextAction, onNextAction: _nextAction,
onPositionChange: _onPositionChange, onPositionChange: _onPositionChange,
initPosition: widget.initPictureRank == null initPosition: widget.initPicturePosition,
? null
: widget.initPictureRank! - 1,
pagerType: widget.pagerType, pagerType: widget.pagerType,
pagerDirection: widget.pagerDirection, pagerDirection: widget.pagerDirection,
), ),
@ -199,7 +218,7 @@ class _DownloadReaderScreenState extends State<DownloadReaderScreen> {
comicInfo: widget.comicInfo, comicInfo: widget.comicInfo,
epList: widget.epList, epList: widget.epList,
currentEpOrder: widget.currentEpOrder, currentEpOrder: widget.currentEpOrder,
initPictureRank: _lastChangeRank ?? widget.initPictureRank, initPicturePosition: _lastChangeRank ?? widget.initPicturePosition,
// maybe null // maybe null
autoFullScreen: _fullScreen, autoFullScreen: _fullScreen,
), ),

View File

@ -37,7 +37,7 @@ class _ContinueReadButtonState extends State<ContinueReadButton> {
snapshot.data?.lastViewPictureRank, snapshot.data?.lastViewPictureRank,
); );
text = text =
'继续阅读 ${snapshot.data?.lastViewEpTitle} P. ${snapshot.data?.lastViewPictureRank}'; '继续阅读 ${snapshot.data?.lastViewEpTitle} P. ${(snapshot.data?.lastViewPictureRank ?? 0) + 1}';
} else { } else {
onPressed = () => widget.onChoose(null, null); onPressed = () => widget.onChoose(null, null);
text = '开始阅读'; text = '开始阅读';

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:another_xlider/another_xlider.dart'; import 'package:another_xlider/another_xlider.dart';
import 'package:event/event.dart'; import 'package:event/event.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:photo_view/photo_view_gallery.dart'; import 'package:photo_view/photo_view_gallery.dart';
@ -11,11 +12,11 @@ import 'package:pikapika/basic/Cross.dart';
import 'package:pikapika/basic/Entities.dart'; import 'package:pikapika/basic/Entities.dart';
import 'package:pikapika/basic/Method.dart'; import 'package:pikapika/basic/Method.dart';
import 'package:pikapika/basic/config/FullScreenAction.dart'; import 'package:pikapika/basic/config/FullScreenAction.dart';
import 'package:pikapika/basic/config/GalleryPreloadCount.dart';
import 'package:pikapika/basic/config/KeyboardController.dart'; import 'package:pikapika/basic/config/KeyboardController.dart';
import 'package:pikapika/basic/config/NoAnimation.dart'; import 'package:pikapika/basic/config/NoAnimation.dart';
import 'package:pikapika/basic/config/ReaderDirection.dart'; import 'package:pikapika/basic/config/ReaderDirection.dart';
import 'package:pikapika/basic/config/ReaderType.dart'; import 'package:pikapika/basic/config/ReaderType.dart';
import 'package:pikapika/basic/const.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import '../FilePhotoViewScreen.dart'; import '../FilePhotoViewScreen.dart';
import 'gesture_zoom_box.dart'; import 'gesture_zoom_box.dart';
@ -69,7 +70,7 @@ var _volumeListenCount = 0;
EventChannel volumeButtonChannel = EventChannel("volume_button"); EventChannel volumeButtonChannel = EventChannel("volume_button");
StreamSubscription? volumeS; StreamSubscription? volumeS;
addVolumeListen() { void addVolumeListen() {
_volumeListenCount++; _volumeListenCount++;
if (_volumeListenCount == 1) { if (_volumeListenCount == 1) {
volumeS = volumeS =
@ -77,7 +78,7 @@ addVolumeListen() {
} }
} }
delVolumeListen() { void delVolumeListen() {
_volumeListenCount--; _volumeListenCount--;
if (_volumeListenCount == 0) { if (_volumeListenCount == 0) {
volumeS?.cancel(); volumeS?.cancel();
@ -106,6 +107,7 @@ class ImageReaderStruct {
final bool fullScreen; final bool fullScreen;
final FutureOr<dynamic> Function(bool fullScreen) onFullScreenChange; final FutureOr<dynamic> Function(bool fullScreen) onFullScreenChange;
final String onNextText; final String onNextText;
final FutureOr<dynamic> Function() onPreviousAction;
final FutureOr<dynamic> Function() onNextAction; final FutureOr<dynamic> Function() onNextAction;
final FutureOr<dynamic> Function(int) onPositionChange; final FutureOr<dynamic> Function(int) onPositionChange;
final int? initPosition; final int? initPosition;
@ -117,6 +119,7 @@ class ImageReaderStruct {
required this.fullScreen, required this.fullScreen,
required this.onFullScreenChange, required this.onFullScreenChange,
required this.onNextText, required this.onNextText,
required this.onPreviousAction,
required this.onNextAction, required this.onNextAction,
required this.onPositionChange, required this.onPositionChange,
this.initPosition, this.initPosition,
@ -127,51 +130,259 @@ class ImageReaderStruct {
// //
class ImageReader extends StatelessWidget { class ImageReader extends StatefulWidget {
final ImageReaderStruct struct; final ImageReaderStruct struct;
const ImageReader(this.struct); const ImageReader(this.struct);
@override @override
Widget build(BuildContext context) { State<StatefulWidget> createState() {
late Widget reader;
switch (struct.pagerType) { switch (struct.pagerType) {
case ReaderType.WEB_TOON: case ReaderType.WEB_TOON:
reader = _WebToonReader(struct); return _WebToonReaderState();
break;
case ReaderType.WEB_TOON_ZOOM: case ReaderType.WEB_TOON_ZOOM:
reader = _WebToonZoomReader(struct); return _WebToonZoomReaderState();
break;
case ReaderType.GALLERY: case ReaderType.GALLERY:
reader = _GalleryReader(struct); return _GalleryReaderState();
break;
default: default:
reader = Container(); throw Exception("ERROR READER TYPE");
break;
} }
switch (currentFullScreenAction()) { }
case FullScreenAction.CONTROLLER: }
reader = Stack(
abstract class _ImageReaderState extends State<ImageReader> {
//
Widget _buildViewer();
// ,
void _needJumpTo(int index, bool animation);
@override
void initState() {
_initCurrent();
_readerControllerEvent.subscribe(_onPageControl);
super.initState();
}
@override
void dispose() {
_readerControllerEvent.unsubscribe(_onPageControl);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [ children: [
reader, _buildViewer(),
_buildFullScreenController( _buildControllerAndBar(),
struct.fullScreen,
struct.onFullScreenChange,
),
], ],
); );
}
void _onPageControl(_ReaderControllerEventArgs? args) {
if (args != null) {
var event = args.key;
switch ("$event") {
case "UP":
if (_current > 0) {
_needJumpTo(_current - 1, true);
}
break; break;
case FullScreenAction.TOUCH_ONCE: case "DOWN":
reader = GestureDetector( if (_current < widget.struct.images.length - 1) {
onTap: () => struct.onFullScreenChange(!struct.fullScreen), _needJumpTo(_current + 1, true);
child: reader, }
);
break; break;
case FullScreenAction.THREE_AREA: }
reader = Stack( }
}
late int _startIndex;
late int _current;
late int _slider;
void _initCurrent() {
if (widget.struct.initPosition != null &&
widget.struct.images.length > widget.struct.initPosition!) {
_startIndex = widget.struct.initPosition!;
} else {
_startIndex = 0;
}
_current = _startIndex;
_slider = _startIndex;
}
void _onCurrentChange(int index) {
if (index != _current) {
setState(() {
_current = index;
_slider = index;
widget.struct.onPositionChange(index);
});
}
}
Widget _buildControllerAndBar() {
if (widget.struct.fullScreen) {
return _buildController();
}
return SafeArea(
child: Column(
children: [ children: [
reader, Expanded(
LayoutBuilder( child: _buildController(hiddenFullScreen: true),
),
Container(
color: readerAppbarColor2,
child: Row(
children: [
Container(width: 15),
IconButton(
icon: Icon(Icons.fullscreen),
color: Colors.white,
onPressed: () {
widget.struct.onFullScreenChange(!widget.struct.fullScreen);
},
),
Container(width: 10),
Expanded(
child: Column(
children: [
Container(height: 3),
Container(
height: 25,
child: FlutterSlider(
axis: Axis.horizontal,
values: [_slider.toDouble()],
min: 0,
max: widget.struct.images.length.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,
),
),
);
}),
),
),
Container(height: 3),
],
),
),
Container(width: 10),
IconButton(
icon: Icon(Icons.skip_next_outlined),
color: Colors.white,
onPressed: () {
widget.struct.onNextAction();
},
),
Container(width: 15),
],
),
),
],
),
);
}
Widget _buildController({bool hiddenFullScreen = false}) {
switch (currentFullScreenAction()) {
case FullScreenAction.CONTROLLER:
if (hiddenFullScreen) {
return Container();
}
return _buildFullScreenController();
case FullScreenAction.TOUCH_ONCE:
return _buildTouchOnceController();
case FullScreenAction.THREE_AREA:
return _buildThreeAreaController();
default:
return Container();
}
}
Widget _buildFullScreenController() {
return Align(
alignment: Alignment.bottomLeft,
child: Material(
color: Color(0x0),
child: Container(
padding: EdgeInsets.only(left: 10, right: 10, top: 4, bottom: 4),
margin: EdgeInsets.only(bottom: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(10),
bottomRight: Radius.circular(10),
),
color: Color(0x88000000),
),
child: GestureDetector(
onTap: () {
widget.struct.onFullScreenChange(!widget.struct.fullScreen);
},
child: Icon(
widget.struct.fullScreen
? Icons.fullscreen_exit
: Icons.fullscreen_outlined,
size: 30,
color: Colors.white,
),
),
),
),
);
}
Widget _buildTouchOnceController() {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
widget.struct.onFullScreenChange(!widget.struct.fullScreen);
},
child: Container(),
);
}
Widget _buildThreeAreaController() {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) { builder: (BuildContext context, BoxConstraints constraints) {
var up = Expanded( var up = Expanded(
child: GestureDetector( child: GestureDetector(
@ -190,19 +401,19 @@ class ImageReader extends StatelessWidget {
_readerControllerEvent _readerControllerEvent
.broadcast(_ReaderControllerEventArgs("DOWN")); .broadcast(_ReaderControllerEventArgs("DOWN"));
}, },
child: Container( child: Container(),
),
), ),
); );
var fullScreen = Expanded( var fullScreen = Expanded(
child: GestureDetector( child: GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTap: () => struct.onFullScreenChange(!struct.fullScreen), onTap: () =>
widget.struct.onFullScreenChange(!widget.struct.fullScreen),
child: Container(), child: Container(),
), ),
); );
late Widget child; late Widget child;
switch (struct.pagerDirection) { switch (widget.struct.pagerDirection) {
case ReaderDirection.TOP_TO_BOTTOM: case ReaderDirection.TOP_TO_BOTTOM:
child = Column(children: [ child = Column(children: [
up, up,
@ -231,81 +442,17 @@ class ImageReader extends StatelessWidget {
child: child, child: child,
); );
}, },
),
],
);
break;
}
//
return reader;
}
Widget _buildFullScreenController(
bool fullScreen,
FutureOr<dynamic> Function(bool fullScreen) onFullScreenChange,
) {
return Align(
alignment: Alignment.bottomLeft,
child: Material(
color: Color(0x0),
child: Container(
padding: EdgeInsets.only(left: 10, right: 10, top: 4, bottom: 4),
margin: EdgeInsets.only(bottom: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(10),
bottomRight: Radius.circular(10),
),
color: Color(0x88000000),
),
child: GestureDetector(
onTap: () {
onFullScreenChange(!fullScreen);
},
child: Icon(
fullScreen ? Icons.fullscreen_exit : Icons.fullscreen_outlined,
size: 30,
color: Colors.white,
),
),
),
),
); );
} }
} }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
class _WebToonReader extends StatefulWidget { class _WebToonReaderState extends _ImageReaderState {
final ImageReaderStruct struct; var _controllerTime = DateTime.now().millisecondsSinceEpoch + 400;
const _WebToonReader(this.struct);
@override
State<StatefulWidget> createState() => _WebToonReaderState();
}
class _WebToonReaderState extends State<_WebToonReader> {
late final List<Size?> _trueSizes = []; late final List<Size?> _trueSizes = [];
late final ItemScrollController _itemScrollController; late final ItemScrollController _itemScrollController;
late final ItemPositionsListener _itemPositionsListener; late final ItemPositionsListener _itemPositionsListener;
late final int _initialPosition;
var _current = 1;
var _slider = 1;
void _onCurrentChange() {
var to = _itemPositionsListener.itemPositions.value.first.index + 1;
if (_current != to) {
setState(() {
_current = to;
_slider = to;
if (to - 1 < widget.struct.images.length) {
widget.struct.onPositionChange(to - 1);
}
});
}
}
@override @override
void initState() { void initState() {
@ -318,81 +465,49 @@ class _WebToonReaderState extends State<_WebToonReader> {
}); });
_itemScrollController = ItemScrollController(); _itemScrollController = ItemScrollController();
_itemPositionsListener = ItemPositionsListener.create(); _itemPositionsListener = ItemPositionsListener.create();
_itemPositionsListener.itemPositions.addListener(_onCurrentChange); _itemPositionsListener.itemPositions.addListener(_onListCurrentChange);
if (widget.struct.initPosition != null &&
widget.struct.images.length > widget.struct.initPosition!) {
_initialPosition = widget.struct.initPosition!;
} else {
_initialPosition = 0;
}
_readerControllerEvent.subscribe(_onPageControllerEvent);
super.initState(); super.initState();
} }
@override @override
void dispose() { void dispose() {
_itemPositionsListener.itemPositions.removeListener(_onCurrentChange); _itemPositionsListener.itemPositions.removeListener(_onListCurrentChange);
_readerControllerEvent.unsubscribe(_onPageControllerEvent);
super.dispose(); super.dispose();
} }
void _onPageControllerEvent(_ReaderControllerEventArgs? args) { void _onListCurrentChange() {
if (args != null) { var to = _itemPositionsListener.itemPositions.value.first.index;
var event = args.key; // , 5 0,1,2,3,4 length=5, =5
print("EVENT : $event"); if (to >= 0 && to < widget.struct.images.length) {
switch ("$event") { super._onCurrentChange(to);
case "UP":
if (_current > 1) {
if (noAnimation()) {
_itemScrollController.jumpTo(
index: _current - 2, // 1 position 1
);
} else {
if (DateTime.now().millisecondsSinceEpoch < _controllerTime) {
return;
}
_controllerTime = DateTime.now().millisecondsSinceEpoch + 400;
_itemScrollController.scrollTo(
index: _current - 2, // 1 position 1
duration: Duration(milliseconds: 400),
);
} }
} }
break;
case "DOWN":
if (_current < widget.struct.images.length) {
if (noAnimation()) {
_itemScrollController.jumpTo(index: _current);
} else {
if (DateTime.now().millisecondsSinceEpoch < _controllerTime) {
return;
}
_controllerTime = DateTime.now().millisecondsSinceEpoch + 400;
_itemScrollController.scrollTo(
index: _current,
duration: Duration(milliseconds: 400),
);
}
}
break;
}
}
}
var _controllerTime = DateTime.now().millisecondsSinceEpoch + 400;
@override @override
Widget build(BuildContext context) { void _needJumpTo(int index, bool animation) {
if (noAnimation() || animation == false) {
_itemScrollController.jumpTo(
index: index,
);
} else {
if (DateTime.now().millisecondsSinceEpoch < _controllerTime) {
return;
}
_controllerTime = DateTime.now().millisecondsSinceEpoch + 400;
_itemScrollController.scrollTo(
index: index, // 1 position 1
duration: Duration(milliseconds: 400),
);
}
}
@override
Widget _buildViewer() {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black, color: Colors.black,
), ),
child: Stack( child: _buildList(),
children: [
_buildList(),
..._buildControllers(),
],
),
); );
} }
@ -459,20 +574,22 @@ class _WebToonReaderState extends State<_WebToonReader> {
} }
} }
return ScrollablePositionedList.builder( return ScrollablePositionedList.builder(
initialScrollIndex: _initialPosition, initialScrollIndex: super._startIndex,
scrollDirection: scrollDirection:
widget.struct.pagerDirection == ReaderDirection.TOP_TO_BOTTOM widget.struct.pagerDirection == ReaderDirection.TOP_TO_BOTTOM
? Axis.vertical ? Axis.vertical
: Axis.horizontal, : Axis.horizontal,
reverse: reverse:
widget.struct.pagerDirection == ReaderDirection.RIGHT_TO_LEFT, widget.struct.pagerDirection == ReaderDirection.RIGHT_TO_LEFT,
padding: widget.struct.fullScreen && padding: EdgeInsets.only(
top: widget.struct.fullScreen ? (scaffold.appBarMaxHeight ?? 0) : 0,
bottom:
widget.struct.pagerDirection == ReaderDirection.TOP_TO_BOTTOM widget.struct.pagerDirection == ReaderDirection.TOP_TO_BOTTOM
? EdgeInsets.only( ? 130
top: scaffold.appBarMaxHeight ?? 0, : (widget.struct.fullScreen
bottom: scaffold.appBarMaxHeight ?? 0, ? (scaffold.appBarMaxHeight ?? 0)
) : 0),
: null, ),
itemScrollController: _itemScrollController, itemScrollController: _itemScrollController,
itemPositionsListener: _itemPositionsListener, itemPositionsListener: _itemPositionsListener,
itemCount: widget.struct.images.length + 1, itemCount: widget.struct.images.length + 1,
@ -487,27 +604,6 @@ class _WebToonReaderState extends State<_WebToonReader> {
); );
} }
List<Widget> _buildControllers() {
if (widget.struct.fullScreen) {
return [];
}
return [
_buildImageCount(context, "$_current / ${widget.struct.images.length}"),
_buildScrollController(
context,
_current,
_slider,
widget.struct.images.length,
(value) => _slider = value,
() {
if (_slider != _current && _slider > 0) {
_itemScrollController.jumpTo(index: _slider - 1);
}
},
),
];
}
Widget _buildNextEp() { Widget _buildNextEp() {
return Container( return Container(
padding: EdgeInsets.all(20), padding: EdgeInsets.all(20),
@ -649,15 +745,6 @@ class _WebToonReaderImageState extends State<_WebToonReaderImage> {
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
class _WebToonZoomReader extends _WebToonReader {
const _WebToonZoomReader(
ImageReaderStruct struct,
) : super(struct);
@override
State<StatefulWidget> createState() => _WebToonZoomReaderState();
}
class _WebToonZoomReaderState extends _WebToonReaderState { class _WebToonZoomReaderState extends _WebToonReaderState {
@override @override
Widget _buildList() { Widget _buildList() {
@ -667,81 +754,41 @@ class _WebToonZoomReaderState extends _WebToonReaderState {
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
class _GalleryReader extends StatefulWidget { class _GalleryReaderState extends _ImageReaderState {
final ImageReaderStruct struct; late PageController _pageController;
_GalleryReader(this.struct);
@override
State<StatefulWidget> createState() => _GalleryReaderState();
}
class _GalleryReaderState extends State<_GalleryReader> {
late int _current = (widget.struct.initPosition ?? 0) + 1;
late int _slider = (widget.struct.initPosition ?? 0) + 1;
late PageController _pageController =
PageController(initialPage: widget.struct.initPosition ?? 0);
@override @override
void initState() { void initState() {
_readerControllerEvent.subscribe(_onPageControllerEvent); _pageController = PageController(initialPage: super._startIndex);
super.initState(); super.initState();
} }
@override @override
void dispose() { void dispose() {
_pageController.dispose(); _pageController.dispose();
_readerControllerEvent.unsubscribe(_onPageControllerEvent);
super.dispose(); super.dispose();
} }
void _onPageControllerEvent(_ReaderControllerEventArgs? args) { @override
if (args != null) { void _needJumpTo(int index, bool animation) {
var event = args.key; if (noAnimation() || animation == false) {
print("EVENT : $event"); _pageController.jumpToPage(
switch ("$event") { index,
case "UP":
if (_current > 1) {
if (noAnimation()) {
_pageController.previousPage(
duration: Duration(milliseconds: 1),
curve: Curves.ease,
); );
} else { } else {
_pageController.previousPage( _pageController.animateToPage(
index,
duration: Duration(milliseconds: 400), duration: Duration(milliseconds: 400),
curve: Curves.ease, curve: Curves.ease,
); );
} }
} }
break;
case "DOWN":
if (_current < widget.struct.images.length) {
if (noAnimation()) {
_pageController.nextPage(
duration: Duration(milliseconds: 1),
curve: Curves.ease,
);
} else {
_pageController.nextPage(
duration: Duration(milliseconds: 400),
curve: Curves.ease,
);
}
}
break;
}
}
}
@override void _onGalleryPageChange(int to) {
Widget build(BuildContext context) { // , 5 0,1,2,3,4 length=5, =5
return Stack( if (to >= 0 && to < widget.struct.images.length) {
children: [ super._onCurrentChange(to);
_buildViewer(), }
..._buildControllers(),
],
);
} }
Widget _buildViewer() { Widget _buildViewer() {
@ -758,34 +805,7 @@ class _GalleryReaderState extends State<_GalleryReader> {
}, },
), ),
pageController: _pageController, pageController: _pageController,
onPageChanged: (value) { onPageChanged: _onGalleryPageChange,
setState(() {
_current = value + 1;
_slider = value + 1;
widget.struct.onPositionChange(value);
if (galleryPrePreloadCount > 0) {
for (var count = 1;
count <= galleryPrePreloadCount && value - count >= 0;
count++) {
var target = widget.struct.images[value - count];
if (target.downloadLocalPath == null) {
method.remoteImagePreload(target.fileServer, target.path);
}
}
}
if (galleryPreloadCount > 0) {
for (var count = 1;
count <= galleryPreloadCount &&
value + count < widget.struct.images.length;
count++) {
var target = widget.struct.images[value + count];
if (target.downloadLocalPath == null) {
method.remoteImagePreload(target.fileServer, target.path);
}
}
}
});
},
itemCount: widget.struct.images.length, itemCount: widget.struct.images.length,
builder: (BuildContext context, int index) { builder: (BuildContext context, int index) {
var item = widget.struct.images[index]; var item = widget.struct.images[index];
@ -821,10 +841,9 @@ class _GalleryReaderState extends State<_GalleryReader> {
return GestureDetector( return GestureDetector(
child: gallery, child: gallery,
onLongPress: () async { onLongPress: () async {
var index = _current - 1; if (_current >= 0 && _current < widget.struct.images.length) {
if (index >= 0 && _current < widget.struct.images.length) {
Future<String> Function() load = () async { Future<String> Function() load = () async {
var item = widget.struct.images[index]; var item = widget.struct.images[_current];
if (item.downloadLocalPath != null) { if (item.downloadLocalPath != null) {
return method.downloadImagePath(item.downloadLocalPath!); return method.downloadImagePath(item.downloadLocalPath!);
} }
@ -858,155 +877,9 @@ class _GalleryReaderState extends State<_GalleryReader> {
); );
} }
List<Widget> _buildControllers() { Widget _buildNextEpButton() {
var controllers = <Widget>[]; return Container();
if (!widget.struct.fullScreen) {
controllers.addAll([
_buildImageCount(context, "$_current / ${widget.struct.images.length}"),
_buildScrollController(
context,
_current,
_slider,
widget.struct.images.length,
(value) => _slider = value,
() {
if (_slider != _current && _slider > 0) {
_pageController.jumpToPage(_slider - 1);
}
},
),
]);
}
if (_current == widget.struct.images.length) {
controllers.add(_buildNextEpController(
widget.struct.onNextAction,
widget.struct.onNextText,
));
}
return controllers;
} }
} }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
Widget _buildImageCount(BuildContext context, String info) {
return Align(
alignment: Alignment.topRight,
child: Material(
color: Color(0x0),
child: Container(
margin: EdgeInsets.only(top: 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: () {
// TODO
},
child: Text("$info", style: TextStyle(color: Colors.white)),
),
),
),
);
}
Widget _buildScrollController(
BuildContext context,
int current,
int slider,
int total,
Function(int) onSliderChange,
Function() onSliderDown,
) {
if (total == 0) {
return Container();
}
var theme = Theme.of(context);
return 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: FlutterSlider(
axis: Axis.vertical,
values: [(slider > total ? total : slider).toDouble()],
max: total.toDouble(),
min: 1,
onDragging: (handlerIndex, lowerValue, upperValue) {
onSliderChange(lowerValue.toInt());
},
onDragCompleted: (handlerIndex, lowerValue, upperValue) {
onSliderChange(lowerValue.toInt());
onSliderDown();
},
trackBar: FlutterSliderTrackBar(
inactiveTrackBar: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.grey.shade300,
),
activeTrackBar: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: theme.colorScheme.secondary,
),
),
step: FlutterSliderStep(
step: 1,
isPercentRange: false,
),
tooltip: FlutterSliderTooltip(custom: (value) {
double a = value;
return Container(
padding: EdgeInsets.all(5),
color: Colors.white,
child:
Text('${a.toInt()}', style: TextStyle(color: Colors.black)),
);
}),
),
),
),
),
);
}
Widget _buildNextEpController(Function() next, String text) {
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: () {
next();
},
child: Text(text, style: TextStyle(color: Colors.white)),
),
),
),
);
}