diff --git a/flutter_monisuo/lib/ui/components/asset_card.dart b/flutter_monisuo/lib/ui/components/asset_card.dart index 4b6d884..5cff1cf 100644 --- a/flutter_monisuo/lib/ui/components/asset_card.dart +++ b/flutter_monisuo/lib/ui/components/asset_card.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; -import '../../core/theme/app_colors.dart'; +import '../../core/theme/app_color_scheme.dart'; /// 资产卡片组件 - 用于显示资产总览 class AssetCard extends StatelessWidget { @@ -13,9 +13,9 @@ class AssetCard extends StatelessWidget { final Gradient? gradient; final VoidCallback? onTap; - // 默认渐变色 - 使用品牌蓝 + // 默认渐变色 - 使用品牌青绿色 static const defaultGradient = LinearGradient( - colors: [Color(0xFF2563EB), Color(0xFF1D4ED8)], + colors: [AppColorScheme.primaryDark, Color(0xFF00B894)], begin: Alignment.topLeft, end: Alignment.bottomRight, ); @@ -54,7 +54,7 @@ class AssetCard extends StatelessWidget { borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.1), + color: Colors.black.withValues(alpha: 0.1), blurRadius: 10, offset: const Offset(0, 4), ), @@ -72,7 +72,7 @@ class AssetCard extends StatelessWidget { ), const Spacer(), if (onTap != null) - Icon( + const Icon( LucideIcons.chevronRight, color: Colors.white70, size: 18, @@ -161,9 +161,6 @@ class AssetCardCompact extends StatelessWidget { final bool? isUp; final VoidCallback? onTap; - static const upColor = Color(0xFF00C853); - static const downColor = Color(0xFFFF5252); - const AssetCardCompact({ super.key, required this.title, @@ -176,6 +173,7 @@ class AssetCardCompact extends StatelessWidget { @override Widget build(BuildContext context) { final theme = ShadTheme.of(context); + final isValueUp = isUp ?? true; return ShadCard( padding: const EdgeInsets.all(16), @@ -205,13 +203,13 @@ class AssetCardCompact extends StatelessWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( - color: (isUp ?? true) ? upColor.withOpacity(0.2) : downColor.withOpacity(0.2), + color: getChangeBackgroundColor(isValueUp), borderRadius: BorderRadius.circular(6), ), child: Text( change!, style: TextStyle( - color: (isUp ?? true) ? upColor : downColor, + color: getChangeColor(isValueUp), fontWeight: FontWeight.w600, ), ), diff --git a/flutter_monisuo/lib/ui/components/coin_card.dart b/flutter_monisuo/lib/ui/components/coin_card.dart index a69ad5e..bc09285 100644 --- a/flutter_monisuo/lib/ui/components/coin_card.dart +++ b/flutter_monisuo/lib/ui/components/coin_card.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; -import '../../core/constants/app_colors.dart'; +import '../../core/theme/app_color_scheme.dart'; /// 币种卡片组件 - 用于显示币种信息 class CoinCard extends StatelessWidget { @@ -38,7 +38,7 @@ class CoinCard extends StatelessWidget { width: 44, height: 44, decoration: BoxDecoration( - color: theme.colorScheme.primary.withOpacity(0.1), + color: theme.colorScheme.primary.withValues(alpha: 0.1), shape: BoxShape.circle, ), child: Center( @@ -75,13 +75,13 @@ class CoinCard extends StatelessWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( - color: AppColors.getChangeBackgroundColor(isUp), + color: getChangeBackgroundColor(isUp), borderRadius: BorderRadius.circular(6), ), child: Text( change, style: TextStyle( - color: AppColors.getChangeColor(isUp), + color: getChangeColor(isUp), fontWeight: FontWeight.w600, ), ), diff --git a/flutter_monisuo/lib/ui/components/trade_button.dart b/flutter_monisuo/lib/ui/components/trade_button.dart index 148568b..70d04d1 100644 --- a/flutter_monisuo/lib/ui/components/trade_button.dart +++ b/flutter_monisuo/lib/ui/components/trade_button.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; +import '../../core/theme/app_color_scheme.dart'; /// 交易按钮组件 - 买入/卖出按钮 class TradeButton extends StatelessWidget { @@ -9,10 +10,6 @@ class TradeButton extends StatelessWidget { final bool isLoading; final bool fullWidth; - // 颜色常量 - static const buyColor = Color(0xFF00C853); - static const sellColor = Color(0xFFFF5252); - const TradeButton({ super.key, this.isBuy = true, @@ -42,7 +39,7 @@ class TradeButton extends StatelessWidget { @override Widget build(BuildContext context) { - final color = isBuy ? buyColor : sellColor; + final color = isBuy ? AppColorScheme.up : AppColorScheme.down; final text = isBuy ? '买入${coinCode != null ? ' $coinCode' : ''}' : '卖出${coinCode != null ? ' $coinCode' : ''}'; final icon = isBuy ? LucideIcons.arrowDownToLine : LucideIcons.arrowUpFromLine; diff --git a/flutter_monisuo/lib/ui/shared/modern_bottom_sheet.dart b/flutter_monisuo/lib/ui/shared/modern_bottom_sheet.dart new file mode 100644 index 0000000..3592f8e --- /dev/null +++ b/flutter_monisuo/lib/ui/shared/modern_bottom_sheet.dart @@ -0,0 +1,309 @@ +import 'package:flutter/material.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; +import '../../core/theme/app_spacing.dart'; + +/// 现代底部抽屉模板 - 基于 modernization-v2.md 规范 +/// +/// 使用方法: +/// ```dart +/// ModernBottomSheet.show( +/// context: context, +/// title: '选择币种', +/// child: YourContentWidget(), +/// ); +/// ``` +class ModernBottomSheet extends StatelessWidget { + final String? title; + final Widget? titleWidget; + final Widget child; + final double? height; + final bool showDragHandle; + final bool showCloseButton; + final VoidCallback? onClose; + + const ModernBottomSheet({ + super.key, + this.title, + this.titleWidget, + required this.child, + this.height, + this.showDragHandle = true, + this.showCloseButton = false, + this.onClose, + }); + + /// 显示底部抽屉 + static Future show({ + required BuildContext context, + String? title, + Widget? titleWidget, + required Widget child, + double? height, + bool showDragHandle = true, + bool showCloseButton = false, + bool isScrollControlled = true, + bool isDismissible = true, + bool enableDrag = true, + }) { + return showModalBottomSheet( + context: context, + isScrollControlled: isScrollControlled, + isDismissible: isDismissible, + enableDrag: enableDrag, + backgroundColor: Colors.transparent, + builder: (context) => ModernBottomSheet( + title: title, + titleWidget: titleWidget, + child: child, + height: height, + showDragHandle: showDragHandle, + showCloseButton: showCloseButton, + ), + ); + } + + /// 显示操作列表 + static Future showActions({ + required BuildContext context, + String? title, + required List actions, + }) { + return show( + context: context, + title: title, + child: _ActionList(actions: actions), + ); + } + + /// 显示确认操作 + static Future confirmAction({ + required BuildContext context, + required String title, + String? description, + String confirmText = '确认', + String cancelText = '取消', + bool isDestructive = false, + }) async { + final result = await show( + context: context, + title: title, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (description != null) ...[ + Text( + description, + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox(height: AppSpacing.lg), + ], + Row( + children: [ + Expanded( + child: ShadButton.outline( + onPressed: () => Navigator.of(context).pop(0), + child: Text(cancelText), + ), + ), + const SizedBox(width: AppSpacing.md), + Expanded( + child: ShadButton( + backgroundColor: isDestructive + ? ShadTheme.of(context).colorScheme.destructive + : null, + onPressed: () => Navigator.of(context).pop(1), + child: Text(confirmText), + ), + ), + ], + ), + ], + ), + ); + return result == 1; + } + + @override + Widget build(BuildContext context) { + final theme = ShadTheme.of(context); + final bottomPadding = MediaQuery.of(context).padding.bottom; + + return Container( + decoration: BoxDecoration( + color: theme.colorScheme.card, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(AppRadius.xxl), + ), + ), + constraints: height != null + ? BoxConstraints(maxHeight: height!) + : null, + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.only( + left: AppSpacing.lg, + right: AppSpacing.lg, + top: AppSpacing.md, + bottom: bottomPadding + AppSpacing.lg, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 拖动指示器 + if (showDragHandle) _buildDragHandle(theme), + // 标题行 + if (title != null || titleWidget != null || showCloseButton) + _buildHeader(context, theme), + // 内容 + child, + ], + ), + ), + ), + ); + } + + Widget _buildDragHandle(ShadThemeData theme) { + return Center( + child: Container( + width: 40, + height: 4, + margin: const EdgeInsets.only(bottom: AppSpacing.md), + decoration: BoxDecoration( + color: theme.colorScheme.muted, + borderRadius: BorderRadius.circular(2), + ), + ), + ); + } + + Widget _buildHeader(BuildContext context, ShadThemeData theme) { + return Padding( + padding: const EdgeInsets.only(bottom: AppSpacing.md), + child: Row( + children: [ + // 标题 + Expanded( + child: titleWidget ?? Text( + title!, + style: theme.textTheme.h3.copyWith( + fontWeight: FontWeight.w600, + ), + ), + ), + // 关闭按钮 + if (showCloseButton) + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + onClose?.call(); + }, + child: Icon( + LucideIcons.x, + size: 20, + color: theme.colorScheme.mutedForeground, + ), + ), + ], + ), + ); + } +} + +/// 操作列表 +class _ActionList extends StatelessWidget { + final List actions; + + const _ActionList({required this.actions}); + + @override + Widget build(BuildContext context) { + final theme = ShadTheme.of(context); + + return Column( + children: actions.asMap().entries.map((entry) { + final index = entry.key; + final action = entry.value; + + return Column( + children: [ + if (index > 0 && actions[index - 1].isDivider) + Divider(color: theme.colorScheme.border, height: 1), + _ActionTile(action: action), + ], + ); + }).toList(), + ); + } +} + +/// 操作项 +class _ActionTile extends StatelessWidget { + final ModernSheetAction action; + + const _ActionTile({required this.action}); + + @override + Widget build(BuildContext context) { + final theme = ShadTheme.of(context); + + return InkWell( + onTap: () { + Navigator.of(context).pop(action.returnValue); + action.onPressed?.call(); + }, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.md, + vertical: AppSpacing.md, + ), + child: Row( + children: [ + if (action.icon != null) ...[ + Icon( + action.icon, + size: 20, + color: action.isDestructive + ? theme.colorScheme.destructive + : theme.colorScheme.foreground, + ), + const SizedBox(width: AppSpacing.md), + ], + Expanded( + child: Text( + action.label, + style: TextStyle( + fontSize: 16, + color: action.isDestructive + ? theme.colorScheme.destructive + : theme.colorScheme.foreground, + ), + ), + ), + ], + ), + ), + ); + } +} + +/// 底部抽屉操作配置 +class ModernSheetAction { + final String label; + final IconData? icon; + final dynamic returnValue; + final bool isDestructive; + final bool isDivider; + final VoidCallback? onPressed; + + const ModernSheetAction({ + required this.label, + this.icon, + this.returnValue, + this.isDestructive = false, + this.isDivider = false, + this.onPressed, + }); +} diff --git a/flutter_monisuo/lib/ui/shared/modern_dialog.dart b/flutter_monisuo/lib/ui/shared/modern_dialog.dart new file mode 100644 index 0000000..fa5b6b9 --- /dev/null +++ b/flutter_monisuo/lib/ui/shared/modern_dialog.dart @@ -0,0 +1,207 @@ +import 'package:flutter/material.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; +import '../../core/theme/app_spacing.dart'; + +/// 现代弹窗模板 - 基于 modernization-v2.md 规范 +/// +/// 使用方法: +/// ```dart +/// ModernDialog.show( +/// context: context, +/// title: '确认操作', +/// description: '确定要执行此操作吗?', +/// actions: [ +/// ModernDialogAction(label: '取消', isDestructive: false), +/// ModernDialogAction(label: '确认', isPrimary: true), +/// ], +/// ); +/// ``` +class ModernDialog extends StatelessWidget { + final String? title; + final Widget? titleWidget; + final String? description; + final Widget? content; + final List? actions; + final VoidCallback? onClose; + + const ModernDialog({ + super.key, + this.title, + this.titleWidget, + this.description, + this.content, + this.actions, + this.onClose, + }); + + /// 显示现代弹窗 + static Future show({ + required BuildContext context, + String? title, + Widget? titleWidget, + String? description, + Widget? content, + List? actions, + bool barrierDismissible = true, + }) { + return showDialog( + context: context, + barrierDismissible: barrierDismissible, + builder: (context) => ModernDialog( + title: title, + titleWidget: titleWidget, + description: description, + content: content, + actions: actions, + ), + ); + } + + /// 显示确认弹窗 + static Future confirm({ + required BuildContext context, + required String title, + String? description, + String confirmText = '确认', + String cancelText = '取消', + bool isDestructive = false, + }) async { + final result = await show( + context: context, + title: title, + description: description, + actions: [ + ModernDialogAction(label: cancelText, returnValue: false), + ModernDialogAction( + label: confirmText, + returnValue: true, + isPrimary: true, + isDestructive: isDestructive, + ), + ], + ); + return result ?? false; + } + + /// 显示信息弹窗 + static Future info({ + required BuildContext context, + required String title, + String? description, + String buttonText = '知道了', + }) { + return show( + context: context, + title: title, + description: description, + actions: [ + ModernDialogAction(label: buttonText, isPrimary: true), + ], + ); + } + + @override + Widget build(BuildContext context) { + final theme = ShadTheme.of(context); + + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadius.xl), + ), + backgroundColor: theme.colorScheme.card, + child: Container( + padding: const EdgeInsets.all(AppSpacing.lg), + constraints: const BoxConstraints(maxWidth: 400), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 标题 + if (titleWidget != null) + titleWidget! + else if (title != null) + Text( + title!, + style: theme.textTheme.h3.copyWith( + fontWeight: FontWeight.w600, + ), + ), + if (title != null || titleWidget != null) + const SizedBox(height: AppSpacing.md), + // 描述 + if (description != null) ...[ + Text( + description!, + style: theme.textTheme.muted, + ), + const SizedBox(height: AppSpacing.md), + ], + // 自定义内容 + if (content != null) ...[ + content!, + const SizedBox(height: AppSpacing.lg), + ], + // 按钮 + if (actions != null && actions!.isNotEmpty) + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: _buildActions(context), + ), + ], + ), + ), + ); + } + + List _buildActions(BuildContext context) { + return actions!.asMap().entries.map((entry) { + final index = entry.key; + final action = entry.value; + + return Padding( + padding: EdgeInsets.only(left: index > 0 ? AppSpacing.sm : 0), + child: _buildActionButton(context, action), + ); + }).toList(); + } + + Widget _buildActionButton(BuildContext context, ModernDialogAction action) { + final theme = ShadTheme.of(context); + + if (action.isPrimary) { + return ShadButton( + backgroundColor: action.isDestructive ? theme.colorScheme.destructive : theme.colorScheme.primary, + onPressed: () { + Navigator.of(context).pop(action.returnValue); + action.onPressed?.call(); + }, + child: Text(action.label), + ); + } + + return ShadButton.outline( + onPressed: () { + Navigator.of(context).pop(action.returnValue); + action.onPressed?.call(); + }, + child: Text(action.label), + ); + } +} + +/// 弹窗按钮配置 +class ModernDialogAction { + final String label; + final dynamic returnValue; + final bool isPrimary; + final bool isDestructive; + final VoidCallback? onPressed; + + const ModernDialogAction({ + required this.label, + this.returnValue, + this.isPrimary = false, + this.isDestructive = false, + this.onPressed, + }); +}