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