logo和名称替换
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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)),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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 ?? '',
|
||||
|
||||
@@ -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)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user