logo和名称替换
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hgmj/assets_tool/images.dart';
|
||||
import 'package:hgmj/hj_utils/screen.dart';
|
||||
import 'package:hgmj/routers/jump_router.dart';
|
||||
import 'package:hgmj/tools_base/widget/net_image_widget.dart';
|
||||
import 'package:wydj/assets_tool/images.dart';
|
||||
import 'package:wydj/hj_utils/screen.dart';
|
||||
import 'package:wydj/routers/jump_router.dart';
|
||||
import 'package:wydj/tools_base/widget/net_image_widget.dart';
|
||||
|
||||
import '../../config/config.dart';
|
||||
import '../../hj_model/cartoon_media_info.dart';
|
||||
@@ -83,7 +83,10 @@ class AcgItemWidget extends StatelessWidget {
|
||||
child: Text(
|
||||
'${info?.episodeNumberStatus ?? ''} · ${info?.updateDesc}',
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(fontSize: 11, color: _statusColor, fontWeight: FontWeight.w400),
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: _statusColor,
|
||||
fontWeight: FontWeight.w400),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
@@ -96,8 +99,12 @@ class AcgItemWidget extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildLevelIcon() {
|
||||
if (info?.permission == 1 && Config.coinMark) return _levelTag('金币', const Color(0xFF1F2029), bgColor: [const Color(0xFFFFE580), const Color(0xFFFACC15)]);
|
||||
if (info?.permission == 0 && Config.vipMark) return _levelTag('VIP', Colors.white, bgColor: [ const Color(0xFFFF8000), const Color(0xFFFF7A01)]);
|
||||
if (info?.permission == 1 && Config.coinMark)
|
||||
return _levelTag('金币', const Color(0xFF1F2029),
|
||||
bgColor: [const Color(0xFFFFE580), const Color(0xFFFACC15)]);
|
||||
if (info?.permission == 0 && Config.vipMark)
|
||||
return _levelTag('VIP', Colors.white,
|
||||
bgColor: [const Color(0xFFFF8000), const Color(0xFFFF7A01)]);
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
@@ -107,7 +114,8 @@ class AcgItemWidget extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(2)),
|
||||
gradient: LinearGradient(colors: bgColor ?? [Color(0xffFFC000), Color(0xffFFC000)]),
|
||||
gradient: LinearGradient(
|
||||
colors: bgColor ?? [Color(0xffFFC000), Color(0xffFFC000)]),
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
@@ -117,6 +125,8 @@ class AcgItemWidget extends StatelessWidget {
|
||||
}
|
||||
|
||||
Color get _statusColor {
|
||||
return info?.updateStatus == 1 ? const Color(0xffFFC000) : Colors.white.withValues(alpha: .45);
|
||||
return info?.updateStatus == 1
|
||||
? const Color(0xffFFC000)
|
||||
: Colors.white.withValues(alpha: .45);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/assets_tool/images.dart';
|
||||
import 'package:hgmj/hj_model/acg/comic_chapters_model.dart';
|
||||
import 'package:hgmj/hj_page/live/live_widget.dart';
|
||||
import 'package:hgmj/hj_utils/screen.dart';
|
||||
import 'package:wydj/assets_tool/images.dart';
|
||||
import 'package:wydj/hj_model/acg/comic_chapters_model.dart';
|
||||
import 'package:wydj/hj_page/live/live_widget.dart';
|
||||
import 'package:wydj/hj_utils/screen.dart';
|
||||
|
||||
import '../../assets_tool/app_colors.dart';
|
||||
import '../../hj_utils/widget_util.dart';
|
||||
import 'widget/free_badge.dart';
|
||||
import 'package:hgmj/tools_base/widget/stagger_in_item.dart';
|
||||
import 'package:hgmj/tools_base/widget/sheet_handle_bar.dart';
|
||||
import 'package:wydj/tools_base/widget/stagger_in_item.dart';
|
||||
import 'package:wydj/tools_base/widget/sheet_handle_bar.dart';
|
||||
|
||||
//有声小说 选择子集
|
||||
class AudioPlayerBottomSheet extends StatefulWidget {
|
||||
@@ -62,7 +62,8 @@ class _AudioPlayerBottomSheetState extends State<AudioPlayerBottomSheet> {
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
InkWell(enableFeedback: false,
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => setState(() => sortType = sortType == 1 ? 0 : 1),
|
||||
child: Row(
|
||||
children: [
|
||||
@@ -70,12 +71,14 @@ class _AudioPlayerBottomSheetState extends State<AudioPlayerBottomSheet> {
|
||||
turns: sortType == 1 ? 0 : 0.5,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeOutCubic,
|
||||
child: Image.asset('text_play_sort.png'.acgImgPath, width: 18),
|
||||
child: Image.asset('text_play_sort.png'.acgImgPath,
|
||||
width: 18),
|
||||
),
|
||||
4.sizeBoxW,
|
||||
Text(
|
||||
sortType == 1 ? '正序' : '倒序',
|
||||
style: textStyle(12, Colors.white.withValues(alpha: .45), FontWeight.w400),
|
||||
style: textStyle(12, Colors.white.withValues(alpha: .45),
|
||||
FontWeight.w400),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -96,52 +99,57 @@ class _AudioPlayerBottomSheetState extends State<AudioPlayerBottomSheet> {
|
||||
final model = widget.allAudiobooks[realIndex];
|
||||
final select = widget.currentPlayIndex == realIndex;
|
||||
return StaggerInItem(
|
||||
index: index,
|
||||
child: InkWell(enableFeedback: false,
|
||||
onTap: () {
|
||||
Get.back(result: realIndex);
|
||||
widget.indexHandler?.call(realIndex);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: Column(
|
||||
children: [
|
||||
16.sizeBoxH,
|
||||
Row(
|
||||
index: index,
|
||||
child: InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () {
|
||||
Get.back(result: realIndex);
|
||||
widget.indexHandler?.call(realIndex);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: Column(
|
||||
children: [
|
||||
if (select) ...[
|
||||
AudioWaveView(),
|
||||
8.sizeBoxW,
|
||||
],
|
||||
Expanded(
|
||||
child: Text(
|
||||
//与详情/抽屉子集 title 格式一致:第X章
|
||||
'第${model.episodeNumber ?? 1}${widget.unit ?? ''}',
|
||||
style: TextStyle(
|
||||
color: select ? AppColors.actionRed : Colors.white.withValues(alpha: .9),
|
||||
fontSize: 14,
|
||||
16.sizeBoxH,
|
||||
Row(
|
||||
children: [
|
||||
if (select) ...[
|
||||
AudioWaveView(),
|
||||
8.sizeBoxW,
|
||||
],
|
||||
Expanded(
|
||||
child: Text(
|
||||
//与详情/抽屉子集 title 格式一致:第X章
|
||||
'第${model.episodeNumber ?? 1}${widget.unit ?? ''}',
|
||||
style: TextStyle(
|
||||
color: select
|
||||
? AppColors.actionRed
|
||||
: Colors.white
|
||||
.withValues(alpha: .9),
|
||||
fontSize: 14,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
//无任何权限时,前 N 集免费展示「免费」角标
|
||||
if (widget.hasPermission == false &&
|
||||
model.inFreeEpisode) ...[
|
||||
8.sizeBoxW,
|
||||
const FreeBadge(),
|
||||
],
|
||||
8.sizeBoxW,
|
||||
const Icon(
|
||||
Icons.navigate_next,
|
||||
size: 18,
|
||||
color: Color(0xffDCDCDC),
|
||||
)
|
||||
],
|
||||
),
|
||||
//无任何权限时,前 N 集免费展示「免费」角标
|
||||
if (widget.hasPermission == false && model.inFreeEpisode) ...[
|
||||
8.sizeBoxW,
|
||||
const FreeBadge(),
|
||||
],
|
||||
8.sizeBoxW,
|
||||
const Icon(
|
||||
Icons.navigate_next,
|
||||
size: 18,
|
||||
color: Color(0xffDCDCDC),
|
||||
)
|
||||
16.sizeBoxH,
|
||||
],
|
||||
),
|
||||
16.sizeBoxH,
|
||||
],
|
||||
)),
|
||||
));
|
||||
)),
|
||||
));
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -2,11 +2,11 @@ import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/assets_tool/app_colors.dart';
|
||||
import 'package:hgmj/assets_tool/images.dart';
|
||||
import 'package:hgmj/hj_utils/date_time_util.dart';
|
||||
import 'package:hgmj/hj_utils/screen.dart';
|
||||
import 'package:hgmj/tools_base/widget/net_image_widget.dart';
|
||||
import 'package:wydj/assets_tool/app_colors.dart';
|
||||
import 'package:wydj/assets_tool/images.dart';
|
||||
import 'package:wydj/hj_utils/date_time_util.dart';
|
||||
import 'package:wydj/hj_utils/screen.dart';
|
||||
import 'package:wydj/tools_base/widget/net_image_widget.dart';
|
||||
|
||||
import 'audio_player_bottomsheet.dart';
|
||||
import 'widget/acg_source_manager.dart';
|
||||
@@ -16,7 +16,8 @@ class AudioPlayerContent extends StatefulWidget {
|
||||
final int index;
|
||||
final AudioPlayerManager manager;
|
||||
final ACGSourceManager sourceManager;
|
||||
const AudioPlayerContent(this.sourceManager, {super.key, required this.manager, this.index = 0});
|
||||
const AudioPlayerContent(this.sourceManager,
|
||||
{super.key, required this.manager, this.index = 0});
|
||||
|
||||
@override
|
||||
State<AudioPlayerContent> createState() => _AudioPlayerContentState();
|
||||
@@ -33,7 +34,8 @@ class _AudioPlayerContentState extends State<AudioPlayerContent> {
|
||||
// - onInit 已 initPlayer
|
||||
// - onClose 已 dispose
|
||||
// 此处不要再调 initPlayer,否则 4 个 stream listener 重复注册导致旧 sub 泄漏
|
||||
manager.play(widget.sourceManager.allEpisodes[widget.index].getRealAudioUrl);
|
||||
manager
|
||||
.play(widget.sourceManager.allEpisodes[widget.index].getRealAudioUrl);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -60,7 +62,10 @@ class _AudioPlayerContentState extends State<AudioPlayerContent> {
|
||||
children: [
|
||||
Text(
|
||||
'正在播放:第${(audioValue.onPlayIndex ?? 0) + 1}集',
|
||||
style: TextStyle(color: Colors.white, fontSize: 16.sp, fontWeight: FontWeight.w500),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16.sp,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -76,7 +81,8 @@ class _AudioPlayerContentState extends State<AudioPlayerContent> {
|
||||
}
|
||||
|
||||
// 进度条行:左右 ±10s 快进退、中间滑块、两侧时间
|
||||
Widget _progressRow(AudioPlayerValue audioValue, double progress, double max) {
|
||||
Widget _progressRow(
|
||||
AudioPlayerValue audioValue, double progress, double max) {
|
||||
return Row(
|
||||
children: [
|
||||
Column(
|
||||
@@ -96,7 +102,10 @@ class _AudioPlayerContentState extends State<AudioPlayerContent> {
|
||||
buildMMSS(audioValue.position?.inSeconds ?? 0),
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
style: TextStyle(color: Colors.white.withValues(alpha: .9), fontSize: 12, fontWeight: FontWeight.w500),
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .9),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -115,7 +124,8 @@ class _AudioPlayerContentState extends State<AudioPlayerContent> {
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => manager.seekTenMinsWithDirection(1),
|
||||
child: Image.asset('noval_time_forword.png'.acgImgPath, width: 24),
|
||||
child:
|
||||
Image.asset('noval_time_forword.png'.acgImgPath, width: 24),
|
||||
),
|
||||
8.sizeBoxH,
|
||||
SizedBox(
|
||||
@@ -158,9 +168,12 @@ class _AudioPlayerContentState extends State<AudioPlayerContent> {
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
transitionBuilder: (child, anim) => ScaleTransition(scale: anim, child: child),
|
||||
transitionBuilder: (child, anim) =>
|
||||
ScaleTransition(scale: anim, child: child),
|
||||
child: Image.asset(
|
||||
isLoop ? 'single_cycle.png'.acgImgPath : 'list_cycle.png'.acgImgPath,
|
||||
isLoop
|
||||
? 'single_cycle.png'.acgImgPath
|
||||
: 'list_cycle.png'.acgImgPath,
|
||||
// key 必须按状态区分,AnimatedSwitcher 才会把它当成新 child 触发动画
|
||||
key: ValueKey(isLoop ? 'loop' : 'loop_list'),
|
||||
width: 24,
|
||||
@@ -169,7 +182,10 @@ class _AudioPlayerContentState extends State<AudioPlayerContent> {
|
||||
2.sizeBoxH,
|
||||
Text(
|
||||
isLoop ? '单曲循环' : '多集循环',
|
||||
style: TextStyle(color: Colors.white.withValues(alpha: .9), fontSize: 12, fontWeight: FontWeight.w400),
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .9),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -188,7 +204,8 @@ class _AudioPlayerContentState extends State<AudioPlayerContent> {
|
||||
onTap: () => isPlaying ? manager.pause() : manager.resume(),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
transitionBuilder: (child, anim) => ScaleTransition(scale: anim, child: child),
|
||||
transitionBuilder: (child, anim) =>
|
||||
ScaleTransition(scale: anim, child: child),
|
||||
child: Image.asset(
|
||||
isPlaying ? 'pause.png'.acgImgPath : 'play.png'.acgImgPath,
|
||||
key: ValueKey(isPlaying ? 'pause' : 'play'),
|
||||
@@ -214,7 +231,10 @@ class _AudioPlayerContentState extends State<AudioPlayerContent> {
|
||||
2.sizeBoxH,
|
||||
Text(
|
||||
'播放列表',
|
||||
style: TextStyle(color: Colors.white.withValues(alpha: .9), fontSize: 12, fontWeight: FontWeight.w400),
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .9),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -278,7 +298,8 @@ class GradientTrackShape extends SliderTrackShape {
|
||||
}) {
|
||||
final trackHeight = sliderTheme.trackHeight ?? 2;
|
||||
final trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2;
|
||||
return Rect.fromLTWH(offset.dx, trackTop, parentBox.size.width, trackHeight);
|
||||
return Rect.fromLTWH(
|
||||
offset.dx, trackTop, parentBox.size.width, trackHeight);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -303,12 +324,14 @@ class GradientTrackShape extends SliderTrackShape {
|
||||
final inactivePaint = Paint()..color = const Color(0xff3D3D3D);
|
||||
// 左侧已播放
|
||||
context.canvas.drawRect(
|
||||
Rect.fromLTRB(trackRect.left, trackRect.top, thumbCenter.dx, trackRect.bottom),
|
||||
Rect.fromLTRB(
|
||||
trackRect.left, trackRect.top, thumbCenter.dx, trackRect.bottom),
|
||||
activePaint,
|
||||
);
|
||||
// 右侧未播放
|
||||
context.canvas.drawRect(
|
||||
Rect.fromLTRB(thumbCenter.dx, trackRect.top, trackRect.right, trackRect.bottom),
|
||||
Rect.fromLTRB(
|
||||
thumbCenter.dx, trackRect.top, trackRect.right, trackRect.bottom),
|
||||
inactivePaint,
|
||||
);
|
||||
}
|
||||
@@ -321,7 +344,8 @@ class GradientThumbShape extends SliderComponentShape {
|
||||
const GradientThumbShape({this.radius = 8});
|
||||
|
||||
@override
|
||||
Size getPreferredSize(bool isEnabled, bool isDiscrete) => Size.fromRadius(radius);
|
||||
Size getPreferredSize(bool isEnabled, bool isDiscrete) =>
|
||||
Size.fromRadius(radius);
|
||||
|
||||
@override
|
||||
void paint(
|
||||
@@ -339,7 +363,8 @@ class GradientThumbShape extends SliderComponentShape {
|
||||
required double value,
|
||||
}) {
|
||||
final canvas = context.canvas;
|
||||
final outerPaint = Paint()..color = AppColors.actionRed.withValues(alpha: 0.7);
|
||||
final outerPaint = Paint()
|
||||
..color = AppColors.actionRed.withValues(alpha: 0.7);
|
||||
final innerPaint = Paint()..color = Colors.white;
|
||||
canvas.drawCircle(center, radius, outerPaint);
|
||||
canvas.drawCircle(center, radius - 3, innerPaint);
|
||||
|
||||
@@ -2,9 +2,9 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/hj_utils/const.dart';
|
||||
import 'package:hgmj/tools_base/event_bus/events.dart';
|
||||
import 'package:hgmj/tools_base/widget/scroll_positioned_list/src/scrollable_positioned_list.dart';
|
||||
import 'package:wydj/hj_utils/const.dart';
|
||||
import 'package:wydj/tools_base/event_bus/events.dart';
|
||||
import 'package:wydj/tools_base/widget/scroll_positioned_list/src/scrollable_positioned_list.dart';
|
||||
|
||||
import '../../../hj_model/acg/comic_chapters_model.dart';
|
||||
import '../../../hj_model/cartoon_media_info.dart';
|
||||
@@ -15,7 +15,8 @@ import 'cartoon_read_page.dart';
|
||||
import 'widget/acg_source_manager.dart';
|
||||
import 'widget/cartoon_subset_alert.dart';
|
||||
|
||||
class CartoonDetailLogic extends GetxController with GetTickerProviderStateMixin {
|
||||
class CartoonDetailLogic extends GetxController
|
||||
with GetTickerProviderStateMixin {
|
||||
String? id;
|
||||
CartoonMediaInfo? mediaInfo;
|
||||
|
||||
@@ -39,7 +40,8 @@ class CartoonDetailLogic extends GetxController with GetTickerProviderStateMixin
|
||||
late ACGSourceManager manager;
|
||||
late StreamSubscription subscription;
|
||||
|
||||
CartoonDetailLogic({this.id}) : manager = ACGSourceManager(id ?? ''); //初始化,加载子集
|
||||
CartoonDetailLogic({this.id})
|
||||
: manager = ACGSourceManager(id ?? ''); //初始化,加载子集
|
||||
|
||||
@override
|
||||
void onReady() async {
|
||||
@@ -88,7 +90,8 @@ class CartoonDetailLogic extends GetxController with GetTickerProviderStateMixin
|
||||
|
||||
play() async {
|
||||
//判断权限
|
||||
ComicChapterInfo? info = await manager.getEpisodeHasPermisson(manager.index);
|
||||
ComicChapterInfo? info =
|
||||
await manager.getEpisodeHasPermisson(manager.index);
|
||||
if (info == null) return;
|
||||
Get.to(() => CartoonReadPage(manager: manager..index = manager.index));
|
||||
}
|
||||
|
||||
@@ -3,15 +3,15 @@ import 'dart:math';
|
||||
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/assets_tool/app_colors.dart';
|
||||
import 'package:hgmj/assets_tool/images.dart';
|
||||
import 'package:hgmj/extension/extensions.dart';
|
||||
import 'package:hgmj/tools_base/indicator/custom_tab_indicator.dart';
|
||||
import 'package:hgmj/tools_base/toast.dart';
|
||||
import 'package:hgmj/tools_base/unique_tag_mixin.dart';
|
||||
import 'package:hgmj/tools_base/widget/follow_button.dart';
|
||||
import 'package:hgmj/tools_base/widget/keep_alive_widget.dart';
|
||||
import 'package:hgmj/tools_base/widget/net_image_widget.dart';
|
||||
import 'package:wydj/assets_tool/app_colors.dart';
|
||||
import 'package:wydj/assets_tool/images.dart';
|
||||
import 'package:wydj/extension/extensions.dart';
|
||||
import 'package:wydj/tools_base/indicator/custom_tab_indicator.dart';
|
||||
import 'package:wydj/tools_base/toast.dart';
|
||||
import 'package:wydj/tools_base/unique_tag_mixin.dart';
|
||||
import 'package:wydj/tools_base/widget/follow_button.dart';
|
||||
import 'package:wydj/tools_base/widget/keep_alive_widget.dart';
|
||||
import 'package:wydj/tools_base/widget/net_image_widget.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../../hj_utils/const.dart';
|
||||
@@ -38,7 +38,8 @@ class CartoonDetailPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
// 详情页可经「推荐页 → 再开详情页」成环并存,用 UniqueTagMixin 给每个实例唯一 tag
|
||||
class _CartoonDetailPageState extends State<CartoonDetailPage> with UniqueTagMixin {
|
||||
class _CartoonDetailPageState extends State<CartoonDetailPage>
|
||||
with UniqueTagMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -65,7 +66,8 @@ class _CartoonDetailPageState extends State<CartoonDetailPage> with UniqueTagMix
|
||||
}
|
||||
|
||||
// 当前内容对应的推荐类型(小说等无对应 tab 时为 null)
|
||||
MediaStyle? _selfStyle(CartoonDetailLogic logic) => switch (logic.mediaInfo?.mediaType) {
|
||||
MediaStyle? _selfStyle(CartoonDetailLogic logic) =>
|
||||
switch (logic.mediaInfo?.mediaType) {
|
||||
'video' => MediaStyle.Cartoon,
|
||||
'image' => MediaStyle.Comics,
|
||||
_ => null,
|
||||
@@ -111,7 +113,8 @@ class _CartoonDetailPageState extends State<CartoonDetailPage> with UniqueTagMix
|
||||
return [
|
||||
SliverToBoxAdapter(child: _buildImgHeader(logic)), //header
|
||||
SliverToBoxAdapter(child: _buildMenu(logic)), //目录
|
||||
SliverToBoxAdapter(child: _buildTabbar(logic, styles)) //推荐 tabbar
|
||||
SliverToBoxAdapter(
|
||||
child: _buildTabbar(logic, styles)) //推荐 tabbar
|
||||
];
|
||||
},
|
||||
//推荐
|
||||
@@ -137,8 +140,10 @@ class _CartoonDetailPageState extends State<CartoonDetailPage> with UniqueTagMix
|
||||
if (logic.isCartoonType) ...[
|
||||
Consumer<ACGSourceManager>(
|
||||
builder: (context, manager, child) {
|
||||
bool liked = manager.mediaInfo?.mediaStatus?.hasLiked == true;
|
||||
return InkWell(enableFeedback: false,
|
||||
bool liked =
|
||||
manager.mediaInfo?.mediaStatus?.hasLiked == true;
|
||||
return InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => manager.onLikeAction(),
|
||||
child: Container(
|
||||
width: 108,
|
||||
@@ -152,13 +157,16 @@ class _CartoonDetailPageState extends State<CartoonDetailPage> with UniqueTagMix
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset(
|
||||
liked ? 'acg_like_sel.png'.acgImgPath : 'acg_like_nor.png'.acgImgPath,
|
||||
liked
|
||||
? 'acg_like_sel.png'.acgImgPath
|
||||
: 'acg_like_nor.png'.acgImgPath,
|
||||
width: 16,
|
||||
),
|
||||
10.sizeBoxW,
|
||||
Text(
|
||||
'喜欢',
|
||||
style: textStyle(16, Color(0xff989898), FontWeight.w500),
|
||||
style: textStyle(
|
||||
16, Color(0xff989898), FontWeight.w500),
|
||||
),
|
||||
],
|
||||
)),
|
||||
@@ -168,7 +176,8 @@ class _CartoonDetailPageState extends State<CartoonDetailPage> with UniqueTagMix
|
||||
12.sizeBoxW,
|
||||
],
|
||||
Expanded(
|
||||
child: InkWell(enableFeedback: false,
|
||||
child: InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => logic.play(),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
@@ -268,12 +277,14 @@ class _CartoonDetailPageState extends State<CartoonDetailPage> with UniqueTagMix
|
||||
if (logic.mediaInfo?.permission == 0) ...[
|
||||
Text(
|
||||
'VIP',
|
||||
style: textStyle(12, Color(0xff989898), FontWeight.w400),
|
||||
style:
|
||||
textStyle(12, Color(0xff989898), FontWeight.w400),
|
||||
),
|
||||
] else if (logic.mediaInfo?.permission == 1) ...[
|
||||
Text(
|
||||
'${logic.mediaInfo?.price ?? 0}金币',
|
||||
style: textStyle(12, Color(0xff989898), FontWeight.w400),
|
||||
style:
|
||||
textStyle(12, Color(0xff989898), FontWeight.w400),
|
||||
),
|
||||
],
|
||||
],
|
||||
@@ -300,7 +311,8 @@ class _CartoonDetailPageState extends State<CartoonDetailPage> with UniqueTagMix
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: (context, index) {
|
||||
TagsBean? tag = logic.mediaInfo?.tagDetails?[index];
|
||||
return InkWell(enableFeedback: false,
|
||||
return InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () {
|
||||
Get.to(CartoonTagPage(
|
||||
title: tag?.name ?? '',
|
||||
@@ -310,8 +322,9 @@ class _CartoonDetailPageState extends State<CartoonDetailPage> with UniqueTagMix
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
alignment: Alignment.center,
|
||||
decoration:
|
||||
BoxDecoration(color: Colors.white.withValues(alpha: .05), borderRadius: BorderRadius.circular(3)),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: .05),
|
||||
borderRadius: BorderRadius.circular(3)),
|
||||
height: 26,
|
||||
child: Text(
|
||||
tag?.name ?? '',
|
||||
@@ -375,7 +388,8 @@ class _CartoonDetailPageState extends State<CartoonDetailPage> with UniqueTagMix
|
||||
labelStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
labelColor: Colors.white.withValues(alpha: 0.9),
|
||||
unselectedLabelColor: Colors.white.withValues(alpha: .55),
|
||||
unselectedLabelStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
|
||||
unselectedLabelStyle:
|
||||
TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
|
||||
indicator: CustomIndicator(
|
||||
isGradient: true,
|
||||
width: 13,
|
||||
@@ -397,7 +411,9 @@ class _CartoonDetailPageState extends State<CartoonDetailPage> with UniqueTagMix
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
logic.isCartoonType ? '选集' : '共${logic.mediaInfo?.totalEpisode ?? 0}${logic.mediaInfo?.unit}',
|
||||
logic.isCartoonType
|
||||
? '选集'
|
||||
: '共${logic.mediaInfo?.totalEpisode ?? 0}${logic.mediaInfo?.unit}',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
@@ -407,10 +423,13 @@ class _CartoonDetailPageState extends State<CartoonDetailPage> with UniqueTagMix
|
||||
Spacer(),
|
||||
Row(
|
||||
children: [
|
||||
InkWell(enableFeedback: false,
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: logic.onMenuAction,
|
||||
child: Text(
|
||||
logic.isCartoonType ? '全${logic.mediaInfo?.totalEpisode ?? 0}${logic.mediaInfo?.unit}' : '目录',
|
||||
logic.isCartoonType
|
||||
? '全${logic.mediaInfo?.totalEpisode ?? 0}${logic.mediaInfo?.unit}'
|
||||
: '目录',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFA7A7A7),
|
||||
fontSize: 14,
|
||||
@@ -437,10 +456,12 @@ class _CartoonDetailPageState extends State<CartoonDetailPage> with UniqueTagMix
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
final epm = provider.allEpisodes[index];
|
||||
return InkWell(enableFeedback: false,
|
||||
return InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => logic.changeEpisode(index, false),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: logic.isCartoonType ? 6 : 12),
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: logic.isCartoonType ? 6 : 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
@@ -450,13 +471,18 @@ class _CartoonDetailPageState extends State<CartoonDetailPage> with UniqueTagMix
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: textStyle(
|
||||
16,
|
||||
provider.index == index ? Colors.white.withValues(alpha: .9) : Colors.white.withValues(alpha: .45),
|
||||
provider.index == index ? FontWeight.w500 : FontWeight.w400,
|
||||
provider.index == index
|
||||
? Colors.white.withValues(alpha: .9)
|
||||
: Colors.white.withValues(alpha: .45),
|
||||
provider.index == index
|
||||
? FontWeight.w500
|
||||
: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
//无任何权限时,前 N 集免费展示「免费」角标
|
||||
if (provider.mediaInfo?.hasPermission == false && epm.inFreeEpisode) ...[
|
||||
if (provider.mediaInfo?.hasPermission == false &&
|
||||
epm.inFreeEpisode) ...[
|
||||
8.sizeBoxW,
|
||||
const FreeBadge(),
|
||||
],
|
||||
@@ -478,7 +504,8 @@ class _CartoonDetailPageState extends State<CartoonDetailPage> with UniqueTagMix
|
||||
}
|
||||
|
||||
Widget _buildBackView() {
|
||||
return InkWell(enableFeedback: false,
|
||||
return InkWell(
|
||||
enableFeedback: false,
|
||||
child: Image.asset(
|
||||
'back_circle_grey.png'.commonImgPath,
|
||||
width: 24,
|
||||
@@ -488,7 +515,8 @@ class _CartoonDetailPageState extends State<CartoonDetailPage> with UniqueTagMix
|
||||
}
|
||||
|
||||
Widget _buildShareView(CartoonDetailLogic logic) {
|
||||
return InkWell(enableFeedback: false,
|
||||
return InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () {
|
||||
Get.dialog(
|
||||
ShareMediaDialog(
|
||||
@@ -510,20 +538,25 @@ class _CartoonDetailPageState extends State<CartoonDetailPage> with UniqueTagMix
|
||||
return Offstage(
|
||||
offstage: logic.showHeader,
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 16, right: 16, top: screen.paddingTop),
|
||||
padding:
|
||||
EdgeInsets.only(left: 16, right: 16, top: screen.paddingTop),
|
||||
height: 44 + screen.paddingTop,
|
||||
color: Color(0xff05000E),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => Get.back(),
|
||||
child: Icon(Icons.arrow_back_ios, size: 24, color: Colors.white),
|
||||
child:
|
||||
Icon(Icons.arrow_back_ios, size: 24, color: Colors.white),
|
||||
),
|
||||
8.sizeBoxW,
|
||||
Expanded(
|
||||
child: Text(
|
||||
logic.mediaInfo?.title ?? '',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
||||
@@ -2,10 +2,10 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/hj_model/media_content.dart';
|
||||
import 'package:hgmj/tools_base/toast.dart';
|
||||
import 'package:hgmj/tools_base/widget/scroll_positioned_list/src/item_positions_listener.dart';
|
||||
import 'package:hgmj/tools_base/widget/scroll_positioned_list/src/scrollable_positioned_list.dart';
|
||||
import 'package:wydj/hj_model/media_content.dart';
|
||||
import 'package:wydj/tools_base/toast.dart';
|
||||
import 'package:wydj/tools_base/widget/scroll_positioned_list/src/item_positions_listener.dart';
|
||||
import 'package:wydj/tools_base/widget/scroll_positioned_list/src/scrollable_positioned_list.dart';
|
||||
|
||||
import 'widget/acg_source_manager.dart';
|
||||
|
||||
@@ -156,9 +156,11 @@ class CartoonReadLogic extends GetxController {
|
||||
/// [isFirstLoad] 失败时才进错误页
|
||||
Future<void> _openEpisode(int index, {bool isFirstLoad = false}) async {
|
||||
// 首次进入页面已有全屏 loading,不再叠遮罩
|
||||
final model = await manager.getComicsMediaContent(index: index, showLoading: !isFirstLoad);
|
||||
final model = await manager.getComicsMediaContent(
|
||||
index: index, showLoading: !isFirstLoad);
|
||||
if (model == null) {
|
||||
if (isFirstLoad) detailInfo = MediaContent(); // 空 model → id 为 null → 页面走错误态
|
||||
if (isFirstLoad)
|
||||
detailInfo = MediaContent(); // 空 model → id 为 null → 页面走错误态
|
||||
update();
|
||||
return;
|
||||
}
|
||||
@@ -196,7 +198,8 @@ class CartoonReadLogic extends GetxController {
|
||||
void _startTimer() {
|
||||
_timer?.cancel(); // 防重入:先停旧的再建新的,避免重复开启时旧定时器泄漏
|
||||
_timer = Timer.periodic(const Duration(milliseconds: 500), (_) {
|
||||
scrollOffsetCtr.animateScroll(offset: 50, duration: const Duration(milliseconds: 500));
|
||||
scrollOffsetCtr.animateScroll(
|
||||
offset: 50, duration: const Duration(milliseconds: 500));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,23 +3,23 @@ import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/assets_tool/app_colors.dart';
|
||||
import 'package:hgmj/assets_tool/images.dart';
|
||||
import 'package:hgmj/hj_model/video_model.dart';
|
||||
import 'package:hgmj/hj_page/comment/comment_alert.dart';
|
||||
import 'package:hgmj/hj_utils/screen.dart';
|
||||
import 'package:hgmj/tools_base/loading/loading_center_widget.dart';
|
||||
import 'package:hgmj/tools_base/toast.dart';
|
||||
import 'package:hgmj/tools_base/widget/follow_button.dart';
|
||||
import 'package:hgmj/tools_base/widget/net_image_widget.dart';
|
||||
import 'package:hgmj/tools_base/widget/scroll_positioned_list/src/scrollable_positioned_list.dart';
|
||||
import 'package:wydj/assets_tool/app_colors.dart';
|
||||
import 'package:wydj/assets_tool/images.dart';
|
||||
import 'package:wydj/hj_model/video_model.dart';
|
||||
import 'package:wydj/hj_page/comment/comment_alert.dart';
|
||||
import 'package:wydj/hj_utils/screen.dart';
|
||||
import 'package:wydj/tools_base/loading/loading_center_widget.dart';
|
||||
import 'package:wydj/tools_base/toast.dart';
|
||||
import 'package:wydj/tools_base/widget/follow_button.dart';
|
||||
import 'package:wydj/tools_base/widget/net_image_widget.dart';
|
||||
import 'package:wydj/tools_base/widget/scroll_positioned_list/src/scrollable_positioned_list.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';
|
||||
import 'package:hgmj/tools_base/widget/stagger_in_item.dart';
|
||||
import 'package:wydj/tools_base/widget/stagger_in_item.dart';
|
||||
|
||||
// 漫画阅读页面
|
||||
class CartoonReadPage extends StatelessWidget {
|
||||
@@ -42,11 +42,13 @@ class CartoonReadPage extends StatelessWidget {
|
||||
|
||||
Widget _buildBody(BuildContext context, CartoonReadLogic logic) {
|
||||
if (logic.detailInfo == null) return const LoadingCenterWidget();
|
||||
if (logic.detailInfo?.id == null) return CErrorWidget(retryOnTap: logic.loadData);
|
||||
if (logic.detailInfo?.id == null)
|
||||
return CErrorWidget(retryOnTap: logic.loadData);
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
InkWell(enableFeedback: false,
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: logic.changeShowMenu,
|
||||
child: ScrollablePositionedList.builder(
|
||||
scrollAction: logic.onScrollAction,
|
||||
@@ -85,21 +87,27 @@ class CartoonReadPage extends StatelessWidget {
|
||||
padding: EdgeInsets.fromLTRB(10, screen.paddingTop + 8, 10, 8),
|
||||
child: Row(
|
||||
children: [
|
||||
InkWell(enableFeedback: false,
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => Get.back(),
|
||||
child: const Icon(Icons.arrow_back_ios_new, size: 24, color: Colors.white),
|
||||
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),
|
||||
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;
|
||||
final col =
|
||||
manager.mediaInfo?.mediaStatus?.hasCollected ?? false;
|
||||
return FollowButton(
|
||||
mediaId: manager.mediaInfo?.id ?? '',
|
||||
isFollow: col,
|
||||
@@ -112,7 +120,8 @@ class CartoonReadPage extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
12.sizeBoxW,
|
||||
InkWell(enableFeedback: false,
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => Get.dialog(
|
||||
ShareMediaDialog(
|
||||
videoModel: VideoModel()
|
||||
@@ -196,8 +205,10 @@ class CartoonReadPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActionItem({String? title, String? icon, VoidCallback? action, Color? color}) {
|
||||
return InkWell(enableFeedback: false,
|
||||
Widget _buildActionItem(
|
||||
{String? title, String? icon, VoidCallback? action, Color? color}) {
|
||||
return InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: action,
|
||||
child: Column(
|
||||
children: [
|
||||
@@ -223,13 +234,15 @@ class CartoonReadPage extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
InkWell(enableFeedback: false,
|
||||
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)),
|
||||
Text('上一篇',
|
||||
style: textStyle(12, Colors.white, FontWeight.w400)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -238,9 +251,12 @@ class CartoonReadPage extends StatelessWidget {
|
||||
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),
|
||||
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),
|
||||
@@ -261,16 +277,19 @@ class CartoonReadPage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
13.sizeBoxW,
|
||||
InkWell(enableFeedback: false,
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => logic.loadOtherEpisode(true),
|
||||
child: Column(
|
||||
children: [
|
||||
Transform.rotate(
|
||||
angle: pi,
|
||||
child: Image.asset('common_back_new.png'.commonImgPath, width: 24),
|
||||
child: Image.asset('common_back_new.png'.commonImgPath,
|
||||
width: 24),
|
||||
),
|
||||
4.sizeBoxH,
|
||||
Text('下一篇', style: textStyle(12, Colors.white, FontWeight.w400)),
|
||||
Text('下一篇',
|
||||
style: textStyle(12, Colors.white, FontWeight.w400)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -290,10 +309,12 @@ class CartoonReadPage extends StatelessWidget {
|
||||
child: Consumer<ACGSourceManager>(
|
||||
builder: (_, manager, __) {
|
||||
final liked = manager.mediaInfo?.mediaStatus?.hasLiked == true;
|
||||
return InkWell(enableFeedback: false,
|
||||
return InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: manager.onLikeAction,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: .5),
|
||||
borderRadius: const BorderRadius.only(
|
||||
@@ -306,12 +327,15 @@ class CartoonReadPage extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset(
|
||||
liked ? 'acg_like_sel.png'.acgImgPath : 'acg_like_nor.png'.acgImgPath,
|
||||
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)),
|
||||
Text('喜欢',
|
||||
style: textStyle(12, Colors.white, FontWeight.w400)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -344,7 +368,8 @@ class CartoonReadPage extends StatelessWidget {
|
||||
color: Colors.white,
|
||||
),
|
||||
child: Center(
|
||||
child: Image.asset('cartoon_auto_play.gif'.acgImgPath, width: 24),
|
||||
child: Image.asset('cartoon_auto_play.gif'.acgImgPath,
|
||||
width: 24),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -390,9 +415,13 @@ class CartoonReadPage extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
'共${logic.manager.mediaInfo?.totalEpisode}话 ${logic.manager.mediaInfo?.getDetailUpdateString ?? ''}',
|
||||
style: const TextStyle(fontSize: 14, color: Color(0xff141414), fontWeight: FontWeight.w400),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xff141414),
|
||||
fontWeight: FontWeight.w400),
|
||||
),
|
||||
InkWell(enableFeedback: false,
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: logic.toggleSort,
|
||||
child: Row(
|
||||
children: [
|
||||
@@ -400,12 +429,18 @@ class CartoonReadPage extends StatelessWidget {
|
||||
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),
|
||||
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),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xff141414),
|
||||
fontWeight: FontWeight.w400),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -422,13 +457,15 @@ class CartoonReadPage extends StatelessWidget {
|
||||
// 不使用 list.reverse(items 不够会从底部排),采用数据倒序方法
|
||||
final info = logic.sortType
|
||||
? logic.manager.allEpisodes[index]
|
||||
: logic.manager.allEpisodes[logic.manager.allEpisodes.length - index - 1];
|
||||
: 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; //点的就是当前章,不重复加载
|
||||
if (logic.detailInfo?.id == info.id)
|
||||
return; //点的就是当前章,不重复加载
|
||||
logic.loadDataWithIndex(info.id ?? '');
|
||||
},
|
||||
child: Container(
|
||||
@@ -461,7 +498,9 @@ class CartoonReadPage extends StatelessWidget {
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 16.sp,
|
||||
color: select ? AppColors.actionRed : Colors.white.withValues(alpha: 0.55),
|
||||
color: select
|
||||
? AppColors.actionRed
|
||||
: Colors.white.withValues(alpha: 0.55),
|
||||
fontWeight: select ? FontWeight.w500 : FontWeight.w400,
|
||||
),
|
||||
maxLines: 1,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//小说/视频推荐
|
||||
import 'package:hgmj/hj_utils/api_service/acg_service.dart';
|
||||
import 'package:hgmj/hj_utils/const.dart';
|
||||
import 'package:hgmj/tools_base/base_list_controller.dart';
|
||||
import 'package:wydj/hj_utils/api_service/acg_service.dart';
|
||||
import 'package:wydj/hj_utils/const.dart';
|
||||
import 'package:wydj/tools_base/base_list_controller.dart';
|
||||
|
||||
import '../../hj_utils/api_service/common_service.dart';
|
||||
|
||||
@@ -10,7 +10,8 @@ class CartoonRecommendLogic extends ListBaseLogic<dynamic> {
|
||||
final MediaStyle mediaStyle; //推荐类型:Video(真人视频) / Cartoon(动漫) / Comics(漫画)
|
||||
final String? videoTagId; //视频(SP)推荐的 tagId,按当前视频 tag 拉同类
|
||||
|
||||
CartoonRecommendLogic({this.mediaId, this.mediaStyle = MediaStyle.Comics, this.videoTagId});
|
||||
CartoonRecommendLogic(
|
||||
{this.mediaId, this.mediaStyle = MediaStyle.Comics, this.videoTagId});
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
@@ -30,10 +31,15 @@ class CartoonRecommendLogic extends ListBaseLogic<dynamic> {
|
||||
//按类型分流:视频走 getRecommendList,漫画/动漫走 comicsRecommendList
|
||||
Future<(List<dynamic>?, bool)> _fetch(int page) async {
|
||||
if (mediaStyle == MediaStyle.Video) {
|
||||
final resp = await CommonService.getRecommendList(pageNumber: page, pageSize: 12, tagId: videoTagId, newsType: mediaStyle.rankParam);
|
||||
final resp = await CommonService.getRecommendList(
|
||||
pageNumber: page,
|
||||
pageSize: 12,
|
||||
tagId: videoTagId,
|
||||
newsType: mediaStyle.rankParam);
|
||||
return (resp?.videos, resp?.hasNext == true);
|
||||
}
|
||||
final model = await ACGService.comicsRecommendList(page, 12, tagId: mediaId, mediaType: mediaStyle.rankParam);
|
||||
final model = await ACGService.comicsRecommendList(page, 12,
|
||||
tagId: mediaId, mediaType: mediaStyle.rankParam);
|
||||
return (model?.list, model?.hasNext == true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/hj_model/cartoon_media_info.dart';
|
||||
import 'package:hgmj/hj_utils/const.dart';
|
||||
import 'package:hgmj/routers/jump_router.dart';
|
||||
import 'package:hgmj/tools_base/loading/loading_center_widget.dart';
|
||||
import 'package:hgmj/tools_base/refresh/pull_refresh.dart';
|
||||
import 'package:hgmj/tools_base/unique_tag_mixin.dart';
|
||||
import 'package:wydj/hj_model/cartoon_media_info.dart';
|
||||
import 'package:wydj/hj_utils/const.dart';
|
||||
import 'package:wydj/routers/jump_router.dart';
|
||||
import 'package:wydj/tools_base/loading/loading_center_widget.dart';
|
||||
import 'package:wydj/tools_base/refresh/pull_refresh.dart';
|
||||
import 'package:wydj/tools_base/unique_tag_mixin.dart';
|
||||
|
||||
import '../../hj_model/video_model.dart';
|
||||
import '../home/home_cell_style/video_simple_cell.dart';
|
||||
@@ -40,7 +40,8 @@ class CartoonRecommendPage extends StatefulWidget {
|
||||
State<CartoonRecommendPage> createState() => _CartoonRecommendPageState();
|
||||
}
|
||||
|
||||
class _CartoonRecommendPageState extends State<CartoonRecommendPage> with UniqueTagMixin {
|
||||
class _CartoonRecommendPageState extends State<CartoonRecommendPage>
|
||||
with UniqueTagMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<CartoonRecommendLogic>(
|
||||
@@ -52,7 +53,8 @@ class _CartoonRecommendPageState extends State<CartoonRecommendPage> with Unique
|
||||
),
|
||||
builder: (logic) {
|
||||
if (logic.isLoading) return LoadingCenterWidget();
|
||||
if (logic.isEmptyData) return CErrorWidget(retryOnTap: () => logic.loadData());
|
||||
if (logic.isEmptyData)
|
||||
return CErrorWidget(retryOnTap: () => logic.loadData());
|
||||
final list = logic.dataList!;
|
||||
return pullYsRefresh(
|
||||
onInit: (ctr) => logic.refreshCtr = ctr,
|
||||
@@ -68,7 +70,9 @@ class _CartoonRecommendPageState extends State<CartoonRecommendPage> with Unique
|
||||
childAspectRatio: widget.childAspectRatio,
|
||||
),
|
||||
itemCount: list.length,
|
||||
itemBuilder: (_, index) => widget.mediaStyle == MediaStyle.Video ? _videoCell(list[index]) : _acgCell(list[index]),
|
||||
itemBuilder: (_, index) => widget.mediaStyle == MediaStyle.Video
|
||||
? _videoCell(list[index])
|
||||
: _acgCell(list[index]),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hgmj/hj_utils/const.dart';
|
||||
import 'package:hgmj/tools_base/widget/keep_alive_widget.dart';
|
||||
import 'package:wydj/hj_utils/const.dart';
|
||||
import 'package:wydj/tools_base/widget/keep_alive_widget.dart';
|
||||
|
||||
import 'cartoon_sectionlist_sub_page.dart';
|
||||
|
||||
@@ -26,8 +26,10 @@ class CartoonSectionListPage extends StatefulWidget {
|
||||
State<CartoonSectionListPage> createState() => _CartoonSectionListPageState();
|
||||
}
|
||||
|
||||
class _CartoonSectionListPageState extends State<CartoonSectionListPage> with SingleTickerProviderStateMixin {
|
||||
late final TabController tabCtr = TabController(length: _sortTabs.length, vsync: this);
|
||||
class _CartoonSectionListPageState extends State<CartoonSectionListPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final TabController tabCtr =
|
||||
TabController(length: _sortTabs.length, vsync: this);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -73,7 +75,8 @@ class _CartoonSectionListPageState extends State<CartoonSectionListPage> with Si
|
||||
decoration: BoxDecoration(
|
||||
border: index != _sortTabs.length - 1
|
||||
? Border(
|
||||
right: BorderSide(width: .5, color: Colors.white.withValues(alpha: .1)),
|
||||
right: BorderSide(
|
||||
width: .5, color: Colors.white.withValues(alpha: .1)),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
@@ -84,7 +87,8 @@ class _CartoonSectionListPageState extends State<CartoonSectionListPage> with Si
|
||||
labelPadding: EdgeInsets.zero,
|
||||
labelStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
unselectedLabelColor: Colors.white.withValues(alpha: 0.5),
|
||||
unselectedLabelStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
|
||||
unselectedLabelStyle:
|
||||
const TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
|
||||
indicator: const BoxDecoration(),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:hgmj/hj_model/cartoon_media_info.dart';
|
||||
import 'package:hgmj/hj_utils/api_service/acg_service.dart';
|
||||
import 'package:hgmj/tools_base/base_list_controller.dart';
|
||||
import 'package:wydj/hj_model/cartoon_media_info.dart';
|
||||
import 'package:wydj/hj_utils/api_service/acg_service.dart';
|
||||
import 'package:wydj/tools_base/base_list_controller.dart';
|
||||
|
||||
class CartoonSectionSubLogic extends ListBaseLogic<CartoonMediaInfo> {
|
||||
final int? sortType;
|
||||
@@ -8,13 +8,13 @@ class CartoonSectionSubLogic extends ListBaseLogic<CartoonMediaInfo> {
|
||||
|
||||
CartoonSectionSubLogic({this.sortType, this.sectionID});
|
||||
|
||||
|
||||
bool get isCartoon {
|
||||
if(dataList?.isNotEmpty == true){
|
||||
if (dataList?.isNotEmpty == true) {
|
||||
return dataList!.first.mediaType == "video";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
@@ -25,7 +25,6 @@ class CartoonSectionSubLogic extends ListBaseLogic<CartoonMediaInfo> {
|
||||
|
||||
void loadMoreData() => fetchData(isRefresh: false, fetch: _fetch);
|
||||
|
||||
|
||||
Future<(List<CartoonMediaInfo>?, bool)> _fetch(int page) async {
|
||||
final resp = await ACGService.getPreferences(
|
||||
page,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/tools_base/loading/loading_center_widget.dart';
|
||||
import 'package:hgmj/tools_base/refresh/pull_refresh.dart';
|
||||
import 'package:wydj/tools_base/loading/loading_center_widget.dart';
|
||||
import 'package:wydj/tools_base/refresh/pull_refresh.dart';
|
||||
|
||||
import 'acg_widget_item.dart';
|
||||
import 'cartoon_sectionlist_sub_logic.dart';
|
||||
@@ -35,14 +35,15 @@ class CartoonSectionSubPage extends StatelessWidget {
|
||||
Widget _buildGrid(CartoonSectionSubLogic logic) {
|
||||
return GridView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: logic.isCartoon ? 2 : 3,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 5,
|
||||
childAspectRatio: logic.isCartoon ? 174 /138 : 111 / 190,
|
||||
childAspectRatio: logic.isCartoon ? 174 / 138 : 111 / 190,
|
||||
),
|
||||
itemCount: logic.dataList?.length ?? 0,
|
||||
itemBuilder: (context, index) => AcgItemWidget(info: logic.dataList?[index]),
|
||||
itemBuilder: (context, index) =>
|
||||
AcgItemWidget(info: logic.dataList?[index]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hgmj/hj_model/home/plate_model.dart';
|
||||
import 'package:hgmj/hj_utils/const.dart';
|
||||
import 'package:hgmj/tools_base/widget/keep_alive_widget.dart';
|
||||
import 'package:wydj/hj_model/home/plate_model.dart';
|
||||
import 'package:wydj/hj_utils/const.dart';
|
||||
import 'package:wydj/tools_base/widget/keep_alive_widget.dart';
|
||||
|
||||
import 'cartoon_sub_grid_page.dart';
|
||||
|
||||
@@ -21,6 +21,7 @@ class CartoonSubDetailPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CartoonSubGridPage(tagData: tagData, type: type, scrollCtr: scroll).keepAlive;
|
||||
return CartoonSubGridPage(tagData: tagData, type: type, scrollCtr: scroll)
|
||||
.keepAlive;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hgmj/hj_model/home/plate_model.dart';
|
||||
import 'package:hgmj/hj_model/home/module_detail_model.dart';
|
||||
import 'package:hgmj/hj_utils/api_service/vid_service.dart';
|
||||
import 'package:hgmj/hj_utils/const.dart';
|
||||
import 'package:hgmj/tools_base/base_list_controller.dart';
|
||||
import 'package:wydj/hj_model/home/plate_model.dart';
|
||||
import 'package:wydj/hj_model/home/module_detail_model.dart';
|
||||
import 'package:wydj/hj_utils/api_service/vid_service.dart';
|
||||
import 'package:wydj/hj_utils/const.dart';
|
||||
import 'package:wydj/tools_base/base_list_controller.dart';
|
||||
|
||||
class CartoonSubGridLogic extends ListBaseLogic {
|
||||
final MediaStyle type; //-1:动漫和漫画 0:图集 1:黄游 2:小说
|
||||
@@ -30,11 +30,13 @@ class CartoonSubGridLogic extends ListBaseLogic {
|
||||
|
||||
String get newsType {
|
||||
if (tagData?.moduleName == '热门推荐' && tagData?.type == -100) return 'PIC';
|
||||
if (tagData?.moduleName == '热门推荐' && tagData?.type == -101) return 'SEED_LINK';
|
||||
if (tagData?.moduleName == '热门推荐' && tagData?.type == -101)
|
||||
return 'SEED_LINK';
|
||||
return '';
|
||||
}
|
||||
|
||||
bool get isAcg => type == MediaStyle.Novel || type == MediaStyle.Cartoon; //是否是acg模块
|
||||
bool get isAcg =>
|
||||
type == MediaStyle.Novel || type == MediaStyle.Cartoon; //是否是acg模块
|
||||
|
||||
CartoonSubGridLogic({this.tagData, this.type = MediaStyle.Pic});
|
||||
|
||||
@@ -57,7 +59,11 @@ class CartoonSubGridLogic extends ListBaseLogic {
|
||||
SortTab('最多收藏', 7),
|
||||
];
|
||||
} else if (type == MediaStyle.Pic || type == MediaStyle.Novel) {
|
||||
sortTabs = const [SortTab('最多观看', 3), SortTab('最新发布', 1), SortTab('最多收藏', 7)];
|
||||
sortTabs = const [
|
||||
SortTab('最多观看', 3),
|
||||
SortTab('最新发布', 1),
|
||||
SortTab('最多收藏', 7)
|
||||
];
|
||||
} else {
|
||||
sortTabs = const [SortTab('最新', 1), SortTab('最热', 2), SortTab('畅销', 8)];
|
||||
}
|
||||
@@ -74,7 +80,8 @@ class CartoonSubGridLogic extends ListBaseLogic {
|
||||
loadData({int pageNum = 1}) async {
|
||||
dataList ??= [];
|
||||
ModuleDetailModel? retModel;
|
||||
if (tagData?.moduleName == '热门推荐' && (tagData?.type == -100 || tagData?.type == -101)) {
|
||||
if (tagData?.moduleName == '热门推荐' &&
|
||||
(tagData?.type == -100 || tagData?.type == -101)) {
|
||||
//图集
|
||||
retModel = await VidService.communityRecommend(
|
||||
pageNum,
|
||||
@@ -111,7 +118,9 @@ class CartoonSubGridLogic extends ListBaseLogic {
|
||||
}
|
||||
update();
|
||||
refreshCtr?.refreshCompleted();
|
||||
retModel?.hasNext == true ? refreshCtr?.loadComplete() : refreshCtr?.loadNoData();
|
||||
retModel?.hasNext == true
|
||||
? refreshCtr?.loadComplete()
|
||||
: refreshCtr?.loadNoData();
|
||||
}
|
||||
|
||||
loadMoreData() => loadData(pageNum: currentPage + 1);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/assets_tool/app_colors.dart';
|
||||
import 'package:hgmj/hj_model/home/plate_model.dart';
|
||||
import 'package:hgmj/hj_utils/const.dart';
|
||||
import 'package:hgmj/hj_utils/sliver_delegate.dart';
|
||||
import 'package:hgmj/tools_base/banner/ads_grid_view.dart';
|
||||
import 'package:hgmj/tools_base/loading/loading_center_widget.dart';
|
||||
import 'package:hgmj/tools_base/refresh/pull_refresh.dart';
|
||||
import 'package:wydj/assets_tool/app_colors.dart';
|
||||
import 'package:wydj/hj_model/home/plate_model.dart';
|
||||
import 'package:wydj/hj_utils/const.dart';
|
||||
import 'package:wydj/hj_utils/sliver_delegate.dart';
|
||||
import 'package:wydj/tools_base/banner/ads_grid_view.dart';
|
||||
import 'package:wydj/tools_base/loading/loading_center_widget.dart';
|
||||
import 'package:wydj/tools_base/refresh/pull_refresh.dart';
|
||||
|
||||
import '../home/home_cell_style/quick_entry_row.dart';
|
||||
import '../home/home_cell_style/video_simple_cell.dart';
|
||||
@@ -89,7 +89,8 @@ class CartoonSubGridPage extends StatelessWidget {
|
||||
controller: logic.tabCtr,
|
||||
isScrollable: true,
|
||||
//选中/未选中只差颜色,unselectedLabelStyle 不传会 fallback 到 labelStyle
|
||||
labelStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
|
||||
labelStyle:
|
||||
const TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
|
||||
labelColor: Colors.white.withValues(alpha: .9),
|
||||
unselectedLabelColor: Colors.white.withValues(alpha: .55),
|
||||
indicator: const BoxDecoration(), //去掉默认下划线
|
||||
@@ -110,11 +111,13 @@ class CartoonSubGridPage extends StatelessWidget {
|
||||
final list = logic.dataList;
|
||||
//null 是还没拉过,空 list 才是真没数据
|
||||
if (list == null) {
|
||||
return const SliverToBoxAdapter(child: SizedBox(height: 400, child: LoadingCenterWidget()));
|
||||
return const SliverToBoxAdapter(
|
||||
child: SizedBox(height: 400, child: LoadingCenterWidget()));
|
||||
}
|
||||
if (list.isEmpty) {
|
||||
return SliverToBoxAdapter(
|
||||
child: SizedBox(height: 400, child: CErrorWidget(retryOnTap: logic.loadData)),
|
||||
child: SizedBox(
|
||||
height: 400, child: CErrorWidget(retryOnTap: logic.loadData)),
|
||||
);
|
||||
}
|
||||
return SliverPadding(
|
||||
@@ -129,8 +132,11 @@ class CartoonSubGridPage extends StatelessWidget {
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
//-1动漫/漫画 0-图集 1-黄游 2-小说
|
||||
(_, index) => switch (type) {
|
||||
MediaStyle.Cartoon || MediaStyle.Novel => AcgItemWidget(info: list[index], coverV: true),
|
||||
MediaStyle.Pic => PhotoGalleryItem(videoModel: list[index], textline: 1),
|
||||
MediaStyle.Cartoon ||
|
||||
MediaStyle.Novel =>
|
||||
AcgItemWidget(info: list[index], coverV: true),
|
||||
MediaStyle.Pic =>
|
||||
PhotoGalleryItem(videoModel: list[index], textline: 1),
|
||||
MediaStyle.Game => VideoSimpleCell(
|
||||
videoModel: list[index],
|
||||
isFromHY: true,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/config/config.dart';
|
||||
import 'package:hgmj/hj_model/home/plate_model.dart';
|
||||
import 'package:hgmj/hj_page/main_page/provider/bottom_bar_provider.dart';
|
||||
import 'package:hgmj/hj_utils/const.dart';
|
||||
import 'package:wydj/config/config.dart';
|
||||
import 'package:wydj/hj_model/home/plate_model.dart';
|
||||
import 'package:wydj/hj_page/main_page/provider/bottom_bar_provider.dart';
|
||||
import 'package:wydj/hj_utils/const.dart';
|
||||
|
||||
class CartoonSubModuleLogic extends GetxController with GetTickerProviderStateMixin {
|
||||
class CartoonSubModuleLogic extends GetxController
|
||||
with GetTickerProviderStateMixin {
|
||||
final int tabIndex; // 未初始化时,内链跳转需要
|
||||
final MediaStyle type; //-1:动漫和漫画 0:图集 1:黄游 2:小说
|
||||
List<ModuleData> tabs = []; //漫画
|
||||
@@ -39,7 +40,8 @@ class CartoonSubModuleLogic extends GetxController with GetTickerProviderStateMi
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
tabController.dispose(); // 自建 TabController,随 logic 释放(含内部 AnimationController + 匿名监听)
|
||||
tabController
|
||||
.dispose(); // 自建 TabController,随 logic 释放(含内部 AnimationController + 匿名监听)
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/hj_page/main_page/provider/bottom_bar_provider.dart';
|
||||
import 'package:hgmj/hj_utils/const.dart';
|
||||
import 'package:hgmj/tools_base/indicator/custom_tab_indicator.dart';
|
||||
import 'package:wydj/hj_page/main_page/provider/bottom_bar_provider.dart';
|
||||
import 'package:wydj/hj_utils/const.dart';
|
||||
import 'package:wydj/tools_base/indicator/custom_tab_indicator.dart';
|
||||
|
||||
import 'cartoon_sub_detail_page.dart';
|
||||
import 'cartoon_sub_module_logic.dart';
|
||||
@@ -12,7 +12,8 @@ class CartoonSubModulePage extends StatelessWidget {
|
||||
final MediaStyle type; //-1:动漫和漫画 0:图集 1:黄游 2:小说
|
||||
final int tabIndex;
|
||||
|
||||
const CartoonSubModulePage({super.key, this.type = MediaStyle.Pic, this.tabIndex = 0});
|
||||
const CartoonSubModulePage(
|
||||
{super.key, this.type = MediaStyle.Pic, this.tabIndex = 0});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -31,8 +32,11 @@ class CartoonSubModulePage extends StatelessWidget {
|
||||
tagData: logic.tabs[index],
|
||||
type: type,
|
||||
index: index,
|
||||
scroll: CommunityBottomProvider()
|
||||
.scrollCtr(type == MediaStyle.Pic ? MediaStyle.Pic : MediaStyle.Novel, index),
|
||||
scroll: CommunityBottomProvider().scrollCtr(
|
||||
type == MediaStyle.Pic
|
||||
? MediaStyle.Pic
|
||||
: MediaStyle.Novel,
|
||||
index),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -59,8 +63,10 @@ class CartoonSubModulePage extends StatelessWidget {
|
||||
labelColor: Colors.white.withValues(alpha: .9),
|
||||
labelStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||
unselectedLabelColor: Colors.white.withValues(alpha: .55),
|
||||
unselectedLabelStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
|
||||
indicator: CustomIndicator(width: 16, height: 2, offsetY: 6, color: Color(0xFFFFC000)),
|
||||
unselectedLabelStyle:
|
||||
const TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
|
||||
indicator: CustomIndicator(
|
||||
width: 16, height: 2, offsetY: 6, color: Color(0xFFFFC000)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||
|
||||
import '../../hj_model/cartoon_media_info.dart';
|
||||
import '../../hj_utils/api_service/acg_service.dart';
|
||||
import 'package:hgmj/tools_base/toast.dart';
|
||||
import 'package:wydj/tools_base/toast.dart';
|
||||
import '../../hj_utils/history_util.dart';
|
||||
import 'package:hgmj/hj_utils/const.dart';
|
||||
import 'package:wydj/hj_utils/const.dart';
|
||||
import '../../routers/jump_router.dart';
|
||||
import 'audio_player_bottomsheet.dart';
|
||||
import 'text_novel_read_page.dart';
|
||||
@@ -71,12 +71,15 @@ class NovelDetailLogic extends GetxController {
|
||||
|
||||
// 获取相关推荐
|
||||
Future fetchRecommendData() async {
|
||||
final result = await ACGService.comicsRecommendList(page, 12, tagId: model?.tagDetails?.firstOrNull?.id ?? '', mediaType: 'text');
|
||||
final result = await ACGService.comicsRecommendList(page, 12,
|
||||
tagId: model?.tagDetails?.firstOrNull?.id ?? '', mediaType: 'text');
|
||||
if (result != null) {
|
||||
page += 1;
|
||||
recommends.addAll(result.list ?? []);
|
||||
}
|
||||
result?.hasNext ?? false ? refreshCtr?.loadComplete() : refreshCtr?.loadNoData();
|
||||
result?.hasNext ?? false
|
||||
? refreshCtr?.loadComplete()
|
||||
: refreshCtr?.loadNoData();
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -96,11 +99,13 @@ class NovelDetailLogic extends GetxController {
|
||||
|
||||
gotoPlay({int index = 0}) async {
|
||||
final mediaSubType = model?.mediaSubType ?? -1;
|
||||
final opm = await manager.getEpisodeHasPermisson(index); //通过时内部已切集,不必再 changeEpisodesIndex
|
||||
final opm = await manager
|
||||
.getEpisodeHasPermisson(index); //通过时内部已切集,不必再 changeEpisodesIndex
|
||||
if (opm != null) {
|
||||
//0-默认文本小说 1-有声小说
|
||||
if (mediaSubType == 0) {
|
||||
Get.to(() => TextNovelReadPage(manager: manager), preventDuplicates: false);
|
||||
Get.to(() => TextNovelReadPage(manager: manager),
|
||||
preventDuplicates: false);
|
||||
} else if (mediaSubType == 1) {
|
||||
Get.to(
|
||||
() => VoiceNovelReadPage(
|
||||
@@ -123,5 +128,4 @@ class NovelDetailLogic extends GetxController {
|
||||
}
|
||||
pushToCartoonPage(info);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/assets_tool/app_colors.dart';
|
||||
import 'package:hgmj/assets_tool/images.dart';
|
||||
import 'package:hgmj/tools_base/toast.dart';
|
||||
import 'package:hgmj/hj_utils/screen.dart';
|
||||
import 'package:hgmj/tools_base/unique_tag_mixin.dart';
|
||||
import 'package:hgmj/tools_base/widget/follow_button.dart';
|
||||
import 'package:wydj/assets_tool/app_colors.dart';
|
||||
import 'package:wydj/assets_tool/images.dart';
|
||||
import 'package:wydj/tools_base/toast.dart';
|
||||
import 'package:wydj/hj_utils/screen.dart';
|
||||
import 'package:wydj/tools_base/unique_tag_mixin.dart';
|
||||
import 'package:wydj/tools_base/widget/follow_button.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../hj_utils/widget_util.dart';
|
||||
@@ -87,7 +87,8 @@ class _NovelDetailPageState extends State<NovelDetailPage> with UniqueTagMixin {
|
||||
padding: EdgeInsets.only(bottom: 80),
|
||||
sliver: SliverGrid.builder(
|
||||
itemCount: _.recommends.length,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 6,
|
||||
@@ -112,7 +113,8 @@ class _NovelDetailPageState extends State<NovelDetailPage> with UniqueTagMixin {
|
||||
),
|
||||
),
|
||||
),
|
||||
InkWell(enableFeedback: false,
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
height: 44,
|
||||
@@ -124,7 +126,10 @@ class _NovelDetailPageState extends State<NovelDetailPage> with UniqueTagMixin {
|
||||
child: Consumer<ACGSourceManager>(
|
||||
builder: (context, value, child) => Text(
|
||||
_.manager.actionTitle(),
|
||||
style: TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.w500),
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
)),
|
||||
onTap: () => _.gotoPlay(index: _.manager.index),
|
||||
@@ -152,7 +157,8 @@ class _NovelDetailPageState extends State<NovelDetailPage> with UniqueTagMixin {
|
||||
color: !_.showHeader ? Color(0xff0E141E) : Colors.transparent,
|
||||
child: Row(
|
||||
children: [
|
||||
InkWell(enableFeedback: false,
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
child: Image.asset('acg_noval_back.png'.acgImgPath, width: 24),
|
||||
onTap: () => Get.back(),
|
||||
),
|
||||
@@ -165,7 +171,8 @@ class _NovelDetailPageState extends State<NovelDetailPage> with UniqueTagMixin {
|
||||
Spacer(),
|
||||
Consumer<ACGSourceManager>(
|
||||
builder: (context, manager, child) {
|
||||
bool col = manager.mediaInfo?.mediaStatus?.hasCollected ?? false;
|
||||
bool col =
|
||||
manager.mediaInfo?.mediaStatus?.hasCollected ?? false;
|
||||
return FollowButton(
|
||||
mediaId: manager.mediaInfo?.id ?? '',
|
||||
isFollow: col,
|
||||
@@ -207,7 +214,8 @@ class _NovelDetailPageState extends State<NovelDetailPage> with UniqueTagMixin {
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
InkWell(enableFeedback: false,
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => _.episodesBottomSheet(),
|
||||
child: Row(
|
||||
children: [
|
||||
@@ -241,7 +249,8 @@ class _NovelDetailPageState extends State<NovelDetailPage> with UniqueTagMixin {
|
||||
),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final epm = manager.allEpisodes[index];
|
||||
return InkWell(enableFeedback: false,
|
||||
return InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => _.gotoPlay(index: index),
|
||||
child: Column(
|
||||
children: [
|
||||
@@ -251,18 +260,22 @@ class _NovelDetailPageState extends State<NovelDetailPage> with UniqueTagMixin {
|
||||
Text(
|
||||
'第${epm.episodeNumber ?? 1}${_.model?.unit}',
|
||||
style: TextStyle(
|
||||
color: manager.index == index ? AppColors.actionRed : Color(0xff989898),
|
||||
color: manager.index == index
|
||||
? AppColors.actionRed
|
||||
: Color(0xff989898),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
//无任何权限时,前 N 集免费展示「免费」角标
|
||||
if (manager.mediaInfo?.hasPermission == false && epm.inFreeEpisode) ...[
|
||||
if (manager.mediaInfo?.hasPermission == false &&
|
||||
epm.inFreeEpisode) ...[
|
||||
6.sizeBoxW,
|
||||
const FreeBadge(),
|
||||
],
|
||||
Spacer(),
|
||||
Icon(Icons.navigate_next_sharp, size: 20, color: Colors.white)
|
||||
Icon(Icons.navigate_next_sharp,
|
||||
size: 20, color: Colors.white)
|
||||
],
|
||||
),
|
||||
12.sizeBoxH,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/assets_tool/images.dart';
|
||||
import 'package:hgmj/hj_utils/screen.dart';
|
||||
import 'package:hgmj/tools_base/widget/net_image_widget.dart';
|
||||
import 'package:wydj/assets_tool/images.dart';
|
||||
import 'package:wydj/hj_utils/screen.dart';
|
||||
import 'package:wydj/tools_base/widget/net_image_widget.dart';
|
||||
|
||||
import '../../hj_model/cartoon_media_info.dart';
|
||||
import '../../hj_model/video_model.dart';
|
||||
@@ -87,17 +87,20 @@ class NovelHeader extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
'共${info?.totalEpisode ?? 0}${info?.unit}',
|
||||
style: textStyle(12, const Color(0xff999999), FontWeight.w400),
|
||||
style:
|
||||
textStyle(12, const Color(0xff999999), FontWeight.w400),
|
||||
),
|
||||
8.sizeBoxW,
|
||||
Text(
|
||||
'/',
|
||||
style: textStyle(12, const Color(0xff999999), FontWeight.w400),
|
||||
style:
|
||||
textStyle(12, const Color(0xff999999), FontWeight.w400),
|
||||
),
|
||||
8.sizeBoxW,
|
||||
Text(
|
||||
info?.updateDesc ?? '',
|
||||
style: textStyle(12, info!.getDetailUpdateColor, FontWeight.w400),
|
||||
style: textStyle(
|
||||
12, info!.getDetailUpdateColor, FontWeight.w400),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -118,10 +121,13 @@ class NovelHeader extends StatelessWidget {
|
||||
runSpacing: 10,
|
||||
maxLines: 1,
|
||||
children: tags
|
||||
.map((tag) => InkWell(enableFeedback: false,
|
||||
onTap: () => Get.to(CartoonTagPage(title: tag.name ?? '', sId: tag.id ?? '')),
|
||||
.map((tag) => InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => Get.to(
|
||||
CartoonTagPage(title: tag.name ?? '', sId: tag.id ?? '')),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xff22252D),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
@@ -129,7 +135,8 @@ class NovelHeader extends StatelessWidget {
|
||||
child: Text(
|
||||
'#${tag.name ?? ''}',
|
||||
maxLines: 1,
|
||||
style: const TextStyle(color: Color(0xFF999999), fontSize: 12),
|
||||
style:
|
||||
const TextStyle(color: Color(0xFF999999), fontSize: 12),
|
||||
),
|
||||
),
|
||||
))
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/assets_tool/images.dart';
|
||||
import 'package:hgmj/hj_model/video_model.dart';
|
||||
import 'package:hgmj/hj_page/find/pic_collect/pics_detail_page.dart';
|
||||
import 'package:hgmj/hj_utils/screen.dart';
|
||||
import 'package:hgmj/tools_base/widget/net_image_widget.dart';
|
||||
import 'package:hgmj/extension/extensions.dart';
|
||||
import 'package:wydj/assets_tool/images.dart';
|
||||
import 'package:wydj/hj_model/video_model.dart';
|
||||
import 'package:wydj/hj_page/find/pic_collect/pics_detail_page.dart';
|
||||
import 'package:wydj/hj_utils/screen.dart';
|
||||
import 'package:wydj/tools_base/widget/net_image_widget.dart';
|
||||
import 'package:wydj/extension/extensions.dart';
|
||||
|
||||
//图集item
|
||||
class PhotoGalleryItem extends StatelessWidget {
|
||||
@@ -13,7 +13,8 @@ class PhotoGalleryItem extends StatelessWidget {
|
||||
final Function? callback;
|
||||
final int textline;
|
||||
|
||||
const PhotoGalleryItem({super.key,
|
||||
const PhotoGalleryItem({
|
||||
super.key,
|
||||
this.videoModel,
|
||||
this.callback,
|
||||
this.textline = 1,
|
||||
@@ -21,7 +22,8 @@ class PhotoGalleryItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(enableFeedback: false,
|
||||
return InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () {
|
||||
if (callback != null) {
|
||||
callback?.call();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/hj_model/media_content.dart';
|
||||
import 'package:hgmj/hj_utils/api_service/acg_service.dart';
|
||||
import 'package:hgmj/tools_base/toast.dart';
|
||||
import 'package:wydj/hj_model/media_content.dart';
|
||||
import 'package:wydj/hj_utils/api_service/acg_service.dart';
|
||||
import 'package:wydj/tools_base/toast.dart';
|
||||
|
||||
import '../../hj_model/acg/comic_chapters_model.dart';
|
||||
import '../../hj_model/cartoon_media_info.dart';
|
||||
@@ -49,7 +49,8 @@ class TextNovelReadLogic extends GetxController {
|
||||
|
||||
// ========== 公开方法 ==========
|
||||
// 获取文字小说。返回 Future 不是可有可无:CErrorWidget 靠它做重试 loading 和防连点
|
||||
Future<void> loadData({bool showLoading = false, bool needToTop = false}) async {
|
||||
Future<void> loadData(
|
||||
{bool showLoading = false, bool needToTop = false}) async {
|
||||
if (showLoading) LoadingHelper.showLoading();
|
||||
final model = await manager.getComicsMediaContent(index: currentEpisode);
|
||||
if (showLoading) LoadingHelper.dismissLoading();
|
||||
@@ -89,7 +90,8 @@ class TextNovelReadLogic extends GetxController {
|
||||
final epm = await manager.getEpisodeHasPermisson(realIndex);
|
||||
if (epm == null) return;
|
||||
LoadingHelper.showLoading();
|
||||
final model = await ACGService.getMediaDetail(mediaId: manager.mediaInfo?.id, id: id);
|
||||
final model =
|
||||
await ACGService.getMediaDetail(mediaId: manager.mediaInfo?.id, id: id);
|
||||
LoadingHelper.dismissLoading();
|
||||
if (model != null) {
|
||||
currentEpisode = realIndex;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/assets_tool/app_colors.dart';
|
||||
import 'package:hgmj/assets_tool/images.dart';
|
||||
import 'package:hgmj/hj_utils/screen.dart';
|
||||
import 'package:hgmj/tools_base/widget/stagger_in_item.dart';
|
||||
import 'package:wydj/assets_tool/app_colors.dart';
|
||||
import 'package:wydj/assets_tool/images.dart';
|
||||
import 'package:wydj/hj_utils/screen.dart';
|
||||
import 'package:wydj/tools_base/widget/stagger_in_item.dart';
|
||||
|
||||
import '../../tools_base/loading/loading_center_widget.dart';
|
||||
import 'text_novel_read_logic.dart';
|
||||
@@ -65,7 +65,8 @@ class TextNovelReadPage extends StatelessWidget {
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
controller: logic.scrollCtr,
|
||||
padding: const EdgeInsets.only(bottom: 60), //给底部菜单栏留位,别被挡住最后几行
|
||||
padding:
|
||||
const EdgeInsets.only(bottom: 60), //给底部菜单栏留位,别被挡住最后几行
|
||||
child: Align(
|
||||
//正文不足一行时居中(长文自然撑满,看不出差别),与旧版 Column 默认的 center 保持一致
|
||||
alignment: Alignment.topCenter,
|
||||
@@ -73,7 +74,8 @@ class TextNovelReadPage extends StatelessWidget {
|
||||
logic.detailInfo?.text ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: (logic.isDark ? Colors.white : Colors.black).withValues(alpha: .9),
|
||||
color: (logic.isDark ? Colors.white : Colors.black)
|
||||
.withValues(alpha: .9),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -98,7 +100,8 @@ class TextNovelReadPage extends StatelessWidget {
|
||||
child: Container(
|
||||
height: height,
|
||||
color: logic.isDark ? Colors.black : Colors.white,
|
||||
padding: EdgeInsets.only(top: screen.paddingTop + 5, left: 16, right: 16),
|
||||
padding:
|
||||
EdgeInsets.only(top: screen.paddingTop + 5, left: 16, right: 16),
|
||||
alignment: Alignment.topCenter, //按钮贴状态栏下沿,余量留在底部
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
@@ -111,7 +114,8 @@ class TextNovelReadPage extends StatelessWidget {
|
||||
Expanded(
|
||||
child: Text(
|
||||
logic.detailInfo?.name ?? '',
|
||||
style: TextStyle(fontSize: 18, color: fg, fontWeight: FontWeight.w600),
|
||||
style: TextStyle(
|
||||
fontSize: 18, color: fg, fontWeight: FontWeight.w600),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
@@ -124,9 +128,12 @@ class TextNovelReadPage extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 7),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
transitionBuilder: (child, anim) => ScaleTransition(scale: anim, child: child),
|
||||
transitionBuilder: (child, anim) =>
|
||||
ScaleTransition(scale: anim, child: child),
|
||||
child: Image.asset(
|
||||
logic.isDark ? 'text_sun.png'.acgImgPath : 'text_moon.png'.acgImgPath,
|
||||
logic.isDark
|
||||
? 'text_sun.png'.acgImgPath
|
||||
: 'text_moon.png'.acgImgPath,
|
||||
// key 按状态区分,AnimatedSwitcher 才会把它当成新 child 触发动画
|
||||
key: ValueKey(logic.isDark),
|
||||
width: 24,
|
||||
@@ -140,7 +147,8 @@ class TextNovelReadPage extends StatelessWidget {
|
||||
onTap: () => Scaffold.of(ctx).openEndDrawer(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 7),
|
||||
child: Image.asset('text_play_menu.png'.acgImgPath, width: 24, color: fg),
|
||||
child: Image.asset('text_play_menu.png'.acgImgPath,
|
||||
width: 24, color: fg),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -151,7 +159,8 @@ class TextNovelReadPage extends StatelessWidget {
|
||||
|
||||
// 底部翻章栏,收起时整条移出屏幕下方
|
||||
Widget _buildFooter(TextNovelReadLogic logic) {
|
||||
final style = TextStyle(fontSize: 14, color: logic.isDark ? Colors.white : Colors.black);
|
||||
final style = TextStyle(
|
||||
fontSize: 14, color: logic.isDark ? Colors.white : Colors.black);
|
||||
return AnimatedPositioned(
|
||||
duration: Duration(milliseconds: logic.durationTime),
|
||||
bottom: logic.showMenu ? 0 : -60,
|
||||
@@ -170,7 +179,8 @@ class TextNovelReadPage extends StatelessWidget {
|
||||
onTap: () => logic.loadOtherEpisode(false, showLoading: true),
|
||||
child: Row(
|
||||
children: [
|
||||
Image.asset('text_last.png'.acgImgPath, color: AppColors.actionRed, width: 20, height: 20),
|
||||
Image.asset('text_last.png'.acgImgPath,
|
||||
color: AppColors.actionRed, width: 20, height: 20),
|
||||
12.sizeBoxW,
|
||||
Text('上一章', style: style),
|
||||
],
|
||||
@@ -183,7 +193,8 @@ class TextNovelReadPage extends StatelessWidget {
|
||||
children: [
|
||||
Text('下一章', style: style),
|
||||
12.sizeBoxW,
|
||||
Image.asset('text_next.png'.acgImgPath, color: AppColors.actionRed, width: 20, height: 20),
|
||||
Image.asset('text_next.png'.acgImgPath,
|
||||
color: AppColors.actionRed, width: 20, height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -207,7 +218,10 @@ class TextNovelReadPage extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
logic.mediaInfo?.title ?? '',
|
||||
style: TextStyle(fontSize: 16, color: fg.withValues(alpha: .9), fontWeight: FontWeight.w500),
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: fg.withValues(alpha: .9),
|
||||
fontWeight: FontWeight.w500),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
@@ -221,7 +235,10 @@ class TextNovelReadPage extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
'共${logic.mediaInfo?.totalEpisode ?? 0}话 ${logic.mediaInfo?.updateDesc ?? ''}',
|
||||
style: const TextStyle(fontSize: 14, color: Color(0xFF1F2029), fontWeight: FontWeight.w500),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF1F2029),
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
@@ -234,12 +251,19 @@ class TextNovelReadPage extends StatelessWidget {
|
||||
turns: logic.isAscend ? 0 : 0.5,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeOutCubic,
|
||||
child: Image.asset('text_play_sort.png'.acgImgPath, width: 20, color: Color(0xFF1F2029),),
|
||||
child: Image.asset(
|
||||
'text_play_sort.png'.acgImgPath,
|
||||
width: 20,
|
||||
color: Color(0xFF1F2029),
|
||||
),
|
||||
),
|
||||
4.sizeBoxW,
|
||||
Text(
|
||||
logic.isAscend ? '正序' : '倒序',
|
||||
style: const TextStyle(fontSize: 14, color: Color(0xFF1F2029), fontWeight: FontWeight.w500),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF1F2029),
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -252,7 +276,8 @@ class TextNovelReadPage extends StatelessWidget {
|
||||
child: GetBuilder<TextNovelReadLogic>(
|
||||
id: 'list',
|
||||
builder: (_) => ListView.builder(
|
||||
key: ValueKey(logic.isAscend), // 切换正/倒序时 key 变化→列表重建→item 重新错峰入场
|
||||
key:
|
||||
ValueKey(logic.isAscend), // 切换正/倒序时 key 变化→列表重建→item 重新错峰入场
|
||||
itemCount: logic.allEpisodes.length,
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
itemBuilder: (_, index) {
|
||||
@@ -261,7 +286,8 @@ class TextNovelReadPage extends StatelessWidget {
|
||||
return StaggerInItem(
|
||||
index: index,
|
||||
child: InkWell(
|
||||
enableFeedback: false, //换掉裸 GestureDetector:整行可点,不再只有文字那一小块
|
||||
enableFeedback:
|
||||
false, //换掉裸 GestureDetector:整行可点,不再只有文字那一小块
|
||||
onTap: () {
|
||||
Get.back();
|
||||
logic.loadDataWithIndex(info.id ?? '');
|
||||
@@ -275,8 +301,11 @@ class TextNovelReadPage extends StatelessWidget {
|
||||
'第${info.episodeNumber ?? 1}${logic.mediaInfo?.unit ?? ''}',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: select ? AppColors.actionRed : fg.withValues(alpha: .55),
|
||||
fontWeight: select ? FontWeight.w500 : FontWeight.w400,
|
||||
color: select
|
||||
? AppColors.actionRed
|
||||
: fg.withValues(alpha: .55),
|
||||
fontWeight:
|
||||
select ? FontWeight.w500 : FontWeight.w400,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/tools_base/event_bus/event_bus_util.dart';
|
||||
import 'package:hgmj/tools_base/event_bus/events.dart';
|
||||
import 'package:wydj/tools_base/event_bus/event_bus_util.dart';
|
||||
import 'package:wydj/tools_base/event_bus/events.dart';
|
||||
|
||||
import 'widget/acg_source_manager.dart';
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@ import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/hj_utils/screen.dart';
|
||||
import 'package:hgmj/hj_utils/widget_util.dart';
|
||||
import 'package:hgmj/tools_base/loading/loading_center_widget.dart';
|
||||
import 'package:hgmj/tools_base/unique_tag_mixin.dart';
|
||||
import 'package:wydj/hj_utils/screen.dart';
|
||||
import 'package:wydj/hj_utils/widget_util.dart';
|
||||
import 'package:wydj/tools_base/loading/loading_center_widget.dart';
|
||||
import 'package:wydj/tools_base/unique_tag_mixin.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'audio_player_content.dart';
|
||||
@@ -34,7 +34,8 @@ class VoiceNovelReadPage extends StatefulWidget {
|
||||
|
||||
// 可重复 push(preventDuplicates: false)+ 间接环 NovelDetail→VoiceNovelRead 可能多实例并存,
|
||||
// 用 UniqueTagMixin 给每个实例分独立 tag 隔离
|
||||
class _VoiceNovelReadPageState extends State<VoiceNovelReadPage> with UniqueTagMixin {
|
||||
class _VoiceNovelReadPageState extends State<VoiceNovelReadPage>
|
||||
with UniqueTagMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<VoiceNovelReadLogic>(
|
||||
@@ -49,9 +50,11 @@ class _VoiceNovelReadPageState extends State<VoiceNovelReadPage> with UniqueTagM
|
||||
value: logic.sourceManager,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: InkWell(enableFeedback: false,
|
||||
leading: InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => Get.back(),
|
||||
child: const Icon(Icons.arrow_back_ios, size: 24, color: Colors.white),
|
||||
child: const Icon(Icons.arrow_back_ios,
|
||||
size: 24, color: Colors.white),
|
||||
),
|
||||
centerTitle: false,
|
||||
titleSpacing: -10,
|
||||
@@ -91,17 +94,18 @@ class _VoiceNovelReadPageState extends State<VoiceNovelReadPage> with UniqueTagM
|
||||
padding: const EdgeInsets.fromLTRB(16, 18, 16, 12),
|
||||
child: Text(
|
||||
'相关推荐',
|
||||
style: textStyle(18, Colors.white.withValues(alpha: .9), FontWeight.w600),
|
||||
style: textStyle(
|
||||
18, Colors.white.withValues(alpha: .9), FontWeight.w600),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
body: CartoonRecommendPage(
|
||||
mediaId: logic.sourceManager.mediaInfo?.tagDetails?.firstOrNull?.id ?? '',
|
||||
mediaId:
|
||||
logic.sourceManager.mediaInfo?.tagDetails?.firstOrNull?.id ?? '',
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
tapAction: () => logic.audioManager.pause(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//查看更多
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/assets_tool/images.dart';
|
||||
import 'package:hgmj/hj_utils/screen.dart';
|
||||
import 'package:wydj/assets_tool/images.dart';
|
||||
import 'package:wydj/hj_utils/screen.dart';
|
||||
|
||||
import '../../../hj_utils/widget_util.dart';
|
||||
|
||||
@@ -33,7 +33,9 @@ class _AcgShowMoreTextWidgetState extends State<AcgShowMoreTextWidget> {
|
||||
Widget build(BuildContext context) {
|
||||
//用 LayoutBuilder 拿真实约束宽度测量,避免传入的 maxWidth 与实际渲染宽度不符导致"该展开却没按钮"
|
||||
return LayoutBuilder(builder: (context, cons) {
|
||||
final maxWidth = cons.maxWidth.isFinite ? cons.maxWidth : (widget.maxWidth ?? Get.width);
|
||||
final maxWidth = cons.maxWidth.isFinite
|
||||
? cons.maxWidth
|
||||
: (widget.maxWidth ?? Get.width);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -41,7 +43,9 @@ class _AcgShowMoreTextWidgetState extends State<AcgShowMoreTextWidget> {
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
widget.summary?.isNotEmpty == true ? widget.summary ?? '' : '暂无简介',
|
||||
widget.summary?.isNotEmpty == true
|
||||
? widget.summary ?? ''
|
||||
: '暂无简介',
|
||||
textAlign: TextAlign.left,
|
||||
style: textStyle(12, widget.txColor, FontWeight.w400),
|
||||
maxLines: _isFold ? widget.maxLine : null,
|
||||
@@ -59,7 +63,8 @@ class _AcgShowMoreTextWidgetState extends State<AcgShowMoreTextWidget> {
|
||||
children: [
|
||||
Text(
|
||||
_isFold ? '更多简介' : '收起简介',
|
||||
style: textStyle(12, const Color(0xffEFEFEF), FontWeight.w400),
|
||||
style: textStyle(
|
||||
12, const Color(0xffEFEFEF), FontWeight.w400),
|
||||
),
|
||||
2.sizeBoxW,
|
||||
AnimatedRotation(
|
||||
@@ -93,7 +98,8 @@ class _AcgShowMoreTextWidgetState extends State<AcgShowMoreTextWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
bool _textExceedMaxLines(String text, TextStyle style, int maxLine, double maxWidth) {
|
||||
bool _textExceedMaxLines(
|
||||
String text, TextStyle style, int maxLine, double maxWidth) {
|
||||
//与 Text 组件保持一致:合并 DefaultTextStyle(letterSpacing/字体等差异会导致测量行数偏少)
|
||||
final effectiveStyle = DefaultTextStyle.of(context).style.merge(style);
|
||||
final painter = TextPainter(
|
||||
|
||||
@@ -3,13 +3,13 @@ import 'dart:async';
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/hj_model/cartoon_media_info.dart';
|
||||
import 'package:hgmj/hj_model/media_content.dart';
|
||||
import 'package:hgmj/hj_utils/api_service/common_service.dart';
|
||||
import 'package:hgmj/tools_base/event_bus/event_bus_util.dart';
|
||||
import 'package:hgmj/tools_base/event_bus/events.dart';
|
||||
import 'package:hgmj/tools_base/loading/loading_helper.dart';
|
||||
import 'package:hgmj/tools_base/toast.dart';
|
||||
import 'package:wydj/hj_model/cartoon_media_info.dart';
|
||||
import 'package:wydj/hj_model/media_content.dart';
|
||||
import 'package:wydj/hj_utils/api_service/common_service.dart';
|
||||
import 'package:wydj/tools_base/event_bus/event_bus_util.dart';
|
||||
import 'package:wydj/tools_base/event_bus/events.dart';
|
||||
import 'package:wydj/tools_base/loading/loading_helper.dart';
|
||||
import 'package:wydj/tools_base/toast.dart';
|
||||
|
||||
import '../../../alert/video/comic_buy_alert.dart';
|
||||
import '../../../alert/video/vip_acg_dialog.dart';
|
||||
@@ -44,7 +44,9 @@ class ACGSourceManager extends ChangeNotifier {
|
||||
final liked = mediaInfo?.mediaStatus?.hasLiked ?? false;
|
||||
final mid = mediaInfo?.id ?? '';
|
||||
final type = mediaInfo?.mediaType ?? '';
|
||||
final suc = liked ? await CommonService.cancelLike(mid, type) : await CommonService.sendLike(mid, type);
|
||||
final suc = liked
|
||||
? await CommonService.cancelLike(mid, type)
|
||||
: await CommonService.sendLike(mid, type);
|
||||
LoadingHelper.dismissLoading();
|
||||
if (suc) {
|
||||
mediaInfo?.mediaStatus?.hasLiked = !liked;
|
||||
@@ -94,7 +96,8 @@ class ACGSourceManager extends ChangeNotifier {
|
||||
final list = result.list ?? [];
|
||||
// 每页拉到就按集号标免费集(与播放放行/角标同一口径),避免全部拉完才标导致角标迟到
|
||||
for (final e in list) {
|
||||
e.inFreeEpisode = mediaInfo?.isFreeEpisodeNumber(e.episodeNumber) ?? false;
|
||||
e.inFreeEpisode =
|
||||
mediaInfo?.isFreeEpisodeNumber(e.episodeNumber) ?? false;
|
||||
}
|
||||
allEpisodes.addAll(list);
|
||||
notifyListeners();
|
||||
@@ -146,7 +149,8 @@ class ACGSourceManager extends ChangeNotifier {
|
||||
}
|
||||
|
||||
/// 拉漫画章节详情(带缓存 + 权限校验)
|
||||
Future<MediaContent?> getComicsMediaContent({int index = 0, bool showLoading = false}) async {
|
||||
Future<MediaContent?> getComicsMediaContent(
|
||||
{int index = 0, bool showLoading = false}) async {
|
||||
final cached = mediaContentCache[index];
|
||||
if (cached != null) {
|
||||
changeEpisodesIndex(index); // 命中缓存也要同步当前章,否则退回详情页「续看」还停在旧章
|
||||
@@ -244,9 +248,11 @@ class AudioPlayerManager {
|
||||
double playSpeed = 1;
|
||||
|
||||
void initPlayer() {
|
||||
if (_audioPlayer != null) return; // 防重入:已初始化则跳过,避免 5 个 stream 监听重复注册、旧 sub 泄漏
|
||||
if (_audioPlayer != null)
|
||||
return; // 防重入:已初始化则跳过,避免 5 个 stream 监听重复注册、旧 sub 泄漏
|
||||
_audioPlayer = AudioPlayer();
|
||||
_audioPlayer?.setReleaseMode(playMod == AudioPlayMod.loop ? ReleaseMode.loop : ReleaseMode.stop);
|
||||
_audioPlayer?.setReleaseMode(
|
||||
playMod == AudioPlayMod.loop ? ReleaseMode.loop : ReleaseMode.stop);
|
||||
|
||||
_audioDurationSub = _audioPlayer?.onDurationChanged.listen((event) {
|
||||
audioValue.value = AudioPlayerValue.copyWith(value..duration = event);
|
||||
@@ -270,7 +276,8 @@ class AudioPlayerManager {
|
||||
audioValue.value = AudioPlayerValue.copyWith(value..isPlaying = true);
|
||||
break;
|
||||
case PlayerState.paused:
|
||||
audioValue.value = AudioPlayerValue.copyWith(value..isPlaying = false);
|
||||
audioValue.value =
|
||||
AudioPlayerValue.copyWith(value..isPlaying = false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -292,7 +299,8 @@ class AudioPlayerManager {
|
||||
audioValue.value = AudioPlayerValue.copyWith(value..isPlaying = true);
|
||||
});
|
||||
|
||||
audioValue.value = AudioPlayerValue.copyWith(value..isInitialized = _audioPlayer != null);
|
||||
audioValue.value =
|
||||
AudioPlayerValue.copyWith(value..isInitialized = _audioPlayer != null);
|
||||
}
|
||||
|
||||
void setPlaySpeed(double speed) {
|
||||
@@ -355,9 +363,11 @@ class AudioPlayerManager {
|
||||
}
|
||||
|
||||
void setPlayMod(AudioPlayMod mod) {
|
||||
_audioPlayer?.setReleaseMode(mod == AudioPlayMod.loop ? ReleaseMode.loop : ReleaseMode.stop);
|
||||
_audioPlayer?.setReleaseMode(
|
||||
mod == AudioPlayMod.loop ? ReleaseMode.loop : ReleaseMode.stop);
|
||||
playMod = mod;
|
||||
audioValue.value = AudioPlayerValue.copyWith(value..isLooping = mod == AudioPlayMod.loop);
|
||||
audioValue.value =
|
||||
AudioPlayerValue.copyWith(value..isLooping = mod == AudioPlayMod.loop);
|
||||
}
|
||||
|
||||
Future<void> playWithIndex(int index) async {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/assets_tool/app_colors.dart';
|
||||
import 'package:hgmj/assets_tool/images.dart';
|
||||
import 'package:hgmj/hj_model/acg/comic_chapters_model.dart';
|
||||
import 'package:hgmj/hj_model/cartoon_media_info.dart';
|
||||
import 'package:hgmj/hj_utils/screen.dart';
|
||||
import 'package:hgmj/tools_base/widget/sheet_handle_bar.dart';
|
||||
import 'package:hgmj/tools_base/widget/stagger_in_item.dart';
|
||||
import 'package:wydj/assets_tool/app_colors.dart';
|
||||
import 'package:wydj/assets_tool/images.dart';
|
||||
import 'package:wydj/hj_model/acg/comic_chapters_model.dart';
|
||||
import 'package:wydj/hj_model/cartoon_media_info.dart';
|
||||
import 'package:wydj/hj_utils/screen.dart';
|
||||
import 'package:wydj/tools_base/widget/sheet_handle_bar.dart';
|
||||
import 'package:wydj/tools_base/widget/stagger_in_item.dart';
|
||||
|
||||
import 'acg_source_manager.dart';
|
||||
import 'free_badge.dart';
|
||||
@@ -63,7 +63,8 @@ class _CartoonSubSetAlertState extends State<CartoonSubSetAlert> {
|
||||
16.sizeBoxW,
|
||||
const Text(
|
||||
'漫画列表',
|
||||
style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500),
|
||||
style: TextStyle(
|
||||
color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500),
|
||||
),
|
||||
10.sizeBoxW,
|
||||
const Spacer(),
|
||||
@@ -117,13 +118,16 @@ class _CartoonSubSetAlertState extends State<CartoonSubSetAlert> {
|
||||
//与详情/阅读抽屉子集 title 一致:第X话(CartoonDetailPage 只处理 image,恒为漫画)
|
||||
'第${info.episodeNumber ?? ''}${mediaInfo?.unit ?? ''}',
|
||||
style: TextStyle(
|
||||
color: sel ? Colors.white.withValues(alpha: .9) : Colors.white.withValues(alpha: .45),
|
||||
color: sel
|
||||
? Colors.white.withValues(alpha: .9)
|
||||
: Colors.white.withValues(alpha: .45),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
//无任何权限时,前 N 集免费展示「免费」角标
|
||||
if (mediaInfo?.hasPermission == false && info.inFreeEpisode) ...[
|
||||
if (mediaInfo?.hasPermission == false &&
|
||||
info.inFreeEpisode) ...[
|
||||
8.sizeBoxW,
|
||||
const FreeBadge(),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user