| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254 |
- 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<CopyPosition> currentPositions;
- final List<CopyPosition> historyPositions;
- final List<Trader> 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<CopyPosition>? currentPositions,
- List<CopyPosition>? historyPositions,
- List<Trader>? 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<MyCopyTradingState> {
- 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<void> _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<String, dynamic>?;
- final currentMaps = results[1] as List<Map<String, dynamic>>;
- final traderMaps = results[2] as List<Map<String, dynamic>>;
- final historyMaps = results[3] as List<Map<String, dynamic>>;
- // 未实现盈亏 = 当前所有持仓的 profit 之和
- // (follow-wallet/get 的 currentRevenue 字段服务端返回 0,需客户端汇总)
- final totalUnrealizedPnl = currentMaps.fold<double>(
- 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<void> refresh() => _load();
- /// 静默刷新,不显示全屏 spinner(用于下拉刷新、tab 切换)
- Future<void> silentRefresh() => _load(silent: true);
- Future<void> 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<void> 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<void> 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<void> unfollowTrader(String traderId) async {
- await _repo.unfollowTrader(traderId);
- await _load();
- }
- Future<void> 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<void> _refreshCurrentPositions() async {
- try {
- final maps = await _repo.getCurrentCopyPositions(page: 1, pageSize: _pageSize);
- final totalUnrealizedPnl = maps.fold<double>(
- 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, MyCopyTradingState>(
- MyCopyTradingNotifier.new,
- );
|