import 'package:flutter/foundation.dart'; import 'package:interactive_chart/interactive_chart.dart'; import '../models/candle.dart'; /// 时间周期 enum ChartInterval { min1('1分', '1m'), min5('5分', '5m'), min15('15分', '15m'), hour1('1小时', '1h'), hour4('4小时', '4h'), day1('1天', '1d'); final String label; final String value; const ChartInterval(this.label, this.value); } /// 技术指标开关 class IndicatorSettings { final bool showMA; final bool showEMA; final bool showBOLL; final bool showVOL; final int maPeriod; final int emaPeriod; final int bollPeriod; const IndicatorSettings({ this.showMA = true, this.showEMA = false, this.showBOLL = false, this.showVOL = true, this.maPeriod = 7, this.emaPeriod = 14, this.bollPeriod = 20, }); IndicatorSettings copyWith({ bool? showMA, bool? showEMA, bool? showBOLL, bool? showVOL, int? maPeriod, int? emaPeriod, int? bollPeriod, }) { return IndicatorSettings( showMA: showMA ?? this.showMA, showEMA: showEMA ?? this.showEMA, showBOLL: showBOLL ?? this.showBOLL, showVOL: showVOL ?? this.showVOL, maPeriod: maPeriod ?? this.maPeriod, emaPeriod: emaPeriod ?? this.emaPeriod, bollPeriod: bollPeriod ?? this.bollPeriod, ); } } /// K 线数据 Provider class ChartProvider extends ChangeNotifier { // 当前币种 String _symbol = 'BTC'; String get symbol => _symbol; // 时间周期 ChartInterval _interval = ChartInterval.hour1; ChartInterval get interval => _interval; // 技术指标 IndicatorSettings _indicators = const IndicatorSettings(); IndicatorSettings get indicators => _indicators; // K 线数据 List _candles = []; List get candles => _candles; List get candleData { final baseData = _candles .map((c) => CandleData( timestamp: c.timestamp, open: c.open, high: c.high, low: c.low, close: c.close, volume: c.volume, )) .toList(); // 如果开启MA,计算均线并添加到趋势线 if (_indicators.showMA && baseData.isNotEmpty) { final ma7 = CandleData.computeMA(baseData, 7); final ma14 = CandleData.computeMA(baseData, 14); final ma30 = CandleData.computeMA(baseData, 30); return List.generate(baseData.length, (i) { return CandleData( timestamp: baseData[i].timestamp, open: baseData[i].open, high: baseData[i].high, low: baseData[i].low, close: baseData[i].close, volume: baseData[i].volume, trends: [ma7[i], ma14[i], ma30[i]], ); }); } return baseData; } // 状态 bool _loading = false; bool get loading => _loading; String? _error; String? get error => _error; /// 切换币种 void setSymbol(String symbol) { if (_symbol != symbol) { _symbol = symbol; loadCandles(); // 切换币种显示loading } } /// 切换时间周期 void setInterval(ChartInterval interval) { if (_interval != interval) { _interval = interval; loadCandles(showLoading: false); // 切换时间周期不显示loading } } /// 更新指标设置 void updateIndicators(IndicatorSettings settings) { _indicators = settings; notifyListeners(); } /// 加载 K 线数据 Future loadCandles({bool showLoading = true}) async { if (showLoading) { _loading = true; _error = null; notifyListeners(); } try { // TODO: 对接真实 API // final response = await _api.getKlineData( // symbol: _symbol, // interval: _interval.value, // ); // 临时使用模拟数据 await Future.delayed(const Duration(milliseconds: 300)); _candles = _generateMockCandles(); if (showLoading) { _loading = false; notifyListeners(); } else { notifyListeners(); } } catch (e) { if (showLoading) { _loading = false; } _error = e.toString(); notifyListeners(); } } /// 生成模拟 K 线数据 List _generateMockCandles() { final now = DateTime.now(); final basePrice = _getBasePrice(_symbol); final candles = []; for (int i = 100; i >= 0; i--) { final time = now.subtract(Duration( minutes: _interval == ChartInterval.min1 ? i : _interval == ChartInterval.min5 ? i * 5 : _interval == ChartInterval.min15 ? i * 15 : _interval == ChartInterval.hour1 ? i * 60 : _interval == ChartInterval.hour4 ? i * 240 : i * 1440, )); final random = (i * 17) % 100 / 100; final change = basePrice * 0.02 * (random - 0.5); final open = basePrice + change; final close = open + basePrice * 0.01 * ((i * 13) % 100 / 100 - 0.5); final high = open > close ? open * 1.005 : close * 1.005; final low = open < close ? open * 0.995 : close * 0.995; final volume = 1000 + (i * 37) % 5000; candles.add(Candle( timestamp: time.millisecondsSinceEpoch, open: open, high: high, low: low, close: close, volume: volume.toDouble(), )); } return candles; } double _getBasePrice(String symbol) { switch (symbol.toUpperCase()) { case 'BTC': return 42000.0; case 'ETH': return 2200.0; case 'BNB': return 300.0; case 'SOL': return 100.0; case 'XRP': return 0.5; case 'DOGE': return 0.08; default: return 100.0; } } }