mirror of https://github.com/CympleTech/ESSE.git
409 lines
13 KiB
409 lines
13 KiB
import 'package:flutter/material.dart'; |
|
import 'package:provider/provider.dart'; |
|
|
|
import 'package:esse/l10n/localizations.dart'; |
|
import 'package:esse/widgets/show_pin.dart'; |
|
import 'package:esse/widgets/button_text.dart'; |
|
import 'package:esse/widgets/input_text.dart'; |
|
import 'package:esse/widgets/shadow_dialog.dart'; |
|
import 'package:esse/apps/wallet/models.dart'; |
|
import 'package:esse/provider.dart'; |
|
import 'package:esse/rpc.dart'; |
|
|
|
class Transfer extends StatefulWidget { |
|
final Function callback; |
|
final String to; |
|
const Transfer({Key? key, required this.callback, required this.to}) : super(key: key); |
|
|
|
@override |
|
_TransferState createState() => _TransferState(); |
|
} |
|
|
|
class _TransferState extends State<Transfer> { |
|
Network _selectedNetwork = Network.EthMain; |
|
Color _networkColor = Colors.green; |
|
List<Network> _networks = []; |
|
|
|
Address? _selectedAddress; |
|
List<Address> _addresses = []; |
|
|
|
Token _selectedToken = Token.eth(Network.EthMain); |
|
Token _mainToken = Token.eth(Network.EthMain); |
|
List<Token> _tokens = []; |
|
List<String> _nft = []; |
|
String _selectedNft = ''; |
|
|
|
TextEditingController _amountController = TextEditingController(); |
|
FocusNode _amountFocus = FocusNode(); |
|
|
|
bool _checked = false; |
|
bool _checking = false; |
|
String _price = ''; |
|
String _gas = '0'; |
|
String _networkError = ''; |
|
|
|
Map<ChainToken, String> _mains = {}; |
|
String _main = ''; |
|
ChainToken _mainChain = ChainToken.ETH; |
|
|
|
@override |
|
initState() { |
|
rpc.addListener('wallet-token', _walletToken); |
|
rpc.addListener('wallet-balance', _walletBalance); |
|
_amountController.addListener(() { |
|
setState(() { |
|
this._checked = false; |
|
this._checking = false; |
|
}); |
|
}); |
|
super.initState(); |
|
_loadWallet(); |
|
_split_address(widget.to); |
|
} |
|
|
|
_split_address(String s) { |
|
print(s); |
|
if (s.length > 0) { |
|
s.split(",").forEach((ss) { |
|
final sss = ss.split(":"); |
|
this._mains[ChainTokenExtension.fromInt(int.parse(sss[0]))] = sss[1]; |
|
}); |
|
if (this._mains.length == 0) { |
|
Navigator.of(context).pop(); |
|
} |
|
this._mainChain = ChainToken.ETH; |
|
this._main = this._mains[ChainToken.ETH]!; |
|
setState(() {}); |
|
} |
|
} |
|
|
|
_walletToken(List params) { |
|
final network = NetworkExtension.fromInt(params[0]); |
|
if (network == this._selectedNetwork) { |
|
this._tokens.clear(); |
|
params[1].forEach((param) { |
|
this._tokens.add(Token.fromList(param, '0')); |
|
}); |
|
} |
|
setState(() {}); |
|
} |
|
|
|
_walletBalance(List params) { |
|
final address = params[0]; |
|
final network = NetworkExtension.fromInt(params[1]); |
|
if (address == this._selectedAddress!.address && network == this._selectedNetwork) { |
|
final balance = params[2]; |
|
|
|
if (params.length == 4) { |
|
for (int i=0;i<this._tokens.length;i++) { |
|
if (this._tokens[i].contract == params[3][4]) { |
|
this._tokens[i].updateBalance(balance); |
|
} |
|
} |
|
} else { |
|
this._mainToken.updateBalance(balance); |
|
} |
|
setState(() {}); |
|
} |
|
} |
|
|
|
_loadWallet() async { |
|
final res = await httpPost('wallet-list', []); |
|
if (res.isOk) { |
|
this._addresses.clear(); |
|
res.params.forEach((param) { |
|
final address = Address.fromList(param); |
|
this._addresses.add(address); |
|
if (address.isMain && this._mainChain == address.chain) { |
|
_changeAddress(address); |
|
} |
|
}); |
|
setState(() {}); |
|
} else { |
|
print(res.error); |
|
} |
|
} |
|
|
|
_changeAddress(Address address) { |
|
if (!this._mains.containsKey(address.chain)) { |
|
return; |
|
} |
|
this._main = this._mains[address.chain]!; |
|
this._mainChain = address.chain; |
|
this._selectedAddress = address; |
|
this._networks = address.networks(); |
|
if (!this._networks.contains(this._selectedNetwork)) { |
|
_changeNetwork(this._networks[0]); |
|
} else { |
|
rpc.send('wallet-token', [ |
|
this._selectedNetwork.toInt(), this._selectedAddress!.address |
|
]); |
|
} |
|
this._mainToken = address.mainToken(this._selectedNetwork); |
|
this._selectedToken = _mainToken; |
|
} |
|
|
|
_changeNetwork(Network network) { |
|
this._selectedNetwork = network; |
|
this._networkColor = network.params()[1]; |
|
rpc.send('wallet-token', [ |
|
this._selectedNetwork.toInt(), this._selectedAddress!.address |
|
]); |
|
} |
|
|
|
_gasPrice(String amount) async { |
|
if (this._main.length < 2) { |
|
return; |
|
} |
|
final res = await httpPost('wallet-gas-price', [ |
|
this._selectedToken.chain.toInt(), this._selectedNetwork.toInt(), |
|
this._selectedAddress!.address, this._main, amount, |
|
this._selectedToken.contract |
|
]); |
|
if (res.isOk) { |
|
this._price = unitBalance(res.params[0], 9, 0); |
|
this._gas = unitBalance(res.params[1], 18, 6); |
|
this._networkError = ''; |
|
this._checked = true; |
|
} else { |
|
this._networkError = res.error; |
|
} |
|
this._checking = false; |
|
setState(() {}); |
|
} |
|
|
|
@override |
|
Widget build(BuildContext context) { |
|
final color = Theme.of(context).colorScheme; |
|
final lang = AppLocalizations.of(context); |
|
|
|
if (_selectedAddress == null) { |
|
return Center(child: Text(lang.waiting)); |
|
} |
|
|
|
final mainWidget = [DropdownMenuItem<Token>( |
|
value: this._mainToken, |
|
child: Row( |
|
children: [ |
|
Text(this._mainToken.name), |
|
Spacer(), |
|
Text(this._selectedAddress!.balance(this._selectedNetwork)), |
|
const SizedBox(width: 10.0), |
|
] |
|
), |
|
)]; |
|
|
|
return Column( |
|
children: [ |
|
Text(this._main, style: TextStyle(color: color.primary)), |
|
const SizedBox(height: 10.0), |
|
Row( |
|
children: [ |
|
Icon(Icons.public, color: this._networkColor), |
|
const SizedBox(width: 10.0), |
|
Expanded( |
|
child: DropdownButtonHideUnderline( |
|
child: DropdownButton<Network>( |
|
iconEnabledColor: Color(0xFFADB0BB), |
|
isExpanded: true, |
|
value: this._selectedNetwork, |
|
onChanged: (Network? network) { |
|
if (network != null) { |
|
setState(() { |
|
this._checked = false; |
|
_changeNetwork(network); |
|
}); |
|
} |
|
}, |
|
items: this._networks.map((network) { |
|
final params = network.params(); |
|
return DropdownMenuItem<Network>( |
|
value: network, |
|
child: Text(params[0], style: TextStyle(fontSize: 16, color: params[1])) |
|
); |
|
}).toList(), |
|
), |
|
) |
|
)]), // network select. |
|
Row( |
|
children: [ |
|
const Icon(Icons.account_circle, color: Color(0xFF6174FF)), |
|
const SizedBox(width: 10.0), |
|
Expanded( |
|
child: DropdownButtonHideUnderline( |
|
child: DropdownButton<Address>( |
|
iconEnabledColor: Color(0xFFADB0BB), |
|
isExpanded: true, |
|
value: this._selectedAddress, |
|
onChanged: (Address? addr) { |
|
if (addr != null) { |
|
setState(() { |
|
this._checked = false; |
|
_changeAddress(addr); |
|
}); |
|
} |
|
}, |
|
items: this._addresses.map((address) { |
|
return DropdownMenuItem<Address>( |
|
value: address, |
|
child: Row( |
|
children: [ |
|
Text(address.name), |
|
Spacer(), |
|
Text('(' + address.short() + ')'), |
|
const SizedBox(width: 10.0), |
|
] |
|
), |
|
); |
|
}).toList(), |
|
), |
|
), |
|
)]), // address select. |
|
Row( |
|
children: [ |
|
const SizedBox(width: 2.0), |
|
Container( |
|
width: 20.0, |
|
height: 20.0, |
|
decoration: BoxDecoration( |
|
image: DecorationImage( |
|
image: AssetImage(this._selectedToken.logo), |
|
fit: BoxFit.cover, |
|
), |
|
), |
|
), |
|
const SizedBox(width: 10.0), |
|
Expanded( |
|
child: DropdownButtonHideUnderline( |
|
child: DropdownButton<Token>( |
|
iconEnabledColor: Color(0xFFADB0BB), |
|
isExpanded: true, |
|
value: this._selectedToken, |
|
onChanged: (Token? token) { |
|
if (token != null) { |
|
setState(() { |
|
this._checked = false; |
|
this._selectedToken = token; |
|
}); |
|
} |
|
}, |
|
items: mainWidget + this._tokens.map((token) { |
|
return DropdownMenuItem<Token>( |
|
value: token, |
|
child: Row( |
|
children: [ |
|
Text(token.name), |
|
Spacer(), |
|
Text(token.balance), |
|
const SizedBox(width: 10.0), |
|
] |
|
), |
|
); |
|
}).toList(), |
|
), |
|
), |
|
)]), // token select. |
|
const SizedBox(height: 20.0), |
|
InputText( |
|
icon: this._selectedToken.isNft() ? Icons.verified : Icons.paid, |
|
text: this._selectedToken.isNft() ? 'TokenID' : '0.0', |
|
controller: _amountController, |
|
focus: _amountFocus |
|
), |
|
const SizedBox(height: 10.0), |
|
if (this._checked) |
|
Row( |
|
mainAxisAlignment: MainAxisAlignment.center, |
|
children: [ |
|
Icon(Icons.arrow_forward, color: Colors.green), |
|
const SizedBox(width: 10.0), |
|
Expanded( |
|
child: this._networkError.length > 1 |
|
? Text(this._networkError, |
|
textAlign: TextAlign.center, style: TextStyle(color: Colors.red)) |
|
: RichText( |
|
textAlign: TextAlign.center, |
|
text: TextSpan( |
|
text: 'Estimated Price = ', |
|
style: TextStyle( |
|
fontSize: 14.0, fontStyle: FontStyle.italic, color: Colors.green), |
|
children: <TextSpan>[ |
|
TextSpan(text: this._price + ' Gwei', |
|
style: TextStyle(fontWeight: FontWeight.bold)), |
|
TextSpan(text: ', Gas ≈ '), |
|
TextSpan(text: this._gas + ' ETH', |
|
style: TextStyle(fontWeight: FontWeight.bold)), |
|
], |
|
), |
|
) |
|
), |
|
] |
|
), |
|
const SizedBox(height: 10.0), |
|
this._checked |
|
? ButtonText( |
|
text: lang.send, |
|
action: () { |
|
String a = _amountController.text.trim(); |
|
if (this._selectedToken.isNft()) { |
|
a = this._selectedNft; |
|
} |
|
if (a.length == 0 || (!this._selectedToken.isNft() && double.parse(a) == 0)) { |
|
_amountFocus.requestFocus(); |
|
return; |
|
} |
|
final amount = restoreBalance(a, this._selectedToken.decimal); |
|
final pid = context.read<AccountProvider>().id; |
|
showShadowDialog( |
|
context, |
|
Icons.security_rounded, |
|
lang.verifyPin, |
|
PinWords( |
|
pid: pid, |
|
callback: (key) async { |
|
if (this._main.length < 2) { |
|
return; |
|
} |
|
Navigator.of(context).pop(); |
|
final res = await httpPost('wallet-transfer', [ |
|
this._selectedToken.chain.toInt(), this._selectedNetwork.toInt(), |
|
this._selectedAddress!.id, this._main, amount, |
|
this._selectedToken.contract, key, |
|
]); |
|
if (res.isOk) { |
|
final addressId = res.params[0]; |
|
final network = NetworkExtension.fromInt(res.params[1]); |
|
final tx = Transaction.fromList(res.params[2]); |
|
widget.callback(tx.hash, tx.to, amount, |
|
this._selectedToken.name, this._selectedNetwork.toInt(), |
|
this._selectedToken.decimal, |
|
); |
|
Navigator.of(context).pop(); |
|
} else { |
|
this._networkError = res.error; |
|
} |
|
}), |
|
0.0, |
|
); |
|
}) |
|
: ButtonText( |
|
enable: !this._checking, |
|
text: this._checking ? lang.waiting : lang.check, |
|
action: () { |
|
String a = _amountController.text.trim(); |
|
if (this._selectedToken.isNft()) { |
|
a = this._selectedNft; |
|
} |
|
if (a.length == 0 || (!this._selectedToken.isNft() && double.parse(a) == 0)) { |
|
_amountFocus.requestFocus(); |
|
return; |
|
} |
|
final amount = restoreBalance(a, this._selectedToken.decimal); |
|
_gasPrice(amount); |
|
setState(() { |
|
this._checking = true; |
|
}); |
|
}) |
|
]); |
|
} |
|
}
|
|
|