import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../data/models/copy_trading/copy_account.dart'; import '../data/models/copy_trading/copy_position.dart'; import '../data/models/copy_trading/trader.dart'; import '../data/repositories/copy_trading_repository.dart'; import 'auth_provider.dart'; // ── UI State ───────────────────────────────────────────── class MyCopyTradingState { final CopyAccount? account; final List currentPositions; final List historyPositions; final List myTraders; final bool isLoading; final int tabIndex; // 0=当前跟单 1=我的交易员 2=历史跟单 // ── 分页状态(每个 tab 独立) ───────────────────────── final int currentPage; final bool currentHasMore; final bool currentLoadingMore; final int tradersPage; final bool tradersHasMore; final bool tradersLoadingMore; final int historyPage; final bool historyHasMore; final bool historyLoadingMore; const MyCopyTradingState({ this.account, this.currentPositions = const [], this.historyPositions = const [], this.myTraders = const [], this.isLoading = false, this.tabIndex = 0, this.currentPage = 1, this.currentHasMore = true, this.currentLoadingMore = false, this.tradersPage = 1, this.tradersHasMore = true, this.tradersLoadingMore = false, this.historyPage = 1, this.historyHasMore = true, this.historyLoadingMore = false, }); MyCopyTradingState copyWith({ CopyAccount? account, List? currentPositions, List? historyPositions, List? myTraders, bool? isLoading, int? tabIndex, int? currentPage, bool? currentHasMore, bool? currentLoadingMore, int? tradersPage, bool? tradersHasMore, bool? tradersLoadingMore, int? historyPage, bool? historyHasMore, bool? historyLoadingMore, }) => MyCopyTradingState( account: account ?? this.account, currentPositions: currentPositions ?? this.currentPositions, historyPositions: historyPositions ?? this.historyPositions, myTraders: myTraders ?? this.myTraders, isLoading: isLoading ?? this.isLoading, tabIndex: tabIndex ?? this.tabIndex, currentPage: currentPage ?? this.currentPage, currentHasMore: currentHasMore ?? this.currentHasMore, currentLoadingMore: currentLoadingMore ?? this.currentLoadingMore, tradersPage: tradersPage ?? this.tradersPage, tradersHasMore: tradersHasMore ?? this.tradersHasMore, tradersLoadingMore: tradersLoadingMore ?? this.tradersLoadingMore, historyPage: historyPage ?? this.historyPage, historyHasMore: historyHasMore ?? this.historyHasMore, historyLoadingMore: historyLoadingMore ?? this.historyLoadingMore, ); } // ── Notifier ───────────────────────────────────────────── class MyCopyTradingNotifier extends Notifier { static const _pageSize = 10; @override MyCopyTradingState build() { final isLoggedIn = ref.watch(isLoggedInProvider); if (isLoggedIn) Future.microtask(_load); return const MyCopyTradingState(isLoading: true); } CopyTradingRepository get _repo => ref.read(copyTradingRepositoryProvider); Future _load({bool silent = false}) async { if (!silent) state = state.copyWith(isLoading: true); try { // 并行加载全部数据(第1页,每页10条) final results = await Future.wait([ _repo.getFollowWallet(), _repo.getCurrentCopyPositions(page: 1, pageSize: _pageSize), _repo.getFollowingTraders(page: 1, pageSize: _pageSize), _repo.getHistoryCopyPositions(page: 1, pageSize: _pageSize), ]); final walletMap = results[0] as Map?; final currentMaps = results[1] as List>; final traderMaps = results[2] as List>; final historyMaps = results[3] as List>; // 未实现盈亏 = 当前所有持仓的 profit 之和 // (follow-wallet/get 的 currentRevenue 字段服务端返回 0,需客户端汇总) final totalUnrealizedPnl = currentMaps.fold( 0.0, (sum, m) => sum + (double.tryParse((m['profit'] ?? '0').toString()) ?? 0.0), ); CopyAccount? account; if (walletMap != null) { account = CopyAccount.fromApi(walletMap).copyWith(unrealizedPnl: totalUnrealizedPnl); } state = state.copyWith( isLoading: false, account: account, currentPositions: currentMaps.map(CopyPosition.fromApi).toList(), myTraders: traderMaps.map((e) => Trader.fromApi(e)).toList(), historyPositions: historyMaps.map(CopyPosition.fromApi).toList(), currentPage: 1, currentHasMore: currentMaps.length >= _pageSize, currentLoadingMore: false, tradersPage: 1, tradersHasMore: traderMaps.length >= _pageSize, tradersLoadingMore: false, historyPage: 1, historyHasMore: historyMaps.length >= _pageSize, historyLoadingMore: false, ); } catch (e) { state = state.copyWith(isLoading: false); } } void setTab(int index) { state = state.copyWith(tabIndex: index); Future.microtask(silentRefresh); } /// 首次加载(显示全屏 spinner) Future refresh() => _load(); /// 静默刷新,不显示全屏 spinner(用于下拉刷新、tab 切换) Future silentRefresh() => _load(silent: true); Future loadMoreCurrent() async { if (!state.currentHasMore || state.currentLoadingMore || state.isLoading) return; final nextPage = state.currentPage + 1; state = state.copyWith(currentLoadingMore: true); try { final maps = await _repo.getCurrentCopyPositions(page: nextPage, pageSize: _pageSize); state = state.copyWith( currentLoadingMore: false, currentPositions: [...state.currentPositions, ...maps.map(CopyPosition.fromApi)], currentPage: nextPage, currentHasMore: maps.length >= _pageSize, ); } catch (_) { state = state.copyWith(currentLoadingMore: false); } } Future loadMoreTraders() async { if (!state.tradersHasMore || state.tradersLoadingMore || state.isLoading) return; final nextPage = state.tradersPage + 1; state = state.copyWith(tradersLoadingMore: true); try { final maps = await _repo.getFollowingTraders(page: nextPage, pageSize: _pageSize); state = state.copyWith( tradersLoadingMore: false, myTraders: [...state.myTraders, ...maps.map((e) => Trader.fromApi(e))], tradersPage: nextPage, tradersHasMore: maps.length >= _pageSize, ); } catch (_) { state = state.copyWith(tradersLoadingMore: false); } } Future loadMoreHistory() async { if (!state.historyHasMore || state.historyLoadingMore || state.isLoading) return; final nextPage = state.historyPage + 1; state = state.copyWith(historyLoadingMore: true); try { final maps = await _repo.getHistoryCopyPositions(page: nextPage, pageSize: _pageSize); state = state.copyWith( historyLoadingMore: false, historyPositions: [...state.historyPositions, ...maps.map(CopyPosition.fromApi)], historyPage: nextPage, historyHasMore: maps.length >= _pageSize, ); } catch (_) { state = state.copyWith(historyLoadingMore: false); } } Future unfollowTrader(String traderId) async { await _repo.unfollowTrader(traderId); await _load(); } Future closePosition(String positionId) async { // 乐观更新:立即从列表移除,让用户感知即时响应 final original = state.currentPositions; state = state.copyWith( currentPositions: original.where((p) => p.id != positionId).toList(), ); try { await _repo.closeCopyPosition(positionId); // 静默刷新当前持仓列表(不影响其他 tab 数据、不显示全屏 loading) await _refreshCurrentPositions(); } catch (e) { // API 失败则还原列表 state = state.copyWith(currentPositions: original); rethrow; } } Future _refreshCurrentPositions() async { try { final maps = await _repo.getCurrentCopyPositions(page: 1, pageSize: _pageSize); final totalUnrealizedPnl = maps.fold( 0.0, (sum, m) => sum + (double.tryParse((m['profit'] ?? '0').toString()) ?? 0.0), ); final account = state.account?.copyWith(unrealizedPnl: totalUnrealizedPnl); state = state.copyWith( currentPositions: maps.map(CopyPosition.fromApi).toList(), account: account ?? state.account, currentPage: 1, currentHasMore: maps.length >= _pageSize, currentLoadingMore: false, ); } catch (_) {} } } final myCopyTradingProvider = NotifierProvider( MyCopyTradingNotifier.new, );