角标和占位图替换

This commit is contained in:
谢宇宁
2026-09-21 23:21:09 +07:00
parent 4d7e30da95
commit f6a91760f3
10 changed files with 5 additions and 86 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 22 KiB

-1
View File
@@ -238,7 +238,6 @@ class _DramaListPageState extends State<DramaListPage> with UniqueTagMixin {
videoModel: model, videoModel: model,
textLines: 1, textLines: 1,
coverRightText: model.dramaInfo?.episodeNumberStatus, coverRightText: model.dramaInfo?.episodeNumberStatus,
showLevelIcon: false,
onTap: () => _openDrama(model), onTap: () => _openDrama(model),
onTagTap: (tag) => DramaListPage.toTag(tag), onTagTap: (tag) => DramaListPage.toTag(tag),
); );
@@ -1,44 +0,0 @@
import 'package:flutter/material.dart';
import '../../../config/config.dart';
import '../../../hj_model/video_model.dart';
import '../../../hj_utils/free_play_manager.dart';
/// 封面右上角的售卖角标:免费试看 / 金币 / VIP,三者互斥,都不满足就不占位。
/// 展不展示还受 ping 下发的 freeMark / coinMark / vipMark 三个开关控制
class LevelMarkChip extends StatelessWidget {
final VideoModel? videoModel;
const LevelMarkChip(this.videoModel, {super.key});
@override
Widget build(BuildContext context) {
if (videoModel?.freeArea == true) return const SizedBox();
// 免费试看:ping 免费类角标总开关 freeMark + 实时试看权益(次数用尽即不再展示)
if (Config.freeMark && FreePlayManager().canShowFreeTrialBadge(videoModel)) {
return _chip("免费试看", const Color(0xff141414), const [Color(0xff35DEBC), Color(0xff22BB9C)]);
}
final isCoin = videoModel?.isCoinVideo() == true;
if (isCoin && Config.coinMark) {
return _chip("金币", const Color(0xff8B3E00), const [Color(0xffFFE580), Color(0xffFACC15)]);
}
if (!isCoin && Config.vipMark) {
return _chip("VIP", const Color(0xff141414), const [Color(0xffF8F3AE), Color(0xffDDAC44)]);
}
return const SizedBox();
}
Widget _chip(String text, Color textColor, List<Color> gradientColors) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2),
gradient: LinearGradient(colors: gradientColors),
),
child: Text(
text,
style: TextStyle(color: textColor, fontSize: 10, height: 1.6),
),
);
}
}
@@ -3,10 +3,8 @@ import 'package:tgdj/hj_utils/api_service/common_service.dart';
import 'package:tgdj/hj_utils/screen.dart'; import 'package:tgdj/hj_utils/screen.dart';
import '../../../hj_model/video_model.dart'; import '../../../hj_model/video_model.dart';
import '../../../hj_utils/free_play_manager.dart';
import '../../../routers/jump_router.dart'; import '../../../routers/jump_router.dart';
import '../../../tools_base/widget/net_image_widget.dart'; import '../../../tools_base/widget/net_image_widget.dart';
import 'level_mark_chip.dart';
import 'package:tgdj/extension/extensions.dart'; import 'package:tgdj/extension/extensions.dart';
/// 横版视频 item:左封面(带售卖角标) + 右标题/收藏数,用在专题的小列表样式 /// 横版视频 item:左封面(带售卖角标) + 右标题/收藏数,用在专题的小列表样式
@@ -30,15 +28,6 @@ class VideoHorCell extends StatelessWidget {
borderRadius: 4, borderRadius: 4,
imageUrl: videoModel?.cover ?? "", imageUrl: videoModel?.cover ?? "",
), ),
Positioned(
right: 6,
top: 6,
// 监听免费次数变化:会话内试看用尽时角标实时刷新,不再残留「免费试看」
child: ValueListenableBuilder<int>(
valueListenable: FreePlayManager().revision,
builder: (_, __, ___) => LevelMarkChip(videoModel),
),
),
], ],
), ),
12.sizeBoxW, 12.sizeBoxW,
@@ -11,10 +11,8 @@ import 'package:tgdj/tools_base/global_store/store.dart';
import '../../../hj_model/splash/ads_model.dart'; import '../../../hj_model/splash/ads_model.dart';
import '../../../hj_model/video_model.dart'; import '../../../hj_model/video_model.dart';
import '../../../hj_utils/free_play_manager.dart';
import '../../../tools_base/banner/ads_banner_widget.dart'; import '../../../tools_base/banner/ads_banner_widget.dart';
import '../../../tools_base/widget/net_image_widget.dart'; import '../../../tools_base/widget/net_image_widget.dart';
import 'level_mark_chip.dart';
/// 竖版视频 item:封面(角标/售卖标/暗网遮罩) + 标题 +(可选)标签/评论数。 /// 竖版视频 item:封面(角标/售卖标/暗网遮罩) + 标题 +(可选)标签/评论数。
/// 命中广告数据时整体换成 [VideoSimpleAdsCell] /// 命中广告数据时整体换成 [VideoSimpleAdsCell]
@@ -29,7 +27,6 @@ class VideoSimpleCell extends StatelessWidget {
final double titleFontSize; // 标题字号,小卡场景传小值 final double titleFontSize; // 标题字号,小卡场景传小值
final double coverFontSize; // 封面底部播放量/时长字号,小卡场景传小值避免溢出 final double coverFontSize; // 封面底部播放量/时长字号,小卡场景传小值避免溢出
final String? coverRightText; // 封面右下角文案,默认时长;短剧传「共N集」 final String? coverRightText; // 封面右下角文案,默认时长;短剧传「共N集」
final bool showLevelIcon; // 封面右上角「金币/VIP/免费试看」角标;短剧按集卖,整部剧标一个对不上,传 false
final ValueChanged<TagsBean>? onTagTap; // 左下角标签点击;短剧跳标签详情 final ValueChanged<TagsBean>? onTagTap; // 左下角标签点击;短剧跳标签详情
const VideoSimpleCell({ const VideoSimpleCell({
@@ -44,7 +41,6 @@ class VideoSimpleCell extends StatelessWidget {
this.titleFontSize = 12, this.titleFontSize = 12,
this.coverFontSize = 10, this.coverFontSize = 10,
this.coverRightText, this.coverRightText,
this.showLevelIcon = true,
this.onTagTap, this.onTagTap,
}); });
@@ -134,16 +130,6 @@ class VideoSimpleCell extends StatelessWidget {
child: isFromHY ? _buildCoverHYBottom() : _buildCoverNormalBottom(), child: isFromHY ? _buildCoverHYBottom() : _buildCoverNormalBottom(),
), ),
), ),
if (showLevelIcon)
Positioned(
right: 6,
top: 6,
// 监听免费次数变化:会话内试看用尽时角标实时刷新,不再残留「免费试看」
child: ValueListenableBuilder<int>(
valueListenable: FreePlayManager().revision,
builder: (_, __, ___) => LevelMarkChip(videoModel),
),
),
if (_needVipUnlock) if (_needVipUnlock)
Container( Container(
color: Colors.black.withValues(alpha: 0.1), color: Colors.black.withValues(alpha: 0.1),
@@ -211,7 +211,6 @@ class _SearchVideoResultPageState extends State<SearchVideoResultPage> {
videoModel: model, videoModel: model,
textLines: 1, textLines: 1,
coverRightText: model.dramaInfo?.episodeNumberStatus, coverRightText: model.dramaInfo?.episodeNumberStatus,
showLevelIcon: false,
isFromSearch: true, isFromSearch: true,
onTap: () => _openDrama(model), onTap: () => _openDrama(model),
), ),
@@ -299,7 +298,6 @@ class _SearchVideoResultPageState extends State<SearchVideoResultPage> {
videoModel: videoModel, videoModel: videoModel,
textLines: 1, textLines: 1,
coverRightText: videoModel.dramaInfo?.episodeNumberStatus, coverRightText: videoModel.dramaInfo?.episodeNumberStatus,
showLevelIcon: false,
isFromSearch: true, isFromSearch: true,
onTap: () => _openDrama(videoModel), onTap: () => _openDrama(videoModel),
), ),
@@ -64,7 +64,11 @@ class _CommonSearchBarViewState extends State<CommonSearchBarView> {
borderRadius: BorderRadius.circular(99), borderRadius: BorderRadius.circular(99),
), ),
child: Config.searchHints.isEmpty child: Config.searchHints.isEmpty
? Text('请输入关键字', style: _hintStyle) //父级给的是 36 的紧约束,Text 会撑满高度后从顶部起排,要 Align 压回垂直居中
? Align(
alignment: Alignment.centerLeft,
child: Text('请输入关键字', style: _hintStyle),
)
: MarqueeWidget( : MarqueeWidget(
count: Config.searchHints.length, count: Config.searchHints.length,
onIndexChanged: (index) => onIndexChanged: (index) =>
@@ -82,7 +82,6 @@ abstract class ColHisBuySubLogic extends ListBaseLogic {
videoModel: model, videoModel: model,
textLines: 1, textLines: 1,
coverRightText: (model as VideoModel).dramaInfo?.episodeNumberStatus, coverRightText: (model as VideoModel).dramaInfo?.episodeNumberStatus,
showLevelIcon: false,
onTap: () => _openDrama(model), onTap: () => _openDrama(model),
), ),
}; };
@@ -149,7 +149,6 @@ class _VideoCacheCellState extends State<VideoCacheCell> {
//短剧和「热门短剧」橱窗一个样式:标题是剧名,封面右下角标本条是第几集; //短剧和「热门短剧」橱窗一个样式:标题是剧名,封面右下角标本条是第几集;
//按集卖,整部剧标一个金币/VIP 角标对不上,跟橱窗一样关掉 //按集卖,整部剧标一个金币/VIP 角标对不上,跟橱窗一样关掉
coverRightText: isDrama ? '第${model.episodeNo ?? 1}集' : null, coverRightText: isDrama ? '第${model.episodeNo ?? 1}集' : null,
showLevelIcon: !isDrama,
); );
} }
-11
View File
@@ -57,17 +57,6 @@ class FreePlayManager {
_bumpRevision(); _bumpRevision();
} }
/// 该视频当前是否仍应展示「免费试看」角标(无扣次副作用,供列表实时判断):
/// - 后端未下发该角标:不展示;
/// - 次数未知(remain 未拉到):信任后端下发的角标,避免误隐藏;
/// - 次数已知:仅在还有剩余(>0)时展示,用尽即隐藏(含已试看过的视频,一并回落到真实角标)。
bool canShowFreeTrialBadge(VideoModel? video) {
if (video?.showFreeTrialBadge != true) return false;
final left = remain?.watchCount;
if (left == null) return true;
return left > 0;
}
/// 尝试消费一次免费观看权益,返回本视频能否免费播放。 /// 尝试消费一次免费观看权益,返回本视频能否免费播放。
/// ⚠️ 有副作用:首次命中会扣次数 + 写本地缓存 + 上报服务端; /// ⚠️ 有副作用:首次命中会扣次数 + 写本地缓存 + 上报服务端;
/// 同一视频再次调用命中 [usedIds] 直接返回 true,不重复扣。 /// 同一视频再次调用命中 [usedIds] 直接返回 true,不重复扣。