From b7083fdc155da2ec7ae3b973b08902662db946a0 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 14 Jul 2023 22:44:21 +0800 Subject: [PATCH] =?UTF-8?q?mod:=20=E7=94=A8=E6=88=B7=E9=A1=B5=E8=B7=B3?= =?UTF-8?q?=E8=BD=AC&=E6=A0=B7=E5=BC=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/images/live.gif | Bin 0 -> 3678 bytes lib/http/api.dart | 6 +- lib/http/member.dart | 19 +- lib/models/member/info.dart | 64 ++- lib/models/search/result.dart | 2 +- lib/pages/dynamics/widgets/author_panel.dart | 23 +- lib/pages/dynamics/widgets/up_panel.dart | 7 +- lib/pages/fan/widgets/fan_item.dart | 19 +- lib/pages/follow/widgets/follow_item.dart | 20 +- lib/pages/liveRoom/controller.dart | 2 +- lib/pages/liveRoom/view.dart | 25 +- lib/pages/member/controller.dart | 32 +- lib/pages/member/view.dart | 375 +++++++++++++++++- lib/pages/searchPanel/widgets/user_panel.dart | 20 +- lib/pages/video/detail/introduction/view.dart | 124 +++--- lib/pages/video/detail/reply/view.dart | 201 +++++----- .../detail/reply/widgets/reply_item.dart | 52 ++- 17 files changed, 764 insertions(+), 227 deletions(-) create mode 100644 assets/images/live.gif diff --git a/assets/images/live.gif b/assets/images/live.gif new file mode 100644 index 0000000000000000000000000000000000000000..ca0fb096436e737cf44a1331f63c73415ffe7c15 GIT binary patch literal 3678 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 json) { mid = json['mid']; name = json['name']; sex = json['sex']; face = json['face']; - sign = json['sign']; + sign = json['sign'] == '' ? '该用户还没有签名' : json['sign']; level = json['level']; 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 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 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']; } } diff --git a/lib/models/search/result.dart b/lib/models/search/result.dart index 08adad59..8aea087c 100644 --- a/lib/models/search/result.dart +++ b/lib/models/search/result.dart @@ -193,7 +193,7 @@ class SearchUserItemModel { usign = json['usign']; fans = json['fans']; videos = json['videos']; - upic = json['upic']; + upic = 'https:' + json['upic']; faceNft = json['face_nft']; faceNftType = json['face_nft_type']; verifyInfo = json['verify_info']; diff --git a/lib/pages/dynamics/widgets/author_panel.dart b/lib/pages/dynamics/widgets/author_panel.dart index 8aa33487..a1b67a92 100644 --- a/lib/pages/dynamics/widgets/author_panel.dart +++ b/lib/pages/dynamics/widgets/author_panel.dart @@ -1,20 +1,29 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/utils/utils.dart'; Widget author(item, context) { + String heroTag = Utils.makeHeroTag(item.modules.moduleAuthor.mid); return Container( padding: const EdgeInsets.fromLTRB(12, 12, 12, 8), child: Row( children: [ GestureDetector( - onTap: () => - Get.toNamed('/member?mid=${item.modules.moduleAuthor.mid}'), - child: NetworkImgLayer( - width: 40, - height: 40, - type: 'avatar', - src: item.modules.moduleAuthor.face, + onTap: () => Get.toNamed( + '/member?mid=${item.modules.moduleAuthor.mid}', + arguments: { + 'face': item.modules.moduleAuthor.face, + 'heroTag': heroTag + }), + child: Hero( + tag: heroTag, + child: NetworkImgLayer( + width: 40, + height: 40, + type: 'avatar', + src: item.modules.moduleAuthor.face, + ), ), ), const SizedBox(width: 10), diff --git a/lib/pages/dynamics/widgets/up_panel.dart b/lib/pages/dynamics/widgets/up_panel.dart index d36f7b30..96ae30d5 100644 --- a/lib/pages/dynamics/widgets/up_panel.dart +++ b/lib/pages/dynamics/widgets/up_panel.dart @@ -174,12 +174,13 @@ class _UpPanelState extends State { Badge( smallSize: 8, label: data.type == 'live' ? const Text('Live') : null, - textColor: Theme.of(context).colorScheme.onPrimary, + textColor: Theme.of(context).colorScheme.onSecondaryContainer, alignment: AlignmentDirectional.bottomCenter, - padding: const EdgeInsets.only(left: 4, right: 4), + padding: const EdgeInsets.only(left: 6, right: 6), isLabelVisible: data.type == 'live' || (data.type == 'up' && (data.hasUpdate ?? false)), - backgroundColor: Theme.of(context).primaryColor, + backgroundColor: + Theme.of(context).colorScheme.secondaryContainer, child: NetworkImgLayer( width: 49, height: 49, diff --git a/lib/pages/fan/widgets/fan_item.dart b/lib/pages/fan/widgets/fan_item.dart index 074a8d5e..072b959f 100644 --- a/lib/pages/fan/widgets/fan_item.dart +++ b/lib/pages/fan/widgets/fan_item.dart @@ -1,14 +1,21 @@ import 'package:flutter/material.dart'; +import 'package:get/get.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/utils/utils.dart'; Widget fanItem({item}) { + String heroTag = Utils.makeHeroTag(item!.mid); return ListTile( - onTap: () {}, - leading: NetworkImgLayer( - width: 38, - height: 38, - type: 'avatar', - src: item.face, + onTap: () => Get.toNamed('/member?mid=${item.mid}', + arguments: {'face': item.face, 'heroTag': heroTag}), + leading: Hero( + tag: heroTag, + child: NetworkImgLayer( + width: 38, + height: 38, + type: 'avatar', + src: item.face, + ), ), title: Text(item.uname), subtitle: Text( diff --git a/lib/pages/follow/widgets/follow_item.dart b/lib/pages/follow/widgets/follow_item.dart index 37a7b761..c1ac61a2 100644 --- a/lib/pages/follow/widgets/follow_item.dart +++ b/lib/pages/follow/widgets/follow_item.dart @@ -1,15 +1,21 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:get/get.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/utils/utils.dart'; Widget followItem({item}) { + String heroTag = Utils.makeHeroTag(item!.mid); return ListTile( - onTap: () {}, - leading: NetworkImgLayer( - width: 38, - height: 38, - type: 'avatar', - src: item.face, + onTap: () => Get.toNamed('/member?mid=${item.mid}', + arguments: {'face': item.face, 'heroTag': heroTag}), + leading: Hero( + tag: heroTag, + child: NetworkImgLayer( + width: 38, + height: 38, + type: 'avatar', + src: item.face, + ), ), title: Text(item.uname), subtitle: Text( diff --git a/lib/pages/liveRoom/controller.dart b/lib/pages/liveRoom/controller.dart index a4593ba2..93bf1018 100644 --- a/lib/pages/liveRoom/controller.dart +++ b/lib/pages/liveRoom/controller.dart @@ -23,7 +23,7 @@ class LiveRoomController extends GetxController { super.onInit(); if (Get.arguments != null) { var args = Get.arguments['liveItem']; - heroTag = Get.arguments['heroTag']; + heroTag = Get.arguments['heroTag'] ?? ''; liveItem = args; roomId = liveItem.roomId!; if (args.pic != null && args.pic != '') { diff --git a/lib/pages/liveRoom/view.dart b/lib/pages/liveRoom/view.dart index dc0694ac..556803e5 100644 --- a/lib/pages/liveRoom/view.dart +++ b/lib/pages/liveRoom/view.dart @@ -89,20 +89,21 @@ class _LiveRoomPageState extends State { controller: _meeduPlayerController!, ), ), - Visibility( - visible: isShowCover, - child: Positioned( - top: 0, - left: 0, - right: 0, - child: NetworkImgLayer( - type: 'emote', - src: _liveRoomController.liveItem.cover, - width: Get.size.width, - height: videoHeight, + if (_liveRoomController.liveItem.cover != null) + Visibility( + visible: isShowCover, + child: Positioned( + top: 0, + left: 0, + right: 0, + child: NetworkImgLayer( + type: 'emote', + src: _liveRoomController.liveItem.cover, + width: Get.size.width, + height: videoHeight, + ), ), ), - ), ], )), if (_liveRoomController.liveItem.watchedShow != null) diff --git a/lib/pages/member/controller.dart b/lib/pages/member/controller.dart index dc662f6d..6fb0f336 100644 --- a/lib/pages/member/controller.dart +++ b/lib/pages/member/controller.dart @@ -1,18 +1,26 @@ import 'package:get/get.dart'; import 'package:pilipala/http/member.dart'; +import 'package:pilipala/models/member/info.dart'; import 'package:pilipala/utils/wbi_sign.dart'; class MemberController extends GetxController { late int mid; + Rx memberInfo = MemberInfoModel().obs; + Map? userStat; + String? face; + String? heroTag; @override void onInit() { super.onInit(); mid = int.parse(Get.parameters['mid']!); - getInfo(); + face = Get.arguments['face']!; + heroTag = Get.arguments['heroTag']!; } - getInfo() async { + // 获取用户信息 + Future> getInfo() async { + await getMemberStat(); String params = await WbiSign().makSign({ 'mid': mid, 'token': '', @@ -22,7 +30,25 @@ class MemberController extends GetxController { params = '?$params'; var res = await MemberHttp.memberInfo(params: params); if (res['status']) { - print(res['data']); + memberInfo.value = res['data']; } + return res; + } + + // 获取用户状态 + Future> 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; } } diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index 0cd02c55..eed507a2 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -1,7 +1,10 @@ import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:flutter/material.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/utils/utils.dart'; class MemberPage extends StatefulWidget { const MemberPage({super.key}); @@ -10,11 +13,379 @@ class MemberPage extends StatefulWidget { State createState() => _MemberPageState(); } -class _MemberPageState extends State { +class _MemberPageState extends State + with SingleTickerProviderStateMixin { 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 Widget build(BuildContext context) { - return Scaffold(); + return Scaffold( + primary: true, + body: ExtendedNestedScrollView( + controller: _extendNestCtr, + headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { + return [ + 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('发消息'), + ) + ], + ) + ], + ), + ), + ], + ), + ); } } diff --git a/lib/pages/searchPanel/widgets/user_panel.dart b/lib/pages/searchPanel/widgets/user_panel.dart index fa81a98a..471b8c98 100644 --- a/lib/pages/searchPanel/widgets/user_panel.dart +++ b/lib/pages/searchPanel/widgets/user_panel.dart @@ -1,10 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:get/get.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/utils/utils.dart'; Widget searchUserPanel(BuildContext context, ctr, list) { TextStyle style = TextStyle( fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, color: Theme.of(context).colorScheme.outline); + return ListView.builder( controller: ctr!.scrollController, addAutomaticKeepAlives: false, @@ -12,17 +15,22 @@ Widget searchUserPanel(BuildContext context, ctr, list) { itemCount: list!.length, itemBuilder: (context, index) { var i = list![index]; + String heroTag = Utils.makeHeroTag(i!.mid); return InkWell( - onTap: () {}, + onTap: () => Get.toNamed('/member?mid=${i.mid}', + arguments: {'heroTag': heroTag, 'face': i.upic}), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), child: Row( children: [ - NetworkImgLayer( - width: 42, - height: 42, - src: i.upic, - type: 'avatar', + Hero( + tag: heroTag, + child: NetworkImgLayer( + width: 42, + height: 42, + src: i.upic, + type: 'avatar', + ), ), const SizedBox(width: 10), Column( diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index b11da53e..1b7a2b6a 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -360,62 +360,80 @@ class _VideoInfoState extends State with TickerProviderStateMixin { height: 26, color: Theme.of(context).dividerColor.withOpacity(0.1), ), - Row( - children: [ - NetworkImgLayer( - type: 'avatar', - src: !widget.loadingStatus - ? widget.videoDetail!.owner!.face - : videoItem['owner'].face, - width: 38, - height: 38, - fadeInDuration: Duration.zero, - fadeOutDuration: Duration.zero, - ), - 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(), + GestureDetector( + onTap: () { + int mid = !widget.loadingStatus + ? widget.videoDetail!.owner!.mid + : videoItem['owner'].mid; + String face = !widget.loadingStatus + ? widget.videoDetail!.owner!.face + : videoItem['owner'].face; + Get.toNamed('/member?mid=$mid', arguments: { + 'face': face, + 'heroTag': (mid + 99).toString() + }); + }, + child: Row( + children: [ + Hero( + tag: videoItem['owner'].mid + 99, + child: NetworkImgLayer( + type: 'avatar', + src: !widget.loadingStatus + ? widget.videoDetail!.owner!.face + : videoItem['owner'].face, + width: 38, + height: 38, + fadeInDuration: Duration.zero, + fadeOutDuration: Duration.zero, ), ), - ), - ], + 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( height: 26, color: Theme.of(context).dividerColor.withOpacity(0.1), diff --git a/lib/pages/video/detail/reply/view.dart b/lib/pages/video/detail/reply/view.dart index 16489ef2..1c038ad9 100644 --- a/lib/pages/video/detail/reply/view.dart +++ b/lib/pages/video/detail/reply/view.dart @@ -128,115 +128,112 @@ class _VideoReplyPanelState extends State _videoReplyController.currentPage = 0; return await _videoReplyController.queryReplyList(); }, - child: Scaffold( - resizeToAvoidBottomInset: false, - body: Stack( - children: [ - CustomScrollView( - controller: _videoReplyController.scrollController, - key: const PageStorageKey('评论'), - slivers: [ - const SliverToBoxAdapter(child: SizedBox(height: 12)), - FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - Map data = snapshot.data as Map; - if (data['status']) { - // 请求成功 - return Obx( - () => SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - if (index == - _videoReplyController.replyList.length) { - return Container( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context) - .padding - .bottom), - height: - MediaQuery.of(context).padding.bottom + - 100, - child: Center( - child: Obx(() => Text( - _videoReplyController.noMore.value)), - ), - ); - } else { - return ReplyItem( - replyItem: _videoReplyController - .replyList[index], - showReplyRow: true, - replyLevel: replyLevel); - } - }, - childCount: - _videoReplyController.replyList.length + 1, - ), + child: Stack( + children: [ + CustomScrollView( + controller: _videoReplyController.scrollController, + key: const PageStorageKey('评论'), + slivers: [ + const SliverToBoxAdapter(child: SizedBox(height: 12)), + FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + Map data = snapshot.data as Map; + if (data['status']) { + // 请求成功 + return Obx( + () => SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index == + _videoReplyController.replyList.length) { + return Container( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context) + .padding + .bottom), + height: + MediaQuery.of(context).padding.bottom + + 100, + child: Center( + child: Obx(() => Text( + _videoReplyController.noMore.value)), + ), + ); + } else { + return ReplyItem( + replyItem: + _videoReplyController.replyList[index], + showReplyRow: true, + replyLevel: replyLevel); + } + }, + childCount: + _videoReplyController.replyList.length + 1, ), - ); - } else { - // 请求错误 - return HttpError( - errMsg: data['msg'], - fn: () => setState(() {}), - ); - } + ), + ); } else { - // 骨架屏 - return SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - return const VideoReplySkeleton(); - }, childCount: 5), + // 请求错误 + return HttpError( + errMsg: data['msg'], + fn: () => setState(() {}), ); } - }, - ) - ], - ), - Positioned( - bottom: MediaQuery.of(context).padding.bottom + 14, - right: 14, - child: SlideTransition( - position: Tween( - 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'])} - }, + } else { + // 骨架屏 + return SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + return const VideoReplySkeleton(); + }, childCount: 5), ); - }, - tooltip: '发表评论', - child: const Icon(Icons.reply), - ), + } + }, + ) + ], + ), + Positioned( + bottom: MediaQuery.of(context).padding.bottom + 14, + right: 14, + child: SlideTransition( + position: Tween( + 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), ), ), - ], - ), + ), + ], ), ); } diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index 2ea5f5c6..390a2420 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -46,16 +46,19 @@ class ReplyItem extends StatelessWidget { ); } - Widget lfAvtar(context) { + Widget lfAvtar(context, heroTag) { return Container( margin: const EdgeInsets.only(top: 5), child: Stack( children: [ - NetworkImgLayer( - src: replyItem!.member!.avatar, - width: 34, - height: 34, - type: 'avatar', + Hero( + tag: heroTag, + child: NetworkImgLayer( + src: replyItem!.member!.avatar, + width: 34, + height: 34, + type: 'avatar', + ), ), if (replyItem!.member!.officialVerify != null && replyItem!.member!.officialVerify!['type'] == 0) @@ -87,16 +90,18 @@ class ReplyItem extends StatelessWidget { } Widget content(context) { + String heroTag = Utils.makeHeroTag(replyItem!.mid); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 头像、昵称 GestureDetector( - // onTap: () => - // Get.toNamed('/member/${reply.userName}', parameters: { - // 'memberAvatar': reply.avatar, - // 'heroTag': reply.userName + heroTag, - // }), + onTap: () { + Get.toNamed('/member?mid=${replyItem!.mid}', arguments: { + 'face': replyItem!.member!.avatar!, + 'heroTag': heroTag + }); + }, child: SizedBox( width: double.infinity, child: Stack( @@ -105,7 +110,7 @@ class ReplyItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - lfAvtar(context), + lfAvtar(context, heroTag), const SizedBox(width: 12), Text( replyItem!.member!.uname!, @@ -367,9 +372,16 @@ class ReplyItemRow extends StatelessWidget { color: Theme.of(context).colorScheme.primary, ), recognizer: TapGestureRecognizer() - ..onTap = () => { - print('跳转至用户主页'), - }, + ..onTap = () { + 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) WidgetSpan( @@ -521,9 +533,13 @@ InlineSpan buildContent(BuildContext context, content) { color: Theme.of(context).colorScheme.primary, ), recognizer: TapGestureRecognizer() - ..onTap = () => { - print('跳转至用户主页'), - }, + ..onTap = () { + String heroTag = Utils.makeHeroTag(value); + Get.toNamed( + '/member?mid=$value', + arguments: {'face': '', 'heroTag': heroTag}, + ); + }, ), ); });