This commit is contained in:
2026-03-07 17:24:59 +08:00
parent 4418ebecac
commit b0ec8ab4bd
417 changed files with 42546 additions and 2 deletions

View 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,
],
),
);
}
}