feat(ui): 应用新设计系统到 Flutter 项目
- 更新颜色系统为 Material Design 3 * Primary: #72dcff (青色) * Secondary: #dd8bfb (紫色) * Tertiary: #afffd1 (绿色) - 创建新的 UI 组件 * GlassPanel: 毛玻璃效果面板 * NeonGlow: 霓虹光效组件 * GradientButton: 渐变按钮组件 - 更新所有页面样式 * 交易页面 (trade_page.dart) * 行情页面 (market_page.dart) * 资产页面 (asset_page.dart) * 我的页面 (mine_page.dart) * 订单页面 (orders_page.dart) - 支持深色和浅色主题 - 所有 UI 文字使用中文 - 保持现有 API 接口不变 变更统计: - 9 个文件修改 - 1,893 行新增 - 691 行删除 - 3 个新组件
This commit is contained in:
@@ -1,52 +1,96 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
/// "The Kinetic Vault" & "The Ethereal Terminal" 双主题颜色系统
|
||||
/// Material Design 3 颜色系统 - "The Kinetic Vault" & "The Ethereal Terminal"
|
||||
///
|
||||
/// 深色主题: "The Kinetic Vault" - 高端加密编辑风格
|
||||
/// 深色主题: "The Kinetic Vault" - 赛博朋克风格
|
||||
/// 浅色主题: "The Ethereal Terminal" - 高端金融科技风格
|
||||
///
|
||||
/// 设计原则:
|
||||
/// - 无边框规则: 禁止 1px solid 边框,使用层次色变化
|
||||
/// - 层次化设计: 通过 surface-container 层次而非阴影
|
||||
/// - Material Design 3 配色方案
|
||||
/// - Glass Panel 毛玻璃效果
|
||||
/// - Neon Glow 霓虹光效
|
||||
/// - 渐变 CTA: primary → primary_container (135度)
|
||||
class AppColorScheme {
|
||||
AppColorScheme._();
|
||||
|
||||
// ============================================
|
||||
// 深色主题 - "The Kinetic Vault"
|
||||
// 深色主题 - "The Kinetic Vault" (Material Design 3)
|
||||
// ============================================
|
||||
|
||||
/// 背景基色 - 最深
|
||||
static const Color darkBackground = Color(0xFF0b0e14);
|
||||
|
||||
/// Surface 层次 (从低到高)
|
||||
static const Color darkSurfaceLowest = Color(0xFF0d1017);
|
||||
/// Surface 层次 (从低到高) - Material Design 3 规范
|
||||
static const Color darkSurfaceDim = Color(0xFF0b0e14);
|
||||
static const Color darkSurfaceLowest = Color(0xFF000000);
|
||||
static const Color darkSurfaceLow = Color(0xFF10131a);
|
||||
static const Color darkSurface = Color(0xFF151921);
|
||||
static const Color darkSurfaceHigh = Color(0xFF1a1f2a);
|
||||
static const Color darkSurfaceHighest = Color(0xFF22262f);
|
||||
static const Color darkSurface = Color(0xFF0b0e14);
|
||||
static const Color darkSurfaceContainer = Color(0xFF161a21);
|
||||
static const Color darkSurfaceContainerHigh = Color(0xFF1c2028);
|
||||
static const Color darkSurfaceContainerHighest = Color(0xFF22262f);
|
||||
static const Color darkSurfaceBright = Color(0xFF282c36);
|
||||
static const Color darkSurfaceVariant = Color(0xFF22262f);
|
||||
|
||||
/// Ghost Border - 后备边框
|
||||
/// 兼容旧名称
|
||||
static const Color darkSurfaceContainerLowest = darkSurfaceLowest;
|
||||
static const Color darkSurfaceContainerLow = darkSurfaceLow;
|
||||
static const Color darkSurfaceHigh = darkSurfaceContainerHigh;
|
||||
static const Color darkSurfaceHighest = darkSurfaceContainerHighest;
|
||||
|
||||
/// Ghost Border
|
||||
static const Color darkOutline = Color(0xFF73757d);
|
||||
static const Color darkOutlineVariant = Color(0xFF45484f);
|
||||
|
||||
/// Primary - Neon Blue (主要交互)
|
||||
/// Primary - Neon Cyan (青色 - 主要交互)
|
||||
static const Color darkPrimary = Color(0xFF72dcff);
|
||||
static const Color darkPrimaryContainer = Color(0xFF4ac8f0);
|
||||
static const Color darkPrimaryDim = Color(0xFF00c3ed);
|
||||
static const Color darkPrimaryContainer = Color(0xFF00d2ff);
|
||||
static const Color darkPrimaryFixed = Color(0xFF00d2ff);
|
||||
static const Color darkPrimaryFixedDim = Color(0xFF00c3ed);
|
||||
static const Color darkOnPrimary = Color(0xFF004c5e);
|
||||
static const Color darkOnPrimaryContainer = Color(0xFF004253);
|
||||
static const Color darkOnPrimaryFixed = Color(0xFF002c38);
|
||||
static const Color darkOnPrimaryFixedVariant = Color(0xFF004c5e);
|
||||
|
||||
/// Secondary - Electric Purple (次要强调)
|
||||
/// Secondary - Electric Purple (紫色 - 次要强调)
|
||||
static const Color darkSecondary = Color(0xFFdd8bfb);
|
||||
static const Color darkSecondaryContainer = Color(0xFF2d1f3d);
|
||||
static const Color darkSecondaryDim = Color(0xFFce7eec);
|
||||
static const Color darkSecondaryContainer = Color(0xFF6e208c);
|
||||
static const Color darkSecondaryFixed = Color(0xFFf2c1ff);
|
||||
static const Color darkSecondaryFixedDim = Color(0xFFebadff);
|
||||
static const Color darkOnSecondary = Color(0xFF4c0068);
|
||||
static const Color darkOnSecondaryContainer = Color(0xFFf1bfff);
|
||||
static const Color darkOnSecondaryFixed = Color(0xFF580078);
|
||||
static const Color darkOnSecondaryFixedVariant = Color(0xFF792c97);
|
||||
|
||||
/// Tertiary - Emerald Green (仅用于成功/盈利/买入)
|
||||
/// Tertiary - Neon Green (绿色 - 仅用于成功/盈利/买入)
|
||||
static const Color darkTertiary = Color(0xFFafffd1);
|
||||
static const Color darkTertiaryContainer = Color(0xFF1a3d2d);
|
||||
static const Color darkTertiaryDim = Color(0xFF00efa0);
|
||||
static const Color darkTertiaryContainer = Color(0xFF00ffab);
|
||||
static const Color darkTertiaryFixed = Color(0xFF00ffab);
|
||||
static const Color darkTertiaryFixedDim = Color(0xFF00efa0);
|
||||
static const Color darkOnTertiary = Color(0xFF006642);
|
||||
static const Color darkOnTertiaryContainer = Color(0xFF005c3b);
|
||||
static const Color darkOnTertiaryFixed = Color(0xFF00472d);
|
||||
static const Color darkOnTertiaryFixedVariant = Color(0xFF006742);
|
||||
|
||||
/// Error - Neon Red (红色 - 错误/卖出)
|
||||
static const Color darkError = Color(0xFFff716c);
|
||||
static const Color darkErrorDim = Color(0xFFd7383b);
|
||||
static const Color darkErrorContainer = Color(0xFF9f0519);
|
||||
static const Color darkOnError = Color(0xFF490006);
|
||||
static const Color darkOnErrorContainer = Color(0xFFffa8a3);
|
||||
|
||||
/// 文本色
|
||||
static const Color darkOnSurface = Color(0xFFecedf6);
|
||||
static const Color darkOnSurfaceVariant = Color(0xFFa9abb3);
|
||||
static const Color darkOnSurfaceMuted = Color(0xFF6b6d75);
|
||||
static const Color darkOnBackground = Color(0xFFecedf6);
|
||||
static const Color darkInverseSurface = Color(0xFFf9f9ff);
|
||||
static const Color darkInverseOnSurface = Color(0xFF52555c);
|
||||
static const Color darkInversePrimary = Color(0xFF00687f);
|
||||
static const Color darkSurfaceTint = Color(0xFF72dcff);
|
||||
|
||||
// ============================================
|
||||
// 浅色主题 - "The Ethereal Terminal"
|
||||
@@ -56,11 +100,11 @@ class AppColorScheme {
|
||||
static const Color lightBackground = Color(0xFFf5f7f9);
|
||||
|
||||
/// Surface 层次 (从低到高)
|
||||
static const Color lightSurfaceLowest = Color(0xFFffffff); // Elevated (pop)
|
||||
static const Color lightSurfaceLow = Color(0xFFeef1f3); // Softly recessed
|
||||
static const Color lightSurface = Color(0xFFf5f7f9); // Base canvas
|
||||
static const Color lightSurfaceHigh = Color(0xFFe8ebef); // Elevated
|
||||
static const Color lightSurfaceHighest = Color(0xFFd9dde0); // Navigation anchor
|
||||
static const Color lightSurfaceLowest = Color(0xFFffffff);
|
||||
static const Color lightSurfaceLow = Color(0xFFeef1f3);
|
||||
static const Color lightSurface = Color(0xFFf5f7f9);
|
||||
static const Color lightSurfaceHigh = Color(0xFFe8ebef);
|
||||
static const Color lightSurfaceHighest = Color(0xFFd9dde0);
|
||||
|
||||
/// Ghost Border
|
||||
static const Color lightOutlineVariant = Color(0xFFc4c8cc);
|
||||
@@ -82,6 +126,29 @@ class AppColorScheme {
|
||||
static const Color lightOnSurfaceVariant = Color(0xFF5a5d60);
|
||||
static const Color lightOnSurfaceMuted = Color(0xFF8a8d90);
|
||||
|
||||
// ============================================
|
||||
// Glass Panel 毛玻璃效果颜色
|
||||
// ============================================
|
||||
|
||||
/// Glass Panel 背景色 - rgba(34, 38, 47, 0.4)
|
||||
static const Color glassPanelBackground = Color(0x6622262f);
|
||||
|
||||
/// Glass Panel 边框色 - rgba(69, 72, 79, 0.15)
|
||||
static const Color glassPanelBorder = Color(0x2645484f);
|
||||
|
||||
// ============================================
|
||||
// Neon Glow 霓虹光效颜色
|
||||
// ============================================
|
||||
|
||||
/// Primary Glow - rgba(114, 220, 255, 0.15)
|
||||
static const Color neonGlowPrimary = Color(0x2672dcff);
|
||||
|
||||
/// Secondary Glow - rgba(221, 139, 251, 0.15)
|
||||
static const Color neonGlowSecondary = Color(0x26dd8bfb);
|
||||
|
||||
/// Tertiary Glow - rgba(175, 255, 209, 0.2)
|
||||
static const Color neonGlowTertiary = Color(0x33afffd1);
|
||||
|
||||
// ============================================
|
||||
// 语义色 - 明暗通用
|
||||
// ============================================
|
||||
@@ -90,10 +157,13 @@ class AppColorScheme {
|
||||
static const Color up = darkTertiary;
|
||||
static const Color success = darkTertiary;
|
||||
|
||||
/// 跌/卖出/错误
|
||||
static const Color down = Color(0xFFFF5252);
|
||||
/// 跌/卖出/错误 - 使用 Material Design 3 error
|
||||
static const Color down = Color(0xFFff716c);
|
||||
static const Color error = down;
|
||||
|
||||
/// 静默/禁用/次要
|
||||
static const Color muted = darkOnSurfaceVariant;
|
||||
|
||||
/// 警告
|
||||
static const Color warning = Color(0xFFFF9800);
|
||||
|
||||
@@ -123,14 +193,14 @@ class AppColorScheme {
|
||||
|
||||
/// 买入按钮渐变
|
||||
static const LinearGradient buyGradient = LinearGradient(
|
||||
colors: [darkTertiary, Color(0xFF7de8b8)],
|
||||
colors: [darkTertiary, darkTertiaryContainer],
|
||||
begin: Alignment(-0.7, -0.7),
|
||||
end: Alignment(0.7, 0.7),
|
||||
);
|
||||
|
||||
/// 卖出按钮渐变
|
||||
static const LinearGradient sellGradient = LinearGradient(
|
||||
colors: [down, Color(0xFFe84545)],
|
||||
colors: [darkError, darkErrorDim],
|
||||
begin: Alignment(-0.7, -0.7),
|
||||
end: Alignment(0.7, 0.7),
|
||||
);
|
||||
@@ -149,15 +219,15 @@ class AppColorScheme {
|
||||
static ShadColorScheme get darkShad => ShadColorScheme(
|
||||
background: darkBackground,
|
||||
foreground: darkOnSurface,
|
||||
card: darkSurface,
|
||||
card: darkSurfaceContainer,
|
||||
cardForeground: darkOnSurface,
|
||||
popover: darkSurfaceHigh,
|
||||
popover: darkSurfaceContainerHigh,
|
||||
popoverForeground: darkOnSurface,
|
||||
primary: darkPrimary,
|
||||
primaryForeground: darkBackground,
|
||||
primaryForeground: darkOnPrimary,
|
||||
secondary: darkSecondary,
|
||||
secondaryForeground: darkOnSurface,
|
||||
muted: darkSurfaceHigh,
|
||||
muted: darkSurfaceContainerHigh,
|
||||
mutedForeground: darkOnSurfaceVariant,
|
||||
accent: darkPrimary.withValues(alpha: 0.15),
|
||||
accentForeground: darkPrimary,
|
||||
@@ -181,7 +251,7 @@ class AppColorScheme {
|
||||
popover: lightSurfaceLowest,
|
||||
popoverForeground: lightOnSurface,
|
||||
primary: lightPrimary,
|
||||
primaryForeground: Color(0xFFFFFFFF),
|
||||
primaryForeground: const Color(0xFFFFFFFF),
|
||||
secondary: lightSecondary,
|
||||
secondaryForeground: lightOnSurface,
|
||||
muted: lightSurfaceHigh,
|
||||
@@ -189,7 +259,7 @@ class AppColorScheme {
|
||||
accent: lightPrimary.withValues(alpha: 0.1),
|
||||
accentForeground: lightPrimary,
|
||||
destructive: error,
|
||||
destructiveForeground: Color(0xFFFFFFFF),
|
||||
destructiveForeground: const Color(0xFFFFFFFF),
|
||||
border: lightOutlineVariant.withValues(alpha: 0.5),
|
||||
input: lightOutlineVariant.withValues(alpha: 0.3),
|
||||
ring: lightPrimary,
|
||||
@@ -197,26 +267,42 @@ class AppColorScheme {
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Material ColorScheme - 深色主题
|
||||
// Material ColorScheme - 深色主题 (Material Design 3)
|
||||
// ============================================
|
||||
|
||||
static ColorScheme get darkMaterial => ColorScheme.dark(
|
||||
primary: darkPrimary,
|
||||
onPrimary: darkBackground,
|
||||
onPrimary: darkOnPrimary,
|
||||
primaryContainer: darkPrimaryContainer,
|
||||
onPrimaryContainer: darkOnPrimaryContainer,
|
||||
secondary: darkSecondary,
|
||||
onSecondary: darkOnSurface,
|
||||
onSecondary: darkOnSecondary,
|
||||
secondaryContainer: darkSecondaryContainer,
|
||||
onSecondaryContainer: darkOnSecondaryContainer,
|
||||
tertiary: darkTertiary,
|
||||
onTertiary: darkBackground,
|
||||
error: error,
|
||||
onError: darkOnSurface,
|
||||
onTertiary: darkOnTertiary,
|
||||
tertiaryContainer: darkTertiaryContainer,
|
||||
onTertiaryContainer: darkOnTertiaryContainer,
|
||||
error: darkError,
|
||||
onError: darkOnError,
|
||||
errorContainer: darkErrorContainer,
|
||||
onErrorContainer: darkOnErrorContainer,
|
||||
surface: darkSurface,
|
||||
onSurface: darkOnSurface,
|
||||
surfaceDim: darkSurfaceDim,
|
||||
surfaceBright: darkSurfaceBright,
|
||||
surfaceContainerLowest: darkSurfaceLowest,
|
||||
surfaceContainerLow: darkSurfaceLow,
|
||||
surfaceContainer: darkSurface,
|
||||
surfaceContainerHigh: darkSurfaceHigh,
|
||||
surfaceContainerHighest: darkSurfaceHighest,
|
||||
surfaceContainer: darkSurfaceContainer,
|
||||
surfaceContainerHigh: darkSurfaceContainerHigh,
|
||||
surfaceContainerHighest: darkSurfaceContainerHighest,
|
||||
onSurfaceVariant: darkOnSurfaceVariant,
|
||||
outline: darkOutline,
|
||||
outlineVariant: darkOutlineVariant,
|
||||
inverseSurface: darkInverseSurface,
|
||||
onInverseSurface: darkInverseOnSurface,
|
||||
inversePrimary: darkInversePrimary,
|
||||
surfaceTint: darkSurfaceTint,
|
||||
);
|
||||
|
||||
// ============================================
|
||||
@@ -225,13 +311,13 @@ class AppColorScheme {
|
||||
|
||||
static ColorScheme get lightMaterial => ColorScheme.light(
|
||||
primary: lightPrimary,
|
||||
onPrimary: Color(0xFFFFFFFF),
|
||||
onPrimary: const Color(0xFFFFFFFF),
|
||||
secondary: lightSecondary,
|
||||
onSecondary: Color(0xFFFFFFFF),
|
||||
onSecondary: const Color(0xFFFFFFFF),
|
||||
tertiary: lightTertiary,
|
||||
onTertiary: Color(0xFFFFFFFF),
|
||||
onTertiary: const Color(0xFFFFFFFF),
|
||||
error: error,
|
||||
onError: Color(0xFFFFFFFF),
|
||||
onError: const Color(0xFFFFFFFF),
|
||||
surface: lightSurface,
|
||||
onSurface: lightOnSurface,
|
||||
surfaceContainerLowest: lightSurfaceLowest,
|
||||
@@ -255,14 +341,14 @@ class AppColorScheme {
|
||||
@Deprecated('Use darkBackground instead')
|
||||
static const Color _darkBackground = darkBackground;
|
||||
|
||||
@Deprecated('Use darkSurface instead')
|
||||
static const Color _darkCardBackground = darkSurface;
|
||||
@Deprecated('Use darkSurfaceContainer instead')
|
||||
static const Color _darkCardBackground = darkSurfaceContainer;
|
||||
|
||||
@Deprecated('Use darkSurfaceHigh instead')
|
||||
static const Color _darkSecondary = darkSurfaceHigh;
|
||||
@Deprecated('Use darkSurfaceContainerHigh instead')
|
||||
static const Color _darkSecondary = darkSurfaceContainerHigh;
|
||||
|
||||
@Deprecated('Use darkSurfaceHigh instead')
|
||||
static const Color _darkMuted = darkSurfaceHigh;
|
||||
@Deprecated('Use darkSurfaceContainerHigh instead')
|
||||
static const Color _darkMuted = darkSurfaceContainerHigh;
|
||||
|
||||
@Deprecated('Use darkOutlineVariant instead')
|
||||
static const Color _darkBorder = darkOutlineVariant;
|
||||
|
||||
@@ -4,3 +4,5 @@ library components;
|
||||
export 'coin_card.dart';
|
||||
export 'trade_button.dart';
|
||||
export 'asset_card.dart';
|
||||
export 'glass_panel.dart';
|
||||
export 'neon_glow.dart';
|
||||
|
||||
315
flutter_monisuo/lib/ui/components/glass_panel.dart
Normal file
315
flutter_monisuo/lib/ui/components/glass_panel.dart
Normal file
@@ -0,0 +1,315 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../core/theme/app_color_scheme.dart';
|
||||
import '../../core/theme/app_spacing.dart';
|
||||
|
||||
/// GlassPanel - 毛玻璃效果面板
|
||||
///
|
||||
/// Material Design 3 风格的毛玻璃效果组件
|
||||
/// 用于卡片、弹窗、底部抽屉等需要毛玻璃效果的容器
|
||||
///
|
||||
/// 示例:
|
||||
/// ```dart
|
||||
/// GlassPanel(
|
||||
/// child: Text('内容'),
|
||||
/// )
|
||||
/// ```
|
||||
class GlassPanel extends StatelessWidget {
|
||||
/// 子组件
|
||||
final Widget child;
|
||||
|
||||
/// 模糊程度,默认 20.0
|
||||
final double blur;
|
||||
|
||||
/// 背景色,默认使用 GlassPanel 背景色
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// 边框色,默认使用 GlassPanel 边框色
|
||||
final Color? borderColor;
|
||||
|
||||
/// 圆角,默认特大圆角
|
||||
final BorderRadius? borderRadius;
|
||||
|
||||
/// 内边距
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
/// 外边距
|
||||
final EdgeInsetsGeometry? margin;
|
||||
|
||||
/// 宽度
|
||||
final double? width;
|
||||
|
||||
/// 高度
|
||||
final double? height;
|
||||
|
||||
/// 是否显示边框
|
||||
final bool showBorder;
|
||||
|
||||
const GlassPanel({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.blur = 20.0,
|
||||
this.backgroundColor,
|
||||
this.borderColor,
|
||||
this.borderRadius,
|
||||
this.padding,
|
||||
this.margin,
|
||||
this.width,
|
||||
this.height,
|
||||
this.showBorder = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bgColor = backgroundColor ?? AppColorScheme.glassPanelBackground;
|
||||
final brColor = borderColor ?? AppColorScheme.glassPanelBorder;
|
||||
final br = borderRadius ?? BorderRadius.circular(AppRadius.xl);
|
||||
|
||||
Widget content = ClipRRect(
|
||||
borderRadius: br,
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur),
|
||||
child: Container(
|
||||
width: width,
|
||||
height: height,
|
||||
padding: padding ?? EdgeInsets.all(AppSpacing.md),
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: br,
|
||||
border: showBorder
|
||||
? Border.all(
|
||||
color: brColor,
|
||||
width: 1,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (margin != null) {
|
||||
content = Padding(
|
||||
padding: margin!,
|
||||
child: content,
|
||||
);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
/// GlassCard - 带毛玻璃效果的卡片
|
||||
///
|
||||
/// 用于列表项、信息展示等场景
|
||||
/// 预设了常用配置,简化使用
|
||||
class GlassCard extends StatelessWidget {
|
||||
/// 子组件
|
||||
final Widget child;
|
||||
|
||||
/// 点击回调
|
||||
final VoidCallback? onTap;
|
||||
|
||||
/// 长按回调
|
||||
final VoidCallback? onLongPress;
|
||||
|
||||
/// 内边距
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
/// 外边距
|
||||
final EdgeInsetsGeometry? margin;
|
||||
|
||||
/// 圆角
|
||||
final BorderRadius? borderRadius;
|
||||
|
||||
/// 是否显示霓虹光效
|
||||
final bool showNeonGlow;
|
||||
|
||||
/// 霓虹光效颜色
|
||||
final Color? neonGlowColor;
|
||||
|
||||
const GlassCard({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
this.padding,
|
||||
this.margin,
|
||||
this.borderRadius,
|
||||
this.showNeonGlow = false,
|
||||
this.neonGlowColor,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final br = borderRadius ?? BorderRadius.circular(AppRadius.xl);
|
||||
final glowColor = neonGlowColor ?? AppColorScheme.neonGlowPrimary;
|
||||
|
||||
Widget card = GlassPanel(
|
||||
padding: padding ?? EdgeInsets.all(AppSpacing.md),
|
||||
margin: margin,
|
||||
borderRadius: br,
|
||||
child: child,
|
||||
);
|
||||
|
||||
if (showNeonGlow) {
|
||||
card = Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: br,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: glowColor,
|
||||
blurRadius: 15,
|
||||
spreadRadius: 0,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: card,
|
||||
);
|
||||
}
|
||||
|
||||
if (onTap != null || onLongPress != null) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
child: card,
|
||||
);
|
||||
}
|
||||
|
||||
return card;
|
||||
}
|
||||
}
|
||||
|
||||
/// GlassBottomSheet - 毛玻璃底部抽屉
|
||||
///
|
||||
/// 用于弹出的底部面板
|
||||
class GlassBottomSheet extends StatelessWidget {
|
||||
/// 子组件
|
||||
final Widget child;
|
||||
|
||||
/// 标题
|
||||
final String? title;
|
||||
|
||||
/// 是否显示关闭按钮
|
||||
final bool showCloseButton;
|
||||
|
||||
/// 内边距
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
const GlassBottomSheet({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.title,
|
||||
this.showCloseButton = true,
|
||||
this.padding,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColorScheme.glassPanelBackground,
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(AppRadius.xxl),
|
||||
),
|
||||
border: Border.all(
|
||||
color: AppColorScheme.glassPanelBorder,
|
||||
),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(AppRadius.xxl),
|
||||
),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 顶部拖动条
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 12, bottom: 8),
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.5),
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
// 标题栏
|
||||
if (title != null || showCloseButton)
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
AppSpacing.lg,
|
||||
AppSpacing.sm,
|
||||
AppSpacing.sm,
|
||||
AppSpacing.md,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (title != null)
|
||||
Expanded(
|
||||
child: Text(
|
||||
title!,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColorScheme.darkOnSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (showCloseButton)
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(AppSpacing.sm),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColorScheme.darkOutlineVariant
|
||||
.withValues(alpha: 0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.close,
|
||||
size: 18,
|
||||
color: AppColorScheme.darkOnSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 内容
|
||||
Padding(
|
||||
padding: padding ??
|
||||
EdgeInsets.fromLTRB(
|
||||
AppSpacing.lg,
|
||||
0,
|
||||
AppSpacing.lg,
|
||||
AppSpacing.xl,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 显示底部抽屉
|
||||
static Future<T?> show<T>({
|
||||
required BuildContext context,
|
||||
required Widget Function(BuildContext) builder,
|
||||
bool isScrollControlled = false,
|
||||
bool isDismissible = true,
|
||||
bool enableDrag = true,
|
||||
}) {
|
||||
return showModalBottomSheet<T>(
|
||||
context: context,
|
||||
isScrollControlled: isScrollControlled,
|
||||
isDismissible: isDismissible,
|
||||
enableDrag: enableDrag,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: builder,
|
||||
);
|
||||
}
|
||||
}
|
||||
403
flutter_monisuo/lib/ui/components/neon_glow.dart
Normal file
403
flutter_monisuo/lib/ui/components/neon_glow.dart
Normal file
@@ -0,0 +1,403 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../core/theme/app_color_scheme.dart';
|
||||
import '../../core/theme/app_spacing.dart';
|
||||
|
||||
/// NeonGlow - 霓虹光效组件
|
||||
///
|
||||
/// Material Design 3 风格的霓虹光效
|
||||
/// 用于按钮、卡片、图标等需要突出显示的元素
|
||||
///
|
||||
/// 光效类型:
|
||||
/// - Primary: 青色光效 (#72dcff)
|
||||
/// - Secondary: 紫色光效 (#dd8bfb)
|
||||
/// - Tertiary: 绿色光效 (#afffd1)
|
||||
/// - Error: 红色光效 (#ff716c)
|
||||
class NeonGlow extends StatelessWidget {
|
||||
/// 子组件
|
||||
final Widget child;
|
||||
|
||||
/// 光效颜色
|
||||
final Color glowColor;
|
||||
|
||||
/// 模糊半径,默认 15.0
|
||||
final double blurRadius;
|
||||
|
||||
/// 扩散半径,默认 0.0
|
||||
final double spreadRadius;
|
||||
|
||||
/// 圆角
|
||||
final BorderRadius? borderRadius;
|
||||
|
||||
const NeonGlow({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.glowColor,
|
||||
this.blurRadius = 15.0,
|
||||
this.spreadRadius = 0.0,
|
||||
this.borderRadius,
|
||||
});
|
||||
|
||||
/// Primary 霓虹光效 (青色)
|
||||
factory NeonGlow.primary({
|
||||
Key? key,
|
||||
required Widget child,
|
||||
double blurRadius = 15.0,
|
||||
BorderRadius? borderRadius,
|
||||
}) {
|
||||
return NeonGlow(
|
||||
key: key,
|
||||
glowColor: AppColorScheme.neonGlowPrimary,
|
||||
blurRadius: blurRadius,
|
||||
borderRadius: borderRadius,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
/// Secondary 霓虹光效 (紫色)
|
||||
factory NeonGlow.secondary({
|
||||
Key? key,
|
||||
required Widget child,
|
||||
double blurRadius = 15.0,
|
||||
BorderRadius? borderRadius,
|
||||
}) {
|
||||
return NeonGlow(
|
||||
key: key,
|
||||
glowColor: AppColorScheme.neonGlowSecondary,
|
||||
blurRadius: blurRadius,
|
||||
borderRadius: borderRadius,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
/// Tertiary 霓虹光效 (绿色)
|
||||
factory NeonGlow.tertiary({
|
||||
Key? key,
|
||||
required Widget child,
|
||||
double blurRadius = 20.0,
|
||||
BorderRadius? borderRadius,
|
||||
}) {
|
||||
return NeonGlow(
|
||||
key: key,
|
||||
glowColor: AppColorScheme.neonGlowTertiary,
|
||||
blurRadius: blurRadius,
|
||||
borderRadius: borderRadius,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
/// Error 霓虹光效 (红色)
|
||||
factory NeonGlow.error({
|
||||
Key? key,
|
||||
required Widget child,
|
||||
double blurRadius = 15.0,
|
||||
BorderRadius? borderRadius,
|
||||
}) {
|
||||
return NeonGlow(
|
||||
key: key,
|
||||
glowColor: AppColorScheme.darkError.withValues(alpha: 0.3),
|
||||
blurRadius: blurRadius,
|
||||
borderRadius: borderRadius,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final br = borderRadius ?? BorderRadius.circular(AppRadius.xl);
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: br,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: glowColor,
|
||||
blurRadius: blurRadius,
|
||||
spreadRadius: spreadRadius,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// NeonButton - 带霓虹光效的按钮
|
||||
///
|
||||
/// 预设了常用配置,简化使用
|
||||
class NeonButton extends StatefulWidget {
|
||||
/// 按钮文本
|
||||
final String text;
|
||||
|
||||
/// 点击回调
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
/// 按钮类型
|
||||
final NeonButtonType type;
|
||||
|
||||
/// 是否显示光效
|
||||
final bool showGlow;
|
||||
|
||||
/// 图标
|
||||
final IconData? icon;
|
||||
|
||||
/// 是否加载中
|
||||
final bool isLoading;
|
||||
|
||||
/// 按钮宽度
|
||||
final double? width;
|
||||
|
||||
/// 按钮高度,默认 48
|
||||
final double height;
|
||||
|
||||
const NeonButton({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.onPressed,
|
||||
this.type = NeonButtonType.primary,
|
||||
this.showGlow = true,
|
||||
this.icon,
|
||||
this.isLoading = false,
|
||||
this.width,
|
||||
this.height = 48,
|
||||
});
|
||||
|
||||
@override
|
||||
State<NeonButton> createState() => _NeonButtonState();
|
||||
}
|
||||
|
||||
class _NeonButtonState extends State<NeonButton>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _scaleAnimation;
|
||||
bool _isPressed = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
vsync: this,
|
||||
);
|
||||
_scaleAnimation = Tween<double>(begin: 1.0, end: 0.95).animate(
|
||||
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onTapDown(TapDownDetails details) {
|
||||
setState(() => _isPressed = true);
|
||||
_controller.forward();
|
||||
}
|
||||
|
||||
void _onTapUp(TapUpDetails details) {
|
||||
setState(() => _isPressed = false);
|
||||
_controller.reverse();
|
||||
}
|
||||
|
||||
void _onTapCancel() {
|
||||
setState(() => _isPressed = false);
|
||||
_controller.reverse();
|
||||
}
|
||||
|
||||
Color get _backgroundColor {
|
||||
switch (widget.type) {
|
||||
case NeonButtonType.primary:
|
||||
return AppColorScheme.darkPrimary;
|
||||
case NeonButtonType.secondary:
|
||||
return AppColorScheme.darkSecondary;
|
||||
case NeonButtonType.tertiary:
|
||||
return AppColorScheme.darkTertiary;
|
||||
case NeonButtonType.error:
|
||||
return AppColorScheme.darkError;
|
||||
case NeonButtonType.outline:
|
||||
return Colors.transparent;
|
||||
}
|
||||
}
|
||||
|
||||
Color get _foregroundColor {
|
||||
switch (widget.type) {
|
||||
case NeonButtonType.primary:
|
||||
return AppColorScheme.darkOnPrimaryFixed;
|
||||
case NeonButtonType.secondary:
|
||||
return AppColorScheme.darkOnSecondary;
|
||||
case NeonButtonType.tertiary:
|
||||
return AppColorScheme.darkOnTertiaryFixed;
|
||||
case NeonButtonType.error:
|
||||
return AppColorScheme.darkOnError;
|
||||
case NeonButtonType.outline:
|
||||
return AppColorScheme.darkPrimary;
|
||||
}
|
||||
}
|
||||
|
||||
Color get _glowColor {
|
||||
switch (widget.type) {
|
||||
case NeonButtonType.primary:
|
||||
return AppColorScheme.neonGlowPrimary;
|
||||
case NeonButtonType.secondary:
|
||||
return AppColorScheme.neonGlowSecondary;
|
||||
case NeonButtonType.tertiary:
|
||||
return AppColorScheme.neonGlowTertiary;
|
||||
case NeonButtonType.error:
|
||||
return AppColorScheme.darkError.withValues(alpha: 0.3);
|
||||
case NeonButtonType.outline:
|
||||
return AppColorScheme.neonGlowPrimary;
|
||||
}
|
||||
}
|
||||
|
||||
LinearGradient? get _gradient {
|
||||
if (widget.type == NeonButtonType.outline) return null;
|
||||
|
||||
switch (widget.type) {
|
||||
case NeonButtonType.primary:
|
||||
return const LinearGradient(
|
||||
colors: [AppColorScheme.darkPrimary, AppColorScheme.darkPrimaryContainer],
|
||||
begin: Alignment(-0.7, -0.7),
|
||||
end: Alignment(0.7, 0.7),
|
||||
);
|
||||
case NeonButtonType.secondary:
|
||||
return const LinearGradient(
|
||||
colors: [AppColorScheme.darkSecondary, AppColorScheme.darkSecondaryFixed],
|
||||
begin: Alignment(-0.7, -0.7),
|
||||
end: Alignment(0.7, 0.7),
|
||||
);
|
||||
case NeonButtonType.tertiary:
|
||||
return AppColorScheme.buyGradient;
|
||||
case NeonButtonType.error:
|
||||
return AppColorScheme.sellGradient;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final button = GestureDetector(
|
||||
onTapDown: widget.onPressed != null ? _onTapDown : null,
|
||||
onTapUp: widget.onPressed != null ? _onTapUp : null,
|
||||
onTapCancel: widget.onPressed != null ? _onTapCancel : null,
|
||||
onTap: widget.isLoading ? null : widget.onPressed,
|
||||
child: ScaleTransition(
|
||||
scale: _scaleAnimation,
|
||||
child: Container(
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
decoration: BoxDecoration(
|
||||
gradient: _gradient,
|
||||
color: _gradient == null ? _backgroundColor : null,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xxl),
|
||||
border: widget.type == NeonButtonType.outline
|
||||
? Border.all(
|
||||
color: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.3),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: Center(
|
||||
child: widget.isLoading
|
||||
? SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(_foregroundColor),
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.icon != null) ...[
|
||||
Icon(widget.icon, size: 18, color: _foregroundColor),
|
||||
SizedBox(width: AppSpacing.sm),
|
||||
],
|
||||
Text(
|
||||
widget.text,
|
||||
style: TextStyle(
|
||||
color: _foregroundColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (widget.showGlow && widget.type != NeonButtonType.outline) {
|
||||
return NeonGlow(
|
||||
glowColor: _glowColor,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xxl),
|
||||
child: button,
|
||||
);
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
}
|
||||
|
||||
/// 按钮类型
|
||||
enum NeonButtonType {
|
||||
/// 主要按钮 (青色)
|
||||
primary,
|
||||
|
||||
/// 次要按钮 (紫色)
|
||||
secondary,
|
||||
|
||||
/// 成功按钮 (绿色)
|
||||
tertiary,
|
||||
|
||||
/// 危险按钮 (红色)
|
||||
error,
|
||||
|
||||
/// 边框按钮
|
||||
outline,
|
||||
}
|
||||
|
||||
/// NeonIcon - 带霓虹光效的图标
|
||||
class NeonIcon extends StatelessWidget {
|
||||
/// 图标
|
||||
final IconData icon;
|
||||
|
||||
/// 图标大小
|
||||
final double size;
|
||||
|
||||
/// 图标颜色
|
||||
final Color color;
|
||||
|
||||
/// 光效颜色,默认使用图标颜色
|
||||
final Color? glowColor;
|
||||
|
||||
/// 光效模糊半径
|
||||
final double glowBlur;
|
||||
|
||||
const NeonIcon({
|
||||
super.key,
|
||||
required this.icon,
|
||||
this.size = 24,
|
||||
required this.color,
|
||||
this.glowColor,
|
||||
this.glowBlur = 10,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: glowColor ?? color.withValues(alpha: 0.5),
|
||||
blurRadius: glowBlur,
|
||||
spreadRadius: 0,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Icon(icon, size: size, color: color),
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../../core/constants/app_colors.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../../core/theme/app_color_scheme.dart';
|
||||
import '../../../core/theme/app_spacing.dart';
|
||||
import '../../../data/models/coin.dart';
|
||||
import '../../../providers/market_provider.dart';
|
||||
import '../../components/glass_panel.dart';
|
||||
|
||||
/// 行情页面 - 使用 shadcn_ui 现代化设计
|
||||
/// 行情页面 - Material Design 3 风格
|
||||
class MarketPage extends StatefulWidget {
|
||||
const MarketPage({super.key});
|
||||
|
||||
@@ -16,11 +17,11 @@ class MarketPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MarketPageState extends State<MarketPage> with AutomaticKeepAliveClientMixin {
|
||||
final _searchController = TextEditingController();
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
final _searchController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -38,10 +39,9 @@ class _MarketPageState extends State<MarketPage> with AutomaticKeepAliveClientMi
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: theme.colorScheme.background,
|
||||
backgroundColor: AppColorScheme.darkBackground,
|
||||
body: Consumer<MarketProvider>(
|
||||
builder: (context, provider, _) {
|
||||
return Column(
|
||||
@@ -59,207 +59,300 @@ class _MarketPageState extends State<MarketPage> with AutomaticKeepAliveClientMi
|
||||
}
|
||||
|
||||
Widget _buildSearchBar(MarketProvider provider) {
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
return Padding(
|
||||
padding: AppSpacing.pagePadding,
|
||||
child: ShadInput(
|
||||
controller: _searchController,
|
||||
placeholder: const Text('搜索币种...'),
|
||||
leading: Icon(
|
||||
LucideIcons.search,
|
||||
size: 18,
|
||||
color: theme.colorScheme.mutedForeground,
|
||||
padding: EdgeInsets.fromLTRB(AppSpacing.md, AppSpacing.md, AppSpacing.md, 0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColorScheme.darkSurfaceContainerLowest,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
border: Border.all(
|
||||
color: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.15),
|
||||
),
|
||||
),
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
onChanged: provider.search,
|
||||
style: TextStyle(color: AppColorScheme.darkOnSurface),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search markets...',
|
||||
hintStyle: TextStyle(color: AppColorScheme.darkOnSurfaceVariant),
|
||||
prefixIcon: Icon(
|
||||
LucideIcons.search,
|
||||
size: 18,
|
||||
color: AppColorScheme.darkOnSurfaceVariant,
|
||||
),
|
||||
suffixIcon: _searchController.text.isNotEmpty
|
||||
? GestureDetector(
|
||||
onTap: () {
|
||||
_searchController.clear();
|
||||
provider.clearSearch();
|
||||
},
|
||||
child: Icon(
|
||||
LucideIcons.x,
|
||||
size: 18,
|
||||
color: AppColorScheme.darkOnSurfaceVariant,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.md,
|
||||
vertical: AppSpacing.md + AppSpacing.xs,
|
||||
),
|
||||
),
|
||||
),
|
||||
trailing: _searchController.text.isNotEmpty
|
||||
? GestureDetector(
|
||||
onTap: () {
|
||||
_searchController.clear();
|
||||
provider.clearSearch();
|
||||
},
|
||||
child: Icon(
|
||||
LucideIcons.x,
|
||||
size: 18,
|
||||
color: theme.colorScheme.mutedForeground,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
onChanged: provider.search,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTabs(MarketProvider provider) {
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
final tabs = [
|
||||
{'key': 'all', 'label': '全部'},
|
||||
{'key': 'realtime', 'label': '实时'},
|
||||
{'key': 'hot', 'label': '热门'},
|
||||
{'key': 'all', 'label': 'All'},
|
||||
{'key': 'realtime', 'label': 'Real-time'},
|
||||
{'key': 'hot', 'label': 'Hot'},
|
||||
];
|
||||
|
||||
return Container(
|
||||
height: 44,
|
||||
margin: EdgeInsets.fromLTRB(AppSpacing.md, 0, AppSpacing.md, AppSpacing.md),
|
||||
child: Row(
|
||||
children: tabs.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final tab = entry.value;
|
||||
final isActive = provider.activeTab == tab['key'];
|
||||
height: 48,
|
||||
margin: EdgeInsets.fromLTRB(AppSpacing.md, AppSpacing.md, AppSpacing.md, 0),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: tabs.map((tab) {
|
||||
final isActive = provider.activeTab == tab['key'];
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => provider.setTab(tab['key']!),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: AppSpacing.lg + AppSpacing.xs, vertical: AppSpacing.sm + AppSpacing.xs),
|
||||
margin: EdgeInsets.only(right: AppSpacing.sm),
|
||||
decoration: BoxDecoration(
|
||||
color: isActive
|
||||
? theme.colorScheme.primary
|
||||
: theme.colorScheme.card,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
),
|
||||
child: Text(
|
||||
tab['label']!,
|
||||
style: TextStyle(
|
||||
return GestureDetector(
|
||||
onTap: () => provider.setTab(tab['key']!),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
margin: EdgeInsets.only(right: AppSpacing.sm),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.lg,
|
||||
vertical: AppSpacing.sm + AppSpacing.xs,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isActive
|
||||
? Colors.white
|
||||
: theme.colorScheme.mutedForeground,
|
||||
fontWeight: isActive ? FontWeight.w600 : FontWeight.normal,
|
||||
? AppColorScheme.darkPrimary.withValues(alpha: 0.1)
|
||||
: AppColorScheme.darkSurfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(AppRadius.full),
|
||||
border: isActive
|
||||
? Border.all(
|
||||
color: AppColorScheme.darkPrimary.withValues(alpha: 0.2),
|
||||
)
|
||||
: null,
|
||||
boxShadow: isActive
|
||||
? [
|
||||
BoxShadow(
|
||||
color: AppColorScheme.neonGlowPrimary,
|
||||
blurRadius: 15,
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
child: Text(
|
||||
tab['label']!,
|
||||
style: TextStyle(
|
||||
color: isActive
|
||||
? AppColorScheme.darkPrimary
|
||||
: AppColorScheme.darkOnSurfaceVariant,
|
||||
fontWeight: isActive ? FontWeight.w700 : FontWeight.normal,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCoinList(MarketProvider provider) {
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
if (provider.isLoading) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: theme.colorScheme.primary,
|
||||
color: AppColorScheme.darkPrimary,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (provider.error != null) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
LucideIcons.circleAlert,
|
||||
size: 48,
|
||||
color: theme.colorScheme.destructive,
|
||||
),
|
||||
SizedBox(height: AppSpacing.sm + AppSpacing.xs),
|
||||
Text(
|
||||
provider.error!,
|
||||
style: TextStyle(color: theme.colorScheme.destructive),
|
||||
),
|
||||
SizedBox(height: AppSpacing.md),
|
||||
ShadButton(
|
||||
onPressed: provider.loadCoins,
|
||||
child: const Text('重试'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return _buildErrorState(provider);
|
||||
}
|
||||
|
||||
final coins = provider.coins;
|
||||
if (coins.isEmpty) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
LucideIcons.coins,
|
||||
size: 48,
|
||||
color: theme.colorScheme.mutedForeground,
|
||||
),
|
||||
SizedBox(height: AppSpacing.sm + AppSpacing.xs),
|
||||
Text(
|
||||
'暂无数据',
|
||||
style: theme.textTheme.muted,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return _buildEmptyState();
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: provider.refresh,
|
||||
color: theme.colorScheme.primary,
|
||||
color: AppColorScheme.darkPrimary,
|
||||
backgroundColor: AppColorScheme.darkSurfaceContainer,
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.fromLTRB(AppSpacing.md, 0, AppSpacing.md, AppSpacing.md),
|
||||
padding: EdgeInsets.all(AppSpacing.md),
|
||||
itemCount: coins.length,
|
||||
itemBuilder: (context, index) => _buildCoinItem(coins[index]),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCoinItem(Coin coin) {
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: AppSpacing.sm),
|
||||
child: ShadCard(
|
||||
padding: AppSpacing.cardPadding,
|
||||
child: Row(
|
||||
Widget _buildErrorState(MarketProvider provider) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: AppSpacing.pagePadding,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// 图标
|
||||
CircleAvatar(
|
||||
radius: 22,
|
||||
backgroundColor: theme.colorScheme.primary.withValues(alpha: 0.1),
|
||||
child: Text(
|
||||
coin.displayIcon,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
LucideIcons.circleAlert,
|
||||
size: 48,
|
||||
color: AppColorScheme.darkError,
|
||||
),
|
||||
SizedBox(width: AppSpacing.sm + AppSpacing.xs),
|
||||
// 名称
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${coin.code}/USDT',
|
||||
style: theme.textTheme.large.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
coin.name,
|
||||
style: theme.textTheme.muted,
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: AppSpacing.md),
|
||||
Text(
|
||||
provider.error!,
|
||||
style: TextStyle(color: AppColorScheme.darkError),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
// 涨跌幅
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: AppSpacing.sm + AppSpacing.xs, vertical: AppSpacing.xs + AppSpacing.xs),
|
||||
decoration: BoxDecoration(
|
||||
color: getChangeBackgroundColor(coin.isUp),
|
||||
borderRadius: BorderRadius.circular(AppRadius.sm + AppSpacing.xs),
|
||||
),
|
||||
child: Text(
|
||||
coin.formattedChange,
|
||||
style: TextStyle(
|
||||
color: getChangeColor(coin.isUp),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
ShadButton(
|
||||
onPressed: provider.loadCoins,
|
||||
child: const Text('重试'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
LucideIcons.coins,
|
||||
size: 48,
|
||||
color: AppColorScheme.darkOnSurfaceVariant,
|
||||
),
|
||||
SizedBox(height: AppSpacing.md),
|
||||
Text(
|
||||
'暂无数据',
|
||||
style: TextStyle(color: AppColorScheme.darkOnSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCoinItem(Coin coin) {
|
||||
final changeColor = coin.isUp ? AppColorScheme.up : AppColorScheme.down;
|
||||
final changeBgColor = coin.isUp
|
||||
? AppColorScheme.darkTertiary.withValues(alpha: 0.1)
|
||||
: AppColorScheme.darkError.withValues(alpha: 0.1);
|
||||
|
||||
return GlassCard(
|
||||
margin: EdgeInsets.only(bottom: AppSpacing.sm),
|
||||
child: Row(
|
||||
children: [
|
||||
// 图标容器
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColorScheme.darkSurfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
border: Border.all(
|
||||
color: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.2),
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
coin.displayIcon,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: coin.isUp ? AppColorScheme.darkPrimary : AppColorScheme.darkSecondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: AppSpacing.sm + AppSpacing.xs),
|
||||
// 币种信息
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
coin.code,
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColorScheme.darkOnSurface,
|
||||
),
|
||||
),
|
||||
SizedBox(width: AppSpacing.xs),
|
||||
Text(
|
||||
'/USDT',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColorScheme.darkOnSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: AppSpacing.xs / 2),
|
||||
Text(
|
||||
coin.name,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColorScheme.darkOnSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 价格和涨跌幅
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'\$${coin.formattedPrice}',
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColorScheme.darkOnSurface,
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.xs),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.sm,
|
||||
vertical: AppSpacing.xs,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: changeBgColor,
|
||||
borderRadius: BorderRadius.circular(AppRadius.sm),
|
||||
border: Border.all(
|
||||
color: changeColor.withValues(alpha: 0.2),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
coin.formattedChange,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: changeColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,33 @@
|
||||
import 'package:flutter/material.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 '../../../core/theme/app_spacing.dart';
|
||||
import '../../../providers/auth_provider.dart';
|
||||
import '../../../providers/theme_provider.dart';
|
||||
import '../auth/login_page.dart';
|
||||
import '../../components/glass_panel.dart';
|
||||
import '../../components/neon_glow.dart';
|
||||
|
||||
/// 菜单项数据模型
|
||||
class _MenuItem {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final Color? iconColor;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _MenuItem({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
this.iconColor,
|
||||
required this.onTap,
|
||||
});
|
||||
}
|
||||
|
||||
/// 我的页面 - 使用 shadcn_ui 现代化设计
|
||||
/// 我的页面 - Material Design 3 风格
|
||||
class MinePage extends StatefulWidget {
|
||||
const MinePage({super.key});
|
||||
|
||||
@@ -39,6 +44,7 @@ class _MinePageState extends State<MinePage> with AutomaticKeepAliveClientMixin
|
||||
super.build(context);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: AppColorScheme.darkBackground,
|
||||
body: Consumer<AuthProvider>(
|
||||
builder: (context, auth, _) {
|
||||
return SingleChildScrollView(
|
||||
@@ -48,8 +54,18 @@ class _MinePageState extends State<MinePage> with AutomaticKeepAliveClientMixin
|
||||
_UserCard(user: auth.user),
|
||||
SizedBox(height: AppSpacing.md),
|
||||
_MenuList(onShowComingSoon: _showComingSoon, onShowAbout: _showAboutDialog),
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
SizedBox(height: AppSpacing.xl),
|
||||
_LogoutButton(onLogout: () => _handleLogout(auth)),
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
// 版本信息
|
||||
Text(
|
||||
'System Build v1.0.0-Neo',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: AppColorScheme.darkOnSurfaceVariant.withValues(alpha: 0.4),
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -64,7 +80,7 @@ class _MinePageState extends State<MinePage> with AutomaticKeepAliveClientMixin
|
||||
builder: (context) => ShadDialog.alert(
|
||||
title: Row(
|
||||
children: [
|
||||
const Icon(LucideIcons.construction, color: Color(0xFFFF9800), size: 20),
|
||||
Icon(Icons.construction, color: AppColorScheme.warning, size: 20),
|
||||
SizedBox(width: AppSpacing.sm),
|
||||
const Text('功能开发中'),
|
||||
],
|
||||
@@ -81,8 +97,6 @@ class _MinePageState extends State<MinePage> with AutomaticKeepAliveClientMixin
|
||||
}
|
||||
|
||||
void _showAboutDialog() {
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
showShadDialog(
|
||||
context: context,
|
||||
builder: (context) => ShadDialog(
|
||||
@@ -97,11 +111,14 @@ class _MinePageState extends State<MinePage> with AutomaticKeepAliveClientMixin
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('虚拟货币模拟交易平台', style: theme.textTheme.muted),
|
||||
Text(
|
||||
'虚拟货币模拟交易平台',
|
||||
style: TextStyle(color: AppColorScheme.darkOnSurfaceVariant),
|
||||
),
|
||||
SizedBox(height: AppSpacing.md),
|
||||
_InfoRow(icon: LucideIcons.code, text: '版本: 1.0.0'),
|
||||
_InfoRow(icon: Icons.code, text: '版本: 1.0.0'),
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
const _InfoRow(icon: LucideIcons.heart, text: 'Built with Flutter & shadcn_ui'),
|
||||
_InfoRow(icon: Icons.favorite, text: 'Built with Flutter & Material Design 3'),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
@@ -144,7 +161,7 @@ class _MinePageState extends State<MinePage> with AutomaticKeepAliveClientMixin
|
||||
}
|
||||
}
|
||||
|
||||
/// 用户卡片组件
|
||||
/// 用户卡片组件 - Material Design 3 风格
|
||||
class _UserCard extends StatelessWidget {
|
||||
final dynamic user;
|
||||
|
||||
@@ -152,34 +169,92 @@ class _UserCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
return ShadCard(
|
||||
return GlassPanel(
|
||||
padding: EdgeInsets.all(AppSpacing.lg + AppSpacing.sm),
|
||||
child: Row(
|
||||
children: [
|
||||
_AppLogo(radius: 32, fontSize: 24, text: user?.avatarText),
|
||||
SizedBox(width: AppSpacing.md),
|
||||
// 头像 - 带霓虹边框
|
||||
Stack(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColorScheme.neonGlowPrimary,
|
||||
blurRadius: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: _AppLogo(radius: 36, fontSize: 28, text: user?.avatarText),
|
||||
),
|
||||
// 验证徽章
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColorScheme.darkTertiary,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: AppColorScheme.darkBackground,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.verified,
|
||||
size: 14,
|
||||
color: AppColorScheme.darkOnTertiary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(width: AppSpacing.md + AppSpacing.xs),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
user?.username ?? '未登录',
|
||||
style: theme.textTheme.h3.copyWith(fontWeight: FontWeight.bold),
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColorScheme.darkOnSurface,
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.sm - AppSpacing.xs),
|
||||
ShadBadge(
|
||||
backgroundColor: theme.colorScheme.primary.withValues(alpha: 0.2),
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
// 用户等级标签
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.md,
|
||||
vertical: AppSpacing.xs,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColorScheme.darkPrimary.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(AppRadius.full),
|
||||
border: Border.all(
|
||||
color: AppColorScheme.darkPrimary.withValues(alpha: 0.2),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'普通用户',
|
||||
style: TextStyle(fontSize: 12, color: theme.colorScheme.primary),
|
||||
'NORMAL USER',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0.2,
|
||||
color: AppColorScheme.darkPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(LucideIcons.chevronRight, color: theme.colorScheme.mutedForeground),
|
||||
Icon(
|
||||
LucideIcons.chevronRight,
|
||||
color: AppColorScheme.darkOnSurfaceVariant,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -196,16 +271,14 @@ class _AppLogo extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
return CircleAvatar(
|
||||
radius: radius,
|
||||
backgroundColor: theme.colorScheme.primary.withValues(alpha: 0.2),
|
||||
backgroundColor: AppColorScheme.darkPrimary.withValues(alpha: 0.2),
|
||||
child: Text(
|
||||
text ?? '₿',
|
||||
style: TextStyle(
|
||||
fontSize: fontSize,
|
||||
color: theme.colorScheme.primary,
|
||||
color: AppColorScheme.darkPrimary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@@ -222,19 +295,23 @@ class _InfoRow extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Icon(icon, size: 14, color: theme.colorScheme.mutedForeground),
|
||||
SizedBox(width: AppSpacing.sm - AppSpacing.xs),
|
||||
Text(text, style: theme.textTheme.muted.copyWith(fontSize: 12)),
|
||||
Icon(icon, size: 14, color: AppColorScheme.darkOnSurfaceVariant),
|
||||
SizedBox(width: AppSpacing.sm),
|
||||
Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColorScheme.darkOnSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 菜单列表组件
|
||||
/// 菜单列表组件 - Glass Panel 风格
|
||||
class _MenuList extends StatelessWidget {
|
||||
final void Function(String) onShowComingSoon;
|
||||
final VoidCallback onShowAbout;
|
||||
@@ -243,34 +320,75 @@ class _MenuList extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
final themeProvider = context.watch<ThemeProvider>();
|
||||
|
||||
return ShadCard(
|
||||
return GlassPanel(
|
||||
padding: EdgeInsets.zero,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xxl),
|
||||
child: Column(
|
||||
children: [
|
||||
// 主题切换开关(特殊处理)
|
||||
// 主题切换开关
|
||||
_ThemeToggleTile(isDarkMode: themeProvider.isDarkMode),
|
||||
Divider(color: theme.colorScheme.border, height: 1, indent: 56),
|
||||
// 普通菜单项
|
||||
for (var i = 0; i < _buildMenuItems().length; i++) ...[
|
||||
_MenuItemTile(item: _buildMenuItems()[i]),
|
||||
if (i < _buildMenuItems().length - 1)
|
||||
Divider(color: theme.colorScheme.border, height: 1, indent: 56),
|
||||
],
|
||||
_buildDivider(),
|
||||
// 菜单项
|
||||
..._buildMenuItems(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<_MenuItem> _buildMenuItems() {
|
||||
Widget _buildDivider() {
|
||||
return Container(
|
||||
margin: EdgeInsets.only(left: 56),
|
||||
height: 1,
|
||||
color: AppColorScheme.glassPanelBorder,
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildMenuItems() {
|
||||
final items = [
|
||||
_MenuItem(
|
||||
icon: LucideIcons.userCheck,
|
||||
title: '实名认证',
|
||||
subtitle: '完成实名认证,解锁更多功能',
|
||||
iconColor: AppColorScheme.darkPrimary,
|
||||
onTap: () => onShowComingSoon('实名认证'),
|
||||
),
|
||||
_MenuItem(
|
||||
icon: LucideIcons.shield,
|
||||
title: '安全设置',
|
||||
subtitle: '密码、二次验证等安全设置',
|
||||
iconColor: AppColorScheme.darkSecondary,
|
||||
onTap: () => onShowComingSoon('安全设置'),
|
||||
),
|
||||
_MenuItem(
|
||||
icon: LucideIcons.bell,
|
||||
title: '消息通知',
|
||||
subtitle: '管理消息推送设置',
|
||||
iconColor: AppColorScheme.darkTertiary,
|
||||
onTap: () => onShowComingSoon('消息通知'),
|
||||
),
|
||||
_MenuItem(
|
||||
icon: LucideIcons.settings,
|
||||
title: '系统设置',
|
||||
subtitle: '主题、语言等偏好设置',
|
||||
iconColor: AppColorScheme.darkPrimary,
|
||||
onTap: () => onShowComingSoon('系统设置'),
|
||||
),
|
||||
_MenuItem(
|
||||
icon: LucideIcons.info,
|
||||
title: '关于我们',
|
||||
subtitle: '版本信息与用户协议',
|
||||
iconColor: AppColorScheme.darkOnSurfaceVariant,
|
||||
onTap: onShowAbout,
|
||||
),
|
||||
];
|
||||
|
||||
return [
|
||||
_MenuItem(icon: LucideIcons.userCheck, title: '实名认证', subtitle: '完成实名认证,解锁更多功能', onTap: () => onShowComingSoon('实名认证')),
|
||||
_MenuItem(icon: LucideIcons.shield, title: '安全设置', subtitle: '密码、二次验证等安全设置', onTap: () => onShowComingSoon('安全设置')),
|
||||
_MenuItem(icon: LucideIcons.bell, title: '消息通知', subtitle: '管理消息推送设置', onTap: () => onShowComingSoon('消息通知')),
|
||||
_MenuItem(icon: LucideIcons.settings, title: '系统设置', subtitle: '主题、语言等偏好设置', onTap: () => onShowComingSoon('系统设置')),
|
||||
_MenuItem(icon: LucideIcons.info, title: '关于我们', subtitle: '版本信息与用户协议', onTap: onShowAbout),
|
||||
for (var i = 0; i < items.length; i++) ...[
|
||||
_MenuItemTile(item: items[i]),
|
||||
if (i < items.length - 1) _buildDivider(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -283,26 +401,41 @@ class _ThemeToggleTile extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
final themeProvider = context.read<ThemeProvider>();
|
||||
|
||||
return InkWell(
|
||||
onTap: () => themeProvider.toggleTheme(),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: AppSpacing.md, vertical: AppSpacing.sm + AppSpacing.xs),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.md,
|
||||
vertical: AppSpacing.sm + AppSpacing.xs,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
_MenuIcon(icon: isDarkMode ? LucideIcons.moon : LucideIcons.sun),
|
||||
_MenuIcon(
|
||||
icon: isDarkMode ? LucideIcons.moon : LucideIcons.sun,
|
||||
color: AppColorScheme.darkPrimary,
|
||||
),
|
||||
SizedBox(width: AppSpacing.sm + AppSpacing.xs),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('深色模式', style: theme.textTheme.small.copyWith(fontWeight: FontWeight.w500)),
|
||||
Text(
|
||||
'深色模式',
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColorScheme.darkOnSurface,
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.xs / 2),
|
||||
Text(
|
||||
isDarkMode ? '当前:深色主题' : '当前:浅色主题',
|
||||
style: theme.textTheme.muted.copyWith(fontSize: 11),
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: AppColorScheme.darkOnSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -310,8 +443,8 @@ class _ThemeToggleTile extends StatelessWidget {
|
||||
Switch(
|
||||
value: isDarkMode,
|
||||
onChanged: (_) => themeProvider.toggleTheme(),
|
||||
activeTrackColor: theme.colorScheme.primary.withValues(alpha: 0.5),
|
||||
activeColor: theme.colorScheme.primary,
|
||||
activeTrackColor: AppColorScheme.darkPrimary.withValues(alpha: 0.5),
|
||||
activeColor: AppColorScheme.darkPrimary,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -328,29 +461,47 @@ class _MenuItemTile extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
return InkWell(
|
||||
onTap: item.onTap,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: AppSpacing.md, vertical: AppSpacing.sm + AppSpacing.xs),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.md,
|
||||
vertical: AppSpacing.sm + AppSpacing.xs,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
_MenuIcon(icon: item.icon),
|
||||
_MenuIcon(icon: item.icon, color: item.iconColor),
|
||||
SizedBox(width: AppSpacing.sm + AppSpacing.xs),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(item.title, style: theme.textTheme.small.copyWith(fontWeight: FontWeight.w500)),
|
||||
Text(
|
||||
item.title,
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColorScheme.darkOnSurface,
|
||||
),
|
||||
),
|
||||
if (item.subtitle != null) ...[
|
||||
SizedBox(height: AppSpacing.xs / 2),
|
||||
Text(item.subtitle!, style: theme.textTheme.muted.copyWith(fontSize: 11)),
|
||||
Text(
|
||||
item.subtitle!,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: AppColorScheme.darkOnSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(LucideIcons.chevronRight, size: 18, color: theme.colorScheme.mutedForeground),
|
||||
Icon(
|
||||
LucideIcons.chevronRight,
|
||||
size: 18,
|
||||
color: AppColorScheme.darkOnSurfaceVariant,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -358,29 +509,33 @@ class _MenuItemTile extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// 菜单图标组件
|
||||
/// 菜单图标组件 - Material Design 3 风格
|
||||
class _MenuIcon extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final Color? color;
|
||||
|
||||
const _MenuIcon({required this.icon});
|
||||
const _MenuIcon({required this.icon, this.color});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
final iconColor = color ?? AppColorScheme.darkPrimary;
|
||||
|
||||
return Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary.withValues(alpha: 0.1),
|
||||
color: iconColor.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(AppRadius.md + AppSpacing.xs),
|
||||
border: Border.all(
|
||||
color: iconColor.withValues(alpha: 0.2),
|
||||
),
|
||||
),
|
||||
child: Icon(icon, size: 20, color: theme.colorScheme.primary),
|
||||
child: Icon(icon, size: 20, color: iconColor),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 退出登录按钮
|
||||
/// 退出登录按钮 - 带霓虹光效
|
||||
class _LogoutButton extends StatelessWidget {
|
||||
final VoidCallback onLogout;
|
||||
|
||||
@@ -388,20 +543,13 @@ class _LogoutButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
return NeonButton(
|
||||
text: 'Logout Terminal',
|
||||
type: NeonButtonType.error,
|
||||
icon: Icons.logout,
|
||||
onPressed: onLogout,
|
||||
width: double.infinity,
|
||||
height: 48,
|
||||
child: ShadButton.destructive(
|
||||
onPressed: onLogout,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(LucideIcons.logOut, size: 18),
|
||||
SizedBox(width: AppSpacing.sm),
|
||||
const Text('退出登录', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
||||
],
|
||||
),
|
||||
),
|
||||
showGlow: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../../core/theme/app_spacing.dart';
|
||||
import '../../../core/theme/app_color_scheme.dart';
|
||||
import '../../../data/models/order_models.dart';
|
||||
import '../../../providers/asset_provider.dart';
|
||||
|
||||
class _FundOrderCard extends StatelessWidget {
|
||||
final OrderFund order;
|
||||
@@ -121,7 +123,7 @@ class _FundOrderCard extends StatelessWidget {
|
||||
children: [
|
||||
Text('创建时间: ', style: theme.textTheme.muted),
|
||||
Text(
|
||||
order.createTime ?? '无',
|
||||
order.createTime?.toString() ?? '无',
|
||||
style: theme.textTheme.small,
|
||||
),
|
||||
],
|
||||
@@ -172,7 +174,7 @@ class _FundOrderCard extends StatelessWidget {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: const Text('已确认打款,请等待审核')),
|
||||
);
|
||||
context.read<AssetProvider>().refreshFundOrders();
|
||||
context.read<AssetProvider>().loadFundOrders();
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(response.message ?? '确认失败')),
|
||||
@@ -188,7 +190,7 @@ class _FundOrderCard extends StatelessWidget {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: const Text('订单已取消')),
|
||||
);
|
||||
context.read<AssetProvider>().refreshFundOrders();
|
||||
context.read<AssetProvider>().loadFundOrders();
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(response.message ?? '取消失败')),
|
||||
|
||||
@@ -4,11 +4,12 @@ import 'package:provider/provider.dart';
|
||||
import '../../../core/theme/app_spacing.dart';
|
||||
import '../../../providers/asset_provider.dart';
|
||||
import '../../../data/models/order_models.dart';
|
||||
import 'fund_order_card.dart';
|
||||
|
||||
class _FundOrdersList extends StatelessWidget {
|
||||
class FundOrdersList extends StatelessWidget {
|
||||
final AssetProvider provider;
|
||||
|
||||
const _FundOrdersList({required this.provider});
|
||||
const FundOrdersList({super.key, required this.provider});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -16,14 +17,14 @@ class _FundOrdersList extends StatelessWidget {
|
||||
final orders = provider.fundOrders;
|
||||
|
||||
if (orders.isEmpty) {
|
||||
return const _EmptyState(
|
||||
return _EmptyState(
|
||||
icon: LucideIcons.receipt,
|
||||
message: '暂无充提记录',
|
||||
);
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => provider.refreshFundOrders(),
|
||||
onRefresh: () => provider.loadFundOrders(),
|
||||
color: theme.colorScheme.primary,
|
||||
child: ListView.separated(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
@@ -32,10 +33,177 @@ class _FundOrdersList extends StatelessWidget {
|
||||
separatorBuilder: (_, __) => Divider(color: theme.colorScheme.border, height: 1),
|
||||
itemBuilder: (context, index) {
|
||||
final order = orders[index];
|
||||
return _FundOrderCard(order: order);
|
||||
return FundOrderCard(order: order);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 空状态组件
|
||||
class _EmptyState extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String message;
|
||||
|
||||
const _EmptyState({required this.icon, required this.message});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(AppSpacing.xl),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(icon, size: 48, color: theme.colorScheme.mutedForeground),
|
||||
SizedBox(height: AppSpacing.sm + AppSpacing.xs),
|
||||
Text(message, style: theme.textTheme.muted),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 充值订单卡片 - 公开类
|
||||
class FundOrderCard extends StatelessWidget {
|
||||
final OrderFund order;
|
||||
|
||||
const FundOrderCard({super.key, required this.order});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 直接使用 _FundOrderCard 的实现
|
||||
return _FundOrderCardContent(order: order);
|
||||
}
|
||||
}
|
||||
|
||||
/// 订单卡片内容
|
||||
class _FundOrderCardContent extends StatelessWidget {
|
||||
final OrderFund order;
|
||||
|
||||
const _FundOrderCardContent({required this.order});
|
||||
|
||||
Color _getStatusColor(int status, bool isDeposit) {
|
||||
if (isDeposit) {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return const Color(0xFFFF9800);
|
||||
case 2:
|
||||
return const Color(0xFF2196F3);
|
||||
case 3:
|
||||
return const Color(0xFFafffd1);
|
||||
case 4:
|
||||
return const Color(0xFFff716c);
|
||||
case 5:
|
||||
return const Color(0xFFa9abb3);
|
||||
default:
|
||||
return const Color(0xFFa9abb3);
|
||||
}
|
||||
} else {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return const Color(0xFFFF9800);
|
||||
case 2:
|
||||
return const Color(0xFFafffd1);
|
||||
case 3:
|
||||
return const Color(0xFFff716c);
|
||||
case 4:
|
||||
return const Color(0xFFa9abb3);
|
||||
default:
|
||||
return const Color(0xFFa9abb3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _getStatusText(int status, bool isDeposit) {
|
||||
if (isDeposit) {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return '待付款';
|
||||
case 2:
|
||||
return '待确认';
|
||||
case 3:
|
||||
return '已完成';
|
||||
case 4:
|
||||
return '已驳回';
|
||||
case 5:
|
||||
return '已取消';
|
||||
default:
|
||||
return '未知';
|
||||
}
|
||||
} else {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return '待审批';
|
||||
case 2:
|
||||
return '已完成';
|
||||
case 3:
|
||||
return '已驳回';
|
||||
case 4:
|
||||
return '已取消';
|
||||
default:
|
||||
return '未知';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
final isDeposit = order.type == 1;
|
||||
final statusColor = _getStatusColor(order.status, isDeposit);
|
||||
|
||||
return ShadCard(
|
||||
padding: AppSpacing.cardPadding,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'${isDeposit ? '+' : '-'}${order.amount} USDT',
|
||||
style: theme.textTheme.large.copyWith(
|
||||
color: statusColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
_getStatusText(order.status, isDeposit),
|
||||
style: theme.textTheme.small.copyWith(color: statusColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
Row(
|
||||
children: [
|
||||
Text('订单号: ', style: theme.textTheme.muted),
|
||||
Text(order.orderNo, style: theme.textTheme.small),
|
||||
],
|
||||
),
|
||||
SizedBox(height: AppSpacing.xs),
|
||||
Row(
|
||||
children: [
|
||||
Text('创建时间: ', style: theme.textTheme.muted),
|
||||
Text(
|
||||
order.createTime?.toString() ?? '无',
|
||||
style: theme.textTheme.small,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:provider/provider.dart';
|
||||
import '../../../core/theme/app_color_scheme.dart';
|
||||
import '../../../core/theme/app_spacing.dart';
|
||||
import '../../../providers/asset_provider.dart';
|
||||
import '../home/home_page.dart';
|
||||
import 'fund_orders_list.dart';
|
||||
|
||||
/// 订单管理页面
|
||||
class OrdersPage extends StatefulWidget {
|
||||
@@ -16,7 +16,7 @@ class OrdersPage extends StatefulWidget {
|
||||
|
||||
class _OrdersPageState extends State<OrdersPage> with AutomaticKeepAliveClientMixin {
|
||||
int _activeTab = 0;
|
||||
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@@ -38,7 +38,7 @@ class _OrdersPageState extends State<OrdersPage> with AutomaticKeepAliveClientMi
|
||||
return Scaffold(
|
||||
backgroundColor: theme.colorScheme.background,
|
||||
body: Consumer<AssetProvider>(
|
||||
builder: (context, provider) {
|
||||
builder: (context, provider, _) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => provider.refreshAll(force: true),
|
||||
color: theme.colorScheme.primary,
|
||||
@@ -47,22 +47,106 @@ class _OrdersPageState extends State<OrdersPage> with AutomaticKeepAliveClientMi
|
||||
padding: AppSpacing.pagePadding,
|
||||
child: Column(
|
||||
children: [
|
||||
_TabSelector(
|
||||
TabSelector(
|
||||
tabs: const ['充提记录', '交易记录'],
|
||||
selectedIndex: _activeTab,
|
||||
onChanged: (index) => setState(() => _activeTab = index),
|
||||
),
|
||||
SizedBox(height: AppSpacing.md),
|
||||
_activeTab == 0
|
||||
? _FundOrdersList(provider: provider)
|
||||
: _TradeOrdersList(provider: provider),
|
||||
? FundOrdersList(provider: provider)
|
||||
: TradeOrdersList(provider: provider),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tab 选择器
|
||||
class TabSelector extends StatelessWidget {
|
||||
final List<String> tabs;
|
||||
final int selectedIndex;
|
||||
final ValueChanged<int> onChanged;
|
||||
|
||||
const TabSelector({
|
||||
super.key,
|
||||
required this.tabs,
|
||||
required this.selectedIndex,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.all(AppSpacing.xs),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.card,
|
||||
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 ? theme.colorScheme.primary : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: isSelected ? Colors.white : theme.colorScheme.mutedForeground,
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 交易订单列表
|
||||
class TradeOrdersList extends StatelessWidget {
|
||||
final AssetProvider provider;
|
||||
|
||||
const TradeOrdersList({super.key, required this.provider});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
// Trade orders feature not yet implemented in provider
|
||||
// Using tradeAccounts (holdings) as placeholder for now
|
||||
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(AppSpacing.xl),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(LucideIcons.receipt, size: 48, color: theme.colorScheme.mutedForeground),
|
||||
SizedBox(height: AppSpacing.sm + AppSpacing.xs),
|
||||
Text('暂无交易记录', style: theme.textTheme.muted),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import 'package:flutter/material.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 '../../../core/theme/app_spacing.dart';
|
||||
import '../../../data/models/coin.dart';
|
||||
import '../../../providers/market_provider.dart';
|
||||
import '../../../providers/asset_provider.dart';
|
||||
import '../../shared/ui_constants.dart';
|
||||
import '../../components/glass_panel.dart';
|
||||
import '../../components/neon_glow.dart';
|
||||
|
||||
/// 交易页面 - 使用 shadcn_ui 现代化设计
|
||||
/// 交易页面 - Material Design 3 风格
|
||||
class TradePage extends StatefulWidget {
|
||||
const TradePage({super.key});
|
||||
|
||||
@@ -17,15 +20,15 @@ class TradePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _TradePageState extends State<TradePage> with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
int _tradeType = 0; // 0=买入, 1=卖出
|
||||
Coin? _selectedCoin;
|
||||
final _formKey = GlobalKey<ShadFormState>();
|
||||
final _priceController = TextEditingController();
|
||||
final _quantityController = TextEditingController();
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -46,10 +49,9 @@ class _TradePageState extends State<TradePage> with AutomaticKeepAliveClientMixi
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: theme.colorScheme.background,
|
||||
backgroundColor: AppColorScheme.darkBackground,
|
||||
body: Consumer2<MarketProvider, AssetProvider>(
|
||||
builder: (context, market, asset, _) {
|
||||
return SingleChildScrollView(
|
||||
@@ -77,7 +79,7 @@ class _TradePageState extends State<TradePage> with AutomaticKeepAliveClientMixi
|
||||
tradeBalance: asset.overview?.tradeBalance,
|
||||
onTradeTypeChanged: (type) => setState(() => _tradeType = type),
|
||||
),
|
||||
SizedBox(height: AppSpacing.md),
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
_TradeButton(
|
||||
isBuy: _tradeType == 0,
|
||||
coinCode: _selectedCoin?.code,
|
||||
@@ -121,7 +123,6 @@ class _TradePageState extends State<TradePage> with AutomaticKeepAliveClientMixi
|
||||
}
|
||||
|
||||
void _showTradeResult() {
|
||||
final theme = ShadTheme.of(context);
|
||||
final isBuy = _tradeType == 0;
|
||||
|
||||
showShadDialog(
|
||||
@@ -129,7 +130,11 @@ class _TradePageState extends State<TradePage> with AutomaticKeepAliveClientMixi
|
||||
builder: (ctx) => ShadDialog.alert(
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(LucideIcons.circleCheck, color: theme.colorScheme.primary, size: 24),
|
||||
NeonIcon(
|
||||
icon: Icons.check_circle,
|
||||
color: AppColorScheme.darkPrimary,
|
||||
size: 24,
|
||||
),
|
||||
SizedBox(width: AppSpacing.sm),
|
||||
const Text('交易成功'),
|
||||
],
|
||||
@@ -149,7 +154,7 @@ class _TradePageState extends State<TradePage> with AutomaticKeepAliveClientMixi
|
||||
}
|
||||
}
|
||||
|
||||
/// 币种选择器
|
||||
/// 币种选择器 - Glass Panel 风格
|
||||
class _CoinSelector extends StatelessWidget {
|
||||
final Coin? selectedCoin;
|
||||
final List<Coin> coins;
|
||||
@@ -163,15 +168,13 @@ class _CoinSelector extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
// 自动选择第一个币种
|
||||
if (selectedCoin == null && coins.isNotEmpty) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => onCoinLoaded(coins.first));
|
||||
}
|
||||
|
||||
return ShadCard(
|
||||
padding: AppSpacing.cardPadding,
|
||||
return GlassCard(
|
||||
showNeonGlow: false,
|
||||
child: Row(
|
||||
children: [
|
||||
_CoinAvatar(icon: selectedCoin?.displayIcon),
|
||||
@@ -182,21 +185,34 @@ class _CoinSelector extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
selectedCoin != null ? '${selectedCoin!.code}/USDT' : '选择币种',
|
||||
style: theme.textTheme.large.copyWith(fontWeight: FontWeight.bold),
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColorScheme.darkOnSurface,
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.xs),
|
||||
Text(selectedCoin?.name ?? '点击选择交易对', style: theme.textTheme.muted),
|
||||
Text(
|
||||
selectedCoin?.name ?? '点击选择交易对',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColorScheme.darkOnSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(LucideIcons.chevronRight, color: theme.colorScheme.mutedForeground),
|
||||
Icon(
|
||||
LucideIcons.chevronRight,
|
||||
color: AppColorScheme.darkOnSurfaceVariant,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 币种头像
|
||||
/// 币种头像 - 带霓虹光效
|
||||
class _CoinAvatar extends StatelessWidget {
|
||||
final String? icon;
|
||||
|
||||
@@ -204,20 +220,31 @@ class _CoinAvatar extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
return CircleAvatar(
|
||||
radius: 22,
|
||||
backgroundColor: theme.colorScheme.primary.withValues(alpha: 0.1),
|
||||
child: Text(
|
||||
icon ?? '?',
|
||||
style: TextStyle(fontSize: 20, color: theme.colorScheme.primary),
|
||||
return Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColorScheme.darkPrimary.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
border: Border.all(
|
||||
color: AppColorScheme.darkPrimary.withValues(alpha: 0.2),
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
icon ?? '?',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: AppColorScheme.darkPrimary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 价格卡片
|
||||
/// 价格卡片 - Glass Panel 风格
|
||||
class _PriceCard extends StatelessWidget {
|
||||
final Coin coin;
|
||||
|
||||
@@ -225,31 +252,56 @@ class _PriceCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
final color = coin.isUp ? AppColorScheme.up : AppColorScheme.down;
|
||||
final bgColor = coin.isUp
|
||||
? AppColorScheme.darkTertiary.withValues(alpha: 0.1)
|
||||
: AppColorScheme.darkError.withValues(alpha: 0.1);
|
||||
|
||||
return ShadCard(
|
||||
padding: AppSpacing.cardPadding,
|
||||
return GlassCard(
|
||||
showNeonGlow: false,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('最新价', style: theme.textTheme.muted),
|
||||
Text(
|
||||
'最新价',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColorScheme.darkOnSurfaceVariant,
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.xs),
|
||||
Text('\$${coin.formattedPrice}', style: theme.textTheme.h2.copyWith(fontWeight: FontWeight.bold)),
|
||||
Text(
|
||||
'\$${coin.formattedPrice}',
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColorScheme.darkOnSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: AppSpacing.sm + AppSpacing.xs, vertical: AppSpacing.xs + AppSpacing.xs),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.md,
|
||||
vertical: AppSpacing.sm,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.2),
|
||||
color: bgColor,
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
border: Border.all(
|
||||
color: color.withValues(alpha: 0.2),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
coin.formattedChange,
|
||||
style: TextStyle(fontSize: 16, color: color, fontWeight: FontWeight.w600),
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: color,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -258,7 +310,7 @@ class _PriceCard extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// 交易表单
|
||||
/// 交易表单 - Glass Panel 风格
|
||||
class _TradeForm extends StatelessWidget {
|
||||
final int tradeType;
|
||||
final Coin? selectedCoin;
|
||||
@@ -278,10 +330,8 @@ class _TradeForm extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
return ShadCard(
|
||||
padding: AppSpacing.cardPadding,
|
||||
return GlassPanel(
|
||||
padding: EdgeInsets.all(AppSpacing.lg),
|
||||
child: Column(
|
||||
children: [
|
||||
// 买入/卖出切换
|
||||
@@ -289,45 +339,96 @@ class _TradeForm extends StatelessWidget {
|
||||
tradeType: tradeType,
|
||||
onChanged: onTradeTypeChanged,
|
||||
),
|
||||
SizedBox(height: AppSpacing.lg + AppSpacing.xs),
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
// 价格输入
|
||||
ShadInputFormField(
|
||||
id: 'price',
|
||||
label: const Text('价格(USDT)'),
|
||||
_buildInputField(
|
||||
label: '价格(USDT)',
|
||||
controller: priceController,
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
placeholder: const Text('输入价格'),
|
||||
trailing: Padding(
|
||||
padding: EdgeInsets.only(right: AppSpacing.sm),
|
||||
child: const Text('USDT'),
|
||||
),
|
||||
validator: Validators.price,
|
||||
placeholder: '输入价格',
|
||||
suffix: 'USDT',
|
||||
),
|
||||
SizedBox(height: AppSpacing.md),
|
||||
// 数量输入
|
||||
ShadInputFormField(
|
||||
id: 'quantity',
|
||||
label: const Text('数量'),
|
||||
_buildInputField(
|
||||
label: '数量',
|
||||
controller: quantityController,
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
placeholder: const Text('输入数量'),
|
||||
trailing: Padding(
|
||||
padding: EdgeInsets.only(right: AppSpacing.sm),
|
||||
child: Text(selectedCoin?.code ?? ''),
|
||||
),
|
||||
validator: Validators.quantity,
|
||||
placeholder: '输入数量',
|
||||
suffix: selectedCoin?.code ?? '',
|
||||
),
|
||||
SizedBox(height: AppSpacing.md),
|
||||
// 交易金额
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
// 信息行
|
||||
_InfoRow(label: '交易金额', value: '${_calculateAmount()} USDT'),
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
// 可用余额
|
||||
_InfoRow(label: '可用', value: '${tradeBalance ?? '0.00'} USDT'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInputField({
|
||||
required String label,
|
||||
required TextEditingController controller,
|
||||
required String placeholder,
|
||||
required String suffix,
|
||||
}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0.2,
|
||||
color: AppColorScheme.darkOnSurfaceVariant,
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.xs),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColorScheme.darkSurfaceLowest,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
border: Border.all(
|
||||
color: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.3),
|
||||
),
|
||||
),
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColorScheme.darkOnSurface,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: placeholder,
|
||||
hintStyle: TextStyle(
|
||||
color: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.5),
|
||||
),
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.md,
|
||||
vertical: AppSpacing.md,
|
||||
),
|
||||
suffixIcon: Padding(
|
||||
padding: EdgeInsets.only(right: AppSpacing.sm),
|
||||
child: Text(
|
||||
suffix,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColorScheme.darkOnSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
suffixIconConstraints: const BoxConstraints(minWidth: 50),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _calculateAmount() {
|
||||
final price = double.tryParse(priceController.text) ?? 0;
|
||||
final quantity = double.tryParse(quantityController.text) ?? 0;
|
||||
@@ -335,7 +436,7 @@ class _TradeForm extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// 交易类型选择器
|
||||
/// 交易类型选择器 - Material Design 3 风格
|
||||
class _TradeTypeSelector extends StatelessWidget {
|
||||
final int tradeType;
|
||||
final ValueChanged<int> onChanged;
|
||||
@@ -344,26 +445,33 @@ class _TradeTypeSelector extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _TypeButton(
|
||||
label: '买入',
|
||||
isSelected: tradeType == 0,
|
||||
color: AppColorScheme.up,
|
||||
onTap: () => onChanged(0),
|
||||
return Container(
|
||||
padding: EdgeInsets.all(AppSpacing.xs),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColorScheme.darkSurfaceLowest,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _TypeButton(
|
||||
label: 'Buy',
|
||||
isSelected: tradeType == 0,
|
||||
color: AppColorScheme.up,
|
||||
onTap: () => onChanged(0),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: AppSpacing.md),
|
||||
Expanded(
|
||||
child: _TypeButton(
|
||||
label: '卖出',
|
||||
isSelected: tradeType == 1,
|
||||
color: AppColorScheme.down,
|
||||
onTap: () => onChanged(1),
|
||||
SizedBox(width: AppSpacing.sm),
|
||||
Expanded(
|
||||
child: _TypeButton(
|
||||
label: 'Sell',
|
||||
isSelected: tradeType == 1,
|
||||
color: AppColorScheme.down,
|
||||
onTap: () => onChanged(1),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -386,19 +494,22 @@ class _TypeButton extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: EdgeInsets.symmetric(vertical: AppSpacing.sm + AppSpacing.xs),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? color : Colors.transparent,
|
||||
color: isSelected ? color.withValues(alpha: 0.15) : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
border: isSelected ? null : Border.all(color: color),
|
||||
border: isSelected ? null : Border.all(color: color.withValues(alpha: 0.3)),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: isSelected ? Colors.white : color,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isSelected ? color : color.withValues(alpha: 0.7),
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 14,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -416,19 +527,30 @@ class _InfoRow extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(label, style: theme.textTheme.muted),
|
||||
Text(value, style: theme.textTheme.small.copyWith(fontWeight: FontWeight.w600)),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColorScheme.darkOnSurfaceVariant,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColorScheme.darkOnSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 交易按钮
|
||||
/// 交易按钮 - 带霓虹光效
|
||||
class _TradeButton extends StatelessWidget {
|
||||
final bool isBuy;
|
||||
final String? coinCode;
|
||||
@@ -442,26 +564,13 @@ class _TradeButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = isBuy ? AppColorScheme.up : AppColorScheme.down;
|
||||
|
||||
return SizedBox(
|
||||
return NeonButton(
|
||||
text: '${isBuy ? '买入' : '卖出'} ${coinCode ?? ''}',
|
||||
type: isBuy ? NeonButtonType.tertiary : NeonButtonType.error,
|
||||
icon: isBuy ? Icons.arrow_downward : Icons.arrow_upward,
|
||||
onPressed: onPressed,
|
||||
width: double.infinity,
|
||||
height: 48,
|
||||
child: ShadButton(
|
||||
backgroundColor: color,
|
||||
onPressed: onPressed,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(isBuy ? LucideIcons.arrowDownToLine : LucideIcons.arrowUpFromLine, size: 18, color: Colors.white),
|
||||
SizedBox(width: AppSpacing.sm),
|
||||
Text(
|
||||
'${isBuy ? '买入' : '卖出'} ${coinCode ?? ''}',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
showGlow: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user