Files
sendico/frontend/pweb/lib/pages/payment_methods/add/crypto_address.dart

195 lines
6.5 KiB
Dart

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<CryptoAddressForm> createState() => _CryptoAddressFormState();
}
class _CryptoAddressFormState extends State<CryptoAddressForm> {
final _formKey = GlobalKey<FormState>();
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<ChainNetwork>(
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();
}
}