2076 lines
63 KiB
Dart
2076 lines
63 KiB
Dart
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:yumi/app_localizations.dart';
|
|
import 'package:yumi/main.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/data_sources/sources/local/user_manager.dart';
|
|
import 'package:yumi/modules/index/main_route.dart';
|
|
import 'package:yumi/services/audio/rtm_manager.dart';
|
|
import 'package:yumi/ui_kit/widgets/room/room_msg_input.dart';
|
|
import 'package:yumi/ui_kit/widgets/room/room_user_card_setting.dart';
|
|
import 'package:yumi/ui_kit/widgets/badge/sc_user_badge_strip.dart';
|
|
import 'package:marquee/marquee.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
|
|
import 'package:tencent_cloud_chat_sdk/enum/conversation_type.dart';
|
|
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
|
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
|
import 'package:yumi/shared/tools/sc_lk_dialog_util.dart';
|
|
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
|
import 'package:yumi/shared/tools/sc_room_utils.dart';
|
|
import 'package:yumi/shared/tools/sc_room_top_layer_guard.dart';
|
|
import 'package:yumi/shared/tools/sc_string_utils.dart';
|
|
import 'package:yumi/shared/tools/sc_cp_relation_level_utils.dart';
|
|
import 'package:yumi/shared/tools/sc_cp_relation_notice_utils.dart';
|
|
import 'package:yumi/shared/data_sources/models/enum/sc_props_type.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/sc_cp_rights_config_res.dart';
|
|
import 'package:yumi/shared/business_logic/models/res/room_user_card_res.dart'
|
|
as room_card;
|
|
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
|
import 'package:yumi/services/audio/rtc_manager.dart';
|
|
import 'package:yumi/services/auth/user_profile_manager.dart';
|
|
import 'package:yumi/modules/gift/gift_page.dart';
|
|
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
|
import 'package:yumi/ui_kit/widgets/id/sc_special_id_badge.dart';
|
|
import 'package:yumi/ui_kit/widgets/props/sc_data_card_resource_view.dart';
|
|
import 'package:yumi/ui_kit/widgets/room/cp/sc_cp_corner_label.dart';
|
|
import 'package:yumi/ui_kit/widgets/room/cp/room_cp_progress_dialog.dart';
|
|
|
|
import '../../../shared/data_sources/models/enum/sc_room_roles_type.dart';
|
|
import '../../../shared/business_logic/models/res/sc_user_identity_res.dart';
|
|
import '../../../modules/chat/chat_route.dart';
|
|
|
|
class RoomUserInfoCard extends StatefulWidget {
|
|
final String? userId;
|
|
|
|
const RoomUserInfoCard({super.key, this.userId});
|
|
|
|
@override
|
|
State<RoomUserInfoCard> createState() => _RoomUserInfoCardState();
|
|
}
|
|
|
|
class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|
static const String _leaveMicActionIconAsset =
|
|
"sc_images/room/sc_icon_room_user_card_leave_mic.png";
|
|
static const String _defaultProfileCardBackgroundAsset =
|
|
"sc_images/room/sc_bg_room_user_profile_card_default.png";
|
|
static const String _cpProfileAssetBase = "sc_images/room/cp_profile";
|
|
static const double _profileSheetHeightFactor = 0.54;
|
|
static const double _dataCardBackgroundTranslateFactor = 0.8;
|
|
|
|
SocialChatUserProfileManager? userProvider;
|
|
String roomId = "";
|
|
SCUserIdentityRes? userIdentity;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
userProvider = Provider.of<SocialChatUserProfileManager>(
|
|
context,
|
|
listen: false,
|
|
);
|
|
roomId =
|
|
Provider.of<RtcProvider>(
|
|
context,
|
|
listen: false,
|
|
).currenRoom?.roomProfile?.roomProfile?.id ??
|
|
"";
|
|
userProvider?.roomUserCard(roomId, widget.userId ?? "");
|
|
SCAccountRepository().userIdentity(userId: widget.userId).then((v) {
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
userIdentity = v;
|
|
setState(() {});
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
super.dispose();
|
|
userProvider?.userCardInfo = null;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final screenHeight = MediaQuery.of(context).size.height;
|
|
final defaultSheetHeight = screenHeight * _profileSheetHeightFactor;
|
|
|
|
return Consumer<SocialChatUserProfileManager>(
|
|
builder: (context, ref, child) {
|
|
final currentUserId =
|
|
AccountStorage().getCurrentUser()?.userProfile?.id;
|
|
final isSelf = widget.userId == currentUserId;
|
|
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
|
final currentMicIndex =
|
|
isSelf && currentUserId != null
|
|
? rtcProvider.userOnMaiInIndex(currentUserId)
|
|
: -1;
|
|
final canLeaveMic = currentMicIndex > -1;
|
|
|
|
if (ref.userCardInfo == null) {
|
|
return _buildLoadingSheet(defaultSheetHeight);
|
|
}
|
|
|
|
final profile = ref.userCardInfo?.userProfile;
|
|
final dataCard = _activeDataCard(ref.userCardInfo);
|
|
final sheetHeight = _resolvedProfileSheetHeight(screenHeight, profile);
|
|
|
|
return _buildProfileSheet(
|
|
context: context,
|
|
ref: ref,
|
|
rtcProvider: rtcProvider,
|
|
sheetHeight: sheetHeight,
|
|
dataCardBackgroundHeight: screenHeight,
|
|
dataCard: dataCard,
|
|
isSelf: isSelf,
|
|
currentMicIndex: currentMicIndex,
|
|
canLeaveMic: canLeaveMic,
|
|
currentUserId: currentUserId,
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildLoadingSheet(double sheetHeight) {
|
|
return SizedBox(
|
|
width: double.infinity,
|
|
height: sheetHeight,
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
|
|
child: Stack(
|
|
children: [
|
|
Positioned.fill(child: _buildDefaultSheetBackground()),
|
|
Center(
|
|
child: Container(
|
|
width: 55.w,
|
|
height: 55.w,
|
|
decoration: BoxDecoration(
|
|
color: Colors.black26,
|
|
borderRadius: BorderRadius.circular(8.w),
|
|
),
|
|
child: CupertinoActivityIndicator(color: Colors.white24),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
double _resolvedProfileSheetHeight(
|
|
double screenHeight,
|
|
SocialChatUserProfile? profile,
|
|
) {
|
|
final defaultSheetHeight = screenHeight * _profileSheetHeightFactor;
|
|
final hasCpRelation = _hasCpRelation(profile);
|
|
final hasCloseFriends = _closeFriendProfileData(profile).isNotEmpty;
|
|
var resolvedHeight = defaultSheetHeight;
|
|
if (!hasCpRelation) {
|
|
resolvedHeight -= 126.w;
|
|
}
|
|
if (!hasCloseFriends) {
|
|
resolvedHeight -= (hasCpRelation ? 52.w : 53.w);
|
|
}
|
|
final minHeight = screenHeight * 0.36;
|
|
return resolvedHeight < minHeight ? minHeight : resolvedHeight;
|
|
}
|
|
|
|
Widget _buildProfileSheet({
|
|
required BuildContext context,
|
|
required SocialChatUserProfileManager ref,
|
|
required RtcProvider rtcProvider,
|
|
required double sheetHeight,
|
|
required double dataCardBackgroundHeight,
|
|
required PropsResources? dataCard,
|
|
required bool isSelf,
|
|
required num currentMicIndex,
|
|
required bool canLeaveMic,
|
|
required String? currentUserId,
|
|
}) {
|
|
final bottomPadding = MediaQuery.of(context).padding.bottom;
|
|
final profile = ref.userCardInfo?.userProfile;
|
|
final canShowReport = currentUserId != widget.userId;
|
|
final canShowSetting =
|
|
canShowReport &&
|
|
ref.userCardInfo?.roomRole != SCRoomRolesType.HOMEOWNER.name &&
|
|
(rtcProvider.isFz() || rtcProvider.isGL());
|
|
|
|
final profileContent = _buildProfileContentLayer(
|
|
context: context,
|
|
ref: ref,
|
|
rtcProvider: rtcProvider,
|
|
profile: profile,
|
|
bottomPadding: bottomPadding,
|
|
isSelf: isSelf,
|
|
canLeaveMic: canLeaveMic,
|
|
currentMicIndex: currentMicIndex,
|
|
canShowReport: canShowReport,
|
|
canShowSetting: canShowSetting,
|
|
);
|
|
|
|
if (dataCard == null) {
|
|
return SizedBox(
|
|
width: double.infinity,
|
|
height: sheetHeight,
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
|
|
child: Stack(
|
|
children: [
|
|
Positioned.fill(child: _buildDefaultSheetBackground()),
|
|
Positioned.fill(child: profileContent),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
return _buildDataCardFrame(
|
|
sheetHeight: sheetHeight,
|
|
backgroundHeight: dataCardBackgroundHeight,
|
|
dataCard: dataCard,
|
|
child: profileContent,
|
|
);
|
|
}
|
|
|
|
Widget _buildDataCardFrame({
|
|
required double sheetHeight,
|
|
required double backgroundHeight,
|
|
required PropsResources dataCard,
|
|
required Widget child,
|
|
}) {
|
|
return SizedBox(
|
|
width: double.infinity,
|
|
height: sheetHeight,
|
|
child: Stack(
|
|
clipBehavior: Clip.none,
|
|
children: [
|
|
Positioned.fill(
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
|
|
child: DecoratedBox(decoration: _sheetDecoration()),
|
|
),
|
|
),
|
|
Positioned(
|
|
left: 0,
|
|
right: 0,
|
|
top:
|
|
-(backgroundHeight - sheetHeight) +
|
|
sheetHeight * _dataCardBackgroundTranslateFactor,
|
|
height: backgroundHeight,
|
|
child: IgnorePointer(child: _buildSheetBackground(dataCard)),
|
|
),
|
|
Positioned.fill(
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
|
|
child: child,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildProfileContentLayer({
|
|
required BuildContext context,
|
|
required SocialChatUserProfileManager ref,
|
|
required RtcProvider rtcProvider,
|
|
required SocialChatUserProfile? profile,
|
|
required double bottomPadding,
|
|
required bool isSelf,
|
|
required bool canLeaveMic,
|
|
required num currentMicIndex,
|
|
required bool canShowReport,
|
|
required bool canShowSetting,
|
|
}) {
|
|
final hasCpRelation = _hasCpRelation(profile);
|
|
final closeFriends = _closeFriendProfileData(profile).take(5).toList();
|
|
|
|
return Stack(
|
|
children: [
|
|
Positioned.fill(
|
|
child: LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
return Align(
|
|
alignment: Alignment.topCenter,
|
|
child: FittedBox(
|
|
fit: BoxFit.scaleDown,
|
|
alignment: Alignment.topCenter,
|
|
child: SizedBox(
|
|
width: constraints.maxWidth,
|
|
child: Padding(
|
|
padding: EdgeInsets.fromLTRB(
|
|
10.w,
|
|
16.w,
|
|
10.w,
|
|
bottomPadding > 6.w ? bottomPadding : 6.w,
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
_buildCpProfileHeader(context, profile, ref),
|
|
SizedBox(height: 6.w),
|
|
_buildCompactBadgeStrip(
|
|
profile?.id ?? widget.userId,
|
|
ref,
|
|
profile,
|
|
),
|
|
if (hasCpRelation) ...[
|
|
SizedBox(height: 12.w),
|
|
_buildCpRelationCard(
|
|
context: context,
|
|
profile: profile,
|
|
isSelf: isSelf,
|
|
),
|
|
],
|
|
if (closeFriends.isNotEmpty) ...[
|
|
SizedBox(height: hasCpRelation ? 11.w : 12.w),
|
|
_buildCloseFriendStrip(closeFriends),
|
|
],
|
|
SizedBox(
|
|
height:
|
|
hasCpRelation || closeFriends.isNotEmpty
|
|
? 14.w
|
|
: 12.w,
|
|
),
|
|
_buildActions(
|
|
context: context,
|
|
ref: ref,
|
|
rtcProvider: rtcProvider,
|
|
isSelf: isSelf,
|
|
canLeaveMic: canLeaveMic,
|
|
currentMicIndex: currentMicIndex,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
if (canShowReport)
|
|
Positioned(
|
|
top: 14.w,
|
|
right: 16.w,
|
|
child: _buildHeaderIconButton(
|
|
iconAsset: "sc_images/room/sc_icon_user_card_report.png",
|
|
onTap: () {
|
|
final route =
|
|
"${SCMainRoute.report}?type=user&tageId=${widget.userId}";
|
|
Navigator.of(context).pop();
|
|
SCNavigatorUtils.push(
|
|
navigatorKey.currentState?.context ?? context,
|
|
route,
|
|
replace: false,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
if (canShowSetting)
|
|
Positioned(
|
|
top: 14.w,
|
|
right: 52.w,
|
|
child: _buildHeaderIconButton(
|
|
iconAsset: "sc_images/room/sc_icon_room_user_card_setting.png",
|
|
onTap: () {
|
|
if (userProvider?.userCardInfo == null) {
|
|
return;
|
|
}
|
|
Navigator.of(context).pop();
|
|
showBottomInBottomDialog(
|
|
navigatorKey.currentState?.context ?? context,
|
|
RoomUserCardSetting(
|
|
roomId: roomId,
|
|
userCardInfo: userProvider?.userCardInfo?.copyWith(),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
BoxDecoration _sheetDecoration() {
|
|
return const BoxDecoration(color: Color(0xff061F1E));
|
|
}
|
|
|
|
Widget _buildDefaultSheetBackground() {
|
|
return DecoratedBox(
|
|
decoration: _sheetDecoration(),
|
|
child: SizedBox.expand(
|
|
child: Image.asset(
|
|
_defaultProfileCardBackgroundAsset,
|
|
fit: BoxFit.cover,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSheetBackground(PropsResources? dataCard) {
|
|
if (dataCard == null) {
|
|
return _buildDefaultSheetBackground();
|
|
}
|
|
|
|
return SCDataCardResourceView(
|
|
resource: dataCard,
|
|
fit: BoxFit.cover,
|
|
showCoverFallback: false,
|
|
allowCoverAsResource: false,
|
|
);
|
|
}
|
|
|
|
PropsResources? _activeDataCard(room_card.RoomUserCardRes? cardInfo) {
|
|
return _activeDataCardFromUseProps(cardInfo?.userProfile?.useProps) ??
|
|
_activeDataCardFromUseProps(cardInfo?.useProps);
|
|
}
|
|
|
|
PropsResources? _activeDataCardFromUseProps(List<UseProps>? useProps) {
|
|
final now = DateTime.now().millisecondsSinceEpoch;
|
|
for (final item in 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 _buildCpProfileHeader(
|
|
BuildContext context,
|
|
SocialChatUserProfile? profile,
|
|
SocialChatUserProfileManager ref,
|
|
) {
|
|
return Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
GestureDetector(
|
|
onTap: () => _openProfile(context),
|
|
child: head(
|
|
url: _avatarFallback(profile?.userAvatar),
|
|
width: 72.w,
|
|
border: Border.all(color: Colors.white.withValues(alpha: 0.85)),
|
|
headdress: profile?.getHeaddress()?.sourceUrl,
|
|
headdressCover: profile?.getHeaddress()?.cover,
|
|
),
|
|
),
|
|
SizedBox(height: 8.w),
|
|
Wrap(
|
|
alignment: WrapAlignment.center,
|
|
crossAxisAlignment: WrapCrossAlignment.center,
|
|
spacing: 4.w,
|
|
runSpacing: 4.w,
|
|
children: [
|
|
msgRoleTag(
|
|
ref.userCardInfo?.roomRole ?? "",
|
|
width: 16.w,
|
|
height: 16.w,
|
|
),
|
|
socialchatNickNameText(
|
|
maxWidth: 174.w,
|
|
profile?.userNickname ?? "",
|
|
fontSize: 18.sp,
|
|
textColor: Colors.white,
|
|
fontWeight: FontWeight.bold,
|
|
type: "",
|
|
needScroll: (profile?.userNickname?.characters.length ?? 0) > 12,
|
|
),
|
|
getWealthLevel(
|
|
profile?.wealthLevel ??
|
|
ref.userCardInfo?.userLevel?.wealthLevel ??
|
|
0,
|
|
width: 42.w,
|
|
height: 20.w,
|
|
fontSize: 9.sp,
|
|
textOffsetX: 3,
|
|
),
|
|
getUserLevel(
|
|
profile?.charmLevel ??
|
|
ref.userCardInfo?.userLevel?.charmLevel ??
|
|
0,
|
|
width: 42.w,
|
|
height: 20.w,
|
|
fontSize: 9.sp,
|
|
textOffsetX: 3,
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 4.w),
|
|
_buildSpecialIdMetaRow(context, profile),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildCompactBadgeStrip(
|
|
String? userId,
|
|
SocialChatUserProfileManager ref,
|
|
SocialChatUserProfile? profile,
|
|
) {
|
|
return SizedBox(
|
|
width: double.infinity,
|
|
height: 16.w,
|
|
child: Center(
|
|
child: SCUserBadgeStrip(
|
|
userId: userId,
|
|
fallbackBadges: _roomCardFallbackBadges(ref, profile),
|
|
displayScope: SCUserBadgeDisplayScope.short,
|
|
height: 16.w,
|
|
badgeHeight: 16.w,
|
|
longBadgeWidth: 32.w,
|
|
spacing: 4.w,
|
|
maxBadges: 3,
|
|
wrap: false,
|
|
reserveSpace: false,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildCpRelationCard({
|
|
required BuildContext context,
|
|
required SocialChatUserProfile? profile,
|
|
required bool isSelf,
|
|
}) {
|
|
final cp = _cpProfileData(profile);
|
|
final card = SizedBox(
|
|
height: 114.w,
|
|
child: Stack(
|
|
clipBehavior: Clip.none,
|
|
children: [
|
|
Positioned.fill(
|
|
child: Image.asset(
|
|
_cpLevelAsset(cp.levelText),
|
|
fit: BoxFit.fill,
|
|
errorBuilder:
|
|
(_, __, ___) => Image.asset(
|
|
"$_cpProfileAssetBase/sc_cp_profile_relation_card_bg.png",
|
|
fit: BoxFit.fill,
|
|
),
|
|
),
|
|
),
|
|
Positioned(right: 10.w, top: 8.w, child: const SCCpCornerLabel()),
|
|
Positioned(
|
|
left: 39.w,
|
|
top: 26.w,
|
|
child: _buildCpAvatar(cp.leftAvatarUrl),
|
|
),
|
|
Positioned(
|
|
right: 38.w,
|
|
top: 26.w,
|
|
child: _buildCpAvatar(cp.rightAvatarUrl),
|
|
),
|
|
Positioned(
|
|
left: 142.w,
|
|
right: 142.w,
|
|
top: 25.w,
|
|
height: 22.w,
|
|
child: _buildCpCardText(
|
|
cp.days,
|
|
color: const Color(0xffFF0979),
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
Positioned(
|
|
left: 142.w,
|
|
right: 142.w,
|
|
top: 48.w,
|
|
height: 14.w,
|
|
child: _buildCpCardText(
|
|
"Days",
|
|
color: const Color(0xffFF0979),
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w400,
|
|
),
|
|
),
|
|
Positioned(
|
|
left: 110.w,
|
|
right: 110.w,
|
|
top: 78.w,
|
|
height: 16.w,
|
|
child: _buildCpCardText(
|
|
cp.levelText,
|
|
color: const Color(0xffFEFEFF),
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w400,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
if (!isSelf) {
|
|
return card;
|
|
}
|
|
return SCDebounceWidget(
|
|
onTap: () => _openCpProgress(context, profile),
|
|
child: card,
|
|
);
|
|
}
|
|
|
|
Widget _buildCpCardText(
|
|
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 _buildCpAvatar(String avatarUrl) {
|
|
final trimmedUrl = avatarUrl.trim();
|
|
return Container(
|
|
width: 60.w,
|
|
height: 60.w,
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
border: Border.all(color: Colors.white.withValues(alpha: 0.86)),
|
|
),
|
|
clipBehavior: Clip.antiAlias,
|
|
child:
|
|
trimmedUrl.isEmpty
|
|
? const SizedBox.shrink()
|
|
: netImage(
|
|
url: trimmedUrl,
|
|
width: 60.w,
|
|
height: 60.w,
|
|
fit: BoxFit.cover,
|
|
noDefaultImg: true,
|
|
loadingWidget: const SizedBox.shrink(),
|
|
errorWidget: const SizedBox.shrink(),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildCloseFriendStrip(List<String> friends) {
|
|
return SizedBox(
|
|
height: 41.w,
|
|
width: double.infinity,
|
|
child: Stack(
|
|
clipBehavior: Clip.none,
|
|
children: [
|
|
Positioned.fill(
|
|
child: Image.asset(
|
|
"$_cpProfileAssetBase/sc_cp_profile_close_friend_bg.png",
|
|
fit: BoxFit.fill,
|
|
),
|
|
),
|
|
PositionedDirectional(
|
|
top: 7.w,
|
|
end: 11.w,
|
|
width: 118.w,
|
|
height: 27.w,
|
|
child: DecoratedBox(
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xffebe2ff).withValues(alpha: 0.92),
|
|
borderRadius: BorderRadius.circular(14.w),
|
|
),
|
|
),
|
|
),
|
|
PositionedDirectional(
|
|
top: 8.w,
|
|
end: 13.w,
|
|
child: SizedBox(
|
|
height: 25.w,
|
|
width: 112.w,
|
|
child: Stack(
|
|
alignment: AlignmentDirectional.centerEnd,
|
|
children: [
|
|
for (var index = 0; index < friends.length; index++)
|
|
PositionedDirectional(
|
|
end: (friends.length - 1 - index) * 17.w,
|
|
child: _buildCloseFriendAvatar(friends[index]),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
_RoomProfileCpData _cpProfileData(SocialChatUserProfile? profile) {
|
|
final currentProfile = AccountStorage().getCurrentUser()?.userProfile;
|
|
final cp = _firstRelation(
|
|
profile?.cpList,
|
|
"CP",
|
|
profile: profile,
|
|
requirePartner: true,
|
|
);
|
|
final owner = _relationOwner(profile, cp);
|
|
final partner = _relationPartner(profile, cp);
|
|
return _RoomProfileCpData(
|
|
leftAvatarUrl: _avatarFallback(
|
|
owner.avatarUrl ?? profile?.userAvatar ?? currentProfile?.userAvatar,
|
|
),
|
|
rightAvatarUrl: partner.avatarUrl?.trim() ?? "",
|
|
leftName: _firstNonBlank([
|
|
owner.nickname,
|
|
profile?.userNickname,
|
|
currentProfile?.userNickname,
|
|
]),
|
|
rightName: _firstNonBlank([partner.nickname, partner.account]),
|
|
leftUserId: _firstNonBlank([
|
|
owner.userId,
|
|
profile?.id,
|
|
currentProfile?.id,
|
|
]),
|
|
rightUserId: _firstNonBlank([partner.userId]),
|
|
days: scCpRelationDisplayDays(_firstNonBlank([cp?.days, "0"])),
|
|
relationType: _normalizeCpRelationType(cp?.relationType),
|
|
dismissCost: cp?.dismissCost,
|
|
cpValue: cp?.cpValue ?? 0,
|
|
levelText:
|
|
cp == null
|
|
? ""
|
|
: _cpLevelDisplayText(
|
|
_firstNonBlank([cp.levelText, "Lv.1 Simple Love"]),
|
|
),
|
|
);
|
|
}
|
|
|
|
CPRes? _firstRelation(
|
|
List<CPRes>? relations,
|
|
String relationType, {
|
|
SocialChatUserProfile? profile,
|
|
bool requirePartner = false,
|
|
}) {
|
|
for (final relation in relations ?? const <CPRes>[]) {
|
|
if (_normalizeCpRelationType(relation.relationType) == relationType &&
|
|
(!requirePartner || _relationHasPartner(profile, relation))) {
|
|
return relation;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
bool _hasCpRelation(SocialChatUserProfile? profile) {
|
|
return _firstRelation(
|
|
profile?.cpList,
|
|
"CP",
|
|
profile: profile,
|
|
requirePartner: true,
|
|
) !=
|
|
null;
|
|
}
|
|
|
|
List<String> _closeFriendProfileData(SocialChatUserProfile? profile) {
|
|
final avatars = <String>[];
|
|
for (final relation in profile?.closeFriendList ?? const <CPRes>[]) {
|
|
final type = _normalizeCpRelationType(relation.relationType);
|
|
if (type != "BROTHER" && type != "SISTERS") {
|
|
continue;
|
|
}
|
|
if (!_relationHasPartner(profile, relation)) {
|
|
continue;
|
|
}
|
|
final friend = _relationPartner(profile, relation);
|
|
final avatar = friend.avatarUrl?.trim() ?? "";
|
|
if (avatar.isNotEmpty) {
|
|
avatars.add(avatar);
|
|
}
|
|
}
|
|
return avatars;
|
|
}
|
|
|
|
Widget _buildCloseFriendAvatar(String avatarUrl) {
|
|
return Container(
|
|
width: 25.w,
|
|
height: 25.w,
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
border: Border.all(
|
|
color: Colors.white.withValues(alpha: 0.85),
|
|
width: 1.w,
|
|
),
|
|
),
|
|
clipBehavior: Clip.antiAlias,
|
|
child: netImage(
|
|
url: avatarUrl,
|
|
width: 25.w,
|
|
height: 25.w,
|
|
fit: BoxFit.cover,
|
|
noDefaultImg: true,
|
|
loadingWidget: const SizedBox.shrink(),
|
|
errorWidget: const SizedBox.shrink(),
|
|
),
|
|
);
|
|
}
|
|
|
|
String _cpLevelAsset(String levelText) {
|
|
return "$_cpProfileAssetBase/sc_cp_profile_cp_level_${_cpLevelIndex(levelText)}.png";
|
|
}
|
|
|
|
int _cpLevelIndex(String levelText) {
|
|
return scCpRelationLevelIndex(levelText);
|
|
}
|
|
|
|
String _cpLevelDisplayText(String rawText) {
|
|
return scCpRelationLevelDisplayText(rawText);
|
|
}
|
|
|
|
bool _relationHasPartner(SocialChatUserProfile? profile, CPRes relation) {
|
|
final partner = _relationPartner(profile, relation);
|
|
return _firstNonBlank([
|
|
partner.userId,
|
|
partner.account,
|
|
partner.nickname,
|
|
partner.avatarUrl,
|
|
]).isNotEmpty;
|
|
}
|
|
|
|
_RoomRelationUser _relationOwner(
|
|
SocialChatUserProfile? profile,
|
|
CPRes? relation,
|
|
) {
|
|
if (relation == null) {
|
|
return _RoomRelationUser(
|
|
userId: profile?.id,
|
|
account: profile?.account,
|
|
nickname: profile?.userNickname,
|
|
avatarUrl: profile?.userAvatar,
|
|
);
|
|
}
|
|
final profileId = profile?.id?.trim() ?? "";
|
|
if (profileId.isNotEmpty && profileId == (relation.cpUserId ?? "")) {
|
|
return _RoomRelationUser(
|
|
userId: relation.cpUserId,
|
|
account: relation.cpAccount,
|
|
nickname: relation.cpUserNickname,
|
|
avatarUrl: relation.cpUserAvatar,
|
|
);
|
|
}
|
|
return _RoomRelationUser(
|
|
userId: relation.meUserId,
|
|
account: relation.meAccount,
|
|
nickname: relation.meUserNickname,
|
|
avatarUrl: relation.meUserAvatar,
|
|
);
|
|
}
|
|
|
|
_RoomRelationUser _relationPartner(
|
|
SocialChatUserProfile? profile,
|
|
CPRes? relation,
|
|
) {
|
|
if (relation == null) {
|
|
return const _RoomRelationUser();
|
|
}
|
|
final profileId = profile?.id?.trim() ?? "";
|
|
if (profileId.isNotEmpty && profileId == (relation.cpUserId ?? "")) {
|
|
return _RoomRelationUser(
|
|
userId: relation.meUserId,
|
|
account: relation.meAccount,
|
|
nickname: relation.meUserNickname,
|
|
avatarUrl: relation.meUserAvatar,
|
|
);
|
|
}
|
|
return _RoomRelationUser(
|
|
userId: relation.cpUserId,
|
|
account: relation.cpAccount,
|
|
nickname: relation.cpUserNickname,
|
|
avatarUrl: relation.cpUserAvatar,
|
|
);
|
|
}
|
|
|
|
String _normalizeCpRelationType(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 _avatarFallback(String? avatarUrl) {
|
|
final value = avatarUrl?.trim() ?? "";
|
|
if (value.isNotEmpty) {
|
|
return value;
|
|
}
|
|
return AccountStorage().getCurrentUser()?.userProfile?.userAvatar ?? "";
|
|
}
|
|
|
|
String _firstNonBlank(List<String?> values) {
|
|
for (final value in values) {
|
|
final trimmed = value?.trim() ?? "";
|
|
if (trimmed.isNotEmpty) {
|
|
return trimmed;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
// ignore: unused_element
|
|
Widget _buildProfileHeader(
|
|
BuildContext context,
|
|
SocialChatUserProfile? profile,
|
|
SocialChatUserProfileManager ref,
|
|
) {
|
|
return Column(
|
|
children: [
|
|
GestureDetector(
|
|
onTap: () => _openProfile(context),
|
|
child: head(
|
|
url: profile?.userAvatar ?? "",
|
|
width: 105.w,
|
|
border: Border.all(color: Colors.white, width: 3),
|
|
headdress: profile?.getHeaddress()?.sourceUrl,
|
|
headdressCover: profile?.getHeaddress()?.cover,
|
|
),
|
|
),
|
|
SizedBox(height: 5.w),
|
|
Wrap(
|
|
alignment: WrapAlignment.center,
|
|
crossAxisAlignment: WrapCrossAlignment.center,
|
|
spacing: 4.w,
|
|
runSpacing: 5.w,
|
|
children: [
|
|
msgRoleTag(
|
|
ref.userCardInfo?.roomRole ?? "",
|
|
width: 16.w,
|
|
height: 16.w,
|
|
),
|
|
socialchatNickNameText(
|
|
maxWidth: 116.w,
|
|
profile?.userNickname ?? "",
|
|
fontSize: 18.sp,
|
|
textColor: Colors.white,
|
|
fontWeight: FontWeight.bold,
|
|
type: "",
|
|
needScroll: (profile?.userNickname?.characters.length ?? 0) > 12,
|
|
),
|
|
getVIPBadge(
|
|
profile?.vipLevel ?? profile?.getVIP()?.name,
|
|
width: 45.w,
|
|
height: 25.w,
|
|
),
|
|
getWealthLevel(
|
|
profile?.wealthLevel ??
|
|
ref.userCardInfo?.userLevel?.wealthLevel ??
|
|
0,
|
|
width: 42.w,
|
|
height: 20.w,
|
|
fontSize: 9.sp,
|
|
textOffsetX: 3,
|
|
),
|
|
getUserLevel(
|
|
profile?.charmLevel ??
|
|
ref.userCardInfo?.userLevel?.charmLevel ??
|
|
0,
|
|
width: 42.w,
|
|
height: 20.w,
|
|
fontSize: 9.sp,
|
|
textOffsetX: 3,
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 3.w),
|
|
_buildHeaderLongBadgeStrip(profile?.id ?? widget.userId, ref, profile),
|
|
SizedBox(height: 2.w),
|
|
_buildSpecialIdMetaRow(context, profile),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildSpecialIdMetaRow(
|
|
BuildContext context,
|
|
SocialChatUserProfile? profile,
|
|
) {
|
|
final flagUrl = _countryFlagUrl(context, profile);
|
|
final hasSex = profile?.userSex != null;
|
|
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
SCSpecialIdBadge(
|
|
idText: profile?.getID() ?? "",
|
|
showAnimated: profile?.hasSpecialId() ?? false,
|
|
assetPath: SCSpecialIdAssets.userId,
|
|
animationWidth: 62.w,
|
|
animationHeight: 24.w,
|
|
showTextBesideAnimated: true,
|
|
animatedTextSpacing: 0,
|
|
showAnimatedGradientText:
|
|
profile?.shouldShowColoredSpecialIdText() ?? false,
|
|
animationTextStyle: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
normalTextStyle: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
animationFit: BoxFit.contain,
|
|
),
|
|
if (flagUrl.isNotEmpty) ...[
|
|
SizedBox(width: 8.w),
|
|
_buildCountryFlag(flagUrl),
|
|
],
|
|
if (hasSex) ...[SizedBox(width: 7.w), _buildSexIcon(profile)],
|
|
],
|
|
);
|
|
}
|
|
|
|
String _countryFlagUrl(BuildContext context, SocialChatUserProfile? profile) {
|
|
final countryName = profile?.countryName ?? "";
|
|
try {
|
|
return Provider.of<SCAppGeneralManager>(
|
|
context,
|
|
listen: false,
|
|
).findCountryByName(countryName)?.nationalFlag ??
|
|
"";
|
|
} catch (_) {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
Widget _buildCountryFlag(String flagUrl) {
|
|
final trimmedUrl = flagUrl.trim();
|
|
if (trimmedUrl.isEmpty) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
return ClipRRect(
|
|
borderRadius: BorderRadius.circular(2.w),
|
|
child: netImage(
|
|
url: trimmedUrl,
|
|
width: 22.w,
|
|
height: 15.w,
|
|
fit: BoxFit.cover,
|
|
noDefaultImg: true,
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSexIcon(SocialChatUserProfile? profile) {
|
|
if (profile?.userSex == null) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
return xb(profile?.userSex, height: 15.w, color: null);
|
|
}
|
|
|
|
Widget _buildHeaderLongBadgeStrip(
|
|
String? userId,
|
|
SocialChatUserProfileManager ref,
|
|
SocialChatUserProfile? profile,
|
|
) {
|
|
return SizedBox(
|
|
width: double.infinity,
|
|
child: Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 10.w),
|
|
child: SCUserBadgeStrip(
|
|
userId: userId,
|
|
fallbackBadges: _roomCardFallbackBadges(ref, profile),
|
|
displayScope: SCUserBadgeDisplayScope.long,
|
|
height: 62.w,
|
|
badgeHeight: 20.w,
|
|
longBadgeWidth: 62.w,
|
|
spacing: 4.w,
|
|
runSpacing: 4.w,
|
|
wrap: true,
|
|
maxWrapRows: 2,
|
|
scrollLastWrapRow: true,
|
|
reserveSpace: false,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ignore: unused_element
|
|
Widget _buildBadgeStrip(
|
|
String? userId,
|
|
SocialChatUserProfileManager ref,
|
|
SocialChatUserProfile? profile,
|
|
) {
|
|
return SizedBox(
|
|
width: double.infinity,
|
|
child: Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 10.w),
|
|
child: SCUserBadgeStrip(
|
|
userId: userId,
|
|
fallbackBadges: _roomCardFallbackBadges(ref, profile),
|
|
displayScope: SCUserBadgeDisplayScope.short,
|
|
height: 62.w,
|
|
badgeHeight: 57.5.w,
|
|
longBadgeWidth: 145.w,
|
|
spacing: 12.w,
|
|
maxBadges: 12,
|
|
wrap: false,
|
|
reserveSpace: false,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
List<WearBadge> _roomCardFallbackBadges(
|
|
SocialChatUserProfileManager ref,
|
|
SocialChatUserProfile? profile,
|
|
) {
|
|
final wearBadges =
|
|
profile?.wearBadge
|
|
?.where((item) => item.use != false && _hasBadgeUrl(item))
|
|
.toList() ??
|
|
const <WearBadge>[];
|
|
if (wearBadges.isNotEmpty) {
|
|
return wearBadges;
|
|
}
|
|
return ref.userCardInfo?.useBadge
|
|
?.map<WearBadge>(_roomUseBadgeToWearBadge)
|
|
.where(_hasBadgeUrl)
|
|
.toList() ??
|
|
const <WearBadge>[];
|
|
}
|
|
|
|
WearBadge _roomUseBadgeToWearBadge(room_card.UseBadge badge) {
|
|
return WearBadge(
|
|
animationUrl: badge.animationUrl,
|
|
badgeKey: badge.badgeKey,
|
|
badgeLevel: badge.badgeLevel,
|
|
badgeName: badge.badgeName,
|
|
expireTime: badge.expireTime?.toInt(),
|
|
id: badge.id,
|
|
milestone: badge.milestone,
|
|
notSelectUrl: badge.notSelectUrl,
|
|
selectUrl: badge.selectUrl,
|
|
type: badge.type,
|
|
userId: badge.userId,
|
|
use: true,
|
|
);
|
|
}
|
|
|
|
bool _hasBadgeUrl(WearBadge badge) {
|
|
return (badge.selectUrl ?? "").trim().isNotEmpty;
|
|
}
|
|
|
|
Widget _buildActions({
|
|
required BuildContext context,
|
|
required SocialChatUserProfileManager ref,
|
|
required RtcProvider rtcProvider,
|
|
required bool isSelf,
|
|
required bool canLeaveMic,
|
|
required num currentMicIndex,
|
|
}) {
|
|
if (isSelf) {
|
|
if (!canLeaveMic) {
|
|
return SizedBox(height: 55.w);
|
|
}
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
SizedBox(
|
|
width: 92.w,
|
|
child: _buildSheetAction(
|
|
iconAsset: _leaveMicActionIconAsset,
|
|
label: SCAppLocalizations.of(context)!.leavelTheMic,
|
|
onTap: () {
|
|
Navigator.of(context).pop();
|
|
rtcProvider.xiaMai(currentMicIndex);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
return Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildSheetAction(
|
|
iconAsset: "sc_images/room/sc_icon_send_user_message.png",
|
|
label: SCAppLocalizations.of(context)!.sayHi2,
|
|
onTap: () => _openChat(context),
|
|
),
|
|
),
|
|
Expanded(child: _buildFollowAction(context)),
|
|
Expanded(
|
|
child: _buildSheetAction(
|
|
iconAsset: "sc_images/room/sc_icon_at_tag_user.png",
|
|
label: SCAppLocalizations.of(context)!.atTag,
|
|
onTap: () => _openAtInput(context, ref),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: _buildSheetAction(
|
|
iconAsset: "sc_images/room/sc_icon_send_user_gift.png",
|
|
label: SCAppLocalizations.of(context)!.send,
|
|
onTap: () => _openGift(context, ref),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildSheetAction({
|
|
required String iconAsset,
|
|
required String label,
|
|
required VoidCallback onTap,
|
|
}) {
|
|
return SCDebounceWidget(
|
|
onTap: onTap,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
width: 42.w,
|
|
height: 42.w,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withValues(alpha: 0.58),
|
|
shape: BoxShape.circle,
|
|
border: Border.all(
|
|
color: const Color(0xff18F2B1).withValues(alpha: 0.18),
|
|
),
|
|
),
|
|
alignment: Alignment.center,
|
|
child: Image.asset(iconAsset, width: 28.w, height: 28.w),
|
|
),
|
|
SizedBox(height: 5.w),
|
|
SizedBox(
|
|
height: 18.w,
|
|
child: text(
|
|
label,
|
|
maxLines: 1,
|
|
textAlign: TextAlign.center,
|
|
textColor: Colors.white,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 12.sp,
|
|
lineHeight: 1,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildFollowAction(BuildContext context) {
|
|
return SCDebounceWidget(
|
|
onTap: () {
|
|
final provider = Provider.of<SocialChatUserProfileManager>(
|
|
context,
|
|
listen: false,
|
|
);
|
|
provider.followUser(widget.userId ?? "");
|
|
},
|
|
child: Selector<SocialChatUserProfileManager, bool>(
|
|
selector: (context, provider) => provider.userCardInfo?.follow ?? false,
|
|
shouldRebuild: (prev, next) => prev != next,
|
|
builder: (context, follow, _) {
|
|
return Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
width: 42.w,
|
|
height: 42.w,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withValues(alpha: 0.58),
|
|
shape: BoxShape.circle,
|
|
border: Border.all(
|
|
color: const Color(0xff18F2B1).withValues(alpha: 0.18),
|
|
),
|
|
),
|
|
alignment: Alignment.center,
|
|
child: Image.asset(
|
|
follow
|
|
? "sc_images/room/sc_icon_user_un_follow.png"
|
|
: "sc_images/room/sc_icon_user_follow.png",
|
|
width: 28.w,
|
|
height: 28.w,
|
|
),
|
|
),
|
|
SizedBox(height: 5.w),
|
|
SizedBox(
|
|
height: 18.w,
|
|
child: text(
|
|
follow
|
|
? SCAppLocalizations.of(context)!.followed
|
|
: SCAppLocalizations.of(context)!.follow,
|
|
maxLines: 1,
|
|
textAlign: TextAlign.center,
|
|
textColor: Colors.white,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 12.sp,
|
|
lineHeight: 1,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildHeaderIconButton({
|
|
required String iconAsset,
|
|
required VoidCallback onTap,
|
|
}) {
|
|
return SCDebounceWidget(
|
|
onTap: onTap,
|
|
child: Container(
|
|
width: 28.w,
|
|
height: 28.w,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withValues(alpha: 0.45),
|
|
shape: BoxShape.circle,
|
|
),
|
|
alignment: Alignment.center,
|
|
child: Image.asset(
|
|
iconAsset,
|
|
width: 18.w,
|
|
height: 18.w,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _openProfile(BuildContext context) {
|
|
final route =
|
|
"${SCMainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == widget.userId}&tageId=${widget.userId}";
|
|
Navigator.of(context).pop();
|
|
SCNavigatorUtils.push(navigatorKey.currentState?.context ?? context, route);
|
|
}
|
|
|
|
Future<void> _openChat(BuildContext context) async {
|
|
final rtmProvider = Provider.of<RtmProvider>(context, listen: false);
|
|
Navigator.of(context).pop();
|
|
var conversation = V2TimConversation(
|
|
type: ConversationType.V2TIM_C2C,
|
|
userID: widget.userId,
|
|
conversationID: '',
|
|
);
|
|
final didStart = await rtmProvider.startConversation(conversation);
|
|
if (!didStart || !mounted) return;
|
|
var json = jsonEncode(conversation.toJson());
|
|
SCNavigatorUtils.push(
|
|
navigatorKey.currentState!.context,
|
|
"${SCChatRouter.chat}?conversation=${Uri.encodeComponent(json)}",
|
|
);
|
|
}
|
|
|
|
Future<void> _openAtInput(
|
|
BuildContext context,
|
|
SocialChatUserProfileManager ref,
|
|
) async {
|
|
if (!SCRoomUtils.touristCanMsg(context)) {
|
|
return;
|
|
}
|
|
final nickname = ref.userCardInfo?.userProfile?.userNickname ?? "";
|
|
if (nickname.isEmpty) {
|
|
return;
|
|
}
|
|
Navigator.of(context).pop();
|
|
Navigator.push(
|
|
navigatorKey.currentState?.context ?? context,
|
|
PopRoute(
|
|
child: RoomMsgInput(
|
|
atTextContent: "<user id=\"${widget.userId}\">$nickname</user>",
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _openGift(BuildContext context, SocialChatUserProfileManager ref) {
|
|
Navigator.of(context).pop();
|
|
SmartDialog.show(
|
|
tag: "showGiftControl",
|
|
alignment: Alignment.bottomCenter,
|
|
maskColor: Colors.transparent,
|
|
animationType: SmartAnimationType.fade,
|
|
clickMaskDismiss: true,
|
|
builder: (_) {
|
|
return SCRoomTopLayerSuppressor(
|
|
reason: 'room_user_gift_panel',
|
|
child: GiftPage(toUser: ref.userCardInfo?.userProfile),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> _openCpProgress(
|
|
BuildContext context,
|
|
SocialChatUserProfile? profile,
|
|
) async {
|
|
final cp = _cpProfileData(profile);
|
|
final cancelText = SCAppLocalizations.of(context)!.cancel;
|
|
SCLoadingManager.show();
|
|
late final _RoomCpProgressData progress;
|
|
try {
|
|
progress = await _loadCpProgressData(cp);
|
|
} finally {
|
|
SCLoadingManager.hide();
|
|
}
|
|
if (!mounted || !context.mounted) {
|
|
return;
|
|
}
|
|
RoomCpProgressDialog.show(
|
|
context,
|
|
leftUser: RoomCpProgressDialogUser(
|
|
nickname: cp.leftName,
|
|
userId: cp.leftUserId,
|
|
avatarUrl: cp.leftAvatarUrl,
|
|
),
|
|
rightUser: RoomCpProgressDialogUser(
|
|
nickname: cp.rightName,
|
|
userId: cp.rightUserId,
|
|
avatarUrl: cp.rightAvatarUrl,
|
|
),
|
|
daysValue: cp.days,
|
|
levelText: progress.levelText,
|
|
currentValue: progress.currentValue,
|
|
targetValue: progress.targetValue,
|
|
expAwayText: progress.expAwayText,
|
|
nextLevelText: progress.nextLevelText,
|
|
cancelText: cancelText,
|
|
onCancel:
|
|
() => _showCpDismissConfirm(
|
|
context,
|
|
cp,
|
|
dismissCost: progress.dismissCost,
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<_RoomCpProgressData> _loadCpProgressData(_RoomProfileCpData cp) async {
|
|
final partnerUserId = cp.rightUserId.trim();
|
|
final fallback = _RoomCpProgressData.fallback(cp);
|
|
if (partnerUserId.isEmpty) {
|
|
return fallback;
|
|
}
|
|
var latestFallback = fallback;
|
|
try {
|
|
final config = await SCAccountRepository().cpRightsConfig(
|
|
relationType: cp.relationType,
|
|
cpUserId: partnerUserId,
|
|
);
|
|
final progress = _RoomCpProgressData.fromRightsConfig(config, cp);
|
|
latestFallback = progress;
|
|
if (config.levels.isNotEmpty) {
|
|
return progress;
|
|
}
|
|
} catch (_) {
|
|
// Fall back to the Java-current CP cabin endpoint below.
|
|
}
|
|
try {
|
|
final cabin = await SCAccountRepository().cpCabin(
|
|
cp.leftUserId,
|
|
relationType: cp.relationType,
|
|
);
|
|
return _RoomCpProgressData.fromCabin(
|
|
cabin,
|
|
cp,
|
|
fallbackDismissCost: latestFallback.dismissCost,
|
|
);
|
|
} catch (_) {
|
|
return latestFallback;
|
|
}
|
|
}
|
|
|
|
Future<void> _showCpDismissConfirm(
|
|
BuildContext context,
|
|
_RoomProfileCpData cp, {
|
|
int? dismissCost,
|
|
}) async {
|
|
final l10n = SCAppLocalizations.of(context)!;
|
|
final partnerUserId = cp.rightUserId.trim();
|
|
if (partnerUserId.isEmpty) {
|
|
return;
|
|
}
|
|
await showRoomCpDismissConfirmDialog(
|
|
context,
|
|
message: l10n.cpDismissConfirmMessage(
|
|
dismissCost == null ? "--" : "$dismissCost",
|
|
),
|
|
cancelText: l10n.cancel,
|
|
confirmText: l10n.confirm,
|
|
onConfirm: () async {
|
|
SCLoadingManager.show();
|
|
try {
|
|
await SCAccountRepository().cpRelationshipDismissApply(
|
|
partnerUserId,
|
|
relationType: cp.relationType,
|
|
);
|
|
await userProvider?.refreshLoadedCpProfiles();
|
|
await SmartDialog.dismiss(tag: RoomCpProgressDialog.dialogTag);
|
|
SCTts.show(l10n.operationSuccessful);
|
|
} catch (_) {
|
|
// The repository/network layer already surfaces the backend error.
|
|
} finally {
|
|
SCLoadingManager.hide();
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
String roleName(String role) {
|
|
if (role == SCRoomRolesType.HOMEOWNER.name) {
|
|
return SCAppLocalizations.of(context)!.owner;
|
|
} else if (role == SCRoomRolesType.ADMIN.name) {
|
|
return SCAppLocalizations.of(context)!.admin;
|
|
} else if (role == SCRoomRolesType.MEMBER.name) {
|
|
return SCAppLocalizations.of(context)!.member;
|
|
}
|
|
return SCAppLocalizations.of(context)!.guest;
|
|
}
|
|
|
|
// ignore: unused_element
|
|
_buildEmptyCpItem(bool canAdd) {
|
|
return Container(
|
|
width: ScreenUtil().screenWidth,
|
|
margin: EdgeInsets.symmetric(horizontal: 12.w),
|
|
height: 105.w,
|
|
decoration: BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage(
|
|
canAdd
|
|
? "sc_images/person/sc_icon_no_cp_item_bg.png"
|
|
: "sc_images/person/sc_icon_no_cp_item_bg4.png",
|
|
),
|
|
fit: BoxFit.fill,
|
|
),
|
|
),
|
|
child: Container(
|
|
margin: EdgeInsets.only(bottom: 13.w),
|
|
alignment: AlignmentDirectional.bottomCenter,
|
|
child: text(
|
|
SCAppLocalizations.of(context)!.noMatchedCP,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 12.sp,
|
|
textColor: canAdd ? Color(0xFFFF79A1) : Colors.white,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ignore: unused_element
|
|
Widget _buildCpItem(CPRes item) {
|
|
return Stack(
|
|
alignment: AlignmentDirectional.center,
|
|
children: [
|
|
Container(
|
|
width: ScreenUtil().screenWidth,
|
|
margin: EdgeInsets.symmetric(horizontal: 15.w),
|
|
height: 135.w,
|
|
decoration: BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage("sc_images/person/sc_icon_no_cp_item_bg2.png"),
|
|
fit: BoxFit.fill,
|
|
),
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
SizedBox(height: 68.w),
|
|
Container(
|
|
alignment: AlignmentDirectional.bottomCenter,
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Image.asset(
|
|
"sc_images/person/sc_icon_cp_value_tag.png",
|
|
height: 18.w,
|
|
),
|
|
SizedBox(width: 5.w),
|
|
text(
|
|
_cpVaFormat(item.cpValue ?? 0),
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 12.sp,
|
|
textColor: Color(0xFFFF79A1),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(height: 8.w),
|
|
Container(
|
|
alignment: AlignmentDirectional.bottomCenter,
|
|
child: text(
|
|
SCAppLocalizations.of(
|
|
context,
|
|
)!.timeSpentTogether(scCpRelationDisplayDays(item.days)),
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 12.sp,
|
|
textColor: Color(0xFFFF79A1),
|
|
),
|
|
),
|
|
Container(
|
|
alignment: AlignmentDirectional.bottomCenter,
|
|
child: text(
|
|
SCAppLocalizations.of(context)!.firstDay(item.firstDay ?? ""),
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 11.sp,
|
|
textColor: Colors.white,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
PositionedDirectional(
|
|
top: 30.w,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Column(
|
|
children: [
|
|
GestureDetector(
|
|
child: SizedBox(
|
|
height: 50.w,
|
|
child: Stack(
|
|
alignment: AlignmentDirectional.center,
|
|
children: [
|
|
netImage(
|
|
url: item.meUserAvatar ?? "",
|
|
width: 48.w,
|
|
shape: BoxShape.circle,
|
|
defaultImg:
|
|
"sc_images/general/sc_icon_avar_defalt.png",
|
|
),
|
|
Image.asset(
|
|
"sc_images/person/sc_icon_cp_head_ring.png",
|
|
),
|
|
],
|
|
),
|
|
),
|
|
onTap: () {
|
|
SCNavigatorUtils.push(
|
|
context,
|
|
replace: false,
|
|
"${SCMainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == item.meUserId}&tageId=${item.meUserId}",
|
|
);
|
|
},
|
|
),
|
|
|
|
Container(
|
|
alignment: Alignment.center,
|
|
width: 80.w,
|
|
height: 15.w,
|
|
child:
|
|
(item.meUserNickname?.length ?? 0) > 8
|
|
? Marquee(
|
|
text: item.meUserNickname ?? "",
|
|
style: TextStyle(
|
|
fontSize: 10.sp,
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w600,
|
|
decoration: TextDecoration.none,
|
|
),
|
|
scrollAxis: Axis.horizontal,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
blankSpace: 20.0,
|
|
velocity: 40.0,
|
|
pauseAfterRound: Duration(seconds: 1),
|
|
accelerationDuration: Duration(seconds: 1),
|
|
accelerationCurve: Curves.easeOut,
|
|
decelerationDuration: Duration(milliseconds: 550),
|
|
decelerationCurve: Curves.easeOut,
|
|
)
|
|
: Text(
|
|
item.meUserNickname ?? "",
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: TextStyle(
|
|
fontSize: 10.sp,
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w600,
|
|
decoration: TextDecoration.none,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(width: 120.w),
|
|
Column(
|
|
children: [
|
|
GestureDetector(
|
|
child: SizedBox(
|
|
height: 50.w,
|
|
child: Stack(
|
|
alignment: AlignmentDirectional.center,
|
|
children: [
|
|
netImage(
|
|
url: item.cpUserAvatar ?? "",
|
|
width: 48.w,
|
|
shape: BoxShape.circle,
|
|
defaultImg:
|
|
"sc_images/general/sc_icon_avar_defalt.png",
|
|
),
|
|
Image.asset(
|
|
"sc_images/person/sc_icon_cp_head_ring.png",
|
|
),
|
|
],
|
|
),
|
|
),
|
|
onTap: () {
|
|
SCNavigatorUtils.push(
|
|
context,
|
|
replace: false,
|
|
"${SCMainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == item.cpUserId}&tageId=${item.cpUserId}",
|
|
);
|
|
},
|
|
),
|
|
Container(
|
|
alignment: Alignment.center,
|
|
width: 80.w,
|
|
height: 15.w,
|
|
child:
|
|
(item.cpUserNickname?.length ?? 0) > 8
|
|
? Marquee(
|
|
text: item.cpUserNickname ?? "",
|
|
style: TextStyle(
|
|
fontSize: 10.sp,
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w600,
|
|
decoration: TextDecoration.none,
|
|
),
|
|
scrollAxis: Axis.horizontal,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
blankSpace: 20.0,
|
|
velocity: 40.0,
|
|
pauseAfterRound: Duration(seconds: 1),
|
|
accelerationDuration: Duration(seconds: 1),
|
|
accelerationCurve: Curves.easeOut,
|
|
decelerationDuration: Duration(milliseconds: 550),
|
|
decelerationCurve: Curves.easeOut,
|
|
)
|
|
: Text(
|
|
item.cpUserNickname ?? "",
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: TextStyle(
|
|
fontSize: 10.sp,
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w600,
|
|
decoration: TextDecoration.none,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
String getUserCardBg() {
|
|
return "";
|
|
}
|
|
|
|
String _cpVaFormat(double cpValue) {
|
|
return SCStringUtils.formatCompactNumber(cpValue);
|
|
}
|
|
}
|
|
|
|
class _RoomCpProgressData {
|
|
const _RoomCpProgressData({
|
|
required this.levelText,
|
|
required this.currentValue,
|
|
required this.targetValue,
|
|
required this.expAwayText,
|
|
required this.nextLevelText,
|
|
this.dismissCost,
|
|
});
|
|
|
|
factory _RoomCpProgressData.fallback(_RoomProfileCpData cp) {
|
|
final currentValue = cp.cpValue;
|
|
return _RoomCpProgressData(
|
|
levelText:
|
|
cp.levelText.trim().isEmpty ? "Lv.1 Simple Love" : cp.levelText,
|
|
currentValue: currentValue,
|
|
targetValue: currentValue > 0 ? currentValue : 1,
|
|
expAwayText: SCStringUtils.formatCompactNumber(0),
|
|
nextLevelText: _levelShortText(
|
|
_nextLevelIndex(_levelIndex(cp.levelText)),
|
|
),
|
|
dismissCost: cp.dismissCost,
|
|
);
|
|
}
|
|
|
|
factory _RoomCpProgressData.fromRightsConfig(
|
|
SCCpRightsConfigRes config,
|
|
_RoomProfileCpData cp,
|
|
) {
|
|
final currentValue = (config.intimacyValue ?? cp.cpValue).toDouble();
|
|
final levels = config.levels.map(_CpProgressLevel.fromRightsLevel).toList();
|
|
return _RoomCpProgressData.fromLevels(
|
|
currentValue: currentValue,
|
|
levels: levels,
|
|
cp: cp,
|
|
dismissCost: config.dismissConsumeGold ?? cp.dismissCost,
|
|
);
|
|
}
|
|
|
|
factory _RoomCpProgressData.fromCabin(
|
|
SCCpCabinRes cabin,
|
|
_RoomProfileCpData cp, {
|
|
int? fallbackDismissCost,
|
|
}) {
|
|
final currentValue =
|
|
(cabin.cpVal?.toDouble() ??
|
|
cabin.cpPairUserProfileCO?.cpValue ??
|
|
cp.cpValue);
|
|
final levels =
|
|
(cabin.cpCabinConfigCO ?? const <dynamic>[])
|
|
.map(_CpProgressLevel.fromDynamic)
|
|
.toList();
|
|
return _RoomCpProgressData.fromLevels(
|
|
currentValue: currentValue,
|
|
levels: levels,
|
|
cp: cp,
|
|
dismissCost: cabin.dismissCost ?? fallbackDismissCost ?? cp.dismissCost,
|
|
);
|
|
}
|
|
|
|
factory _RoomCpProgressData.fromLevels({
|
|
required double currentValue,
|
|
required List<_CpProgressLevel> levels,
|
|
required _RoomProfileCpData cp,
|
|
int? dismissCost,
|
|
}) {
|
|
final sortedLevels =
|
|
levels.where((level) => level.requiredValue != null).toList()..sort(
|
|
(left, right) => left.requiredValue!.compareTo(right.requiredValue!),
|
|
);
|
|
_CpProgressLevel? nextLevel;
|
|
for (final level in sortedLevels) {
|
|
if ((level.requiredValue ?? 0) > currentValue) {
|
|
nextLevel = level;
|
|
break;
|
|
}
|
|
}
|
|
|
|
final targetValue =
|
|
nextLevel?.requiredValue?.toDouble() ??
|
|
(currentValue > 0 ? currentValue : 1);
|
|
final expAway =
|
|
nextLevel == null
|
|
? 0
|
|
: (targetValue - currentValue).ceil().clamp(0, 1 << 62).toInt();
|
|
final currentLevel = _currentLevelFromConfig(sortedLevels, currentValue);
|
|
|
|
return _RoomCpProgressData(
|
|
levelText: _levelDisplayText(currentLevel, cp.levelText),
|
|
currentValue: currentValue,
|
|
targetValue: targetValue > 0 ? targetValue : 1,
|
|
expAwayText: SCStringUtils.formatCompactNumber(expAway),
|
|
nextLevelText:
|
|
nextLevel == null
|
|
? _levelShortText(
|
|
_nextLevelIndex(
|
|
currentLevel?.level ?? _levelIndex(cp.levelText),
|
|
),
|
|
)
|
|
: _levelShortText(nextLevel.level, fallback: nextLevel.levelName),
|
|
dismissCost: dismissCost,
|
|
);
|
|
}
|
|
|
|
final String levelText;
|
|
final double currentValue;
|
|
final double targetValue;
|
|
final String expAwayText;
|
|
final String nextLevelText;
|
|
final int? dismissCost;
|
|
|
|
static _CpProgressLevel? _currentLevelFromConfig(
|
|
List<_CpProgressLevel> levels,
|
|
double currentValue,
|
|
) {
|
|
_CpProgressLevel? currentLevel;
|
|
for (final level in levels) {
|
|
if ((level.requiredValue ?? 0) <= currentValue) {
|
|
currentLevel = level;
|
|
}
|
|
}
|
|
return currentLevel;
|
|
}
|
|
|
|
static String _levelDisplayText(_CpProgressLevel? level, String fallback) {
|
|
final fallbackText = fallback.trim();
|
|
if (level == null) {
|
|
return fallbackText.isEmpty ? "Lv.1 Simple Love" : fallbackText;
|
|
}
|
|
final levelName = level.levelName?.trim();
|
|
if (levelName != null && levelName.isNotEmpty) {
|
|
return levelName;
|
|
}
|
|
if (level.level != null) {
|
|
return "Lv.${level.level}";
|
|
}
|
|
return fallbackText.isEmpty ? "Lv.1 Simple Love" : fallbackText;
|
|
}
|
|
|
|
static String _levelShortText(int? level, {String? fallback}) {
|
|
if (level != null) {
|
|
return "Lv. $level";
|
|
}
|
|
final fallbackText = fallback?.trim() ?? "";
|
|
if (fallbackText.isNotEmpty) {
|
|
return fallbackText;
|
|
}
|
|
return "Lv. 1";
|
|
}
|
|
|
|
static int _levelIndex(String levelText) {
|
|
return scCpRelationLevelIndex(levelText);
|
|
}
|
|
|
|
static int _nextLevelIndex(int level) {
|
|
return (level + 1).clamp(1, 5).toInt();
|
|
}
|
|
}
|
|
|
|
class _CpProgressLevel {
|
|
const _CpProgressLevel({this.level, this.levelName, this.requiredValue});
|
|
|
|
factory _CpProgressLevel.fromRightsLevel(SCCpRightsLevelRes level) {
|
|
return _CpProgressLevel(
|
|
level: level.level,
|
|
levelName: level.levelName,
|
|
requiredValue: level.requiredIntimacyValue,
|
|
);
|
|
}
|
|
|
|
factory _CpProgressLevel.fromDynamic(dynamic value) {
|
|
final source = value is Map ? value : const <String, dynamic>{};
|
|
return _CpProgressLevel(
|
|
level: _asInt(
|
|
_firstValue(source, const ["level", "cpLevel", "cabinLevel", "lv"]),
|
|
),
|
|
levelName: _asString(
|
|
_firstValue(source, const [
|
|
"levelName",
|
|
"name",
|
|
"title",
|
|
"cabinName",
|
|
"cpName",
|
|
]),
|
|
),
|
|
requiredValue: _asInt(
|
|
_firstValue(source, const [
|
|
"requiredIntimacyValue",
|
|
"requiredCpValue",
|
|
"requiredCpVal",
|
|
"cpValue",
|
|
"cpVal",
|
|
"intimacyValue",
|
|
]),
|
|
),
|
|
);
|
|
}
|
|
|
|
final int? level;
|
|
final String? levelName;
|
|
final int? requiredValue;
|
|
|
|
static dynamic _firstValue(Map source, List<String> keys) {
|
|
for (final key in keys) {
|
|
if (source.containsKey(key) && source[key] != null) {
|
|
return source[key];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static String? _asString(dynamic value) {
|
|
final text = value?.toString().trim();
|
|
return text == null || text.isEmpty ? null : text;
|
|
}
|
|
|
|
static int? _asInt(dynamic value) {
|
|
if (value is int) {
|
|
return value;
|
|
}
|
|
if (value is num) {
|
|
return value.toInt();
|
|
}
|
|
return int.tryParse(value?.toString() ?? "");
|
|
}
|
|
}
|
|
|
|
class _RoomProfileCpData {
|
|
const _RoomProfileCpData({
|
|
required this.leftAvatarUrl,
|
|
required this.rightAvatarUrl,
|
|
required this.leftName,
|
|
required this.rightName,
|
|
required this.leftUserId,
|
|
required this.rightUserId,
|
|
required this.days,
|
|
required this.levelText,
|
|
required this.relationType,
|
|
required this.cpValue,
|
|
this.dismissCost,
|
|
});
|
|
|
|
final String leftAvatarUrl;
|
|
final String rightAvatarUrl;
|
|
final String leftName;
|
|
final String rightName;
|
|
final String leftUserId;
|
|
final String rightUserId;
|
|
final String days;
|
|
final String levelText;
|
|
final String relationType;
|
|
final double cpValue;
|
|
final int? dismissCost;
|
|
}
|
|
|
|
class _RoomRelationUser {
|
|
const _RoomRelationUser({
|
|
this.userId,
|
|
this.account,
|
|
this.nickname,
|
|
this.avatarUrl,
|
|
});
|
|
|
|
final String? userId;
|
|
final String? account;
|
|
final String? nickname;
|
|
final String? avatarUrl;
|
|
}
|