import 'package:flutter/material.dart'; import 'package:pshared/models/payment/asset.dart'; import 'package:pshared/models/payment/chain_network.dart'; import 'package:pshared/models/payment/methods/crypto_address.dart'; import 'package:pshared/utils/l10n/chain.dart'; import 'package:pweb/utils/text_field_styles.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; class CryptoAddressForm extends StatefulWidget { final void Function(CryptoAddressPaymentMethod) onChanged; final CryptoAddressPaymentMethod? initialData; final bool isEditable; const CryptoAddressForm({ super.key, required this.onChanged, this.initialData, required this.isEditable, }); @override State createState() => _CryptoAddressFormState(); } class _CryptoAddressFormState extends State { final _formKey = GlobalKey(); late TextEditingController _addressCtrl; late TextEditingController _tokenCtrl; late TextEditingController _contractCtrl; late TextEditingController _memoCtrl; late ChainNetwork _chain; @override void initState() { super.initState(); final initial = widget.initialData; _chain = initial?.asset?.chain ?? ChainNetwork.unspecified; _addressCtrl = TextEditingController(text: initial?.address ?? ''); _tokenCtrl = TextEditingController(text: initial?.asset?.tokenSymbol ?? ''); _contractCtrl = TextEditingController(text: initial?.asset?.contractAddress ?? ''); _memoCtrl = TextEditingController(text: initial?.memo ?? ''); WidgetsBinding.instance.addPostFrameCallback((_) => _emitIfValid()); } bool get _hasChainSelection => _chain != ChainNetwork.unspecified; String? _validateAddress(AppLocalizations l10n, String? value) { if (value == null || value.trim().isEmpty) return l10n.enterCryptoAddress; return null; } String? _validateToken(AppLocalizations l10n) { final token = _tokenCtrl.text.trim(); final contract = _contractCtrl.text.trim(); if ((_hasChainSelection || contract.isNotEmpty) && token.isEmpty) { return l10n.tokenSymbolRequiredWhenNetwork; } return null; } PaymentAsset? _buildAsset() { final token = _tokenCtrl.text.trim(); final contract = _contractCtrl.text.trim(); if (token.isEmpty && contract.isEmpty && !_hasChainSelection) return null; if (token.isEmpty) return null; return PaymentAsset( chain: _chain, tokenSymbol: token, contractAddress: contract.isNotEmpty ? contract : null, ); } void _emitIfValid() { if (!(_formKey.currentState?.validate() ?? false)) return; widget.onChanged( CryptoAddressPaymentMethod( asset: _buildAsset(), address: _addressCtrl.text.trim(), memo: _memoCtrl.text.trim().isNotEmpty ? _memoCtrl.text.trim() : null, ), ); } @override void didUpdateWidget(covariant CryptoAddressForm oldWidget) { super.didUpdateWidget(oldWidget); final newData = widget.initialData; final oldData = oldWidget.initialData; if (newData == null && oldData != null) { _addressCtrl.clear(); _tokenCtrl.clear(); _contractCtrl.clear(); _memoCtrl.clear(); _chain = ChainNetwork.unspecified; return; } if (newData != null && newData != oldData) { final hasAddressChange = newData.address != _addressCtrl.text; final hasTokenChange = newData.asset?.tokenSymbol != _tokenCtrl.text; final hasContractChange = newData.asset?.contractAddress != _contractCtrl.text; final hasMemoChange = newData.memo != _memoCtrl.text; final hasChainChange = newData.asset?.chain != _chain; if (hasAddressChange) _addressCtrl.text = newData.address; if (hasTokenChange) _tokenCtrl.text = newData.asset?.tokenSymbol ?? ''; if (hasContractChange) _contractCtrl.text = newData.asset?.contractAddress ?? ''; if (hasMemoChange) _memoCtrl.text = newData.memo ?? ''; if (hasChainChange) _chain = newData.asset?.chain ?? ChainNetwork.unspecified; if (hasAddressChange || hasTokenChange || hasContractChange || hasMemoChange || hasChainChange) { WidgetsBinding.instance.addPostFrameCallback((_) => _emitIfValid()); } } } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return Form( key: _formKey, onChanged: _emitIfValid, child: Column( mainAxisSize: MainAxisSize.min, spacing: 12.0, children: [ TextFormField( readOnly: !widget.isEditable, controller: _addressCtrl, decoration: getInputDecoration(context, l10n.cryptoAddressLabel, widget.isEditable), style: getTextFieldStyle(context, widget.isEditable), validator: (val) => _validateAddress(l10n, val), ), DropdownButtonFormField( initialValue: _chain, decoration: getInputDecoration(context, l10n.walletTopUpNetworkLabel, widget.isEditable), items: ChainNetwork.values .map((chain) => DropdownMenuItem( value: chain, child: Text(chain.localizedName(context)), )) .toList(), onChanged: widget.isEditable ? (value) { if (value == null) return; setState(() => _chain = value); _emitIfValid(); } : null, ), TextFormField( readOnly: !widget.isEditable, controller: _tokenCtrl, decoration: getInputDecoration(context, l10n.tokenSymbolLabel, widget.isEditable), style: getTextFieldStyle(context, widget.isEditable), validator: (_) => _validateToken(l10n), ), TextFormField( readOnly: !widget.isEditable, controller: _contractCtrl, decoration: getInputDecoration(context, l10n.contractAddressLabel, widget.isEditable), style: getTextFieldStyle(context, widget.isEditable), ), TextFormField( readOnly: !widget.isEditable, controller: _memoCtrl, decoration: getInputDecoration(context, l10n.memoLabel, widget.isEditable), style: getTextFieldStyle(context, widget.isEditable), ), ], ), ); } @override void dispose() { _addressCtrl.dispose(); _tokenCtrl.dispose(); _contractCtrl.dispose(); _memoCtrl.dispose(); super.dispose(); } }