Files
monisuo/flutter_monisuo/lib/ui/pages/main/main_page.dart
sion123 f5ac578892 docs(theme): update documentation and clean up deprecated color scheme definitions
Removed outdated compatibility aliases and deprecated methods from AppColorScheme,
and updated CLAUDE.md to reflect new theme system requirements with centralized
color management and no hard-coded values in UI components.
2026-04-05 23:37:27 +08:00

266 lines
7.3 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import '../../../core/theme/app_color_scheme.dart';
import '../../../core/theme/app_spacing.dart';
import '../../../core/theme/app_theme.dart';
import '../../../providers/asset_provider.dart';
import '../../../providers/market_provider.dart';
import '../home/home_page.dart';
import '../market/market_page.dart';
import '../trade/trade_page.dart';
import '../asset/asset_page.dart';
import '../mine/mine_page.dart';
/// 底部导航项
class _NavItem {
final String label;
final IconData icon;
const _NavItem({required this.label, required this.icon});
}
/// 主页面 - "The Kinetic Vault" 设计风格
class MainPage extends StatefulWidget {
const MainPage({super.key});
@override
State<MainPage> createState() => MainPageState();
}
class MainPageState extends State<MainPage> {
int _currentIndex = 0;
final Set<int> _loadedPages = {0};
String? _tradeCoinCode; // 交易页面选中的币种代码
late final List<Widget> _pages;
// 防抖:记录上次刷新时间,同一 Tab 500ms 内不重复刷新
final Map<int, DateTime> _lastRefreshTime = {};
@override
void initState() {
super.initState();
_pages = [
const HomePage(),
const MarketPage(),
TradePage(initialCoinCode: _tradeCoinCode),
const AssetPage(),
const MinePage(),
];
}
void _onTabChanged(int index) {
final wasLoaded = _loadedPages.contains(index);
setState(() {
_currentIndex = index;
_loadedPages.add(index);
});
// 切换到已加载的 Tab 时刷新数据
if (wasLoaded) {
_refreshTab(index);
}
}
/// 刷新对应 Tab 的数据(带防抖)
void _refreshTab(int index) {
final now = DateTime.now();
final last = _lastRefreshTime[index];
if (last != null && now.difference(last).inMilliseconds < 500) return;
_lastRefreshTime[index] = now;
switch (index) {
case 0: // 首页 - 刷新资产概览
context.read<AssetProvider>().loadOverview(force: true);
break;
case 1: // 行情 - 刷新币种列表
context.read<MarketProvider>().loadCoins(force: true);
break;
case 3: // 资产 - 刷新全部资产
context.read<AssetProvider>().refreshAll(force: true);
break;
}
}
/// 切换到交易页面并选中指定币种
void switchToTrade(String coinCode) {
setState(() {
_tradeCoinCode = coinCode;
_currentIndex = 2; // 交易页面索引
_loadedPages.add(2);
// 重新构建交易页面
_pages[2] = TradePage(initialCoinCode: _tradeCoinCode);
});
}
/// 切换到指定 tab
void switchToTab(int index) {
setState(() {
_currentIndex = index;
_loadedPages.add(index);
});
}
static const _navItems = [
_NavItem(label: '首页', icon: LucideIcons.house),
_NavItem(label: '行情', icon: LucideIcons.trendingUp),
_NavItem(label: '交易', icon: LucideIcons.arrowLeftRight),
_NavItem(label: '资产', icon: LucideIcons.wallet),
_NavItem(label: '我的', icon: LucideIcons.user),
];
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: colorScheme.background,
body: LazyIndexedStack(
index: _currentIndex,
loadedIndexes: _loadedPages,
children: _pages,
),
bottomNavigationBar: _BottomNavBar(
items: _navItems,
currentIndex: _currentIndex,
onTap: _onTabChanged,
),
);
}
}
/// 底部导航栏 - 专业信任主题
class _BottomNavBar extends StatelessWidget {
final List<_NavItem> items;
final int currentIndex;
final ValueChanged<int> onTap;
const _BottomNavBar({
required this.items,
required this.currentIndex,
required this.onTap,
});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
decoration: BoxDecoration(
color: colorScheme.surfaceContainerLow,
borderRadius: BorderRadius.vertical(top: Radius.circular(AppRadius.xxl + AppSpacing.sm)),
border: Border(
top: BorderSide(
color: colorScheme.outlineVariant.withValues(alpha: 0.15),
),
),
),
child: SafeArea(
child: Padding(
padding: EdgeInsets.fromLTRB(AppSpacing.md, AppSpacing.sm, AppSpacing.md, AppSpacing.lg),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: items.asMap().entries.map((entry) {
return _NavItemWidget(
item: entry.value,
isSelected: entry.key == currentIndex,
onTap: () => onTap(entry.key),
);
}).toList(),
),
),
),
);
}
}
/// 导航项组件
class _NavItemWidget extends StatelessWidget {
final _NavItem item;
final bool isSelected;
final VoidCallback onTap;
const _NavItemWidget({
required this.item,
required this.isSelected,
required this.onTap,
});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final color = isSelected ? colorScheme.primary : colorScheme.onSurfaceVariant;
return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
padding: EdgeInsets.symmetric(horizontal: AppSpacing.md, vertical: AppSpacing.sm),
decoration: isSelected
? BoxDecoration(
color: colorScheme.primary.withValues(alpha: 0.1),
borderRadius: AppRadius.radiusMd,
)
: null,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
item.icon,
color: color,
size: 24,
),
SizedBox(height: AppSpacing.xs),
Text(
item.label,
style: AppTextStyles.bodyMedium(context).copyWith(
color: color,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
),
),
],
),
),
);
}
}
/// 懒加载 IndexedStack - 只渲染已访问过的页面
class LazyIndexedStack extends StatefulWidget {
final int index;
final Set<int> loadedIndexes;
final List<Widget> children;
const LazyIndexedStack({
super.key,
required this.index,
required this.loadedIndexes,
required this.children,
});
@override
State<LazyIndexedStack> createState() => _LazyIndexedStackState();
}
class _LazyIndexedStackState extends State<LazyIndexedStack> {
@override
Widget build(BuildContext context) {
return Stack(
children: widget.children.asMap().entries.map((entry) {
final isVisible = entry.key == widget.index;
final isLoaded = widget.loadedIndexes.contains(entry.key);
return Positioned.fill(
child: Offstage(
offstage: !isVisible,
child: TickerMode(
enabled: isVisible,
child: isLoaded ? entry.value : const SizedBox.shrink(),
),
),
);
}).toList(),
);
}
}