yumi-flutter/lib/modules/user/profile/person_detail_page.dart
2026-05-16 04:39:07 +08:00

1816 lines
68 KiB
Dart
Raw 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 'dart:ui';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
import 'package:yumi/ui_kit/components/text/sc_text.dart';
import 'package:yumi/shared/tools/sc_room_utils.dart';
import 'package:yumi/shared/tools/sc_user_utils.dart';
import 'package:yumi/modules/index/main_route.dart';
import 'package:provider/provider.dart';
import 'package:tencent_cloud_chat_sdk/enum/conversation_type.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import 'package:yumi/app_localizations.dart';
import 'package:yumi/ui_kit/components/appbar/socialchat_appbar.dart';
import 'package:yumi/ui_kit/components/dialog/dialog_base.dart';
import 'package:yumi/ui_kit/components/sc_compontent.dart';
import 'package:yumi/ui_kit/components/sc_tts.dart';
import 'package:yumi/app/routes/sc_routes.dart';
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
import 'package:yumi/shared/tools/sc_loading_manager.dart';
import 'package:yumi/shared/tools/sc_pick_utils.dart';
import 'package:yumi/shared/business_logic/models/res/gift_res.dart';
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
import 'package:yumi/shared/data_sources/models/enum/sc_props_type.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_gift_repository_imp.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
import 'package:yumi/services/audio/rtm_manager.dart';
import 'package:yumi/services/auth/user_profile_manager.dart';
import 'package:yumi/services/general/sc_app_general_manager.dart';
import 'package:yumi/ui_kit/widgets/badge/sc_user_badge_strip.dart';
import '../../../app/constants/sc_screen.dart';
import '../../../shared/business_logic/models/res/sc_user_counter_res.dart';
import '../../../shared/business_logic/models/res/sc_user_identity_res.dart';
import '../../../ui_kit/widgets/id/sc_special_id_badge.dart';
import '../../chat/chat_route.dart';
class PersonDetailPage extends StatefulWidget {
final String isMe;
final String tageId;
const PersonDetailPage({Key? key, required this.isMe, required this.tageId})
: super(key: key);
@override
_PersonDetailPageState createState() => _PersonDetailPageState();
}
class _PersonDetailPageState extends State<PersonDetailPage> {
static const Color _profileBg = Color(0xff072121);
static const Color _cardBg = Color(0xff08251E);
static const Color _profileBorder = Color(0xffB2FBCC);
static const int _wallPreviewItemCount = 9;
SCUserIdentityRes? userIdentity;
bool isFollow = false;
bool isLoading = true;
bool isBlacklistLoading = true;
bool isBlacklist = false;
bool _isBackgroundUpdating = false;
Map<String, SCUserCounterRes> counterMap = {};
List<SocialChatGiftRes> giftWallList = [];
List<WearBadge> honorWallBadges = [];
// 添加滚动控制器
final ScrollController _scrollController = ScrollController();
double _opacity = 0.0;
@override
void initState() {
super.initState();
// 监听滚动
_scrollController.addListener(() {
if (_scrollController.hasClients) {
final offset = _scrollController.offset;
// 当滚动到一定位置时头像和昵称应该完全显示在AppBar上
// 这里计算透明度,可以根据需要调整
final newOpacity = (offset / 320).clamp(0.0, 1.0);
if (newOpacity != _opacity) {
setState(() {
_opacity = newOpacity;
});
}
}
});
Provider.of<SocialChatUserProfileManager>(context, listen: false)
.userProfile = null;
Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
).getUserInfoById(widget.tageId);
SCAccountRepository().userIdentity(userId: widget.tageId).then((v) {
userIdentity = v;
setState(() {});
});
if (widget.isMe != "true") {
SCAccountRepository()
.followCheck(widget.tageId)
.then((v) {
isFollow = v;
isLoading = false;
setState(() {});
})
.catchError((e) {
setState(() {
isLoading = false;
});
});
SCAccountRepository()
.blacklistCheck(widget.tageId)
.then((v) {
isBlacklist = v;
isBlacklistLoading = false;
setState(() {});
if (isBlacklist) {
SCTts.show(
SCAppLocalizations.of(context)!.thisUserHasBeenBlacklisted,
);
}
})
.catchError((e) {
setState(() {
isBlacklistLoading = false;
});
});
} else {
isBlacklist = false;
isBlacklistLoading = false;
}
userCounter(widget.tageId);
_loadHonorWall();
_loadGiftWall();
}
void userCounter(String userId) async {
counterMap.clear();
var userCounterList = await SCAccountRepository().userCounter(userId);
counterMap = Map.fromEntries(
userCounterList.map(
(counter) => MapEntry(counter.counterType ?? "", counter),
),
);
setState(() {});
}
void _loadGiftWall() {
SCGiftRepositoryImp()
.giftWall(widget.tageId)
.then((result) {
if (!mounted) return;
giftWallList = result.take(_wallPreviewItemCount).toList();
setState(() {});
})
.catchError((e) {});
}
void _loadHonorWall() {
final userId = widget.tageId.trim();
if (userId.isEmpty) {
return;
}
final currentUserId =
AccountStorage().getCurrentUser()?.userProfile?.id?.trim() ?? "";
final future =
currentUserId.isNotEmpty && currentUserId == userId
? SCAccountRepository().currentUserBadges()
: SCAccountRepository().otherUserBadges(userId);
future
.then((result) {
if (!mounted) return;
honorWallBadges = result.honorWallBadges;
setState(() {});
})
.catchError((e) {});
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
Widget _buildHeaderAvatar(SocialChatUserProfileManager ref) {
if (ref.userProfile?.cpList?.isNotEmpty ?? false) {
return SizedBox(
width: double.infinity,
height: 100.w,
child: Center(
child: SizedBox(
width: 170.w,
height: 100.w,
child: Stack(
alignment: Alignment.center,
children: [
Transform.translate(
offset: Offset(0, -5.w),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GestureDetector(
child: netImage(
url:
ref.userProfile?.cpList?.first.meUserAvatar ?? "",
defaultImg:
"sc_images/general/sc_icon_avar_defalt.png",
width: 56.w,
shape: BoxShape.circle,
),
onTap: () {
String encodedUrls = Uri.encodeComponent(
jsonEncode([
ref.userProfile?.cpList?.first.meUserAvatar,
]),
);
SCNavigatorUtils.push(
context,
"${SCMainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=0",
);
},
),
SizedBox(width: 32.w),
GestureDetector(
onTap: () {
SCNavigatorUtils.push(
context,
replace: true,
"${SCMainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == ref.userProfile?.cpList?.first.cpUserId}&tageId=${ref.userProfile?.cpList?.first.cpUserId}",
);
},
child: netImage(
url:
ref.userProfile?.cpList?.first.cpUserAvatar ?? "",
defaultImg:
"sc_images/general/sc_icon_avar_defalt.png",
width: 56.w,
shape: BoxShape.circle,
),
),
],
),
),
IgnorePointer(
child: Transform.translate(
offset: Offset(0, -15.w),
child: Image.asset(
"sc_images/person/sc_icon_send_cp_requst_dialog_head.png",
),
),
),
],
),
),
),
);
}
return Center(
child: GestureDetector(
child: head(
url: ref.userProfile?.userAvatar ?? "",
width: 88.w,
headdress: ref.userProfile?.getHeaddress()?.sourceUrl,
headdressCover: ref.userProfile?.getHeaddress()?.cover,
),
onTap: () {
String encodedUrls = Uri.encodeComponent(
jsonEncode([ref.userProfile?.userAvatar]),
);
SCNavigatorUtils.push(
context,
"${SCMainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=0",
);
},
),
);
}
Widget _buildHeaderName(SocialChatUserProfileManager ref) {
final nickname = ref.userProfile?.userNickname ?? "";
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
socialchatNickNameText(
nickname,
maxWidth: 240.w,
fontSize: 18.sp,
textColor: Colors.white,
fontWeight: FontWeight.w600,
type: ref.userProfile?.getVIP()?.name ?? "",
needScroll: nickname.characters.length > 18,
),
SizedBox(width: 3.w),
getVIPBadge(ref.userProfile?.getVIP()?.name, width: 45.w, height: 25.w),
],
);
}
Widget _buildHeaderMeta(SocialChatUserProfileManager ref) {
final idText = ref.userProfile?.getID() ?? "";
return Directionality(
textDirection: TextDirection.ltr,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
Clipboard.setData(ClipboardData(text: idText));
SCTts.show(SCAppLocalizations.of(context)!.copiedToClipboard);
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SCSpecialIdBadge(
idText: idText,
showAnimated: ref.userProfile?.hasSpecialId() ?? false,
assetPath: SCSpecialIdAssets.userIdLarge,
animationWidth: 80.w,
animationHeight: 32.w,
showTextBesideAnimated: true,
animatedTextSpacing: 0,
showAnimatedGradientText:
ref.userProfile?.shouldShowColoredSpecialIdText() ??
false,
animationTextStyle: TextStyle(
color: Colors.white,
fontSize: 16.sp,
fontWeight: FontWeight.w700,
),
normalTextStyle: TextStyle(
color: Colors.white.withValues(alpha: 0.72),
fontSize: 16.sp,
fontWeight: FontWeight.w400,
),
animationFit: BoxFit.contain,
),
SizedBox(width: 6.w),
Image.asset(
"sc_images/room/sc_icon_user_card_copy_id.png",
width: 15.w,
height: 15.w,
),
],
),
),
SizedBox(width: 9.w),
_buildHeaderGenderAge(ref),
SizedBox(width: 8.w),
_buildHeaderCountryFlag(ref),
],
),
);
}
Widget _buildHeaderGenderAge(SocialChatUserProfileManager ref) {
return Container(
width: (ref.userProfile?.age ?? 0) > 999 ? 58.w : 48.w,
height: 24.w,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(
ref.userProfile?.userSex == 0
? "sc_images/login/sc_icon_sex_woman_bg.png"
: "sc_images/login/sc_icon_sex_man_bg.png",
),
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
xb(ref.userProfile?.userSex),
text(
"${ref.userProfile?.age ?? 0}",
textColor: Colors.white,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
SizedBox(width: 3.w),
],
),
);
}
Widget _buildHeaderCountryFlag(SocialChatUserProfileManager ref) {
final countryName = ref.userProfile?.countryName ?? "";
String flag = "";
try {
flag =
Provider.of<SCAppGeneralManager>(
context,
listen: false,
).findCountryByName(countryName)?.nationalFlag ??
"";
} catch (_) {
flag = "";
}
if (flag.isEmpty) return const SizedBox.shrink();
return ClipRRect(
borderRadius: BorderRadius.circular(2.w),
child: netImage(
url: flag,
width: 24.w,
height: 16.w,
fit: BoxFit.cover,
noDefaultImg: true,
),
);
}
Widget _buildHeaderBadgeStrip(SocialChatUserProfileManager ref) {
return SCUserBadgeStrip(
userId: ref.userProfile?.id ?? widget.tageId,
fallbackBadges:
ref.userProfile?.wearBadge
?.where((item) => item.use ?? false)
.toList() ??
const <WearBadge>[],
height: 26.w,
badgeHeight: 24.w,
longBadgeWidth: 58.w,
spacing: 6.w,
maxBadges: 8,
reserveSpace: true,
);
}
Widget _buildHeaderIntroduction(SocialChatUserProfileManager ref) {
final intro = (ref.userProfile?.autograph ?? "").trim();
return Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 10.w),
child: Text.rich(
TextSpan(
children: [
const TextSpan(text: "Introduction: "),
TextSpan(text: intro.isEmpty ? "--" : intro),
],
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.start,
style: TextStyle(
color: Colors.white,
fontSize: 17.sp,
fontWeight: FontWeight.w400,
height: 1.28,
),
),
);
}
Widget _buildHeaderStats() {
return _buildGradientBorder(
width: 355.w,
height: 51.w,
radius: 8.w,
backgroundColor: _cardBg,
endAlpha: 0.42,
child: Row(
children: [
_buildHeaderStatItem(
value: "${counterMap["INTERVIEW"]?.quantity ?? 0}",
label: SCAppLocalizations.of(context)!.vistors,
onTap: () {
if (widget.isMe == "true") {
SCNavigatorUtils.push(context, SCMainRoute.vistors);
}
},
),
_buildHeaderStatsDivider(),
_buildHeaderStatItem(
value: "${counterMap["SUBSCRIPTION"]?.quantity ?? 0}",
label: SCAppLocalizations.of(context)!.follow,
onTap: () {
if (widget.isMe == "true") {
SCNavigatorUtils.push(context, SCMainRoute.follow);
}
},
),
_buildHeaderStatsDivider(),
_buildHeaderStatItem(
value: "${counterMap["FANS"]?.quantity ?? 0}",
label: SCAppLocalizations.of(context)!.fans,
onTap: () {
if (widget.isMe == "true") {
SCNavigatorUtils.push(context, SCMainRoute.fans);
}
},
),
],
),
);
}
Widget _buildHeaderStatItem({
required String value,
required String label,
required VoidCallback onTap,
}) {
return Expanded(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
text(
value,
fontSize: 17.sp,
textColor: Colors.white,
fontWeight: FontWeight.bold,
),
text(label, fontSize: 14.sp, textColor: Color(0xffB1B1B1)),
],
),
),
);
}
Widget _buildHeaderStatsDivider() {
return Container(
height: 28.w,
width: 1.w,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color.fromRGBO(255, 255, 255, 0),
Colors.white,
Color.fromRGBO(255, 255, 255, 0),
],
stops: [0, 0.4862, 1],
),
),
);
}
Widget _buildGradientBorder({
required double width,
required double height,
required double radius,
required Widget child,
Color backgroundColor = _cardBg,
double endAlpha = 0,
}) {
final borderRadius = BorderRadius.circular(radius);
return Container(
width: width,
height: height,
decoration: BoxDecoration(
borderRadius: borderRadius,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [_profileBorder, _profileBorder.withValues(alpha: endAlpha)],
),
),
padding: EdgeInsets.all(1.w),
child: Container(
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular((radius - 1.w).clamp(0, radius)),
),
child: child,
),
);
}
Widget _buildProfileDataCardBackground(PropsResources? _) {
return const ColoredBox(color: _profileBg);
}
PropsResources? _activeDataCard(SocialChatUserProfile? profile) {
final now = DateTime.now().millisecondsSinceEpoch;
for (final item in profile?.useProps ?? const <UseProps>[]) {
final resource = item.propsResources;
if (resource?.type != SCPropsType.DATA_CARD.name) {
continue;
}
final expireText = item.expireTime?.trim() ?? "";
final expireTime = int.tryParse(expireText);
if (expireText.isNotEmpty && (expireTime ?? 0) <= now) {
continue;
}
final source = resource?.sourceUrl?.trim() ?? "";
final cover = resource?.cover?.trim() ?? "";
if (source.isNotEmpty || cover.isNotEmpty) {
return resource;
}
}
return null;
}
Widget _buildProfileHeader(
SocialChatUserProfileManager ref,
List<PersonPhoto> backgroundPhotos,
PropsResources? dataCard,
) {
return SizedBox(
height: 520.w,
child: Stack(
children: [
SizedBox(
height: 300.w,
width: ScreenUtil().screenWidth,
child:
backgroundPhotos.isNotEmpty
? CarouselSlider(
options: CarouselOptions(
height: 300.w,
autoPlay: backgroundPhotos.length > 1,
enlargeCenterPage: false,
aspectRatio: 1 / 1,
enableInfiniteScroll: true,
autoPlayAnimationDuration: Duration(milliseconds: 800),
viewportFraction: 1,
onPageChanged: (index, reason) {
setState(() {});
},
),
items:
backgroundPhotos.map((item) {
return _buildProfileBackgroundTapTarget(
ref,
netImage(
url: item.url ?? "",
width: ScreenUtil().screenWidth,
height: 300.w,
fit: BoxFit.cover,
),
);
}).toList(),
)
: _buildProfileBackgroundTapTarget(
ref,
Image.asset(
'sc_images/person/sc_icon_profile_card_default_bg.png',
width: ScreenUtil().screenWidth,
height: 300.w,
fit: BoxFit.cover,
),
),
),
Positioned(
top: 250.w,
left: 0,
right: 0,
bottom: 0,
child: _buildProfileDataCardBackground(dataCard),
),
Positioned(
top: 210.w,
left: 0,
right: 0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildHeaderAvatar(ref),
SizedBox(height: 6.w),
_buildHeaderName(ref),
SizedBox(height: 5.w),
_buildHeaderMeta(ref),
SizedBox(height: 4.w),
_buildHeaderBadgeStrip(ref),
SizedBox(height: 6.w),
_buildHeaderIntroduction(ref),
SizedBox(height: 12.w),
_buildHeaderStats(),
SizedBox(height: 10.w),
],
),
),
],
),
);
}
Widget _buildProfileColumnContent() {
return Container(
width: double.infinity,
color: Colors.transparent,
padding: EdgeInsets.fromLTRB(10.w, 4.w, 10.w, 220.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildProfileSectionTitle(
"Wall Of Honors",
onTap: () {
SCNavigatorUtils.push(
context,
"${SCMainRoute.wallOfHonors}?tageId=${widget.tageId}",
);
},
),
SizedBox(height: 12.w),
_buildWallGrid(
itemCount:
honorWallBadges.isEmpty
? _wallPreviewItemCount
: honorWallBadges.length
.clamp(0, _wallPreviewItemCount)
.toInt(),
itemBuilder: (index) {
final badge =
index < honorWallBadges.length
? honorWallBadges[index]
: null;
return _buildHonorWallItem(badge);
},
),
SizedBox(height: 18.w),
_buildProfileSectionTitle(
"Gift Wall",
onTap: () {
SCNavigatorUtils.push(
context,
"${SCMainRoute.giftWall}?tageId=${widget.tageId}",
);
},
),
SizedBox(height: 12.w),
_buildWallGrid(
itemCount:
giftWallList.isEmpty
? _wallPreviewItemCount
: giftWallList.length,
itemBuilder: (index) {
final gift =
index < giftWallList.length ? giftWallList[index] : null;
return _buildGiftWallItem(gift);
},
),
],
),
);
}
Widget _buildProfileSectionTitle(String title, {VoidCallback? onTap}) {
final titleAsset =
title == "Wall Of Honors"
? "sc_images/person/sc_icon_profile_wall_of_honors_title.png"
: "sc_images/person/sc_icon_profile_gift_wall_title.png";
final titleHeight = 32.h;
final titleWidth = title == "Wall Of Honors" ? 98.w : 61.w;
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Row(
children: [
Expanded(
child: Align(
alignment: AlignmentDirectional.centerStart,
child: Image.asset(
titleAsset,
height: titleHeight,
width: titleWidth,
fit: BoxFit.contain,
errorBuilder:
(_, __, ___) => Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: _profileBorder,
fontSize: 19.sp,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.w800,
),
),
),
),
),
Icon(Icons.chevron_right_rounded, color: Colors.white, size: 28.w),
],
),
);
}
Widget _buildWallGrid({
required int itemCount,
required Widget Function(int index) itemBuilder,
}) {
final spacing =
((ScreenUtil().screenWidth - 20.w - 76.w * 4) / 3)
.clamp(8.w, 26.w)
.toDouble();
return Wrap(
spacing: spacing,
runSpacing: 17.w,
children: List.generate(itemCount, itemBuilder),
);
}
Widget _buildHonorWallItem(WearBadge? badge) {
return _buildWallItemFrame(
child:
badge == null
? Opacity(
opacity: 0.45,
child: Image.asset(
"sc_images/room/sc_icon_room_music_empty.png",
width: 38.w,
height: 38.w,
fit: BoxFit.contain,
),
)
: Padding(
padding: EdgeInsets.all(8.w),
child: netImage(
url: _badgeUrl(badge),
width: double.infinity,
height: double.infinity,
fit: BoxFit.contain,
noDefaultImg: true,
),
),
);
}
String _badgeUrl(WearBadge badge) => (badge.selectUrl ?? "").trim();
Widget _buildGiftWallItem(SocialChatGiftRes? gift) {
return _buildWallItemFrame(
child: Stack(
children: [
Positioned.fill(
child: Padding(
padding: EdgeInsets.all(8.w),
child:
gift == null
? Opacity(
opacity: 0.28,
child: Image.asset(
"sc_images/room/sc_icon_room_music_empty.png",
fit: BoxFit.contain,
),
)
: netImage(
url: gift.giftPhoto ?? "",
width: double.infinity,
height: double.infinity,
fit: BoxFit.contain,
noDefaultImg: true,
),
),
),
if (gift != null)
Positioned(
right: 5.w,
bottom: 4.w,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 1.w),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.35),
borderRadius: BorderRadius.circular(8.w),
),
child: text(
"x${gift.quantity ?? 0}",
fontSize: 10.sp,
textColor: Colors.white,
),
),
),
],
),
);
}
Widget _buildWallItemFrame({required Widget child}) {
return _buildGradientBorder(
width: 76.w,
height: 76.w,
radius: 8.w,
backgroundColor: _cardBg,
endAlpha: 0.62,
child: child,
);
}
Widget _buildBottomActions(SocialChatUserProfileManager ref) {
final hasRoom = (ref.userProfile?.inRoomId ?? "").isNotEmpty;
final safeBottom = MediaQuery.of(context).padding.bottom;
final bottomPadding = safeBottom + 32.w;
final actionRowBottom = bottomPadding;
final enterBottom = actionRowBottom + 36.w + 43.w;
final backgroundHeight = 93.w + safeBottom + 24.w;
final height = hasRoom ? enterBottom + 38.w : backgroundHeight;
return SizedBox(
height: height,
child: Stack(
clipBehavior: Clip.none,
children: [
Positioned(
left: 0,
right: 0,
bottom: 0,
child: SizedBox(height: backgroundHeight),
),
if (hasRoom)
Positioned(
left: 0,
right: 0,
bottom: enterBottom,
child: _buildEnterRoomPill(ref),
),
Positioned(
left: 0,
right: 0,
bottom: actionRowBottom,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SCDebounceWidget(
debounceTime: const Duration(milliseconds: 800),
onTap: () {
SCAccountRepository().followUser(widget.tageId).then((v) {
if (!mounted) return;
isFollow = !isFollow;
setState(() {});
});
},
child: _buildBottomButton(
width: 154.w,
icon:
isFollow
? "sc_images/person/sc_icon_profile_following.png"
: "sc_images/person/sc_icon_person_follow.png",
label:
isFollow
? SCAppLocalizations.of(context)!.following
: SCAppLocalizations.of(context)!.follow,
),
),
SizedBox(width: 20.w),
SCDebounceWidget(
debounceTime: const Duration(milliseconds: 800),
onTap: _openChat,
child: _buildBottomButton(
width: 165.w,
icon: "sc_images/person/sc_icon_person_tochat.png",
label: SCAppLocalizations.of(context)!.chat,
),
),
],
),
),
],
),
);
}
Widget _buildBottomButton({
required double width,
required String icon,
required String label,
}) {
return Container(
width: width,
height: 36.w,
decoration: BoxDecoration(
color: const Color(0xff0A342D),
borderRadius: BorderRadius.circular(22.w),
border: Border.all(color: const Color(0xff18F2B1), width: 1.w),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(icon, width: 28.w, height: 28.w, fit: BoxFit.contain),
SizedBox(width: 6.w),
Flexible(
child: text(
label,
fontSize: 16.sp,
textColor: Colors.white,
fontWeight: FontWeight.w400,
maxLines: 1,
),
),
],
),
);
}
Widget _buildEnterRoomPill(SocialChatUserProfileManager ref) {
return Center(
child: Container(
width: 343.w,
height: 38.w,
decoration: BoxDecoration(
color: const Color(0xff0A342D),
borderRadius: BorderRadius.circular(22.w),
border: Border.all(color: const Color(0xff18F2B1), width: 1.w),
),
child: Row(
children: [
SizedBox(width: 5.w),
head(url: ref.userProfile?.userAvatar ?? "", width: 30.w),
SizedBox(width: 8.w),
Expanded(
child: socialchatNickNameText(
ref.userProfile?.userNickname ?? "",
maxWidth: 190.w,
fontSize: 16.sp,
textColor: Colors.white,
fontWeight: FontWeight.w400,
type: ref.userProfile?.getVIP()?.name ?? "",
needScroll:
(ref.userProfile?.userNickname?.characters.length ?? 0) >
16,
),
),
SCDebounceWidget(
debounceTime: const Duration(milliseconds: 800),
onTap: () {
SCRoomUtils.goRoom(ref.userProfile?.inRoomId ?? "", context);
},
child: Container(
width: 69.w,
height: 27.w,
alignment: Alignment.center,
decoration: BoxDecoration(
color: const Color(0xff18F2B1),
borderRadius: BorderRadius.circular(22.w),
),
child: text(
SCAppLocalizations.of(context)!.enter,
fontSize: 16.sp,
textColor: Colors.black,
fontWeight: FontWeight.w500,
),
),
),
SizedBox(width: 8.w),
],
),
),
);
}
Widget _buildProfileBackgroundTapTarget(
SocialChatUserProfileManager ref,
Widget child,
) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap:
widget.isMe == "true"
? () => _pickAndUpdateProfileBackground(ref)
: null,
child: child,
);
}
void _pickAndUpdateProfileBackground(SocialChatUserProfileManager ref) {
if (_isBackgroundUpdating) {
return;
}
SCPickUtils.pickImage(
context,
(bool success, String url) {
final imageUrl = url.trim();
if (!success || imageUrl.isEmpty || !mounted) {
return;
}
_updateProfileBackground(ref, imageUrl);
},
aspectRatio: ScreenUtil().screenWidth / 300.w,
backOriginalFile: false,
);
}
Future<void> _updateProfileBackground(
SocialChatUserProfileManager ref,
String imageUrl,
) async {
if (_isBackgroundUpdating) {
return;
}
_isBackgroundUpdating = true;
SCLoadingManager.show(context: context);
try {
final updatedProfile = await SCAccountRepository().updateUserInfo(
backgroundPhotos: [imageUrl],
);
final returnedPhotos = updatedProfile.backgroundPhotos ?? [];
final hasUploadedPhoto = returnedPhotos.any(
(photo) => (photo.url ?? "").trim() == imageUrl,
);
final backgroundPhotos =
hasUploadedPhoto
? returnedPhotos
: [PersonPhoto(url: imageUrl, status: 1)];
final mergedProfile = updatedProfile.copyWith(
backgroundPhotos: backgroundPhotos,
);
ref.userProfile = mergedProfile;
ref.syncCurrentUserProfile(mergedProfile);
if (!mounted) {
return;
}
setState(() {});
SCTts.show(SCAppLocalizations.of(context)!.operationSuccessful);
} catch (e) {
if (mounted) {
SCTts.show("update fail $e");
}
} finally {
_isBackgroundUpdating = false;
SCLoadingManager.hide();
}
}
Future<void> _openChat() async {
final conversation = V2TimConversation(
type: ConversationType.V2TIM_C2C,
userID: widget.tageId,
conversationID: '',
);
final started = await Provider.of<RtmProvider>(
context,
listen: false,
).startConversation(conversation);
if (!started || !mounted) return;
final json = jsonEncode(conversation.toJson());
SCNavigatorUtils.push(
context,
"${SCChatRouter.chat}?conversation=${Uri.encodeComponent(json)}",
);
}
@override
Widget build(BuildContext context) {
return Consumer<SocialChatUserProfileManager>(
builder: (context, ref, child) {
final bool isProfileLoading = ref.userProfile == null;
List<PersonPhoto> backgroundPhotos = [];
backgroundPhotos =
(isProfileLoading
? <PersonPhoto>[]
: (ref.userProfile?.backgroundPhotos ?? []))
.where((t) => t.status == 1)
.toList();
final dataCard =
isProfileLoading ? null : _activeDataCard(ref.userProfile);
return Directionality(
textDirection:
window.locale.languageCode == "ar"
? TextDirection.rtl
: TextDirection.ltr,
child: SafeArea(
top: false,
child: Stack(
children: [
Positioned.fill(
child: Stack(
children: [
Column(
children: [
SizedBox(
height: 300.w,
width: ScreenUtil().screenWidth,
child:
backgroundPhotos.isNotEmpty
? netImage(
url: backgroundPhotos.first.url ?? "",
width: ScreenUtil().screenWidth,
height: 300.w,
fit: BoxFit.cover,
)
: Image.asset(
'sc_images/person/sc_icon_profile_card_default_bg.png',
width: ScreenUtil().screenWidth,
height: 300.w,
fit: BoxFit.cover,
),
),
const Expanded(child: ColoredBox(color: _profileBg)),
],
),
Positioned(
top: 250.w,
left: 0,
right: 0,
bottom: 0,
child: _buildProfileDataCardBackground(dataCard),
),
],
),
),
Scaffold(
extendBodyBehindAppBar: true,
resizeToAvoidBottomInset: false,
backgroundColor: Colors.transparent,
appBar: SocialChatStandardAppBar(
backButtonColor: Colors.white,
title: "",
leading: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
Navigator.pop(context);
},
child: Container(
width: width(50),
height: height(30),
alignment: AlignmentDirectional.centerStart,
padding: EdgeInsetsDirectional.only(start: width(15)),
child: Icon(
window.locale.languageCode == "ar"
? Icons.keyboard_arrow_right
: Icons.keyboard_arrow_left,
size: 28.w,
color: Colors.white,
),
),
),
actions: [
// 在AppBar中显示头像和昵称
if (_opacity > 0.5 && !isProfileLoading) ...[
SizedBox(width: 45.w),
head(
url: ref.userProfile?.userAvatar ?? "",
width: 50.w,
height: 50.w,
),
SizedBox(width: 8.w),
socialchatNickNameText(
maxWidth: 135.w,
textColor: Colors.white,
ref.userProfile?.userNickname ?? "",
fontSize: 16.sp,
fontWeight: FontWeight.w600,
type: ref.userProfile?.getVIP()?.name ?? "",
needScroll:
(ref
.userProfile
?.userNickname
?.characters
.length ??
0) >
13,
),
SizedBox(width: 8.w),
],
Spacer(),
Builder(
builder: (ct) {
return IconButton(
icon:
widget.isMe == "true"
? Image.asset(
"sc_images/person/sc_icon_edit_user_info2.png",
width: 22.w,
color: Colors.white,
)
: Icon(
Icons.more_horiz,
size: 22.w,
color: Colors.white,
),
onPressed: () {
if (widget.isMe == "true") {
SCNavigatorUtils.push(
context,
SCMainRoute.edit,
replace: false,
);
return;
}
SmartDialog.showAttach(
tag: "showUserInfoOptMenu",
targetContext: ct,
alignment: Alignment.bottomCenter,
maskColor: Colors.transparent,
animationType: SmartAnimationType.fade,
scalePointBuilder:
(selfSize) => Offset(selfSize.width, 10),
builder: (_) {
return GestureDetector(
child: Transform.translate(
offset: Offset(0, -10),
child: Container(
width: 163.w,
margin: EdgeInsetsDirectional.only(
end: 8.w,
),
decoration: BoxDecoration(
color: Colors.white30,
borderRadius: BorderRadius.circular(
4,
),
),
padding: EdgeInsets.symmetric(
horizontal: 8.w,
vertical: 5.w,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
child: text(
SCAppLocalizations.of(
context,
)!.report,
textColor: Colors.white,
fontSize: 12.sp,
),
onTap: () {
SmartDialog.dismiss(
tag: "showUserInfoOptMenu",
);
SCNavigatorUtils.push(
context,
"${SCMainRoute.report}?type=user&tageId=${ref.userProfile?.id}",
replace: false,
);
},
),
((!(userIdentity?.admin ?? false) &&
!(userIdentity
?.superAdmin ??
false)) &&
((Provider.of<
SocialChatUserProfileManager
>(
context,
listen: false,
).userIdentity?.admin ??
false)))
? GestureDetector(
child: Container(
margin: EdgeInsets.only(
top: 5.w,
),
child: text(
SCAppLocalizations.of(
context,
)!.userEditing,
letterSpacing: 0.1,
textColor: Colors.white,
fontSize: 12.sp,
),
),
onTap: () {
SmartDialog.dismiss(
tag:
"showUserInfoOptMenu",
);
SCNavigatorUtils.push(
context,
"${SCMainRoute.editingUserRoomAdmin}?type=User&profile=${Uri.encodeComponent(jsonEncode(ref.userProfile?.toJson()))}",
);
},
)
: Container(),
((ref.userIdentity?.admin ??
false) ||
(ref
.userIdentity
?.superFreightAgent ??
false)) &&
!((userIdentity?.admin ??
false) ||
(userIdentity
?.superFreightAgent ??
false))
? SizedBox(height: 5.w)
: Container(),
((ref.userIdentity?.admin ??
false) ||
(ref
.userIdentity
?.superFreightAgent ??
false)) &&
!((userIdentity?.admin ??
false) ||
(userIdentity
?.superFreightAgent ??
false))
? GestureDetector(
child: text(
letterSpacing: 0.1,
SCAppLocalizations.of(
context,
)!.becomeAgent,
textColor: Colors.white,
fontSize: 12.sp,
),
onTap: () {
SmartDialog.dismiss(
tag:
"showUserInfoOptMenu",
);
SCAccountRepository()
.teamCreate(
ref.userProfile
?.getID() ??
"",
);
},
)
: Container(),
(ref.userProfile?.isCpRelation ??
false)
? GestureDetector(
behavior:
HitTestBehavior.opaque,
child: Container(
padding: EdgeInsets.only(
top: 3.w,
),
child: text(
letterSpacing: 0.1,
SCAppLocalizations.of(
context,
)!.partWays,
textColor: Colors.white,
fontSize: 12.sp,
),
),
onTap: () {
SmartDialog.dismiss(
tag:
"showUserInfoOptMenu",
);
_showPartWaysDialog(ref);
},
)
: Container(),
isBlacklistLoading
? Container()
: GestureDetector(
behavior:
HitTestBehavior.opaque,
child: Container(
padding: EdgeInsets.only(
top: 3.w,
),
child: text(
letterSpacing: 0.1,
isBlacklist
? SCAppLocalizations.of(
context,
)!.removeFromBlacklist
: SCAppLocalizations.of(
context,
)!.moveToBlacklist,
textColor: Colors.white,
fontSize: 12.sp,
),
),
onTap: () {
if ((ref
.userProfile
?.isCpRelation ??
false)) {
SmartDialog.show(
tag:
"showConfirmDialog",
alignment:
Alignment.center,
debounce: true,
animationType:
SmartAnimationType
.fade,
builder: (_) {
return MsgDialog(
title:
SCAppLocalizations.of(
context,
)!.tips,
msg:
SCAppLocalizations.of(
context,
)!.youAreCurrentlyCPRelationshipPleaseDissolve,
btnText:
SCAppLocalizations.of(
context,
)!.confirm,
onEnsure: () {},
);
},
);
return;
}
SCAccountHelper.optBlacklist(
widget.tageId,
isBlacklist,
context,
(isOptBlacklist) {
isBlacklist =
isOptBlacklist;
setState(() {});
},
);
},
),
],
),
),
),
onTap: () {
SmartDialog.dismiss(
tag: "showUserInfoOptMenu",
);
},
);
},
);
},
);
},
),
],
),
body:
isBlacklist
? Container()
: (isProfileLoading || isBlacklistLoading
? const SizedBox.shrink()
: SingleChildScrollView(
controller: _scrollController,
physics: const BouncingScrollPhysics(),
child: Column(
children: [
_buildProfileHeader(
ref,
backgroundPhotos,
dataCard,
),
_buildProfileColumnContent(),
],
),
)),
),
// 底部按钮区域
isBlacklistLoading || isBlacklist || isProfileLoading
? Container()
: Positioned(
bottom: 0,
left: 0,
right: 0,
child:
widget.isMe == "true"
? Container()
: (isLoading
? Container()
: _buildBottomActions(ref)),
),
],
),
),
);
},
);
}
void _showPartWaysDialog(SocialChatUserProfileManager ref) {
SmartDialog.show(
tag: "showPartWaysDialog",
alignment: Alignment.center,
debounce: true,
animationType: SmartAnimationType.fade,
builder: (_) {
return Container(
height: 530.w,
width: ScreenUtil().screenWidth * 0.9,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(
"sc_images/person/sc_icon_send_cp_requst_dialog_bg.png",
),
fit: BoxFit.fill,
),
),
child: Column(
children: [
SizedBox(height: 58.w),
Container(
child: text(
SCAppLocalizations.of(context)!.partWays,
fontSize: 22.sp,
textColor: Color(0xffDB5872),
fontWeight: FontWeight.bold,
),
),
Transform.translate(
offset: Offset(0, -25),
child: Stack(
alignment: AlignmentDirectional.center,
children: [
PositionedDirectional(
top: 28.w,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
head(
url:
AccountStorage()
.getCurrentUser()
?.userProfile
?.userAvatar ??
"",
width: 90.w,
),
SizedBox(width: 15.w),
head(
url: ref.userProfile?.userAvatar ?? "",
width: 90.w,
),
],
),
),
Image.asset(
"sc_images/person/sc_icon_send_cp_requst_dialog_head2.png",
height: 120.w,
fit: BoxFit.fill,
),
],
),
),
Transform.translate(
offset: Offset(0, -23),
child: Container(
height: 25.w,
width: ScreenUtil().screenWidth * 0.6,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(
"sc_images/person/sc_icon_send_cp_requst_username_bg.png",
),
fit: BoxFit.fill,
),
),
child: Row(
children: [
socialchatNickNameText(
maxWidth: 100.w,
AccountStorage()
.getCurrentUser()
?.userProfile
?.userNickname ??
"",
fontSize: 10.sp,
fontWeight: FontWeight.w600,
type:
AccountStorage()
.getCurrentUser()
?.userProfile
?.getVIP()
?.name ??
"",
needScroll:
(AccountStorage()
.getCurrentUser()
?.userProfile
?.userNickname
?.characters
.length ??
0) >
8,
),
SizedBox(width: 20.w),
socialchatNickNameText(
maxWidth: 100.w,
ref.userProfile?.userNickname ?? "",
fontSize: 10.sp,
fontWeight: FontWeight.w600,
type: ref.userProfile?.getVIP()?.name ?? "",
needScroll:
(ref.userProfile?.userNickname?.characters.length ??
0) >
8,
),
],
),
),
),
Container(
padding: EdgeInsets.only(bottom: 12.w),
margin: EdgeInsets.symmetric(horizontal: 18.w),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(
"sc_images/person/sc_icon_send_cp_requst_dialog_content.png",
),
fit: BoxFit.fill,
),
),
child: Column(
children: [
Container(
margin: EdgeInsets.symmetric(
horizontal: 35.w,
).copyWith(top: 45.w),
child: text(
SCAppLocalizations.of(
context,
)!.areYouSureYouWantToPartWaysWithYourCP,
fontWeight: FontWeight.w500,
textColor: Color(0xffFF79A1),
maxLines: 3,
fontSize: 14.sp,
),
),
Container(
margin: EdgeInsets.symmetric(horizontal: 20.w),
alignment: AlignmentDirectional.center,
child: text(
SCAppLocalizations.of(context)!.partWaysTips,
fontSize: 10.w,
textColor: Color(0xffFE91B0),
maxLines: 6,
fontWeight: FontWeight.w500,
),
),
],
),
),
SizedBox(height: 10.w),
Row(
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
child: Container(
padding: EdgeInsets.only(top: 7.w),
alignment: AlignmentDirectional.center,
width: 130.w,
height: 48.w,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(
"sc_images/person/sc_icon_send_cp_requst_ok_bg.png",
),
fit: BoxFit.fill,
),
),
child: text(
SCAppLocalizations.of(context)!.cancel,
fontSize: 18.sp,
fontWeight: FontWeight.bold,
textColor: Color(0xffDB5872),
),
),
onTap: () {
SmartDialog.dismiss(tag: "showPartWaysDialog");
},
),
SizedBox(width: 25.w),
GestureDetector(
child: Container(
padding: EdgeInsets.only(top: 7.w),
alignment: AlignmentDirectional.center,
width: 130.w,
height: 48.w,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(
"sc_images/person/sc_icon_send_cp_requst_cancel_bg.png",
),
fit: BoxFit.fill,
),
),
child: text(
SCAppLocalizations.of(context)!.confirm,
fontSize: 18.sp,
fontWeight: FontWeight.bold,
textColor: Colors.white,
),
),
onTap: () {
SCLoadingManager.show();
SCAccountRepository()
.cpRelationshipDismissApply(ref.userProfile?.id ?? "")
.then((result) {
SmartDialog.dismiss(tag: "showPartWaysDialog");
SCTts.show(
SCAppLocalizations.of(
context,
)!.operationSuccessful,
);
SCLoadingManager.hide();
SCNavigatorUtils.popUntil(
context,
ModalRoute.withName(SCRoutes.home),
);
})
.catchError((e) {
SCLoadingManager.hide();
});
},
),
],
),
],
),
);
},
);
}
}