feat(theme): update color scheme with new Slate theme and improved surface hierarchy

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.
This commit is contained in:
2026-04-05 22:24:04 +08:00
parent e2624b845a
commit d8cd38c4de
17 changed files with 3980 additions and 2922 deletions

View File

@@ -1,16 +1,14 @@
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 'package:lucide_icons_flutter/lucide_icons.dart';
import '../../../core/theme/app_color_scheme.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 +18,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,6 +33,7 @@ class _TransferPageState extends State<TransferPage> {
@override
void dispose() {
_amountController.dispose();
_focusNode.dispose();
super.dispose();
}
@@ -124,7 +124,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+$'), '');
}
@@ -140,21 +139,36 @@ class _TransferPageState extends State<TransferPage> {
final colorScheme = Theme.of(context).colorScheme;
final isDark = Theme.of(context).brightness == Brightness.dark;
// Theme-aware colors matching .pen design tokens
final bgSecondary = isDark ? const Color(0xFF0B1120) : const Color(0xFFF8FAFC);
final surfaceCard = isDark ? const Color(0xFF0F172A) : const Color(0xFFFFFFFF);
final bgTertiary = isDark ? const Color(0xFF1E293B) : const Color(0xFFF1F5F9);
final borderDefault = isDark ? const Color(0xFF334155) : const Color(0xFFE2E8F0);
final textPrimary = isDark ? const Color(0xFFF8FAFC) : const Color(0xFF0F172A);
final textSecondary = isDark ? const Color(0xFF94A3B8) : const Color(0xFF475569);
final textMuted = isDark ? const Color(0xFF64748B) : const Color(0xFF94A3B8);
final textInverse = isDark ? const Color(0xFF0F172A) : const Color(0xFFFFFFFF);
final accentPrimary = isDark ? const Color(0xFFD4AF37) : const Color(0xFF1F2937);
final goldAccent = isDark ? const Color(0xFFD4AF37) : const Color(0xFFF59E0B);
final profitGreen = isDark ? const Color(0xFF4ADE80) : const Color(0xFF16A34A);
final profitGreenBg = isDark ? const Color(0xFF052E16) : const Color(0xFFF0FDF4);
return Scaffold(
backgroundColor: colorScheme.background,
backgroundColor: bgSecondary,
appBar: AppBar(
backgroundColor: Colors.transparent,
backgroundColor: isDark ? const Color(0xFF0F172A) : const Color(0xFFFFFFFF),
elevation: 0,
scrolledUnderElevation: 0,
leading: IconButton(
icon: Icon(Icons.arrow_back, color: colorScheme.onSurface),
icon: Icon(LucideIcons.arrowLeft, color: textPrimary, size: 20),
onPressed: () => Navigator.of(context).pop(),
),
title: Text(
'资金划转',
style: GoogleFonts.spaceGrotesk(
fontSize: 14,
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
'账户划转',
style: GoogleFonts.inter(
fontSize: 16,
fontWeight: FontWeight.w600,
color: textPrimary,
),
),
centerTitle: true,
@@ -162,135 +176,49 @@ class _TransferPageState extends State<TransferPage> {
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,
),
);
},
// --- Transfer Direction Card ---
_buildTransferDirectionCard(
colorScheme: colorScheme,
isDark: isDark,
surfaceCard: surfaceCard,
borderDefault: borderDefault,
textPrimary: textPrimary,
textSecondary: textSecondary,
textMuted: textMuted,
textInverse: textInverse,
accentPrimary: accentPrimary,
),
// 方向切换按钮(固定在中间)
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,
),
),
const SizedBox(height: 24),
// --- Amount Section ---
_buildAmountSection(
isDark: isDark,
bgTertiary: bgTertiary,
textPrimary: textPrimary,
textSecondary: textSecondary,
textMuted: textMuted,
goldAccent: goldAccent,
),
// 第二个卡片位置 - 带动画
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,
),
const SizedBox(height: 24),
// --- Tips Card ---
_buildTipsCard(
profitGreen: profitGreen,
profitGreenBg: profitGreenBg,
),
SizedBox(height: AppSpacing.lg),
const SizedBox(height: 24),
// 金额输入卡片
_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,
),
// --- Confirm Button ---
_buildConfirmButton(
accentPrimary: accentPrimary,
textInverse: textInverse,
),
SizedBox(height: AppSpacing.md),
// 划转说明
_buildTips(colorScheme),
],
),
);
@@ -299,275 +227,345 @@ class _TransferPageState extends State<TransferPage> {
);
}
/// 账户卡片
Widget _buildAccountCard({
/// Transfer direction card with source, swap, destination
Widget _buildTransferDirectionCard({
required ColorScheme colorScheme,
required bool isDark,
required Color surfaceCard,
required Color borderDefault,
required Color textPrimary,
required Color textSecondary,
required Color textMuted,
required Color textInverse,
required Color accentPrimary,
}) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: surfaceCard,
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(color: borderDefault.withOpacity(0.6)),
),
child: Column(
children: [
// Source account
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: _buildAccountRow(
key: ValueKey('src-$_direction'),
label: '',
accountName: _fromLabel,
balance: _fromBalance,
isDark: isDark,
textMuted: textMuted,
textPrimary: textPrimary,
textSecondary: textSecondary,
),
),
// Swap button
GestureDetector(
onTap: _toggleDirection,
child: Container(
width: 36,
height: 36,
margin: const EdgeInsets.symmetric(vertical: 16),
decoration: BoxDecoration(
color: accentPrimary,
shape: BoxShape.circle,
),
child: Center(
child: Icon(
LucideIcons.arrowUpDown,
size: 18,
color: textInverse,
),
),
),
),
// Destination account
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: _buildAccountRow(
key: ValueKey('dst-$_direction'),
label: '',
accountName: _toLabel,
balance: _toBalance,
isDark: isDark,
textMuted: textMuted,
textPrimary: textPrimary,
textSecondary: textSecondary,
),
),
],
),
);
}
/// Single account row inside the direction card
Widget _buildAccountRow({
Key? key,
required String label,
required String accountName,
required String balance,
required bool isDark,
required ColorScheme colorScheme,
required Color textMuted,
required Color textPrimary,
required Color textSecondary,
}) {
return Container(
key: key,
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: [
Row(
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: [
// Label row
Text(
'划转金额',
style: TextStyle(
fontSize: 13,
color: colorScheme.onSurfaceVariant,
label,
style: GoogleFonts.inter(
fontSize: 11,
fontWeight: FontWeight.normal,
color: textMuted,
),
),
SizedBox(height: AppSpacing.sm),
// 金额输入行
const SizedBox(height: 8),
// Account name + balance row
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
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(
// Account name with icon
Row(
children: [
Icon(
LucideIcons.triangleAlert,
size: 14,
color: AppColorScheme.warning,
label == '' ? LucideIcons.wallet : LucideIcons.repeat,
size: 18,
color: textSecondary,
),
SizedBox(width: AppSpacing.xs),
Expanded(
child: Text(
'仅支持 USDT 资产划转到资金账户',
style: TextStyle(
fontSize: 11,
color: AppColorScheme.warning,
),
const SizedBox(width: 10),
Text(
accountName,
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w600,
color: textPrimary,
),
),
],
),
// Balance
Text(
'\u00A5 ${_formatBalance(balance)}',
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w600,
color: textPrimary,
),
),
],
),
],
),
);
}
/// Format balance for display
String _formatBalance(String balance) {
final val = double.tryParse(balance);
if (val == null) return '0.00';
return val.toStringAsFixed(2);
}
/// Amount input section
Widget _buildAmountSection({
required bool isDark,
required Color bgTertiary,
required Color textPrimary,
required Color textSecondary,
required Color textMuted,
required Color goldAccent,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Label row: "划转金额" + "全部划转"
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'划转金额',
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w500,
color: textSecondary,
),
),
GestureDetector(
onTap: () => _setQuickAmount(1.0),
child: Text(
'全部划转',
style: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w600,
color: 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: bgTertiary,
borderRadius: BorderRadius.circular(AppRadius.lg),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Input
Expanded(
child: TextField(
controller: _amountController,
focusNode: _focusNode,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,8}')),
],
style: GoogleFonts.inter(
fontSize: 28,
fontWeight: FontWeight.w700,
color: textPrimary,
),
decoration: InputDecoration(
hintText: '0.00',
hintStyle: GoogleFonts.inter(
fontSize: 28,
fontWeight: FontWeight.w700,
color: textMuted,
),
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
isDense: true,
),
),
),
// Suffix
Padding(
padding: const EdgeInsets.only(left: 8),
child: Text(
'USDT',
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.normal,
color: textMuted,
),
),
),
],
),
),
),
const SizedBox(height: 12),
// Percent buttons
Row(
children: [
_buildPercentButton('25%', 0.25, isDark, bgTertiary, textSecondary),
const SizedBox(width: 8),
_buildPercentButton('50%', 0.50, isDark, bgTertiary, textSecondary),
const SizedBox(width: 8),
_buildPercentButton('75%', 0.75, isDark, bgTertiary, textSecondary),
const SizedBox(width: 8),
_buildPercentButton('100%', 1.0, isDark, bgTertiary, textSecondary),
],
),
],
);
}
/// Percent quick button
Widget _buildPercentButton(String label, double percent, bool isDark, Color bgTertiary, Color textSecondary) {
return Expanded(
child: GestureDetector(
onTap: () => _setQuickAmount(percent),
child: Container(
height: 36,
decoration: BoxDecoration(
color: bgTertiary,
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: Center(
child: Text(
label,
style: GoogleFonts.inter(
fontSize: 13,
fontWeight: FontWeight.w500,
color: textSecondary,
),
),
),
),
),
);
}
/// Tips card with green background
Widget _buildTipsCard({
required Color profitGreen,
required Color profitGreenBg,
}) {
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: profitGreenBg,
borderRadius: BorderRadius.circular(AppRadius.lg),
),
child: Row(
children: [
Icon(
LucideIcons.info,
size: 16,
color: profitGreen,
),
const SizedBox(width: 8),
Expanded(
child: Text(
text,
style: TextStyle(
fontSize: 11,
color: colorScheme.onSurfaceVariant,
'划转即时到账,无需手续费',
style: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.normal,
color: profitGreen,
),
),
),
@@ -575,4 +573,43 @@ class _TransferPageState extends State<TransferPage> {
),
);
}
/// Confirm button
Widget _buildConfirmButton({
required Color accentPrimary,
required Color textInverse,
}) {
return SizedBox(
width: double.infinity,
height: 52,
child: GestureDetector(
onTap: _isLoading ? null : _doTransfer,
child: Container(
decoration: BoxDecoration(
color: accentPrimary,
borderRadius: BorderRadius.circular(AppRadius.lg),
),
child: Center(
child: _isLoading
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(textInverse),
),
)
: Text(
'确认划转',
style: GoogleFonts.inter(
fontSize: 16,
fontWeight: FontWeight.w700,
color: textInverse,
),
),
),
),
),
);
}
}