unify horizontal video models

Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-04-28 10:32:15 +08:00
parent 8dd1028fdd
commit cca5149640
8 changed files with 128 additions and 129 deletions

View File

@@ -25,7 +25,7 @@ class VideoProgressIndicator extends LeafRenderObjectWidget {
this.radius = 10, this.radius = 10,
this.height = 4, this.height = 4,
required this.progress, required this.progress,
}) : assert(progress >= 0 && progress <= 1); });
final Color color; final Color color;
final Color backgroundColor; final Color backgroundColor;
@@ -136,9 +136,9 @@ class RenderProgressBar extends RenderBox {
bottomRight: radius, bottomRight: radius,
); );
if (progress == 0) { if (progress <= 0) {
canvas.drawRRect(rrect, paint..color = _backgroundColor); canvas.drawRRect(rrect, paint..color = _backgroundColor);
} else if (progress == 1) { } else if (progress >= 1) {
canvas.drawRRect(rrect, paint..color = _color); canvas.drawRRect(rrect, paint..color = _color);
} else { } else {
final w = size.width * progress; final w = size.width * progress;

View File

@@ -7,18 +7,13 @@ import 'package:PiliPlus/common/widgets/progress_bar/video_progress_indicator.da
import 'package:PiliPlus/common/widgets/stat/stat.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/common/widgets/video_popup_menu.dart'; import 'package:PiliPlus/common/widgets/video_popup_menu.dart';
import 'package:PiliPlus/http/search.dart'; import 'package:PiliPlus/http/search.dart';
import 'package:PiliPlus/models/common/badge_type.dart'; import 'package:PiliPlus/models/horizontal_video_model.dart';
import 'package:PiliPlus/models/common/stat_type.dart';
import 'package:PiliPlus/models/model_hot_video_item.dart';
import 'package:PiliPlus/models/model_video.dart';
import 'package:PiliPlus/models/search/result.dart';
import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; import 'package:PiliPlus/models_new/video/video_detail/dimension.dart';
import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/date_utils.dart';
import 'package:PiliPlus/utils/duration_utils.dart'; import 'package:PiliPlus/utils/duration_utils.dart';
import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:flutter/material.dart' hide LayoutBuilder;
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
// 视频卡片 - 水平布局 // 视频卡片 - 水平布局
class VideoCardH extends StatelessWidget { class VideoCardH extends StatelessWidget {
@@ -29,37 +24,13 @@ class VideoCardH extends StatelessWidget {
this.onViewLater, this.onViewLater,
this.onRemove, this.onRemove,
}); });
final BaseVideoItemModel videoItem; final HorizontalVideoModel videoItem;
final VoidCallback? onTap; final VoidCallback? onTap;
final ValueChanged<int>? onViewLater; final ValueChanged<int>? onViewLater;
final VoidCallback? onRemove; final VoidCallback? onRemove;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String type = 'video';
String? badge;
if (videoItem case final SearchVideoItemModel item) {
final typeOrNull = item.type;
if (typeOrNull != null && typeOrNull.isNotEmpty) {
type = typeOrNull;
if (type == 'ketang') {
badge = '课堂';
} else if (type == 'live_room') {
badge = '直播';
}
}
if (item.isUnionVideo == 1) {
badge = '合作';
}
} else if (videoItem case final HotVideoItemModel item) {
if (item.isCharging == true) {
badge = '充电专属';
} else if (item.isCooperation == 1) {
badge = '合作';
} else {
badge = item.pgcLabel;
}
}
void onLongPress() => imageSaveDialog( void onLongPress() => imageSaveDialog(
bvid: videoItem.bvid, bvid: videoItem.bvid,
title: videoItem.title, title: videoItem.title,
@@ -67,9 +38,9 @@ class VideoCardH extends StatelessWidget {
); );
final theme = Theme.of(context); final theme = Theme.of(context);
return Material( return Material(
type: MaterialType.transparency, type: .transparency,
child: Stack( child: Stack(
clipBehavior: Clip.none, clipBehavior: .none,
children: [ children: [
InkWell( InkWell(
onLongPress: onLongPress, onLongPress: onLongPress,
@@ -77,33 +48,25 @@ class VideoCardH extends StatelessWidget {
onTap: onTap:
onTap ?? onTap ??
() async { () async {
if (type == 'ketang') { if (videoItem.isPugv ?? false) {
PageUtils.viewPugv(seasonId: videoItem.aid); PageUtils.viewPugv(seasonId: videoItem.seasonId);
return; return;
} else if (type == 'live_room') { }
if (videoItem case final SearchVideoItemModel item) {
int? roomId = item.id; if (videoItem.isLive ?? false) {
if (roomId != null) { if (videoItem.roomId case final roomId?) {
PageUtils.toLiveRoom(roomId); PageUtils.toLiveRoom(roomId);
}
} else {
SmartDialog.showToast(
'err: live_room : ${videoItem.runtimeType}',
);
} }
return; return;
} }
Dimension? dimension; if (videoItem.redirectUrl?.isNotEmpty == true &&
if (videoItem case final HotVideoItemModel item) { PageUtils.viewPgcFromUri(videoItem.redirectUrl!)) {
if (item.redirectUrl?.isNotEmpty == true && return;
PageUtils.viewPgcFromUri(item.redirectUrl!)) {
return;
}
dimension = item.dimension;
} }
int? cid = videoItem.cid; int? cid = videoItem.cid;
Dimension? dimension = videoItem.dimension;
if (cid == null) { if (cid == null) {
if (await SearchHttp.ab2cWithDimension( if (await SearchHttp.ab2cWithDimension(
aid: videoItem.aid, aid: videoItem.aid,
@@ -125,40 +88,38 @@ class VideoCardH extends StatelessWidget {
} }
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const .symmetric(
horizontal: Style.safeSpace, horizontal: Style.safeSpace,
vertical: 5, vertical: 5,
), ),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: .start,
children: <Widget>[ children: [
AspectRatio( AspectRatio(
aspectRatio: Style.aspectRatio, aspectRatio: Style.aspectRatio,
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, boxConstraints) { builder: (context, boxConstraints) {
final double maxWidth = boxConstraints.maxWidth; final double maxWidth = boxConstraints.maxWidth;
final double maxHeight = boxConstraints.maxHeight; final double maxHeight = boxConstraints.maxHeight;
num? progress;
if (videoItem case final HotVideoItemModel item) { final progress = videoItem.progress;
progress = item.progress;
}
return Stack( return Stack(
clipBehavior: Clip.none, clipBehavior: .none,
children: [ children: [
NetworkImgLayer( NetworkImgLayer(
src: videoItem.cover, src: videoItem.cover,
width: maxWidth, width: maxWidth,
height: maxHeight, height: maxHeight,
), ),
if (badge != null) if (videoItem.badge case final badge?)
PBadge( PBadge(
text: badge, text: badge,
top: 6.0, top: 6.0,
right: 6.0, right: 6.0,
type: switch (badge) { type: switch (badge) {
'充电专属' => PBadgeType.error, '充电专属' => .error,
_ => PBadgeType.primary, _ => .primary,
}, },
), ),
if (progress != null && progress != 0) ...[ if (progress != null && progress != 0) ...[
@@ -168,7 +129,7 @@ class VideoCardH extends StatelessWidget {
: '${DurationUtils.formatDuration(progress)}/${DurationUtils.formatDuration(videoItem.duration)}', : '${DurationUtils.formatDuration(progress)}/${DurationUtils.formatDuration(videoItem.duration)}',
right: 6, right: 6,
bottom: 8, bottom: 8,
type: PBadgeType.gray, type: .gray,
), ),
Positioned( Positioned(
left: 0, left: 0,
@@ -190,7 +151,7 @@ class VideoCardH extends StatelessWidget {
), ),
right: 6.0, right: 6.0,
bottom: 6.0, bottom: 6.0,
type: PBadgeType.gray, type: .gray,
), ),
], ],
); );
@@ -224,45 +185,44 @@ class VideoCardH extends StatelessWidget {
if (pubdate != '') pubdate += ' '; if (pubdate != '') pubdate += ' ';
return Expanded( return Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: .start,
children: [ children: [
if (videoItem case final SearchVideoItemModel item) ...[ if (videoItem.titleList?.isNotEmpty == true)
if (item.titleList?.isNotEmpty == true) Expanded(
Expanded( child: Text.rich(
child: Text.rich( overflow: .ellipsis,
overflow: TextOverflow.ellipsis, maxLines: 2,
maxLines: 2, TextSpan(
TextSpan( children: videoItem.titleList!
children: item.titleList! .map(
.map( (e) => TextSpan(
(e) => TextSpan( text: e.text,
text: e.text, style: TextStyle(
style: TextStyle( fontSize: theme.textTheme.bodyMedium!.fontSize,
fontSize: theme.textTheme.bodyMedium!.fontSize, height: 1.42,
height: 1.42, letterSpacing: 0.3,
letterSpacing: 0.3, color: e.isEm
color: e.isEm ? theme.colorScheme.primary
? theme.colorScheme.primary : theme.colorScheme.onSurface,
: theme.colorScheme.onSurface,
),
), ),
) ),
.toList(), )
), .toList(),
), ),
), ),
] else )
else
Expanded( Expanded(
child: Text( child: Text(
videoItem.title, videoItem.title,
textAlign: TextAlign.start, textAlign: .start,
style: TextStyle( style: TextStyle(
fontSize: theme.textTheme.bodyMedium!.fontSize, fontSize: theme.textTheme.bodyMedium!.fontSize,
height: 1.42, height: 1.42,
letterSpacing: 0.3, letterSpacing: 0.3,
), ),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: .ellipsis,
), ),
), ),
Text( Text(
@@ -272,7 +232,7 @@ class VideoCardH extends StatelessWidget {
fontSize: 12, fontSize: 12,
height: 1, height: 1,
color: theme.colorScheme.outline, color: theme.colorScheme.outline,
overflow: TextOverflow.clip, overflow: .clip,
), ),
), ),
const SizedBox(height: 3), const SizedBox(height: 3),
@@ -280,11 +240,11 @@ class VideoCardH extends StatelessWidget {
spacing: 8, spacing: 8,
children: [ children: [
StatWidget( StatWidget(
type: StatType.play, type: .play,
value: videoItem.stat.view, value: videoItem.stat.view,
), ),
StatWidget( StatWidget(
type: StatType.danmaku, type: .danmaku,
value: videoItem.stat.danmu, value: videoItem.stat.danmu,
), ),
], ],

View File

@@ -0,0 +1,21 @@
import 'package:PiliPlus/models/model_video.dart';
import 'package:PiliPlus/models_new/video/video_detail/dimension.dart';
abstract class HorizontalVideoModel extends BaseVideoItemModel {
bool? isPugv;
int? seasonId;
int? roomId;
bool? isLive;
Dimension? dimension;
String? badge;
num? progress;
String? redirectUrl;
// search
List<({bool isEm, String text})>? titleList;
}

View File

@@ -1,25 +1,19 @@
import 'package:PiliPlus/models/horizontal_video_model.dart';
import 'package:PiliPlus/models/model_owner.dart'; import 'package:PiliPlus/models/model_owner.dart';
import 'package:PiliPlus/models/model_rec_video_item.dart';
import 'package:PiliPlus/models/model_video.dart'; import 'package:PiliPlus/models/model_video.dart';
import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; import 'package:PiliPlus/models_new/video/video_detail/dimension.dart';
import 'package:PiliPlus/pages/common/multi_select/base.dart'; import 'package:PiliPlus/pages/common/multi_select/base.dart';
// 稍后再看, 排行榜等网页返回也使用该类 // 稍后再看, 排行榜等网页返回也使用该类
class HotVideoItemModel extends BaseRcmdVideoItemModel with MultiSelectData { class HotVideoItemModel extends HorizontalVideoModel with MultiSelectData {
int? videos; int? videos;
int? tid; int? tid;
String? tname; String? tname;
int? copyright; int? copyright;
int? ctime; int? ctime;
int? state; int? state;
Dimension? dimension;
String? firstFrame; String? firstFrame;
String? pubLocation; String? pubLocation;
String? pgcLabel;
String? redirectUrl;
num? progress;
int? isCooperation;
bool? isCharging;
HotVideoItemModel.fromJson(Map<String, dynamic> json) { HotVideoItemModel.fromJson(Map<String, dynamic> json) {
aid = json["aid"]; aid = json["aid"];
@@ -43,23 +37,16 @@ class HotVideoItemModel extends BaseRcmdVideoItemModel with MultiSelectData {
: Dimension.fromJson(json['dimension']); : Dimension.fromJson(json['dimension']);
firstFrame = json["first_frame"]; firstFrame = json["first_frame"];
pubLocation = json["pub_location"]; pubLocation = json["pub_location"];
dynamic rcmd = json['rcmd_reason'];
rcmdReason = rcmd is Map ? rcmd['content'] : rcmd; // 相关视频里rcmd为String,
if (rcmdReason?.isEmpty == true) rcmdReason = null;
pgcLabel = json['pgc_label'];
redirectUrl = json['redirect_url']; redirectUrl = json['redirect_url'];
// uri = json['uri']; // 仅在稍后再看存在
progress = json['progress']; progress = json['progress'];
isCooperation = json['rights']?['is_cooperation']; if (json['charging_pay']?['level'] != null) {
isCharging = json['charging_pay']?['level'] != null; badge = '充电专属';
} else if (json['rights']?['is_cooperation'] == 1) {
badge = '合作';
} else {
badge = json['pgc_label'];
}
} }
// @override
// get isFollowed => false;
// @override
// get goto => 'av';
// @override
// get uri => 'bilibili://video/$aid';
} }
class HotStat extends Stat { class HotStat extends Stat {

View File

@@ -1,3 +1,4 @@
import 'package:PiliPlus/models/horizontal_video_model.dart';
import 'package:PiliPlus/models/model_avatar.dart'; import 'package:PiliPlus/models/model_avatar.dart';
import 'package:PiliPlus/models/model_owner.dart'; import 'package:PiliPlus/models/model_owner.dart';
import 'package:PiliPlus/models/model_video.dart'; import 'package:PiliPlus/models/model_video.dart';
@@ -64,18 +65,16 @@ class SearchVideoData extends SearchNumData<SearchVideoItemModel> {
} }
} }
class SearchVideoItemModel extends BaseVideoItemModel { class SearchVideoItemModel extends HorizontalVideoModel {
String? type;
int? id; int? id;
String? arcurl; String? arcurl;
String? tag; String? tag;
int? ctime; int? ctime;
int? isUnionVideo;
List<({bool isEm, String text})>? titleList; @override
int? get seasonId => aid;
SearchVideoItemModel.fromJson(Map<String, dynamic> json) { SearchVideoItemModel.fromJson(Map<String, dynamic> json) {
type = json['type'];
id = json['id']; id = json['id'];
arcurl = json['arcurl']; arcurl = json['arcurl'];
aid = json['aid']; aid = json['aid'];
@@ -89,7 +88,19 @@ class SearchVideoItemModel extends BaseVideoItemModel {
duration = DurationUtils.parseDuration(json['duration']); duration = DurationUtils.parseDuration(json['duration']);
owner = SearchOwner.fromJson(json); owner = SearchOwner.fromJson(json);
stat = SearchStat.fromJson(json); stat = SearchStat.fromJson(json);
isUnionVideo = json['is_union_video']; switch (json['type']) {
case 'ketang':
badge = '课堂';
isPugv = true;
case 'live_room':
badge = '直播';
isLive = true;
roomId = json['roomid'];
default:
if (json['is_union_video'] == 1) {
badge = '合作';
}
}
} }
} }

View File

@@ -1,7 +1,8 @@
import 'package:PiliPlus/models/horizontal_video_model.dart';
import 'package:PiliPlus/models/model_video.dart'; import 'package:PiliPlus/models/model_video.dart';
import 'package:PiliPlus/utils/duration_utils.dart'; import 'package:PiliPlus/utils/duration_utils.dart';
class VListItemModel extends BaseVideoItemModel { class VListItemModel extends HorizontalVideoModel {
VListItemModel.fromJson(Map<String, dynamic> json) { VListItemModel.fromJson(Map<String, dynamic> json) {
cover = json['pic']; cover = json['pic'];
desc = json['description']; desc = json['description'];
@@ -14,6 +15,24 @@ class VListItemModel extends BaseVideoItemModel {
bvid = json['bvid']; bvid = json['bvid'];
stat = VListStat.fromJson(json); stat = VListStat.fromJson(json);
owner = VListOwner.fromJson(json); owner = VListOwner.fromJson(json);
if (json['is_lesson_video'] == 1) {
isPugv = true;
badge = '课堂';
} else if (json['is_charging_arc'] == true) {
badge = '充电专属';
} else if (json['is_union_video'] == 1) {
badge = '合作';
}
seasonId = json['season_id'];
redirectUrl = json['jump_url'];
final position = json['playback_position'] as num?; // percent
if (position != null) {
if (position == 100) {
progress = -1;
} else {
progress = ((position / 100) * duration).round();
}
}
} }
} }

View File

@@ -1,6 +1,7 @@
import 'package:PiliPlus/models/horizontal_video_model.dart';
import 'package:PiliPlus/models/model_video.dart'; import 'package:PiliPlus/models/model_video.dart';
class SeasonArchive extends BaseVideoItemModel { class SeasonArchive extends HorizontalVideoModel {
SeasonArchive.fromJson(Map<String, dynamic> json) { SeasonArchive.fromJson(Map<String, dynamic> json) {
aid = json['aid']; aid = json['aid'];
bvid = json['bvid']; bvid = json['bvid'];

View File

@@ -7,7 +7,7 @@ import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_header.dart';
import 'package:PiliPlus/common/widgets/video_card/video_card_h.dart'; import 'package:PiliPlus/common/widgets/video_card/video_card_h.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/enum_with_label.dart'; import 'package:PiliPlus/models/common/enum_with_label.dart';
import 'package:PiliPlus/models/model_video.dart'; import 'package:PiliPlus/models/horizontal_video_model.dart';
import 'package:PiliPlus/pages/member_video_web/base/controller.dart'; import 'package:PiliPlus/pages/member_video_web/base/controller.dart';
import 'package:PiliPlus/pages/search/widgets/search_text.dart'; import 'package:PiliPlus/pages/search/widgets/search_text.dart';
import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/grid.dart';
@@ -19,7 +19,7 @@ import 'package:get/get.dart';
abstract class BaseVideoWebState< abstract class BaseVideoWebState<
S extends StatefulWidget, S extends StatefulWidget,
R, R,
T extends BaseVideoItemModel, T extends HorizontalVideoModel,
V extends EnumWithLabel V extends EnumWithLabel
> >
extends State<S> extends State<S>