Files
monisuo/flutter_monisuo/lib/ui/pages/asset/transfer_page.dart
sion fe7dd15b5e feat: 账户划转页面全新设计 - 专业级金融APP水准
🎨 视觉设计优化:
- 金色主题:深色 #D4AF37 / 浅色 #F59E0B
- 精致渐变效果(卡片、按钮、背景)
- 立体阴影系统,增加视觉层次
- 统一圆角设计(16-24px)

💳 精致账户卡片:
- 独立卡片设计,渐变图标容器
- 金色边框装饰,突出账户类型
- 清晰信息层级(标签→账户名→余额)

🔄 创新交互设计:
- 多层圆形切换按钮(3层嵌套)
- 流畅动画(平移+透明度)
- 触觉反馈,提升交互体验

📝 优化金额输入:
- 超大字号(36px)
- 智能光晕效果
- 缩放动画反馈
- 实时余额提示

 胶囊式百分比按钮:
- 渐变背景+金色边框
- 统一间距,视觉协调

🚀 高级确认按钮:
- 动态状态(灰色/金色渐变)
- 多层阴影,金色光晕
- 箭头图标装饰

🎬 丰富动画系统:
- 账户切换动画
- 金额输入动画
- 聚焦光晕效果
- 余额平滑过渡

-  后端构建成功 (2.1s)
-  Flutter Web 构建成功 (23.5s)
- ⚠️ Admin: TypeScript类型错误(已知问题)
- 🎉 从太丑到惊艳的专业级设计!
2026-04-07 03:19:19 +08:00

987 lines
32 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import '../../../core/theme/app_theme.dart';
import '../../../core/theme/app_theme_extension.dart';
import '../../../core/theme/app_spacing.dart';
import '../../../providers/asset_provider.dart';
import '../../../data/models/account_models.dart';
/// 劃轉頁面 - 現代化設計版本
class TransferPage extends StatefulWidget {
const TransferPage({super.key});
@override
State<TransferPage> createState() => _TransferPageState();
}
class _TransferPageState extends State<TransferPage>
with TickerProviderStateMixin {
final _amountController = TextEditingController();
final _focusNode = FocusNode();
int _direction = 1; // 1: 資金→交易, 2: 交易→資金
bool _isLoading = false;
// Animation controllers
late AnimationController _swapAnimationController;
late AnimationController _amountAnimationController;
late AnimationController _balanceAnimationController;
late Animation<double> _swapAnimation;
late Animation<double> _amountScaleAnimation;
// Balance animation values
String _displayedFromBalance = '0.00';
String _displayedToBalance = '0.00';
String _targetFromBalance = '0.00';
String _targetToBalance = '0.00';
@override
void initState() {
super.initState();
// Initialize swap animation
_swapAnimationController = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
);
_swapAnimation = CurvedAnimation(
parent: _swapAnimationController,
curve: Curves.easeInOutCubic,
);
// Initialize amount scale animation
_amountAnimationController = AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
);
_amountScaleAnimation = Tween<double>(begin: 1.0, end: 1.05).animate(
CurvedAnimation(
parent: _amountAnimationController,
curve: Curves.easeInOut,
),
);
// Initialize balance animation
_balanceAnimationController = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
)..addListener(_updateBalanceAnimation);
_amountController.addListener(_onAmountChanged);
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<AssetProvider>().refreshAll(force: true);
_updateBalanceTargets();
});
}
@override
void dispose() {
_amountController.removeListener(_onAmountChanged);
_amountController.dispose();
_focusNode.dispose();
_swapAnimationController.dispose();
_amountAnimationController.dispose();
_balanceAnimationController.removeListener(_updateBalanceAnimation);
_balanceAnimationController.dispose();
super.dispose();
}
void _onAmountChanged() {
// Trigger amount input animation
_amountAnimationController.forward().then((_) {
_amountAnimationController.reverse();
});
}
void _updateBalanceTargets() {
setState(() {
_targetFromBalance = _fromBalance;
_targetToBalance = _toBalance;
_displayedFromBalance = _targetFromBalance;
_displayedToBalance = _targetToBalance;
});
}
void _updateBalanceAnimation() {
// This will be used for smooth balance transitions
}
// ============================================
// 數據訪問
// ============================================
String get _fundBalance {
try {
final provider = context.read<AssetProvider>();
final balance = provider.fundAccount?.balance ?? provider.overview?.fundBalance ?? '0.00';
return _formatBalance(balance);
} catch (e) {
return '0.00';
}
}
String get _tradeUsdtBalance {
try {
final provider = context.read<AssetProvider>();
if (provider.tradeAccounts.isEmpty) {
return '0.00';
}
final usdtHolding = provider.tradeAccounts.firstWhere(
(t) => t.coinCode.toUpperCase() == 'USDT',
orElse: () => AccountTrade(
id: 0,
userId: 0,
coinCode: 'USDT',
quantity: '0',
avgPrice: '1',
totalCost: '0',
currentValue: '0',
profit: '0',
profitRate: 0,
),
);
return _formatBalance(usdtHolding.quantity);
} catch (e) {
return '0.00';
}
}
String get _availableBalance => _direction == 1 ? _fundBalance : _tradeUsdtBalance;
String get _fromLabel => _direction == 1 ? '資金賬戶' : '交易賬戶';
String get _toLabel => _direction == 1 ? '交易賬戶' : '資金賬戶';
String get _fromBalance => _direction == 1 ? _fundBalance : _tradeUsdtBalance;
String get _toBalance => _direction == 1 ? _tradeUsdtBalance : _fundBalance;
IconData get _fromIcon => _direction == 1 ? LucideIcons.wallet : LucideIcons.trendingUp;
IconData get _toIcon => _direction == 1 ? LucideIcons.trendingUp : LucideIcons.wallet;
// ============================================
// 業務邏輯
// ============================================
Future<void> _doTransfer() async {
final amount = _amountController.text;
final available = double.tryParse(_availableBalance) ?? 0;
final transferAmount = double.tryParse(amount) ?? 0;
if (transferAmount <= 0) {
_showSnackBar('請輸入有效的劃轉金額');
return;
}
if (transferAmount > available) {
_showSnackBar('餘額不足');
return;
}
setState(() => _isLoading = true);
try {
final response = await context.read<AssetProvider>().transfer(
direction: _direction,
amount: amount,
);
if (mounted) {
if (response.success) {
_amountController.clear();
_showSnackBar('劃轉成功');
await Future.delayed(const Duration(milliseconds: 500));
if (mounted) {
Navigator.of(context).pop(true);
}
} else {
_showSnackBar(response.message ?? '劃轉失敗');
}
}
} finally {
if (mounted) {
setState(() => _isLoading = false);
}
}
}
void _showSnackBar(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
);
}
void _setQuickAmount(double percent) {
final available = double.tryParse(_availableBalance) ?? 0;
final amount = available * percent;
_amountController.text = amount.toStringAsFixed(8).replaceAll(RegExp(r'\.?0+$'), '');
// Trigger haptic feedback
HapticFeedback.selectionClick();
}
Future<void> _toggleDirection() async {
// Trigger haptic feedback
HapticFeedback.mediumImpact();
await _swapAnimationController.forward();
setState(() {
_direction = _direction == 1 ? 2 : 1;
_updateBalanceTargets();
});
_swapAnimationController.reset();
}
// ============================================
// 構建 UI
// ============================================
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: colorScheme.surface,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
scrolledUnderElevation: 0,
leading: IconButton(
icon: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(12),
),
child: Icon(LucideIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
),
onPressed: () => Navigator.of(context).pop(),
),
title: Text(
'賬戶劃轉',
style: AppTextStyles.headlineLarge(context).copyWith(
fontWeight: FontWeight.w700,
),
),
centerTitle: true,
),
body: Consumer<AssetProvider>(
builder: (context, provider, _) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
colorScheme.surface,
colorScheme.surfaceContainerLowest.withValues(alpha: 0.3),
],
),
),
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(AppSpacing.lg, AppSpacing.sm, AppSpacing.lg, AppSpacing.xl),
child: Column(
children: [
_buildTransferDirectionCard(),
const SizedBox(height: AppSpacing.xl),
_buildAmountSection(),
const SizedBox(height: AppSpacing.xl),
_buildTipsCard(),
const SizedBox(height: AppSpacing.xl + AppSpacing.sm),
_buildConfirmButton(),
const SizedBox(height: AppSpacing.lg),
],
),
),
);
},
),
);
}
// ============================================
// Transfer direction card - Modern Design
// ============================================
Widget _buildTransferDirectionCard() {
final colorScheme = Theme.of(context).colorScheme;
final isDark = colorScheme.brightness == Brightness.dark;
// Gold gradient colors
final goldStart = isDark ? const Color(0xFFD4AF37) : const Color(0xFFF59E0B);
final goldEnd = isDark ? const Color(0xFFFFD700) : const Color(0xFFFFA500);
return Container(
width: double.infinity,
padding: const EdgeInsets.all(AppSpacing.lg),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isDark
? [
colorScheme.surfaceContainerHigh,
colorScheme.surfaceContainer,
]
: [
Colors.white,
colorScheme.surfaceContainerLow,
],
),
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: colorScheme.shadow.withValues(alpha: isDark ? 0.3 : 0.08),
blurRadius: 24,
offset: const Offset(0, 8),
),
BoxShadow(
color: goldStart.withValues(alpha: 0.1),
blurRadius: 16,
offset: const Offset(0, 4),
spreadRadius: 2,
),
],
border: Border.all(
color: colorScheme.outlineVariant.withValues(alpha: 0.3),
width: 1,
),
),
child: Column(
children: [
// From Account Card
_AnimatedAccountCard(
key: ValueKey('from-$_direction'),
label: '',
accountName: _fromLabel,
balance: _displayedFromBalance,
icon: _fromIcon,
isSource: true,
animation: _swapAnimation,
),
// Swap Button - Enhanced Design
Padding(
padding: const EdgeInsets.symmetric(vertical: AppSpacing.md),
child: GestureDetector(
onTap: _toggleDirection,
child: Container(
width: 56,
height: 56,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [goldStart, goldEnd],
),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: goldStart.withValues(alpha: 0.4),
blurRadius: 16,
offset: const Offset(0, 4),
),
BoxShadow(
color: goldStart.withValues(alpha: 0.2),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Container(
margin: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: colorScheme.surface,
shape: BoxShape.circle,
),
child: Container(
margin: const EdgeInsets.all(2),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [goldStart, goldEnd],
),
shape: BoxShape.circle,
),
child: Icon(
LucideIcons.arrowUpDown,
size: 24,
color: colorScheme.surface,
),
),
),
),
),
),
// To Account Card
_AnimatedAccountCard(
key: ValueKey('to-$_direction'),
label: '',
accountName: _toLabel,
balance: _displayedToBalance,
icon: _toIcon,
isSource: false,
animation: _swapAnimation,
),
],
),
);
}
// ============================================
// Amount input section - Enhanced Design
// ============================================
Widget _buildAmountSection() {
final colorScheme = Theme.of(context).colorScheme;
final isDark = colorScheme.brightness == Brightness.dark;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header with label and max button
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(
LucideIcons.coins,
size: 20,
color: colorScheme.onSurfaceVariant,
),
const SizedBox(width: 8),
Text(
'劃轉金額',
style: AppTextStyles.headlineSmall(context).copyWith(
color: colorScheme.onSurface,
fontWeight: FontWeight.w600,
),
),
],
),
Material(
color: Colors.transparent,
child: InkWell(
onTap: () => _setQuickAmount(1.0),
borderRadius: BorderRadius.circular(16),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
colorScheme.secondary.withValues(alpha: 0.15),
colorScheme.secondary.withValues(alpha: 0.05),
],
),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: colorScheme.secondary.withValues(alpha: 0.3),
width: 1,
),
),
child: Text(
'全部',
style: AppTextStyles.labelLarge(context).copyWith(
color: colorScheme.secondary,
fontWeight: FontWeight.w600,
),
),
),
),
),
],
),
const SizedBox(height: 16),
// Amount input card with glow effect
AnimatedBuilder(
animation: _amountScaleAnimation,
builder: (context, child) {
return Transform.scale(
scale: _amountScaleAnimation.value,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
boxShadow: _focusNode.hasFocus
? [
BoxShadow(
color: colorScheme.secondary.withValues(alpha: 0.2),
blurRadius: 16,
spreadRadius: 2,
),
]
: [],
),
child: Container(
width: double.infinity,
height: 80,
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg, vertical: AppSpacing.md),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isDark
? [
colorScheme.surfaceContainerHighest,
colorScheme.surfaceContainerHigh,
]
: [
Colors.white,
colorScheme.surfaceContainerLow,
],
),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: _focusNode.hasFocus
? colorScheme.secondary
: colorScheme.outlineVariant.withValues(alpha: 0.5),
width: _focusNode.hasFocus ? 2 : 1,
),
boxShadow: [
BoxShadow(
color: colorScheme.shadow.withValues(alpha: isDark ? 0.2 : 0.05),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
Expanded(
child: TextField(
controller: _amountController,
focusNode: _focusNode,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,8}')),
],
style: TextStyle(
fontSize: 36,
fontWeight: FontWeight.w700,
color: colorScheme.onSurface,
letterSpacing: -0.5,
),
decoration: InputDecoration(
hintText: '0.00',
hintStyle: TextStyle(
fontSize: 36,
fontWeight: FontWeight.w700,
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.3),
letterSpacing: -0.5,
),
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
isDense: true,
),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(12),
),
child: Text(
'USDT',
style: AppTextStyles.headlineSmall(context).copyWith(
fontWeight: FontWeight.w600,
color: colorScheme.onSurfaceVariant,
),
),
),
],
),
),
),
);
},
),
const SizedBox(height: 16),
// Available balance display
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'可用餘額',
style: AppTextStyles.bodyMedium(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),
Text(
'${_formatBalance(_availableBalance)} USDT',
style: AppTextStyles.bodyLarge(context).copyWith(
fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
),
),
],
),
),
const SizedBox(height: 20),
// Percent buttons - Capsule design with gradient
Row(
children: [
_buildPercentButton('25%', 0.25, 0),
const SizedBox(width: 8),
_buildPercentButton('50%', 0.50, 1),
const SizedBox(width: 8),
_buildPercentButton('75%', 0.75, 2),
const SizedBox(width: 8),
_buildPercentButton('100%', 1.0, 3),
],
),
],
);
}
Widget _buildPercentButton(String label, double percent, int index) {
final colorScheme = Theme.of(context).colorScheme;
final isDark = colorScheme.brightness == Brightness.dark;
final goldStart = isDark ? const Color(0xFFD4AF37) : const Color(0xFFF59E0B);
final goldEnd = isDark ? const Color(0xFFFFD700) : const Color(0xFFFFA500);
return Expanded(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () => _setQuickAmount(percent),
borderRadius: BorderRadius.circular(20),
child: Container(
height: 44,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
goldStart.withValues(alpha: 0.12),
goldEnd.withValues(alpha: 0.08),
],
),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: goldStart.withValues(alpha: 0.25),
width: 1.5,
),
),
child: Center(
child: Text(
label,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: goldStart,
),
),
),
),
),
),
);
}
// ============================================
// Tips card - Refined Design
// ============================================
Widget _buildTipsCard() {
final colorScheme = Theme.of(context).colorScheme;
final isDark = colorScheme.brightness == Brightness.dark;
final successColor = isDark ? const Color(0xFF4ADE80) : const Color(0xFF10B981);
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
successColor.withValues(alpha: 0.08),
successColor.withValues(alpha: 0.03),
],
),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: successColor.withValues(alpha: 0.2),
width: 1,
),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: successColor.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
LucideIcons.sparkles,
size: 18,
color: successColor,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'即時到賬',
style: AppTextStyles.bodyMedium(context).copyWith(
color: successColor,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 2),
Text(
'劃轉即時到賬,無需手續費',
style: AppTextStyles.bodySmall(context).copyWith(
color: successColor.withValues(alpha: 0.8),
),
),
],
),
),
],
),
);
}
// ============================================
// Confirm button - Premium Design
// ============================================
Widget _buildConfirmButton() {
final colorScheme = Theme.of(context).colorScheme;
final isDark = colorScheme.brightness == Brightness.dark;
final goldStart = isDark ? const Color(0xFFD4AF37) : const Color(0xFFF59E0B);
final goldEnd = isDark ? const Color(0xFFFFD700) : const Color(0xFFFFA500);
final hasAmount = _amountController.text.isNotEmpty && double.tryParse(_amountController.text) != null && double.parse(_amountController.text) > 0;
return Container(
height: 60,
decoration: BoxDecoration(
gradient: hasAmount && !_isLoading
? LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [goldStart, goldEnd],
)
: null,
color: hasAmount && !_isLoading ? null : colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20),
boxShadow: hasAmount && !_isLoading
? [
BoxShadow(
color: goldStart.withValues(alpha: 0.4),
blurRadius: 20,
offset: const Offset(0, 8),
),
BoxShadow(
color: goldStart.withValues(alpha: 0.2),
blurRadius: 8,
offset: const Offset(0, 4),
),
]
: [
BoxShadow(
color: colorScheme.shadow.withValues(alpha: 0.08),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: _isLoading ? null : _doTransfer,
borderRadius: BorderRadius.circular(20),
child: Center(
child: _isLoading
? SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2.5,
valueColor: AlwaysStoppedAnimation<Color>(
isDark ? Colors.white : colorScheme.onSurface,
),
),
)
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
LucideIcons.arrowRight,
size: 22,
color: hasAmount ? Colors.white : colorScheme.onSurfaceVariant,
),
const SizedBox(width: 8),
Text(
'確認劃轉',
style: TextStyle(
color: hasAmount ? Colors.white : colorScheme.onSurfaceVariant,
fontSize: 18,
fontWeight: FontWeight.w700,
letterSpacing: 0.5,
),
),
],
),
),
),
),
);
}
// ============================================
// Helpers
// ============================================
String _formatBalance(String balance) {
final val = double.tryParse(balance);
if (val == null) return '0.00';
return val.toStringAsFixed(2);
}
}
// ============================================
// Animated Account Card Widget
// ============================================
class _AnimatedAccountCard extends StatelessWidget {
final String label;
final String accountName;
final String balance;
final IconData icon;
final bool isSource;
final Animation<double> animation;
const _AnimatedAccountCard({
super.key,
required this.label,
required this.accountName,
required this.balance,
required this.icon,
required this.isSource,
required this.animation,
});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final isDark = colorScheme.brightness == Brightness.dark;
final goldColor = isDark ? const Color(0xFFD4AF37) : const Color(0xFFF59E0B);
return AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, animation.value * (isSource ? -10 : 10)),
child: Opacity(
opacity: 1.0 - (animation.value * 0.3),
child: child,
),
);
},
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: isDark
? colorScheme.surfaceContainer.withValues(alpha: 0.5)
: colorScheme.surface.withValues(alpha: 0.8),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: colorScheme.outlineVariant.withValues(alpha: 0.2),
width: 1,
),
),
child: Row(
children: [
// Icon container with gradient
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
goldColor.withValues(alpha: 0.15),
goldColor.withValues(alpha: 0.05),
],
),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: goldColor.withValues(alpha: 0.2),
width: 1,
),
),
child: Icon(
icon,
size: 22,
color: goldColor,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: AppTextStyles.bodySmall(context).copyWith(
color: colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(
accountName,
style: AppTextStyles.headlineSmall(context).copyWith(
fontWeight: FontWeight.w600,
),
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'¥ $balance',
style: AppTextStyles.headlineSmall(context).copyWith(
fontWeight: FontWeight.w700,
color: colorScheme.onSurface,
),
),
const SizedBox(height: 4),
Text(
'USDT',
style: AppTextStyles.bodySmall(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
),
],
),
),
);
}
}