111
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -18,6 +19,8 @@ class MarketPage extends StatefulWidget {
|
||||
|
||||
class _MarketPageState extends State<MarketPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
Timer? _refreshTimer;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@@ -26,6 +29,23 @@ class _MarketPageState extends State<MarketPage>
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
context.read<MarketProvider>().loadCoins();
|
||||
_startAutoRefresh();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_refreshTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _startAutoRefresh() {
|
||||
_refreshTimer?.cancel();
|
||||
_refreshTimer = Timer.periodic(const Duration(seconds: 10), (_) {
|
||||
if (!mounted) return;
|
||||
final mainState = context.findAncestorStateOfType<MainPageState>();
|
||||
if (mainState?.isPageVisible(1) != true) return;
|
||||
context.read<MarketProvider>().loadCoins(force: true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,109 +66,67 @@ class _MarketPageState extends State<MarketPage>
|
||||
return _buildErrorState(provider);
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => provider.refresh(),
|
||||
color: colorScheme.primary,
|
||||
backgroundColor: colorScheme.surfaceContainerHighest,
|
||||
child: SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.only(
|
||||
top: AppSpacing.md,
|
||||
left: AppSpacing.md,
|
||||
right: AppSpacing.md,
|
||||
bottom: AppSpacing.xl,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'行情',
|
||||
style: AppTextStyles.displaySmall(context).copyWith(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 48,
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'行情',
|
||||
style: AppTextStyles.headlineLarge(context).copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () => provider.refresh(),
|
||||
color: colorScheme.primary,
|
||||
backgroundColor: colorScheme.surfaceContainerHighest,
|
||||
child: SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.only(
|
||||
top: AppSpacing.sm,
|
||||
left: AppSpacing.md,
|
||||
right: AppSpacing.md,
|
||||
bottom: AppSpacing.xl,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 平台代币区域 — 使用 Selector 精确监听
|
||||
Selector<MarketProvider, List<Coin>>(
|
||||
selector: (_, p) => p.platformCoins,
|
||||
builder: (_, platformCoins, __) {
|
||||
if (platformCoins.isEmpty) return const SizedBox.shrink();
|
||||
return Column(
|
||||
children: platformCoins.map((coin) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: _PlatformTokenCard(coin: coin),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
Selector<MarketProvider, List<Coin>>(
|
||||
selector: (_, p) => p.nonPlatformCoins,
|
||||
builder: (_, coins, __) => _buildCoinList(coins, provider),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
_buildSearchBar(colorScheme),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
_buildFeaturedSection(provider),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
_buildCoinList(provider),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 搜索框
|
||||
// ============================================
|
||||
|
||||
Widget _buildSearchBar(ColorScheme colorScheme) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
// TODO: 彈出搜索界面
|
||||
},
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(LucideIcons.search,
|
||||
size: 16, color: colorScheme.onSurfaceVariant),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
Text(
|
||||
'搜索幣種名稱或代碼',
|
||||
style: AppTextStyles.bodyMedium(context).copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 精選區域
|
||||
// ============================================
|
||||
|
||||
Widget _buildFeaturedSection(MarketProvider provider) {
|
||||
final featured = provider.featuredCoins;
|
||||
if (featured.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
final btc = featured.where((c) => c.code == 'BTC').firstOrNull;
|
||||
final eth = featured.where((c) => c.code == 'ETH').firstOrNull;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
if (btc != null)
|
||||
Expanded(child: _FeaturedCard(coin: btc))
|
||||
else
|
||||
const Expanded(child: SizedBox.shrink()),
|
||||
const SizedBox(width: 10),
|
||||
if (eth != null)
|
||||
Expanded(child: _FeaturedCard(coin: eth))
|
||||
else
|
||||
const Expanded(child: SizedBox.shrink()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 幣種列表
|
||||
// ============================================
|
||||
|
||||
Widget _buildCoinList(MarketProvider provider) {
|
||||
Widget _buildCoinList(List<Coin> coins, MarketProvider provider) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final coins = provider.otherCoins;
|
||||
|
||||
if (coins.isEmpty) {
|
||||
return _EmptyState(
|
||||
@@ -192,79 +170,34 @@ class _MarketPageState extends State<MarketPage>
|
||||
Widget _buildListHeader(ColorScheme colorScheme) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
AppSpacing.md,
|
||||
AppSpacing.sm + AppSpacing.xs,
|
||||
AppSpacing.md,
|
||||
AppSpacing.sm,
|
||||
AppSpacing.md, AppSpacing.sm + AppSpacing.xs, AppSpacing.md, AppSpacing.sm,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'幣種',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 90,
|
||||
child: Text(
|
||||
'最新價',
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
child: Text('幣種', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: colorScheme.onSurfaceVariant)),
|
||||
),
|
||||
SizedBox(width: 90, child: Text('最新價', textAlign: TextAlign.right, style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: colorScheme.onSurfaceVariant))),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
SizedBox(
|
||||
width: 72,
|
||||
child: Text(
|
||||
'漲跌幅',
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 72, child: Text('漲跌幅', textAlign: TextAlign.right, style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: colorScheme.onSurfaceVariant))),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 錯誤狀態
|
||||
// ============================================
|
||||
|
||||
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: colorScheme.error),
|
||||
Icon(LucideIcons.circleAlert, size: 48, color: colorScheme.error),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Text(
|
||||
provider.error ?? '加載失敗',
|
||||
style: TextStyle(color: colorScheme.error),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Text(provider.error ?? '加載失敗', style: TextStyle(color: colorScheme.error), textAlign: TextAlign.center),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
ElevatedButton(
|
||||
onPressed: () => provider.refresh(),
|
||||
child: const Text('重試'),
|
||||
),
|
||||
ElevatedButton(onPressed: () => provider.refresh(), child: const Text('重試')),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -273,94 +206,130 @@ class _MarketPageState extends State<MarketPage>
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 精選卡片 — 簡約風格,用貨幣圖標代替柱狀圖
|
||||
// 平台代币卡片 — 跟随主题色,简洁专业
|
||||
// ============================================
|
||||
|
||||
class _FeaturedCard extends StatelessWidget {
|
||||
class _PlatformTokenCard extends StatelessWidget {
|
||||
final Coin coin;
|
||||
|
||||
const _FeaturedCard({required this.coin});
|
||||
const _PlatformTokenCard({required this.coin});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final isUp = coin.isUp;
|
||||
final changeColor = isUp ? colorScheme.tertiary : colorScheme.error;
|
||||
|
||||
final changeColor =
|
||||
isUp ? colorScheme.tertiary : colorScheme.error;
|
||||
final changeBgColor =
|
||||
changeColor.withValues(alpha: 0.12);
|
||||
|
||||
return Container(
|
||||
height: 120,
|
||||
padding: const EdgeInsets.all(AppSpacing.md),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
border: Border.all(
|
||||
color: colorScheme.outlineVariant.withValues(alpha: 0.5),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// 左側:圖標 + 交易對
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CoinIcon(symbol: coin.code, size: 32),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'${coin.code}/U',
|
||||
style: AppTextStyles.bodyLarge(context).copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
final mainState = context.findAncestorStateOfType<MainPageState>();
|
||||
mainState?.switchToTrade(coin.code);
|
||||
},
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: colorScheme.outlineVariant.withValues(alpha: 0.5),
|
||||
),
|
||||
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,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: changeBgColor,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
coin.formattedChange,
|
||||
style: TextStyle(
|
||||
color: changeColor,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 第一行:图标 + 币种名/交易对 + 涨跌幅
|
||||
Row(
|
||||
children: [
|
||||
CoinIcon(symbol: coin.code, size: 36),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
coin.code,
|
||||
style: TextStyle(
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'/USDT',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
coin.name,
|
||||
style: TextStyle(fontSize: 12, color: colorScheme.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
_formatPrice(coin),
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
coin.formattedChange,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: changeColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// 第二行:左侧统计 + 右侧交易入口
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'24h量 ${_fmtVol(coin.volume24h)}',
|
||||
style: TextStyle(fontSize: 11, color: colorScheme.onSurfaceVariant),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'交易 →',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: colorScheme.primary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatFeaturedPrice(Coin coin) {
|
||||
if (coin.price >= 1000) {
|
||||
return _addCommas(coin.price.toStringAsFixed(2));
|
||||
}
|
||||
String _fmtVol(double? v) {
|
||||
if (v == null) return '--';
|
||||
if (v >= 1000000) return '${(v / 1000000).toStringAsFixed(2)}M';
|
||||
if (v >= 1000) return '${(v / 1000).toStringAsFixed(2)}K';
|
||||
return v.toStringAsFixed(2);
|
||||
}
|
||||
|
||||
String _formatPrice(Coin coin) {
|
||||
if (coin.price >= 1000) return _addCommas(coin.price.toStringAsFixed(2));
|
||||
return coin.price.toStringAsFixed(2);
|
||||
}
|
||||
|
||||
@@ -371,9 +340,7 @@ class _FeaturedCard extends StatelessWidget {
|
||||
final buffer = StringBuffer();
|
||||
int count = 0;
|
||||
for (int i = intPart.length - 1; i >= 0; i--) {
|
||||
if (count > 0 && count % 3 == 0) {
|
||||
buffer.write(',');
|
||||
}
|
||||
if (count > 0 && count % 3 == 0) buffer.write(',');
|
||||
buffer.write(intPart[i]);
|
||||
count++;
|
||||
}
|
||||
@@ -387,7 +354,6 @@ class _FeaturedCard extends StatelessWidget {
|
||||
|
||||
class _CoinRow extends StatelessWidget {
|
||||
final Coin coin;
|
||||
|
||||
const _CoinRow({required this.coin});
|
||||
|
||||
@override
|
||||
@@ -395,82 +361,39 @@ class _CoinRow extends StatelessWidget {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final isUp = coin.isUp;
|
||||
final changeColor = isUp ? colorScheme.tertiary : colorScheme.error;
|
||||
final changeBgColor = changeColor.withValues(alpha: 0.12);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => _navigateToTrade(context),
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.md,
|
||||
vertical: 14,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
CoinIcon(
|
||||
symbol: coin.code,
|
||||
size: 36,
|
||||
isCircle: true,
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md, vertical: 14),
|
||||
child: Row(
|
||||
children: [
|
||||
CoinIcon(symbol: coin.code, size: 36, isCircle: false),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('${coin.code}/USDT', style: AppTextStyles.headlineMedium(context).copyWith(fontWeight: FontWeight.bold)),
|
||||
Text(coin.name, style: AppTextStyles.bodySmall(context)),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: AppSpacing.sm + AppSpacing.xs),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'${coin.code}/U',
|
||||
style: AppTextStyles.numberMedium(context).copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
coin.name,
|
||||
style: AppTextStyles.bodySmall(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text(coin.formattedPrice, style: AppTextStyles.numberMedium(context)),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 90,
|
||||
child: Text(
|
||||
coin.formattedPrice,
|
||||
textAlign: TextAlign.right,
|
||||
style: AppTextStyles.numberMedium(context),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
SizedBox(
|
||||
width: 72,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.xs + 2,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: changeBgColor,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
coin.formattedChange,
|
||||
textAlign: TextAlign.center,
|
||||
style: AppTextStyles.labelSmall(context).copyWith(
|
||||
color: changeColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(coin.formattedChange, style: AppTextStyles.labelMedium(context).copyWith(color: changeColor)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _navigateToTrade(BuildContext context) {
|
||||
final mainState = context.findAncestorStateOfType<MainPageState>();
|
||||
mainState?.switchToTrade(coin.code);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
@@ -482,16 +405,11 @@ class _EmptyState extends StatelessWidget {
|
||||
final String message;
|
||||
final VoidCallback? onRetry;
|
||||
|
||||
const _EmptyState({
|
||||
required this.icon,
|
||||
required this.message,
|
||||
this.onRetry,
|
||||
});
|
||||
const _EmptyState({required this.icon, required this.message, this.onRetry});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(AppSpacing.xl),
|
||||
@@ -499,16 +417,10 @@ class _EmptyState extends StatelessWidget {
|
||||
children: [
|
||||
Icon(icon, size: 48, color: colorScheme.onSurfaceVariant),
|
||||
const SizedBox(height: AppSpacing.sm + AppSpacing.xs),
|
||||
Text(
|
||||
message,
|
||||
style: TextStyle(color: colorScheme.onSurfaceVariant),
|
||||
),
|
||||
Text(message, style: TextStyle(color: colorScheme.onSurfaceVariant)),
|
||||
if (onRetry != null) ...[
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
ElevatedButton(
|
||||
onPressed: onRetry,
|
||||
child: const Text('重試'),
|
||||
),
|
||||
ElevatedButton(onPressed: onRetry, child: const Text('重試')),
|
||||
],
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user