Updated the app's color scheme to implement a new "Slate" theme with refined dark and light variants. Changed background colors from #0A0E14 to #0B1120 for dark mode and updated surface layer colors to follow Material Design 3 specifications. Modified text colors and outline variants for better contrast and accessibility. Updated font sizes in transaction details screen from 11px to 12px for improved readability.
1259 lines
42 KiB
Dart
1259 lines
42 KiB
Dart
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 'package:lucide_icons_flutter/lucide_icons.dart';
|
|
import '../../../core/theme/app_color_scheme.dart';
|
|
import '../../../core/theme/app_spacing.dart';
|
|
import '../../../core/utils/toast_utils.dart';
|
|
import '../../../core/event/app_event_bus.dart';
|
|
import '../../../data/models/account_models.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 '../orders/fund_orders_page.dart';
|
|
import 'transfer_page.dart';
|
|
|
|
/// 资产页面 - Matching .pen design spec (CMcqs)
|
|
class AssetPage extends StatefulWidget {
|
|
const AssetPage({super.key});
|
|
|
|
@override
|
|
State<AssetPage> createState() => _AssetPageState();
|
|
}
|
|
|
|
class _AssetPageState extends State<AssetPage> with AutomaticKeepAliveClientMixin {
|
|
int _activeTab = 0;
|
|
StreamSubscription<AppEvent>? _eventSub;
|
|
|
|
@override
|
|
bool get wantKeepAlive => true;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
_loadData();
|
|
_listenEvents();
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_eventSub?.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
void _listenEvents() {
|
|
final eventBus = context.read<AppEventBus>();
|
|
_eventSub = eventBus.on(AppEventType.assetChanged, (_) {
|
|
if (mounted) {
|
|
context.read<AssetProvider>().refreshAll(force: true);
|
|
}
|
|
});
|
|
}
|
|
|
|
void _loadData() {
|
|
context.read<AssetProvider>().refreshAll(force: true);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
super.build(context);
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
|
|
return Scaffold(
|
|
backgroundColor: colorScheme.background,
|
|
body: Consumer<AssetProvider>(
|
|
builder: (context, provider, _) {
|
|
return RefreshIndicator(
|
|
onRefresh: () => provider.refreshAll(force: true),
|
|
color: colorScheme.primary,
|
|
backgroundColor: colorScheme.surfaceContainerHighest,
|
|
child: SingleChildScrollView(
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
padding: const EdgeInsets.fromLTRB(AppSpacing.md, AppSpacing.md + 8, AppSpacing.md, 32),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// 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),
|
|
),
|
|
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 : []),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// Account Tab Switcher — .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({
|
|
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,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// Balance Card — .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({
|
|
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) => ',',
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// Action Buttons Row — .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({
|
|
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,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Single action button — 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({
|
|
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,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// Records Link Row — .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({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,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// Holdings Section — .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
|
|
// Each row: padding [14, 16], justifyContent space_between
|
|
// Left: avatar (36x36, cornerRadius 18, fill $accent-light) + coin info (gap 2)
|
|
// AvatarText: coinCode first letter, 14px w700 $accent-primary
|
|
// CoinName: 14px w600 $text-primary
|
|
// CoinAmt: 12px normal $text-secondary
|
|
// Right: value + pnl (gap 2, alignItems end)
|
|
// Value: 13px w500 $text-primary
|
|
// Pnl: 12px w500, $profit-green / $loss-red
|
|
// Divider: $border-default height 1 opacity 0.5
|
|
// ============================================
|
|
|
|
class _HoldingsSection extends StatelessWidget {
|
|
final List holdings;
|
|
|
|
const _HoldingsSection({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(),
|
|
],
|
|
);
|
|
}),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Divider between holding rows — .pen node BCCbR / yejhE
|
|
/// fill: $border-default, height: 1, opacity: 0.5
|
|
class _HoldingDivider extends StatelessWidget {
|
|
const _HoldingDivider();
|
|
|
|
@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),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Holding row — 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({
|
|
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,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// Dialogs — 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,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
class _InfoRow extends StatelessWidget {
|
|
final String label;
|
|
final String value;
|
|
final bool isBold;
|
|
|
|
const _InfoRow({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({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,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
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 _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: 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,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|