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( init: CartoonReadLogic(manager), builder: (logic) => Scaffold( body: ChangeNotifierProvider.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( 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( 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( 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( 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( 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( id: 'like', builder: (_) => AnimatedPositioned( duration: Duration(milliseconds: logic.animationTime), bottom: 160, right: logic.showMenu ? 0 : -120, child: Consumer( 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( 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( 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.reverse(items 不够会从底部排),采用数据倒序方法 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, ), ); } }