yumi-flutter/lib/modules/user/me_page2.dart
2026-05-09 13:38:27 +08:00

999 lines
30 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 'package:flutter/material.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/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/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});
@override
State<MePage2> createState() => _MePage2State();
}
class _MePage2State extends State<MePage2> {
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;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadPageData();
});
}
void _loadPageData() {
final profileManager = Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
);
profileManager.fetchUserProfileData();
profileManager.balance();
_loadCounter();
_loadVipStatus();
}
Future<void> _loadCounter() async {
final userId = AccountStorage().getCurrentUser()?.userProfile?.id ?? "";
if (userId.isEmpty) return;
try {
final userCounterList = await SCAccountRepository().userCounter(userId);
if (!mounted) return;
setState(() {
_counterMap = Map.fromEntries(
userCounterList.map(
(counter) => MapEntry(counter.counterType ?? "", counter),
),
);
});
} catch (_) {}
}
Future<void> _loadVipStatus() async {
if (mounted) {
setState(() => _isVipStatusLoading = true);
}
try {
final status = await _loadVipEntryStatus();
debugPrint(
'[VIP][MeEntry] status loaded active=${status.active} '
'level=${status.level} levelCode=${status.levelCode} '
'expireAt=${status.expireAt} '
'badge=${_vipEntryBadgeLogValue(status.badge)}',
);
if (!mounted) return;
_syncCurrentProfileVipStatus(status);
setState(() {
_vipStatus = status;
_isVipStatusLoading = false;
});
} catch (error) {
debugPrint('[VIP][MeEntry] status load failed error=$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) {
debugPrint('[VIP][MeEntry] use vip home state');
return state;
}
debugPrint('[VIP][MeEntry] vip home state empty, fallback status');
} catch (error) {
debugPrint('[VIP][MeEntry] vip home load failed error=$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();
if (profile.vipLevel == nextVipLevel) return;
profileManager.syncCurrentUserProfile(
profile.copyWith(vipLevel: nextVipLevel),
);
}
void _refreshAfterVipReturn(SocialChatUserProfileManager profileManager) {
debugPrint('[VIP][MeEntry] refresh user data after vip page return');
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<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),
),
],
);
}
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;
// 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",
);
},
),
);
}
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;
}