yumi-flutter/lib/modules/user/profile/person_detail_page.dart
2026-05-21 18:55:14 +08:00

2523 lines
88 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_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_lk_event_bus.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_room_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/shared/business_logic/models/res/my_room_res.dart';
import 'package:yumi/shared/business_logic/models/res/room_res.dart'
hide WearBadge, UseProps, PropsResources;
import 'package:yumi/services/audio/rtc_manager.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 'package:yumi/ui_kit/widgets/room/cp/sc_cp_corner_label.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 String _cpProfileAssetBase = "sc_images/room/cp_profile";
static const int _wallPreviewItemCount = 9;
SCUserIdentityRes? userIdentity;
bool isFollow = false;
bool isLoading = true;
bool isBlacklistLoading = true;
bool isBlacklist = false;
bool _isBackgroundUpdating = false;
bool _isCreatedRoomLoading = false;
String? _createdRoomLookupKey;
_ProfileCreatedRoom? _createdRoom;
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 {
try {
var userCounterList = await SCAccountRepository().userCounter(userId);
if (!mounted) return;
counterMap = Map.fromEntries(
userCounterList.map(
(counter) => MapEntry(counter.counterType ?? "", counter),
),
);
setState(() {});
} catch (_) {}
}
void _applyCounterDelta(String type, int delta) {
final currentCounter = counterMap[type];
final currentQuantity = int.tryParse(currentCounter?.quantity ?? "0") ?? 0;
final nextQuantity = (currentQuantity + delta).clamp(0, 1 << 31);
counterMap = {
...counterMap,
type: (currentCounter ?? SCUserCounterRes(counterType: type)).copyWith(
quantity: nextQuantity.toString(),
),
};
}
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) {});
}
void _ensureCreatedRoomLoaded(SocialChatUserProfile? profile) {
final userId = (profile?.id ?? widget.tageId).trim();
final lookupKey = [
userId,
profile?.account?.trim() ?? "",
profile?.getID().trim() ?? "",
profile?.inRoomId?.trim() ?? "",
].join("|");
if (userId.isEmpty || _createdRoomLookupKey == lookupKey) {
return;
}
_createdRoomLookupKey = lookupKey;
_createdRoom = null;
_isCreatedRoomLoading = true;
_loadCreatedRoom(profile: profile, userId: userId)
.then((room) {
if (!mounted || _createdRoomLookupKey != lookupKey) {
return;
}
setState(() {
_createdRoom = room;
_isCreatedRoomLoading = false;
});
})
.catchError((e) {
if (!mounted || _createdRoomLookupKey != lookupKey) {
return;
}
setState(() {
_createdRoom = null;
_isCreatedRoomLoading = false;
});
});
}
Future<_ProfileCreatedRoom?> _loadCreatedRoom({
required SocialChatUserProfile? profile,
required String userId,
}) async {
final repository = SCChatRoomRepository();
final searchKeys = <String>{
profile?.account?.trim() ?? "",
profile?.getID().trim() ?? "",
userId,
}..removeWhere((value) => value.isEmpty);
for (final key in searchKeys) {
try {
final rooms = await repository.searchRoom(key);
final ownedRoom = _ownedRoomFromSearchResults(rooms, profile, userId);
if (ownedRoom != null) {
return _ProfileCreatedRoom.fromSocialChatRoom(
ownedRoom,
ownerProfile: profile,
);
}
} catch (_) {
continue;
}
}
final currentRoomId = profile?.inRoomId?.trim() ?? "";
if (currentRoomId.isNotEmpty) {
try {
final room = await repository.specific(currentRoomId);
if ((room.userId ?? "").trim() == userId) {
return _ProfileCreatedRoom.fromMyRoom(room, ownerProfile: profile);
}
} catch (_) {
return null;
}
}
return null;
}
SocialChatRoomRes? _ownedRoomFromSearchResults(
List<SocialChatRoomRes> rooms,
SocialChatUserProfile? profile,
String userId,
) {
final account = profile?.account?.trim() ?? "";
final displayId = profile?.getID().trim() ?? "";
for (final room in rooms) {
if ((room.id ?? "").trim().isEmpty) {
continue;
}
final roomUserId = (room.userId ?? room.userProfile?.id ?? "").trim();
if (roomUserId == userId) {
return room;
}
if (roomUserId.isEmpty) {
final roomAccount = (room.roomAccount ?? "").trim();
if (roomAccount.isNotEmpty &&
(roomAccount == account || roomAccount == displayId)) {
return room;
}
}
}
return null;
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
Widget _buildHeaderAvatar(SocialChatUserProfileManager ref) {
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 _buildHeaderLongBadgeStrip(SocialChatUserProfileManager ref) {
return SizedBox(
width: ScreenUtil().screenWidth - 40.w,
child: SCUserBadgeStrip(
userId: ref.userProfile?.id ?? widget.tageId,
fallbackBadges:
ref.userProfile?.wearBadge
?.where((item) => item.use ?? false)
.toList() ??
const <WearBadge>[],
displayScope: SCUserBadgeDisplayScope.long,
height: 45.w,
badgeHeight: 20.w,
longBadgeWidth: 62.w,
spacing: 4.w,
wrap: true,
maxWrapRows: 2,
scrollLastWrapRow: true,
reserveSpace: false,
),
);
}
Widget _buildHeaderIntroduction(SocialChatUserProfileManager ref) {
final intro = (ref.userProfile?.autograph ?? "").trim();
final hobby = (ref.userProfile?.hobby ?? "").trim();
final localizations = SCAppLocalizations.of(context)!;
return Padding(
padding: EdgeInsets.symmetric(horizontal: 10.w),
child: Column(
children: [
_buildHeaderInfoRow(title: localizations.introduction, value: intro),
SizedBox(height: 4.w),
_buildHeaderInfoRow(title: localizations.hobby, value: hobby),
],
),
);
}
Widget _buildHeaderInfoRow({required String title, required String value}) {
final displayValue = value.isEmpty ? "--" : value;
final isRtl = Directionality.of(context) == TextDirection.rtl;
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
_showProfileTextDetailDialog(title: title, value: value);
},
child: SizedBox(
height: 25.w,
child: Row(
children: [
Expanded(
child: Text.rich(
TextSpan(
children: [
TextSpan(text: "$title: "),
TextSpan(text: displayValue),
],
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.start,
style: TextStyle(
color: Colors.white,
fontSize: 15.sp,
fontWeight: FontWeight.w400,
height: 1.2,
),
),
),
SizedBox(width: 4.w),
Icon(
isRtl ? Icons.keyboard_arrow_left : Icons.keyboard_arrow_right,
size: 18.w,
color: Colors.white.withValues(alpha: 0.72),
),
],
),
),
);
}
void _showProfileTextDetailDialog({
required String title,
required String value,
}) {
SmartDialog.dismiss(tag: "showProfileTextDetailDialog");
final displayValue = value.trim().isEmpty ? "--" : value.trim();
SmartDialog.show(
tag: "showProfileTextDetailDialog",
alignment: Alignment.center,
debounce: true,
animationType: SmartAnimationType.fade,
maskColor: Colors.black.withValues(alpha: 0.45),
builder: (_) {
return ConstrainedBox(
constraints: BoxConstraints(maxHeight: 260.w),
child: Container(
width: 275.w,
constraints: BoxConstraints(minHeight: 112.w),
padding: EdgeInsets.fromLTRB(12.w, 8.w, 12.w, 10.w),
decoration: BoxDecoration(
color: const Color(0xff0A342D),
border: Border.all(color: const Color(0xff18F2B1), width: 1.w),
borderRadius: BorderRadius.circular(10.w),
),
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Text.rich(
TextSpan(
children: [
TextSpan(text: "$title: "),
TextSpan(text: displayValue),
],
),
textAlign: TextAlign.justify,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
fontWeight: FontWeight.w400,
height: 16 / 12,
),
),
),
),
);
},
);
}
Widget _buildHeaderStats() {
return _buildGradientBorder(
width: 355.w,
height: 56.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(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
text(
value,
fontSize: 17.sp,
textColor: Colors.white,
fontWeight: FontWeight.bold,
lineHeight: 1,
),
SizedBox(height: 4.w),
text(
label,
fontSize: 14.sp,
textColor: const Color(0xffB1B1B1),
lineHeight: 1,
),
],
),
),
);
}
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: 590.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),
_buildHeaderLongBadgeStrip(ref),
SizedBox(height: 4.w),
_buildHeaderBadgeStrip(ref),
SizedBox(height: 6.w),
_buildHeaderIntroduction(ref),
SizedBox(height: 12.w),
_buildHeaderStats(),
SizedBox(height: 10.w),
],
),
),
],
),
);
}
Widget _buildProfileColumnContent(SocialChatUserProfile? profile) {
final cpRelation = _firstDisplayProfileRelation(
profile?.cpList,
"CP",
profile,
);
final closeFriendRelations = _profileCloseFriendRelations(profile);
final hasCloseFriendContent =
cpRelation != null || closeFriendRelations.isNotEmpty;
return Container(
width: double.infinity,
color: _profileBg,
padding: EdgeInsets.fromLTRB(10.w, 4.w, 10.w, 220.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildProfileGuildPlaceholder(),
if (hasCloseFriendContent) ...[
SizedBox(height: 12.w),
_buildProfileCloseFriendTitle(),
SizedBox(height: 12.w),
if (cpRelation != null) ...[
_buildProfileCpRelationCard(profile, cpRelation),
SizedBox(height: 8.w),
],
if (closeFriendRelations.isNotEmpty) ...[
_buildProfileCloseFriendGrid(closeFriendRelations, profile),
SizedBox(height: 18.w),
],
] else
SizedBox(height: 18.w),
_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 _buildProfileGuildPlaceholder() {
return ClipRRect(
borderRadius: BorderRadius.circular(8.w),
child: Image.asset(
"$_cpProfileAssetBase/sc_cp_profile_guild_placeholder.png",
width: double.infinity,
height: 64.w,
fit: BoxFit.fill,
),
);
}
Widget _buildProfileCloseFriendTitle() {
return Image.asset(
"$_cpProfileAssetBase/sc_cp_profile_close_friend_title.png",
width: 85.w,
height: 16.w,
fit: BoxFit.contain,
);
}
Widget _buildProfileCpRelationCard(
SocialChatUserProfile? profile,
CPRes relation,
) {
final owner = _profileRelationOwner(profile, relation);
final partner = _profileRelationPartner(profile, relation);
return SizedBox(
height: 114.w,
width: double.infinity,
child: Stack(
clipBehavior: Clip.none,
children: [
Positioned.fill(
child: Image.asset(
_profileCpLevelAsset(relation),
fit: BoxFit.fill,
),
),
Positioned(right: 10.w, top: 8.w, child: const SCCpCornerLabel()),
Positioned(
left: 39.w,
top: 26.w,
child: _buildProfileRelationAvatar(
_firstNonBlankProfileValue([
owner.avatarUrl,
profile?.userAvatar,
]),
60.w,
),
),
Positioned(
right: 38.w,
top: 26.w,
child: _buildProfileRelationAvatar(partner.avatarUrl ?? "", 60.w),
),
Positioned(
left: 142.w,
right: 142.w,
top: 25.w,
height: 22.w,
child: _buildProfileCpCardText(
_profileRelationDays(relation),
color: const Color(0xffFF0979),
fontSize: 20,
fontWeight: FontWeight.w700,
),
),
Positioned(
left: 142.w,
right: 142.w,
top: 48.w,
height: 14.w,
child: _buildProfileCpCardText(
"Days",
color: const Color(0xffFF0979),
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
Positioned(
left: 110.w,
right: 110.w,
top: 78.w,
height: 16.w,
child: _buildProfileCpCardText(
_profileRelationLevelText(relation),
color: const Color(0xffFEFEFF),
fontSize: 11,
fontWeight: FontWeight.w400,
),
),
],
),
);
}
Widget _buildProfileCpCardText(
String value, {
required Color color,
required double fontSize,
required FontWeight fontWeight,
}) {
return FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.center,
child: Text(
value,
maxLines: 1,
softWrap: false,
overflow: TextOverflow.visible,
textAlign: TextAlign.center,
textScaler: TextScaler.noScaling,
style: TextStyle(
color: color,
fontSize: fontSize.sp,
height: 1,
fontWeight: fontWeight,
decoration: TextDecoration.none,
),
),
);
}
Widget _buildProfileCloseFriendGrid(
List<CPRes> relations,
SocialChatUserProfile? profile,
) {
final spacing =
((ScreenUtil().screenWidth - 20.w - 113.w * 3) / 2)
.clamp(6.w, 12.w)
.toDouble();
return Wrap(
spacing: spacing,
runSpacing: 8.w,
children:
relations
.map(
(relation) => _buildProfileCloseFriendCard(relation, profile),
)
.toList(),
);
}
Widget _buildProfileCloseFriendCard(
CPRes relation,
SocialChatUserProfile? profile,
) {
final type = _normalizeProfileRelationType(relation.relationType);
final friend = _profileRelationPartner(profile, relation);
final isSister = type == "SISTERS";
return SizedBox(
width: 113.w,
height: 100.w,
child: Stack(
alignment: Alignment.topCenter,
children: [
Positioned.fill(
child: Image.asset(
isSister
? "$_cpProfileAssetBase/sc_cp_profile_sister_card_bg.png"
: "$_cpProfileAssetBase/sc_cp_profile_brother_card_bg.png",
fit: BoxFit.fill,
),
),
Positioned(
top: 2.w,
left: 0,
right: 0,
child: Text(
isSister ? "Sisters" : "Brother",
textAlign: TextAlign.center,
textScaler: TextScaler.noScaling,
style: TextStyle(
color:
isSister
? const Color(0xff7F49C8)
: const Color(0xff3474B8),
fontSize: 10.sp,
height: 1,
fontWeight: FontWeight.w500,
decoration: TextDecoration.none,
),
),
),
Positioned(
top: 20.w,
child: _buildProfileRelationAvatar(friend.avatarUrl ?? "", 51.w),
),
Positioned(
top: 66.w,
child: Container(
width: 27.w,
height: 12.w,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.72),
borderRadius: BorderRadius.circular(6.w),
),
child: Text(
_profileRelationLevelShortText(relation),
textAlign: TextAlign.center,
textScaler: TextScaler.noScaling,
style: TextStyle(
color:
isSister
? const Color(0xff7F49C8)
: const Color(0xff3474B8),
fontSize: 8.sp,
height: 1,
fontWeight: FontWeight.w600,
decoration: TextDecoration.none,
),
),
),
),
Positioned(
bottom: 9.w,
left: 0,
right: 0,
child: Text(
"${_profileRelationDays(relation)} Days",
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
textScaler: TextScaler.noScaling,
style: TextStyle(
color: const Color(0xff4C5C66),
fontSize: 12.sp,
height: 1,
fontWeight: FontWeight.w500,
decoration: TextDecoration.none,
),
),
),
],
),
);
}
Widget _buildProfileRelationAvatar(String avatarUrl, double size) {
final trimmedUrl = avatarUrl.trim();
return Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.white.withValues(alpha: 0.85)),
),
clipBehavior: Clip.antiAlias,
child:
trimmedUrl.isEmpty
? const SizedBox.shrink()
: netImage(
url: trimmedUrl,
width: size,
height: size,
fit: BoxFit.cover,
noDefaultImg: true,
loadingWidget: const SizedBox.shrink(),
errorWidget: const SizedBox.shrink(),
),
);
}
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,
);
}
CPRes? _firstDisplayProfileRelation(
List<CPRes>? relations,
String relationType,
SocialChatUserProfile? profile,
) {
for (final relation in relations ?? const <CPRes>[]) {
if (_normalizeProfileRelationType(relation.relationType) !=
relationType) {
continue;
}
if (_profileRelationHasPartner(profile, relation)) {
return relation;
}
}
return null;
}
List<CPRes> _profileCloseFriendRelations(SocialChatUserProfile? profile) {
return (profile?.closeFriendList ?? const <CPRes>[])
.where((relation) {
final type = _normalizeProfileRelationType(relation.relationType);
return (type == "BROTHER" || type == "SISTERS") &&
_profileRelationHasPartner(profile, relation);
})
.take(6)
.toList();
}
bool _profileRelationHasPartner(
SocialChatUserProfile? profile,
CPRes relation,
) {
final partner = _profileRelationPartner(profile, relation);
return _firstNonBlankProfileValue([
partner.userId,
partner.account,
partner.nickname,
partner.avatarUrl,
]).isNotEmpty;
}
_ProfileRelationUser _profileRelationOwner(
SocialChatUserProfile? profile,
CPRes relation,
) {
final profileId = profile?.id?.trim() ?? "";
if (profileId.isNotEmpty && profileId == (relation.cpUserId ?? "")) {
return _ProfileRelationUser(
userId: relation.cpUserId,
account: relation.cpAccount,
nickname: relation.cpUserNickname,
avatarUrl: relation.cpUserAvatar,
);
}
return _ProfileRelationUser(
userId: relation.meUserId ?? profile?.id,
account: relation.meAccount ?? profile?.account,
nickname: relation.meUserNickname ?? profile?.userNickname,
avatarUrl: relation.meUserAvatar ?? profile?.userAvatar,
);
}
_ProfileRelationUser _profileRelationPartner(
SocialChatUserProfile? profile,
CPRes relation,
) {
final profileId = profile?.id?.trim() ?? "";
if (profileId.isNotEmpty && profileId == (relation.cpUserId ?? "")) {
return _ProfileRelationUser(
userId: relation.meUserId,
account: relation.meAccount,
nickname: relation.meUserNickname,
avatarUrl: relation.meUserAvatar,
);
}
return _ProfileRelationUser(
userId: relation.cpUserId,
account: relation.cpAccount,
nickname: relation.cpUserNickname,
avatarUrl: relation.cpUserAvatar,
);
}
String _normalizeProfileRelationType(String? relationType) {
final value = relationType?.trim().toUpperCase() ?? "";
switch (value) {
case "BROTHER":
case "BROTHERS":
case "SWORN_BROTHER":
case "SWORN_BROTHERS":
return "BROTHER";
case "SISTER":
case "SISTERS":
return "SISTERS";
default:
return "CP";
}
}
String _profileCpLevelAsset(CPRes relation) {
return "$_cpProfileAssetBase/sc_cp_profile_cp_level_${_profileRelationLevel(relation)}.png";
}
int _profileRelationLevel(CPRes relation) {
final levelText = relation.levelText ?? "";
final levelMatch = RegExp(
r'(?:Lv\.?|Level)\s*(\d+)',
caseSensitive: false,
).firstMatch(levelText);
final fallbackMatch = RegExp(r'\d+').firstMatch(levelText);
final rawLevel =
int.tryParse(levelMatch?.group(1) ?? fallbackMatch?.group(0) ?? "") ??
1;
return rawLevel.clamp(1, 5).toInt();
}
String _profileRelationLevelText(CPRes relation) {
return _profileRelationLevelDisplayText(
_firstNonBlankProfileValue([relation.levelText, "Lv.1 Simple Love"]),
relation,
);
}
String _profileRelationLevelShortText(CPRes relation) {
return "Lv.${_profileRelationLevel(relation)}";
}
String _profileRelationDays(CPRes relation) {
return _firstNonBlankProfileValue([relation.days, "0"]);
}
String _profileRelationLevelDisplayText(String rawText, CPRes relation) {
final text = rawText.trim();
final level = _profileRelationLevel(relation);
if (text.isEmpty) {
return "Lv.1 Simple Love";
}
final compactLevelPattern = RegExp(
r'^(?:Lv\.?|Level)?\s*\d+$',
caseSensitive: false,
);
if (compactLevelPattern.hasMatch(text) && level == 1) {
return "Lv.1 Simple Love";
}
return text;
}
String _firstNonBlankProfileValue(List<String?> values) {
for (final value in values) {
final trimmed = value?.trim() ?? "";
if (trimmed.isNotEmpty) {
return trimmed;
}
}
return "";
}
Widget _buildBottomActions(SocialChatUserProfileManager ref) {
_ensureCreatedRoomLoaded(ref.userProfile);
final createdRoom = _createdRoom;
final hasRoom =
createdRoom != null &&
createdRoom.roomId.trim().isNotEmpty &&
!_isCreatedRoomLoading;
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, createdRoom),
),
Positioned(
left: 0,
right: 0,
bottom: actionRowBottom,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SCDebounceWidget(
debounceTime: const Duration(milliseconds: 800),
onTap: () {
final nextIsFollow = !isFollow;
SCAccountRepository().followUser(widget.tageId).then((v) {
if (!mounted) return;
if (v != true) return;
_applyCounterDelta("FANS", nextIsFollow ? 1 : -1);
isFollow = nextIsFollow;
setState(() {});
eventBus.fire(
SCUserRelationChangedEvent(
actorUserId:
AccountStorage()
.getCurrentUser()
?.userProfile
?.id ??
"",
targetUserId: widget.tageId,
isFollowing: nextIsFollow,
),
);
userCounter(widget.tageId);
});
},
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,
_ProfileCreatedRoom room,
) {
final displayName = room.displayName(ref.userProfile);
return Center(
child: SCDebounceWidget(
debounceTime: const Duration(milliseconds: 800),
onTap: () => _enterCreatedRoom(room),
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),
netImage(
url: resolveRoomCoverUrl(room.roomId, room.roomCover),
defaultImg: kRoomCoverDefaultImg,
width: 30.w,
height: 30.w,
borderRadius: BorderRadius.circular(15.w),
),
SizedBox(width: 8.w),
Expanded(
child: socialchatNickNameText(
displayName,
maxWidth: 190.w,
fontSize: 16.sp,
textColor: Colors.white,
fontWeight: FontWeight.w400,
type: ref.userProfile?.getVIP()?.name ?? "",
needScroll: displayName.characters.length > 16,
),
),
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),
],
),
),
),
);
}
void _enterCreatedRoom(_ProfileCreatedRoom room) {
final roomId = room.roomId.trim();
if (roomId.isEmpty) {
return;
}
Provider.of<RtcProvider>(
context,
listen: false,
).joinVoiceRoomSession(context, roomId, previewData: room.previewData);
}
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)}",
);
}
Future<void> _openEditProfile() async {
await SCNavigatorUtils.push(context, SCMainRoute.edit, replace: false);
if (!mounted) {
return;
}
final ref = Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
);
final currentProfile = AccountStorage().getCurrentUser()?.userProfile;
if (currentProfile?.id == widget.tageId) {
ref.syncCurrentUserProfile(currentProfile);
}
ref.getUserInfoById(widget.tageId);
}
@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: [
const Positioned.fill(child: ColoredBox(color: _profileBg)),
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: () async {
if (widget.isMe == "true") {
await _openEditProfile();
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(ref.userProfile),
],
),
)),
),
// 底部按钮区域
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: () {
final cpRelation =
ref.userProfile?.cpList?.isNotEmpty == true
? ref.userProfile?.cpList?.first
: null;
SCLoadingManager.show();
SCAccountRepository()
.cpRelationshipDismissApply(
ref.userProfile?.id ?? "",
relationType: cpRelation?.relationType ?? "CP",
)
.then((result) {
ref.refreshLoadedCpProfiles();
SmartDialog.dismiss(tag: "showPartWaysDialog");
SCTts.show(
SCAppLocalizations.of(
context,
)!.operationSuccessful,
);
SCLoadingManager.hide();
SCNavigatorUtils.popUntil(
context,
ModalRoute.withName(SCRoutes.home),
);
})
.catchError((e) {
SCLoadingManager.hide();
});
},
),
],
),
],
),
);
},
);
}
}
class _ProfileRelationUser {
const _ProfileRelationUser({
this.userId,
this.account,
this.nickname,
this.avatarUrl,
});
final String? userId;
final String? account;
final String? nickname;
final String? avatarUrl;
}
class _ProfileCreatedRoom {
const _ProfileCreatedRoom({
required this.roomId,
required this.roomCover,
required this.roomName,
this.previewData,
});
factory _ProfileCreatedRoom.fromSocialChatRoom(
SocialChatRoomRes room, {
SocialChatUserProfile? ownerProfile,
}) {
return _ProfileCreatedRoom(
roomId: room.id ?? "",
roomCover: room.roomCover ?? "",
roomName: room.roomName ?? "",
previewData: RoomEntryPreviewData.fromSocialChatRoom(
ownerProfile == null ? room : room.copyWith(userProfile: ownerProfile),
),
);
}
factory _ProfileCreatedRoom.fromMyRoom(
MyRoomRes room, {
SocialChatUserProfile? ownerProfile,
}) {
return _ProfileCreatedRoom(
roomId: room.id ?? "",
roomCover: room.roomCover ?? "",
roomName: room.roomName ?? "",
previewData: RoomEntryPreviewData.fromMyRoom(
room,
ownerProfile: ownerProfile,
),
);
}
final String roomId;
final String roomCover;
final String roomName;
final RoomEntryPreviewData? previewData;
String displayName(SocialChatUserProfile? fallbackProfile) {
final nickname = fallbackProfile?.userNickname?.trim() ?? "";
if (nickname.isNotEmpty) {
return nickname;
}
final name = roomName.trim();
if (name.isNotEmpty) {
return name;
}
return "";
}
}