import 'dart:async'; import 'package:flutter/material.dart'; import '../data/models/kline_candle.dart'; import '../data/services/kline_service.dart'; import '../data/services/kline_websocket_service.dart'; /// K线状态管理 class KlineProvider extends ChangeNotifier { final KlineService _klineService; final KlineWebSocketService _wsService; KlineProvider(this._klineService, this._wsService); List _candles = []; KlineCandle? _currentCandle; String _interval = '1h'; String _coinCode = ''; bool _isLoading = false; bool _isLoadingMore = false; bool _isConnected = false; String? _error; StreamSubscription? _wsSubscription; Timer? _pollingTimer; // Getters List get candles => _candles; KlineCandle? get currentCandle => _currentCandle; String get interval => _interval; String get coinCode => _coinCode; bool get isLoading => _isLoading; bool get isLoadingMore => _isLoadingMore; bool get isConnected => _isConnected; String? get error => _error; /// 加载某个币种的K线数据 Future loadCoin(String coinCode, {String? interval}) async { _coinCode = coinCode; if (interval != null) _interval = interval; _candles = []; _currentCandle = null; _error = null; _isLoading = true; notifyListeners(); try { // 1. 获取历史K线 final response = await _klineService.fetchHistory( coinCode: _coinCode, interval: _interval, limit: 200, ); if (response.success && response.data != null) { _candles = response.data!; } // 2. 获取当前K线 final currentResponse = await _klineService.fetchCurrentCandle( coinCode: _coinCode, interval: _interval, ); if (currentResponse.success && currentResponse.data != null) { _currentCandle = currentResponse.data; } // 3. 连接 WebSocket _connectWebSocket(); _isLoading = false; notifyListeners(); } catch (e) { _error = '加载K线数据失败: $e'; _isLoading = false; notifyListeners(); } } /// 切换周期 Future changeInterval(String newInterval) async { if (newInterval == _interval) return; _interval = newInterval; _candles = []; _currentCandle = null; // 重新加载 await loadCoin(_coinCode, interval: newInterval); } /// 加载更多历史数据(分页) Future loadMore() async { if (_isLoadingMore || _candles.isEmpty) return; _isLoadingMore = true; notifyListeners(); try { final oldestTime = _candles.first.closeTime; final response = await _klineService.fetchHistory( coinCode: _coinCode, interval: _interval, limit: 200, before: oldestTime, ); if (response.success && response.data != null) { _candles = [...response.data!, ..._candles]; } } catch (_) {} _isLoadingMore = false; notifyListeners(); } void _connectWebSocket() { // 取消之前的订阅 _wsSubscription?.cancel(); _wsService.unsubscribe(_coinCode); // 订阅新币种 _wsService.connect(); _wsSubscription = _wsService.subscribe(_coinCode).listen( _onTick, onError: (_) => _startPolling(), onDone: () => _startPolling(), ); _isConnected = _wsService.isConnected; notifyListeners(); } void _onTick(KlineCandle tick) { if (tick.interval != _interval) return; _isConnected = true; if (tick.isClosed) { // 收盘 tick → 添加到历史列表 _candles.add(tick); _currentCandle = null; // 停止轮询(如果之前在轮询) _pollingTimer?.cancel(); _pollingTimer = null; } else { // 进行中的 tick → 更新当前K线 _currentCandle = tick; } notifyListeners(); } /// WebSocket 断连时降级为 HTTP 轮询 void _startPolling() { _isConnected = false; notifyListeners(); _pollingTimer?.cancel(); _pollingTimer = Timer.periodic( const Duration(seconds: 5), (_) => _pollCurrentCandle(), ); } Future _pollCurrentCandle() async { try { final response = await _klineService.fetchCurrentCandle( coinCode: _coinCode, interval: _interval, ); if (response.success && response.data != null) { _currentCandle = response.data; notifyListeners(); } } catch (_) {} } @override void dispose() { _wsSubscription?.cancel(); _wsService.unsubscribe(_coinCode); _pollingTimer?.cancel(); super.dispose(); } }