初始化
This commit is contained in:
@@ -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.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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user