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? currentIndexListenable; final int tabIndex; @override State createState() => _MePage2State(); } class _MePage2State extends State with WidgetsBindingObserver { static const Duration _counterStaleDuration = Duration(seconds: 30); static const String _inviteActivityUrl = 'https://h5.haiyihy.com/app-invite/index.html'; static const double _inviteActivityHorizontalScale = 750 / 662; Map _counterMap = {}; SCVipStatusRes? _vipStatus; bool _isVipStatusLoading = false; bool _isCounterLoading = false; bool _counterRefreshQueued = false; bool _counterReloadAfterCurrent = false; DateTime? _lastCounterLoadedAt; StreamSubscription? _relationChangedSubscription; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); widget.currentIndexListenable?.addListener(_handleTabVisibilityChanged); _relationChangedSubscription = eventBus .on() .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( 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 _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 _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 _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( 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", ); } 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.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( 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), ), ], ); } 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( 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(); 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; }