初始化

This commit is contained in:
谢宇宁
2026-09-15 15:44:13 +07:00
commit a23ba685e9
1065 changed files with 101122 additions and 0 deletions
+511
View File
@@ -0,0 +1,511 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:hgdj/assets_tool/app_colors.dart';
import 'package:hgdj/assets_tool/images.dart';
import 'package:hgdj/hj_model/video_model.dart';
import 'package:hgdj/hj_page/comment/comment_alert.dart';
import 'package:hgdj/hj_utils/screen.dart';
import 'package:hgdj/tools_base/loading/loading_center_widget.dart';
import 'package:hgdj/tools_base/toast.dart';
import 'package:hgdj/tools_base/widget/follow_button.dart';
import 'package:hgdj/tools_base/widget/net_image_widget.dart';
import 'package:hgdj/tools_base/widget/scroll_positioned_list/src/scrollable_positioned_list.dart';
import 'package:hgdj/tools_base/widget/stagger_in_item.dart';
import 'package:provider/provider.dart';
import '../../alert/video/share_media_dialog.dart';
import '../../hj_utils/widget_util.dart';
import 'cartoon_read_logic.dart';
import 'widget/acg_source_manager.dart';
// 漫画阅读页面
class CartoonReadPage extends StatelessWidget {
final ACGSourceManager manager;
const CartoonReadPage({super.key, required this.manager});
@override
Widget build(BuildContext context) {
return GetBuilder<CartoonReadLogic>(
init: CartoonReadLogic(manager),
builder: (logic) => Scaffold(
body: ChangeNotifierProvider<ACGSourceManager>.value(
value: manager,
child: _buildBody(context, logic),
),
drawer: _buildDrawer(logic),
),
);
}
Widget _buildBody(BuildContext context, CartoonReadLogic logic) {
if (logic.detailInfo == null) return const LoadingCenterWidget();
if (logic.detailInfo?.id == null)
return CErrorWidget(retryOnTap: logic.loadData);
return Stack(
alignment: Alignment.center,
children: [
InkWell(
enableFeedback: false,
onTap: logic.changeShowMenu,
child: ScrollablePositionedList.builder(
scrollAction: logic.onScrollAction,
scrollOffsetController: logic.scrollOffsetCtr,
itemPositionsListener: logic.itemListener,
itemScrollController: logic.itemScrollCtr,
itemCount: logic.imgsLength,
itemBuilder: (_, index) => NetworkImageLoader(
imageUrl: logic.detailInfo?.urlSet?[index] ?? '',
fit: BoxFit.cover,
borderRadius: 0,
),
),
),
_buildHeader(logic),
_buildFooter(context, logic),
_buildLiked(logic),
_buildAutoPlay(logic),
],
);
}
// header bar
Widget _buildHeader(CartoonReadLogic logic) {
final headerHeight = screen.paddingTop + 50;
return GetBuilder<CartoonReadLogic>(
id: 'header',
builder: (_) => AnimatedPositioned(
duration: Duration(milliseconds: logic.animationTime),
top: logic.showMenu ? 0 : -headerHeight,
left: 0,
right: 0,
child: Container(
color: Colors.black,
height: headerHeight,
padding: EdgeInsets.fromLTRB(10, screen.paddingTop + 8, 10, 8),
child: Row(
children: [
InkWell(
enableFeedback: false,
onTap: () => Get.back(),
child: const Icon(Icons.arrow_back_ios_new,
size: 24, color: Colors.white),
),
10.sizeBoxW,
Expanded(
child: Text(
logic.detailInfo?.name ?? '',
style: TextStyle(
fontSize: 18.sp,
color: Colors.white,
fontWeight: FontWeight.w600),
),
),
21.sizeBoxW,
Consumer<ACGSourceManager>(
builder: (ctx, manager, _) {
final col =
manager.mediaInfo?.mediaStatus?.hasCollected ?? false;
return FollowButton(
mediaId: manager.mediaInfo?.id ?? '',
isFollow: col,
followType: FollowEnum.cartoon,
successsAction: (isSuccess) {
manager.updateCollectState(isSuccess);
if (isSuccess) showToast('收藏成功');
},
);
},
),
12.sizeBoxW,
InkWell(
enableFeedback: false,
onTap: () => Get.dialog(
ShareMediaDialog(
videoModel: VideoModel()
..id = manager.mediaInfo?.id
..cover = manager.mediaInfo?.coverH,
),
),
child: Image.asset('acg_share.png'.acgImgPath, width: 24),
),
],
),
),
),
);
}
Widget _buildFooter(BuildContext context, CartoonReadLogic logic) {
return GetBuilder<CartoonReadLogic>(
id: 'footer',
builder: (_) => AnimatedPositioned(
duration: Duration(milliseconds: logic.animationTime),
bottom: logic.showMenu ? 0 : -logic.bottomHeight,
left: 0,
right: 0,
child: Column(
children: [
15.sizeBoxH,
GetBuilder<CartoonReadLogic>(
id: 'progress',
builder: (_) => AnimatedSlide(
offset: logic.showProgress ? Offset.zero : const Offset(0, 1),
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutCubic,
child: _buildProgressView(context, logic),
),
),
Container(
color: Colors.black,
padding: const EdgeInsets.fromLTRB(16, 18, 16, 25),
child: Row(
children: [
Expanded(
child: Builder(
builder: (ctx) => _buildActionItem(
title: '目录',
icon: 'cartoon_menu.png'.acgImgPath,
action: () => Scaffold.of(ctx).openDrawer(),
),
),
),
Expanded(
child: _buildActionItem(
title: '评论',
icon: 'cartoon_comment.png'.acgImgPath,
action: () => showCommentDialog(
logic.manager.mediaInfo?.id ?? '',
objType: logic.manager.mediaInfo?.commentType ?? '',
),
),
),
Expanded(
child: _buildActionItem(
title: '进度',
icon: 'cartoon_progress.png'.acgImgPath,
action: logic.toggleProgress,
),
),
Expanded(
child: _buildActionItem(
title: '自动',
icon: 'cartoon_auto_play.png'.acgImgPath,
action: () => logic.onChangeAutoPlay(true),
),
),
],
),
),
],
),
),
);
}
Widget _buildActionItem(
{String? title, String? icon, VoidCallback? action, Color? color}) {
return InkWell(
enableFeedback: false,
onTap: action,
child: Column(
children: [
Image.asset(icon ?? '', width: 24),
4.sizeBoxH,
Text(
title ?? '',
style: TextStyle(
color: color ?? Colors.white.withValues(alpha: .45),
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
],
),
);
}
// 进度条 + 上/下一篇
Widget _buildProgressView(BuildContext context, CartoonReadLogic logic) {
return Container(
color: Colors.black,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
InkWell(
enableFeedback: false,
onTap: () => logic.loadOtherEpisode(false),
child: Column(
children: [
Image.asset('common_back_new.png'.commonImgPath, width: 24),
4.sizeBoxH,
Text('上一篇',
style: textStyle(12, Colors.white, FontWeight.w400)),
],
),
),
13.sizeBoxW,
Expanded(
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
trackHeight: 4,
thumbShape:
const RoundSliderThumbShape(enabledThumbRadius: 8.0),
overlayShape:
const RoundSliderOverlayShape(overlayRadius: 12.0),
tickMarkShape:
const RoundSliderTickMarkShape(tickMarkRadius: 10.0),
trackShape: const RoundedRectSliderTrackShape(),
overlayColor: Colors.white.withValues(alpha: .3),
activeTrackColor: const Color(0xffFFD460),
inactiveTrackColor: Colors.white.withValues(alpha: .3),
thumbColor: const Color(0xffFFD460),
inactiveTickMarkColor: Colors.amber,
),
child: GetBuilder<CartoonReadLogic>(
id: 'slider',
builder: (_) => Slider(
min: 0,
max: 1,
value: logic.readScale,
onChanged: logic.updateReadPicIndex,
onChangeEnd: logic.onSeekEnd,
),
),
),
),
13.sizeBoxW,
InkWell(
enableFeedback: false,
onTap: () => logic.loadOtherEpisode(true),
child: Column(
children: [
Transform.rotate(
angle: pi,
child: Image.asset('common_back_new.png'.commonImgPath,
width: 24),
),
4.sizeBoxH,
Text('下一篇',
style: textStyle(12, Colors.white, FontWeight.w400)),
],
),
),
],
),
);
}
// 喜欢按钮
Widget _buildLiked(CartoonReadLogic logic) {
return GetBuilder<CartoonReadLogic>(
id: 'like',
builder: (_) => AnimatedPositioned(
duration: Duration(milliseconds: logic.animationTime),
bottom: 160,
right: logic.showMenu ? 0 : -120,
child: Consumer<ACGSourceManager>(
builder: (_, manager, __) {
final liked = manager.mediaInfo?.mediaStatus?.hasLiked == true;
return InkWell(
enableFeedback: false,
onTap: manager.onLikeAction,
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: .5),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(999),
bottomLeft: Radius.circular(999),
),
),
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
liked
? 'acg_like_sel.png'.acgImgPath
: 'acg_like_nor.png'.acgImgPath,
width: 24,
color: liked ? null : Colors.white,
),
5.sizeBoxW,
Text('喜欢',
style: textStyle(12, Colors.white, FontWeight.w400)),
],
),
),
);
},
),
),
);
}
// 自动播放按钮
Widget _buildAutoPlay(CartoonReadLogic logic) {
return Positioned(
bottom: 30,
child: GetBuilder<CartoonReadLogic>(
id: 'autoplay',
builder: (_) => AnimatedScale(
scale: logic.autoPlay ? 1 : 0,
duration: const Duration(milliseconds: 200),
curve: Curves.linear,
child: IgnorePointer(
ignoring: !logic.autoPlay,
child: GestureDetector(
onTap: () => logic.onChangeAutoPlay(false),
child: Container(
height: 56,
width: 56,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(28),
color: Colors.white,
),
child: Center(
child: Image.asset('cartoon_auto_play.gif'.acgImgPath,
width: 24),
),
),
),
),
),
),
);
}
// 章节抽屉
Widget _buildDrawer(CartoonReadLogic logic) {
return GetBuilder<CartoonReadLogic>(
id: 'menu',
builder: (_) => Container(
color: AppColors.primaryColor,
width: 210,
height: screen.screenHeight,
child: Column(
children: [
80.sizeBoxH,
Row(
children: [
16.sizeBoxW,
Expanded(
child: Text(
logic.manager.mediaInfo?.title ?? '',
style: TextStyle(
fontSize: 20,
color: Colors.white.withValues(alpha: 0.9),
fontWeight: FontWeight.w900,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
18.sizeBoxH,
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
color: AppColors.actionRed,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${logic.manager.mediaInfo?.totalEpisode}${logic.manager.mediaInfo?.getDetailUpdateString ?? ''}',
style: const TextStyle(
fontSize: 14,
color: Color(0xff141414),
fontWeight: FontWeight.w400),
),
InkWell(
enableFeedback: false,
onTap: logic.toggleSort,
child: Row(
children: [
AnimatedRotation(
turns: logic.sortType ? 0 : 0.5,
duration: const Duration(milliseconds: 250),
curve: Curves.easeOutCubic,
child: Image.asset('text_play_sort.png'.acgImgPath,
color: const Color(0xff141414),
width: 16,
height: 16),
),
4.sizeBoxW,
Text(
logic.sortType ? '正序' : '倒序',
style: const TextStyle(
fontSize: 14,
color: Color(0xff141414),
fontWeight: FontWeight.w400),
),
],
),
),
],
),
),
Expanded(
child: ListView.builder(
key: ValueKey(logic.sortType),
itemCount: logic.manager.allEpisodes.length,
padding: const EdgeInsets.only(top: 6, bottom: 20),
itemBuilder: (_, index) {
// 不使用 list.reverseitems 不够会从底部排),采用数据倒序方法
final info = logic.sortType
? logic.manager.allEpisodes[index]
: logic.manager.allEpisodes[
logic.manager.allEpisodes.length - index - 1];
return StaggerInItem(
index: index,
child: GestureDetector(
onTap: () {
Get.back();
if (logic.detailInfo?.id == info.id)
return; //点的就是当前章,不重复加载
logic.loadDataWithIndex(info.id ?? '');
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
height: 40,
child: Row(
children: [
_buildDrawerItem(
//与详情/弹窗子集 title 格式一致:第X话(漫画不带 name)
'${info.episodeNumber ?? ''}${logic.manager.mediaInfo?.unit ?? ''}',
logic.detailInfo?.id == info.id,
),
],
),
),
),
);
},
),
),
],
),
),
);
}
Widget _buildDrawerItem(String title, bool select) {
return Expanded(
child: Text(
title,
style: TextStyle(
fontSize: 16.sp,
color: select
? AppColors.actionRed
: Colors.white.withValues(alpha: 0.55),
fontWeight: select ? FontWeight.w500 : FontWeight.w400,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
);
}
}