|
|
|
|
@@ -1,9 +1,14 @@
|
|
|
|
|
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;
|
|
|
|
|
@@ -22,28 +27,67 @@ class CryptoAddressForm extends StatefulWidget {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _CryptoAddressFormState extends State<CryptoAddressForm> {
|
|
|
|
|
final _formKey = GlobalKey<FormState>();
|
|
|
|
|
|
|
|
|
|
late TextEditingController _addressCtrl;
|
|
|
|
|
late TextEditingController _networkCtrl;
|
|
|
|
|
late TextEditingController _destinationTagCtrl;
|
|
|
|
|
late TextEditingController _tokenCtrl;
|
|
|
|
|
late TextEditingController _contractCtrl;
|
|
|
|
|
late TextEditingController _memoCtrl;
|
|
|
|
|
late ChainNetwork _chain;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
|
|
|
|
_addressCtrl = TextEditingController(text: widget.initialData?.address);
|
|
|
|
|
_networkCtrl = TextEditingController(text: widget.initialData?.network);
|
|
|
|
|
_destinationTagCtrl = TextEditingController(text: widget.initialData?.destinationTag);
|
|
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _emit() {
|
|
|
|
|
if (_addressCtrl.text.isNotEmpty && _networkCtrl.text.isNotEmpty) {
|
|
|
|
|
widget.onChanged(
|
|
|
|
|
CryptoAddressPaymentMethod(
|
|
|
|
|
address: _addressCtrl.text,
|
|
|
|
|
network: _networkCtrl.text,
|
|
|
|
|
destinationTag: _destinationTagCtrl.text.isNotEmpty ? _destinationTagCtrl.text : null,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
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
|
|
|
|
|
@@ -54,48 +98,88 @@ class _CryptoAddressFormState extends State<CryptoAddressForm> {
|
|
|
|
|
|
|
|
|
|
if (newData == null && oldData != null) {
|
|
|
|
|
_addressCtrl.clear();
|
|
|
|
|
_networkCtrl.clear();
|
|
|
|
|
_destinationTagCtrl.clear();
|
|
|
|
|
_tokenCtrl.clear();
|
|
|
|
|
_contractCtrl.clear();
|
|
|
|
|
_memoCtrl.clear();
|
|
|
|
|
_chain = ChainNetwork.unspecified;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (newData != null && newData != oldData) {
|
|
|
|
|
_addressCtrl.text = newData.address;
|
|
|
|
|
_networkCtrl.text = newData.network;
|
|
|
|
|
_destinationTagCtrl.text = newData.destinationTag ?? '';
|
|
|
|
|
_tokenCtrl.text = newData.asset?.tokenSymbol ?? '';
|
|
|
|
|
_contractCtrl.text = newData.asset?.contractAddress ?? '';
|
|
|
|
|
_memoCtrl.text = newData.memo ?? '';
|
|
|
|
|
_chain = newData.asset?.chain ?? ChainNetwork.unspecified;
|
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) => _emitIfValid());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
return Column(
|
|
|
|
|
children: [
|
|
|
|
|
TextFormField(
|
|
|
|
|
readOnly: !widget.isEditable,
|
|
|
|
|
controller: _addressCtrl,
|
|
|
|
|
decoration: getInputDecoration(context, 'Crypto address', widget.isEditable),
|
|
|
|
|
style: getTextFieldStyle(context, widget.isEditable),
|
|
|
|
|
onChanged: (_) => _emit(),
|
|
|
|
|
validator: (val) => (val?.isEmpty ?? true) ? 'Enter crypto address' : null,
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
|
TextFormField(
|
|
|
|
|
readOnly: !widget.isEditable,
|
|
|
|
|
controller: _networkCtrl,
|
|
|
|
|
decoration: getInputDecoration(context, 'Network', widget.isEditable),
|
|
|
|
|
style: getTextFieldStyle(context, widget.isEditable),
|
|
|
|
|
onChanged: (_) => _emit(),
|
|
|
|
|
validator: (val) => (val?.isEmpty ?? true) ? 'Enter network' : null,
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
|
TextFormField(
|
|
|
|
|
readOnly: !widget.isEditable,
|
|
|
|
|
controller: _destinationTagCtrl,
|
|
|
|
|
decoration: getInputDecoration(context, 'Destination tag / memo (optional)', widget.isEditable),
|
|
|
|
|
style: getTextFieldStyle(context, widget.isEditable),
|
|
|
|
|
onChanged: (_) => _emit(),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|