Merge remote-tracking branch 'origin/main'
# Conflicts: # flutter_monisuo/lib/ui/pages/asset/asset_page.dart # flutter_monisuo/lib/ui/pages/orders/fund_orders_page.dart
This commit is contained in:
@@ -1,23 +1,20 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../../core/theme/app_color_scheme.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../../core/theme/app_spacing.dart';
|
||||
import '../../../core/utils/toast_utils.dart';
|
||||
import '../../../core/event/app_event_bus.dart';
|
||||
import '../../../providers/asset_provider.dart';
|
||||
import '../../../providers/auth_provider.dart';
|
||||
import '../../shared/ui_constants.dart';
|
||||
import '../../components/glass_panel.dart';
|
||||
import '../../components/neon_glow.dart';
|
||||
import 'components/account_tab_switcher.dart';
|
||||
import 'components/action_buttons_row.dart';
|
||||
import 'components/asset_dialogs.dart';
|
||||
import 'components/balance_card.dart';
|
||||
import 'components/holdings_section.dart';
|
||||
import 'components/records_link_row.dart';
|
||||
import '../orders/fund_orders_page.dart';
|
||||
import 'transfer_page.dart';
|
||||
|
||||
/// 资产页面 - Material Design 3 风格
|
||||
/// 资产页面 - Matching .pen design spec (CMcqs)
|
||||
class AssetPage extends StatefulWidget {
|
||||
const AssetPage({super.key});
|
||||
|
||||
@@ -75,227 +72,54 @@ class _AssetPageState extends State<AssetPage> with AutomaticKeepAliveClientMixi
|
||||
backgroundColor: colorScheme.surfaceContainerHighest,
|
||||
child: SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
padding: AppSpacing.pagePadding,
|
||||
padding: const EdgeInsets.fromLTRB(AppSpacing.md, AppSpacing.md + 8, AppSpacing.md, 32),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_TabSelector(
|
||||
tabs: const ['资金账户', '交易账户'],
|
||||
// Page title: "资产" 22px bold — matching .pen titleFrame padding [16,0,8,0]
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 16, bottom: 8),
|
||||
child: Text(
|
||||
'资产',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
// Account tab switcher — pill-style matching .pen UE6xC
|
||||
AccountTabSwitcher(
|
||||
selectedIndex: _activeTab,
|
||||
onChanged: (index) => setState(() => _activeTab = index),
|
||||
),
|
||||
SizedBox(height: AppSpacing.md),
|
||||
_activeTab == 0
|
||||
? _FundAccountCard(provider: provider)
|
||||
: _TradeAccountCard(
|
||||
holdings: provider.holdings,
|
||||
tradeBalance: provider.overview?.tradeBalance,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
// Balance card — matching .pen 59637 (cornerRadius lg, stroke, padding 20, gap 12)
|
||||
BalanceCard(
|
||||
provider: provider,
|
||||
activeTab: _activeTab,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
// Action buttons row — matching .pen pIpHe (gap 12)
|
||||
ActionButtonsRow(
|
||||
onDeposit: () => showDepositDialog(context),
|
||||
onWithdraw: () => showWithdrawDialog(context, provider.fundAccount?.balance),
|
||||
onTransfer: () => _navigateToTransfer(context),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
// Records link row — matching .pen fLHtq (cornerRadius lg, padding [14,16], stroke)
|
||||
RecordsLinkRow(
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const FundOrdersPage()),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
// Holdings section — matching .pen th9BG + 6X6tC
|
||||
HoldingsSection(holdings: _activeTab == 1 ? provider.holdings : []),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 资产总览卡片 - Material Design 3 风格
|
||||
class _AssetCard extends StatelessWidget {
|
||||
final dynamic overview;
|
||||
|
||||
const _AssetCard({required this.overview});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.all(AppSpacing.lg + AppSpacing.sm),
|
||||
decoration: BoxDecoration(
|
||||
gradient: AppColorScheme.assetCardGradient,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: colorScheme.primary.withOpacity(isDark ? 0.15 : 0.08),
|
||||
blurRadius: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'PORTFOLIO VALUE',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0.2,
|
||||
color: Colors.white.withOpacity(0.7),
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
Text(
|
||||
'\$${overview?.totalAsset ?? '0.00'}',
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.md),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.md,
|
||||
vertical: AppSpacing.xs + AppSpacing.xs,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(AppRadius.full),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
LucideIcons.trendingUp,
|
||||
color: Colors.white.withOpacity(0.7),
|
||||
size: 14,
|
||||
),
|
||||
SizedBox(width: AppSpacing.xs),
|
||||
Text(
|
||||
'总盈亏: ${overview?.totalProfit ?? '0.00'} USDT',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.white.withOpacity(0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tab 选择器 - Material Design 3 风格
|
||||
class _TabSelector extends StatelessWidget {
|
||||
final List<String> tabs;
|
||||
final int selectedIndex;
|
||||
final ValueChanged<int> onChanged;
|
||||
|
||||
const _TabSelector({
|
||||
required this.tabs,
|
||||
required this.selectedIndex,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.all(AppSpacing.xs),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
),
|
||||
child: Row(
|
||||
children: tabs.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final label = entry.value;
|
||||
final isSelected = index == selectedIndex;
|
||||
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => onChanged(index),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: EdgeInsets.symmetric(vertical: AppSpacing.sm + AppSpacing.xs),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? colorScheme.primary : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
boxShadow: isSelected
|
||||
? [
|
||||
BoxShadow(
|
||||
color: colorScheme.primary.withOpacity(isDark ? 0.15 : 0.08),
|
||||
blurRadius: 10,
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: isSelected ? colorScheme.onPrimary : colorScheme.onSurfaceVariant,
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 资金账户卡片 - Glass Panel 风格
|
||||
class _FundAccountCard extends StatelessWidget {
|
||||
final AssetProvider provider;
|
||||
|
||||
const _FundAccountCard({required this.provider});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final fund = provider.fundAccount;
|
||||
final overview = provider.overview;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
// 优先使用fund数据,如果为null则使用overview的fundBalance
|
||||
final displayBalance = fund?.balance ?? overview?.fundBalance ?? '0.00';
|
||||
|
||||
return GlassPanel(
|
||||
padding: EdgeInsets.all(AppSpacing.lg + AppSpacing.xs),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'USDT 余额',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const FundOrdersPage()),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'订单记录',
|
||||
style: TextStyle(
|
||||
color: colorScheme.primary,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
LucideIcons.chevronRight,
|
||||
size: 14,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
@@ -1202,57 +1026,13 @@ void _showWithdrawDialog(BuildContext context, String? balance) {
|
||||
);
|
||||
}
|
||||
|
||||
void _navigateToTransfer(BuildContext context) async {
|
||||
final result = await Navigator.push<bool>(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const TransferPage()),
|
||||
);
|
||||
// 如果划转成功,刷新数据
|
||||
if (result == true && context.mounted) {
|
||||
context.read<AssetProvider>().refreshAll(force: true);
|
||||
void _navigateToTransfer(BuildContext context) async {
|
||||
final result = await Navigator.push<bool>(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const TransferPage()),
|
||||
);
|
||||
if (result == true && context.mounted) {
|
||||
context.read<AssetProvider>().refreshAll(force: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _showResultDialog(BuildContext context, String title, String? message) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
showShadDialog(
|
||||
context: context,
|
||||
builder: (ctx) => Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: GlassPanel(
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
padding: EdgeInsets.all(AppSpacing.lg),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(title,
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
)),
|
||||
if (message != null) ...[
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
Text(message,
|
||||
style: TextStyle(color: colorScheme.onSurfaceVariant),
|
||||
textAlign: TextAlign.center),
|
||||
],
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: NeonButton(
|
||||
text: '确定',
|
||||
type: NeonButtonType.primary,
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
height: 44,
|
||||
showGlow: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../../../core/theme/app_spacing.dart';
|
||||
|
||||
/// 账户标签切换器 — .pen node UE6xC
|
||||
/// height: 40, padding: 3, cornerRadius: md, fill: $bg-tertiary
|
||||
/// activeTab: fill $bg-primary, cornerRadius sm, shadow blur 3, color #0000000D, offset y 1
|
||||
/// activeTabText: 14px, fontWeight 600, fill $text-primary
|
||||
/// inactiveTabText: 14px, fontWeight 500, fill $text-secondary
|
||||
class AccountTabSwitcher extends StatelessWidget {
|
||||
final int selectedIndex;
|
||||
final ValueChanged<int> onChanged;
|
||||
|
||||
const AccountTabSwitcher({
|
||||
super.key,
|
||||
required this.selectedIndex,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
return Container(
|
||||
height: 40,
|
||||
padding: const EdgeInsets.all(3),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? colorScheme.surfaceContainerHighest : colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
_buildTab(
|
||||
context: context,
|
||||
label: '资金账户',
|
||||
isSelected: selectedIndex == 0,
|
||||
onTap: () => onChanged(0),
|
||||
isDark: isDark,
|
||||
),
|
||||
_buildTab(
|
||||
context: context,
|
||||
label: '交易账户',
|
||||
isSelected: selectedIndex == 1,
|
||||
onTap: () => onChanged(1),
|
||||
isDark: isDark,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTab({
|
||||
required BuildContext context,
|
||||
required String label,
|
||||
required bool isSelected,
|
||||
required VoidCallback onTap,
|
||||
required bool isDark,
|
||||
}) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? colorScheme.surface
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(AppRadius.sm),
|
||||
boxShadow: isSelected
|
||||
? [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.05),
|
||||
blurRadius: 3,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
label,
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 14,
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500,
|
||||
color: isSelected ? colorScheme.onSurface : colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
|
||||
/// 操作按钮行 — .pen node pIpHe
|
||||
/// gap: 12, three buttons evenly distributed
|
||||
/// Each button: circle 48x48 fill $bg-tertiary, cornerRadius 24
|
||||
/// icon: 20px $accent-primary (lucide: arrow-up-right / arrow-down-left / repeat)
|
||||
/// label: 12px w500 $text-secondary
|
||||
class ActionButtonsRow extends StatelessWidget {
|
||||
final VoidCallback onDeposit;
|
||||
final VoidCallback onWithdraw;
|
||||
final VoidCallback onTransfer;
|
||||
|
||||
const ActionButtonsRow({
|
||||
super.key,
|
||||
required this.onDeposit,
|
||||
required this.onWithdraw,
|
||||
required this.onTransfer,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final accentColor = isDark ? colorScheme.secondary : colorScheme.primary;
|
||||
final bgColor = isDark ? colorScheme.surfaceContainerHighest : colorScheme.surfaceContainerHigh;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
ActionButton(
|
||||
icon: LucideIcons.arrowUpRight,
|
||||
label: '充值',
|
||||
accentColor: accentColor,
|
||||
bgColor: bgColor,
|
||||
onTap: onDeposit,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
ActionButton(
|
||||
icon: LucideIcons.arrowDownLeft,
|
||||
label: '提现',
|
||||
accentColor: accentColor,
|
||||
bgColor: bgColor,
|
||||
onTap: onWithdraw,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
ActionButton(
|
||||
icon: LucideIcons.repeat,
|
||||
label: '划转',
|
||||
accentColor: accentColor,
|
||||
bgColor: bgColor,
|
||||
onTap: onTransfer,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 单个操作按钮 — matching .pen btn1/btn2/btn3
|
||||
class ActionButton extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final Color accentColor;
|
||||
final Color bgColor;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const ActionButton({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.accentColor,
|
||||
required this.bgColor,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Icon(
|
||||
icon,
|
||||
size: 20,
|
||||
color: accentColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
label,
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
602
flutter_monisuo/lib/ui/pages/asset/components/asset_dialogs.dart
Normal file
602
flutter_monisuo/lib/ui/pages/asset/components/asset_dialogs.dart
Normal file
@@ -0,0 +1,602 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../../../core/theme/app_color_scheme.dart';
|
||||
import '../../../../core/theme/app_spacing.dart';
|
||||
import '../../../../core/utils/toast_utils.dart';
|
||||
import '../../../../providers/asset_provider.dart';
|
||||
import '../../../components/glass_panel.dart';
|
||||
import '../../../components/neon_glow.dart';
|
||||
import '../../../shared/ui_constants.dart';
|
||||
|
||||
// ============================================
|
||||
// Dialog helpers — shared sub-widgets
|
||||
// ============================================
|
||||
|
||||
/// 信息行 — 用于对话框中显示 label/value 键值对
|
||||
class InfoRow extends StatelessWidget {
|
||||
final String label;
|
||||
final String value;
|
||||
final bool isBold;
|
||||
|
||||
const InfoRow({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.value,
|
||||
this.isBold = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 钱包地址卡片 — 用于充值结果对话框中展示钱包地址
|
||||
class WalletAddressCard extends StatelessWidget {
|
||||
final String address;
|
||||
final String network;
|
||||
|
||||
const WalletAddressCard({
|
||||
super.key,
|
||||
required this.address,
|
||||
required this.network,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(AppSpacing.md),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
border: Border.all(
|
||||
color: colorScheme.outlineVariant.withValues(alpha: 0.3),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
address,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: address));
|
||||
ToastUtils.show('地址已复制到剪贴板');
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(AppSpacing.xs),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primary.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(AppRadius.sm),
|
||||
),
|
||||
child: Icon(
|
||||
LucideIcons.copy,
|
||||
size: 16,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
Text(
|
||||
'网络: $network',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 11,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Dialog functions — kept from original with style updates
|
||||
// ============================================
|
||||
|
||||
/// 充值对话框
|
||||
void showDepositDialog(BuildContext context) {
|
||||
final amountController = TextEditingController();
|
||||
final formKey = GlobalKey<ShadFormState>();
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
showShadDialog(
|
||||
context: context,
|
||||
builder: (ctx) => Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: GlassPanel(
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'充值',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.xs),
|
||||
Text(
|
||||
'Asset: USDT',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(AppSpacing.sm),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
),
|
||||
child: Icon(
|
||||
LucideIcons.wallet,
|
||||
color: colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
ShadForm(
|
||||
key: formKey,
|
||||
child: ShadInputFormField(
|
||||
id: 'amount',
|
||||
controller: amountController,
|
||||
label: const Text('充值金额'),
|
||||
placeholder: const Text('最低 1000 USDT'),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
validator: (v) {
|
||||
if (v == null || v.isEmpty) return '请输入金额';
|
||||
final n = double.tryParse(v);
|
||||
if (n == null || n <= 0) return '请输入有效金额';
|
||||
if (n < 1000) return '单笔最低充值1000 USDT';
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: NeonButton(
|
||||
text: '取消',
|
||||
type: NeonButtonType.outline,
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
height: 48,
|
||||
showGlow: false,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
Expanded(
|
||||
child: NeonButton(
|
||||
text: '下一步',
|
||||
type: NeonButtonType.primary,
|
||||
onPressed: () async {
|
||||
if (formKey.currentState!.saveAndValidate()) {
|
||||
Navigator.of(ctx).pop();
|
||||
final response = await context.read<AssetProvider>().deposit(
|
||||
amount: amountController.text,
|
||||
);
|
||||
if (context.mounted) {
|
||||
if (response.success && response.data != null) {
|
||||
showDepositResultDialog(context, response.data!);
|
||||
} else {
|
||||
showResultDialog(context, '申请失败', response.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
height: 48,
|
||||
showGlow: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 充值结果对话框 — 展示钱包地址和确认打款
|
||||
void showDepositResultDialog(BuildContext context, Map<String, dynamic> data) {
|
||||
final orderNo = data['orderNo'] as String? ?? '';
|
||||
final amount = data['amount']?.toString() ?? '0.00';
|
||||
final walletAddress = data['walletAddress'] as String? ?? '';
|
||||
final walletNetwork = data['walletNetwork'] as String? ?? 'TRC20';
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
showShadDialog(
|
||||
context: context,
|
||||
builder: (ctx) => Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: GlassPanel(
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
NeonIcon(
|
||||
icon: Icons.check_circle,
|
||||
color: AppColorScheme.getUpColor(isDark),
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
Text(
|
||||
'充值申请成功',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
InfoRow(label: '订单号', value: orderNo),
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
InfoRow(label: '充值金额', value: '$amount USDT', isBold: true),
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
Text(
|
||||
'请向以下地址转账:',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
WalletAddressCard(address: walletAddress, network: walletNetwork),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(AppSpacing.sm),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColorScheme.warning.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
border: Border.all(
|
||||
color: AppColorScheme.warning.withValues(alpha: 0.2),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.info_outline, size: 16, color: AppColorScheme.warning),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'转账完成后请点击"已打款"按钮确认',
|
||||
style: GoogleFonts.inter(fontSize: 12, color: AppColorScheme.warning),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: NeonButton(
|
||||
text: '稍后确认',
|
||||
type: NeonButtonType.outline,
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
height: 44,
|
||||
showGlow: false,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
Expanded(
|
||||
child: NeonButton(
|
||||
text: '已打款',
|
||||
type: NeonButtonType.primary,
|
||||
onPressed: () async {
|
||||
Navigator.of(ctx).pop();
|
||||
final response = await context.read<AssetProvider>().confirmPay(orderNo);
|
||||
if (context.mounted) {
|
||||
showResultDialog(
|
||||
context,
|
||||
response.success ? '确认成功' : '确认失败',
|
||||
response.success ? '请等待管理员审核' : response.message,
|
||||
);
|
||||
}
|
||||
},
|
||||
height: 44,
|
||||
showGlow: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 提现对话框
|
||||
void showWithdrawDialog(BuildContext context, String? balance) {
|
||||
final amountController = TextEditingController();
|
||||
final addressController = TextEditingController();
|
||||
final contactController = TextEditingController();
|
||||
final formKey = GlobalKey<ShadFormState>();
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
showShadDialog(
|
||||
context: context,
|
||||
builder: (ctx) => Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: GlassPanel(
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(AppSpacing.sm),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primary.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
),
|
||||
child: Icon(
|
||||
LucideIcons.wallet,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
Text(
|
||||
'提现',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppSpacing.xs),
|
||||
Text(
|
||||
'安全地将您的资产转移到外部钱包地址',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
if (balance != null) ...[
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.md,
|
||||
vertical: AppSpacing.sm,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColorScheme.up.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(AppRadius.full),
|
||||
border: Border.all(
|
||||
color: AppColorScheme.up.withValues(alpha: 0.2),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'可用余额: ',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 10,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'$balance USDT',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColorScheme.up,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
ShadForm(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
ShadInputFormField(
|
||||
id: 'amount',
|
||||
controller: amountController,
|
||||
label: const Text('提现金额'),
|
||||
placeholder: const Text('请输入提现金额(USDT)'),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
validator: Validators.amount,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
ShadInputFormField(
|
||||
id: 'address',
|
||||
controller: addressController,
|
||||
label: const Text('目标地址'),
|
||||
placeholder: const Text('请输入提现地址'),
|
||||
validator: (v) => Validators.required(v, '提现地址'),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
ShadInputFormField(
|
||||
id: 'contact',
|
||||
controller: contactController,
|
||||
label: const Text('联系方式(可选)'),
|
||||
placeholder: const Text('联系方式'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: NeonButton(
|
||||
text: '取消',
|
||||
type: NeonButtonType.outline,
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
height: 44,
|
||||
showGlow: false,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
Expanded(
|
||||
child: NeonButton(
|
||||
text: '提交',
|
||||
type: NeonButtonType.primary,
|
||||
onPressed: () async {
|
||||
if (formKey.currentState!.saveAndValidate()) {
|
||||
Navigator.of(ctx).pop();
|
||||
final response = await context.read<AssetProvider>().withdraw(
|
||||
amount: amountController.text,
|
||||
withdrawAddress: addressController.text,
|
||||
withdrawContact: contactController.text.isNotEmpty
|
||||
? contactController.text
|
||||
: null,
|
||||
);
|
||||
if (context.mounted) {
|
||||
showResultDialog(
|
||||
context,
|
||||
response.success ? '申请成功' : '申请失败',
|
||||
response.success ? '请等待管理员审批' : response.message,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
height: 44,
|
||||
showGlow: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.verified_user,
|
||||
size: 12,
|
||||
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5),
|
||||
),
|
||||
const SizedBox(width: AppSpacing.xs),
|
||||
Text(
|
||||
'End-to-End Encrypted Transaction',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 10,
|
||||
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 通用结果对话框 — 展示操作成功/失败信息
|
||||
void showResultDialog(BuildContext context, String title, String? message) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
showShadDialog(
|
||||
context: context,
|
||||
builder: (ctx) => Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: GlassPanel(
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
if (message != null) ...[
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
Text(
|
||||
message,
|
||||
style: GoogleFonts.inter(color: colorScheme.onSurfaceVariant),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: NeonButton(
|
||||
text: '确定',
|
||||
type: NeonButtonType.primary,
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
height: 44,
|
||||
showGlow: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../../../core/theme/app_color_scheme.dart';
|
||||
import '../../../../core/theme/app_spacing.dart';
|
||||
import '../../../../providers/asset_provider.dart';
|
||||
import '../../../components/glass_panel.dart';
|
||||
|
||||
/// 余额卡片 — .pen node 59637
|
||||
/// cornerRadius: lg, fill: $surface-card, padding: 20, stroke: $border-default 1px, gap: 12
|
||||
/// balLabel: "USDT 余额" 12px normal $text-secondary
|
||||
/// balAmount: "25,680.50" 28px w700 $text-primary
|
||||
/// balSubRow: "≈ $25,680.50 USD" 12px normal $text-muted
|
||||
class BalanceCard extends StatelessWidget {
|
||||
final AssetProvider provider;
|
||||
final int activeTab;
|
||||
|
||||
const BalanceCard({
|
||||
super.key,
|
||||
required this.provider,
|
||||
required this.activeTab,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
final displayBalance = activeTab == 0
|
||||
? (provider.fundAccount?.balance ?? provider.overview?.fundBalance ?? '0.00')
|
||||
: _calculateTradeTotal();
|
||||
|
||||
return GlassPanel(
|
||||
padding: const EdgeInsets.all(20),
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'USDT 余额',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
_formatBalance(displayBalance),
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'\u2248 \$${_formatBalance(displayBalance)} USD',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: isDark ? AppColorScheme.darkOnSurfaceMuted : colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _calculateTradeTotal() {
|
||||
double total = 0;
|
||||
for (var h in provider.holdings) {
|
||||
total += double.tryParse(h.currentValue?.toString() ?? '0') ?? 0;
|
||||
}
|
||||
return total.toStringAsFixed(2);
|
||||
}
|
||||
|
||||
String _formatBalance(String balance) {
|
||||
final d = double.tryParse(balance) ?? 0;
|
||||
return d.toStringAsFixed(2).replaceAllMapped(
|
||||
RegExp(r'\B(?=(\d{3})+(?!\d))'),
|
||||
(Match m) => ',',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../../../core/theme/app_color_scheme.dart';
|
||||
import '../../../../core/theme/app_spacing.dart';
|
||||
import '../../../../data/models/account_models.dart';
|
||||
import '../../../components/glass_panel.dart';
|
||||
|
||||
/// 持仓区域 — .pen nodes th9BG (header) + 6X6tC (card)
|
||||
/// Holdings Header: "交易账户持仓" 16px w600 $text-primary | "查看全部 >" 12px normal $text-secondary
|
||||
/// Holdings Card: cornerRadius lg, fill $surface-card, stroke $border-default 1px
|
||||
class HoldingsSection extends StatelessWidget {
|
||||
final List holdings;
|
||||
|
||||
const HoldingsSection({super.key, required this.holdings});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// Header row: "交易账户持仓" + "查看全部 >"
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: AppSpacing.sm),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'交易账户持仓',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'查看全部 >',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Holdings card — uses real provider.holdings data
|
||||
if (holdings.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(AppSpacing.xl),
|
||||
child: Text(
|
||||
'暂无持仓',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 13,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
GlassPanel(
|
||||
padding: EdgeInsets.zero,
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
child: Column(
|
||||
children: List.generate(holdings.length, (index) {
|
||||
final h = holdings[index] as AccountTrade;
|
||||
final isProfit = h.profitRate >= 0;
|
||||
return Column(
|
||||
children: [
|
||||
HoldingRow(
|
||||
coinCode: h.coinCode,
|
||||
quantity: double.tryParse(h.quantity)?.toStringAsFixed(4) ?? h.quantity,
|
||||
value: '${double.tryParse(h.currentValue)?.toStringAsFixed(2) ?? h.currentValue} USDT',
|
||||
profitRate: '${isProfit ? '+' : ''}${h.profitRate.toStringAsFixed(2)}%',
|
||||
isProfit: isProfit,
|
||||
),
|
||||
if (index < holdings.length - 1) const HoldingDivider(),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 持仓行分隔线 — .pen node BCCbR / yejhE
|
||||
/// fill: $border-default, height: 1, opacity: 0.5
|
||||
class HoldingDivider extends StatelessWidget {
|
||||
const HoldingDivider({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
return Container(
|
||||
height: 1,
|
||||
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
||||
color: colorScheme.outlineVariant.withValues(alpha: 0.5),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 持仓行 — matching .pen nodes dAt4j / eK6vq / jiSUK
|
||||
/// padding [14, 16], space_between layout
|
||||
/// Left: avatar circle (36x36, radius 18, fill $accent-light) + coin info (gap 2)
|
||||
/// Right: value + pnl (gap 2, align end)
|
||||
class HoldingRow extends StatelessWidget {
|
||||
final String coinCode;
|
||||
final String quantity;
|
||||
final String value;
|
||||
final String profitRate;
|
||||
final bool isProfit;
|
||||
|
||||
const HoldingRow({
|
||||
super.key,
|
||||
required this.coinCode,
|
||||
required this.quantity,
|
||||
required this.value,
|
||||
required this.profitRate,
|
||||
required this.isProfit,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final accentColor = isDark ? colorScheme.secondary : colorScheme.primary;
|
||||
final accentBgColor = accentColor.withValues(alpha: 0.1);
|
||||
final profitColor = isProfit ? AppColorScheme.getUpColor(isDark) : AppColorScheme.getDownColor(isDark);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md, vertical: 14),
|
||||
child: Row(
|
||||
children: [
|
||||
// Avatar circle with first letter — .pen SJNDJ/EjSIN/3GQ5M
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: accentBgColor,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
coinCode.substring(0, 1),
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: accentColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
// Coin name + quantity — .pen fivxJ/Kxv3d/5CsoQ
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
coinCode,
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
quantity,
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Value + profit rate — .pen vYJsU/2nLAg/IlWck
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
value,
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
profitRate,
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: profitColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import '../../../../core/theme/app_color_scheme.dart';
|
||||
import '../../../../core/theme/app_spacing.dart';
|
||||
import '../../../components/glass_panel.dart';
|
||||
|
||||
/// 充提记录链接行 — .pen node fLHtq
|
||||
/// cornerRadius: lg, fill: $surface-card, padding: [14, 16], stroke: $border-default 1px
|
||||
/// recordsText: "充提记录" 14px w500 $text-primary
|
||||
/// recordsChevron: lucide chevron-right 16px $text-muted
|
||||
class RecordsLinkRow extends StatelessWidget {
|
||||
final VoidCallback onTap;
|
||||
|
||||
const RecordsLinkRow({super.key, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final mutedColor = isDark ? AppColorScheme.darkOnSurfaceMuted : colorScheme.onSurfaceVariant;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: GlassPanel(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md, vertical: 14),
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'充提记录',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
LucideIcons.chevronRight,
|
||||
size: 16,
|
||||
color: mutedColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../../core/theme/app_color_scheme.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import '../../../core/theme/app_spacing.dart';
|
||||
import '../../../providers/asset_provider.dart';
|
||||
import '../../../data/models/account_models.dart';
|
||||
import '../../shared/ui_constants.dart';
|
||||
import '../../components/neon_glow.dart';
|
||||
|
||||
/// 划转页面 - 币安风格
|
||||
/// 划转页面
|
||||
class TransferPage extends StatefulWidget {
|
||||
const TransferPage({super.key});
|
||||
|
||||
@@ -20,6 +17,7 @@ class TransferPage extends StatefulWidget {
|
||||
|
||||
class _TransferPageState extends State<TransferPage> {
|
||||
final _amountController = TextEditingController();
|
||||
final _focusNode = FocusNode();
|
||||
int _direction = 1; // 1: 资金→交易, 2: 交易→资金
|
||||
bool _isLoading = false;
|
||||
|
||||
@@ -34,9 +32,14 @@ class _TransferPageState extends State<TransferPage> {
|
||||
@override
|
||||
void dispose() {
|
||||
_amountController.dispose();
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 数据访问
|
||||
// ============================================
|
||||
|
||||
/// 获取资金账户余额
|
||||
String get _fundBalance {
|
||||
final provider = context.read<AssetProvider>();
|
||||
@@ -64,9 +67,7 @@ class _TransferPageState extends State<TransferPage> {
|
||||
}
|
||||
|
||||
/// 获取当前可用余额(根据方向)
|
||||
String get _availableBalance {
|
||||
return _direction == 1 ? _fundBalance : _tradeUsdtBalance;
|
||||
}
|
||||
String get _availableBalance => _direction == 1 ? _fundBalance : _tradeUsdtBalance;
|
||||
|
||||
/// 从账户名
|
||||
String get _fromLabel => _direction == 1 ? '资金账户' : '交易账户';
|
||||
@@ -74,6 +75,27 @@ class _TransferPageState extends State<TransferPage> {
|
||||
String get _fromBalance => _direction == 1 ? _fundBalance : _tradeUsdtBalance;
|
||||
String get _toBalance => _direction == 1 ? _tradeUsdtBalance : _fundBalance;
|
||||
|
||||
// ============================================
|
||||
// 主题辅助
|
||||
// ============================================
|
||||
|
||||
bool get _isDark => Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
/// 一次性获取所有主题感知颜色
|
||||
_TransferColors get _colors => _TransferColors(_isDark);
|
||||
|
||||
TextStyle _inter({
|
||||
required double fontSize,
|
||||
required FontWeight fontWeight,
|
||||
required Color color,
|
||||
}) {
|
||||
return GoogleFonts.inter(fontSize: fontSize, fontWeight: fontWeight, color: color);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 业务逻辑
|
||||
// ============================================
|
||||
|
||||
/// 执行划转
|
||||
Future<void> _doTransfer() async {
|
||||
final amount = _amountController.text;
|
||||
@@ -124,7 +146,6 @@ class _TransferPageState extends State<TransferPage> {
|
||||
void _setQuickAmount(double percent) {
|
||||
final available = double.tryParse(_availableBalance) ?? 0;
|
||||
final amount = available * percent;
|
||||
// 保留8位小数,去除末尾0
|
||||
_amountController.text = amount.toStringAsFixed(8).replaceAll(RegExp(r'\.?0+$'), '');
|
||||
}
|
||||
|
||||
@@ -135,162 +156,43 @@ class _TransferPageState extends State<TransferPage> {
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 构建 UI
|
||||
// ============================================
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final c = _colors;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: colorScheme.background,
|
||||
backgroundColor: c.bgSecondary,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
backgroundColor: c.surfaceCard,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back, color: colorScheme.onSurface),
|
||||
icon: Icon(LucideIcons.arrowLeft, color: c.textPrimary, size: 20),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
title: Text(
|
||||
'资金划转',
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
'账户划转',
|
||||
style: _inter(fontSize: 16, fontWeight: FontWeight.w600, color: c.textPrimary),
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: Consumer<AssetProvider>(
|
||||
builder: (context, provider, _) {
|
||||
return SingleChildScrollView(
|
||||
padding: AppSpacing.pagePadding,
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 32),
|
||||
child: Column(
|
||||
children: [
|
||||
// 第一个卡片位置 - 带动画
|
||||
TweenAnimationBuilder<double>(
|
||||
tween: Tween(begin: 0, end: 1),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
builder: (context, value, child) {
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
switchInCurve: Curves.easeInOut,
|
||||
switchOutCurve: Curves.easeInOut,
|
||||
transitionBuilder: (widget, animation) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, -1),
|
||||
end: Offset.zero,
|
||||
).animate(animation),
|
||||
child: FadeTransition(
|
||||
opacity: animation,
|
||||
child: widget,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: _direction == 1
|
||||
? _buildAccountCard(
|
||||
key: const ValueKey('from-card'),
|
||||
label: '从',
|
||||
accountName: _fromLabel,
|
||||
balance: _fromBalance,
|
||||
isDark: isDark,
|
||||
colorScheme: colorScheme,
|
||||
)
|
||||
: _buildAccountCard(
|
||||
key: const ValueKey('to-card-top'),
|
||||
label: '到',
|
||||
accountName: _toLabel,
|
||||
balance: _toBalance,
|
||||
isDark: isDark,
|
||||
colorScheme: colorScheme,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// 方向切换按钮(固定在中间)
|
||||
GestureDetector(
|
||||
onTap: _toggleDirection,
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(vertical: AppSpacing.sm),
|
||||
padding: EdgeInsets.all(AppSpacing.sm + AppSpacing.xs),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primary,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: colorScheme.primary.withOpacity(0.3),
|
||||
blurRadius: 8,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Icon(
|
||||
Icons.swap_vert,
|
||||
color: colorScheme.onPrimary,
|
||||
size: 22,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 第二个卡片位置 - 带动画
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
switchInCurve: Curves.easeInOut,
|
||||
switchOutCurve: Curves.easeInOut,
|
||||
transitionBuilder: (widget, animation) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, 1),
|
||||
end: Offset.zero,
|
||||
).animate(animation),
|
||||
child: FadeTransition(
|
||||
opacity: animation,
|
||||
child: widget,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: _direction == 1
|
||||
? _buildAccountCard(
|
||||
key: const ValueKey('to-card'),
|
||||
label: '到',
|
||||
accountName: _toLabel,
|
||||
balance: _toBalance,
|
||||
isDark: isDark,
|
||||
colorScheme: colorScheme,
|
||||
)
|
||||
: _buildAccountCard(
|
||||
key: const ValueKey('from-card-bottom'),
|
||||
label: '从',
|
||||
accountName: _fromLabel,
|
||||
balance: _fromBalance,
|
||||
isDark: isDark,
|
||||
colorScheme: colorScheme,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
|
||||
// 金额输入卡片
|
||||
_buildAmountSection(colorScheme, isDark),
|
||||
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
|
||||
// 确认按钮
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: NeonButton(
|
||||
text: _isLoading ? '处理中...' : '确认划转',
|
||||
icon: _isLoading ? null : LucideIcons.arrowRightLeft,
|
||||
type: NeonButtonType.primary,
|
||||
onPressed: _isLoading ? null : _doTransfer,
|
||||
height: 52,
|
||||
showGlow: true,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: AppSpacing.md),
|
||||
|
||||
// 划转说明
|
||||
_buildTips(colorScheme),
|
||||
_buildTransferDirectionCard(c),
|
||||
const SizedBox(height: 24),
|
||||
_buildAmountSection(c),
|
||||
const SizedBox(height: 24),
|
||||
_buildTipsCard(c),
|
||||
const SizedBox(height: 24),
|
||||
_buildConfirmButton(c),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -299,280 +201,317 @@ class _TransferPageState extends State<TransferPage> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 账户卡片
|
||||
Widget _buildAccountCard({
|
||||
Key? key,
|
||||
// ============================================
|
||||
// Transfer direction card
|
||||
// ============================================
|
||||
|
||||
Widget _buildTransferDirectionCard(_TransferColors c) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: c.surfaceCard,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
border: Border.all(color: c.borderDefault.withValues(alpha: 0.6)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Source account
|
||||
_animatedSwitcher(
|
||||
key: 'src-$_direction',
|
||||
beginOffset: const Offset(0, -1),
|
||||
child: _buildAccountRow(
|
||||
label: '从',
|
||||
accountName: _fromLabel,
|
||||
balance: _fromBalance,
|
||||
c: c,
|
||||
),
|
||||
),
|
||||
|
||||
// Swap button
|
||||
GestureDetector(
|
||||
onTap: _toggleDirection,
|
||||
child: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
margin: const EdgeInsets.symmetric(vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: c.accentPrimary,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(LucideIcons.arrowUpDown, size: 18, color: c.textInverse),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Destination account
|
||||
_animatedSwitcher(
|
||||
key: 'dst-$_direction',
|
||||
beginOffset: const Offset(0, 1),
|
||||
child: _buildAccountRow(
|
||||
label: '到',
|
||||
accountName: _toLabel,
|
||||
balance: _toBalance,
|
||||
c: c,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 统一的 AnimatedSwitcher 构造
|
||||
Widget _animatedSwitcher({
|
||||
required String key,
|
||||
required Offset beginOffset,
|
||||
required Widget child,
|
||||
}) {
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
switchInCurve: Curves.easeInOut,
|
||||
switchOutCurve: Curves.easeInOut,
|
||||
transitionBuilder: (widget, animation) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(begin: beginOffset, end: Offset.zero).animate(animation),
|
||||
child: FadeTransition(opacity: animation, child: widget),
|
||||
);
|
||||
},
|
||||
child: KeyedSubtree(key: ValueKey(key), child: child),
|
||||
);
|
||||
}
|
||||
|
||||
/// Single account row inside the direction card
|
||||
Widget _buildAccountRow({
|
||||
required String label,
|
||||
required String accountName,
|
||||
required String balance,
|
||||
required bool isDark,
|
||||
required ColorScheme colorScheme,
|
||||
required _TransferColors c,
|
||||
}) {
|
||||
return Container(
|
||||
key: key,
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.all(AppSpacing.md),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? colorScheme.surfaceContainer : colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
border: Border.all(
|
||||
color: colorScheme.outlineVariant.withOpacity(0.15),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label, style: _inter(fontSize: 11, fontWeight: FontWeight.normal, color: c.textMuted)),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: AppSpacing.sm, vertical: AppSpacing.xs),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(AppRadius.sm),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: AppSpacing.sm),
|
||||
Text(
|
||||
accountName,
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'可用余额',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
SizedBox(width: AppSpacing.xs),
|
||||
Text(
|
||||
'$balance USDT',
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 金额输入区域
|
||||
Widget _buildAmountSection(ColorScheme colorScheme, bool isDark) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.all(AppSpacing.md),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? colorScheme.surfaceContainer : colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
border: Border.all(
|
||||
color: colorScheme.outlineVariant.withOpacity(0.15),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'划转金额',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
// 金额输入行
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _amountController,
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,8}')),
|
||||
],
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: '0.00',
|
||||
hintStyle: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurfaceVariant.withOpacity(0.3),
|
||||
),
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
isDense: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: 4),
|
||||
child: Text(
|
||||
'USDT',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
// 快捷按钮行
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'可用: ${_availableBalance}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
_buildQuickButton('25%', 0.25, colorScheme),
|
||||
SizedBox(width: AppSpacing.xs),
|
||||
_buildQuickButton('50%', 0.50, colorScheme),
|
||||
SizedBox(width: AppSpacing.xs),
|
||||
_buildQuickButton('75%', 0.75, colorScheme),
|
||||
SizedBox(width: AppSpacing.xs),
|
||||
_buildQuickButton('全部', 1.0, colorScheme),
|
||||
],
|
||||
),
|
||||
if (_direction == 2) ...[
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
Container(
|
||||
padding: EdgeInsets.all(AppSpacing.sm),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColorScheme.warning.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(AppRadius.sm),
|
||||
),
|
||||
child: Row(
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
LucideIcons.triangleAlert,
|
||||
size: 14,
|
||||
color: AppColorScheme.warning,
|
||||
),
|
||||
SizedBox(width: AppSpacing.xs),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'仅支持 USDT 资产划转到资金账户',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: AppColorScheme.warning,
|
||||
),
|
||||
),
|
||||
label == '从' ? LucideIcons.wallet : LucideIcons.repeat,
|
||||
size: 18,
|
||||
color: c.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(accountName, style: _inter(fontSize: 14, fontWeight: FontWeight.w600, color: c.textPrimary)),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
'\u00A5 ${_formatBalance(balance)}',
|
||||
style: _inter(fontSize: 14, fontWeight: FontWeight.w600, color: c.textPrimary),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Amount input section
|
||||
// ============================================
|
||||
|
||||
Widget _buildAmountSection(_TransferColors c) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Label row: "划转金额" + "全部划转"
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('划转金额', style: _inter(fontSize: 14, fontWeight: FontWeight.w500, color: c.textSecondary)),
|
||||
GestureDetector(
|
||||
onTap: () => _setQuickAmount(1.0),
|
||||
child: Text('全部划转', style: _inter(fontSize: 12, fontWeight: FontWeight.w600, color: c.goldAccent)),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 快捷百分比按钮
|
||||
Widget _buildQuickButton(String label, double percent, ColorScheme colorScheme) {
|
||||
return GestureDetector(
|
||||
onTap: () => _setQuickAmount(percent),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: AppSpacing.sm, vertical: AppSpacing.xs),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(AppRadius.sm),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
const SizedBox(height: 12),
|
||||
|
||||
/// 划转说明
|
||||
Widget _buildTips(ColorScheme colorScheme) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(AppSpacing.md),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'划转说明',
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
_buildTipItem('资金账户用于充提,交易账户用于买卖币种', colorScheme),
|
||||
_buildTipItem('划转操作即时到账,不可撤销', colorScheme),
|
||||
_buildTipItem('交易账户只有 USDT 可直接划转到资金账户', colorScheme),
|
||||
_buildTipItem('其他币种需先卖出换成 USDT 后才能划转', colorScheme),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTipItem(String text, ColorScheme colorScheme) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: AppSpacing.xs),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 4,
|
||||
height: 4,
|
||||
margin: EdgeInsets.only(top: 6, right: AppSpacing.sm),
|
||||
// Amount input field
|
||||
GestureDetector(
|
||||
onTap: () => _focusNode.requestFocus(),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 56,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
shape: BoxShape.circle,
|
||||
color: c.bgTertiary,
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _amountController,
|
||||
focusNode: _focusNode,
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,8}')),
|
||||
],
|
||||
style: _inter(fontSize: 28, fontWeight: FontWeight.w700, color: c.textPrimary),
|
||||
decoration: InputDecoration(
|
||||
hintText: '0.00',
|
||||
hintStyle: _inter(fontSize: 28, fontWeight: FontWeight.w700, color: c.textMuted),
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
isDense: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
child: Text('USDT', style: _inter(fontSize: 14, fontWeight: FontWeight.normal, color: c.textMuted)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Percent buttons
|
||||
Row(
|
||||
children: [0.25, 0.50, 0.75, 1.0].asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final percent = entry.value;
|
||||
final label = '${(percent * 100).toInt()}%';
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(left: index > 0 ? 8 : 0),
|
||||
child: _buildPercentButton(label, percent, c),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPercentButton(String label, double percent, _TransferColors c) {
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => _setQuickAmount(percent),
|
||||
child: Container(
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: c.bgTertiary,
|
||||
borderRadius: BorderRadius.circular(AppRadius.sm),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(label, style: _inter(fontSize: 13, fontWeight: FontWeight.w500, color: c.textSecondary)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Tips card & Confirm button
|
||||
// ============================================
|
||||
|
||||
Widget _buildTipsCard(_TransferColors c) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: c.profitGreenBg,
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(LucideIcons.info, size: 16, color: c.profitGreen),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
'划转即时到账,无需手续费',
|
||||
style: _inter(fontSize: 12, fontWeight: FontWeight.normal, color: c.profitGreen),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildConfirmButton(_TransferColors c) {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
height: 52,
|
||||
child: GestureDetector(
|
||||
onTap: _isLoading ? null : _doTransfer,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: c.accentPrimary,
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
),
|
||||
child: Center(
|
||||
child: _isLoading
|
||||
? SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(c.textInverse),
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
'确认划转',
|
||||
style: _inter(fontSize: 16, fontWeight: FontWeight.w700, color: c.textInverse),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Helpers
|
||||
// ============================================
|
||||
|
||||
String _formatBalance(String balance) {
|
||||
final val = double.tryParse(balance);
|
||||
if (val == null) return '0.00';
|
||||
return val.toStringAsFixed(2);
|
||||
}
|
||||
}
|
||||
|
||||
/// 主题感知颜色集合,避免在 build() 中重复定义大量局部变量
|
||||
class _TransferColors {
|
||||
final Color bgSecondary;
|
||||
final Color surfaceCard;
|
||||
final Color bgTertiary;
|
||||
final Color borderDefault;
|
||||
final Color textPrimary;
|
||||
final Color textSecondary;
|
||||
final Color textMuted;
|
||||
final Color textInverse;
|
||||
final Color accentPrimary;
|
||||
final Color goldAccent;
|
||||
final Color profitGreen;
|
||||
final Color profitGreenBg;
|
||||
|
||||
_TransferColors(bool isDark)
|
||||
: bgSecondary = isDark ? const Color(0xFF0B1120) : const Color(0xFFF8FAFC),
|
||||
surfaceCard = isDark ? const Color(0xFF0F172A) : const Color(0xFFFFFFFF),
|
||||
bgTertiary = isDark ? const Color(0xFF1E293B) : const Color(0xFFF1F5F9),
|
||||
borderDefault = isDark ? const Color(0xFF334155) : const Color(0xFFE2E8F0),
|
||||
textPrimary = isDark ? const Color(0xFFF8FAFC) : const Color(0xFF0F172A),
|
||||
textSecondary = isDark ? const Color(0xFF94A3B8) : const Color(0xFF475569),
|
||||
textMuted = isDark ? const Color(0xFF64748B) : const Color(0xFF94A3B8),
|
||||
textInverse = isDark ? const Color(0xFF0F172A) : const Color(0xFFFFFFFF),
|
||||
accentPrimary = isDark ? const Color(0xFFD4AF37) : const Color(0xFF1F2937),
|
||||
goldAccent = isDark ? const Color(0xFFD4AF37) : const Color(0xFFF59E0B),
|
||||
profitGreen = isDark ? const Color(0xFF4ADE80) : const Color(0xFF16A34A),
|
||||
profitGreenBg = isDark ? const Color(0xFF052E16) : const Color(0xFFF0FDF4);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user