Compare commits

...

2 Commits

Author SHA1 Message Date
c915ca888e Merge branch 'main' of http://8.155.172.147:3001/sion/monisuo 2026-04-08 02:43:47 +08:00
5d753d3fa4 优化 2026-04-08 02:42:59 +08:00
4 changed files with 128 additions and 173 deletions

View File

@@ -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(

View File

@@ -298,27 +298,30 @@ class _TransferPageState extends State<TransferPage> {
),
],
),
// 交换按钮 - 右侧贴分割线
// 交换按钮 - 右側居中分割
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,
),
),
),

View File

@@ -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<BillsPage> 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,

View File

@@ -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<MarketPage>
@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<MarketProvider>(
builder: (context, provider, _) {
if (provider.isLoading) {
@@ -50,8 +48,8 @@ class _MarketPageState extends State<MarketPage>
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<MarketPage>
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 頁面標題 "行情"
Text(
'行情',
style: AppTextStyles.displaySmall(context).copyWith(
@@ -72,13 +69,10 @@ class _MarketPageState extends State<MarketPage>
),
),
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<MarketPage>
);
}
/// 搜索框
Widget _buildSearchBar(BuildContext context) {
// ============================================
// 搜索框
// ============================================
Widget _buildSearchBar(ColorScheme colorScheme) {
return GestureDetector(
onTap: () {
// TODO: 彈出搜索界面
@@ -98,22 +95,19 @@ class _MarketPageState extends State<MarketPage>
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<MarketPage>
);
}
/// 精選區域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<MarketPage>
);
}
/// 幣種列表(含表頭)
// ============================================
// 幣種列表
// ============================================
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<MarketPage>
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<MarketPage>
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<MarketPage>
);
}
/// 列表表頭行:幣種 | 最新價 | 漲跌幅
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<MarketPage>
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: AppColorScheme.darkOnSurfaceVariant,
color: colorScheme.onSurfaceVariant,
),
),
),
@@ -219,11 +217,11 @@ class _MarketPageState extends State<MarketPage>
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<MarketPage>
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: AppColorScheme.darkOnSurfaceVariant,
color: colorScheme.onSurfaceVariant,
),
),
),
@@ -241,23 +239,29 @@ class _MarketPageState extends State<MarketPage>
);
}
/// 錯誤狀態
// ============================================
// 錯誤狀態
// ============================================
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<MarketPage>
}
}
/// 精選卡片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<double> _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('重試'),
),