import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../domain/entities/price_info.dart'; import '../../domain/entities/market_overview.dart'; import '../../domain/entities/trading_account.dart'; import '../../domain/entities/trade_order.dart'; import '../../domain/entities/kline.dart'; import '../../domain/repositories/trading_repository.dart'; import '../../data/models/trade_order_model.dart'; import '../../core/di/injection.dart'; // Repository Provider final tradingRepositoryProvider = Provider((ref) { return getIt(); }); // 买入功能开关 Provider (2分钟缓存) final buyEnabledProvider = FutureProvider((ref) async { final repository = ref.watch(tradingRepositoryProvider); final result = await repository.getBuyEnabled(); ref.keepAlive(); final timer = Timer(const Duration(minutes: 2), () { ref.invalidateSelf(); }); ref.onDispose(() => timer.cancel()); return result.fold( (failure) => false, // 获取失败时默认关闭 (enabled) => enabled, ); }); // K线周期选择 final selectedKlinePeriodProvider = StateProvider((ref) => '1h'); // 周期字符串映射到API参数 String _periodToApiParam(String period) { const periodMap = { '1分': '1m', '5分': '5m', '15分': '15m', '30分': '30m', '1时': '1h', '4时': '4h', '日': '1d', }; return periodMap[period] ?? '1h'; } /// K线数据状态 class KlinesState { final List klines; final bool isLoading; final bool isLoadingMore; final bool hasMoreHistory; final String? error; final String period; const KlinesState({ this.klines = const [], this.isLoading = false, this.isLoadingMore = false, this.hasMoreHistory = true, this.error, this.period = '1h', }); KlinesState copyWith({ List? klines, bool? isLoading, bool? isLoadingMore, bool? hasMoreHistory, String? error, String? period, }) { return KlinesState( klines: klines ?? this.klines, isLoading: isLoading ?? this.isLoading, isLoadingMore: isLoadingMore ?? this.isLoadingMore, hasMoreHistory: hasMoreHistory ?? this.hasMoreHistory, error: error, period: period ?? this.period, ); } } /// K线数据管理器 - 支持累积数据和加载更多历史 class KlinesNotifier extends StateNotifier { final TradingRepository repository; Timer? _refreshTimer; KlinesNotifier({required this.repository}) : super(const KlinesState()); /// 初始加载K线数据 Future loadKlines(String period) async { final apiPeriod = _periodToApiParam(period); // 如果周期变化,重置数据 if (state.period != apiPeriod) { state = KlinesState(isLoading: true, period: apiPeriod); } else { state = state.copyWith(isLoading: true, error: null); } final result = await repository.getKlines(period: apiPeriod, limit: 100); result.fold( (failure) { state = state.copyWith(isLoading: false, error: failure.message); }, (klines) { state = state.copyWith( klines: klines, isLoading: false, hasMoreHistory: true, // 默认还有更多历史,直到加载返回空数据 ); _startAutoRefresh(apiPeriod); }, ); } /// 加载更多历史数据(向左滑动到边界时触发) Future loadMoreHistory() async { if (state.isLoadingMore || !state.hasMoreHistory || state.klines.isEmpty) { return; } state = state.copyWith(isLoadingMore: true); // 获取当前最早K线的时间作为before参数 final oldestKline = state.klines.first; final beforeTime = oldestKline.time; final result = await repository.getKlines( period: state.period, limit: 100, before: beforeTime, ); result.fold( (failure) { state = state.copyWith(isLoadingMore: false, error: failure.message); }, (newKlines) { if (newKlines.isEmpty) { // 没有更多历史数据了 state = state.copyWith(isLoadingMore: false, hasMoreHistory: false); } else { // 将新数据插入到现有数据前面(按时间顺序) // 需要去重,因为可能有重叠 final existingTimes = state.klines.map((k) => k.time.millisecondsSinceEpoch).toSet(); final uniqueNewKlines = newKlines.where( (k) => !existingTimes.contains(k.time.millisecondsSinceEpoch) ).toList(); // 如果去重后没有新数据,说明已经没有更多历史了 if (uniqueNewKlines.isEmpty) { state = state.copyWith(isLoadingMore: false, hasMoreHistory: false); return; } final combinedKlines = [...uniqueNewKlines, ...state.klines]; // 按时间排序 combinedKlines.sort((a, b) => a.time.compareTo(b.time)); // 判断是否还有更多历史:返回数据少于100条说明到底了 state = state.copyWith( klines: combinedKlines, isLoadingMore: false, hasMoreHistory: newKlines.length >= 100, ); } }, ); } /// 刷新最新数据(不清除历史) Future refreshLatest() async { if (state.klines.isEmpty) { return loadKlines(state.period); } final result = await repository.getKlines(period: state.period, limit: 100); result.fold( (failure) { // 刷新失败不影响现有数据 }, (newKlines) { if (newKlines.isEmpty) return; // 合并新数据到现有数据末尾 final existingTimes = state.klines.map((k) => k.time.millisecondsSinceEpoch).toSet(); final uniqueNewKlines = newKlines.where( (k) => !existingTimes.contains(k.time.millisecondsSinceEpoch) ).toList(); if (uniqueNewKlines.isNotEmpty) { final combinedKlines = [...state.klines, ...uniqueNewKlines]; combinedKlines.sort((a, b) => a.time.compareTo(b.time)); state = state.copyWith(klines: combinedKlines); } // 更新最新K线的收盘价(实时更新) if (newKlines.isNotEmpty && state.klines.isNotEmpty) { final latestNew = newKlines.last; final latestExisting = state.klines.last; if (latestNew.time.millisecondsSinceEpoch == latestExisting.time.millisecondsSinceEpoch) { // 更新最新K线 final updatedKlines = [...state.klines]; updatedKlines[updatedKlines.length - 1] = latestNew; state = state.copyWith(klines: updatedKlines); } } }, ); } void _startAutoRefresh(String period) { _refreshTimer?.cancel(); _refreshTimer = Timer.periodic(const Duration(minutes: 1), (_) { refreshLatest(); }); } @override void dispose() { _refreshTimer?.cancel(); super.dispose(); } } /// K线数据 Provider(支持累积和加载更多) final klinesNotifierProvider = StateNotifierProvider((ref) { final repository = ref.watch(tradingRepositoryProvider); return KlinesNotifier(repository: repository); }); // 保留原有的简单 Provider 以保持向后兼容 final klinesProvider = FutureProvider>((ref) async { final repository = ref.watch(tradingRepositoryProvider); final selectedPeriod = ref.watch(selectedKlinePeriodProvider); final apiPeriod = _periodToApiParam(selectedPeriod); final result = await repository.getKlines(period: apiPeriod, limit: 100); ref.keepAlive(); final timer = Timer(const Duration(minutes: 1), () { ref.invalidateSelf(); }); ref.onDispose(() => timer.cancel()); return result.fold( (failure) => throw Exception(failure.message), (klines) => klines, ); }); // 当前价格 Provider (15秒缓存 - 交易页面需要快速更新) final currentPriceProvider = FutureProvider((ref) async { final repository = ref.watch(tradingRepositoryProvider); final result = await repository.getCurrentPrice(); ref.keepAlive(); final timer = Timer(const Duration(seconds: 15), () { ref.invalidateSelf(); }); ref.onDispose(() => timer.cancel()); return result.fold( (failure) => throw Exception(failure.message), (priceInfo) => priceInfo, ); }); // 市场概览 Provider (30秒缓存 - 市场数据相对稳定) final marketOverviewProvider = FutureProvider((ref) async { final repository = ref.watch(tradingRepositoryProvider); final result = await repository.getMarketOverview(); ref.keepAlive(); final timer = Timer(const Duration(seconds: 30), () { ref.invalidateSelf(); }); ref.onDispose(() => timer.cancel()); return result.fold( (failure) => throw Exception(failure.message), (overview) => overview, ); }); // 交易账户 Provider (15秒缓存 - 交易后快速更新资产) final tradingAccountProvider = FutureProvider.family( (ref, accountSequence) async { if (accountSequence.isEmpty) return null; final repository = ref.watch(tradingRepositoryProvider); final result = await repository.getTradingAccount(accountSequence); ref.keepAlive(); final timer = Timer(const Duration(seconds: 15), () { ref.invalidateSelf(); }); ref.onDispose(() => timer.cancel()); return result.fold( (failure) => throw Exception(failure.message), (account) => account, ); }, ); // 订单列表 Provider (10秒缓存 - 交易后快速看到结果) final ordersProvider = FutureProvider((ref) async { final repository = ref.watch(tradingRepositoryProvider); final result = await repository.getOrders(page: 1, pageSize: 10); ref.keepAlive(); final timer = Timer(const Duration(seconds: 10), () { ref.invalidateSelf(); }); ref.onDispose(() => timer.cancel()); return result.fold( (failure) => throw Exception(failure.message), (orders) => orders, ); }); // 成交记录列表 Provider(含手续费明细) final tradesProvider = FutureProvider((ref) async { final repository = ref.watch(tradingRepositoryProvider); final result = await repository.getTrades(page: 1, pageSize: 50); ref.keepAlive(); final timer = Timer(const Duration(seconds: 30), () { ref.invalidateSelf(); }); ref.onDispose(() => timer.cancel()); return result.fold( (failure) => throw Exception(failure.message), (trades) => trades, ); }); // 交易状态 class TradingState { final bool isLoading; final String? error; final Map? lastOrderResult; TradingState({ this.isLoading = false, this.error, this.lastOrderResult, }); TradingState copyWith({ bool? isLoading, String? error, Map? lastOrderResult, }) { return TradingState( isLoading: isLoading ?? this.isLoading, error: error, lastOrderResult: lastOrderResult ?? this.lastOrderResult, ); } } class TradingNotifier extends StateNotifier { final TradingRepository repository; TradingNotifier({required this.repository}) : super(TradingState()); /// 创建买入订单 Future buyShares(String price, String quantity) async { state = state.copyWith(isLoading: true, error: null); final result = await repository.createOrder( type: 'BUY', price: price, quantity: quantity, ); return result.fold( (failure) { state = state.copyWith(isLoading: false, error: failure.message); return false; }, (orderResult) { state = state.copyWith(isLoading: false, lastOrderResult: orderResult); return true; }, ); } /// 创建卖出订单 Future sellShares(String price, String quantity) async { state = state.copyWith(isLoading: true, error: null); final result = await repository.createOrder( type: 'SELL', price: price, quantity: quantity, ); return result.fold( (failure) { state = state.copyWith(isLoading: false, error: failure.message); return false; }, (orderResult) { state = state.copyWith(isLoading: false, lastOrderResult: orderResult); return true; }, ); } /// 取消订单 Future cancelOrder(String orderNo) async { state = state.copyWith(isLoading: true, error: null); final result = await repository.cancelOrder(orderNo); return result.fold( (failure) { state = state.copyWith(isLoading: false, error: failure.message); return false; }, (_) { state = state.copyWith(isLoading: false); return true; }, ); } /// 划入积分股 Future transferIn(String amount) async { state = state.copyWith(isLoading: true, error: null); final result = await repository.transferIn(amount); return result.fold( (failure) { state = state.copyWith(isLoading: false, error: failure.message); return false; }, (_) { state = state.copyWith(isLoading: false); return true; }, ); } /// 划出积分股 Future transferOut(String amount) async { state = state.copyWith(isLoading: true, error: null); final result = await repository.transferOut(amount); return result.fold( (failure) { state = state.copyWith(isLoading: false, error: failure.message); return false; }, (_) { state = state.copyWith(isLoading: false); return true; }, ); } void clearError() { state = state.copyWith(error: null); } } final tradingNotifierProvider = StateNotifierProvider( (ref) => TradingNotifier( repository: ref.watch(tradingRepositoryProvider), ), );