| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- 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<TraderApplyScreen> createState() => _TraderApplyScreenState();
- }
- class _TraderApplyScreenState extends ConsumerState<TraderApplyScreen> {
- 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<void> _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<void> _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!,
- ],
- );
- }
- }
|