yumi-flutter/lib/modules/user/me_page2.dart
2026-06-26 16:40:25 +08:00

1203 lines
37 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:async';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
import 'package:yumi/app_localizations.dart';
import 'package:yumi/modules/index/main_route.dart';
import 'package:yumi/modules/store/store_route.dart';
import 'package:yumi/modules/user/settings/settings_route.dart';
import 'package:yumi/modules/user/vip/vip_route.dart';
import 'package:yumi/modules/wallet/wallet_route.dart';
import 'package:yumi/services/auth/user_profile_manager.dart';
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_user_counter_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_vip_res.dart';
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_vip_repository_imp.dart';
import 'package:yumi/shared/tools/sc_lk_event_bus.dart';
import 'package:yumi/ui_kit/components/sc_compontent.dart';
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
import 'package:yumi/ui_kit/widgets/id/sc_special_id_badge.dart';
import 'package:yumi/ui_kit/widgets/svga/sc_svga_asset_widget.dart';
class MePage2 extends StatefulWidget {
const MePage2({super.key, this.currentIndexListenable, this.tabIndex = 3});
final ValueListenable<int>? currentIndexListenable;
final int tabIndex;
@override
State<MePage2> createState() => _MePage2State();
}
class _MePage2State extends State<MePage2> with WidgetsBindingObserver {
static const Duration _counterStaleDuration = Duration(seconds: 30);
static const String _cpActivityUrl =
'https://h5.global-interaction.com/activity/cp-space/yumi.html';
static const String _inviteActivityUrl =
'https://h5.haiyihy.com/app-invite/index.html';
static const double _inviteActivityHorizontalScale = 750 / 662;
Map<String, SCUserCounterRes> _counterMap = {};
SCVipStatusRes? _vipStatus;
bool _isVipStatusLoading = false;
bool _isCounterLoading = false;
bool _counterRefreshQueued = false;
bool _counterReloadAfterCurrent = false;
DateTime? _lastCounterLoadedAt;
StreamSubscription<SCUserRelationChangedEvent>? _relationChangedSubscription;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
widget.currentIndexListenable?.addListener(_handleTabVisibilityChanged);
_relationChangedSubscription = eventBus
.on<SCUserRelationChangedEvent>()
.listen(_handleUserRelationChanged);
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadPageData();
});
}
@override
void didUpdateWidget(covariant MePage2 oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.currentIndexListenable == widget.currentIndexListenable) {
return;
}
oldWidget.currentIndexListenable?.removeListener(
_handleTabVisibilityChanged,
);
widget.currentIndexListenable?.addListener(_handleTabVisibilityChanged);
}
@override
void dispose() {
widget.currentIndexListenable?.removeListener(_handleTabVisibilityChanged);
_relationChangedSubscription?.cancel();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed && _isCurrentTabVisible) {
_refreshCounterIfNeeded();
}
}
void _loadPageData() {
final profileManager = Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
);
profileManager.fetchUserProfileData();
profileManager.getUserIdentity();
profileManager.balance();
_loadCounter();
_loadVipStatus();
}
bool get _isCurrentTabVisible {
final currentIndex = widget.currentIndexListenable?.value;
return currentIndex == null || currentIndex == widget.tabIndex;
}
bool get _isCounterStale {
final lastLoadedAt = _lastCounterLoadedAt;
if (lastLoadedAt == null) {
return true;
}
return DateTime.now().difference(lastLoadedAt) >= _counterStaleDuration;
}
void _handleTabVisibilityChanged() {
if (!_isCurrentTabVisible) {
return;
}
_refreshCounterIfNeeded();
}
void _refreshCounterIfNeeded() {
if (_counterRefreshQueued || _isCounterStale) {
unawaited(_loadCounter(force: true));
}
}
void _handleUserRelationChanged(SCUserRelationChangedEvent event) {
final currentUserId =
AccountStorage().getCurrentUser()?.userProfile?.id?.trim() ?? "";
if (currentUserId.isEmpty) {
return;
}
final actorUserId = event.actorUserId.trim();
final targetUserId = event.targetUserId.trim();
final affectsCurrentUser =
actorUserId == currentUserId || targetUserId == currentUserId;
if (!affectsCurrentUser) {
return;
}
final delta = event.isFollowing ? 1 : -1;
if (mounted) {
setState(() {
if (actorUserId == currentUserId) {
_applyCounterDelta("SUBSCRIPTION", delta);
}
if (targetUserId == currentUserId) {
_applyCounterDelta("FANS", delta);
}
});
}
_counterRefreshQueued = true;
if (_isCurrentTabVisible) {
unawaited(_loadCounter(force: true));
}
}
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(),
),
};
}
Future<void> _loadCounter({bool force = false}) async {
if (_isCounterLoading) {
if (force || _counterRefreshQueued) {
_counterReloadAfterCurrent = true;
}
return;
}
if (!force && !_counterRefreshQueued && !_isCounterStale) return;
final userId = AccountStorage().getCurrentUser()?.userProfile?.id ?? "";
if (userId.isEmpty) return;
_isCounterLoading = true;
try {
final userCounterList = await SCAccountRepository().userCounter(userId);
if (!mounted) return;
setState(() {
_counterMap = Map.fromEntries(
userCounterList.map(
(counter) => MapEntry(counter.counterType ?? "", counter),
),
);
_lastCounterLoadedAt = DateTime.now();
_counterRefreshQueued = false;
});
} catch (_) {
} finally {
_isCounterLoading = false;
if (_counterReloadAfterCurrent && mounted) {
_counterReloadAfterCurrent = false;
unawaited(_loadCounter(force: true));
}
}
}
Future<void> _loadVipStatus() async {
if (mounted) {
setState(() => _isVipStatusLoading = true);
}
try {
final status = await _loadVipEntryStatus();
if (!mounted) return;
_syncCurrentProfileVipStatus(status);
setState(() {
_vipStatus = status;
_isVipStatusLoading = false;
});
} catch (error) {
if (!mounted) return;
setState(() => _isVipStatusLoading = false);
}
}
Future<SCVipStatusRes> _loadVipEntryStatus() async {
final repository = SCVipRepositoryImp();
try {
final home = await repository.vipHome();
final state = home.state;
if (state != null && state.levelInt > 0) {
return state;
}
} catch (error) {}
return repository.vipStatus();
}
void _syncCurrentProfileVipStatus(SCVipStatusRes status) {
final profileManager = Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
);
final profile = profileManager.currentUserProfile;
if (profile == null) return;
final nextVipLevel =
status.active == false || status.levelInt <= 0
? '0'
: status.levelInt.toString();
final nextProfile = socialChatProfileWithAvatarFrameResource(
profile.copyWith(vipLevel: nextVipLevel),
_avatarFramePropsFromVipStatus(status),
expireTime: _expireTimeFromVipStatus(status),
);
profileManager.syncCurrentUserProfile(nextProfile);
}
PropsResources? _avatarFramePropsFromVipStatus(SCVipStatusRes status) {
if (!status.isActive) {
return null;
}
final resource = status.avatarFrame;
return socialChatAvatarFrameProps(
id: resource?.resourceId,
name: resource?.name,
sourceUrl: resource?.sourceResourceUrl,
cover: resource?.coverUrl ?? resource?.cover,
code: "VIP_AVATAR_FRAME",
);
}
String? _expireTimeFromVipStatus(SCVipStatusRes status) {
final expireAt = status.expireAt?.trim();
if (expireAt == null || expireAt.isEmpty) {
return null;
}
final normalized =
expireAt.contains("T") ? expireAt : expireAt.replaceFirst(" ", "T");
return DateTime.tryParse(normalized)?.millisecondsSinceEpoch.toString();
}
void _refreshAfterVipReturn(SocialChatUserProfileManager profileManager) {
profileManager.fetchUserProfileData(loadGuardCount: false);
profileManager.balance();
_loadCounter();
_loadVipStatus();
}
void _openInviteActivity() {
final inviteActivityUrl = _appendToken(_inviteActivityUrl);
if (inviteActivityUrl.isEmpty) return;
SCNavigatorUtils.push(
context,
"${SCMainRoute.webViewPage}?url=${Uri.encodeComponent(inviteActivityUrl)}&showTitle=false",
);
}
void _openCpActivity() {
final cpActivityUrl = _cpActivityUrl.trim();
if (cpActivityUrl.isEmpty) {
if (kDebugMode) {
debugPrint('[MePage2] cp activity url is empty');
}
return;
}
SCNavigatorUtils.push(
context,
"${SCMainRoute.webViewPage}?url=${Uri.encodeComponent(cpActivityUrl)}&showTitle=false",
);
}
String _appendToken(String url) {
final token = AccountStorage().getToken();
if (token.isEmpty) return url;
final uri = Uri.tryParse(url);
if (uri == null) return url;
final queryParameters = Map<String, String>.from(uri.queryParameters);
queryParameters['token'] = token;
return uri.replace(queryParameters: queryParameters).toString();
}
@override
Widget build(BuildContext context) {
return SafeArea(
top: true,
bottom: false,
child: Consumer<SocialChatUserProfileManager>(
builder: (context, profileManager, child) {
return SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: EdgeInsets.fromLTRB(10.w, 12.w, 10.w, 24.w),
child: Column(
children: [
_buildProfileSection(profileManager),
SizedBox(height: 12.w),
_buildStatsCard(),
SizedBox(height: 12.w),
_buildEntryRow(),
SizedBox(height: 12.w),
_buildWalletVipRow(profileManager),
_buildInviteActivityBanner(),
_buildMenuCard2(),
SizedBox(height: 12.w),
_buildMenuCard([
_MenuRowData(
title: SCAppLocalizations.of(context)!.settings,
assetIcon: 'sc_images/index/sc_icon_settings.png',
onTap: () {
SCNavigatorUtils.push(
context,
SettingsRoute.settings,
replace: false,
);
},
),
_MenuRowData(
title: SCAppLocalizations.of(context)!.language,
assetIcon: 'sc_images/general/sc_icon_setting_language.png',
onTap: () {
SCNavigatorUtils.push(context, SCMainRoute.language);
},
),
]),
],
),
);
},
),
);
}
Widget _buildProfileSection(SocialChatUserProfileManager profileManager) {
final profile = profileManager.currentUserProfile;
return SCDebounceWidget(
onTap: () {
SCNavigatorUtils.push(
context,
'${SCMainRoute.person}?isMe=true&tageId=${profile?.id ?? ""}',
replace: false,
);
},
child: Column(
children: [
SizedBox(
width: 240.w,
height: 130.w,
child: Stack(
alignment: Alignment.topCenter,
children: [
head(
url: profile?.userAvatar ?? '',
width: 96.w,
height: 96.w,
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 1),
headdress: profile?.getHeaddress()?.sourceUrl,
headdressCover: profile?.getHeaddress()?.cover,
showDefault: true,
),
],
),
),
SizedBox(height: 8.w),
Text(
profile?.userNickname ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 18.sp,
fontWeight: FontWeight.w700,
),
),
SizedBox(height: 2.w),
SCSpecialIdBadge(
idText: profile?.getID() ?? '',
showAnimated: profile?.hasSpecialId() ?? false,
assetPath: SCSpecialIdAssets.userIdLarge,
animationWidth: 80.w,
animationHeight: 32.w,
showTextBesideAnimated: true,
animatedTextSpacing: 0,
showAnimatedGradientText:
profile?.shouldShowColoredSpecialIdText() ?? false,
animationTextStyle: TextStyle(
color: Colors.white,
fontSize: 16.sp,
fontWeight: FontWeight.w700,
),
normalTextStyle: TextStyle(
color: Colors.white,
fontSize: 16.sp,
fontWeight: FontWeight.w600,
),
animationFit: BoxFit.contain,
),
],
),
);
}
Widget _buildStatsCard() {
return _buildGlassCard(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.w),
radius: 8.w,
child: Row(
children: [
_buildStatItem(
value: _counterText('INTERVIEW'),
label: SCAppLocalizations.of(context)!.vistors,
onTap: () => SCNavigatorUtils.push(context, SCMainRoute.vistors),
),
_buildStatDivider(),
_buildStatItem(
value: _counterText('SUBSCRIPTION'),
label: SCAppLocalizations.of(context)!.following,
onTap: () => SCNavigatorUtils.push(context, SCMainRoute.follow),
),
_buildStatDivider(),
_buildStatItem(
value: _counterText('FANS'),
label: SCAppLocalizations.of(context)!.friends,
onTap: () => SCNavigatorUtils.push(context, SCMainRoute.fans),
),
],
),
);
}
Widget _buildEntryRow() {
return Row(
children: [
_buildEntryItem(
title: SCAppLocalizations.of(context)!.store,
iconPath: 'sc_images/index/sc_icon_shop.png',
onTap: () => SCNavigatorUtils.push(context, StoreRoute.list),
),
SizedBox(width: 10.w),
_buildEntryItem(
title: SCAppLocalizations.of(context)!.level,
iconPath: 'sc_images/index/sc_icon_level.png',
onTap: () => SCNavigatorUtils.push(context, SCMainRoute.levelList),
),
SizedBox(width: 10.w),
_buildEntryItem(
title: SCAppLocalizations.of(context)!.bag,
iconPath: 'sc_images/index/sc_icon_bag.png',
onTap: () => SCNavigatorUtils.push(context, StoreRoute.bags),
),
SizedBox(width: 10.w),
_buildEntryItem(
title: 'cp',
iconPath: 'sc_images/index/sc_icon_cp_entry.png',
iconWidth: 47,
iconHeight: 50,
onTap: _openCpActivity,
),
],
);
}
Widget _buildWalletVipRow(SocialChatUserProfileManager profileManager) {
return Row(
children: [
Expanded(child: _buildVipEntryCard(profileManager)),
SizedBox(width: 10.w),
Expanded(child: _buildWalletCard()),
],
);
}
Widget _buildWalletCard() {
return SCDebounceWidget(
onTap: () => SCNavigatorUtils.push(context, WalletRoute.recharge),
child: Container(
height: 68.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.w),
image: const DecorationImage(
image: AssetImage('sc_images/index/sc_icon_wallet_bg.png'),
fit: BoxFit.fill,
),
),
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.w),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(
SCAppLocalizations.of(context)!.wallet,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w800,
fontStyle: FontStyle.italic,
color: Colors.white,
height: 1,
),
),
SizedBox(height: 2.w),
Row(
children: [
Image.asset(
'sc_images/general/sc_icon_wallet_coin.png',
width: 18.w,
height: 18.w,
),
SizedBox(width: 4.w),
Expanded(
child: Consumer<SocialChatUserProfileManager>(
builder: (context, ref, child) {
return Text(
':${_balanceText(ref.myBalance)}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 18.sp,
fontWeight: FontWeight.w700,
fontStyle: FontStyle.italic,
height: 1,
),
);
},
),
),
],
),
],
),
),
Image.asset(
'sc_images/index/sc_icon_wallet_icon.png',
width: 40.w,
height: 40.w,
),
],
),
),
);
}
Widget _buildVipEntryCard(SocialChatUserProfileManager profileManager) {
final vipLevel = _vipEntryLevel(profileManager);
final isActiveVip = vipLevel > 0;
final badgeResource = isActiveVip ? _vipStatus?.badge : null;
return SCDebounceWidget(
onTap: () async {
await SCNavigatorUtils.push(context, VipRoute.detail);
if (mounted) {
_refreshAfterVipReturn(profileManager);
}
},
child: Container(
height: 68.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.w),
border: Border.all(color: const Color(0xFFFFD37A), width: 1.w),
image: const DecorationImage(
image: AssetImage('sc_images/vip/sc_vip_entry_bg.png'),
fit: BoxFit.fill,
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.18),
blurRadius: 10.w,
offset: Offset(0, 4.w),
),
],
),
clipBehavior: Clip.antiAlias,
child: Stack(
children: [
PositionedDirectional(
top: 12.w,
start: 14.w,
child: Text(
'VIP',
style: TextStyle(
color: Colors.white,
fontFamily: 'Source Han Sans SC',
fontSize: 16.sp,
fontWeight: FontWeight.w700,
height: 1,
),
),
),
PositionedDirectional(
start: 13.w,
bottom: 10.w,
child:
_isVipStatusLoading && _vipStatus == null
? SizedBox(
width: 16.w,
height: 16.w,
child: CircularProgressIndicator(
color: const Color(0xFFFFD155),
strokeWidth: 2.w,
),
)
: isActiveVip
? _buildVipLevelMark(vipLevel)
: Text(
'Not activated',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: const Color(
0xFFFFE8A7,
).withValues(alpha: 0.82),
fontSize: 11.sp,
fontWeight: FontWeight.w600,
height: 1,
),
),
),
PositionedDirectional(
end: 2.w,
top: 8.w,
child: Opacity(
opacity: isActiveVip ? 1 : 0.52,
child: _buildVipEntryBadge(resource: badgeResource),
),
),
],
),
),
);
}
Widget _buildVipEntryBadge({required SCVipResourceRes? resource}) {
final empty = SizedBox(width: 52.w, height: 52.w);
final url = resource?.previewUrl?.trim();
if (url == null || url.isEmpty) {
return empty;
}
return netImage(
url: url,
width: 52.w,
height: 52.w,
fit: BoxFit.contain,
noDefaultImg: true,
errorWidget: empty,
);
}
String _vipEntryBadgeLogValue(SCVipResourceRes? resource) {
if (resource == null) {
return 'null';
}
return '{id:${resource.resourceId},name:${resource.name},'
'hasPreviewUrl:${resource.hasPreviewUrl},'
'previewUrl:${resource.previewUrl}}';
}
int _vipEntryLevel(SocialChatUserProfileManager profileManager) {
final status = _vipStatus;
if (status != null && status.isActive && status.levelInt > 0) {
return status.levelInt.clamp(1, 5).toInt();
}
final vipResource = profileManager.currentUserProfile?.getVIP();
return _parseVipLevel(vipResource?.name) ??
_parseVipLevel(vipResource?.code) ??
0;
}
int? _parseVipLevel(String? value) {
final text = value?.trim();
if (text == null || text.isEmpty) {
return null;
}
final vipMatch = RegExp(
r'VIP\s*([1-5])',
caseSensitive: false,
).firstMatch(text);
if (vipMatch != null) {
return int.tryParse(vipMatch.group(1) ?? '');
}
final trailingLevelMatch = RegExp(r'([1-5])$').firstMatch(text);
if (trailingLevelMatch != null) {
return int.tryParse(trailingLevelMatch.group(1) ?? '');
}
return null;
}
Widget _buildVipLevelMark(int level) {
return Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
'sc_images/vip/sc_vip_letter_v.png',
width: 18.1.w,
height: 15.3.w,
fit: BoxFit.contain,
),
Image.asset(
'sc_images/vip/sc_vip_letter_i.png',
width: 8.3.w,
height: 15.3.w,
fit: BoxFit.contain,
),
Image.asset(
'sc_images/vip/sc_vip_letter_p.png',
width: 18.1.w,
height: 15.3.w,
fit: BoxFit.contain,
),
SizedBox(width: 3.w),
_buildVipLevelNumber(level),
],
);
}
Widget _buildVipLevelNumber(int level) {
return Image.asset(
'sc_images/vip/sc_vip_number_${level}_filled.png',
height: 15.247.w,
fit: BoxFit.fill,
color: const Color(0xFFFFD155),
colorBlendMode: BlendMode.srcIn,
);
}
Widget _buildInviteActivityBanner() {
return SCDebounceWidget(
onTap: _openInviteActivity,
child: LayoutBuilder(
builder: (context, constraints) {
final width =
constraints.maxWidth.isFinite
? constraints.maxWidth
: ScreenUtil().screenWidth - 20.w;
final height = width / 5;
return SizedBox(
width: width,
height: height,
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
Positioned.fill(
child: Transform(
alignment: Alignment.center,
transform: Matrix4.diagonal3Values(
_inviteActivityHorizontalScale,
1.0,
1.0,
),
child: SCSvgaAssetWidget(
assetPath: _inviteActivitySvgaAsset,
width: width,
height: height,
active: true,
loop: true,
fit: BoxFit.fill,
allowDrawingOverflow: true,
),
),
),
],
),
);
},
),
);
}
String get _inviteActivitySvgaAsset {
switch (SCGlobalConfig.lang) {
case 'ar':
return 'sc_images/index/sc_invite_activity_rtl.svga';
case 'bn':
case 'fa':
return 'sc_images/index/sc_invite_activity_fa.svga';
case 'tr':
case 'pt':
return 'sc_images/index/sc_invite_activity_pt.svga';
default:
return 'sc_images/index/sc_invite_activity.svga';
}
}
Widget _buildMenuCard2() {
final userProfile = context.watch<SocialChatUserProfileManager>();
final items = <_MenuRowData>[];
final hasCurrentUser = userProfile.currentUserProfile != null;
final isAgent = userProfile.userIdentity?.agent ?? false;
final isYumiManager = userProfile.userIdentity?.yumiManager ?? false;
if (kDebugMode) {
final currentProfile = userProfile.currentUserProfile;
debugPrint(
'[MePage2] menu identity userId=${currentProfile?.id}, '
'account=${currentProfile?.account}, hasCurrentUser=$hasCurrentUser, '
'agent=${userProfile.userIdentity?.agent}, admin=${userProfile.userIdentity?.admin}, '
'manager=${userProfile.userIdentity?.manager}, yumiManager=${userProfile.userIdentity?.yumiManager}',
);
}
// 1. 主播/代理入口
if (hasCurrentUser) {
if (isAgent) {
items.add(
_MenuRowData(
title: SCAppLocalizations.of(context)!.agentCenter,
assetIcon: 'sc_images/index/sc_icon_agent_center.png',
onTap: () {
SCNavigatorUtils.push(
context,
"${SCMainRoute.webViewPage}?url=${Uri.encodeComponent(SCGlobalConfig.agencyCenterUrl)}&showTitle=false",
);
},
),
);
} else {
items.add(
_MenuRowData(
title: SCAppLocalizations.of(context)!.hostCenter,
assetIcon: 'sc_images/index/sc_icon_host_center.png',
onTap: () {
SCNavigatorUtils.push(
context,
"${SCMainRoute.webViewPage}?url=${Uri.encodeComponent(SCGlobalConfig.hostCenterUrl)}&showTitle=false",
);
},
),
);
}
}
// 2. BD 相关(非管理员时)
if (!(userProfile.userIdentity?.admin ?? false)) {
if (userProfile.userIdentity?.bdLeader ?? false) {
items.add(
_MenuRowData(
title: SCAppLocalizations.of(context)!.admin,
assetIcon: 'sc_images/index/sc_icon_bd_leader.png',
onTap: () {
SCNavigatorUtils.push(
context,
"${SCMainRoute.webViewPage}?url=${Uri.encodeComponent(SCGlobalConfig.bdLeaderUrl)}&showTitle=false",
);
},
),
);
} else if (userProfile.userIdentity?.bd ?? false) {
items.add(
_MenuRowData(
title: SCAppLocalizations.of(context)!.bdCenter,
assetIcon: 'sc_images/index/sc_icon_bd_center.png',
onTap: () {
SCNavigatorUtils.push(
context,
"${SCMainRoute.webViewPage}?url=${Uri.encodeComponent(SCGlobalConfig.bdCenterUrl)}&showTitle=false",
);
},
),
);
}
}
// 3. 货运代理
if (userProfile.userIdentity?.freightAgent ?? false) {
items.add(
_MenuRowData(
title: SCAppLocalizations.of(context)!.rechargeAgency,
assetIcon: 'sc_images/index/sc_icon_recharge_agency.png',
onTap: () {
SCNavigatorUtils.push(
context,
"${SCMainRoute.webViewPage}?url=${Uri.encodeComponent(SCGlobalConfig.coinSellerUrl)}&showTitle=false",
);
},
),
);
}
// 4. 管理员注意你的原逻辑中管理员和上面BD逻辑有重叠这里按原样单独处理
if (userProfile.userIdentity?.admin ?? false) {
items.add(
_MenuRowData(
title: SCAppLocalizations.of(context)!.adminCenter,
assetIcon: 'sc_images/index/sc_icon_admin_center.png',
onTap: () {
SCNavigatorUtils.push(
context,
"${SCMainRoute.webViewPage}?url=${Uri.encodeComponent(SCGlobalConfig.adminUrl)}&showTitle=false",
);
},
),
);
}
if (isYumiManager) {
if (kDebugMode) {
debugPrint('[MePage2] show Manager Center');
}
items.add(
_MenuRowData(
title: SCAppLocalizations.of(context)!.managerCenter,
assetIcon: 'sc_images/index/sc_icon_admin_center.png',
onTap: () {
SCNavigatorUtils.push(
context,
"${SCMainRoute.webViewPage}?url=${Uri.encodeComponent(SCGlobalConfig.managerCenterUrl)}&showTitle=false",
);
},
),
);
} else if (kDebugMode) {
debugPrint(
'[MePage2] hide Manager Center because yumiManager=$isYumiManager',
);
}
return _buildMenuCard(items);
}
Widget _buildMenuCard(List<_MenuRowData> items) {
// 没有任何项时,不显示整个卡片
if (items.isEmpty) return const SizedBox.shrink();
return _buildGlassCard(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.w),
radius: 4.w,
child: Column(
children: [
for (int i = 0; i < items.length; i++) ...[_buildMenuRow(items[i])],
],
),
);
}
Widget _buildMenuRow(_MenuRowData item) {
return SCDebounceWidget(
onTap: item.onTap,
child: SizedBox(
height: 46.w,
child: Row(
children: [
Image.asset(
item.assetIcon!,
width: 24.w,
height: 24.w,
color: Colors.white,
),
SizedBox(width: 12.w),
Expanded(
child: Text(
item.title,
style: TextStyle(
color: Colors.white,
fontSize: 15.sp,
fontWeight: FontWeight.w600,
),
),
),
Icon(
SCGlobalConfig.lang == 'ar'
? Icons.keyboard_arrow_left
: Icons.keyboard_arrow_right,
color: Colors.white,
size: 18.w,
),
],
),
),
);
}
Widget _buildEntryItem({
required String title,
required String iconPath,
required VoidCallback onTap,
double? iconWidth,
double? iconHeight,
int badgeCount = 0,
}) {
return Expanded(
child: SCDebounceWidget(
onTap: onTap,
child: Container(
height: 110.w,
decoration: BoxDecoration(
image: const DecorationImage(
image: AssetImage('sc_images/person/sc_icon_me_menu_1_bg.png'),
fit: BoxFit.fill,
),
),
child: Stack(
children: [
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
iconPath,
width: (iconWidth ?? 54).w,
height: (iconHeight ?? 54).w,
fit: BoxFit.contain,
),
SizedBox(height: 4.w),
Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 15.sp,
fontWeight: FontWeight.w700,
),
),
],
),
),
if (badgeCount > 0) _buildEntryBadge(badgeCount),
],
),
),
),
);
}
Widget _buildEntryBadge(int count) {
final text = count > 99 ? '99+' : count.toString();
return PositionedDirectional(
top: 11.w,
end: 14.w,
child: Container(
constraints: BoxConstraints(minWidth: 18.w),
height: 18.w,
padding: EdgeInsets.symmetric(horizontal: 5.w),
alignment: Alignment.center,
decoration: BoxDecoration(
color: const Color(0xFFFF4B4B),
borderRadius: BorderRadius.circular(9.w),
border: Border.all(color: Colors.white, width: 1.w),
),
child: Text(
text,
maxLines: 1,
style: TextStyle(
color: Colors.white,
fontSize: 10.sp,
fontWeight: FontWeight.w700,
height: 1,
),
),
),
);
}
Widget _buildStatItem({
required String value,
required String label,
required VoidCallback onTap,
}) {
return Expanded(
child: SCDebounceWidget(
onTap: onTap,
child: Column(
children: [
Text(
value,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
fontWeight: FontWeight.w700,
),
),
SizedBox(height: 1.w),
Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
fontWeight: FontWeight.w500,
),
),
],
),
),
);
}
Widget _buildStatDivider() {
return Container(
width: 0.5.w,
height: 16.w,
color: const Color(0xFFE6E6E6).withValues(alpha: 0.8),
margin: EdgeInsets.symmetric(horizontal: 6.w),
);
}
Widget _buildGlassCard({
required Widget child,
required EdgeInsets padding,
required double radius,
}) {
return Container(
width: double.infinity,
padding: padding,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(radius),
border: Border.all(color: const Color(0xFFB4FCCE), width: 1.w),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
const Color(0xFF0A7D49).withValues(alpha: 0.20),
const Color(0xFF031513).withValues(alpha: 0.35),
],
),
),
child: child,
);
}
String _counterText(String type) {
return _counterMap[type]?.quantity ?? '0';
}
String _balanceText(double balance) {
if (balance >= 1000) {
return '${(balance / 1000).toStringAsFixed(2)}k';
}
return balance.toStringAsFixed(balance % 1 == 0 ? 0 : 2);
}
}
class _MenuRowData {
const _MenuRowData({
required this.title,
required this.onTap,
this.assetIcon,
});
final String title;
final String? assetIcon;
final VoidCallback onTap;
}