| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239 |
- import 'dart:io';
- import 'dart:ui' as ui;
- import 'package:flutter/foundation.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/rendering.dart';
- import 'package:flutter/services.dart';
- import 'package:flutter_riverpod/flutter_riverpod.dart';
- import 'package:gal/gal.dart';
- import 'package:go_router/go_router.dart';
- import 'package:path_provider/path_provider.dart';
- import 'package:qr_flutter/qr_flutter.dart';
- import 'package:reown_appkit/modal/services/coinbase_service/utils/coinbase_utils.dart';
- import 'package:reown_appkit/reown_appkit.dart';
- import '../../../core/config/app_config.dart';
- import '../../../core/l10n/app_localizations.dart';
- import '../../../core/theme/app_colors.dart';
- import '../../../core/utils/top_toast.dart';
- import '../../../core/wallet/evm_recharge.dart';
- import '../../../core/wallet/tron_recharge.dart';
- import '../../../core/wallet/wallet_connect_recharge_helper.dart';
- import '../../../core/wallet/wallet_connect_tron_recharge.dart';
- import '../../../data/models/asset/recharge_order.dart';
- import '../../../providers/deposit_provider.dart';
- /// AppKit 默认只为 eip155/solana 填 methods;`tron` 会为 `[]`,钱包不会授权 `tron_signTransaction`。
- Map<String, RequiredNamespace> _depositAppKitOptionalNamespaces() {
- final map = <String, RequiredNamespace>{};
- for (final ns in ReownAppKitModalNetworks.getAllSupportedNamespaces()) {
- final networks =
- ReownAppKitModalNetworks.getAllSupportedNetworks(namespace: ns);
- final methods = ns == 'tron'
- ? <String>['tron_signTransaction', 'tron_signMessage']
- : (NetworkUtils.defaultNetworkMethods[ns] ?? <String>[]);
- map[ns] = RequiredNamespace(
- chains: networks.map((e) => e.chainId).toList(),
- methods: methods,
- events: NetworkUtils.defaultNetworkEvents[ns] ?? <String>[],
- );
- }
- return map;
- }
- /// 充币:与 Web `DepositView.vue` + `useWalletConnectDeposit` 对齐(订单 + WalletConnect / 手动 Hash)。
- class DepositScreen extends ConsumerStatefulWidget {
- const DepositScreen({super.key});
- @override
- ConsumerState<DepositScreen> createState() => _DepositScreenState();
- }
- class _DepositScreenState extends ConsumerState<DepositScreen>
- with SingleTickerProviderStateMixin {
- static const int _tabManual = 0;
- static const int _tabOnChain = 1;
- late final TabController _depositModeTab;
- final _qrKey = GlobalKey();
- final _amountCtrl = TextEditingController();
- final _hashCtrl = TextEditingController();
- bool _savingQr = false;
- ReownAppKitModal? _appKitModal;
- bool _appKitInitializing = false;
- @override
- void initState() {
- super.initState();
- _depositModeTab = TabController(length: 2, vsync: this);
- }
- @override
- void dispose() {
- _depositModeTab.dispose();
- _amountCtrl.dispose();
- _hashCtrl.dispose();
- _appKitModal?.disconnect();
- _appKitModal?.dispose();
- super.dispose();
- }
- Future<void> _ensureAppKitModal() async {
- final projectId = AppConfig.walletConnectProjectId;
- if (projectId.isEmpty) {
- return;
- }
- if (_appKitModal != null) {
- return;
- }
- if (_appKitInitializing) {
- return;
- }
- _appKitInitializing = true;
- try {
- ReownAppKitModalNetworks.removeSupportedNetworks('solana');
- ReownAppKitModalNetworks.addSupportedNetworks('tron', [
- ReownAppKitModalNetworkInfo(
- name: 'Tron',
- chainId: kTronMainnetChainIdHex,
- chainIcon:
- 'https://pbs.twimg.com/profile_images/1761904730668675072/v98T7vRL_400x400.jpg',
- currency: 'TRX',
- rpcUrl: AppConfig.tronFullHost,
- explorerUrl: 'https://tronscan.org',
- ),
- ]);
- final m = ReownAppKitModal(
- context: context,
- projectId: projectId,
- logLevel: kDebugMode ? LogLevel.error : LogLevel.nothing,
- disconnectOnDispose: true,
- /// 跳过 Coinbase Wallet 的 Explorer 单钱包拉取,避免弱网环境下访问 api.web3modal.com 被 RST 时阻塞/报错(充币不需 Coinbase)。
- excludedWalletIds: {CoinbaseUtils.walletId},
- metadata: PairingMetadata(
- name: 'iBit',
- description:
- AppLocalizations.of(context)!.walletConnectPairingDescription,
- url: 'https://ibit123.com',
- icons: const [
- 'https://raw.githubusercontent.com/reown-com/reown_flutter/refs/heads/develop/assets/appkit_logo.png',
- ],
- redirect: Redirect(
- native: 'ibit://',
- universal: 'https://ibit123.com',
- linkMode: false,
- ),
- ),
- optionalNamespaces: _depositAppKitOptionalNamespaces(),
- featuresConfig: FeaturesConfig(socials: const []),
- );
- await m.init();
- if (mounted) {
- setState(() {
- _appKitModal = m;
- });
- }
- } finally {
- _appKitInitializing = false;
- }
- }
- Future<void> _saveQr(String address) async {
- if (_savingQr || address.isEmpty) {
- return;
- }
- setState(() => _savingQr = true);
- try {
- final hasAccess = await Gal.hasAccess(toAlbum: true);
- if (!hasAccess) {
- final granted = await Gal.requestAccess(toAlbum: true);
- if (!granted) {
- if (mounted) {
- showTopToast(context,
- message: AppLocalizations.of(context)!.saveFailed);
- }
- return;
- }
- }
- await WidgetsBinding.instance.endOfFrame;
- final boundary =
- _qrKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
- final image = await boundary.toImage(pixelRatio: 3.0);
- final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
- final bytes = byteData!.buffer.asUint8List();
- final tempDir = await getTemporaryDirectory();
- final file = File(
- '${tempDir.path}/deposit_qr_${DateTime.now().millisecondsSinceEpoch}.png');
- await file.writeAsBytes(bytes);
- await Gal.putImage(file.path);
- if (mounted) {
- showTopToast(context,
- message: AppLocalizations.of(context)!.saveSuccess);
- }
- } catch (e) {
- if (mounted) {
- showTopToast(context,
- message: AppLocalizations.of(context)!.saveFailed);
- }
- } finally {
- if (mounted) {
- setState(() => _savingQr = false);
- }
- }
- }
- bool _validateAmount(AppLocalizations l10n) {
- final s = _amountCtrl.text.trim();
- if (s.isEmpty) {
- showTopToast(context, message: l10n.depositEnterAmount);
- return false;
- }
- final n = num.tryParse(s.replaceAll(',', ''));
- if (n == null || n <= 0) {
- showTopToast(context, message: l10n.depositAmountPositive);
- return false;
- }
- return true;
- }
- Future<void> _onCreateOrder() async {
- final l10n = AppLocalizations.of(context)!;
- if (!_validateAmount(l10n)) {
- return;
- }
- await ref.read(depositProvider.notifier).createRechargeOrder(_amountCtrl.text);
- final err = ref.read(depositProvider).errorMessage;
- if (!mounted) {
- return;
- }
- if (err != null) {
- showTopToast(context, message: err);
- } else {
- _hashCtrl.clear();
- showTopToast(context, message: l10n.depositOrderCreated);
- }
- }
- Future<void> _onSubmitHash() async {
- final l10n = AppLocalizations.of(context)!;
- final h = _hashCtrl.text.trim();
- if (h.isEmpty) {
- showTopToast(context, message: l10n.depositTxHashPlaceholder);
- return;
- }
- await ref.read(depositProvider.notifier).submitTxHash(h);
- final err = ref.read(depositProvider).errorMessage;
- if (!mounted) {
- return;
- }
- if (err != null) {
- showTopToast(context, message: err);
- } else {
- showTopToast(context, message: l10n.confirm);
- }
- }
- Future<void> _onWalletPay(
- RechargeFlatNetworkOption net,
- RechargeOrderDetail order,
- ) async {
- final l10n = AppLocalizations.of(context)!;
- if (AppConfig.walletConnectProjectId.isEmpty) {
- showTopToast(context, message: l10n.depositWalletConnectNotConfigured);
- return;
- }
- ref.read(depositProvider.notifier).setWalletPayBusy(true);
- try {
- await _ensureAppKitModal();
- final modal = _appKitModal;
- if (modal == null) {
- return;
- }
- final hash = _isTronLike(net)
- ? await WalletConnectTronRecharge.connectAndPayTron(
- appKitModal: modal,
- network: net,
- order: order,
- )
- : await WalletConnectRechargeHelper.connectAndPay(
- appKitModal: modal,
- network: net,
- order: order,
- );
- if (!mounted) {
- return;
- }
- _hashCtrl.text = hash;
- await ref.read(depositProvider.notifier).submitTxHash(hash);
- final err = ref.read(depositProvider).errorMessage;
- if (!mounted) {
- return;
- }
- if (err != null) {
- showTopToast(context, message: err);
- } else {
- showTopToast(context, message: l10n.confirm);
- }
- } catch (e) {
- if (mounted) {
- showTopToast(context, message: e.toString());
- }
- } finally {
- ref.read(depositProvider.notifier).setWalletPayBusy(false);
- }
- }
- String _statusLabel(AppLocalizations l10n, int status) {
- switch (status) {
- case 0:
- return l10n.rechargeStatus0;
- case 1:
- return l10n.rechargeStatus1;
- case 2:
- return l10n.rechargeStatus2;
- case 3:
- return l10n.rechargeStatus3;
- case 4:
- return l10n.rechargeStatus4;
- default:
- return '$status';
- }
- }
- bool _canWalletConnect(
- RechargeFlatNetworkOption? net,
- RechargeOrderDetail? order,
- ) {
- if (net == null || order == null || !order.isPendingPayment) {
- return false;
- }
- if (resolveEvmChainId(net.protocol, net.networkName) != null) {
- return true;
- }
- return isTronDepositNetwork(net.protocol, net.networkName);
- }
- bool _isTronLike(RechargeFlatNetworkOption? net) {
- if (net == null) {
- return false;
- }
- if (resolveEvmChainId(net.protocol, net.networkName) != null) {
- return false;
- }
- return isTronDepositNetwork(net.protocol, net.networkName);
- }
- String _networkProtocolLabel(RechargeFlatNetworkOption net) {
- final protocol = net.protocol.trim();
- final name = net.networkName.trim();
- if (protocol.isEmpty) {
- return name;
- }
- if (name.isEmpty) {
- return protocol;
- }
- return '$protocol·$name';
- }
- String _subCoinLabel(RechargeFlatNetworkOption net) {
- final cn = net.coinNameCn.trim();
- if (cn.isNotEmpty && cn != net.coinName) {
- return '${net.coinName} ($cn)';
- }
- return net.coinName;
- }
- String _walletPayButtonLabel(
- AppLocalizations l10n,
- RechargeFlatNetworkOption? net,
- ) {
- if (net != null && resolveEvmChainId(net.protocol, net.networkName) != null) {
- return '${l10n.depositWalletConnectPay}(EVM)';
- }
- return l10n.depositWalletConnectPay;
- }
- BoxDecoration _panelDecoration(ColorScheme cs, bool isDark) {
- return BoxDecoration(
- color: isDark ? AppColors.darkBgSecondary : AppColors.lightBgSecondary,
- borderRadius: BorderRadius.circular(12),
- border: Border.all(color: cs.outline.withAlpha(40)),
- );
- }
- Widget _buildModeTabSwitcher(
- ColorScheme cs,
- AppLocalizations l10n,
- bool isDark,
- ) {
- return Container(
- height: 44,
- padding: const EdgeInsets.all(4),
- decoration: BoxDecoration(
- color: isDark ? AppColors.darkBgSecondary : AppColors.lightBgSecondary,
- borderRadius: BorderRadius.circular(10),
- border: Border.all(color: cs.outline.withAlpha(50)),
- ),
- child: TabBar(
- controller: _depositModeTab,
- indicator: BoxDecoration(
- color: AppColors.brand,
- borderRadius: BorderRadius.circular(8),
- ),
- indicatorSize: TabBarIndicatorSize.tab,
- dividerColor: Colors.transparent,
- labelColor: Colors.black,
- unselectedLabelColor: cs.onSurface.withAlpha(180),
- labelStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
- unselectedLabelStyle:
- const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
- tabs: [
- Tab(text: l10n.depositTabManual),
- Tab(text: l10n.depositTabOnChain),
- ],
- ),
- );
- }
- void _resetToNewRecharge(DepositNotifier notifier) {
- notifier.clearCurrentOrder();
- _amountCtrl.clear();
- _hashCtrl.clear();
- }
- @override
- Widget build(BuildContext context) {
- final cs = Theme.of(context).colorScheme;
- final l10n = AppLocalizations.of(context)!;
- final state = ref.watch(depositProvider);
- final notifier = ref.read(depositProvider.notifier);
- final isDark = Theme.of(context).brightness == Brightness.dark;
- final wcConfigured = AppConfig.walletConnectProjectId.isNotEmpty;
- final hasOrder = state.currentOrder != null;
- return ReownAppKitModalTheme(
- isDarkMode: isDark,
- child: Scaffold(
- appBar: AppBar(
- leading: IconButton(
- icon: const Icon(Icons.chevron_left, size: 28),
- onPressed: () => context.pop(),
- ),
- title: Text(
- hasOrder ? l10n.depositOrderInfo : l10n.depositCoin,
- style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
- ),
- centerTitle: true,
- actions: [
- TextButton(
- onPressed: hasOrder
- ? () => _resetToNewRecharge(notifier)
- : () => context.push('/asset/deposit/history'),
- child: Text(
- hasOrder ? l10n.depositNewRecharge : l10n.depositRecord,
- style: TextStyle(
- color: hasOrder ? AppColors.brand : cs.onSurface,
- fontSize: 14,
- fontWeight: FontWeight.w500,
- ),
- ),
- ),
- ],
- ),
- body: _buildBody(context, state, notifier, cs, l10n, isDark,
- wcConfigured: wcConfigured),
- ),
- );
- }
- Widget _buildBody(
- BuildContext context,
- DepositState state,
- DepositNotifier notifier,
- ColorScheme cs,
- AppLocalizations l10n,
- bool isDark, {
- required bool wcConfigured,
- }) {
- if (state.loadingParents) {
- return const Center(child: CircularProgressIndicator());
- }
- if (state.errorMessage != null &&
- state.parentCoins.isEmpty &&
- !state.loadingParents) {
- return Center(
- child: Padding(
- padding: const EdgeInsets.all(24),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Text(state.errorMessage!, textAlign: TextAlign.center),
- const SizedBox(height: 16),
- FilledButton(
- onPressed: () => notifier.refresh(),
- child: Text(l10n.retry),
- ),
- ],
- ),
- ),
- );
- }
- final order = state.currentOrder;
- return SingleChildScrollView(
- padding: const EdgeInsets.fromLTRB(16, 16, 16, 32),
- child: order == null
- ? _buildOrderForm(context, state, notifier, cs, l10n, isDark)
- : _buildOrderDetail(
- context,
- state,
- notifier,
- cs,
- l10n,
- isDark,
- order,
- wcConfigured: wcConfigured,
- ),
- );
- }
- Widget _buildOrderForm(
- BuildContext context,
- DepositState state,
- DepositNotifier notifier,
- ColorScheme cs,
- AppLocalizations l10n,
- bool isDark,
- ) {
- final net = state.selectedNetwork;
- final flatIndex = state.flatNetworks.isEmpty
- ? null
- : state.selectedFlatIndex.clamp(0, state.flatNetworks.length - 1);
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- _buildModeTabSwitcher(cs, l10n, isDark),
- const SizedBox(height: 20),
- Text(
- l10n.depositMainnetProtocol,
- style: TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 13),
- ),
- const SizedBox(height: 8),
- Container(
- padding: const EdgeInsets.symmetric(horizontal: 12),
- decoration: _panelDecoration(cs, isDark),
- child: DropdownButtonHideUnderline(
- child: DropdownButton<int>(
- isExpanded: true,
- value: flatIndex,
- hint: Text(
- state.childrenLoading
- ? l10n.loading
- : l10n.depositSelectNetworkFirst,
- style: TextStyle(color: cs.onSurface.withAlpha(140)),
- ),
- icon: Icon(Icons.keyboard_arrow_down,
- color: cs.onSurface.withAlpha(153)),
- items: [
- for (var i = 0; i < state.flatNetworks.length; i++)
- DropdownMenuItem(
- value: i,
- child: Text(
- _networkProtocolLabel(state.flatNetworks[i]),
- style: TextStyle(color: cs.onSurface, fontSize: 15),
- ),
- ),
- ],
- onChanged: state.childrenLoading
- ? null
- : (v) {
- if (v != null) {
- notifier.selectFlatNetwork(v);
- }
- },
- ),
- ),
- ),
- if (state.parentCoins.length > 1) ...[
- const SizedBox(height: 12),
- Container(
- padding: const EdgeInsets.symmetric(horizontal: 12),
- decoration: _panelDecoration(cs, isDark),
- child: DropdownButtonHideUnderline(
- child: DropdownButton<int>(
- isExpanded: true,
- value: state.parentCoins.isEmpty
- ? null
- : state.selectedParentIndex.clamp(
- 0, state.parentCoins.length - 1),
- items: [
- for (var i = 0; i < state.parentCoins.length; i++)
- DropdownMenuItem(
- value: i,
- child: Text(
- state.parentCoins[i].nameCn.isNotEmpty
- ? state.parentCoins[i].nameCn
- : state.parentCoins[i].name,
- style: TextStyle(color: cs.onSurface, fontSize: 15),
- ),
- ),
- ],
- onChanged: state.childrenLoading
- ? null
- : (v) {
- if (v != null) {
- notifier.selectParent(v);
- }
- },
- ),
- ),
- ),
- ],
- if (state.childrenLoading) ...[
- const SizedBox(height: 12),
- const LinearProgressIndicator(),
- ],
- if (net != null) ...[
- const SizedBox(height: 12),
- Container(
- width: double.infinity,
- padding: const EdgeInsets.all(14),
- decoration: _panelDecoration(cs, isDark),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- l10n.depositSubCoin,
- style:
- TextStyle(color: cs.onSurface.withAlpha(140), fontSize: 12),
- ),
- const SizedBox(height: 4),
- Text(
- _subCoinLabel(net),
- style: TextStyle(color: cs.onSurface, fontSize: 14),
- ),
- if (net.hasTokenContract) ...[
- const SizedBox(height: 12),
- Text(
- l10n.depositContractAddress,
- style: TextStyle(
- color: cs.onSurface.withAlpha(140), fontSize: 12),
- ),
- const SizedBox(height: 4),
- SelectableText(
- net.contractAddress,
- style: TextStyle(
- color: cs.onSurface,
- fontSize: 12,
- fontFamily: 'monospace',
- ),
- ),
- ],
- ],
- ),
- ),
- ],
- const SizedBox(height: 20),
- Text(
- l10n.depositEnterAmount,
- style: TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 13),
- ),
- const SizedBox(height: 8),
- TextField(
- controller: _amountCtrl,
- keyboardType: const TextInputType.numberWithOptions(decimal: true),
- decoration: InputDecoration(
- filled: true,
- fillColor:
- isDark ? AppColors.darkBgSecondary : AppColors.lightBgSecondary,
- border: OutlineInputBorder(
- borderRadius: BorderRadius.circular(10),
- borderSide: BorderSide(color: cs.outline.withAlpha(50)),
- ),
- enabledBorder: OutlineInputBorder(
- borderRadius: BorderRadius.circular(10),
- borderSide: BorderSide(color: cs.outline.withAlpha(50)),
- ),
- hintText: l10n.depositEnterAmount,
- ),
- ),
- const SizedBox(height: 20),
- SizedBox(
- width: double.infinity,
- height: 48,
- child: FilledButton(
- style: FilledButton.styleFrom(
- backgroundColor: AppColors.brand,
- foregroundColor: Colors.black,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(10),
- ),
- ),
- onPressed: state.orderSubmitting || state.flatNetworks.isEmpty
- ? null
- : _onCreateOrder,
- child: state.orderSubmitting
- ? const SizedBox(
- height: 20,
- width: 20,
- child: CircularProgressIndicator(
- strokeWidth: 2,
- color: Colors.black,
- ),
- )
- : Text(
- l10n.depositCreateOrder,
- style: const TextStyle(fontWeight: FontWeight.w600),
- ),
- ),
- ),
- const SizedBox(height: 20),
- _buildRulesCard(context, cs, l10n, isDark, net?.coinName ?? ''),
- ],
- );
- }
- Widget _buildRulesCard(
- BuildContext context,
- ColorScheme cs,
- AppLocalizations l10n,
- bool isDark,
- String currentCoin,
- ) {
- return Container(
- width: double.infinity,
- padding: const EdgeInsets.all(14),
- decoration: _panelDecoration(cs, isDark),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- l10n.depositRules,
- style: TextStyle(
- color: cs.onSurface.withAlpha(153),
- fontSize: 13,
- fontWeight: FontWeight.w600,
- ),
- ),
- const SizedBox(height: 8),
- Text(
- l10n.depositRulesBody,
- style: TextStyle(
- color: cs.onSurface.withAlpha(140),
- fontSize: 12,
- height: 1.45,
- ),
- ),
- if (currentCoin.isNotEmpty) ...[
- const SizedBox(height: 10),
- Text(
- l10n.depositCurrentCoin(currentCoin),
- style: TextStyle(color: cs.onSurface.withAlpha(120), fontSize: 12),
- ),
- ],
- ],
- ),
- );
- }
- Widget _buildOrderDetail(
- BuildContext context,
- DepositState state,
- DepositNotifier notifier,
- ColorScheme cs,
- AppLocalizations l10n,
- bool isDark,
- RechargeOrderDetail order, {
- required bool wcConfigured,
- }) {
- final addr = order.rechargeAddress;
- final net = state.selectedNetwork;
- final canWc = _canWalletConnect(net, order);
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Container(
- width: double.infinity,
- padding: const EdgeInsets.all(14),
- decoration: _panelDecoration(cs, isDark),
- child: Column(
- children: [
- _orderMetaRow(
- context,
- l10n.depositOrderNo,
- order.orderNo,
- cs,
- copyable: true,
- ),
- _orderMetaRow(
- context,
- l10n.depositOrderStatus,
- _statusLabel(l10n, order.status),
- cs,
- valueColor: order.isPendingPayment ? AppColors.brand : null,
- ),
- _orderMetaRow(context, l10n.depositCurrency, order.coinName, cs),
- _orderMetaRow(context, l10n.depositNetwork, order.networkName, cs),
- _orderMetaRow(
- context, l10n.depositOrderAmount, order.amount, cs),
- ],
- ),
- ),
- if (order.isPendingPayment) ...[
- const SizedBox(height: 16),
- _buildModeTabSwitcher(cs, l10n, isDark),
- const SizedBox(height: 16),
- AnimatedBuilder(
- animation: _depositModeTab,
- builder: (context, _) {
- if (_depositModeTab.index == _tabManual) {
- return _buildManualContent(
- context,
- state,
- cs,
- l10n,
- isDark,
- addr,
- net,
- order: order,
- );
- } else if (_depositModeTab.index == _tabOnChain) {
- return _buildOnChainContent(
- context,
- state,
- cs,
- l10n,
- isDark,
- addr,
- net,
- canWc,
- wcConfigured: wcConfigured,
- order: order,
- );
- } else {
- return const SizedBox.shrink();
- }
- },
- ),
- ] else ...[
- const SizedBox(height: 16),
- _buildManualContent(
- context,
- state,
- cs,
- l10n,
- isDark,
- addr,
- net,
- order: order,
- readOnly: true,
- ),
- ],
- ],
- );
- }
- Widget _orderMetaRow(
- BuildContext context,
- String label,
- String value,
- ColorScheme cs, {
- Color? valueColor,
- bool copyable = false,
- }) {
- final l10n = AppLocalizations.of(context)!;
- return Padding(
- padding: const EdgeInsets.only(bottom: 10),
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- SizedBox(
- width: 72,
- child: Text(
- label,
- style: TextStyle(color: cs.onSurface.withAlpha(140), fontSize: 13),
- ),
- ),
- Expanded(
- child: Text(
- value,
- style: TextStyle(
- color: valueColor ?? cs.onSurface,
- fontSize: 13,
- fontWeight:
- valueColor != null ? FontWeight.w600 : FontWeight.w400,
- ),
- ),
- ),
- if (copyable && value.isNotEmpty)
- TextButton(
- onPressed: () {
- Clipboard.setData(ClipboardData(text: value));
- showTopToast(context, message: l10n.copied);
- },
- style: TextButton.styleFrom(
- padding: const EdgeInsets.symmetric(horizontal: 8),
- minimumSize: Size.zero,
- tapTargetSize: MaterialTapTargetSize.shrinkWrap,
- ),
- child: Text(
- l10n.depositCopy,
- style: const TextStyle(
- color: AppColors.brand,
- fontSize: 12,
- fontWeight: FontWeight.w600,
- ),
- ),
- ),
- ],
- ),
- );
- }
- Widget _buildAddressBlock(
- BuildContext context,
- ColorScheme cs,
- AppLocalizations l10n,
- bool isDark,
- String addr, {
- bool showQr = false,
- }) {
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- l10n.depositReceivingAddress,
- style: TextStyle(
- color: cs.onSurface.withAlpha(153),
- fontSize: 13,
- fontWeight: FontWeight.w600,
- ),
- ),
- const SizedBox(height: 6),
- Text(
- l10n.depositPayToHint,
- style: TextStyle(color: cs.onSurface.withAlpha(120), fontSize: 12),
- ),
- const SizedBox(height: 10),
- Container(
- width: double.infinity,
- padding: const EdgeInsets.all(12),
- decoration: _panelDecoration(cs, isDark),
- child: SelectableText(
- addr,
- style: TextStyle(color: cs.onSurface, fontSize: 13),
- ),
- ),
- if (showQr && addr.isNotEmpty) ...[
- const SizedBox(height: 16),
- Text(
- l10n.depositQrReceive,
- style: TextStyle(
- color: cs.onSurface.withAlpha(153),
- fontSize: 13,
- fontWeight: FontWeight.w600,
- ),
- ),
- const SizedBox(height: 10),
- Center(
- child: RepaintBoundary(
- key: _qrKey,
- child: Container(
- width: 200,
- height: 200,
- decoration: BoxDecoration(
- color: Colors.white,
- borderRadius: BorderRadius.circular(12),
- ),
- child: QrImageView(
- data: addr,
- version: QrVersions.auto,
- size: 180,
- backgroundColor: Colors.white,
- ),
- ),
- ),
- ),
- const SizedBox(height: 8),
- Text(
- l10n.depositQrHint,
- textAlign: TextAlign.center,
- style: TextStyle(color: cs.onSurface.withAlpha(120), fontSize: 12),
- ),
- const SizedBox(height: 8),
- Center(
- child: TextButton.icon(
- onPressed:
- addr.isNotEmpty && !_savingQr ? () => _saveQr(addr) : null,
- icon: _savingQr
- ? const SizedBox(
- width: 16,
- height: 16,
- child: CircularProgressIndicator(strokeWidth: 2),
- )
- : Icon(Icons.download_outlined,
- size: 16, color: cs.onSurface.withAlpha(153)),
- label: Text(
- l10n.saveQrCode,
- style: TextStyle(
- color: cs.onSurface.withAlpha(153), fontSize: 13),
- ),
- ),
- ),
- ],
- ],
- );
- }
- Widget _buildContractBlock(
- ColorScheme cs,
- AppLocalizations l10n,
- bool isDark,
- RechargeFlatNetworkOption? net,
- ) {
- if (net == null || !net.hasTokenContract) {
- return const SizedBox.shrink();
- }
- return Padding(
- padding: const EdgeInsets.only(top: 12),
- child: Container(
- width: double.infinity,
- padding: const EdgeInsets.all(12),
- decoration: _panelDecoration(cs, isDark),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- l10n.depositContractAddress,
- style: TextStyle(color: cs.onSurface.withAlpha(140), fontSize: 12),
- ),
- const SizedBox(height: 6),
- SelectableText(
- net.contractAddress,
- style: TextStyle(
- color: cs.onSurface,
- fontSize: 12,
- fontFamily: 'monospace',
- ),
- ),
- ],
- ),
- ),
- );
- }
- Widget _buildTxHashSection(
- BuildContext context,
- DepositState state,
- ColorScheme cs,
- AppLocalizations l10n,
- bool isDark, {
- bool enabled = true,
- }) {
- if (!enabled) {
- return const SizedBox.shrink();
- }
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- const SizedBox(height: 20),
- Text(
- l10n.depositSubmitHashHint,
- style: TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 13),
- ),
- const SizedBox(height: 8),
- TextField(
- controller: _hashCtrl,
- decoration: InputDecoration(
- filled: true,
- fillColor: isDark
- ? AppColors.darkBgSecondary
- : AppColors.lightBgSecondary,
- hintText: l10n.depositTxHashPlaceholder,
- border: OutlineInputBorder(
- borderRadius: BorderRadius.circular(10),
- borderSide: BorderSide(color: cs.outline.withAlpha(50)),
- ),
- enabledBorder: OutlineInputBorder(
- borderRadius: BorderRadius.circular(10),
- borderSide: BorderSide(color: cs.outline.withAlpha(50)),
- ),
- ),
- ),
- const SizedBox(height: 12),
- SizedBox(
- width: double.infinity,
- height: 48,
- child: FilledButton(
- style: FilledButton.styleFrom(
- backgroundColor: AppColors.brand,
- foregroundColor: Colors.black,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(10),
- ),
- ),
- onPressed: state.hashSubmitting || state.walletPayBusy
- ? null
- : _onSubmitHash,
- child: state.hashSubmitting
- ? const SizedBox(
- height: 20,
- width: 20,
- child: CircularProgressIndicator(
- strokeWidth: 2,
- color: Colors.black,
- ),
- )
- : Text(
- l10n.depositSubmitHash,
- style: const TextStyle(fontWeight: FontWeight.w600),
- ),
- ),
- ),
- ],
- );
- }
- Widget _buildOnChainContent(
- BuildContext context,
- DepositState state,
- ColorScheme cs,
- AppLocalizations l10n,
- bool isDark,
- String addr,
- RechargeFlatNetworkOption? net,
- bool canWc, {
- required bool wcConfigured,
- required RechargeOrderDetail order,
- }) {
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- if (addr.isNotEmpty) ...[
- _buildAddressBlock(context, cs, l10n, isDark, addr),
- ],
- if (order.isPendingPayment) ...[
- const SizedBox(height: 16),
- Container(
- width: double.infinity,
- padding: const EdgeInsets.all(14),
- decoration: _panelDecoration(cs, isDark),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- if (canWc && wcConfigured)
- Text(
- l10n.depositWalletConnectHint,
- style: TextStyle(
- color: cs.onSurface.withAlpha(153),
- fontSize: 12,
- height: 1.4,
- ),
- )
- else if (canWc && !wcConfigured)
- Text(
- l10n.depositWalletConnectNotConfigured,
- style: TextStyle(
- color: cs.onSurface.withAlpha(120),
- fontSize: 12,
- height: 1.4,
- ),
- )
- else if (_isTronLike(net))
- Text(
- l10n.depositTronHint,
- style: TextStyle(
- color: cs.onSurface.withAlpha(153),
- fontSize: 12,
- height: 1.4,
- ),
- ),
- if (canWc && wcConfigured) ...[
- const SizedBox(height: 12),
- SizedBox(
- width: double.infinity,
- height: 44,
- child: OutlinedButton(
- style: OutlinedButton.styleFrom(
- foregroundColor: AppColors.brand,
- side: const BorderSide(color: AppColors.brand),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(10),
- ),
- ),
- onPressed: (state.hashSubmitting ||
- state.walletPayBusy ||
- net == null)
- ? null
- : () => _onWalletPay(net, order),
- child: state.walletPayBusy
- ? const SizedBox(
- height: 20,
- width: 20,
- child: CircularProgressIndicator(
- strokeWidth: 2,
- color: AppColors.brand,
- ),
- )
- : Text(_walletPayButtonLabel(l10n, net)),
- ),
- ),
- ],
- ],
- ),
- ),
- _buildTxHashSection(context, state, cs, l10n, isDark),
- ] else if (order.txHash != null && order.txHash!.isNotEmpty) ...[
- const SizedBox(height: 12),
- Text(
- '${l10n.depositTxHashPlaceholder}: ${order.txHash}',
- style: TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 12),
- ),
- ],
- ],
- );
- }
- Widget _buildManualContent(
- BuildContext context,
- DepositState state,
- ColorScheme cs,
- AppLocalizations l10n,
- bool isDark,
- String addr,
- RechargeFlatNetworkOption? net, {
- required RechargeOrderDetail order,
- bool readOnly = false,
- }) {
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- if (addr.isNotEmpty) ...[
- _buildAddressBlock(
- context,
- cs,
- l10n,
- isDark,
- addr,
- showQr: true,
- ),
- ],
- _buildContractBlock(cs, l10n, isDark, net),
- if (order.isPendingPayment && !readOnly)
- _buildTxHashSection(context, state, cs, l10n, isDark)
- else if (order.txHash != null && order.txHash!.isNotEmpty) ...[
- const SizedBox(height: 12),
- Text(
- '${l10n.depositTxHashPlaceholder}: ${order.txHash}',
- style: TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 12),
- ),
- ],
- ],
- );
- }
- }
|