mod: show all the staffs on intro panel

This commit is contained in:
bggRGjQaUbCoE
2024-09-27 21:31:37 +08:00
parent f3744c23bc
commit 293b829b0f
4 changed files with 219 additions and 54 deletions

View File

@@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
/// https://stackoverflow.com/a/76605401
class SelfSizedHorizontalList extends StatefulWidget {
final Widget Function(int) childBuilder;
final int itemCount;
final double gapSize;
final EdgeInsetsGeometry? padding;
const SelfSizedHorizontalList({
super.key,
required this.childBuilder,
required this.itemCount,
this.gapSize = 5,
this.padding,
});
@override
State<SelfSizedHorizontalList> createState() =>
_SelfSizedHorizontalListState();
}
class _SelfSizedHorizontalListState extends State<SelfSizedHorizontalList> {
final infoKey = GlobalKey();
double? prevHeight;
double? get height {
if (prevHeight != null) return prevHeight;
prevHeight = infoKey.globalPaintBounds?.height;
return prevHeight;
}
bool get isInit => height == null;
@override
Widget build(BuildContext context) {
if (height == null) {
WidgetsBinding.instance.addPostFrameCallback((v) => setState(() {}));
}
if (widget.itemCount == 0) return const SizedBox();
if (isInit) return Container(key: infoKey, child: widget.childBuilder(0));
return SizedBox(
height: height,
child: ListView.separated(
padding: widget.padding,
scrollDirection: Axis.horizontal,
itemCount: widget.itemCount,
itemBuilder: (c, i) => widget.childBuilder.call(i),
separatorBuilder: (c, i) => SizedBox(width: widget.gapSize),
),
);
}
}
extension GlobalKeyExtension on GlobalKey {
Rect? get globalPaintBounds {
final renderObject = currentContext?.findRenderObject();
final translation = renderObject?.getTransformTo(null).getTranslation();
if (translation != null && renderObject?.paintBounds != null) {
final offset = Offset(translation.x, translation.y);
return renderObject!.paintBounds.shift(offset);
} else {
return null;
}
}
}

View File

@@ -67,6 +67,7 @@ class VideoDetailData {
String? likeIcon; String? likeIcon;
bool? needJumpBv; bool? needJumpBv;
String? epId; String? epId;
List<Staff>? staff;
VideoDetailData({ VideoDetailData({
this.bvid, this.bvid,
@@ -103,6 +104,7 @@ class VideoDetailData {
this.likeIcon, this.likeIcon,
this.needJumpBv, this.needJumpBv,
this.epId, this.epId,
this.staff,
}); });
VideoDetailData.fromJson(Map<String, dynamic> json) { VideoDetailData.fromJson(Map<String, dynamic> json) {
@@ -152,6 +154,9 @@ class VideoDetailData {
: HonorReply.fromJson(json["honor_reply"]); : HonorReply.fromJson(json["honor_reply"]);
likeIcon = json["like_icon"]; likeIcon = json["like_icon"];
needJumpBv = json["need_jump_bv"]; needJumpBv = json["need_jump_bv"];
staff = json["staff"] == null
? null
: (json["staff"] as List).map((item) => Staff.fromJson(item)).toList();
if (json['redirect_url'] != null) { if (json['redirect_url'] != null) {
epId = resolveEpId(json['redirect_url']); epId = resolveEpId(json['redirect_url']);
} }
@@ -276,6 +281,42 @@ class Dimension {
} }
} }
class Staff {
dynamic mid;
String? title;
String? name;
String? face;
Vip? vip;
Staff({
this.mid,
this.title,
this.name,
this.face,
this.vip,
});
Staff.fromJson(Map<String, dynamic> json) {
mid = json["mid"];
title = json["title"];
name = json["name"];
face = json["face"];
vip = json["vip"] == null ? null : Vip.fromJson(json["vip"]);
}
}
class Vip {
dynamic type;
Vip({
this.type,
});
Vip.fromJson(Map<String, dynamic> json) {
type = json["type"];
}
}
class HonorReply { class HonorReply {
List<Honor>? honor; List<Honor>? honor;

View File

@@ -126,6 +126,7 @@ class VideoIntroController extends GetxController {
var result = await VideoHttp.videoIntro(bvid: bvid); var result = await VideoHttp.videoIntro(bvid: bvid);
if (result['status']) { if (result['status']) {
videoDetail.value = result['data']!; videoDetail.value = result['data']!;
videoItem!['staff'] = result['data'].staff;
if (videoDetailController.videoItem['pic'] == null || if (videoDetailController.videoItem['pic'] == null ||
videoDetailController.videoItem['pic'] == '') { videoDetailController.videoItem['pic'] == '') {
try { try {

View File

@@ -1,3 +1,4 @@
import 'package:PiliPalaX/common/widgets/self_sized_horizontal_list.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
@@ -238,65 +239,120 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row(children: [ Row(
Expanded( children: [
child: GestureDetector( Expanded(
onTap: onPushMember, child: videoItem['staff'] == null
child: Container( ? GestureDetector(
padding: onTap: onPushMember,
const EdgeInsets.symmetric(vertical: 1, horizontal: 0), child: Container(
child: Row( padding: const EdgeInsets.symmetric(
mainAxisAlignment: MainAxisAlignment.center, vertical: 1, horizontal: 0),
mainAxisSize: MainAxisSize.min, child: Row(
children: [ mainAxisAlignment: MainAxisAlignment.center,
NetworkImgLayer( mainAxisSize: MainAxisSize.min,
type: 'avatar', children: [
src: loadingStatus NetworkImgLayer(
? videoItem['owner']?.face ?? "" type: 'avatar',
: widget.videoDetail!.owner!.face, src: loadingStatus
width: 30, ? videoItem['owner']?.face ?? ""
height: 30, : widget.videoDetail!.owner!.face,
fadeInDuration: Duration.zero, width: 30,
fadeOutDuration: Duration.zero, height: 30,
), fadeInDuration: Duration.zero,
const SizedBox(width: 10), fadeOutDuration: Duration.zero,
Expanded( ),
child: Column( const SizedBox(width: 10),
crossAxisAlignment: CrossAxisAlignment.start, Expanded(
children: [ child: Column(
Text( crossAxisAlignment:
loadingStatus CrossAxisAlignment.start,
? videoItem['owner']?.name ?? "" children: [
: widget.videoDetail!.owner!.name, Text(
maxLines: 1, loadingStatus
overflow: TextOverflow.ellipsis, ? videoItem['owner']?.name ?? ""
style: TextStyle( : widget.videoDetail!.owner!.name,
fontSize: 12, color: t.colorScheme.primary), maxLines: 1,
// semanticsLabel: "Up主${owner.name}", overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 12,
color: t.colorScheme.primary),
// semanticsLabel: "Up主${owner.name}",
),
const SizedBox(height: 0),
Obx(
() => Text(
Utils.numFormat(videoIntroController
.userStat.value['follower']),
semanticsLabel:
"${Utils.numFormat(videoIntroController.userStat.value['follower'])}粉丝",
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
),
),
],
),
),
followButton(context, t),
],
),
), ),
const SizedBox(height: 0), )
Obx(() => Text( : SelfSizedHorizontalList(
Utils.numFormat(videoIntroController gapSize: 10,
.userStat.value['follower']), itemCount: videoItem['staff'].length,
semanticsLabel: childBuilder: (index) => Column(
"${Utils.numFormat(videoIntroController.userStat.value['follower'])}粉丝", mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
onTap: () => Get.toNamed(
'/member?mid=${videoItem['staff'][index].mid}',
arguments: {
'face': videoItem['staff'][index].face,
heroTag: Utils.makeHeroTag(
videoItem['staff'][index].mid),
}),
child: NetworkImgLayer(
type: 'avatar',
src: videoItem['staff'][index].face,
width: 40,
height: 40,
fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero,
),
),
const SizedBox(height: 2),
Text(
videoItem['staff'][index].name.length > 5
? '${videoItem['staff'][index].name.toString().substring(0, 5)}...'
: videoItem['staff'][index].name,
style: TextStyle(
color:
videoItem['staff'][index].vip.type == 2
? Utils.vipColor
: null,
),
),
Text(
videoItem['staff'][index].title,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: t.colorScheme.outline, color:
Theme.of(context).colorScheme.outline,
), ),
)), ),
], ],
)), ),
followButton(context, t), ),
],
),
), ),
)), if (isHorizontal) ...[
if (isHorizontal) ...[ const SizedBox(width: 10),
const SizedBox(width: 10), Expanded(child: actionGrid(context, videoIntroController)),
Expanded(child: actionGrid(context, videoIntroController)), ]
] ],
]), ),
const SizedBox(height: 8), const SizedBox(height: 8),
GestureDetector( GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,