deposit_provider.dart 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. import 'package:flutter_riverpod/flutter_riverpod.dart';
  2. import '../core/network/dio_client.dart';
  3. import '../data/models/asset/recharge_order.dart';
  4. import '../data/models/asset/recharge_record.dart';
  5. import '../data/services/asset_service.dart';
  6. import '../data/services/recharge_service.dart';
  7. // ── On-chain deposit (order flow + WalletConnect) ───────────────────
  8. class DepositState {
  9. final List<RechargeParentCoin> parentCoins;
  10. final List<RechargeNetworkItem> networkOptions;
  11. final List<RechargeConfigOption> subCoinOptions;
  12. final int selectedParentIndex;
  13. final int selectedNetworkId;
  14. final int selectedSubCoinIndex;
  15. final RechargeOrderDetail? currentOrder;
  16. final bool loadingParents;
  17. final bool childrenLoading;
  18. final bool subCoinsLoading;
  19. final bool orderSubmitting;
  20. final bool hashSubmitting;
  21. final bool walletPayBusy;
  22. final String? errorMessage;
  23. const DepositState({
  24. this.parentCoins = const [],
  25. this.networkOptions = const [],
  26. this.subCoinOptions = const [],
  27. this.selectedParentIndex = 0,
  28. this.selectedNetworkId = 0,
  29. this.selectedSubCoinIndex = 0,
  30. this.currentOrder,
  31. this.loadingParents = false,
  32. this.childrenLoading = false,
  33. this.subCoinsLoading = false,
  34. this.orderSubmitting = false,
  35. this.hashSubmitting = false,
  36. this.walletPayBusy = false,
  37. this.errorMessage,
  38. });
  39. RechargeParentCoin? get selectedParent =>
  40. parentCoins.isNotEmpty && selectedParentIndex < parentCoins.length
  41. ? parentCoins[selectedParentIndex]
  42. : null;
  43. RechargeConfigOption? get selectedConfig =>
  44. subCoinOptions.isNotEmpty && selectedSubCoinIndex < subCoinOptions.length
  45. ? subCoinOptions[selectedSubCoinIndex]
  46. : null;
  47. RechargeFlatNetworkOption? get selectedNetwork {
  48. final cfg = selectedConfig;
  49. return cfg != null ? RechargeFlatNetworkOption.fromConfig(cfg) : null;
  50. }
  51. DepositState copyWith({
  52. List<RechargeParentCoin>? parentCoins,
  53. List<RechargeNetworkItem>? networkOptions,
  54. List<RechargeConfigOption>? subCoinOptions,
  55. int? selectedParentIndex,
  56. int? selectedNetworkId,
  57. int? selectedSubCoinIndex,
  58. RechargeOrderDetail? currentOrder,
  59. bool? loadingParents,
  60. bool? childrenLoading,
  61. bool? subCoinsLoading,
  62. bool? orderSubmitting,
  63. bool? hashSubmitting,
  64. bool? walletPayBusy,
  65. String? errorMessage,
  66. bool clearError = false,
  67. bool clearOrder = false,
  68. }) {
  69. return DepositState(
  70. parentCoins: parentCoins ?? this.parentCoins,
  71. networkOptions: networkOptions ?? this.networkOptions,
  72. subCoinOptions: subCoinOptions ?? this.subCoinOptions,
  73. selectedParentIndex: selectedParentIndex ?? this.selectedParentIndex,
  74. selectedNetworkId: selectedNetworkId ?? this.selectedNetworkId,
  75. selectedSubCoinIndex: selectedSubCoinIndex ?? this.selectedSubCoinIndex,
  76. currentOrder: clearOrder ? null : (currentOrder ?? this.currentOrder),
  77. loadingParents: loadingParents ?? this.loadingParents,
  78. childrenLoading: childrenLoading ?? this.childrenLoading,
  79. subCoinsLoading: subCoinsLoading ?? this.subCoinsLoading,
  80. orderSubmitting: orderSubmitting ?? this.orderSubmitting,
  81. hashSubmitting: hashSubmitting ?? this.hashSubmitting,
  82. walletPayBusy: walletPayBusy ?? this.walletPayBusy,
  83. errorMessage: clearError ? null : (errorMessage ?? this.errorMessage),
  84. );
  85. }
  86. }
  87. class DepositNotifier extends Notifier<DepositState> {
  88. @override
  89. DepositState build() {
  90. Future.microtask(loadParentCoins);
  91. return const DepositState(loadingParents: true);
  92. }
  93. Future<void> loadParentCoins() async {
  94. state = state.copyWith(loadingParents: true, clearError: true);
  95. try {
  96. final dio = ref.read(dioClientProvider);
  97. final list = await RechargeService(dio).getRechargeParents();
  98. var idx = state.selectedParentIndex;
  99. if (idx >= list.length) {
  100. idx = 0;
  101. }
  102. state = state.copyWith(
  103. parentCoins: list,
  104. selectedParentIndex: idx,
  105. loadingParents: false,
  106. clearError: true,
  107. );
  108. if (list.isNotEmpty) {
  109. await loadChildrenForParent(list[idx].name);
  110. } else {
  111. state = state.copyWith(
  112. networkOptions: [],
  113. subCoinOptions: [],
  114. selectedNetworkId: 0,
  115. selectedSubCoinIndex: 0,
  116. );
  117. }
  118. } catch (e) {
  119. state = state.copyWith(
  120. loadingParents: false,
  121. errorMessage: e.toString(),
  122. parentCoins: [],
  123. networkOptions: [],
  124. subCoinOptions: [],
  125. );
  126. }
  127. }
  128. Future<void> loadChildrenForParent(String coinName) async {
  129. state = state.copyWith(
  130. childrenLoading: true,
  131. networkOptions: [],
  132. subCoinOptions: [],
  133. selectedNetworkId: 0,
  134. selectedSubCoinIndex: 0,
  135. clearError: true,
  136. );
  137. try {
  138. final dio = ref.read(dioClientProvider);
  139. final service = RechargeService(dio);
  140. final nets = await service.getRechargeNetworks(coinName: coinName);
  141. var networkId = state.selectedNetworkId;
  142. if (nets.isEmpty) {
  143. networkId = 0;
  144. } else if (!nets.any((n) => n.id == networkId)) {
  145. networkId = nets.first.id;
  146. }
  147. state = state.copyWith(
  148. networkOptions: nets,
  149. selectedNetworkId: networkId,
  150. selectedSubCoinIndex: 0,
  151. );
  152. if (networkId > 0) {
  153. await _loadSubCoins(coinName, networkId);
  154. } else {
  155. state = state.copyWith(
  156. subCoinOptions: [],
  157. childrenLoading: false,
  158. clearError: true,
  159. );
  160. }
  161. } catch (e) {
  162. state = state.copyWith(
  163. childrenLoading: false,
  164. errorMessage: e.toString(),
  165. networkOptions: [],
  166. subCoinOptions: [],
  167. );
  168. }
  169. }
  170. Future<void> _loadSubCoins(String coinName, int networkId) async {
  171. state = state.copyWith(
  172. subCoinsLoading: true,
  173. subCoinOptions: [],
  174. selectedSubCoinIndex: 0,
  175. clearError: true,
  176. );
  177. try {
  178. final dio = ref.read(dioClientProvider);
  179. final configs = await RechargeService(dio).getRechargeConfigs(
  180. coinName: coinName,
  181. networkId: networkId,
  182. );
  183. var subIdx = state.selectedSubCoinIndex;
  184. if (subIdx >= configs.length) {
  185. subIdx = 0;
  186. }
  187. state = state.copyWith(
  188. subCoinOptions: configs,
  189. selectedSubCoinIndex: subIdx,
  190. subCoinsLoading: false,
  191. childrenLoading: false,
  192. clearError: true,
  193. );
  194. } catch (e) {
  195. state = state.copyWith(
  196. subCoinsLoading: false,
  197. childrenLoading: false,
  198. errorMessage: e.toString(),
  199. subCoinOptions: [],
  200. );
  201. }
  202. }
  203. Future<void> selectParent(int index) async {
  204. if (index < 0 || index >= state.parentCoins.length) {
  205. return;
  206. }
  207. state = state.copyWith(
  208. selectedParentIndex: index,
  209. selectedSubCoinIndex: 0,
  210. );
  211. final p = state.parentCoins[index];
  212. await loadChildrenForParent(p.name);
  213. }
  214. Future<void> selectNetwork(int networkId) async {
  215. if (networkId <= 0 ||
  216. !state.networkOptions.any((n) => n.id == networkId)) {
  217. return;
  218. }
  219. state = state.copyWith(
  220. selectedNetworkId: networkId,
  221. selectedSubCoinIndex: 0,
  222. );
  223. final parent = state.selectedParent;
  224. if (parent != null) {
  225. await _loadSubCoins(parent.name, networkId);
  226. }
  227. }
  228. void selectSubCoin(int index) {
  229. if (index >= 0 && index < state.subCoinOptions.length) {
  230. state = state.copyWith(selectedSubCoinIndex: index);
  231. }
  232. }
  233. Future<void> createRechargeOrder(String amount) async {
  234. final opt = state.selectedConfig;
  235. if (opt == null) {
  236. state = state.copyWith(errorMessage: '请先选择充值网络与子币种');
  237. return;
  238. }
  239. state = state.copyWith(orderSubmitting: true, clearError: true);
  240. try {
  241. final dio = ref.read(dioClientProvider);
  242. final order = await RechargeService(dio).createRechargeOrder(
  243. coinName: opt.coinName,
  244. networkConfigId: opt.networkConfigId,
  245. amount: amount,
  246. );
  247. state = state.copyWith(
  248. currentOrder: order,
  249. orderSubmitting: false,
  250. clearError: true,
  251. );
  252. } catch (e) {
  253. state = state.copyWith(
  254. orderSubmitting: false,
  255. errorMessage: e.toString(),
  256. );
  257. }
  258. }
  259. Future<void> submitTxHash(String txHash) async {
  260. final o = state.currentOrder;
  261. if (o == null || o.orderNo.isEmpty) {
  262. return;
  263. }
  264. state = state.copyWith(hashSubmitting: true, clearError: true);
  265. try {
  266. final dio = ref.read(dioClientProvider);
  267. final updated = await RechargeService(dio).submitTxHash(
  268. orderNo: o.orderNo,
  269. txHash: txHash,
  270. );
  271. state = state.copyWith(
  272. currentOrder: updated,
  273. hashSubmitting: false,
  274. clearError: true,
  275. );
  276. } catch (e) {
  277. state = state.copyWith(
  278. hashSubmitting: false,
  279. errorMessage: e.toString(),
  280. );
  281. }
  282. }
  283. void clearCurrentOrder() {
  284. state = state.copyWith(clearOrder: true);
  285. }
  286. void setWalletPayBusy(bool v) {
  287. state = state.copyWith(walletPayBusy: v);
  288. }
  289. void clearError() {
  290. state = state.copyWith(clearError: true);
  291. }
  292. Future<void> refresh() => loadParentCoins();
  293. }
  294. final depositProvider =
  295. NotifierProvider<DepositNotifier, DepositState>(DepositNotifier.new);
  296. // ── Recharge History (legacy list API) ──────────────────────────────
  297. class RechargeHistoryState {
  298. final List<RechargeRecord> records;
  299. final bool isLoading;
  300. final bool hasMore;
  301. final int currentPage;
  302. final String? errorMessage;
  303. const RechargeHistoryState({
  304. this.records = const [],
  305. this.isLoading = false,
  306. this.hasMore = true,
  307. this.currentPage = 1,
  308. this.errorMessage,
  309. });
  310. RechargeHistoryState copyWith({
  311. List<RechargeRecord>? records,
  312. bool? isLoading,
  313. bool? hasMore,
  314. int? currentPage,
  315. String? errorMessage,
  316. }) =>
  317. RechargeHistoryState(
  318. records: records ?? this.records,
  319. isLoading: isLoading ?? this.isLoading,
  320. hasMore: hasMore ?? this.hasMore,
  321. currentPage: currentPage ?? this.currentPage,
  322. errorMessage: errorMessage,
  323. );
  324. }
  325. class RechargeHistoryNotifier extends Notifier<RechargeHistoryState> {
  326. static const _pageSize = 10;
  327. @override
  328. RechargeHistoryState build() {
  329. Future.microtask(_loadFirst);
  330. return const RechargeHistoryState(isLoading: true);
  331. }
  332. Future<void> _loadFirst() async {
  333. state = state.copyWith(isLoading: true, errorMessage: null);
  334. try {
  335. final dio = ref.read(dioClientProvider);
  336. final records =
  337. await AssetService(dio).getRechargeList(pageNo: 1, pageSize: _pageSize);
  338. state = state.copyWith(
  339. records: records,
  340. isLoading: false,
  341. currentPage: 1,
  342. hasMore: records.length >= _pageSize,
  343. );
  344. } catch (e) {
  345. state = state.copyWith(isLoading: false, errorMessage: e.toString());
  346. }
  347. }
  348. Future<void> refresh() => _loadFirst();
  349. Future<void> loadMore() async {
  350. if (state.isLoading || !state.hasMore) {
  351. return;
  352. }
  353. state = state.copyWith(isLoading: true);
  354. try {
  355. final nextPage = state.currentPage + 1;
  356. final dio = ref.read(dioClientProvider);
  357. final records = await AssetService(dio)
  358. .getRechargeList(pageNo: nextPage, pageSize: _pageSize);
  359. state = state.copyWith(
  360. records: [...state.records, ...records],
  361. isLoading: false,
  362. currentPage: nextPage,
  363. hasMore: records.length >= _pageSize,
  364. );
  365. } catch (e) {
  366. state = state.copyWith(isLoading: false, errorMessage: e.toString());
  367. }
  368. }
  369. }
  370. final rechargeHistoryProvider =
  371. NotifierProvider<RechargeHistoryNotifier, RechargeHistoryState>(
  372. RechargeHistoryNotifier.new,
  373. );