diff --git a/flutter_monisuo/lib/core/theme/app_theme.dart b/flutter_monisuo/lib/core/theme/app_theme.dart index e824c1a..5ff7471 100644 --- a/flutter_monisuo/lib/core/theme/app_theme.dart +++ b/flutter_monisuo/lib/core/theme/app_theme.dart @@ -152,27 +152,25 @@ class AppTheme { ), ), - // 輸入框 - 底部線條風格 + // 輸入框 - Ghost Border 風格 inputDecorationTheme: InputDecorationTheme( filled: true, fillColor: AppColorScheme.lightSurfaceLow, - border: UnderlineInputBorder( + border: OutlineInputBorder( borderRadius: BorderRadius.circular(AppRadius.md), borderSide: BorderSide( color: AppColorScheme.lightOutlineVariant.withValues(alpha: 0.5), - width: 2, ), ), - enabledBorder: UnderlineInputBorder( + enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(AppRadius.md), borderSide: BorderSide( color: AppColorScheme.lightOutlineVariant.withValues(alpha: 0.5), - width: 2, ), ), - focusedBorder: UnderlineInputBorder( + focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(AppRadius.md), - borderSide: const BorderSide(color: AppColorScheme.lightPrimary, width: 2), + borderSide: const BorderSide(color: AppColorScheme.lightPrimary, width: 1), ), hintStyle: TextStyle(color: AppColorScheme.lightOnSurfaceMuted), contentPadding: const EdgeInsets.symmetric( diff --git a/flutter_monisuo/lib/ui/pages/asset/transfer_page.dart b/flutter_monisuo/lib/ui/pages/asset/transfer_page.dart index d27e7a3..65b3929 100644 --- a/flutter_monisuo/lib/ui/pages/asset/transfer_page.dart +++ b/flutter_monisuo/lib/ui/pages/asset/transfer_page.dart @@ -298,27 +298,30 @@ class _TransferPageState extends State { ), ], ), - // 交换按钮 - 右侧贴分割线 + // 交换按钮 - 右側居中分割線 Positioned( right: 12, top: 20, - child: GestureDetector( - onTap: _toggleDirection, - child: Container( - width: 28, - height: 28, - decoration: BoxDecoration( - color: colorScheme.surface, - shape: BoxShape.circle, - border: Border.all( - color: colorScheme.outlineVariant, - width: 1, + bottom: 20, + child: Center( + child: GestureDetector( + onTap: _toggleDirection, + child: Container( + width: 28, + height: 28, + decoration: BoxDecoration( + color: colorScheme.surface, + shape: BoxShape.circle, + border: Border.all( + color: colorScheme.outlineVariant, + width: 1, + ), + ), + child: Icon( + LucideIcons.repeat2, + size: 13, + color: colorScheme.onSurfaceVariant, ), - ), - child: Icon( - LucideIcons.arrowUpDown, - size: 14, - color: colorScheme.onSurfaceVariant, ), ), ), diff --git a/flutter_monisuo/lib/ui/pages/home/bills_page.dart b/flutter_monisuo/lib/ui/pages/home/bills_page.dart index 4eedd57..c08e1ac 100644 --- a/flutter_monisuo/lib/ui/pages/home/bills_page.dart +++ b/flutter_monisuo/lib/ui/pages/home/bills_page.dart @@ -8,6 +8,7 @@ import '../../../core/theme/app_spacing.dart'; import '../../../data/models/account_models.dart'; import '../../../data/services/bonus_service.dart'; import '../../../providers/asset_provider.dart'; +import '../../components/coin_icon.dart'; /// 賬單頁面 — 代幣盈虧賬單 + 新人福利賬單 + 推廣福利賬單 class BillsPage extends StatefulWidget { @@ -302,17 +303,7 @@ class _BillsPageState extends State with SingleTickerProviderStateMix children: [ Row( children: [ - CircleAvatar( - radius: 16, - backgroundColor: colorScheme.primary.withValues(alpha: 0.1), - child: Text( - h.coinCode.substring(0, 1), - style: AppTextStyles.labelLarge(context).copyWith( - color: colorScheme.primary, - fontWeight: FontWeight.bold, - ), - ), - ), + CoinIcon(symbol: h.coinCode, size: 32), const SizedBox(width: AppSpacing.sm), Text(h.coinCode, style: AppTextStyles.headlineMedium(context).copyWith( fontWeight: FontWeight.bold, diff --git a/flutter_monisuo/lib/ui/pages/market/market_page.dart b/flutter_monisuo/lib/ui/pages/market/market_page.dart index ef23582..ce3f50d 100644 --- a/flutter_monisuo/lib/ui/pages/market/market_page.dart +++ b/flutter_monisuo/lib/ui/pages/market/market_page.dart @@ -1,11 +1,8 @@ -import 'dart:math'; import 'package:flutter/material.dart'; -import 'package:shadcn_ui/shadcn_ui.dart'; +import 'package:lucide_icons_flutter/lucide_icons.dart'; import 'package:provider/provider.dart'; -import '../../../core/theme/app_color_scheme.dart'; import '../../../core/theme/app_spacing.dart'; import '../../../core/theme/app_theme.dart'; -import '../../../core/theme/app_theme_extension.dart'; import '../../../data/models/coin.dart'; import '../../../providers/market_provider.dart'; import '../../components/coin_icon.dart'; @@ -35,9 +32,10 @@ class _MarketPageState extends State @override Widget build(BuildContext context) { super.build(context); + final colorScheme = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: context.colors.surface, + backgroundColor: colorScheme.surface, body: Consumer( builder: (context, provider, _) { if (provider.isLoading) { @@ -50,8 +48,8 @@ class _MarketPageState extends State return RefreshIndicator( onRefresh: () => provider.refresh(), - color: context.colors.primary, - backgroundColor: context.colors.surfaceContainerHighest, + color: colorScheme.primary, + backgroundColor: colorScheme.surfaceContainerHighest, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.only( @@ -63,7 +61,6 @@ class _MarketPageState extends State child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 頁面標題 "行情" Text( '行情', style: AppTextStyles.displaySmall(context).copyWith( @@ -72,13 +69,10 @@ class _MarketPageState extends State ), ), const SizedBox(height: AppSpacing.md), - // 搜索框 - _buildSearchBar(context), + _buildSearchBar(colorScheme), const SizedBox(height: AppSpacing.md), - // 精選區域:BTC + ETH 卡片 _buildFeaturedSection(provider), const SizedBox(height: AppSpacing.md), - // 全部幣種列表(含表頭) _buildCoinList(provider), ], ), @@ -89,8 +83,11 @@ class _MarketPageState extends State ); } - /// 搜索框 - Widget _buildSearchBar(BuildContext context) { + // ============================================ + // 搜索框 + // ============================================ + + Widget _buildSearchBar(ColorScheme colorScheme) { return GestureDetector( onTap: () { // TODO: 彈出搜索界面 @@ -98,22 +95,19 @@ class _MarketPageState extends State child: Container( height: 40, decoration: BoxDecoration( - color: context.colors.surfaceContainerHigh, + color: colorScheme.surfaceContainerHigh, borderRadius: BorderRadius.circular(AppRadius.md), ), padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md), child: Row( children: [ - Icon( - LucideIcons.search, - size: 16, - color: context.colors.onSurfaceVariant, - ), + Icon(LucideIcons.search, + size: 16, color: colorScheme.onSurfaceVariant), const SizedBox(width: AppSpacing.sm), Text( '搜索幣種名稱或代碼', style: AppTextStyles.bodyMedium(context).copyWith( - color: context.appColors.onSurfaceMuted, + color: colorScheme.onSurfaceVariant, ), ), ], @@ -122,7 +116,10 @@ class _MarketPageState extends State ); } - /// 精選區域:BTC + ETH 大卡片 + // ============================================ + // 精選區域 + // ============================================ + Widget _buildFeaturedSection(MarketProvider provider) { final featured = provider.featuredCoins; if (featured.isEmpty) return const SizedBox.shrink(); @@ -145,8 +142,12 @@ class _MarketPageState extends State ); } - /// 幣種列表(含表頭) + // ============================================ + // 幣種列表 + // ============================================ + Widget _buildCoinList(MarketProvider provider) { + final colorScheme = Theme.of(context).colorScheme; final coins = provider.otherCoins; if (coins.isEmpty) { @@ -159,19 +160,17 @@ class _MarketPageState extends State return Container( decoration: BoxDecoration( - color: context.appColors.surfaceCard, + color: colorScheme.surface, borderRadius: BorderRadius.circular(AppRadius.lg), border: Border.all( - color: context.appColors.ghostBorder, + color: colorScheme.outlineVariant.withValues(alpha: 0.5), width: 1, ), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ - // 表頭行 - _buildListHeader(context), - // 列表項 + _buildListHeader(colorScheme), ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), @@ -179,7 +178,7 @@ class _MarketPageState extends State separatorBuilder: (_, __) => Divider( height: 1, thickness: 1, - color: context.colors.outlineVariant.withValues(alpha: 0.5 * 0.15), + color: colorScheme.outlineVariant.withValues(alpha: 0.15), indent: AppSpacing.md, endIndent: AppSpacing.md, ), @@ -190,10 +189,9 @@ class _MarketPageState extends State ); } - /// 列表表頭行:幣種 | 最新價 | 漲跌幅 - Widget _buildListHeader(BuildContext context) { - return const Padding( - padding: EdgeInsets.fromLTRB( + Widget _buildListHeader(ColorScheme colorScheme) { + return Padding( + padding: const EdgeInsets.fromLTRB( AppSpacing.md, AppSpacing.sm + AppSpacing.xs, AppSpacing.md, @@ -207,7 +205,7 @@ class _MarketPageState extends State style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, - color: AppColorScheme.darkOnSurfaceVariant, + color: colorScheme.onSurfaceVariant, ), ), ), @@ -219,11 +217,11 @@ class _MarketPageState extends State style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, - color: AppColorScheme.darkOnSurfaceVariant, + color: colorScheme.onSurfaceVariant, ), ), ), - SizedBox(width: AppSpacing.sm), + const SizedBox(width: AppSpacing.sm), SizedBox( width: 72, child: Text( @@ -232,7 +230,7 @@ class _MarketPageState extends State style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, - color: AppColorScheme.darkOnSurfaceVariant, + color: colorScheme.onSurfaceVariant, ), ), ), @@ -241,23 +239,29 @@ class _MarketPageState extends State ); } - /// 錯誤狀態 + // ============================================ + // 錯誤狀態 + // ============================================ + Widget _buildErrorState(MarketProvider provider) { + final colorScheme = Theme.of(context).colorScheme; + return Center( child: Padding( padding: AppSpacing.pagePadding, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(LucideIcons.circleAlert, size: 48, color: context.colors.error), + Icon(LucideIcons.circleAlert, + size: 48, color: colorScheme.error), const SizedBox(height: AppSpacing.md), Text( provider.error ?? '加載失敗', - style: TextStyle(color: context.colors.error), + style: TextStyle(color: colorScheme.error), textAlign: TextAlign.center, ), const SizedBox(height: AppSpacing.md), - ShadButton( + ElevatedButton( onPressed: () => provider.refresh(), child: const Text('重試'), ), @@ -268,7 +272,10 @@ class _MarketPageState extends State } } -/// 精選卡片:BTC / ETH (148px 高度,漸變背景,含迷你柱狀圖) +// ============================================ +// 精選卡片 — 簡約風格,用貨幣圖標代替柱狀圖 +// ============================================ + class _FeaturedCard extends StatelessWidget { final Coin coin; @@ -276,43 +283,55 @@ class _FeaturedCard extends StatelessWidget { @override Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; final isUp = coin.isUp; - final isBtc = coin.code == 'BTC'; - final gradient = - isBtc ? AppColorScheme.btcCardGradient : AppColorScheme.ethCardGradient; - final barColor = - isBtc ? AppColorScheme.btcBarColor : AppColorScheme.ethBarColor; - final changeBgColor = isUp - ? AppColorScheme.darkTertiary.withValues(alpha: 0.2) - : AppColorScheme.darkError.withValues(alpha: 0.2); - final changeColor = isUp - ? AppColorScheme.darkTertiary - : AppColorScheme.darkError; + final changeColor = + isUp ? colorScheme.tertiary : colorScheme.error; + final changeBgColor = + changeColor.withValues(alpha: 0.12); return Container( - height: 148, + height: 120, padding: const EdgeInsets.all(AppSpacing.md), decoration: BoxDecoration( - gradient: gradient, + color: colorScheme.surface, borderRadius: BorderRadius.circular(AppRadius.lg), + border: Border.all( + color: colorScheme.outlineVariant.withValues(alpha: 0.5), + width: 1, + ), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Row( children: [ - // 第一行:交易對名 + 漲跌幅標籤 - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + // 左側:圖標 + 交易對 + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, children: [ + CoinIcon(symbol: coin.code, size: 32), + const SizedBox(height: 8), Text( - '${coin.code}/USDT', - style: const TextStyle( - color: Colors.white, - fontSize: 13, + '${coin.code}/U', + style: AppTextStyles.bodyLarge(context).copyWith( fontWeight: FontWeight.w600, ), ), + ], + ), + const Spacer(), + // 右側:價格 + 漲跌 + Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + _formatFeaturedPrice(coin), + style: AppTextStyles.numberLarge(context).copyWith( + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 4), Container( padding: const EdgeInsets.symmetric( horizontal: AppSpacing.xs + 2, @@ -333,29 +352,11 @@ class _FeaturedCard extends StatelessWidget { ), ], ), - // 第二行:大號價格 - Text( - '\$${_formatFeaturedPrice(coin)}', - style: const TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.w800, - height: 1.2, - ), - ), - // 第三行:迷你柱狀圖 - Expanded( - child: _MiniBarChart( - barColor: barColor, - seed: coin.code.hashCode, - ), - ), ], ), ); } - /// 精選卡片使用簡短價格格式(帶逗號) String _formatFeaturedPrice(Coin coin) { if (coin.price >= 1000) { return _addCommas(coin.price.toStringAsFixed(2)); @@ -380,47 +381,10 @@ class _FeaturedCard extends StatelessWidget { } } -/// 迷你柱狀圖(模擬價格走勢) -class _MiniBarChart extends StatelessWidget { - final Color barColor; - final int seed; +// ============================================ +// 幣種列表行 +// ============================================ - const _MiniBarChart({required this.barColor, required this.seed}); - - @override - Widget build(BuildContext context) { - // 生成隨機但確定的高度序列 - final heights = _generateHeights(); - - return Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: MainAxisAlignment.end, - children: heights.map((h) { - return Expanded( - child: Padding( - padding: const EdgeInsets.only(left: 2), - child: Container( - height: h, - decoration: BoxDecoration( - color: barColor, - borderRadius: BorderRadius.circular(AppRadius.sm), - ), - ), - ), - ); - }).toList(), - ); - } - - List _generateHeights() { - final random = Random(seed); - const base = 6.0; - const range = 12.0; - return List.generate(6, (_) => base + random.nextDouble() * range); - } -} - -/// 幣種列表行 class _CoinRow extends StatelessWidget { final Coin coin; @@ -428,12 +392,10 @@ class _CoinRow extends StatelessWidget { @override Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; final isUp = coin.isUp; - final changeColor = - isUp ? context.appColors.up : context.appColors.down; - final changeBgColor = isUp - ? context.appColors.upBackground - : context.appColors.downBackground; + final changeColor = isUp ? colorScheme.tertiary : colorScheme.error; + final changeBgColor = changeColor.withValues(alpha: 0.12); return GestureDetector( onTap: () => _navigateToTrade(context), @@ -445,21 +407,19 @@ class _CoinRow extends StatelessWidget { ), child: Row( children: [ - // 頭像:36px 圓形 CoinIcon( symbol: coin.code, size: 36, isCircle: true, ), const SizedBox(width: AppSpacing.sm + AppSpacing.xs), - // 幣種信息:交易對 + 全名 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( - '${coin.code}/USDT', + '${coin.code}/U', style: AppTextStyles.numberMedium(context).copyWith( fontWeight: FontWeight.w600, ), @@ -472,17 +432,15 @@ class _CoinRow extends StatelessWidget { ], ), ), - // 價格(固定寬度90,右對齊) SizedBox( width: 90, child: Text( - '\$${coin.formattedPrice}', + coin.formattedPrice, textAlign: TextAlign.right, style: AppTextStyles.numberMedium(context), ), ), const SizedBox(width: AppSpacing.sm), - // 漲跌幅標籤(固定寬度72) SizedBox( width: 72, child: Container( @@ -515,7 +473,10 @@ class _CoinRow extends StatelessWidget { } } -/// 空狀態 +// ============================================ +// 空狀態 +// ============================================ + class _EmptyState extends StatelessWidget { final IconData icon; final String message; @@ -529,20 +490,22 @@ class _EmptyState extends StatelessWidget { @override Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + return Center( child: Padding( padding: const EdgeInsets.all(AppSpacing.xl), child: Column( children: [ - Icon(icon, size: 48, color: context.colors.onSurfaceVariant), + Icon(icon, size: 48, color: colorScheme.onSurfaceVariant), const SizedBox(height: AppSpacing.sm + AppSpacing.xs), Text( message, - style: TextStyle(color: context.colors.onSurfaceVariant), + style: TextStyle(color: colorScheme.onSurfaceVariant), ), if (onRetry != null) ...[ const SizedBox(height: AppSpacing.md), - ShadButton( + ElevatedButton( onPressed: onRetry, child: const Text('重試'), ),