logo和名称替换
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
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/hj_utils/screen.dart';
|
||||
import 'package:hgmj/tools_base/debug_log.dart';
|
||||
import 'package:hgmj/tools_base/refresh/pull_refresh.dart';
|
||||
import 'package:wydj/assets_tool/app_colors.dart';
|
||||
import 'package:wydj/hj_utils/screen.dart';
|
||||
import 'package:wydj/tools_base/debug_log.dart';
|
||||
import 'package:wydj/tools_base/refresh/pull_refresh.dart';
|
||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||
import 'package:hgmj/tools_base/widget/sheet_handle_bar.dart';
|
||||
import 'package:wydj/tools_base/widget/sheet_handle_bar.dart';
|
||||
|
||||
import '../../../hj_model/acg/comic_chapters_model.dart';
|
||||
import '../../../hj_model/cartoon_media_info.dart';
|
||||
@@ -19,7 +19,8 @@ class CartoonEpisodeMenuAlert extends StatefulWidget {
|
||||
final CartoonMediaInfo? cartoonModel;
|
||||
final Function(ComicChapterInfo)? callback;
|
||||
|
||||
CartoonEpisodeMenuAlert(this.curModel, {super.key, this.callback, this.cartoonModel});
|
||||
CartoonEpisodeMenuAlert(this.curModel,
|
||||
{super.key, this.callback, this.cartoonModel});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
@@ -58,7 +59,9 @@ class _CartoonEpisodeMenuAlertState extends State<CartoonEpisodeMenuAlert> {
|
||||
//异步返回时 sheet 可能已关闭,避免对已卸载组件 setState / 操作已释放的 refreshCtr
|
||||
if (!mounted) return;
|
||||
setState(() {});
|
||||
cartoonModel?.haxNextEpisode == true ? refreshCtr?.loadComplete() : refreshCtr?.loadNoData();
|
||||
cartoonModel?.haxNextEpisode == true
|
||||
? refreshCtr?.loadComplete()
|
||||
: refreshCtr?.loadNoData();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -68,8 +71,9 @@ class _CartoonEpisodeMenuAlertState extends State<CartoonEpisodeMenuAlert> {
|
||||
child: Container(
|
||||
padding: EdgeInsets.fromLTRB(18.w, 21, 18.w, 32),
|
||||
height: 460,
|
||||
decoration:
|
||||
BoxDecoration(borderRadius: BorderRadius.vertical(top: Radius.circular(18)), color: Color(0xff0F0F0F)),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(18)),
|
||||
color: Color(0xff0F0F0F)),
|
||||
child: Column(
|
||||
children: [
|
||||
const SheetHandleBar(color: Color(0x4DFFFFFF)),
|
||||
@@ -146,7 +150,9 @@ class CartoonChapterItem extends StatelessWidget {
|
||||
});
|
||||
|
||||
// 前 N 集免费(当前集号 <= freeEpisode)且整本无任何权限时,展示「免费」角标
|
||||
bool get _isFree => fatherM.hasPermission == false && fatherM.isFreeEpisodeNumber(model.episodeNumber);
|
||||
bool get _isFree =>
|
||||
fatherM.hasPermission == false &&
|
||||
fatherM.isFreeEpisodeNumber(model.episodeNumber);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -155,13 +161,17 @@ class CartoonChapterItem extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? Color(0x1A6975FF) : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: isSelected ? AppColors.actionRed : Color(0x1AFFFFFF), width: 1),
|
||||
border: Border.all(
|
||||
color: isSelected ? AppColors.actionRed : Color(0x1AFFFFFF),
|
||||
width: 1),
|
||||
),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
//前 N 集免费且整本无权限 → 左上角「免费」角标
|
||||
if (_isFree && isCanShowFree) const Positioned(top: 0, right: 0, child: FreeBadge(isCorner: true)),
|
||||
if (_isFree && isCanShowFree)
|
||||
const Positioned(
|
||||
top: 0, right: 0, child: FreeBadge(isCorner: true)),
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 12),
|
||||
child: Row(
|
||||
|
||||
@@ -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/video_model.dart';
|
||||
import 'package:hgmj/tools_base/widget/shrink_wrap.dart';
|
||||
import 'package:hgmj/hj_page/home/tag/cartoon_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/routers/jump_router.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/video_model.dart';
|
||||
import 'package:wydj/tools_base/widget/shrink_wrap.dart';
|
||||
import 'package:wydj/hj_page/home/tag/cartoon_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/routers/jump_router.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 '../../../hj_model/acg/comic_chapters_model.dart';
|
||||
@@ -75,7 +75,8 @@ class _VideoDetailCartoonViewState extends State<VideoDetailCartoonView>
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 18),
|
||||
margin: EdgeInsets.symmetric(horizontal: 12),
|
||||
child: VideoDetailBottomMenu(model: videoModel, onShare: _onShare),
|
||||
child:
|
||||
VideoDetailBottomMenu(model: videoModel, onShare: _onShare),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
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:hgmj/tools_base/toast.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/toast.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
import 'package:hgmj/hj_utils/video_view_type.dart';
|
||||
import 'package:wydj/hj_utils/video_view_type.dart';
|
||||
|
||||
import '../../tools_base/debug_log.dart';
|
||||
|
||||
@@ -40,7 +40,8 @@ class SimpleVideoPlayerLogic extends GetxController {
|
||||
videoCtr = PlayerFactory.network(videoUrl);
|
||||
}
|
||||
await videoCtr?.initialize();
|
||||
if (isRetry) confirmPlatformView(); // 仅重试成功(同视频 textureView 挂、platformView 放出)才落本地
|
||||
if (isRetry)
|
||||
confirmPlatformView(); // 仅重试成功(同视频 textureView 挂、platformView 放出)才落本地
|
||||
videoCtr?.play();
|
||||
update();
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/hj_page/video/view/video_menu_view.dart';
|
||||
import 'package:hgmj/tools_base/loading/loading_center_widget.dart';
|
||||
import 'package:wydj/hj_page/video/view/video_menu_view.dart';
|
||||
import 'package:wydj/tools_base/loading/loading_center_widget.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
import 'simple_video_player_logic.dart';
|
||||
@@ -12,7 +12,8 @@ class SimpleVideoPlayerPage extends StatelessWidget {
|
||||
final String title;
|
||||
final String? localPath;
|
||||
|
||||
const SimpleVideoPlayerPage({super.key, required this.videoUrl, required this.title, this.localPath});
|
||||
const SimpleVideoPlayerPage(
|
||||
{super.key, required this.videoUrl, required this.title, this.localPath});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/hj_page/video/video_full_logic.dart';
|
||||
import 'package:hgmj/hj_page/video/view/video_menu_view.dart';
|
||||
import 'package:wydj/hj_page/video/video_full_logic.dart';
|
||||
import 'package:wydj/hj_page/video/view/video_menu_view.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
import '../../hj_model/video_model.dart';
|
||||
|
||||
@@ -3,13 +3,13 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/hj_page/video/video_full_page.dart';
|
||||
import 'package:hgmj/hj_page/video/view/long_video_status.dart';
|
||||
import 'package:hgmj/hj_utils/codec_support.dart';
|
||||
import 'package:hgmj/hj_utils/play_diagnose.dart';
|
||||
import 'package:hgmj/hj_utils/const.dart';
|
||||
import 'package:hgmj/hj_utils/video_view_type.dart';
|
||||
import 'package:hgmj/tools_base/toast.dart';
|
||||
import 'package:wydj/hj_page/video/video_full_page.dart';
|
||||
import 'package:wydj/hj_page/video/view/long_video_status.dart';
|
||||
import 'package:wydj/hj_utils/codec_support.dart';
|
||||
import 'package:wydj/hj_utils/play_diagnose.dart';
|
||||
import 'package:wydj/hj_utils/const.dart';
|
||||
import 'package:wydj/hj_utils/video_view_type.dart';
|
||||
import 'package:wydj/tools_base/toast.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
import '../../alert/video/buy_vip_alert.dart';
|
||||
@@ -73,7 +73,8 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
bool isFullScreen = false; // true:视频进入全屏页面
|
||||
RxBool isShowBuy = false.obs; // 显示购买和vip弹窗
|
||||
|
||||
late final TabController tabCtr = TabController(length: 2, vsync: this); // 简介/评论
|
||||
late final TabController tabCtr =
|
||||
TabController(length: 2, vsync: this); // 简介/评论
|
||||
CartoonMediaInfo? cartoonModel; //动漫
|
||||
ComicChapterInfo? currentEpisode; //动漫集数
|
||||
|
||||
@@ -101,7 +102,8 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
double get normalRatio => 16 / 9;
|
||||
|
||||
/// 实际播放比例:动漫和未初始化都用 16:9 兜底,否则用控制器报告的实际比例
|
||||
double get videoRatio => (!isCartoon && isInited) ? playerCtr!.value.aspectRatio : normalRatio;
|
||||
double get videoRatio =>
|
||||
(!isCartoon && isInited) ? playerCtr!.value.aspectRatio : normalRatio;
|
||||
|
||||
StreamSubscription? _pauseVideoSub;
|
||||
|
||||
@@ -138,13 +140,16 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
GuideManager().canShow(GuideFreeTrialSheet.scene) &&
|
||||
MineMsgProvider().payTier?.status == PayTier.newUnpay &&
|
||||
FreePlayManager().useFreePlay(videoModel) == true &&
|
||||
GuideFreeTrialSheet.isCountLow, //必须排在 useFreePlay 之后:它会先扣一次,扣完才是该显示的剩余数
|
||||
GuideFreeTrialSheet
|
||||
.isCountLow, //必须排在 useFreePlay 之后:它会先扣一次,扣完才是该显示的剩余数
|
||||
onShow: () async {
|
||||
// 先备好卡数据再暂停,否则拉数据期间视频白停几百毫秒;场景没配卡就压根不弹,别白暂停一下
|
||||
if (await loadGuideCard(GuideFreeTrialSheet.scene) == null || isDispose) return;
|
||||
if (await loadGuideCard(GuideFreeTrialSheet.scene) == null || isDispose)
|
||||
return;
|
||||
// 只暂停本来在播的:用户自己停在那看简介/评论时,弹完别擅自续播
|
||||
final wasPlaying = playerCtr?.value.isPlaying == true;
|
||||
if (wasPlaying) playerCtr?.pause(); // 图标由 VideoMenuView 的 Rx 自动跟,不用 update()
|
||||
if (wasPlaying)
|
||||
playerCtr?.pause(); // 图标由 VideoMenuView 的 Rx 自动跟,不用 update()
|
||||
final goVip = await GuideFreeTrialSheet.show();
|
||||
// 跳了会员页 / 已退页 / 期间被硬拦截弹了蒙层,都不能恢复播放
|
||||
if (!wasPlaying || goVip || isDispose || isShowBuy.value) return;
|
||||
@@ -159,8 +164,9 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
bool get _needPayGuide => videoStatus.isNeedPay;
|
||||
|
||||
/// 能连续看的秒数:有试看片就是试看片长,否则是正片免费时长
|
||||
int get _freeSeconds =>
|
||||
videoModel?.previewURL?.isNotEmpty == true ? (playerCtr?.value.duration.inSeconds ?? 0) : (videoModel?.freeTime ?? 0);
|
||||
int get _freeSeconds => videoModel?.previewURL?.isNotEmpty == true
|
||||
? (playerCtr?.value.duration.inSeconds ?? 0)
|
||||
: (videoModel?.freeTime ?? 0);
|
||||
|
||||
/// 各类「整片免看」权益:免费次数 / 免费区 / 动漫整本已购 / 动漫免费章节 / VIP 金币限免。
|
||||
/// 命中任一说明用户本来就能看完整片,场景1、场景2 都不该打扰他。
|
||||
@@ -169,8 +175,11 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
bool get _hasFreeWatchRight {
|
||||
if (FreePlayManager().useFreePlay(videoModel)) return true;
|
||||
if (videoModel?.freeArea == true) return true;
|
||||
if (cartoonModel?.mediaStatus?.hasPaid == true) return true; // 动漫整本解锁, 都可以看, 不管vip权限
|
||||
if (isCartoon && cartoonModel?.isFreeEpisodeNumber(currentEpisode?.episodeNumber) == true) return true;
|
||||
if (cartoonModel?.mediaStatus?.hasPaid == true)
|
||||
return true; // 动漫整本解锁, 都可以看, 不管vip权限
|
||||
if (isCartoon &&
|
||||
cartoonModel?.isFreeEpisodeNumber(currentEpisode?.episodeNumber) ==
|
||||
true) return true;
|
||||
return isVipCoinFree;
|
||||
}
|
||||
|
||||
@@ -199,7 +208,8 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
if (playerCtr?.value.isPlaying != true) return;
|
||||
_payGuideShown = true;
|
||||
// 先备好卡数据再暂停,否则拉数据期间视频白停几百毫秒;场景没配卡就压根不弹,别白暂停一下
|
||||
if (await loadGuideCard(GuideScene.videoPreviewEnd) == null || isDispose) return;
|
||||
if (await loadGuideCard(GuideScene.videoPreviewEnd) == null ||
|
||||
isDispose) return;
|
||||
playerCtr?.pause(); // 图标由 VideoMenuView 的 Rx 自动跟,不用 update()
|
||||
final goVip = await _showPayGuide();
|
||||
// 跳了会员页 / 已退页 / 期间被硬拦截弹了蒙层,都不能恢复播放
|
||||
@@ -218,7 +228,10 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
|
||||
/// 是否允许直接退页。false=先弹返回挽留(VIDEO_BACK):长视频 + 场景开关允许 + 本页未弹过。
|
||||
/// 供 PopScope.canPop 与返回箭头共用,类型跟当前在播内容走
|
||||
bool get canPop => _backGuideShown || isCartoon || !GuideManager().canShow(GuideScene.videoBack);
|
||||
bool get canPop =>
|
||||
_backGuideShown ||
|
||||
isCartoon ||
|
||||
!GuideManager().canShow(GuideScene.videoBack);
|
||||
|
||||
/// VIDEO_BACK:长视频返回挽留。弹优惠倒计时,标记已弹并 update() 让 canPop 放行后续返回。
|
||||
/// 用户是按了返回才弹的,所以挽留失败(点X/点蒙层/被互斥挡下)就把这次返回执行完;
|
||||
@@ -232,7 +245,8 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
try {
|
||||
await TimedPopupManager().trigger(
|
||||
canShow: () => GuideManager().canShow(GuideScene.videoBack),
|
||||
onShow: () async => goVip = await GuideCountdownDialog.show(scene: GuideScene.videoBack),
|
||||
onShow: () async => goVip =
|
||||
await GuideCountdownDialog.show(scene: GuideScene.videoBack),
|
||||
);
|
||||
} catch (e) {
|
||||
debugLog('showBackGuide', e.toString());
|
||||
@@ -276,7 +290,8 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
cartoonModel?.haxNextEpisode = episodeModel?.hasNext ?? true;
|
||||
videoModel?.mediaInfo = cartoonModel;
|
||||
currentEpisode = episodeModel?.list?.first;
|
||||
MediaContent? mediaContent = await ACGService.getMediaDetail(id: currentEpisode?.id ?? "");
|
||||
MediaContent? mediaContent =
|
||||
await ACGService.getMediaDetail(id: currentEpisode?.id ?? "");
|
||||
episodeModel?.list?.first.mediaContent = mediaContent;
|
||||
videoModel = cartoonModel?.toVideoModel(currentEpisode) ?? VideoModel();
|
||||
} else {
|
||||
@@ -350,7 +365,8 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
return;
|
||||
}
|
||||
isFullScreen = true;
|
||||
await Get.to(() => VideoFullPage(playCtr: playerCtr!, videoModel: videoModel));
|
||||
await Get.to(
|
||||
() => VideoFullPage(playCtr: playerCtr!, videoModel: videoModel));
|
||||
isFullScreen = false;
|
||||
}
|
||||
|
||||
@@ -358,7 +374,8 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
void exitFullScreen() {
|
||||
if (!isFullScreen) return;
|
||||
isFullScreen = false;
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top]);
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
||||
overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top]);
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||
Get.back();
|
||||
}
|
||||
@@ -395,7 +412,8 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
playerCtr = PlayerFactory.network(videoUrl); // 仅网络播放,不走本地文件
|
||||
try {
|
||||
await playerCtr?.initialize();
|
||||
if (isRetry) confirmPlatformView(); // 仅重试成功(同视频 textureView 挂、platformView 放出)才落本地
|
||||
if (isRetry)
|
||||
confirmPlatformView(); // 仅重试成功(同视频 textureView 挂、platformView 放出)才落本地
|
||||
update();
|
||||
} catch (e) {
|
||||
debugLog("视频初始化失败:$e play url = $videoUrl");
|
||||
@@ -443,7 +461,8 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
playerCtr?.addListener(_onProgress);
|
||||
int previewStart = videoModel?.previewStart ?? 0;
|
||||
if (previewStart > 0) {
|
||||
final canFree = FreePlayManager().useFreePlay(videoModel) || videoModel?.freeArea == true;
|
||||
final canFree = FreePlayManager().useFreePlay(videoModel) ||
|
||||
videoModel?.freeArea == true;
|
||||
// 免金币视频 / 有免费观看次数(vip视频):从头播,不需要 seekTo
|
||||
// 需要付费/会员才能看的视频,跳到预览起始位置开始播放
|
||||
if (!canFree && videoStatus.isNeedPay) {
|
||||
@@ -469,7 +488,8 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
if (!_isPreview()) {
|
||||
//每帧都走这里,videoStatus 是重算 getter,只读一次
|
||||
final status = videoStatus;
|
||||
final inFreeTime = videoModel?.isInFreeTime(playerCtr!.value.position.inSeconds) == true;
|
||||
final inFreeTime =
|
||||
videoModel?.isInFreeTime(playerCtr!.value.position.inSeconds) == true;
|
||||
final isPlaying = playerCtr?.value.isPlaying == true;
|
||||
if (inFreeTime || !isPlaying) return;
|
||||
// 全屏中:仍处于免费时长则不打扰;否则需付费的视频在播放时先退出全屏,给后续弹窗腾出空间
|
||||
@@ -484,7 +504,8 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
if (!isCartoon && status.isNeedVip) {
|
||||
// 刚退全屏要等 pop 动画和转屏落定再弹:exitFullScreen 的 Get.back() 带动画,
|
||||
// 此刻 push 会叠在正在 pop 的全屏页上,且弹窗高度取的是实时 Get.height,横屏下算出来是错的
|
||||
if (wasFullScreen) await Future.delayed(const Duration(milliseconds: 350));
|
||||
if (wasFullScreen)
|
||||
await Future.delayed(const Duration(milliseconds: 350));
|
||||
if (!isDispose) BuyVipAlert.show(videoId: videoModel?.id);
|
||||
}
|
||||
}
|
||||
@@ -527,7 +548,8 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
void _fallbackTo264() {
|
||||
if (isDispose || !isPlayingH265 || _forceH264) return;
|
||||
_forceH264 = true; // 本条视频后续重建都走 264
|
||||
debugLog("H.265 运行时报错,回退 264 续播 pos=${playerCtr?.value.position.inSeconds}");
|
||||
debugLog(
|
||||
"H.265 运行时报错,回退 264 续播 pos=${playerCtr?.value.position.inSeconds}");
|
||||
releasePlayer(); // 释放当前 → 下一帧按进度续播,选流因 _forceH264 走 264
|
||||
}
|
||||
|
||||
@@ -609,8 +631,10 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
isShowBuy.value = false;
|
||||
releasePlayer(rebuild: false);
|
||||
update();
|
||||
currentEpisode?.mediaContent ??= await ACGService.getMediaDetail(id: currentEpisode?.id ?? "");
|
||||
startPlay(model: cartoonModel?.toVideoModel(currentEpisode) ?? VideoModel());
|
||||
currentEpisode?.mediaContent ??=
|
||||
await ACGService.getMediaDetail(id: currentEpisode?.id ?? "");
|
||||
startPlay(
|
||||
model: cartoonModel?.toVideoModel(currentEpisode) ?? VideoModel());
|
||||
} catch (e) {
|
||||
debugLog(e);
|
||||
}
|
||||
@@ -618,7 +642,8 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
|
||||
/// 释放播放器。[rebuild]=false 只释放(切集前清理);[model] 非空则切新视频,为空则按上次进度续播。
|
||||
/// dispose 走下一帧,避免在播放回调中销毁导致 use-after-free
|
||||
void releasePlayer({VideoModel? model, bool rebuild = true, bool resetPos = false}) {
|
||||
void releasePlayer(
|
||||
{VideoModel? model, bool rebuild = true, bool resetPos = false}) {
|
||||
final old = playerCtr;
|
||||
old?.pause();
|
||||
// resetPos:预览片→正片场景,两者时间轴不对应,不能沿用预览片进度,从头播
|
||||
@@ -641,7 +666,8 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
/// 页面销毁清理:取消事件订阅 → 非预览视频上报观看记录 → 释放播放器
|
||||
@override
|
||||
void onClose() {
|
||||
isDispose = true; // 第一时间置位,让所有异步回调(initialize/postFrame/Future.delayed)能尽早 return
|
||||
isDispose =
|
||||
true; // 第一时间置位,让所有异步回调(initialize/postFrame/Future.delayed)能尽早 return
|
||||
TimedPopupManager().cancel(TimedPopupType.freeWatchPay);
|
||||
TimedPopupManager().cancel(TimedPopupType.videoPayAhead);
|
||||
|
||||
@@ -651,13 +677,15 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
if (playerCtr != null) {
|
||||
bool isPreview = false;
|
||||
if (videoModel?.previewURL?.isNotEmpty == true) {
|
||||
if (playerCtr?.dataSource.contains(videoModel?.previewURL ?? "") == true) {
|
||||
if (playerCtr?.dataSource.contains(videoModel?.previewURL ?? "") ==
|
||||
true) {
|
||||
isPreview = true;
|
||||
}
|
||||
}
|
||||
if (!isPreview) {
|
||||
try {
|
||||
_reportPlay(playerCtr?.value.position, playerCtr?.value.duration, videoModel);
|
||||
_reportPlay(
|
||||
playerCtr?.value.position, playerCtr?.value.duration, videoModel);
|
||||
} catch (e) {
|
||||
debugLog(e);
|
||||
}
|
||||
@@ -670,7 +698,8 @@ class VideoLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
}
|
||||
|
||||
/// 上报播放记录(static:不碰实例状态,避免请求在飞时把已销毁的 logic 一起吊住)
|
||||
static Future<void> _reportPlay(Duration? position, Duration? duration, VideoModel? video) async {
|
||||
static Future<void> _reportPlay(
|
||||
Duration? position, Duration? duration, VideoModel? video) async {
|
||||
if (position == null || duration == null) return;
|
||||
if (position.inMilliseconds < 1000) return; // 不足 1 秒不记
|
||||
if (video?.videoType == 1) return; // 动漫不走这个接口
|
||||
|
||||
@@ -6,19 +6,19 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.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_page/video/video_logic.dart';
|
||||
import 'package:hgmj/hj_page/video/view/video_ad_widget.dart';
|
||||
import 'package:hgmj/hj_page/video/view/video_mask_buy_view.dart';
|
||||
import 'package:hgmj/hj_page/video/view/video_menu_view.dart';
|
||||
import 'package:hgmj/hj_page/video/view/video_status_view.dart';
|
||||
import 'package:hgmj/hj_page/video/view/video_tabbar_menu_widget.dart';
|
||||
import 'package:hgmj/hj_page/video/view/vip_promo_banner.dart';
|
||||
import 'package:hgmj/hj_utils/codec_support.dart';
|
||||
import 'package:hgmj/tools_base/toast.dart';
|
||||
import 'package:hgmj/tools_base/unique_tag_mixin.dart';
|
||||
import 'package:hgmj/tools_base/widget/keep_alive_widget.dart';
|
||||
import 'package:wydj/assets_tool/app_colors.dart';
|
||||
import 'package:wydj/assets_tool/images.dart';
|
||||
import 'package:wydj/hj_page/video/video_logic.dart';
|
||||
import 'package:wydj/hj_page/video/view/video_ad_widget.dart';
|
||||
import 'package:wydj/hj_page/video/view/video_mask_buy_view.dart';
|
||||
import 'package:wydj/hj_page/video/view/video_menu_view.dart';
|
||||
import 'package:wydj/hj_page/video/view/video_status_view.dart';
|
||||
import 'package:wydj/hj_page/video/view/video_tabbar_menu_widget.dart';
|
||||
import 'package:wydj/hj_page/video/view/vip_promo_banner.dart';
|
||||
import 'package:wydj/hj_utils/codec_support.dart';
|
||||
import 'package:wydj/tools_base/toast.dart';
|
||||
import 'package:wydj/tools_base/unique_tag_mixin.dart';
|
||||
import 'package:wydj/tools_base/widget/keep_alive_widget.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
import '../../hj_model/video_model.dart';
|
||||
@@ -159,12 +159,16 @@ class _VideoPageState extends State<VideoPage> with UniqueTagMixin {
|
||||
alignment: Alignment.center,
|
||||
child: AspectRatio(
|
||||
aspectRatio: logic.videoRatio,
|
||||
child: logic.playerCtr != null ? VideoPlayer(logic.playerCtr!) : SizedBox(),
|
||||
child: logic.playerCtr != null
|
||||
? VideoPlayer(logic.playerCtr!)
|
||||
: SizedBox(),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (logic.playerCtr?.value.isInitialized != true)
|
||||
NetworkImageLoader(imageUrl: logic.videoModel?.cover ?? widget.videoCover ?? "", borderRadius: 0),
|
||||
NetworkImageLoader(
|
||||
imageUrl: logic.videoModel?.cover ?? widget.videoCover ?? "",
|
||||
borderRadius: 0),
|
||||
if (logic.playerCtr?.value.isInitialized == true) ...[
|
||||
Positioned(
|
||||
top: 12,
|
||||
@@ -240,7 +244,9 @@ class _VideoPageState extends State<VideoPage> with UniqueTagMixin {
|
||||
return ValueListenableBuilder<VideoPlayerValue>(
|
||||
valueListenable: ctr,
|
||||
builder: (_, value, __) => value.isInitialized && value.isBuffering
|
||||
? const Center(child: CupertinoActivityIndicator(color: AppColors.actionRed, radius: 15))
|
||||
? const Center(
|
||||
child: CupertinoActivityIndicator(
|
||||
color: AppColors.actionRed, radius: 15))
|
||||
: const SizedBox(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/assets_tool/images.dart';
|
||||
import 'package:hgmj/hj_page/video/video_full_page.dart';
|
||||
import 'package:hgmj/hj_page/video/view/video_menu_view.dart';
|
||||
import 'package:hgmj/hj_utils/screen.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_center_widget.dart';
|
||||
import 'package:wydj/assets_tool/images.dart';
|
||||
import 'package:wydj/hj_page/video/video_full_page.dart';
|
||||
import 'package:wydj/hj_page/video/view/video_menu_view.dart';
|
||||
import 'package:wydj/hj_utils/screen.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_center_widget.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
import 'package:hgmj/hj_utils/video_view_type.dart';
|
||||
import 'package:wydj/hj_utils/video_view_type.dart';
|
||||
|
||||
import '../../tools_base/widget/net_image_widget.dart';
|
||||
|
||||
@@ -40,7 +40,8 @@ class _VideoPlayerAlertState extends State<VideoPlayerAlert> {
|
||||
videoCtr = PlayerFactory.network(widget.movieUrl);
|
||||
try {
|
||||
await videoCtr?.initialize();
|
||||
if (isRetry) confirmPlatformView(); // 仅重试成功(同视频 textureView 挂、platformView 放出)才落本地
|
||||
if (isRetry)
|
||||
confirmPlatformView(); // 仅重试成功(同视频 textureView 挂、platformView 放出)才落本地
|
||||
if (mounted) {
|
||||
videoCtr?.play();
|
||||
setState(() {});
|
||||
@@ -74,11 +75,13 @@ class _VideoPlayerAlertState extends State<VideoPlayerAlert> {
|
||||
Positioned(
|
||||
left: 0,
|
||||
top: 0,
|
||||
child: InkWell(enableFeedback: false,
|
||||
child: InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: Get.back,
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 16, top: 6),
|
||||
child: Image.asset('back_circle.png'.commonImgPath, width: 24, height: 24),
|
||||
child: Image.asset('back_circle.png'.commonImgPath,
|
||||
width: 24, height: 24),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -94,7 +97,8 @@ class _VideoPlayerAlertState extends State<VideoPlayerAlert> {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
if (widget.imgCover?.isNotEmpty == true) NetworkImageLoader(imageUrl: widget.imgCover ?? ""),
|
||||
if (widget.imgCover?.isNotEmpty == true)
|
||||
NetworkImageLoader(imageUrl: widget.imgCover ?? ""),
|
||||
LoadingCenterWidget(),
|
||||
],
|
||||
);
|
||||
@@ -113,7 +117,8 @@ class _VideoPlayerAlertState extends State<VideoPlayerAlert> {
|
||||
VideoMenuView(
|
||||
playCtr: videoCtr!,
|
||||
isFull: true,
|
||||
onFullScreen: () => Get.to(() => VideoFullPage(playCtr: videoCtr!, isAutoV: false)),
|
||||
onFullScreen: () =>
|
||||
Get.to(() => VideoFullPage(playCtr: videoCtr!, isAutoV: false)),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -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