mod: 用户页跳转&样式修改

This commit is contained in:
guozhigq
2023-07-14 22:44:21 +08:00
parent 8fd1efd8bf
commit b7083fdc15
17 changed files with 764 additions and 227 deletions

BIN
assets/images/live.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -199,6 +199,8 @@ class Api {
// 用户信息 需要Wbi签名 // 用户信息 需要Wbi签名
// https://api.bilibili.com/x/space/wbi/acc/info?mid=503427686&token=&platform=web&web_location=1550101&w_rid=d709892496ce93e3d94d6d37c95bde91&wts=1689301482 // https://api.bilibili.com/x/space/wbi/acc/info?mid=503427686&token=&platform=web&web_location=1550101&w_rid=d709892496ce93e3d94d6d37c95bde91&wts=1689301482
static const String memberInfo = static const String memberInfo = '/x/space/wbi/acc/info';
'https://api.bilibili.com/x/space/wbi/acc/info';
// 用户名片信息
static const String memberCardInfo = '/x/web-interface/card';
} }

View File

@ -19,10 +19,23 @@ class MemberHttp {
} }
static Future memberStat({int? mid}) async { static Future memberStat({int? mid}) async {
var res = await Request().get(Api.userStat, data: {mid: mid}); var res = await Request().get(Api.userStat, data: {'vmid': mid});
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
print(res.data['data']); return {'status': true, 'data': res.data['data']};
// return {'status': true, 'data': FansDataModel.fromJson(res.data['data'])}; } else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
}
}
static Future memberCardInfo({int? mid}) async {
var res = await Request()
.get(Api.memberCardInfo, data: {'mid': mid, 'photo': true});
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else { } else {
return { return {
'status': false, 'status': false,

View File

@ -7,6 +7,9 @@ class MemberInfoModel {
this.sign, this.sign,
this.level, this.level,
this.isFollowed, this.isFollowed,
this.topPhoto,
this.official,
this.vip,
}); });
int? mid; int? mid;
@ -16,14 +19,73 @@ class MemberInfoModel {
String? sign; String? sign;
int? level; int? level;
bool? isFollowed; bool? isFollowed;
String? topPhoto;
Map? official;
Vip? vip;
LiveRoom? liveRoom;
MemberInfoModel.fromJson(Map<String, dynamic> json) { MemberInfoModel.fromJson(Map<String, dynamic> json) {
mid = json['mid']; mid = json['mid'];
name = json['name']; name = json['name'];
sex = json['sex']; sex = json['sex'];
face = json['face']; face = json['face'];
sign = json['sign']; sign = json['sign'] == '' ? '该用户还没有签名' : json['sign'];
level = json['level']; level = json['level'];
isFollowed = json['is_followed']; isFollowed = json['is_followed'];
topPhoto = json['top_photo'];
official = json['official'];
vip = Vip.fromJson(json['vip']);
liveRoom = LiveRoom.fromJson(json['live_room']);
}
}
class Vip {
Vip({
this.type,
this.status,
this.dueDate,
this.label,
});
int? type;
int? status;
int? dueDate;
Map? label;
Vip.fromJson(Map<String, dynamic> json) {
type = json['type'];
status = json['status'];
dueDate = json['due_date'];
label = json['label'];
}
}
class LiveRoom {
LiveRoom({
this.roomStatus,
this.liveStatus,
this.url,
this.title,
this.cover,
this.roomId,
this.roundStatus,
});
int? roomStatus;
int? liveStatus;
String? url;
String? title;
String? cover;
int? roomId;
int? roundStatus;
LiveRoom.fromJson(Map<String, dynamic> json) {
roomStatus = json['room_status'];
liveStatus = json['live_status'];
url = json['url'];
title = json['title'];
cover = json['cover'];
roomId = json['room_id'];
roundStatus = json['round_status'];
} }
} }

View File

@ -193,7 +193,7 @@ class SearchUserItemModel {
usign = json['usign']; usign = json['usign'];
fans = json['fans']; fans = json['fans'];
videos = json['videos']; videos = json['videos'];
upic = json['upic']; upic = 'https:' + json['upic'];
faceNft = json['face_nft']; faceNft = json['face_nft'];
faceNftType = json['face_nft_type']; faceNftType = json['face_nft_type'];
verifyInfo = json['verify_info']; verifyInfo = json['verify_info'];

View File

@ -1,20 +1,29 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/utils/utils.dart';
Widget author(item, context) { Widget author(item, context) {
String heroTag = Utils.makeHeroTag(item.modules.moduleAuthor.mid);
return Container( return Container(
padding: const EdgeInsets.fromLTRB(12, 12, 12, 8), padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
child: Row( child: Row(
children: [ children: [
GestureDetector( GestureDetector(
onTap: () => onTap: () => Get.toNamed(
Get.toNamed('/member?mid=${item.modules.moduleAuthor.mid}'), '/member?mid=${item.modules.moduleAuthor.mid}',
child: NetworkImgLayer( arguments: {
width: 40, 'face': item.modules.moduleAuthor.face,
height: 40, 'heroTag': heroTag
type: 'avatar', }),
src: item.modules.moduleAuthor.face, child: Hero(
tag: heroTag,
child: NetworkImgLayer(
width: 40,
height: 40,
type: 'avatar',
src: item.modules.moduleAuthor.face,
),
), ),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),

View File

@ -174,12 +174,13 @@ class _UpPanelState extends State<UpPanel> {
Badge( Badge(
smallSize: 8, smallSize: 8,
label: data.type == 'live' ? const Text('Live') : null, label: data.type == 'live' ? const Text('Live') : null,
textColor: Theme.of(context).colorScheme.onPrimary, textColor: Theme.of(context).colorScheme.onSecondaryContainer,
alignment: AlignmentDirectional.bottomCenter, alignment: AlignmentDirectional.bottomCenter,
padding: const EdgeInsets.only(left: 4, right: 4), padding: const EdgeInsets.only(left: 6, right: 6),
isLabelVisible: data.type == 'live' || isLabelVisible: data.type == 'live' ||
(data.type == 'up' && (data.hasUpdate ?? false)), (data.type == 'up' && (data.hasUpdate ?? false)),
backgroundColor: Theme.of(context).primaryColor, backgroundColor:
Theme.of(context).colorScheme.secondaryContainer,
child: NetworkImgLayer( child: NetworkImgLayer(
width: 49, width: 49,
height: 49, height: 49,

View File

@ -1,14 +1,21 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/utils/utils.dart';
Widget fanItem({item}) { Widget fanItem({item}) {
String heroTag = Utils.makeHeroTag(item!.mid);
return ListTile( return ListTile(
onTap: () {}, onTap: () => Get.toNamed('/member?mid=${item.mid}',
leading: NetworkImgLayer( arguments: {'face': item.face, 'heroTag': heroTag}),
width: 38, leading: Hero(
height: 38, tag: heroTag,
type: 'avatar', child: NetworkImgLayer(
src: item.face, width: 38,
height: 38,
type: 'avatar',
src: item.face,
),
), ),
title: Text(item.uname), title: Text(item.uname),
subtitle: Text( subtitle: Text(

View File

@ -1,15 +1,21 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/utils/utils.dart';
Widget followItem({item}) { Widget followItem({item}) {
String heroTag = Utils.makeHeroTag(item!.mid);
return ListTile( return ListTile(
onTap: () {}, onTap: () => Get.toNamed('/member?mid=${item.mid}',
leading: NetworkImgLayer( arguments: {'face': item.face, 'heroTag': heroTag}),
width: 38, leading: Hero(
height: 38, tag: heroTag,
type: 'avatar', child: NetworkImgLayer(
src: item.face, width: 38,
height: 38,
type: 'avatar',
src: item.face,
),
), ),
title: Text(item.uname), title: Text(item.uname),
subtitle: Text( subtitle: Text(

View File

@ -23,7 +23,7 @@ class LiveRoomController extends GetxController {
super.onInit(); super.onInit();
if (Get.arguments != null) { if (Get.arguments != null) {
var args = Get.arguments['liveItem']; var args = Get.arguments['liveItem'];
heroTag = Get.arguments['heroTag']; heroTag = Get.arguments['heroTag'] ?? '';
liveItem = args; liveItem = args;
roomId = liveItem.roomId!; roomId = liveItem.roomId!;
if (args.pic != null && args.pic != '') { if (args.pic != null && args.pic != '') {

View File

@ -89,20 +89,21 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
controller: _meeduPlayerController!, controller: _meeduPlayerController!,
), ),
), ),
Visibility( if (_liveRoomController.liveItem.cover != null)
visible: isShowCover, Visibility(
child: Positioned( visible: isShowCover,
top: 0, child: Positioned(
left: 0, top: 0,
right: 0, left: 0,
child: NetworkImgLayer( right: 0,
type: 'emote', child: NetworkImgLayer(
src: _liveRoomController.liveItem.cover, type: 'emote',
width: Get.size.width, src: _liveRoomController.liveItem.cover,
height: videoHeight, width: Get.size.width,
height: videoHeight,
),
), ),
), ),
),
], ],
)), )),
if (_liveRoomController.liveItem.watchedShow != null) if (_liveRoomController.liveItem.watchedShow != null)

View File

@ -1,18 +1,26 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/http/member.dart'; import 'package:pilipala/http/member.dart';
import 'package:pilipala/models/member/info.dart';
import 'package:pilipala/utils/wbi_sign.dart'; import 'package:pilipala/utils/wbi_sign.dart';
class MemberController extends GetxController { class MemberController extends GetxController {
late int mid; late int mid;
Rx<MemberInfoModel> memberInfo = MemberInfoModel().obs;
Map? userStat;
String? face;
String? heroTag;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
mid = int.parse(Get.parameters['mid']!); mid = int.parse(Get.parameters['mid']!);
getInfo(); face = Get.arguments['face']!;
heroTag = Get.arguments['heroTag']!;
} }
getInfo() async { // 获取用户信息
Future<Map<String, dynamic>> getInfo() async {
await getMemberStat();
String params = await WbiSign().makSign({ String params = await WbiSign().makSign({
'mid': mid, 'mid': mid,
'token': '', 'token': '',
@ -22,7 +30,25 @@ class MemberController extends GetxController {
params = '?$params'; params = '?$params';
var res = await MemberHttp.memberInfo(params: params); var res = await MemberHttp.memberInfo(params: params);
if (res['status']) { if (res['status']) {
print(res['data']); memberInfo.value = res['data'];
} }
return res;
}
// 获取用户状态
Future<Map<String, dynamic>> getMemberStat() async {
var res = await MemberHttp.memberStat(mid: mid);
if (res['status']) {
userStat = res['data'];
}
return res;
}
Future getMemberCardInfo() async {
var res = await MemberHttp.memberCardInfo(mid: mid);
if (res['status']) {
print(userStat);
}
return res;
} }
} }

View File

@ -1,7 +1,10 @@
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/user/stat.dart';
import 'package:pilipala/pages/member/index.dart'; import 'package:pilipala/pages/member/index.dart';
import 'package:pilipala/utils/utils.dart';
class MemberPage extends StatefulWidget { class MemberPage extends StatefulWidget {
const MemberPage({super.key}); const MemberPage({super.key});
@ -10,11 +13,379 @@ class MemberPage extends StatefulWidget {
State<MemberPage> createState() => _MemberPageState(); State<MemberPage> createState() => _MemberPageState();
} }
class _MemberPageState extends State<MemberPage> { class _MemberPageState extends State<MemberPage>
with SingleTickerProviderStateMixin {
final MemberController _memberController = Get.put(MemberController()); final MemberController _memberController = Get.put(MemberController());
final ScrollController _extendNestCtr = ScrollController();
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold(); return Scaffold(
primary: true,
body: ExtendedNestedScrollView(
controller: _extendNestCtr,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
pinned: false,
primary: true,
elevation: 0,
scrolledUnderElevation: 0,
forceElevated: innerBoxIsScrolled,
expandedHeight: 300,
actions: [
IconButton(onPressed: () {}, icon: const Icon(Icons.more_vert)),
const SizedBox(width: 4),
],
flexibleSpace: FlexibleSpaceBar(
background: Stack(
children: [
Positioned.fill(
bottom: 10,
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.fitWidth,
image: NetworkImage(_memberController.face!),
alignment: Alignment.topCenter,
isAntiAlias: true,
),
),
foregroundDecoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Theme.of(context)
.colorScheme
.background
.withOpacity(0.44),
Theme.of(context).colorScheme.background,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: const [0.0, 0.46],
),
),
),
),
Positioned(
left: 0,
right: 0,
bottom: 0,
height: 20,
child: Container(
color: Theme.of(context).colorScheme.background,
),
),
Padding(
padding: const EdgeInsets.only(left: 18, right: 18),
child: FutureBuilder(
future: _memberController.getInfo(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
Map data = snapshot.data!;
if (data['status']) {
return Obx(
() => Stack(
alignment: AlignmentDirectional.center,
children: [
Column(
// mainAxisAlignment:
// MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
profile(
_memberController.memberInfo.value),
const SizedBox(height: 14),
Row(
children: [
Text(
_memberController
.memberInfo.value.name!,
style: Theme.of(context)
.textTheme
.bodyLarge!
.copyWith(
fontWeight:
FontWeight.bold),
),
const SizedBox(width: 6),
Image.asset(
'assets/images/lv/lv${_memberController.memberInfo.value.level}.png',
height: 11,
),
const SizedBox(width: 6),
if (_memberController.memberInfo
.value.vip!.status ==
1 &&
_memberController.memberInfo
.value.vip!.label![
'img_label_uri_hans'] !=
'') ...[
Image.network(
_memberController.memberInfo
.value.vip!.label![
'img_label_uri_hans'],
height: 20,
),
] else if (_memberController
.memberInfo
.value
.vip!
.status ==
1 &&
_memberController.memberInfo
.value.vip!.label![
'img_label_uri_hans_static'] !=
'') ...[
Image.network(
_memberController.memberInfo
.value.vip!.label![
'img_label_uri_hans_static'],
height: 20,
),
]
],
),
if (_memberController.memberInfo.value
.official!['title'] !=
'') ...[
const SizedBox(height: 6),
Row(
children: [
Text(
_memberController
.memberInfo
.value
.official![
'role'] ==
1
? '个人认证:'
: '企业认证:',
),
Text(
_memberController.memberInfo
.value.official!['title']!,
),
],
),
],
const SizedBox(height: 4),
if (_memberController
.memberInfo.value.sign !=
'')
Text(
_memberController
.memberInfo.value.sign!,
textAlign: TextAlign.left,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
],
),
);
} else {
return SizedBox();
}
} else {
// 骨架屏
return profile(null, loadingStatus: true);
}
},
),
)
],
),
),
),
];
},
pinnedHeaderSliverHeightBuilder: () {
return MediaQuery.of(context).padding.top + kToolbarHeight;
},
onlyOneScrollInBody: true,
body: Column(
children: [
Container(
width: double.infinity,
height: 50,
child: TabBar(controller: _tabController, tabs: [
Tab(text: '主页'),
Tab(text: '动态'),
Tab(text: '投稿'),
]),
),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
Text('主页'),
Text('动态'),
Text('投稿'),
],
))
],
),
),
);
}
Widget profile(memberInfo, {loadingStatus = false}) {
return Padding(
padding: EdgeInsets.only(top: 3 * MediaQuery.of(context).padding.top),
child: Row(
children: [
Hero(
tag: _memberController.heroTag!,
child: Stack(
children: [
NetworkImgLayer(
width: 90,
height: 90,
type: 'avatar',
src: !loadingStatus
? memberInfo.face
: _memberController.face,
),
Positioned(
bottom: 0,
left: 14,
child: Container(
padding: const EdgeInsets.fromLTRB(6, 2, 6, 2),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius:
const BorderRadius.all(Radius.circular(10)),
),
child: Row(children: [
Image.asset(
'assets/images/live.gif',
height: 10,
),
Text(
' 直播中',
style: TextStyle(
color: Colors.white,
fontSize: Theme.of(context)
.textTheme
.labelSmall!
.fontSize),
)
]),
),
)
],
)),
const SizedBox(width: 12),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(
children: [
Text(
!loadingStatus
? _memberController.userStat!['following']
.toString()
: '-',
style: const TextStyle(fontWeight: FontWeight.bold),
),
Text(
'关注',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize),
)
],
),
Column(
children: [
Text(
!loadingStatus
? Utils.numFormat(
_memberController.userStat!['follower'],
)
: '-',
style:
const TextStyle(fontWeight: FontWeight.bold)),
Text('粉丝',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize))
],
),
Column(
children: [
const Text('-',
style: TextStyle(fontWeight: FontWeight.bold)),
Text(
'获赞',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize),
)
],
),
],
),
),
const SizedBox(height: 10),
Row(
children: [
TextButton(
onPressed: () {},
style: TextButton.styleFrom(
padding: const EdgeInsets.only(left: 42, right: 42),
foregroundColor: !loadingStatus && memberInfo.isFollowed
? null
: Theme.of(context).colorScheme.onPrimary,
backgroundColor: !loadingStatus && memberInfo.isFollowed
? Theme.of(context).colorScheme.onInverseSurface
: Theme.of(context).colorScheme.primary, // 设置按钮背景色
),
child: Text(!loadingStatus && memberInfo.isFollowed
? '取关'
: '关注'),
),
const SizedBox(width: 8),
TextButton(
onPressed: () {},
style: TextButton.styleFrom(
padding: const EdgeInsets.only(left: 42, right: 42),
backgroundColor:
Theme.of(context).colorScheme.onInverseSurface,
),
child: const Text('发消息'),
)
],
)
],
),
),
],
),
);
} }
} }

View File

@ -1,10 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/utils/utils.dart';
Widget searchUserPanel(BuildContext context, ctr, list) { Widget searchUserPanel(BuildContext context, ctr, list) {
TextStyle style = TextStyle( TextStyle style = TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
color: Theme.of(context).colorScheme.outline); color: Theme.of(context).colorScheme.outline);
return ListView.builder( return ListView.builder(
controller: ctr!.scrollController, controller: ctr!.scrollController,
addAutomaticKeepAlives: false, addAutomaticKeepAlives: false,
@ -12,17 +15,22 @@ Widget searchUserPanel(BuildContext context, ctr, list) {
itemCount: list!.length, itemCount: list!.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
var i = list![index]; var i = list![index];
String heroTag = Utils.makeHeroTag(i!.mid);
return InkWell( return InkWell(
onTap: () {}, onTap: () => Get.toNamed('/member?mid=${i.mid}',
arguments: {'heroTag': heroTag, 'face': i.upic}),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
child: Row( child: Row(
children: [ children: [
NetworkImgLayer( Hero(
width: 42, tag: heroTag,
height: 42, child: NetworkImgLayer(
src: i.upic, width: 42,
type: 'avatar', height: 42,
src: i.upic,
type: 'avatar',
),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Column( Column(

View File

@ -360,62 +360,80 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
height: 26, height: 26,
color: Theme.of(context).dividerColor.withOpacity(0.1), color: Theme.of(context).dividerColor.withOpacity(0.1),
), ),
Row( GestureDetector(
children: [ onTap: () {
NetworkImgLayer( int mid = !widget.loadingStatus
type: 'avatar', ? widget.videoDetail!.owner!.mid
src: !widget.loadingStatus : videoItem['owner'].mid;
? widget.videoDetail!.owner!.face String face = !widget.loadingStatus
: videoItem['owner'].face, ? widget.videoDetail!.owner!.face
width: 38, : videoItem['owner'].face;
height: 38, Get.toNamed('/member?mid=$mid', arguments: {
fadeInDuration: Duration.zero, 'face': face,
fadeOutDuration: Duration.zero, 'heroTag': (mid + 99).toString()
), });
const SizedBox(width: 14), },
Column( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ Hero(
Text(!widget.loadingStatus tag: videoItem['owner'].mid + 99,
? widget.videoDetail!.owner!.name child: NetworkImgLayer(
: videoItem['owner'].name), type: 'avatar',
// const SizedBox(width: 10), src: !widget.loadingStatus
Text( ? widget.videoDetail!.owner!.face
widget.loadingStatus : videoItem['owner'].face,
? '- 粉丝' width: 38,
: '${Utils.numFormat(videoIntroController.userStat['follower'])}粉丝', height: 38,
style: TextStyle( fadeInDuration: Duration.zero,
fontSize: Theme.of(context) fadeOutDuration: Duration.zero,
.textTheme
.labelSmall!
.fontSize,
color: Theme.of(context).colorScheme.outline),
),
],
),
const Spacer(),
AnimatedOpacity(
opacity: widget.loadingStatus ? 0 : 1,
duration: const Duration(milliseconds: 150),
child: SizedBox(
height: 36,
child: Obx(
() => videoIntroController.followStatus.isNotEmpty
? ElevatedButton(
onPressed: () => videoIntroController
.actionRelationMod(),
child: Text(videoIntroController
.followStatus['attribute'] ==
0
? '关注'
: '已关注'),
)
: const SizedBox(),
), ),
), ),
), const SizedBox(width: 14),
], Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(!widget.loadingStatus
? widget.videoDetail!.owner!.name
: videoItem['owner'].name),
// const SizedBox(width: 10),
Text(
widget.loadingStatus
? '- 粉丝'
: '${Utils.numFormat(videoIntroController.userStat['follower'])}粉丝',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelSmall!
.fontSize,
color: Theme.of(context).colorScheme.outline),
),
],
),
const Spacer(),
AnimatedOpacity(
opacity: widget.loadingStatus ? 0 : 1,
duration: const Duration(milliseconds: 150),
child: SizedBox(
height: 36,
child: Obx(
() => videoIntroController.followStatus.isNotEmpty
? ElevatedButton(
onPressed: () => videoIntroController
.actionRelationMod(),
child: Text(videoIntroController
.followStatus['attribute'] ==
0
? '关注'
: '已关注'),
)
: const SizedBox(),
),
),
),
],
),
), ),
Divider( Divider(
height: 26, height: 26,
color: Theme.of(context).dividerColor.withOpacity(0.1), color: Theme.of(context).dividerColor.withOpacity(0.1),

View File

@ -128,115 +128,112 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
_videoReplyController.currentPage = 0; _videoReplyController.currentPage = 0;
return await _videoReplyController.queryReplyList(); return await _videoReplyController.queryReplyList();
}, },
child: Scaffold( child: Stack(
resizeToAvoidBottomInset: false, children: [
body: Stack( CustomScrollView(
children: [ controller: _videoReplyController.scrollController,
CustomScrollView( key: const PageStorageKey<String>('评论'),
controller: _videoReplyController.scrollController, slivers: <Widget>[
key: const PageStorageKey<String>('评论'), const SliverToBoxAdapter(child: SizedBox(height: 12)),
slivers: <Widget>[ FutureBuilder(
const SliverToBoxAdapter(child: SizedBox(height: 12)), future: _futureBuilderFuture,
FutureBuilder( builder: (context, snapshot) {
future: _futureBuilderFuture, if (snapshot.connectionState == ConnectionState.done) {
builder: (context, snapshot) { Map data = snapshot.data as Map;
if (snapshot.connectionState == ConnectionState.done) { if (data['status']) {
Map data = snapshot.data as Map; // 请求成功
if (data['status']) { return Obx(
// 请求成功 () => SliverList(
return Obx( delegate: SliverChildBuilderDelegate(
() => SliverList( (context, index) {
delegate: SliverChildBuilderDelegate( if (index ==
(context, index) { _videoReplyController.replyList.length) {
if (index == return Container(
_videoReplyController.replyList.length) { padding: EdgeInsets.only(
return Container( bottom: MediaQuery.of(context)
padding: EdgeInsets.only( .padding
bottom: MediaQuery.of(context) .bottom),
.padding height:
.bottom), MediaQuery.of(context).padding.bottom +
height: 100,
MediaQuery.of(context).padding.bottom + child: Center(
100, child: Obx(() => Text(
child: Center( _videoReplyController.noMore.value)),
child: Obx(() => Text( ),
_videoReplyController.noMore.value)), );
), } else {
); return ReplyItem(
} else { replyItem:
return ReplyItem( _videoReplyController.replyList[index],
replyItem: _videoReplyController showReplyRow: true,
.replyList[index], replyLevel: replyLevel);
showReplyRow: true, }
replyLevel: replyLevel); },
} childCount:
}, _videoReplyController.replyList.length + 1,
childCount:
_videoReplyController.replyList.length + 1,
),
), ),
); ),
} else { );
// 请求错误
return HttpError(
errMsg: data['msg'],
fn: () => setState(() {}),
);
}
} else { } else {
// 骨架屏 // 请求错误
return SliverList( return HttpError(
delegate: SliverChildBuilderDelegate((context, index) { errMsg: data['msg'],
return const VideoReplySkeleton(); fn: () => setState(() {}),
}, childCount: 5),
); );
} }
}, } else {
) // 骨架屏
], return SliverList(
), delegate: SliverChildBuilderDelegate((context, index) {
Positioned( return const VideoReplySkeleton();
bottom: MediaQuery.of(context).padding.bottom + 14, }, childCount: 5),
right: 14,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 2),
// 评论内容为空/不足一屏
// begin: const Offset(0, 0),
end: const Offset(0, 0),
).animate(CurvedAnimation(
parent: fabAnimationCtr,
curve: Curves.easeInOut,
)),
child: FloatingActionButton(
heroTag: null,
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (builder) {
return VideoReplyNewDialog(
replyLevel: '0',
oid: IdUtils.bv2av(Get.parameters['bvid']!),
root: 0,
parent: 0,
);
},
).then(
(value) => {
// 完成评论,数据添加
if (value != null && value['data'])
{_videoReplyController.replyList.add(value['data'])}
},
); );
}, }
tooltip: '发表评论', },
child: const Icon(Icons.reply), )
), ],
),
Positioned(
bottom: MediaQuery.of(context).padding.bottom + 14,
right: 14,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 2),
// 评论内容为空/不足一屏
// begin: const Offset(0, 0),
end: const Offset(0, 0),
).animate(CurvedAnimation(
parent: fabAnimationCtr,
curve: Curves.easeInOut,
)),
child: FloatingActionButton(
heroTag: null,
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (builder) {
return VideoReplyNewDialog(
replyLevel: '0',
oid: IdUtils.bv2av(Get.parameters['bvid']!),
root: 0,
parent: 0,
);
},
).then(
(value) => {
// 完成评论,数据添加
if (value != null && value['data'])
{_videoReplyController.replyList.add(value['data'])}
},
);
},
tooltip: '发表评论',
child: const Icon(Icons.reply),
), ),
), ),
], ),
), ],
), ),
); );
} }

View File

@ -46,16 +46,19 @@ class ReplyItem extends StatelessWidget {
); );
} }
Widget lfAvtar(context) { Widget lfAvtar(context, heroTag) {
return Container( return Container(
margin: const EdgeInsets.only(top: 5), margin: const EdgeInsets.only(top: 5),
child: Stack( child: Stack(
children: [ children: [
NetworkImgLayer( Hero(
src: replyItem!.member!.avatar, tag: heroTag,
width: 34, child: NetworkImgLayer(
height: 34, src: replyItem!.member!.avatar,
type: 'avatar', width: 34,
height: 34,
type: 'avatar',
),
), ),
if (replyItem!.member!.officialVerify != null && if (replyItem!.member!.officialVerify != null &&
replyItem!.member!.officialVerify!['type'] == 0) replyItem!.member!.officialVerify!['type'] == 0)
@ -87,16 +90,18 @@ class ReplyItem extends StatelessWidget {
} }
Widget content(context) { Widget content(context) {
String heroTag = Utils.makeHeroTag(replyItem!.mid);
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
// 头像、昵称 // 头像、昵称
GestureDetector( GestureDetector(
// onTap: () => onTap: () {
// Get.toNamed('/member/${reply.userName}', parameters: { Get.toNamed('/member?mid=${replyItem!.mid}', arguments: {
// 'memberAvatar': reply.avatar, 'face': replyItem!.member!.avatar!,
// 'heroTag': reply.userName + heroTag, 'heroTag': heroTag
// }), });
},
child: SizedBox( child: SizedBox(
width: double.infinity, width: double.infinity,
child: Stack( child: Stack(
@ -105,7 +110,7 @@ class ReplyItem extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
lfAvtar(context), lfAvtar(context, heroTag),
const SizedBox(width: 12), const SizedBox(width: 12),
Text( Text(
replyItem!.member!.uname!, replyItem!.member!.uname!,
@ -367,9 +372,16 @@ class ReplyItemRow extends StatelessWidget {
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () => { ..onTap = () {
print('跳转至用户主页'), String heroTag =
}, Utils.makeHeroTag(replies![i].member.mid);
Get.toNamed(
'/member?mid=${replies![i].member.mid}',
arguments: {
'face': replies![i].member.avatar,
'heroTag': heroTag
});
},
), ),
if (replies![i].isUp) if (replies![i].isUp)
WidgetSpan( WidgetSpan(
@ -521,9 +533,13 @@ InlineSpan buildContent(BuildContext context, content) {
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () => { ..onTap = () {
print('跳转至用户主页'), String heroTag = Utils.makeHeroTag(value);
}, Get.toNamed(
'/member?mid=$value',
arguments: {'face': '', 'heroTag': heroTag},
);
},
), ),
); );
}); });