2026-07-01 18:25:58 +08:00

1069 lines
53 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<DynamicDetailPage>
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<ChatVibeDynamicContentManager>(
context,
listen: false,
).updateShowEmojiState(false, () {});
Provider.of<ChatVibeDynamicContentManager>(context, listen: false).updateShowGiftState(
false,
() {
setState(() {});
},
);
}
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (Provider.of<ChatVibeDynamicContentManager>(context, listen: false).showEmoji) {
Provider.of<ChatVibeDynamicContentManager>(
context,
listen: false,
).updateShowEmojiState(false, () {
setState(() {});
});
return false;
}
if (Provider.of<ChatVibeDynamicContentManager>(context, listen: false).showGift) {
Provider.of<ChatVibeDynamicContentManager>(
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<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,
);
},
),
],
],
),
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<String> 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<String> 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<ChatVibeDynamicContentManager>(
context,
listen: false,
).updateShowEmojiState(false, () {
setState(() {});
});
Provider.of<ChatVibeDynamicContentManager>(
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<ChatVibeDynamicContentManager>(
context,
listen: false,
).updateRootComment(res, () {
setState(() {});
});
} else {
Provider.of<ChatVibeDynamicContentManager>(
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;
}
}