import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../core/config/app_config.dart'; import '../../../core/l10n/app_localizations.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/utils/dialog_utils.dart'; import '../../../core/utils/top_toast.dart'; import '../../../data/repositories/copy_trading_repository.dart'; import '../../../providers/customer_service_provider.dart'; import '../user/protocol_screen.dart'; class TraderApplyScreen extends ConsumerStatefulWidget { const TraderApplyScreen({super.key}); @override ConsumerState createState() => _TraderApplyScreenState(); } class _TraderApplyScreenState extends ConsumerState { bool _agreed = false; // 协议默认不勾选 bool _loading = true; bool _submitting = false; // 合约账户资金是否满足条件(≥ 1000 USDT) bool _fundsMet = false; // 当前没有跟随交易员 bool _noFollowing = true; // 已提交申请,审核中(traderLevel == "15") bool _isApplying = false; @override void initState() { super.initState(); _loadConditions(); } Future _loadConditions() async { setState(() => _loading = true); try { final repo = ref.read(copyTradingRepositoryProvider); // 与 Android 保持一致:并行请求三个接口 final walletFuture = repo.getContractWallet(); final followerFuture = repo.getFollowerInfo(); final followingFuture = repo.getFollowingTraders(pageSize: 10); final walletData = await walletFuture; final followerData = await followerFuture; final followingList = await followingFuture; // 合约账户权益(字段优先级:currentCapital > balance > availableBalance) double balance = 0; if (walletData != null) { final raw = walletData['currentCapital'] ?? walletData['balance'] ?? walletData['availableBalance'] ?? 0; balance = double.tryParse(raw.toString()) ?? 0; } // 没有跟随交易员:通过实际列表判断(空列表 = 没有跟随),与 Android 逻辑一致 final noFollowing = followingList.isEmpty; // 审核中:traderLevel(API 字段 "trader")== "15" final traderLevel = followerData?['trader']?.toString() ?? followerData?['traderLevel']?.toString() ?? ''; final isApplying = traderLevel == '15'; if (mounted) { setState(() { _fundsMet = balance >= 1000; _noFollowing = noFollowing; _isApplying = isApplying; _loading = false; }); } } catch (_) { if (mounted) setState(() => _loading = false); } } void _openAgreement() { context.push('/protocol', extra: ProtocolArgs( title: AppLocalizations.of(context)!.traderAgreement, categoryCode: 'FOLLOW_PROTOCOL', )); } Future _submitApply() async { if (_submitting) return; // 防抖:避免重复提交 setState(() => _submitting = true); try { final repo = ref.read(copyTradingRepositoryProvider); await repo.applyTrader(); if (!mounted) return; setState(() => _isApplying = true); showTopToast( context, message: AppLocalizations.of(context)!.applicationSubmitted, backgroundColor: const Color(0xFF2ECC71), ); } catch (e) { if (mounted) { showTopToast(context, message: extractErrorMessage(e)); } } finally { if (mounted) setState(() => _submitting = false); } } @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final canSubmit = _agreed && _fundsMet && _noFollowing && !_loading && !_submitting && !_isApplying; return Scaffold( appBar: AppBar( leading: IconButton( icon: const Icon(Icons.arrow_back_ios, size: 18), onPressed: () => context.pop(), ), title: Text(AppLocalizations.of(context)!.traderApply, style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600)), ), body: Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Column( children: [ const SizedBox(height: 32), // 插图占位 _ApplyIllustration(), const SizedBox(height: 28), Text( AppLocalizations.of(context)!.traderApplyConditions, style: TextStyle(color: cs.onSurface, fontSize: 15, fontWeight: FontWeight.w600), ), const SizedBox(height: 20), // 条件1:资金不足时显示"去划转" Builder(builder: (context) { final l10n = AppLocalizations.of(context)!; return _ConditionItem( met: _loading ? null : _fundsMet, label: l10n.contractAccountFundsReq, action: _loading || _fundsMet ? null : GestureDetector( onTap: () async { await context.push('/asset/transfer?from=SPOT&to=SWAP'); if (mounted) _loadConditions(); }, child: Text(l10n.goTransfer, style: const TextStyle(color: AppColors.brand, fontSize: 13, fontWeight: FontWeight.w600)), ), ); }), Padding( padding: const EdgeInsets.symmetric(vertical: 10), child: Container( height: 0.8, color: const Color(0xFFD0D0D0), ), ), // 条件2 _ConditionItem( met: _loading ? null : _noFollowing, label: AppLocalizations.of(context)!.noFollowingTrader, ), const SizedBox(height: 40), // 协议勾选 Row( children: [ GestureDetector( onTap: () => setState(() => _agreed = !_agreed), child: Container( width: 18, height: 18, decoration: BoxDecoration( color: _agreed ? AppColors.brand : Colors.transparent, border: Border.all(color: _agreed ? AppColors.brand : cs.outline), borderRadius: BorderRadius.circular(3), ), child: _agreed ? Icon(Icons.check, size: 13, color: Colors.black) : null, ), ), const SizedBox(width: 8), Text(AppLocalizations.of(context)!.agreeToAgreement, style: TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 13)), GestureDetector( onTap: _openAgreement, child: Text(AppLocalizations.of(context)!.traderAgreementLink, style: const TextStyle(color: AppColors.brand, fontSize: 13)), ), ], ), const SizedBox(height: 16), // 提交按钮 SizedBox( width: double.infinity, height: 50, child: ElevatedButton( onPressed: canSubmit ? _submitApply : null, style: ElevatedButton.styleFrom( backgroundColor: AppColors.brand, foregroundColor: Colors.black, disabledBackgroundColor: AppColors.brand.withAlpha(80), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)), elevation: 0, ), child: _submitting ? SizedBox( width: 20, height: 20, child: CircularProgressIndicator(color: Colors.black, strokeWidth: 2), ) : Builder(builder: (context) { final l10n = AppLocalizations.of(context)!; return Text( _isApplying ? l10n.reviewingApplication : l10n.submitApplication, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), ); }), ), ), const SizedBox(height: 12), if (AppConfig.customerServiceEnabled) GestureDetector( onTap: () => openCustomerService(context, ref), child: Text( AppLocalizations.of(context)!.contactSupport, style: TextStyle( color: cs.onSurface.withAlpha(153), fontSize: 13, decoration: TextDecoration.underline, decorationColor: cs.onSurface.withAlpha(153), ), ), ), const SizedBox(height: 32), ], ), ), ); } } // ── 插图 ────────────────────────────────────────────────── class _ApplyIllustration extends StatelessWidget { @override Widget build(BuildContext context) { return SizedBox( width: double.infinity, height: 160, child: Stack( clipBehavior: Clip.none, children: [ // 左侧建筑 Positioned( left: 30, bottom: 0, child: _Building(width: 72, height: 110, color: const Color(0xFFCDD2E4)), ), // 右侧建筑 Positioned( right: 30, bottom: 0, child: _Building(width: 72, height: 110, color: const Color(0xFFCDD2E4)), ), // 人物(居中,比建筑高一些) Positioned( bottom: 0, left: 0, right: 0, child: Center(child: _PersonFigure()), ), // 云朵上传图标(右上方) Positioned( top: 0, right: 50, child: Container( width: 44, height: 44, decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, boxShadow: [ BoxShadow(color: Colors.black.withAlpha(20), blurRadius: 10, offset: const Offset(0, 2)), ], ), child: const Icon(Icons.cloud_upload_outlined, color: Color(0xFF5B7BE8), size: 24), ), ), // 金色星星点缀 Positioned( top: 28, left: 50, child: Icon(Icons.star_rate_rounded, color: const Color(0xFFF5C842), size: 10), ), Positioned( top: 14, left: 36, child: Icon(Icons.star_rate_rounded, color: const Color(0xFFF5C842), size: 7), ), ], ), ); } } class _PersonFigure extends StatelessWidget { @override Widget build(BuildContext context) { return SizedBox( width: 64, height: 130, child: Stack( alignment: Alignment.topCenter, children: [ // 身体(蓝色,占下部) Positioned( bottom: 0, child: Container( width: 60, height: 98, decoration: const BoxDecoration( color: Color(0xFF4F6EE6), borderRadius: BorderRadius.only( topLeft: Radius.circular(30), topRight: Radius.circular(30), ), ), child: Column( children: [ const SizedBox(height: 14), // 领带/衬衣细节 Container( width: 6, height: 28, decoration: BoxDecoration( color: const Color(0xFF3A5BC7), borderRadius: BorderRadius.circular(3), ), ), ], ), ), ), // 头部(黄色圆形,在身体上方) Positioned( top: 0, child: Container( width: 38, height: 38, decoration: const BoxDecoration( color: Color(0xFFF5C842), shape: BoxShape.circle, ), ), ), // 帽子/头发(深棕色半圆在头顶) Positioned( top: 0, child: Container( width: 38, height: 18, decoration: const BoxDecoration( color: Color(0xFF6B4226), borderRadius: BorderRadius.only( topLeft: Radius.circular(19), topRight: Radius.circular(19), ), ), ), ), ], ), ); } } class _Building extends StatelessWidget { const _Building({required this.width, required this.height, required this.color}); final double width; final double height; final Color color; @override Widget build(BuildContext context) { const windowColor = Color(0xFFEEF1FA); const cols = 3; const rows = 4; const gap = 5.0; const winSize = 12.0; return Container( width: width, height: height, decoration: BoxDecoration( color: color, borderRadius: const BorderRadius.only(topLeft: Radius.circular(4), topRight: Radius.circular(4)), ), child: Padding( padding: const EdgeInsets.fromLTRB(7, 10, 7, 6), child: Column( mainAxisAlignment: MainAxisAlignment.start, children: List.generate(rows, (r) => Padding( padding: EdgeInsets.only(bottom: r < rows - 1 ? gap : 0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: List.generate(cols, (_) => Container( width: winSize, height: winSize, decoration: BoxDecoration( color: windowColor, borderRadius: BorderRadius.circular(2), ), )), ), )), ), ), ); } } class _Star extends StatelessWidget { const _Star({required this.color, required this.size}); final Color color; final double size; @override Widget build(BuildContext context) { return Icon(Icons.star, color: color, size: size); } } // ── 条件行 ──────────────────────────────────────────────── class _ConditionItem extends StatelessWidget { const _ConditionItem({required this.met, required this.label, this.action}); final bool? met; final String label; final Widget? action; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return Row( children: [ if (met == null) const SizedBox( width: 22, height: 22, child: CircularProgressIndicator(strokeWidth: 2), ) else Container( width: 22, height: 22, decoration: BoxDecoration( color: met! ? const Color(0xFF2ECC71) : const Color(0xFFE74C3C), shape: BoxShape.circle, ), child: Icon( met! ? Icons.check : Icons.close, color: Colors.white, size: 14, ), ), const SizedBox(width: 10), Expanded( child: Text(label, style: TextStyle(color: cs.onSurface, fontSize: 14)), ), if (action != null) action!, ], ); } }