import 'dart:convert'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:aslan/chatvibe_core/utilities/at_dialog_utils.dart'; import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; import 'package:aslan/chatvibe_data/sources/repositories/dynamic_repository_imp.dart'; import 'package:provider/provider.dart'; import 'package:readmore/readmore.dart'; import 'package:aslan/app_localizations.dart'; import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; import 'package:aslan/chatvibe_ui/components/at_tts.dart'; import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; import 'package:aslan/chatvibe_domain/models/res/dynamic_list_res.dart'; import 'package:aslan/chatvibe_managers/dynamic_content_manager.dart'; import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; import 'package:aslan/chatvibe_ui/widgets/dynamic/comment/comment_input.dart'; import 'package:aslan/chatvibe_features/index/main_route.dart'; import 'package:aslan/chatvibe_features/dynamic/detail/comment/dynamic_comment_page.dart'; import 'package:aslan/chatvibe_features/dynamic/detail/gift/dynamic_comment_gift_page.dart'; import 'package:aslan/chatvibe_features/dynamic/detail/like/dynamic_comment_like_page.dart'; import '../../../chatvibe_core/at_lk_event_bus.dart'; class DynamicDetailPage extends StatefulWidget { String id = ""; String canGoUserInfo = ""; DynamicDetailPage(this.id, this.canGoUserInfo); @override _DynamicDetailPageState createState() => _DynamicDetailPageState(); } class _DynamicDetailPageState extends State with SingleTickerProviderStateMixin { late TabController _tabController; Records? detail; String? toUserId; num? rootCommentId; String? commentId; String cName = ""; // 添加滚动控制器 final ScrollController _scrollController = ScrollController(); double _opacity = 0.0; @override void initState() { super.initState(); _tabController = TabController(length: 3, vsync: this); _tabController.addListener(_handleTabSelection); // 监听滚动 _scrollController.addListener(() { if (_scrollController.hasClients) { final offset = _scrollController.offset; // 当滚动到一定位置时,头像和昵称应该完全显示在AppBar上 // 这里计算透明度,可以根据需要调整 final newOpacity = (offset / 150).clamp(0.0, 1.0); if (newOpacity != _opacity) { setState(() { _opacity = newOpacity; }); } } }); _loadData(); } _handleTabSelection() { if (!_tabController.indexIsChanging) { Provider.of( context, listen: false, ).updateShowEmojiState(false, () {}); Provider.of(context, listen: false).updateShowGiftState( false, () { setState(() {}); }, ); } } @override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async { if (Provider.of(context, listen: false).showEmoji) { Provider.of( context, listen: false, ).updateShowEmojiState(false, () { setState(() {}); }); return false; } if (Provider.of(context, listen: false).showGift) { Provider.of( context, listen: false, ).updateShowGiftState(false, () { setState(() {}); }); return false; } return true; }, child: GestureDetector( child: Stack( children: [ Image.asset( "atu_images/room/at_icon_room_settig_bg.png", width: ScreenUtil().screenWidth, height: ScreenUtil().screenHeight, fit: BoxFit.fill, ), Scaffold( backgroundColor: Colors.transparent, resizeToAvoidBottomInset: true, appBar: ChatVibeStandardAppBar( title: "", actions: [ if (_opacity > 0.5) ...[ SizedBox(width: 45.w), head( url: detail?.userAvatar ?? "", width: 50.w, height: 50.w, ), SizedBox(width: 8.w), Container( constraints: BoxConstraints( maxWidth: 135.w, maxHeight: 22.w, ), child: chatvibeNickNameText( maxWidth: 135.w, detail?.userProfile?.userNickname ?? "", fontSize: 16.sp, textColor: Colors.black, fontWeight: FontWeight.w600, type: detail?.userProfile?.getVIP()?.name ?? "", needScroll: (detail ?.userProfile ?.userNickname ?.characters .length ?? 0) > 13, ), ), SizedBox(width: 8.w), Spacer(), detail?.userId != AccountStorage().getCurrentUser()?.userProfile?.id ? ((detail?.subscription ?? false) ? Container() : ATDebounceWidget( child: Container( alignment: AlignmentDirectional.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12.w), gradient: LinearGradient( colors: [ Color(0xff7726FF), Color(0xffC670FF), ], ), ), width: 68.w, height: 23.w, child: text( ATAppLocalizations.of(context)!.follow, textColor: Colors.white, fontWeight: FontWeight.w600, fontSize: 13.sp, ), ), onTap: () { AccountRepository() .followUser(detail?.userId ?? "") .then((result) { detail?.setSubscription(true); setState(() {}); }); }, )) : Container(), ATDebounceWidget( child: Container( child: Icon( Icons.more_vert, color: Colors.black45, size: 18.w, ), padding: EdgeInsetsDirectional.all(3.w), ), onTap: () { ATDialogUtils.revealDynamicCommentOptDialog( context, reportCallback: detail?.userId != AccountStorage() .getCurrentUser() ?.userProfile ?.id ? () { ATNavigatorUtils.push( context, "${MainRoute.report}?type=dynamic&tageId=${detail?.dynamicId}", replace: false, ); } : null, deleteCallback: detail?.userId == AccountStorage() .getCurrentUser() ?.userProfile ?.id || (Provider.of( context, listen: false, ).userIdentity?.admin ?? false) ? () { SmartDialog.show( tag: "showConfirmDialog", alignment: Alignment.center, debounce: true, animationType: SmartAnimationType.fade, builder: (_) { return MsgDialog( title: ATAppLocalizations.of( context, )!.tips, msg: ATAppLocalizations.of( context, )!.deleteDynamicTips, btnText: ATAppLocalizations.of( context, )!.confirm, leftConfirm: true, isBtnBgFlip: true, onEnsure: () { _deleteDynamic( detail?.dynamicId ?? "", ); }, ); }, ); } : null, ); }, ), ], ], ), body: SafeArea( top: false, child: Stack( alignment: Alignment.bottomCenter, children: [ NestedScrollView( controller: _scrollController, headerSliverBuilder: (context, innerBoxIsScrolled) { return [ SliverToBoxAdapter( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ SizedBox(width: 10.w), ATDebounceWidget( child: netImage( url: detail?.userProfile?.userAvatar ?? "", shape: BoxShape.circle, height: 45.w, width: 45.w, ), onTap: () { if (widget.canGoUserInfo == "true") { ATNavigatorUtils.push( context, "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == detail?.userProfile?.id}&tageId=${detail?.userProfile?.id}", ); } }, ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( spacing: 3.w, children: [ Container( margin: EdgeInsetsDirectional.only( start: 5.w, ), child: chatvibeNickNameText( maxWidth: 95.w, detail?.userProfile?.userNickname ?? "", fontSize: 15.sp, textColor: Colors.black, fontWeight: FontWeight.w400, type: detail?.userProfile?.getVIP()?.name ?? "", needScroll: (detail ?.userProfile ?.userNickname ?.characters .length ?? 0) > 10, ) ), Container( width: (detail?.userProfile?.age ?? 0) > 999 ? 58.w : 48.w, height: 24.w, decoration: BoxDecoration( image: DecorationImage( image: AssetImage( detail ?.userProfile ?.userSex == 0 ? "atu_images/login/at_icon_sex_woman_bg.png" : "atu_images/login/at_icon_sex_man_bg.png", ), ), ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ xb( detail ?.userProfile ?.userSex, ), text( "${detail?.userProfile?.age ?? 0}", textColor: Colors.white, fontSize: 14.sp, fontWeight: FontWeight.w600, ), SizedBox(width: 3.w), ], ), ), getVIPBadge( detail?.userProfile ?.getVIP() ?.name, width: 45.w, height: 25.w, ), ], ), Container( alignment: AlignmentDirectional .centerStart, child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( mainAxisSize: MainAxisSize.min, children: [ SizedBox(width: 5.w), detail ?.userProfile ?.wearBadge ?.isNotEmpty ?? false ? SizedBox( height: 28.w, child: ListView.separated( scrollDirection: Axis.horizontal, shrinkWrap: true, itemCount: detail ?.userProfile ?.wearBadge ?.length ?? 0, itemBuilder: ( context, index, ) { return netImage( width: 28.w, height: 28.w, url: detail ?.userProfile ?.wearBadge?[index] .selectUrl ?? "", ); }, separatorBuilder: ( BuildContext context, int index, ) { return SizedBox( width: 5.w, ); }, ), ) : Container(), SizedBox(width: 5.w), ], ), ), ), ], ), ), SizedBox(width: 5.w), detail?.userId != AccountStorage() .getCurrentUser() ?.userProfile ?.id ? ((detail?.subscription ?? false) ? Container() : ATDebounceWidget( child: Container( alignment: AlignmentDirectional.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular( 12.w, ), gradient: LinearGradient( colors: [ Color(0xff7726FF), Color(0xffC670FF), ], ), ), width: 68.w, height: 23.w, child: text( ATAppLocalizations.of( context, )!.follow, textColor: Colors.white, fontWeight: FontWeight.w600, fontSize: 13.sp, ), ), onTap: () { AccountRepository() .followUser( detail?.userId ?? "", ) .then((result) { detail?.setSubscription( true, ); setState(() {}); }); }, )) : Container(), ATDebounceWidget( child: Container( child: Icon( Icons.more_vert, color: Colors.black45, size: 18.w, ), padding: EdgeInsetsDirectional.all(3.w), ), onTap: () { ATDialogUtils.revealDynamicCommentOptDialog( context, reportCallback: detail?.userId != AccountStorage() .getCurrentUser() ?.userProfile ?.id ? () { ATNavigatorUtils.push( context, "${MainRoute.report}?type=dynamic&tageId=${detail?.dynamicId}", replace: false, ); } : null, deleteCallback: detail?.userId == AccountStorage() .getCurrentUser() ?.userProfile ?.id || (Provider.of< ChatVibeUserProfileManager >( context, listen: false, ) .userIdentity ?.admin ?? false) ? () { SmartDialog.show( tag: "showConfirmDialog", alignment: Alignment.center, debounce: true, animationType: SmartAnimationType .fade, builder: (_) { return MsgDialog( title: ATAppLocalizations.of( context, )!.tips, msg: ATAppLocalizations.of( context, )!.deleteDynamicTips, btnText: ATAppLocalizations.of( context, )!.confirm, leftConfirm: true, isBtnBgFlip: true, onEnsure: () { _deleteDynamic( detail?.dynamicId ?? "", ); }, ); }, ); } : null, ); }, ), SizedBox(width: 3.w), ], ), SizedBox(height: 3.w), Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Expanded( child: Container( margin: EdgeInsetsDirectional.symmetric( horizontal: 10.w, ), child: ReadMoreText( detail?.content ?? "", trimLines: 3, // 收缩时显示的行数 trimMode: TrimMode.Line, style: TextStyle( fontFamily: "MyCustomFont", fontSize: 13.sp, color: Colors.black, fontWeight: FontWeight.w600, ), // 按行截断 trimCollapsedText: ATAppLocalizations.of( context, )!.showMore, // 收缩时按钮文字 trimExpandedText: ATAppLocalizations.of( context, )!.showLess, // 展开时按钮文字 moreStyle: TextStyle( color: ChatVibeTheme.primaryLight, fontFamily: "MyCustomFont", fontWeight: FontWeight.w600, ), lessStyle: TextStyle( color: ChatVibeTheme.primaryLight, fontFamily: "MyCustomFont", fontWeight: FontWeight.w600, ), ), ), ), ], ), SizedBox(height: 5.w), (detail?.pictures?.isNotEmpty ?? false) ? detail?.pictures?.length == 1 ? Container( margin: EdgeInsets.symmetric( horizontal: 10.w, ), constraints: BoxConstraints( maxWidth: ScreenUtil().screenWidth / 2, ), child: ATDebounceWidget( child: netImage( url: detail ?.pictures ?.first .resourceUrl ?? "", borderRadius: BorderRadius.circular(8.w), ), onTap: () { List ims = []; for (var pc in detail?.pictures ?? []) { String path = pc.resourceUrl; if (path.isNotEmpty) { ims.add(path); } } String encodedUrls = Uri.encodeComponent( jsonEncode(ims), ); ATNavigatorUtils.push( context, "${MainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=0", ); }, ), ) : Container( child: GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: detail ?.pictures ?.length == 1 ? 1 : (detail ?.pictures ?.length == 2 ? 2 : 3), // 每行2个 mainAxisSpacing: 10, crossAxisSpacing: 10, ), padding: EdgeInsets.all(10), itemCount: detail?.pictures?.length, itemBuilder: (context, index) { return ATDebounceWidget( child: netImage( url: detail ?.pictures?[index] .resourceUrl ?? "", borderRadius: BorderRadius.circular( 8.w, ), ), onTap: () { List ims = []; for (var pc in detail?.pictures ?? []) { String path = pc.resourceUrl; if (path.isNotEmpty) { ims.add(path); } } String encodedUrls = Uri.encodeComponent( jsonEncode(ims), ); ATNavigatorUtils.push( context, "${MainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=$index", ); }, ); }, ), ) : Container(), SizedBox(height: 5.w), Row( children: [ Container( margin: EdgeInsetsDirectional.only( start: 10.w, ), alignment: AlignmentDirectional.centerStart, child: text( ATMDateUtils.formatMessageTime( context, DateTime.fromMillisecondsSinceEpoch( detail?.createTime ?? 0, ), ), textColor: Colors.grey, fontSize: 12.sp, ), ), Spacer(), ], ), SizedBox(height: 5.w), ], ), ), SliverPersistentHeader( pinned: true, // TabBar 是否固定 delegate: _SliverAppBarDelegate( Container( color: Colors.white, // 背景色 child: TabBar( controller: _tabController, isScrollable: false, indicatorSize: TabBarIndicatorSize.tab, tabs: [ _buildTab( 0, "atu_images/dynamic/at_icon_reply.png", "${ATAppLocalizations.of(context)!.comment}(${detail?.commentStrQuantity ?? 0})", true, ), _buildTab( 1, (detail?.like ?? false) ? "atu_images/dynamic/at_icon_like_red.png" : "atu_images/dynamic/at_icon_like.png", "${ATAppLocalizations.of(context)!.like}(${detail?.likeStrQuantity ?? 0})", !(detail?.like ?? false), ), _buildTab( 2, "atu_images/dynamic/at_icon_dy_gift2.png", "${ATAppLocalizations.of(context)!.gift}(${detail?.giftStrQuantity ?? 0})", false, ), ], dividerColor: Colors.transparent, labelStyle: TextStyle( fontSize: 16.sp, fontFamily: 'MyCustomFont', fontWeight: FontWeight.bold, ), unselectedLabelStyle: TextStyle( fontSize: 15.sp, fontFamily: 'MyCustomFont', color: Colors.grey, fontWeight: FontWeight.normal, ), indicator: BoxDecoration( gradient: LinearGradient( colors: [ Color(0xff7726FF).withOpacity(0.6), Color(0xff7726FF).withOpacity(0.1), ], ), ), labelPadding: EdgeInsets.zero, ), ), ), ), ]; }, body: Container( margin: EdgeInsets.only(bottom: 45.w), child: TabBarView( physics: NeverScrollableScrollPhysics(), controller: _tabController, children: [ DynamicCommentPage( widget.id, num.parse(detail?.commentStrQuantity ?? "0"), widget.canGoUserInfo, repayCall: ( cId, toUserId, rootCommentId, toUserName, ) { this.toUserId = toUserId; cName = toUserName; this.rootCommentId = rootCommentId; this.commentId = cId; setState(() {}); }, deleteCall: (count) { setState(() { toUserId = null; cName = ""; rootCommentId = null; commentId = null; num cont = int.parse( detail?.commentStrQuantity ?? "0", ) - count; detail?.setCommentStrQuantity("${cont}"); setState(() {}); }); }, ), DynamicCommentLikePage( widget.id, widget.canGoUserInfo, ), DynamicCommentGiftPage( widget.id, widget.canGoUserInfo, ), ], ), ), ), CommentInput( detail?.userId ?? "", widget.id, toUserName: cName, deleteCallBack: () { if (rootCommentId == null && toUserId == null && cName.isEmpty) { return; } setState(() { toUserId = null; cName = ""; rootCommentId = null; commentId = null; }); }, callBack: (tx) { comment(tx); }, sendGiftCallBack: (count) { int cont = int.parse(detail?.giftStrQuantity ?? "0") + count; detail?.setGiftStrQuantity("${cont}"); setState(() {}); }, ), ], ), ), ), ], ), onTap: () { Provider.of( context, listen: false, ).updateShowEmojiState(false, () { setState(() {}); }); Provider.of( context, listen: false, ).updateShowGiftState(false, () { setState(() {}); }); }, ), ); } void _deleteDynamic(String dynamicId) { DynamicRepositoryImp() .dynamicDelete(dynamicId) .then((result) { eventBus.fire(UpdateDynamicEvent()); ATNavigatorUtils.goBack(context); ATTts.show(ATAppLocalizations.of(context)!.deleteSuccessful); }) .catchError((e) {}); } void comment(String text) { DynamicRepositoryImp() .dynamicComment( text, widget.id, toUserId: toUserId, commentId: commentId, rootCommentId: rootCommentId, ) .then((res) { res.setUserNickname( AccountStorage().getCurrentUser()?.userProfile?.userNickname ?? "", ); res.setUserAvatar( AccountStorage().getCurrentUser()?.userProfile?.userAvatar ?? "", ); res.setUserSex( AccountStorage().getCurrentUser()?.userProfile?.userSex ?? 1, ); if (rootCommentId == null) { Provider.of( context, listen: false, ).updateRootComment(res, () { setState(() {}); }); } else { Provider.of( context, listen: false, ).updateChildComment(res, rootCommentId ?? 0, toUserId ?? "", () { setState(() {}); }); } toUserId = null; cName = ""; rootCommentId = null; commentId = null; FocusScope.of(context).unfocus(); int cont = int.parse(detail?.commentStrQuantity ?? "0") + 1; detail?.setCommentStrQuantity("${cont}"); setState(() {}); ATTts.show(ATAppLocalizations.of(context)!.replySucc); }); } Widget _buildTab( int index, String imagePath, String text, bool needSwitchImage, ) { bool isSelected = _tabController.index == index; return Tab( child: Row( mainAxisSize: MainAxisSize.min, children: [ if (isSelected) Image.asset( imagePath, // 原图 height: 20.w, ) else Image.asset( imagePath, // 这里可以使用灰色图片路径,或者使用颜色滤镜 height: 20.w, color: needSwitchImage ? Colors.grey : null, // 使用颜色滤镜将图片变为灰色 ), SizedBox(width: 3.w), Text( text, style: isSelected ? TextStyle( fontSize: 13.sp, fontWeight: FontWeight.w600, color: Colors.white, // 选中时文字颜色 ) : TextStyle( fontSize: 13.sp, fontWeight: FontWeight.normal, color: index == 2 ? ChatVibeTheme.primaryLight : Colors.grey, // 未选中时文字颜色 ), ), ], ), ); } void _loadData() { ATLoadingManager.exhibitOperation(); DynamicRepositoryImp() .dynamicDetails(widget.id) .then((result) { ATLoadingManager.veilRoutine(); detail = result; setState(() {}); }) .catchError((_) { eventBus.fire(UpdateDynamicEvent()); ATLoadingManager.veilRoutine(); ATNavigatorUtils.goBack(context); }); } } class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { _SliverAppBarDelegate(this._tabBar); final Widget _tabBar; @override double get minExtent => 35.w; @override double get maxExtent => 35.w; @override Widget build( BuildContext context, double shrinkOffset, bool overlapsContent, ) { return _tabBar; } @override bool shouldRebuild(_SliverAppBarDelegate oldDelegate) { return true; } }