import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import '../../core/utils/spot_transfer_asset.dart'; import '../models/finance/airdrop_eligibility.dart'; import '../models/finance/airdrop_record_item.dart'; import '../models/finance/staking_config.dart'; import '../models/finance/staking_wallet_balance.dart'; class StakingService { const StakingService(this._dio); final Dio _dio; void _log(String message) { if (kDebugMode) { debugPrint('[Airdrop][Service] $message'); } } /// 与 Web `STAKING_COIN_UNIT` / `stakingCoinUnit()` 默认币种一致 static const stakingCoinUnit = 'IBIT'; Future> getConfigs() async { final resp = await _dio.post>('uc/staking/config/list'); final raw = resp.data?['data']; if (raw is List) { return raw .whereType>() .map(StakingConfig.fromJson) .toList(); } return const []; } Future getMinOpenConfig() async { final resp = await _dio.post>('uc/staking/config/min-open'); final raw = resp.data?['data']; if (raw is Map) { return StakingConfig.fromJson(raw); } return null; } Future submitStake({ required int configId, required String amount, }) async { await _dio.post( 'uc/staking/stake', data: {'configId': configId, 'amount': amount}, options: Options(contentType: Headers.formUrlEncodedContentType), ); } Future getStakingWalletBalance(String coinUnit) async { final resp = await _dio.post>( 'uc/staking/wallet', data: {'coinUnit': coinUnit}, options: Options(contentType: Headers.formUrlEncodedContentType), ); final raw = resp.data?['data']; if (raw is Map) { return StakingWalletBalance.fromJson(raw); } return StakingWalletBalance.empty(coinUnit); } /// 可提现账户指定币种可用余额(对齐 Web `loadFundCoinBalance`) /// /// POST `/uc/asset/wallet`,解析逻辑同 `parseFundWalletBalances`。 Future getFundingWalletAvailable(String coinUnit) async { try { final resp = await _dio.post>( 'uc/asset/wallet', data: {}, ); final map = parseFundWalletBalances(resp.data); return map[coinUnit.toUpperCase()] ?? '0'; } catch (_) { return '0'; } } Future getAirdropEligibility() async { final resp = await _dio.post>('uc/airdrop/eligibility'); final raw = resp.data?['data']; _log( 'eligibility response dataType=${raw.runtimeType} keys=${raw is Map ? raw.keys.toList() : 'n/a'}', ); if (raw is Map) { return AirdropEligibility.fromJson(raw); } _log('eligibility fallback to empty because data is not map'); return AirdropEligibility.empty(); } Future claimAirdrop() async { await _dio.post>('uc/airdrop/claim'); } Future getAirdropRecords({ required int pageNo, int pageSize = 10, }) async { final resp = await _dio.post>( 'uc/airdrop/records', data: {'pageNo': pageNo, 'pageSize': pageSize}, options: Options(contentType: Headers.formUrlEncodedContentType), ); final raw = resp.data?['data']; _log( 'records response pageNo=$pageNo pageSize=$pageSize dataType=${raw.runtimeType}'); if (raw is! Map) { _log('records fallback empty: data is not map'); return const AirdropRecordPage(content: [], totalPages: 0, pageNo: 1); } final contentRaw = raw['content']; final content = contentRaw is List ? contentRaw .whereType>() .map(AirdropRecordItem.fromJson) .toList() : []; final totalPages = int.tryParse('${raw['totalPages'] ?? 0}') ?? 0; // 后端常见两种分页字段: // 1) pageNo: 1 基页码 // 2) number: 0 基页码(Spring Pageable) final rawPageNo = int.tryParse('${raw['pageNo'] ?? ''}'); final rawNumber = int.tryParse('${raw['number'] ?? ''}'); final parsedPageNo = rawPageNo ?? ((rawNumber ?? (pageNo - 1)) + 1); _log( 'records parsed pageNo=$parsedPageNo(totalPages=$totalPages, rawPageNo=$rawPageNo, rawNumber=$rawNumber) contentRawType=${contentRaw.runtimeType} contentCount=${content.length}', ); return AirdropRecordPage( content: content, totalPages: totalPages, pageNo: parsedPageNo, ); } }