| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766 |
- import 'package:decimal/decimal.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:flutter_riverpod/flutter_riverpod.dart';
- import 'package:go_router/go_router.dart';
- import '../../../core/l10n/app_localizations.dart';
- import '../../../core/theme/app_colors.dart';
- import '../../../core/utils/dialog_utils.dart' show extractErrorMessage;
- import '../../../core/utils/top_toast.dart';
- import '../../../providers/withdraw_provider.dart';
- class WithdrawScreen extends ConsumerStatefulWidget {
- const WithdrawScreen({super.key});
- @override
- ConsumerState<WithdrawScreen> createState() => _WithdrawScreenState();
- }
- class _WithdrawScreenState extends ConsumerState<WithdrawScreen>
- with SingleTickerProviderStateMixin {
- bool _obscureFundPwd = true;
- late TabController _tabController;
- late PageController _pageController;
- final _addressController = TextEditingController();
- final _amountController = TextEditingController();
- final _fundPwdController = TextEditingController();
- final _emailCodeController = TextEditingController();
- final _googleCodeController = TextEditingController();
- @override
- void initState() {
- super.initState();
- _tabController = TabController(length: 2, vsync: this);
- _pageController = PageController();
- _tabController.addListener(() {
- if (!mounted) return;
- if (_tabController.indexIsChanging) {
- _pageController.animateToPage(
- _tabController.index,
- duration: const Duration(milliseconds: 280),
- curve: Curves.easeOut,
- );
- } else {
- _amountController.clear();
- ref.read(withdrawProvider.notifier).setTab(_tabController.index);
- }
- });
- _pageController.addListener(() {
- if (!mounted) return;
- if (!_pageController.hasClients) return;
- final page = _pageController.page!;
- final offset = page - _tabController.index;
- if (offset.abs() <= 1.0 && !_tabController.indexIsChanging) {
- _tabController.offset = offset.clamp(-1.0, 1.0);
- }
- });
- // 每次进入页面重置 tab 到链上提币,并刷新数据
- WidgetsBinding.instance.addPostFrameCallback((_) {
- ref.read(withdrawProvider.notifier).setTab(0);
- ref.read(withdrawProvider.notifier).refresh();
- });
- }
- @override
- void dispose() {
- _tabController.dispose();
- _pageController.dispose();
- _addressController.dispose();
- _amountController.dispose();
- _fundPwdController.dispose();
- _emailCodeController.dispose();
- _googleCodeController.dispose();
- super.dispose();
- }
- @override
- Widget build(BuildContext context) {
- final cs = Theme.of(context).colorScheme;
- final state = ref.watch(withdrawProvider);
- final notifier = ref.read(withdrawProvider.notifier);
- return Scaffold(
- appBar: AppBar(
- leading: IconButton(
- icon: const Icon(Icons.chevron_left, size: 28),
- onPressed: () => context.pop(),
- ),
- title: Text(AppLocalizations.of(context)!.withdrawCoin, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
- centerTitle: true,
- actions: [
- TextButton(
- onPressed: () => context.push('/asset/withdraw/history'),
- child: Text(AppLocalizations.of(context)!.withdrawRecord, style: TextStyle(color: cs.onSurface, fontSize: 14)),
- ),
- ],
- ),
- body: _buildBody(context, state, notifier),
- );
- }
- Widget _buildBody(BuildContext context, WithdrawState state, WithdrawNotifier notifier) {
- final isDark = Theme.of(context).brightness == Brightness.dark;
- return Column(
- children: [
- // ── 链上提币 / 内部转账 Tab ────────────────────
- Padding(
- padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
- child: Container(
- height: 44,
- decoration: BoxDecoration(color: isDark ? AppColors.darkBgTertiary : AppColors.lightBgTertiary, borderRadius: BorderRadius.circular(22)),
- child: Row(
- children: [
- _buildTab(context, state, notifier, 0, AppLocalizations.of(context)!.onChainWithdraw),
- _buildTab(context, state, notifier, 1, AppLocalizations.of(context)!.internalTransfer),
- ],
- ),
- ),
- ),
- // ── 内容区 ────────────────────────────────────
- Expanded(
- child: PageView(
- controller: _pageController,
- onPageChanged: (index) {
- _addressController.clear();
- _amountController.clear();
- notifier.setTab(index);
- },
- children: [
- SingleChildScrollView(
- padding: const EdgeInsets.fromLTRB(16, 0, 16, 32),
- child: _buildOnChain(context, state, notifier),
- ),
- SingleChildScrollView(
- padding: const EdgeInsets.fromLTRB(16, 0, 16, 32),
- child: _buildInternal(context, state, notifier),
- ),
- ],
- ),
- ),
- ],
- );
- }
- Widget _buildTab(BuildContext context, WithdrawState state, WithdrawNotifier notifier, int index, String label) {
- final cs = Theme.of(context).colorScheme;
- final selected = index == state.tabIndex;
- return Expanded(
- child: GestureDetector(
- onTap: () {
- _tabController.animateTo(index);
- },
- child: Container(
- margin: const EdgeInsets.all(3),
- decoration: BoxDecoration(
- color: selected ? AppColors.brand : Colors.transparent,
- borderRadius: BorderRadius.circular(20),
- ),
- child: Center(
- child: Text(label, style: TextStyle(
- color: selected ? Colors.black : cs.onSurface.withAlpha(153),
- fontSize: 14,
- fontWeight: selected ? FontWeight.w600 : FontWeight.w400,
- )),
- ),
- ),
- ),
- );
- }
- // ══════════════════════════════════════════════════════════
- // 链上提币
- // ══════════════════════════════════════════════════════════
- void _showNetworkTipDialog(BuildContext context) {
- final l10n = AppLocalizations.of(context)!;
- final cs = Theme.of(context).colorScheme;
- final isDark = Theme.of(context).brightness == Brightness.dark;
- showDialog<void>(
- context: context,
- builder: (ctx) => Dialog(
- backgroundColor: isDark ? AppColors.darkBgSecondary : Colors.white,
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
- child: Padding(
- padding: const EdgeInsets.fromLTRB(20, 16, 16, 20),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- // 标题行
- Row(
- children: [
- Expanded(
- child: Text(
- l10n.withdrawNetwork,
- style: TextStyle(
- color: cs.onSurface,
- fontSize: 16,
- fontWeight: FontWeight.w600,
- ),
- ),
- ),
- GestureDetector(
- onTap: () => Navigator.of(ctx).pop(),
- child: Icon(Icons.close, size: 20, color: cs.onSurface.withAlpha(153)),
- ),
- ],
- ),
- const SizedBox(height: 12),
- // 内容
- Text(
- l10n.withdrawNetworkTip,
- style: TextStyle(
- color: cs.onSurface.withAlpha(179),
- fontSize: 14,
- height: 1.6,
- ),
- ),
- const SizedBox(height: 20),
- // 确认按钮
- SizedBox(
- width: double.infinity,
- height: 44,
- child: ElevatedButton(
- onPressed: () => Navigator.of(ctx).pop(),
- style: ElevatedButton.styleFrom(
- backgroundColor: AppColors.brand,
- foregroundColor: Colors.black,
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
- elevation: 0,
- ),
- child: Text(l10n.confirm, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
- ),
- ),
- ],
- ),
- ),
- ),
- );
- }
- Widget _buildOnChain(BuildContext context, WithdrawState state, WithdrawNotifier notifier) {
- final cs = Theme.of(context).colorScheme;
- final isDark = Theme.of(context).brightness == Brightness.dark;
- final fee = state.fee;
- final minAmount = state.minWithdrawAmount;
- final available = state.withdrawableBalance;
- // 到账金额计算
- final inputAmount = Decimal.tryParse(_amountController.text) ?? Decimal.zero;
- final receiveAmount = inputAmount - fee;
- final receiveDisplay = receiveAmount > Decimal.zero ? receiveAmount.toString() : '0.00';
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- // 选择币种
- _Label(AppLocalizations.of(context)!.selectCoin),
- const SizedBox(height: 8),
- _FixedCoinField(coin: 'USDT'),
- const SizedBox(height: 16),
- // 提币网络
- Row(
- children: [
- _Label(AppLocalizations.of(context)!.withdrawNetwork),
- const SizedBox(width: 4),
- GestureDetector(
- onTap: () => _showNetworkTipDialog(context),
- child: Icon(Icons.info_outline, size: 14, color: cs.onSurface.withAlpha(120)),
- ),
- ],
- ),
- const SizedBox(height: 8),
- Row(
- children: List.generate(state.networkNames.length, (i) {
- final selected = i == state.selectedNetworkIndex;
- return Padding(
- padding: const EdgeInsets.only(right: 10),
- child: GestureDetector(
- onTap: () => notifier.selectNetwork(i),
- child: Container(
- padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
- decoration: BoxDecoration(
- color: selected ? AppColors.brand : Colors.transparent,
- border: Border.all(color: selected ? AppColors.brand : cs.outline.withAlpha(80)),
- borderRadius: BorderRadius.circular(8),
- ),
- child: Text(state.networkNames[i], style: TextStyle(
- color: selected ? Colors.black : cs.onSurface,
- fontSize: 14,
- fontWeight: selected ? FontWeight.w600 : FontWeight.w400,
- )),
- ),
- ),
- );
- }),
- ),
- const SizedBox(height: 16),
- // 提币地址
- _Label(AppLocalizations.of(context)!.withdrawAddress),
- const SizedBox(height: 8),
- _InputField(
- controller: _addressController,
- hint: AppLocalizations.of(context)!.enterWithdrawAddress,
- suffixIcon: Icon(Icons.qr_code_scanner, size: 20, color: cs.onSurface.withAlpha(153)),
- onSuffixIconTap: _scanQrCode,
- ),
- const SizedBox(height: 16),
- // 提币金额
- Row(
- children: [
- _Label(AppLocalizations.of(context)!.withdrawAmount),
- const SizedBox(width: 4),
- Icon(Icons.info_outline, size: 14, color: cs.onSurface.withAlpha(120)),
- ],
- ),
- const SizedBox(height: 8),
- _InputField(
- controller: _amountController,
- hint: AppLocalizations.of(context)!.withdrawMinAmountHint(minAmount),
- keyboardType: const TextInputType.numberWithOptions(decimal: true),
- inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,4}'))],
- onChanged: (_) => setState(() {}),
- suffix: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Text('USDT', style: TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 14)),
- const SizedBox(width: 8),
- GestureDetector(
- onTap: () {
- _amountController.text = available.toString();
- setState(() {});
- },
- child: Text(AppLocalizations.of(context)!.max, style: const TextStyle(color: AppColors.brand, fontSize: 14, fontWeight: FontWeight.w600)),
- ),
- ],
- ),
- ),
- const SizedBox(height: 6),
- Row(
- children: [
- Text('${AppLocalizations.of(context)!.fundAccountAvailable}:', style: TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 12)),
- Flexible(child: Text('$available USDT ', overflow: TextOverflow.ellipsis, style: TextStyle(color: cs.onSurface, fontSize: 12, fontWeight: FontWeight.w600))),
- GestureDetector(
- onTap: () async {
- await context.push('/asset/transfer');
- if (mounted) ref.read(withdrawProvider.notifier).refresh();
- },
- child: Text(AppLocalizations.of(context)!.transfer, style: const TextStyle(color: AppColors.brand, fontSize: 12, fontWeight: FontWeight.w500)),
- ),
- ],
- ),
- const SizedBox(height: 20),
- // 到账数量
- _Label(AppLocalizations.of(context)!.receivedAmount),
- const SizedBox(height: 8),
- Container(
- padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(color: isDark ? AppColors.darkBgTertiary : AppColors.lightBgTertiary, borderRadius: BorderRadius.circular(8)),
- child: Text('$receiveDisplay USDT', style: TextStyle(color: cs.onSurface, fontSize: 16, fontWeight: FontWeight.w600)),
- ),
- const SizedBox(height: 16),
- // 手续费
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- _Label(AppLocalizations.of(context)!.fee),
- Text('$fee USDT', style: TextStyle(color: cs.onSurface, fontSize: 14, fontWeight: FontWeight.w600)),
- ],
- ),
- const SizedBox(height: 20),
- // 安全验证
- _buildSecurityFields(context, state, notifier),
- const SizedBox(height: 24),
- // 提币按钮
- SizedBox(
- width: double.infinity,
- height: 50,
- child: ElevatedButton(
- onPressed: state.isSubmitting ? null : () => _submitOnChain(notifier),
- style: ElevatedButton.styleFrom(
- backgroundColor: AppColors.brand,
- disabledBackgroundColor: AppColors.brand.withAlpha(80),
- foregroundColor: Colors.black,
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)),
- ),
- child: state.isSubmitting
- ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.black))
- : Text(AppLocalizations.of(context)!.withdrawCoin, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
- ),
- ),
- const SizedBox(height: 16),
- ..._notices(context).map((n) => Padding(
- padding: const EdgeInsets.only(bottom: 4),
- child: Text('· $n', style: TextStyle(color: cs.onSurface.withAlpha(100), fontSize: 11, height: 1.4)),
- )),
- ],
- );
- }
- // ══════════════════════════════════════════════════════════
- // 内部转账
- // ══════════════════════════════════════════════════════════
- Widget _buildInternal(BuildContext context, WithdrawState state, WithdrawNotifier notifier) {
- final cs = Theme.of(context).colorScheme;
- final available = state.transferableBalance;
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- _Label(AppLocalizations.of(context)!.selectCoin),
- const SizedBox(height: 8),
- _FixedCoinField(coin: 'USDT'),
- const SizedBox(height: 16),
- _Label(AppLocalizations.of(context)!.recipientUidOrAccount),
- const SizedBox(height: 8),
- _InputField(
- controller: _addressController,
- hint: AppLocalizations.of(context)!.enterRecipientUid,
- ),
- const SizedBox(height: 16),
- _Label(AppLocalizations.of(context)!.withdrawAmount),
- const SizedBox(height: 8),
- _InputField(
- controller: _amountController,
- hint: AppLocalizations.of(context)!.transferMinAmountHint(state.transferMinAmount),
- keyboardType: const TextInputType.numberWithOptions(decimal: true),
- inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,4}'))],
- suffix: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Text('USDT', style: TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 14)),
- const SizedBox(width: 8),
- GestureDetector(
- onTap: () => _amountController.text = available.toString(),
- child: Text(AppLocalizations.of(context)!.max, style: const TextStyle(color: AppColors.brand, fontSize: 14, fontWeight: FontWeight.w600)),
- ),
- ],
- ),
- ),
- const SizedBox(height: 6),
- Row(
- children: [
- Text('${AppLocalizations.of(context)!.fundAccountAvailable}:', style: TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 12)),
- Flexible(child: Text('$available USDT ', overflow: TextOverflow.ellipsis, style: TextStyle(color: cs.onSurface, fontSize: 12, fontWeight: FontWeight.w600))),
- GestureDetector(
- onTap: () async {
- await context.push('/asset/transfer');
- if (mounted) ref.read(withdrawProvider.notifier).refresh();
- },
- child: Text(AppLocalizations.of(context)!.transfer, style: const TextStyle(color: AppColors.brand, fontSize: 12, fontWeight: FontWeight.w500)),
- ),
- ],
- ),
- const SizedBox(height: 20),
- _buildSecurityFields(context, state, notifier),
- const SizedBox(height: 24),
- SizedBox(
- width: double.infinity,
- height: 50,
- child: ElevatedButton(
- onPressed: state.isSubmitting ? null : () => _submitTransfer(notifier),
- style: ElevatedButton.styleFrom(
- backgroundColor: AppColors.brand,
- foregroundColor: Colors.black,
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)),
- ),
- child: state.isSubmitting
- ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.black))
- : Text(AppLocalizations.of(context)!.transfer, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
- ),
- ),
- const SizedBox(height: 16),
- ..._notices(context).map((n) => Padding(
- padding: const EdgeInsets.only(bottom: 4),
- child: Text('· $n', style: TextStyle(color: cs.onSurface.withAlpha(100), fontSize: 11, height: 1.4)),
- )),
- ],
- );
- }
- // ── 安全验证字段(链上/内部共用)───────────────────────
- Widget _buildSecurityFields(BuildContext context, WithdrawState state, WithdrawNotifier notifier) {
- final cs = Theme.of(context).colorScheme;
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(AppLocalizations.of(context)!.securityVerification, style: TextStyle(color: cs.onSurface, fontSize: 15, fontWeight: FontWeight.w600)),
- const SizedBox(height: 12),
- _InputField(
- controller: _fundPwdController,
- hint: AppLocalizations.of(context)!.fundPassword,
- obscure: _obscureFundPwd,
- maxLength: 16,
- suffixIcon: GestureDetector(
- onTap: () => setState(() => _obscureFundPwd = !_obscureFundPwd),
- child: Icon(
- _obscureFundPwd ? Icons.visibility_off_outlined : Icons.visibility_outlined,
- size: 20, color: cs.onSurface.withAlpha(153),
- ),
- ),
- ),
- const SizedBox(height: 10),
- _InputField(
- controller: _emailCodeController,
- hint: AppLocalizations.of(context)!.emailCode,
- keyboardType: TextInputType.number,
- inputFormatters: [FilteringTextInputFormatter.digitsOnly],
- maxLength: 6,
- suffix: GestureDetector(
- onTap: state.codeCountdown > 0
- ? null
- : () async {
- final error = await notifier.sendEmailCode(
- address: _addressController.text,
- amount: _amountController.text,
- );
- if (error != null && mounted) {
- final l10n = AppLocalizations.of(context)!;
- showTopToast(context, message: resolveProviderError(error, l10n) ?? error, backgroundColor: AppColors.fall);
- }
- },
- child: Text(
- state.codeCountdown > 0 ? '${state.codeCountdown}s' : AppLocalizations.of(context)!.sendCode,
- style: TextStyle(
- color: state.codeCountdown > 0 ? cs.onSurface.withAlpha(100) : AppColors.brand,
- fontSize: 13,
- ),
- ),
- ),
- ),
- const SizedBox(height: 10),
- _InputField(
- controller: _googleCodeController,
- hint: AppLocalizations.of(context)!.googleCode,
- keyboardType: TextInputType.number,
- inputFormatters: [FilteringTextInputFormatter.digitsOnly],
- maxLength: 6,
- suffix: GestureDetector(
- onTap: () async {
- final data = await Clipboard.getData('text/plain');
- if (data?.text != null) {
- _googleCodeController.text = data!.text!;
- }
- },
- child: Text(AppLocalizations.of(context)!.paste, style: const TextStyle(color: AppColors.brand, fontSize: 13)),
- ),
- ),
- ],
- );
- }
- // ── 提交逻辑 ────────────────────────────────────────
- void _submitOnChain(WithdrawNotifier notifier) async {
- final error = notifier.validate(
- address: _addressController.text,
- amount: _amountController.text,
- jyPassword: _fundPwdController.text,
- vcode: _emailCodeController.text,
- vcode2: _googleCodeController.text,
- );
- if (error != null) {
- final l10n = AppLocalizations.of(context)!;
- showTopToast(context, message: resolveProviderError(error, l10n) ?? error, backgroundColor: AppColors.fall);
- return;
- }
- try {
- final success = await notifier.submitOnChainWithdraw(
- address: _addressController.text,
- amount: _amountController.text,
- jyPassword: _fundPwdController.text,
- vcode: _emailCodeController.text,
- vcode2: _googleCodeController.text,
- );
- if (!mounted) return;
- if (success) {
- showTopToast(context, message: AppLocalizations.of(context)!.withdrawSubmitted, backgroundColor: AppColors.rise);
- _clearFields();
- notifier.refresh();
- } else {
- final state = ref.read(withdrawProvider);
- if (state.errorMessage != null) {
- final l10nE = AppLocalizations.of(context)!;
- showTopToast(context, message: resolveProviderError(state.errorMessage!, l10nE) ?? state.errorMessage!, backgroundColor: AppColors.fall);
- }
- }
- } catch (e) {
- if (mounted) showTopToast(context, message: extractErrorMessage(e), backgroundColor: AppColors.fall);
- }
- }
- void _submitTransfer(WithdrawNotifier notifier) async {
- final error = notifier.validate(
- address: _addressController.text,
- amount: _amountController.text,
- jyPassword: _fundPwdController.text,
- vcode: _emailCodeController.text,
- vcode2: _googleCodeController.text,
- );
- if (error != null) {
- final l10n = AppLocalizations.of(context)!;
- showTopToast(context, message: resolveProviderError(error, l10n) ?? error, backgroundColor: AppColors.fall);
- return;
- }
- try {
- final success = await notifier.submitInternalTransfer(
- address: _addressController.text,
- amount: _amountController.text,
- jyPassword: _fundPwdController.text,
- vcode: _emailCodeController.text,
- vcode2: _googleCodeController.text,
- );
- if (!mounted) return;
- if (success) {
- showTopToast(context, message: AppLocalizations.of(context)!.transferSuccess2, backgroundColor: AppColors.rise);
- _clearFields();
- notifier.refresh();
- } else {
- final state = ref.read(withdrawProvider);
- if (state.errorMessage != null) {
- final l10nE = AppLocalizations.of(context)!;
- showTopToast(context, message: resolveProviderError(state.errorMessage!, l10nE) ?? state.errorMessage!, backgroundColor: AppColors.fall);
- }
- }
- } catch (e) {
- if (mounted) showTopToast(context, message: extractErrorMessage(e), backgroundColor: AppColors.fall);
- }
- }
- void _clearFields() {
- _addressController.clear();
- _amountController.clear();
- _fundPwdController.clear();
- _emailCodeController.clear();
- _googleCodeController.clear();
- }
- /// 扫描二维码
- Future<void> _scanQrCode() async {
- try {
- final result = await context.push<String>('/qr-scanner');
- if (result != null && result.isNotEmpty) {
- _addressController.text = result;
- }
- } catch (e) {
- showTopToast(context, message: AppLocalizations.of(context)!.scannerFailed, backgroundColor: AppColors.fall);
- }
- }
- List<String> _notices(BuildContext context) {
- final l10n = AppLocalizations.of(context)!;
- return [l10n.withdrawNotice1, l10n.withdrawNotice2];
- }
- }
- // ── 共享组件 ─────────────────────────────────────────────────
- class _FixedCoinField extends StatelessWidget {
- const _FixedCoinField({required this.coin});
- final String coin;
- @override
- Widget build(BuildContext context) {
- final cs = Theme.of(context).colorScheme;
- return Container(
- padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
- decoration: BoxDecoration(
- border: Border.all(color: cs.outline.withAlpha(60)),
- borderRadius: BorderRadius.circular(10),
- ),
- child: Row(
- children: [
- Text(coin, style: TextStyle(color: cs.onSurface, fontSize: 15, fontWeight: FontWeight.w500)),
- const Spacer(),
- Icon(Icons.keyboard_arrow_down, color: cs.onSurface.withAlpha(153)),
- ],
- ),
- );
- }
- }
- class _Label extends StatelessWidget {
- const _Label(this.text);
- final String text;
- @override
- Widget build(BuildContext context) {
- return Text(text, style: TextStyle(color: Theme.of(context).colorScheme.onSurface.withAlpha(153), fontSize: 13));
- }
- }
- class _InputField extends StatelessWidget {
- const _InputField({
- required this.controller,
- required this.hint,
- this.obscure = false,
- this.keyboardType,
- this.suffix,
- this.suffixIcon,
- this.onSuffixIconTap,
- this.onChanged,
- this.inputFormatters,
- this.maxLength,
- });
- final TextEditingController controller;
- final String hint;
- final bool obscure;
- final TextInputType? keyboardType;
- final Widget? suffix;
- final Widget? suffixIcon;
- final VoidCallback? onSuffixIconTap;
- final ValueChanged<String>? onChanged;
- final List<TextInputFormatter>? inputFormatters;
- final int? maxLength;
- @override
- Widget build(BuildContext context) {
- final cs = Theme.of(context).colorScheme;
- return TextField(
- controller: controller,
- obscureText: obscure,
- keyboardType: keyboardType,
- onChanged: onChanged,
- inputFormatters: inputFormatters,
- maxLength: maxLength,
- style: TextStyle(color: cs.onSurface, fontSize: 14),
- decoration: InputDecoration(
- hintText: hint,
- hintStyle: TextStyle(color: cs.onSurface.withAlpha(100), fontSize: 14),
- contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
- suffixIcon: suffixIcon != null
- ? GestureDetector(
- onTap: onSuffixIconTap,
- child: Padding(padding: const EdgeInsets.only(right: 12), child: suffixIcon),
- )
- : null,
- suffix: suffix,
- suffixIconConstraints: const BoxConstraints(minHeight: 20),
- ),
- );
- }
- }
|