logo和名称替换

This commit is contained in:
谢宇宁
2026-10-14 11:23:36 +07:00
parent 1cb514cd02
commit c5c04a7967
478 changed files with 8990 additions and 4948 deletions
+27 -13
View File
@@ -1,6 +1,6 @@
import 'package:hgmj/hj_model/video_model.dart';
import 'package:hgmj/hj_utils/date_time_util.dart';
import 'package:hgmj/tools_base/global_store/store.dart';
import 'package:wydj/hj_model/video_model.dart';
import 'package:wydj/hj_utils/date_time_util.dart';
import 'package:wydj/tools_base/global_store/store.dart';
import '../../../hj_utils/free_play_manager.dart';
import '../../pre_sale/pre_sale_provider.dart';
@@ -37,9 +37,12 @@ class LongVideoStatus {
//复用多处的固定状态,抽出来免得同一串文案散落
const _none = LongVideoStatus();
const _coinFree = LongVideoStatus(type: LongVideoStatusType.coinFree, desc: '已享金币视频免费特权');
const _skipVip = LongVideoStatus(type: LongVideoStatusType.skipPreview, desc: '跳过预览', isNeedVip: true);
const _skipBuy = LongVideoStatus(type: LongVideoStatusType.skipPreview, desc: '跳过预览', isNeedBuy: true);
const _coinFree =
LongVideoStatus(type: LongVideoStatusType.coinFree, desc: '已享金币视频免费特权');
const _skipVip = LongVideoStatus(
type: LongVideoStatusType.skipPreview, desc: '跳过预览', isNeedVip: true);
const _skipBuy = LongVideoStatus(
type: LongVideoStatusType.skipPreview, desc: '跳过预览', isNeedBuy: true);
/// 计算长视频的观看状态(展示文案 + 是否需付费/开会员)
///
@@ -70,35 +73,46 @@ LongVideoStatus longVideoStatus(VideoModel? model) {
late final canFree = FreePlayManager().useFreePlay(model);
if (model.freeArea == true) {
return const LongVideoStatus(type: LongVideoStatusType.freeVideo, desc: '免费视频');
return const LongVideoStatus(
type: LongVideoStatusType.freeVideo, desc: '免费视频');
}
if (!isVIP && freeCount >= 0 && isZeroCoin && canFree) {
return LongVideoStatus(type: LongVideoStatusType.freeRemain, desc: '免费视频剩余${freeCount + 1}次');
return LongVideoStatus(
type: LongVideoStatusType.freeRemain, desc: '免费视频剩余${freeCount + 1}次');
}
if (isVIP && isZeroCoin) {
// 交给 VipFreeTipView 显示,独立于操作台、3 秒后消失
return const LongVideoStatus(type: LongVideoStatusType.vipFree, desc: '已享VIP免费特权');
return const LongVideoStatus(
type: LongVideoStatusType.vipFree, desc: '已享VIP免费特权');
}
if (hasPaid) {
return const LongVideoStatus(type: LongVideoStatusType.purchased, desc: '已购买完整版');
return const LongVideoStatus(
type: LongVideoStatusType.purchased, desc: '已购买完整版');
}
if (!isVIP && isZeroCoin && canFree) {
return _none; // 免费次数内看过的视频,重复观看仍免费(不显示文案)
}
if (coinFreeLeft >= 0) {
return LongVideoStatus(type: LongVideoStatusType.coinFreeRemain, desc: '免费金币观影数剩余: $coinFreeLeft次');
return LongVideoStatus(
type: LongVideoStatusType.coinFreeRemain,
desc: '免费金币观影数剩余: $coinFreeLeft次');
}
// 往下不再判 !hasPaid:已购的上面就 return 了,走到这儿必定是未购
if (hasCoin && globalStore.isAWVIP) {
// 暗网视频要顶级会员,否则仍需单独买
if (model.isDarkTag && !globalStore.isVIPTopLevel) {
return LongVideoStatus(type: LongVideoStatusType.skipPreview, desc: '${model.coins}金币 跳过预览', isNeedBuy: true);
return LongVideoStatus(
type: LongVideoStatusType.skipPreview,
desc: '${model.coins}金币 跳过预览',
isNeedBuy: true);
}
return _coinFree;
}
// 二级会员 / 金币免费期内 / 本片金币已抵扣,都算已享金币免费特权
if ((hasCoin && globalStore.isSuperVip) ||
(isVIP && DateTimeUtil.calTime3(globalStore.meInfo?.goldVideoFreeExpire) > 0 && (coins ?? 50) <= 50) ||
(isVIP &&
DateTimeUtil.calTime3(globalStore.meInfo?.goldVideoFreeExpire) > 0 &&
(coins ?? 50) <= 50) ||
(isVIP && hasCoin && model.coins == 0)) {
return _coinFree;
}
+1 -1
View File
@@ -2,7 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hgmj/assets_tool/images.dart';
import 'package:wydj/assets_tool/images.dart';
import '../../../hj_model/splash/ads_model.dart';
import '../../../tools_base/banner/ads_banner.dart';
@@ -1,13 +1,13 @@
import 'package:flutter/material.dart';
import 'package:hgmj/assets_tool/images.dart';
import 'package:hgmj/extension/extensions.dart';
import 'package:hgmj/hj_model/video_model.dart';
import 'package:hgmj/hj_utils/api_service/acg_service.dart';
import 'package:hgmj/hj_utils/api_service/common_service.dart';
import 'package:hgmj/hj_utils/api_service/mine_service.dart';
import 'package:hgmj/hj_utils/screen.dart';
import 'package:hgmj/tools_base/debug_log.dart';
import 'package:hgmj/tools_base/toast.dart';
import 'package:wydj/assets_tool/images.dart';
import 'package:wydj/extension/extensions.dart';
import 'package:wydj/hj_model/video_model.dart';
import 'package:wydj/hj_utils/api_service/acg_service.dart';
import 'package:wydj/hj_utils/api_service/common_service.dart';
import 'package:wydj/hj_utils/api_service/mine_service.dart';
import 'package:wydj/hj_utils/screen.dart';
import 'package:wydj/tools_base/debug_log.dart';
import 'package:wydj/tools_base/toast.dart';
import 'package:like_button/like_button.dart';
/// 底部操作栏:观看量 + 点赞/收藏/分享。计数与状态就地改在传入的 [VideoModel] 上,
@@ -37,7 +37,8 @@ class _VideoDetailBottomMenuState extends State<VideoDetailBottomMenu> {
static const _labelStyle = TextStyle(color: Color(0xff989898), fontSize: 12);
/// 计数±1:原值为 null 时按操作前的状态兜底,保证取消后不会变成负数
int _nextCount(int? cur, bool wasOn) => (cur ?? (wasOn ? 1 : 0)) + (wasOn ? -1 : 1);
int _nextCount(int? cur, bool wasOn) =>
(cur ?? (wasOn ? 1 : 0)) + (wasOn ? -1 : 1);
// ========== 点赞 ==========
Future<bool> _onLike() async {
@@ -74,7 +75,8 @@ class _VideoDetailBottomMenuState extends State<VideoDetailBottomMenu> {
} else {
await MineService.postCollect(videoModel?.id, "SP", !preCollect);
}
videoModel?.collectCount = _nextCount(videoModel?.collectCount, preCollect);
videoModel?.collectCount =
_nextCount(videoModel?.collectCount, preCollect);
videoModel?.vidStatus?.hasCollected = !preCollect;
videoModel?.mediaInfo?.mediaStatus?.hasCollected = !preCollect;
videoModel?.mediaInfo?.countCollect = videoModel?.collectCount;
@@ -91,7 +93,8 @@ class _VideoDetailBottomMenuState extends State<VideoDetailBottomMenu> {
return Row(
children: [
Expanded(
child: Text("${videoModel?.playCount?.countStr ?? ""}观看量", style: _labelStyle),
child: Text("${videoModel?.playCount?.countStr ?? ""}观看量",
style: _labelStyle),
),
_likeItem(),
12.sizeBoxW,
@@ -110,7 +113,9 @@ class _VideoDetailBottomMenuState extends State<VideoDetailBottomMenu> {
isLiked: isLike,
size: 24,
likeBuilder: (isLiked) => Image.asset(
isLiked ? "like_red.png".commonImgPath : "video_like_grey.webp".videoPath,
isLiked
? "like_red.png".commonImgPath
: "video_like_grey.webp".videoPath,
),
onTap: (_) => _onLike(),
),
@@ -128,15 +133,20 @@ class _VideoDetailBottomMenuState extends State<VideoDetailBottomMenu> {
// 收藏状态切换时,图标 grey↔red 做 scale 弹出过渡
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(
isCollect ? 'collect_yellow.png'.commonImgPath : 'collect_grey.png'.commonImgPath,
key: ValueKey(isCollect), // key 按状态区分,AnimatedSwitcher 才当成新 child 触发动画
isCollect
? 'collect_yellow.png'.commonImgPath
: 'collect_grey.png'.commonImgPath,
key: ValueKey(
isCollect), // key 按状态区分,AnimatedSwitcher 才当成新 child 触发动画
width: 24,
),
),
2.sizeBoxW,
Text(videoModel?.collectCount.countOr('收藏') ?? '收藏', style: _labelStyle),
Text(videoModel?.collectCount.countOr('收藏') ?? '收藏',
style: _labelStyle),
],
),
);
+29 -17
View File
@@ -1,17 +1,17 @@
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hgmj/hj_model/cartoon_media_info.dart';
import 'package:hgmj/hj_model/video_model.dart';
import 'package:hgmj/tools_base/widget/shrink_wrap.dart';
import 'package:hgmj/hj_page/home/tag/video_tag_page.dart';
import 'package:hgmj/hj_page/video/view/video_detail_bottom_menu.dart';
import 'package:hgmj/hj_utils/const.dart';
import 'package:hgmj/hj_utils/screen.dart';
import 'package:hgmj/tools_base/banner/ads_grid_view.dart';
import 'package:hgmj/tools_base/indicator/custom_tab_indicator.dart';
import 'package:hgmj/tools_base/toast.dart';
import 'package:hgmj/tools_base/widget/keep_alive_widget.dart';
import 'package:wydj/hj_model/cartoon_media_info.dart';
import 'package:wydj/hj_model/video_model.dart';
import 'package:wydj/tools_base/widget/shrink_wrap.dart';
import 'package:wydj/hj_page/home/tag/video_tag_page.dart';
import 'package:wydj/hj_page/video/view/video_detail_bottom_menu.dart';
import 'package:wydj/hj_utils/const.dart';
import 'package:wydj/hj_utils/screen.dart';
import 'package:wydj/tools_base/banner/ads_grid_view.dart';
import 'package:wydj/tools_base/indicator/custom_tab_indicator.dart';
import 'package:wydj/tools_base/toast.dart';
import 'package:wydj/tools_base/widget/keep_alive_widget.dart';
import 'package:video_player/video_player.dart';
import '../../../alert/video/share_media_dialog.dart';
@@ -35,11 +35,13 @@ class VideoDetailView extends StatefulWidget {
State<VideoDetailView> createState() => _VideoDetailViewState();
}
class _VideoDetailViewState extends State<VideoDetailView> with SingleTickerProviderStateMixin {
class _VideoDetailViewState extends State<VideoDetailView>
with SingleTickerProviderStateMixin {
VideoModel? get videoModel => widget.model;
// 视频(SP)推荐按当前视频第一个 tag 拉同类;tags 为空则全局推荐
String? get _videoTagId => videoModel?.tags?.isNotEmpty == true ? videoModel?.tags?.first.id : null;
String? get _videoTagId =>
videoModel?.tags?.isNotEmpty == true ? videoModel?.tags?.first.id : null;
late final TabController tabCtr = TabController(length: 3, vsync: this);
@@ -66,7 +68,8 @@ class _VideoDetailViewState extends State<VideoDetailView> with SingleTickerProv
SliverToBoxAdapter(
child: Container(
margin: EdgeInsets.fromLTRB(12, 18, 12, 0),
child: VideoDetailBottomMenu(model: videoModel, onShare: _onShare),
child:
VideoDetailBottomMenu(model: videoModel, onShare: _onShare),
),
),
SliverToBoxAdapter(
@@ -137,7 +140,11 @@ class _VideoDetailViewState extends State<VideoDetailView> with SingleTickerProv
child: Text(
videoModel?.title?.trim() ?? "",
maxLines: 2,
style: const TextStyle(color: Colors.white, height: 1.5, fontSize: 16, fontWeight: FontWeight.w500),
style: const TextStyle(
color: Colors.white,
height: 1.5,
fontSize: 16,
fontWeight: FontWeight.w500),
),
);
}
@@ -193,14 +200,19 @@ class _VideoDetailViewState extends State<VideoDetailView> with SingleTickerProv
labelStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
labelColor: Color(0xE5FFFFFF),
unselectedLabelColor: Color(0x8CFFFFFF),
unselectedLabelStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
unselectedLabelStyle:
TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
tabs: menuTitles
.map((e) => Padding(
padding: const EdgeInsets.only(bottom: 5),
child: Text(e),
))
.toList(),
indicator: CustomIndicator(isGradient: true, width: 13, height: 3, borderRadius: BorderRadius.circular(1.5)),
indicator: CustomIndicator(
isGradient: true,
width: 13,
height: 3,
borderRadius: BorderRadius.circular(1.5)),
),
],
);
+32 -12
View File
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hgmj/hj_model/video_model.dart';
import 'package:hgmj/hj_page/video/view/long_video_status.dart';
import 'package:hgmj/hj_utils/screen.dart';
import 'package:wydj/hj_model/video_model.dart';
import 'package:wydj/hj_page/video/view/long_video_status.dart';
import 'package:wydj/hj_utils/screen.dart';
import 'package:video_player/video_player.dart';
import '../../../alert/video/buy_vip_alert.dart';
@@ -44,12 +44,16 @@ class _VideoMaskBuyViewState extends State<VideoMaskBuyView> {
String get vipBtnTitle {
if (!hasPresale) return "开通VIP免费看";
if (!presaleProvider.isPayFirst) return "开通预售免费看";
return presaleProvider.canPayBalance ? "支付尾款免费看" : "请在${presaleProvider.startTimeMD}支付尾款";
return presaleProvider.canPayBalance
? "支付尾款免费看"
: "请在${presaleProvider.startTimeMD}支付尾款";
}
/// 开通预售/VIP,回来后刷新按钮文案
Future<void> _onVipTap() async {
await (hasPresale ? Get.to(() => PreSalePage()) : BuyVipAlert.show(videoId: widget.model?.id));
await (hasPresale
? Get.to(() => PreSalePage())
: BuyVipAlert.show(videoId: widget.model?.id));
if (mounted) setState(() {});
}
@@ -80,7 +84,9 @@ class _VideoMaskBuyViewState extends State<VideoMaskBuyView> {
if (isBuying) return;
isBuying = true;
// 观影券抵扣:取能覆盖本片金币的券面额
final couponNum = useCoupon ? globalStore.meInfo?.couponGold(widget.model?.originCoins) : null;
final couponNum = useCoupon
? globalStore.meInfo?.couponGold(widget.model?.originCoins)
: null;
await PayManager().buy(
widget.model?.id,
ProductType.media,
@@ -116,18 +122,30 @@ class _VideoMaskBuyViewState extends State<VideoMaskBuyView> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(widget.maskTitle, style: const TextStyle(color: Color(0xffffffff), fontSize: 16, fontWeight: FontWeight.w500)),
Text(widget.maskTitle,
style: const TextStyle(
color: Color(0xffffffff),
fontSize: 16,
fontWeight: FontWeight.w500)),
18.sizeBoxH,
const Text("开通VIP 全站视频免费看", style: TextStyle(color: Color(0xffffffff), fontSize: 16, fontWeight: FontWeight.w500)),
const Text("开通VIP 全站视频免费看",
style: TextStyle(
color: Color(0xffffffff),
fontSize: 16,
fontWeight: FontWeight.w500)),
18.sizeBoxH,
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_btn(
title: status.isNeedBuy ? "${widget.model?.coins ?? 0}金币解锁" : "邀请得3日VIP",
title: status.isNeedBuy
? "${widget.model?.coins ?? 0}金币解锁"
: "邀请得3日VIP",
colors: const [Color(0xffFFE8BE), Color(0xffE6B764)],
textColor: const Color(0xff694923),
onTap: () => status.isNeedBuy ? _onCoinUnlock() : Get.to(() => MineSharePage()),
onTap: () => status.isNeedBuy
? _onCoinUnlock()
: Get.to(() => MineSharePage()),
),
18.sizeBoxW,
_btn(
@@ -147,11 +165,13 @@ class _VideoMaskBuyViewState extends State<VideoMaskBuyView> {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text("使用观影券", style: TextStyle(color: Colors.white, fontSize: 12)),
const Text("使用观影券",
style: TextStyle(color: Colors.white, fontSize: 12)),
3.sizeBoxW,
Text(
"x${me?.couponCount ?? 0}",
style: const TextStyle(color: Color(0xffE5365C), fontSize: 12),
style: const TextStyle(
color: Color(0xffE5365C), fontSize: 12),
),
],
),
+59 -29
View File
@@ -4,11 +4,11 @@ import 'dart:ui' show ImageFilter;
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_utils/screen.dart';
import 'package:hgmj/tools_base/toast.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_utils/screen.dart';
import 'package:wydj/tools_base/toast.dart';
import 'package:video_player/video_player.dart';
import '../../../hj_utils/buy_util.dart';
@@ -75,13 +75,15 @@ class _VideoMenuViewState extends State<VideoMenuView> {
bool get isPreview {
final model = widget.videoModel;
if (FreePlayManager().useFreePlay(model)) return false;
return model?.previewURL?.isNotEmpty == true && (needVip(model) || needCoin(model));
return model?.previewURL?.isNotEmpty == true &&
(needVip(model) || needCoin(model));
}
/// 未购买/未开通 VIP 时,松手后超出试看区间则回弹到边界
bool get _shouldLimitSeek {
final model = widget.videoModel;
if (model?.mediaInfo?.isCartoonFreeEpisode == true) return false; // 动漫免费集:整条进度可拖,不锁试看区间
if (model?.mediaInfo?.isCartoonFreeEpisode == true)
return false; // 动漫免费集:整条进度可拖,不锁试看区间
if (FreePlayManager().useFreePlay(model)) return false;
if (model?.freeArea == true) return false;
if (model?.vidStatus?.hasPaid == true) return false;
@@ -89,7 +91,8 @@ class _VideoMenuViewState extends State<VideoMenuView> {
if (coinFreeCount(model) >= 0) return false;
if (model?.isCoinVideo() == true) {
if (globalStore.isVIP && model?.coins == 0) return false;
if (model?.videoType != 1 && presaleProvider.coinVideoFreeCount >= 0) return false;
if (model?.videoType != 1 && presaleProvider.coinVideoFreeCount >= 0)
return false;
}
final status = longVideoStatus(model);
return status.isNeedPay;
@@ -122,7 +125,8 @@ class _VideoMenuViewState extends State<VideoMenuView> {
}
/// 试看锁定:需付费(区间非空)且 [sec] 已超出免费试看区间 → 禁止拖动
bool _isSeekLocked(int sec) => _seekRange.$1 != null && widget.videoModel?.isInFreeTime(sec) != true;
bool _isSeekLocked(int sec) =>
_seekRange.$1 != null && widget.videoModel?.isInFreeTime(sec) != true;
/// 松手落点:先夹到 [0,总时长],再夹回试看区间边界
Duration _snapSeek(Duration target) {
@@ -234,7 +238,8 @@ class _VideoMenuViewState extends State<VideoMenuView> {
@override
Widget build(BuildContext context) {
final menuVisible = _showMenu || !_isInited || _isLongPress; //操作台/未初始化/长按倍速时可见
final menuVisible =
_showMenu || !_isInited || _isLongPress; //操作台/未初始化/长按倍速时可见
//试看区间每帧只算一次:_shouldLimitSeek 内含 useFreePlay 等带副作用的判断,不能每次拖动都重跑
_seekRange = _calcSeekRange();
return GestureDetector(
@@ -242,7 +247,8 @@ class _VideoMenuViewState extends State<VideoMenuView> {
onTap: _toggleMenu,
onHorizontalDragStart: (_) {
//试看结束/未初始化禁止拖动
if (_isSeekLocked(playCtr.value.position.inSeconds) || !playCtr.value.isInitialized) return;
if (_isSeekLocked(playCtr.value.position.inSeconds) ||
!playCtr.value.isInitialized) return;
_wasPlaying = playCtr.value.isPlaying;
if (_wasPlaying) playCtr.pause();
_seekPos = playCtr.value.position;
@@ -250,7 +256,8 @@ class _VideoMenuViewState extends State<VideoMenuView> {
setState(() {});
},
onHorizontalDragUpdate: (details) {
if (_isSeekLocked(playCtr.value.position.inSeconds) || !playCtr.value.isInitialized) return;
if (_isSeekLocked(playCtr.value.position.inSeconds) ||
!playCtr.value.isInitialized) return;
_wakeMenu();
_dragSeek(details.delta);
},
@@ -310,7 +317,8 @@ class _VideoMenuViewState extends State<VideoMenuView> {
Positioned(
top: 12,
right: 16,
child: VideoStatusView(model: widget.videoModel, onBuyEvent: widget.onBuyEvent),
child: VideoStatusView(
model: widget.videoModel, onBuyEvent: widget.onBuyEvent),
),
Positioned(bottom: 0, left: 0, right: 0, child: _bottomMenu()),
Obx(() {
@@ -323,7 +331,9 @@ class _VideoMenuViewState extends State<VideoMenuView> {
scale: animation,
child: FadeTransition(opacity: animation, child: child),
),
child: (!_isInited || buffering || playing) ? const SizedBox(key: ValueKey('playIconEmpty')) : _playIcon(),
child: (!_isInited || buffering || playing)
? const SizedBox(key: ValueKey('playIconEmpty'))
: _playIcon(),
);
}),
if (_showSeekText) _seekText(),
@@ -334,7 +344,8 @@ class _VideoMenuViewState extends State<VideoMenuView> {
top: 0,
bottom: 0,
right: _showSpeed ? 0 : -150,
child: IgnorePointer(ignoring: !_showSpeed, child: _speedPanel()),
child: IgnorePointer(
ignoring: !_showSpeed, child: _speedPanel()),
),
if (_skipDir != 0)
Positioned(
@@ -350,7 +361,11 @@ class _VideoMenuViewState extends State<VideoMenuView> {
duration: const Duration(milliseconds: 220),
child: Container(
color: Colors.black.withValues(alpha: 0.5),
padding: EdgeInsets.fromLTRB((_skipDir == 1) ? 12 : 24, 12, (_skipDir == 1) ? 24 : 12, 12),
padding: EdgeInsets.fromLTRB(
(_skipDir == 1) ? 12 : 24,
12,
(_skipDir == 1) ? 24 : 12,
12),
child: _skipHint(),
),
),
@@ -397,7 +412,9 @@ class _VideoMenuViewState extends State<VideoMenuView> {
child: FadeTransition(opacity: animation, child: child),
),
child: Image.asset(
showPause ? "pause_icon.webp".videoPath : "play.webp".videoPath,
showPause
? "pause_icon.webp".videoPath
: "play.webp".videoPath,
key: ValueKey(showPause),
width: 20.w,
),
@@ -442,7 +459,8 @@ class _VideoMenuViewState extends State<VideoMenuView> {
},
child: const Padding(
padding: EdgeInsets.fromLTRB(4, 6, 6, 6),
child: Text("倍速", style: TextStyle(color: Colors.white, fontSize: 14)),
child: Text("倍速",
style: TextStyle(color: Colors.white, fontSize: 14)),
),
),
InkWell(
@@ -452,7 +470,8 @@ class _VideoMenuViewState extends State<VideoMenuView> {
height: 26,
width: 26,
margin: const EdgeInsets.only(right: 6),
child: Image.asset("full_icon.png".videoPath, width: 26, height: 26),
child: Image.asset("full_icon.png".videoPath,
width: 26, height: 26),
),
),
],
@@ -480,7 +499,8 @@ class _VideoMenuViewState extends State<VideoMenuView> {
child: ListView.separated(
padding: EdgeInsets.zero,
itemCount: speeds.length,
separatorBuilder: (_, __) => Container(height: 1, color: Colors.white.withValues(alpha: 0.05)),
separatorBuilder: (_, __) => Container(
height: 1, color: Colors.white.withValues(alpha: 0.05)),
itemBuilder: (_, i) => _speedItem(speeds[i]),
),
),
@@ -538,7 +558,8 @@ class _VideoMenuViewState extends State<VideoMenuView> {
children: [
//顶部偏移叠加安全区高度,避免竖屏被刘海/灵动岛遮挡
//注意:必须用 context 的 MediaQuery(外层 SafeArea 已吃掉 top 时这里就该是 0),Get.mediaQuery 拿的是根节点值会多顶 47
((widget.isFull ? 24 : 12) + MediaQuery.of(context).padding.top).sizeBoxH,
((widget.isFull ? 24 : 12) + MediaQuery.of(context).padding.top)
.sizeBoxH,
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
//半透明黑底:亮色画面上也能看清时间
@@ -551,7 +572,8 @@ class _VideoMenuViewState extends State<VideoMenuView> {
children: [
Text(
DateTimeUtil.formatDuration(showDuration) ?? "",
style: const TextStyle(color: AppColors.actionRed, fontSize: 14), //主题黄
style: const TextStyle(
color: AppColors.actionRed, fontSize: 14), //主题黄
),
if (playCtr.value.isInitialized)
Text(
@@ -571,12 +593,14 @@ class _VideoMenuViewState extends State<VideoMenuView> {
final isForward = _skipDir == 1;
final text = Text(
isForward ? "快进10秒" : "快退10秒",
style: TextStyle(color: Colors.white.withValues(alpha: 0.8), fontSize: 13),
style:
TextStyle(color: Colors.white.withValues(alpha: 0.8), fontSize: 13),
);
final arrows = _SeekArrows(forward: isForward);
return Row(
mainAxisSize: MainAxisSize.min,
children: isForward ? [text, 4.sizeBoxW, arrows] : [arrows, 4.sizeBoxW, text],
children:
isForward ? [text, 4.sizeBoxW, arrows] : [arrows, 4.sizeBoxW, text],
);
}
}
@@ -590,8 +614,11 @@ class _SeekArrows extends StatefulWidget {
State<_SeekArrows> createState() => _SeekArrowsState();
}
class _SeekArrowsState extends State<_SeekArrows> with SingleTickerProviderStateMixin {
late final AnimationController _ctr = AnimationController(vsync: this, duration: const Duration(milliseconds: 900))..repeat();
class _SeekArrowsState extends State<_SeekArrows>
with SingleTickerProviderStateMixin {
late final AnimationController _ctr = AnimationController(
vsync: this, duration: const Duration(milliseconds: 900))
..repeat();
@override
void dispose() {
@@ -605,7 +632,8 @@ class _SeekArrowsState extends State<_SeekArrows> with SingleTickerProviderState
? const Icon(Icons.play_arrow_rounded, color: Colors.white, size: 18)
: const RotatedBox(
quarterTurns: 2,
child: Icon(Icons.play_arrow_rounded, color: Colors.white, size: 18),
child:
Icon(Icons.play_arrow_rounded, color: Colors.white, size: 18),
);
return AnimatedBuilder(
animation: _ctr,
@@ -615,9 +643,11 @@ class _SeekArrowsState extends State<_SeekArrows> with SingleTickerProviderState
// 波沿方向流动:快进 左→右、快退 右→左
final i = widget.forward ? index : (2 - index);
final phase = (_ctr.value + i / 3.0) % 1.0;
final opacity = 0.25 + 0.75 * (1 - (2 * phase - 1).abs()); // 三角波 0.25~1.0
final opacity =
0.25 + 0.75 * (1 - (2 * phase - 1).abs()); // 三角波 0.25~1.0
// 压窄每个箭头的占位宽度,让三角波间距更紧凑
return SizedBox(width: 10, child: Opacity(opacity: opacity, child: arrow));
return SizedBox(
width: 10, child: Opacity(opacity: opacity, child: arrow));
}),
),
);
@@ -1,7 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:hgmj/hj_model/video_model.dart';
import 'package:wydj/hj_model/video_model.dart';
import 'long_video_status.dart';
@@ -39,7 +39,8 @@ class _VipFreeTipViewState extends State<VipFreeTipView> {
Widget build(BuildContext context) {
if (!_isShowTip) return const SizedBox();
// 仅「已享VIP免费特权」走本 view,3 秒后自动隐藏;其余状态归 VideoStatusView
if (longVideoStatus(widget.videoModel).type != LongVideoStatusType.vipFree) {
if (longVideoStatus(widget.videoModel).type !=
LongVideoStatusType.vipFree) {
return const SizedBox();
}
_startHideTimer();
@@ -70,9 +71,12 @@ class VideoStatusView extends StatelessWidget {
if (model == null) return const SizedBox.shrink();
final status = longVideoStatus(model);
final isCartoon = model?.videoType == 1;
final hideForCartoon =
isCartoon && (status.type == LongVideoStatusType.skipPreview || status.type == LongVideoStatusType.coinFree);
if (hideForCartoon || status.type == LongVideoStatusType.none || status.type == LongVideoStatusType.vipFree) {
final hideForCartoon = isCartoon &&
(status.type == LongVideoStatusType.skipPreview ||
status.type == LongVideoStatusType.coinFree);
if (hideForCartoon ||
status.type == LongVideoStatusType.none ||
status.type == LongVideoStatusType.vipFree) {
return const SizedBox();
}
return GestureDetector(
@@ -1,13 +1,13 @@
import 'package:flutter/material.dart';
import 'package:hgmj/assets_tool/app_colors.dart';
import 'package:hgmj/assets_tool/images.dart';
import 'package:hgmj/config/address.dart';
import 'package:hgmj/extension/extensions.dart';
import 'package:hgmj/hj_model/video_model.dart';
import 'package:hgmj/tools_base/video_download/download_button.dart';
import 'package:hgmj/hj_utils/screen.dart';
import 'package:hgmj/tools_base/debug_log.dart';
import 'package:hgmj/tools_base/indicator/custom_tab_indicator.dart';
import 'package:wydj/assets_tool/app_colors.dart';
import 'package:wydj/assets_tool/images.dart';
import 'package:wydj/config/address.dart';
import 'package:wydj/extension/extensions.dart';
import 'package:wydj/hj_model/video_model.dart';
import 'package:wydj/tools_base/video_download/download_button.dart';
import 'package:wydj/hj_utils/screen.dart';
import 'package:wydj/tools_base/debug_log.dart';
import 'package:wydj/tools_base/indicator/custom_tab_indicator.dart';
import '../../../alert/video/video_line_menu_alert.dart';
import '../../../hj_model/splash/domain_source_model.dart';
@@ -40,7 +40,8 @@ class _VideoTabbarMenuWidgetState extends State<VideoTabbarMenuWidget> {
Widget build(BuildContext context) {
Domain? selectedCnd;
try {
selectedCnd = Address.cdnAddressLists.firstWhere((element) => element.url == Address.cdnAddress);
selectedCnd = Address.cdnAddressLists
.firstWhere((element) => element.url == Address.cdnAddress);
} catch (e) {
debugLog(e);
}
@@ -61,15 +62,18 @@ class _VideoTabbarMenuWidgetState extends State<VideoTabbarMenuWidget> {
labelPadding: const EdgeInsets.symmetric(horizontal: 12),
labelColor: const Color(0xE5FFFFFF),
unselectedLabelColor: const Color(0x73FFFFFF),
labelStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
unselectedLabelStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400),
labelStyle: const TextStyle(
fontSize: 16, fontWeight: FontWeight.w600),
unselectedLabelStyle: const TextStyle(
fontSize: 16, fontWeight: FontWeight.w400),
indicatorSize: TabBarIndicatorSize.label,
dividerHeight: 0, // 去掉 TabBar 默认底部分割线(外层已有 0.5.line)
indicator: CustomIndicator(
color: AppColors.actionRed,
width: 16,
height: 4,
borderRadius: const BorderRadius.vertical(top: Radius.circular(3)),
borderRadius:
const BorderRadius.vertical(top: Radius.circular(3)),
),
tabs: [
const Tab(text: "简介"),
@@ -104,12 +108,14 @@ class _VideoTabbarMenuWidgetState extends State<VideoTabbarMenuWidget> {
InkWell(
enableFeedback: false,
onTap: () async {
if (await VideoLineMenuAlert.show()) widget.onSwitchLine?.call();
if (await VideoLineMenuAlert.show())
widget.onSwitchLine?.call();
setState(() {});
},
child: Row(
children: [
Image.asset("line_switch.webp".videoPath, width: 16),
Image.asset("line_switch.webp".videoPath,
width: 16),
2.sizeBoxW,
Text(
selectedCnd?.desc ?? '',
+13 -6
View File
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:hgmj/hj_model/video_model.dart';
import 'package:hgmj/hj_utils/screen.dart';
import 'package:wydj/hj_model/video_model.dart';
import 'package:wydj/hj_utils/screen.dart';
import 'package:provider/provider.dart';
import 'package:video_player/video_player.dart';
@@ -42,7 +42,8 @@ class _VipPromoBanner extends State<VipPromoBanner> {
if (limitTime.canShow) return LimitTimeBanner();
// 2. 支付分层横幅(playPage 图 + vipCard 跳转),都没有则不展示
final layeredConfig = MineMsgProvider().payTier?.config;
if ((layeredConfig?.playPage ?? '').isNotEmpty) return _buildLayeredBanner(layeredConfig!);
if ((layeredConfig?.playPage ?? '').isNotEmpty)
return _buildLayeredBanner(layeredConfig!);
return const SizedBox();
},
);
@@ -75,11 +76,16 @@ class _VipPromoBanner extends State<VipPromoBanner> {
fit: StackFit.expand,
children: [
// 分层横幅图(后端加密图)
NetworkImageLoader(imageUrl: config.playPage ?? "", fit: BoxFit.fill, borderRadius: 0),
NetworkImageLoader(
imageUrl: config.playPage ?? "",
fit: BoxFit.fill,
borderRadius: 0),
// 分层倒计时:监听 tick 每秒刷新,过期自动收起
ValueListenableBuilder<int>(
valueListenable: MineMsgProvider().tick,
builder: (_, __, ___) => config.hasDiscountCountdown ? _buildLayeredCountdown(config) : const SizedBox.shrink(),
builder: (_, __, ___) => config.hasDiscountCountdown
? _buildLayeredCountdown(config)
: const SizedBox.shrink(),
),
],
),
@@ -112,7 +118,8 @@ class _VipPromoBanner extends State<VipPromoBanner> {
borderRadius: BorderRadius.circular(3),
border: Border.all(color: const Color(0xffFFE381), width: 0.5),
),
child: Text(value, style: const TextStyle(color: Colors.white, fontSize: 12)),
child: Text(value,
style: const TextStyle(color: Colors.white, fontSize: 12)),
);
}