import 'package:flutter/material.dart'; import 'package:interactive_chart/interactive_chart.dart'; import 'package:provider/provider.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import '../../../core/theme/app_spacing.dart'; import '../../../core/theme/app_theme.dart'; import '../../../core/theme/app_theme_extension.dart'; import '../../../providers/chart_provider.dart'; /// K 线图页面 class ChartPage extends StatelessWidget { final String? symbol; const ChartPage({super.key, this.symbol}); @override Widget build(BuildContext context) { final colorScheme = context.colors; return ChangeNotifierProvider( create: (_) => ChartProvider()..setSymbol(symbol ?? 'BTC'), child: Scaffold( backgroundColor: colorScheme.surface, appBar: AppBar( leading: IconButton( icon: const Icon(LucideIcons.arrowLeft, size: 20), onPressed: () => Navigator.of(context).pop(), ), title: _buildTitle(context), backgroundColor: colorScheme.surface, elevation: 0, scrolledUnderElevation: 0, actions: [ IconButton( icon: const Icon(LucideIcons.settings, size: 20), onPressed: () => _showSettingsSheet(context), ), ], ), body: Consumer( builder: (context, provider, _) { if (provider.loading) { return const Center(child: CircularProgressIndicator()); } if (provider.error != null) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, size: 48, color: colorScheme.error), const SizedBox(height: AppSpacing.md), Text('加载失败', style: AppTextStyles.bodyLarge(context)), const SizedBox(height: AppSpacing.sm), TextButton( onPressed: provider.loadCandles, child: const Text('重试'), ), ], ), ); } final candles = provider.candleData; if (candles.length < 3) { return const Center(child: Text('数据不足')); } return Column( children: [ // 时间周期选择 _buildIntervalTabs(context, provider), // K 线图 Expanded( child: InteractiveChart( candles: candles, style: ChartStyle( priceGainColor: context.appColors.up, priceLossColor: context.appColors.down, volumeColor: colorScheme.primary.withValues(alpha: 0.3), priceLabelStyle: TextStyle( fontSize: 11, color: colorScheme.onSurfaceVariant, ), timeLabelStyle: TextStyle( fontSize: 10, color: colorScheme.onSurfaceVariant, ), overlayTextStyle: TextStyle( fontSize: 12, color: colorScheme.onSurface, ), volumeHeightFactor: provider.indicators.showVOL ? 0.2 : 0, ), ), ), ], ); }, ), ), ); } Widget _buildTitle(BuildContext context) { return Consumer( builder: (context, provider, _) { final colorScheme = context.colors; return Row( mainAxisSize: MainAxisSize.min, children: [ Text( provider.symbol, style: AppTextStyles.headlineMedium(context).copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(width: AppSpacing.xs), Text( '/USDT', style: AppTextStyles.bodyMedium(context).copyWith( color: colorScheme.onSurfaceVariant, ), ), ], ); }, ); } Widget _buildIntervalTabs(BuildContext context, ChartProvider provider) { final colorScheme = context.colors; return Container( height: 44, padding: const EdgeInsets.symmetric(horizontal: AppSpacing.sm), decoration: BoxDecoration( border: Border( bottom: BorderSide( color: colorScheme.outlineVariant.withValues(alpha: 0.2), ), ), ), child: Row( children: ChartInterval.values.map((interval) { final isSelected = provider.interval == interval; return Expanded( child: InkWell( onTap: () => provider.setInterval(interval), child: Container( alignment: Alignment.center, decoration: BoxDecoration( border: Border( bottom: BorderSide( color: isSelected ? colorScheme.primary : Colors.transparent, width: 2, ), ), ), child: Text( interval.label, style: AppTextStyles.labelMedium(context).copyWith( color: isSelected ? colorScheme.primary : colorScheme.onSurfaceVariant, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, ), ), ), ), ); }).toList(), ), ); } void _showSettingsSheet(BuildContext context) { showModalBottomSheet( context: context, builder: (ctx) => Consumer( builder: (ctx, provider, _) { return Container( padding: const EdgeInsets.all(AppSpacing.lg), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '技术指标', style: AppTextStyles.headlineMedium(context).copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: AppSpacing.lg), // MA _buildIndicatorSwitch( context: ctx, label: 'MA 移动平均线', value: provider.indicators.showMA, onChanged: (v) => provider.updateIndicators( provider.indicators.copyWith(showMA: v), ), ), // EMA _buildIndicatorSwitch( context: ctx, label: 'EMA 指数移动平均', value: provider.indicators.showEMA, onChanged: (v) => provider.updateIndicators( provider.indicators.copyWith(showEMA: v), ), ), // BOLL _buildIndicatorSwitch( context: ctx, label: 'BOLL 布林带', value: provider.indicators.showBOLL, onChanged: (v) => provider.updateIndicators( provider.indicators.copyWith(showBOLL: v), ), ), // VOL _buildIndicatorSwitch( context: ctx, label: 'VOL 成交量', value: provider.indicators.showVOL, onChanged: (v) => provider.updateIndicators( provider.indicators.copyWith(showVOL: v), ), ), const SizedBox(height: AppSpacing.lg), ], ), ); }, ), ); } Widget _buildIndicatorSwitch({ required BuildContext context, required String label, required bool value, required ValueChanged onChanged, }) { return Padding( padding: const EdgeInsets.symmetric(vertical: AppSpacing.sm), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, style: AppTextStyles.bodyMedium(context)), Switch(value: value, onChanged: onChanged), ], ), ); } }