v1.0.1
This commit is contained in:
612
lib/modules/comic/reader/comic_reader_page.dart
Normal file
612
lib/modules/comic/reader/comic_reader_page.dart
Normal file
@@ -0,0 +1,612 @@
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:easy_refresh/easy_refresh.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_dmzj/app/app_constant.dart';
|
||||
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/app/log.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/reader/comic_reader_controller.dart';
|
||||
import 'package:flutter_dmzj/widgets/custom_header.dart';
|
||||
import 'package:flutter_dmzj/widgets/local_image.dart';
|
||||
import 'package:flutter_dmzj/widgets/net_image.dart';
|
||||
import 'package:flutter_dmzj/widgets/status/app_error_widget.dart';
|
||||
import 'package:flutter_dmzj/widgets/status/app_loadding_widget.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:preload_page_view/preload_page_view.dart';
|
||||
import 'package:remixicon/remixicon.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
class ComicReaderPage extends GetView<ComicReaderController> {
|
||||
const ComicReaderPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return KeyboardListener(
|
||||
onKeyEvent: (e) {
|
||||
if (e.runtimeType == KeyUpEvent) {
|
||||
controller.keyDown(e.logicalKey);
|
||||
Log.d(e.toString());
|
||||
}
|
||||
},
|
||||
focusNode: controller.focusNode,
|
||||
autofocus: true,
|
||||
child: Theme(
|
||||
data: Theme.of(context),
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: Stack(
|
||||
children: [
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: controller.detail.value.chapterId == 0,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
controller.setShowControls();
|
||||
},
|
||||
child:
|
||||
controller.direction.value == ReaderDirection.kUpToDown
|
||||
? buildVertical(context)
|
||||
: buildHorizontal(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
controller.leftHandMode
|
||||
? controller.nextPage()
|
||||
: controller.forwardPage();
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 8,
|
||||
child: Container(),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
controller.leftHandMode
|
||||
? controller.forwardPage()
|
||||
: controller.nextPage();
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: !controller.pageLoadding.value,
|
||||
child: const AppLoaddingWidget(),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: !controller.pageError.value,
|
||||
child: AppErrorWidget(
|
||||
errorMsg: controller.errorMsg.value,
|
||||
onRefresh: () => controller.loadDetail(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: Obx(
|
||||
() => Offstage(
|
||||
offstage: !controller.settings.comicReaderShowStatus.value,
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.black54,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(8),
|
||||
),
|
||||
),
|
||||
padding:
|
||||
AppStyle.edgeInsetsA12.copyWith(top: 4, bottom: 4),
|
||||
child: Obx(
|
||||
() => Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
buildConnectivity(),
|
||||
buildBattery(),
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 100),
|
||||
child: Text(
|
||||
controller.detail.value.chapterTitle,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
height: 1.0,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
AppStyle.hGap8,
|
||||
Text(
|
||||
"${controller.currentIndex.value + 1} / ${controller.detail.value.pageUrls.length}",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
height: 1.0,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
//顶部
|
||||
Obx(
|
||||
() => AnimatedPositioned(
|
||||
top: controller.showControls.value
|
||||
? 0
|
||||
: -(64 + AppStyle.statusBarHeight),
|
||||
left: 0,
|
||||
right: 0,
|
||||
duration: const Duration(milliseconds: 100),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(16),
|
||||
bottomRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
height: 64 + AppStyle.statusBarHeight,
|
||||
padding: EdgeInsets.only(top: AppStyle.statusBarHeight),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: Get.back,
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
),
|
||||
AppStyle.hGap12,
|
||||
Expanded(
|
||||
child: Obx(
|
||||
() => Text(
|
||||
controller.chapters[controller.chapterIndex.value]
|
||||
.chapterTitle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
//底部
|
||||
Obx(
|
||||
() => AnimatedPositioned(
|
||||
bottom: controller.showControls.value
|
||||
? 0
|
||||
: -(136 + AppStyle.bottomBarHeight),
|
||||
left: 0,
|
||||
right: 0,
|
||||
duration: const Duration(milliseconds: 100),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
height: 136 + AppStyle.bottomBarHeight,
|
||||
padding: EdgeInsets.only(bottom: AppStyle.bottomBarHeight),
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 600,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
buildSilderBar(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
IconButton.filledTonal(
|
||||
onPressed: controller.forwardChapter,
|
||||
icon: const Icon(Remix.skip_back_line),
|
||||
tooltip: "上一话",
|
||||
),
|
||||
IconButton.filledTonal(
|
||||
onPressed: controller.showMenu,
|
||||
icon: const Icon(Remix.file_list_line),
|
||||
tooltip: "目录",
|
||||
),
|
||||
IconButton.filledTonal(
|
||||
onPressed: controller.showSettings,
|
||||
icon: const Icon(Remix.settings_line),
|
||||
tooltip: "设置",
|
||||
),
|
||||
IconButton.filledTonal(
|
||||
onPressed: controller.nextChapter,
|
||||
icon: const Icon(Remix.skip_forward_line),
|
||||
tooltip: "下一话",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildHorizontal(BuildContext context) {
|
||||
return EasyRefresh(
|
||||
header: MaterialHeader2(
|
||||
triggerOffset: 80,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: AppStyle.radius24,
|
||||
),
|
||||
padding: AppStyle.edgeInsetsA12,
|
||||
child: Icon(
|
||||
Icons.arrow_circle_left,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
footer: MaterialFooter2(
|
||||
triggerOffset: 80,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: AppStyle.radius24,
|
||||
),
|
||||
padding: AppStyle.edgeInsetsA12,
|
||||
child: Icon(
|
||||
Icons.arrow_circle_right,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
refreshOnStart: false,
|
||||
onRefresh: () async {
|
||||
controller.forwardChapter();
|
||||
},
|
||||
onLoad: () async {
|
||||
controller.nextChapter();
|
||||
},
|
||||
child: PreloadPageView.builder(
|
||||
controller: controller.preloadPageController,
|
||||
onPageChanged: (e) {
|
||||
controller.currentIndex.value = e;
|
||||
},
|
||||
reverse: controller.direction.value == ReaderDirection.kRightToLeft,
|
||||
physics: controller.lockSwipe.value
|
||||
? const NeverScrollableScrollPhysics()
|
||||
: null,
|
||||
itemCount: controller.detail.value.pageUrls.length,
|
||||
preloadPagesCount: 4,
|
||||
itemBuilder: (_, i) {
|
||||
var url = controller.detail.value.pageUrls[i];
|
||||
// if (i == controller.detail.value.pageUrls.length - 1 && url == "TC") {
|
||||
// return buildViewPoints();
|
||||
// }
|
||||
return PhotoView.customChild(
|
||||
wantKeepAlive: true,
|
||||
initialScale: 1.0,
|
||||
onScaleEnd: (context, detail, e) {
|
||||
controller.lockSwipe.value = (e.scale ?? 1) > 1.0;
|
||||
},
|
||||
child: controller.detail.value.isLocal
|
||||
? LocalImage(url, fit: BoxFit.contain)
|
||||
: NetImage(
|
||||
url,
|
||||
fit: BoxFit.contain,
|
||||
progress: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildVertical(BuildContext context) {
|
||||
return EasyRefresh(
|
||||
header: MaterialHeader2(
|
||||
triggerOffset: 80,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: AppStyle.radius24,
|
||||
),
|
||||
padding: AppStyle.edgeInsetsA12,
|
||||
child: Icon(
|
||||
Icons.arrow_circle_up,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
footer: MaterialFooter2(
|
||||
triggerOffset: 80,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: AppStyle.radius24,
|
||||
),
|
||||
padding: AppStyle.edgeInsetsA12,
|
||||
child: Icon(
|
||||
Icons.arrow_circle_down,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
refreshOnStart: false,
|
||||
onRefresh: () async {
|
||||
controller.forwardChapter();
|
||||
},
|
||||
onLoad: () async {
|
||||
controller.nextChapter();
|
||||
},
|
||||
child: ScrollablePositionedList.builder(
|
||||
itemScrollController: controller.itemScrollController,
|
||||
itemCount: controller.detail.value.pageUrls.length,
|
||||
itemPositionsListener: controller.itemPositionsListener,
|
||||
itemBuilder: (_, i) {
|
||||
// if (i == controller.detail.value.pageUrls.length - 1 &&
|
||||
// controller.detail.value.pageUrls[i] == "TC") {
|
||||
// return buildViewPoints(shrinkWrap: true);
|
||||
// }
|
||||
var url = controller.detail.value.pageUrls[i];
|
||||
return Container(
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: 200,
|
||||
),
|
||||
child: controller.detail.value.isLocal
|
||||
? LocalImage(url, fit: BoxFit.contain)
|
||||
: NetImage(
|
||||
url,
|
||||
fit: BoxFit.fitWidth,
|
||||
progress: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildSilderBar() {
|
||||
return Obx(
|
||||
() {
|
||||
var value = controller.currentIndex.value + 1.0;
|
||||
var max = controller.detail.value.pageUrls.length.toDouble();
|
||||
if (value > max) {
|
||||
return const SizedBox(
|
||||
height: 48,
|
||||
);
|
||||
}
|
||||
return SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Slider(
|
||||
value: value,
|
||||
max: max,
|
||||
onChanged: (e) {
|
||||
controller.jumpToPage((e - 1).toInt());
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildViewPoints({bool shrinkWrap = false}) {
|
||||
return Obx(
|
||||
() => ListView(
|
||||
shrinkWrap: shrinkWrap,
|
||||
physics: shrinkWrap ? const NeverScrollableScrollPhysics() : null,
|
||||
padding: EdgeInsets.zero,
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text("吐槽(${controller.viewPoints.length})"),
|
||||
),
|
||||
Padding(
|
||||
padding: AppStyle.edgeInsetsH12,
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: controller.viewPoints
|
||||
.take(10)
|
||||
.map(
|
||||
(e) => OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
onPressed: () {
|
||||
controller.likeViewPoint(e);
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
e.content,
|
||||
style: const TextStyle(
|
||||
fontSize: 14, color: Colors.white),
|
||||
),
|
||||
AppStyle.hGap12,
|
||||
const Icon(
|
||||
Remix.thumb_up_line,
|
||||
size: 16,
|
||||
),
|
||||
AppStyle.hGap4,
|
||||
Obx(
|
||||
() => Text(
|
||||
"${e.num.value}",
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
width: 100,
|
||||
margin: AppStyle.edgeInsetsA12,
|
||||
child: OutlinedButton(
|
||||
onPressed: () {
|
||||
controller.showComment();
|
||||
},
|
||||
child: const Text("查看更多"),
|
||||
),
|
||||
),
|
||||
AppStyle.vGap12,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildConnectivity() {
|
||||
var connectivityType = controller.connectivityType.value;
|
||||
IconData icon = Remix.wifi_line;
|
||||
var name = "WiFi";
|
||||
switch (connectivityType) {
|
||||
case ConnectivityResult.bluetooth:
|
||||
icon = Remix.wifi_line;
|
||||
name = "蓝牙";
|
||||
break;
|
||||
case ConnectivityResult.ethernet:
|
||||
icon = Remix.computer_line;
|
||||
name = "有线";
|
||||
break;
|
||||
case ConnectivityResult.mobile:
|
||||
icon = Remix.base_station_line;
|
||||
name = "流量";
|
||||
break;
|
||||
case ConnectivityResult.wifi:
|
||||
icon = Remix.wifi_line;
|
||||
name = "WiFi";
|
||||
break;
|
||||
case ConnectivityResult.vpn:
|
||||
icon = Remix.shield_keyhole_line;
|
||||
name = "VPN";
|
||||
break;
|
||||
case ConnectivityResult.none:
|
||||
icon = Remix.wifi_off_line;
|
||||
name = "无网络";
|
||||
break;
|
||||
case ConnectivityResult.other:
|
||||
icon = Remix.question_line;
|
||||
name = "未知";
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 12,
|
||||
color: Colors.white,
|
||||
),
|
||||
AppStyle.hGap4,
|
||||
Text(
|
||||
name,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
height: 1.0,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
AppStyle.hGap8,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildBattery() {
|
||||
var battery = controller.batteryLevel.value;
|
||||
// IconData icon = Icons.battery_0_bar;
|
||||
// if (battery >= 90) {
|
||||
// icon = Icons.battery_full;
|
||||
// } else if (battery < 90 && battery >= 80) {
|
||||
// icon = Icons.battery_6_bar;
|
||||
// } else if (battery < 80 && battery >= 70) {
|
||||
// icon = Icons.battery_5_bar;
|
||||
// } else if (battery < 70 && battery >= 50) {
|
||||
// icon = Icons.battery_4_bar;
|
||||
// } else if (battery < 50 && battery >= 30) {
|
||||
// icon = Icons.battery_3_bar;
|
||||
// } else if (battery < 30 && battery >= 20) {
|
||||
// icon = Icons.battery_2_bar;
|
||||
// } else if (battery < 20 && battery >= 10) {
|
||||
// icon = Icons.battery_1_bar;
|
||||
// } else {
|
||||
// icon = Icons.battery_0_bar;
|
||||
// }
|
||||
return Visibility(
|
||||
visible: controller.showBattery.value,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// Icon(
|
||||
// icon,
|
||||
// size: 16,
|
||||
// ),
|
||||
// AppStyle.hGap4,
|
||||
Text(
|
||||
"电量 $battery%",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
height: 1.0,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
AppStyle.hGap8,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user