@ -1,10 +1,13 @@
@@ -1,10 +1,13 @@
import ' package:flutter/material.dart ' ;
import ' package:flutter/services.dart ' ;
import ' package:provider/provider.dart ' ;
import ' package:url_launcher/url_launcher.dart ' ;
import ' package:qr_flutter/qr_flutter.dart ' ;
import ' package:esse/utils/adaptive.dart ' ;
import ' package:esse/utils/better_print.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 ' ;
@ -35,6 +38,7 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
@@ -35,6 +38,7 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
Token _mainToken = Token ( ) ;
List < Token > _tokens = [ ] ;
List < Transaction > _txs = [ ] ;
@ override
void initState ( ) {
@ -44,6 +48,7 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
@@ -44,6 +48,7 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
rpc . addListener ( ' wallet-import ' , _walletGenerate , false ) ;
rpc . addListener ( ' wallet-token ' , _walletToken , false ) ;
rpc . addListener ( ' wallet-balance ' , _walletBalance , false ) ;
rpc . addListener ( ' wallet-transfer ' , _walletTransfer , false ) ;
super . initState ( ) ;
Future . delayed ( Duration . zero , _load ) ;
@ -104,6 +109,23 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
@@ -104,6 +109,23 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
}
}
_walletTransfer ( List params ) {
final addressId = params [ 0 ] ;
final network = NetworkExtension . fromInt ( params [ 1 ] ) ;
final tx = Transaction . fromList ( params [ 2 ] ) ;
bool isNew = true ;
this . _txs . asMap ( ) . forEach ( ( k , t ) {
if ( t . hash = = tx . hash ) {
isNew = false ;
}
} ) ;
if ( isNew ) {
this . _txs . add ( tx ) ;
this . _tabController ! . animateTo ( 1 ) ;
}
}
_load ( ) async {
final res = await httpPost ( Global . httpRpc , ' wallet-list ' , [ ] ) ;
if ( res . isOk ) {
@ -337,27 +359,63 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
@@ -337,27 +359,63 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
" ${ this . _mainToken . amount } ${ this . _mainToken . name } " ,
style: TextStyle ( fontSize: 24.0 , fontWeight: FontWeight . bold ) ) ,
) ,
Text ( ' \$ 100 0' , style: TextStyle ( color: Color ( 0xFFADB0BB ) ) ) ,
/ / Text ( ' \$ 0. 0' , style: TextStyle ( color: Color ( 0xFFADB0BB ) ) ) ,
const SizedBox ( height: 8.0 ) ,
Row (
mainAxisAlignment: MainAxisAlignment . spaceEvenly ,
children: [
TextButton (
onPressed: ( ) {
setState ( ( ) { } ) ;
} ,
onPressed: ( ) = > showShadowDialog (
context , Icons . input , this . _mainToken . name , _TransferToken (
chain: this . _selectedAddress ! . chain ,
network: this . _selectedNetwork ! ,
address: this . _selectedAddress ! ,
token: this . _mainToken ,
addresses: this . _addresses ,
) , 0.0
) ,
child: Container (
padding: const EdgeInsets . symmetric ( horizontal: 20.0 , vertical: 10.0 ) ,
decoration: BoxDecoration (
color: Color ( 0xFF6174FF ) ,
borderRadius: BorderRadius . circular ( 25.0 )
) ,
child: Center ( child: Text ( ' Send ' , style: TextStyle ( color: Colors . white ) ) )
child: Center ( child:
Row (
children: [
Icon ( Icons . input , color: Colors . white , size: 18.0 ) ,
const SizedBox ( width: 10.0 ) ,
Text ( lang . send , style: TextStyle ( color: Colors . white ) )
]
)
)
)
) ,
TextButton (
onPressed: ( ) {
setState ( ( ) { } ) ;
showShadowDialog ( context , Icons . qr_code , lang . receive ,
Column (
children: [
Container (
width: 200.0 ,
padding: const EdgeInsets . all ( 2.0 ) ,
margin: const EdgeInsets . only ( bottom: 20.0 ) ,
decoration: BoxDecoration (
borderRadius: BorderRadius . circular ( 5.0 ) ,
border: Border . all ( color: Color ( 0x40ADB0BB ) ) ,
color: Colors . white ,
) ,
child: Center (
child: QrImage (
data: this . _selectedAddress ! . address ,
version: QrVersions . auto ,
foregroundColor: Colors . black ,
) ,
) ,
) ,
Text ( this . _selectedAddress ! . address )
]
) ) ;
} ,
child: Container (
padding: const EdgeInsets . symmetric ( horizontal: 20.0 , vertical: 10.0 ) ,
@ -365,7 +423,15 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
@@ -365,7 +423,15 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
color: Color ( 0xFF6174FF ) ,
borderRadius: BorderRadius . circular ( 25.0 )
) ,
child: Center ( child: Text ( ' Receive ' , style: TextStyle ( color: Colors . white ) ) )
child: Center ( child:
Row (
children: [
Icon ( Icons . qr_code , color: Colors . white , size: 18.0 ) ,
const SizedBox ( width: 10.0 ) ,
Text ( lang . receive , style: TextStyle ( color: Colors . white ) )
]
)
)
)
) ,
]
@ -419,19 +485,47 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
@@ -419,19 +485,47 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
) ,
title: Text ( " ${ token . amount } ${ token . name } " , ) ,
subtitle: Text ( token . short ( ) ) ,
trailing: IconButton ( icon: Icon ( Icons . arrow_forward_ios ) ,
onPressed: ( ) { } ) ,
trailing: IconButton ( icon: Icon ( Icons . input , color: color . primary ) ,
onPressed: ( ) = > showShadowDialog (
context , Icons . input , token . name , _TransferToken (
chain: this . _selectedAddress ! . chain ,
network: this . _selectedNetwork ! ,
address: this . _selectedAddress ! ,
token: token ,
addresses: this . _addresses ,
) , 0.0
) ,
) ,
) ;
}
}
) ,
ListView . separated (
separatorBuilder: ( BuildContext context , int index ) = > const Divider ( ) ,
itemCount: 10 ,
itemCount: this . _txs . length + 1 ,
itemBuilder: ( BuildContext context , int index ) {
return Container (
child: Text ( ' TODO ${ index } ' ) ,
) ;
if ( index = = this . _txs . length ) {
return TextButton (
child: Padding (
padding: const EdgeInsets . symmetric ( vertical: 10.0 ) ,
child: Text ( lang . loadMore )
) ,
onPressed: ( ) {
/ /
}
) ;
} else {
final tx = this . _txs [ index ] ;
return ListTile (
title: Text ( ' Hash: ' + tx . short_hash ( ) ) ,
subtitle: Text ( ' To: ' + tx . short_to ( ) ) ,
trailing: IconButton ( icon: Icon ( Icons . link , color: color . primary ) ,
onPressed: ( ) {
launch ( this . _selectedNetwork ! . txUrl ( ) + tx . hash ) ;
}
) ,
) ;
}
}
) ,
] ,
@ -572,3 +666,296 @@ class _ImportTokenState extends State<_ImportToken> {
@@ -572,3 +666,296 @@ class _ImportTokenState extends State<_ImportToken> {
) ;
}
}
class _TransferToken extends StatefulWidget {
final ChainToken chain ;
final Network network ;
final Address address ;
final Token token ;
final List addresses ;
_TransferToken ( { Key ? key , required this . chain , required this . network , required this . address , required this . token , required this . addresses } ) : super ( key: key ) ;
@ override
_TransferTokenState createState ( ) = > _TransferTokenState ( ) ;
}
class _TransferTokenState extends State < _TransferToken > {
TextEditingController _nameController = TextEditingController ( ) ;
FocusNode _nameFocus = FocusNode ( ) ;
TextEditingController _amountController = TextEditingController ( ) ;
FocusNode _amountFocus = FocusNode ( ) ;
bool _myAccount = false ;
String _selectAddress = ' ' ;
String _price = ' ' ;
String _gas = ' 0 ' ;
String _networkError = ' ' ;
bool _checked = false ;
@ override
void initState ( ) {
super . initState ( ) ;
_nameController . addListener ( ( ) {
setState ( ( ) {
this . _checked = false ;
} ) ;
} ) ;
_amountController . addListener ( ( ) {
setState ( ( ) {
this . _checked = false ;
} ) ;
} ) ;
}
_gasPrice ( String to , String amount ) async {
final res = await httpPost ( Global . httpRpc , ' wallet-gas-price ' , [
widget . token . chain . toInt ( ) , widget . network . toInt ( ) ,
widget . address . address , to , amount ,
widget . token . 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 ;
}
setState ( ( ) { } ) ;
}
@ override
Widget build ( BuildContext context ) {
final color = Theme . of ( context ) . colorScheme ;
final lang = AppLocalizations . of ( context ) ;
final params = widget . network . params ( ) ;
return Column (
children: [
Text ( params [ 0 ] , style: TextStyle ( color: params [ 1 ] , fontWeight: FontWeight . bold ) ) ,
const SizedBox ( height: 20.0 ) ,
Container (
margin: const EdgeInsets . only ( bottom: 5.0 ) ,
padding: const EdgeInsets . all ( 15.0 ) ,
decoration: BoxDecoration (
color: Color ( 0x266174FF ) ,
borderRadius: BorderRadius . circular ( 10.0 )
) ,
child: Column (
children: [
Row (
mainAxisAlignment: MainAxisAlignment . center ,
children: [
RichText (
text: TextSpan (
children: < TextSpan > [
TextSpan (
text: widget . address . name ,
style: TextStyle ( fontWeight: FontWeight . bold , color: color . primary )
) ,
TextSpan ( text: ' ( ' + widget . address . short ( ) + ' ) ' , style: TextStyle (
fontSize: 14.0 , fontStyle: FontStyle . italic , color: color . onSurface ) ) ,
] ,
) ,
)
]
) ,
const SizedBox ( height: 20.0 ) ,
Text ( " ${ widget . token . amount } ${ widget . token . name } " ,
style: TextStyle ( fontWeight: FontWeight . bold ) ) ,
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 ) ,
( this . _networkError . length > 1 )
? Text ( this . _networkError , style: TextStyle ( color: Colors . red ) )
: RichText (
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 ) ) ,
] ,
) ,
)
]
)
]
) ) ,
this . _myAccount
? Container (
margin: const EdgeInsets . only ( top: 22.0 , bottom: 5.0 ) ,
padding: const EdgeInsets . symmetric ( horizontal: 20.0 ) ,
decoration: BoxDecoration (
color: color . surface ,
borderRadius: BorderRadius . circular ( 10.0 )
) ,
child: DropdownButtonHideUnderline (
child: Theme (
data: Theme . of ( context ) . copyWith (
canvasColor: color . background ,
) ,
child: DropdownButton < String > (
iconEnabledColor: Color ( 0xFFADB0BB ) ,
isExpanded: true ,
value: this . _selectAddress ,
onChanged: ( String ? addr ) {
if ( addr ! = null ) {
setState ( ( ) {
this . _checked = false ;
this . _selectAddress = addr ;
} ) ;
}
} ,
items: widget . addresses . map ( ( address ) {
return DropdownMenuItem < String > (
value: address . address ,
child: Row (
children: [
Expanded (
child: Text ( " ${ address . name } " ,
maxLines: 1 ,
overflow: TextOverflow . ellipsis ,
style: TextStyle ( fontSize: 16 )
) ,
) ,
Text ( " ( ${ address . short ( ) } ) " , style: TextStyle ( fontSize: 16 ) ) ,
const SizedBox ( width: 10.0 ) ,
]
) ,
) ;
} ) . toList ( ) ,
) ,
) ,
)
)
: Container (
padding: const EdgeInsets . only ( top: 20.0 , bottom: 5.0 ) ,
child: Row (
children: [
Expanded (
child: InputText (
icon: Icons . person ,
text: lang . address + ' (0x...) ' ,
controller: _nameController ,
focus: _nameFocus
) ) ,
SizedBox ( width: 80.0 ,
child: IconButton ( icon: Icon ( Icons . qr_code , color: color . primary ) ,
onPressed: ( ) {
/ /
} )
)
] )
) ,
Row (
mainAxisAlignment: MainAxisAlignment . start ,
children: [
const SizedBox ( width: 10.0 ) ,
TextButton ( child: Text (
this . _myAccount ? ' Input Account ' : ' Between Accounts '
) , onPressed: ( ) = > setState ( ( ) {
this . _myAccount = ! this . _myAccount ;
if ( this . _selectAddress . length = = 0 ) {
this . _selectAddress = widget . addresses [ 0 ] . address ;
}
} )
) ,
]
) ,
Container (
padding: const EdgeInsets . only ( top: 15.0 , bottom: 20.0 ) ,
child: Row (
children: [
Expanded (
child: InputText (
icon: Icons . paid ,
text: ' Amount(0) or NFT(0x...) ' ,
controller: _amountController ,
focus: _amountFocus ) ,
) ,
SizedBox ( width: 80.0 ,
child: IconButton ( icon: Text ( ' Max ' , style: TextStyle ( color: color . primary ) ) ,
onPressed: ( ) = > setState ( ( ) {
if ( widget . token . chain . isMain ( ) ) {
final a = widget . token . amount - double . parse ( this . _gas ) ;
_amountController . text = " ${ a } " ;
} else {
_amountController . text = " ${ widget . token . amount } " ;
}
} )
) ,
)
]
)
) ,
this . _checked
? ButtonText (
text: lang . send ,
action: ( ) {
String to = _nameController . text . trim ( ) ;
if ( _myAccount ) {
to = this . _selectAddress ;
}
final a = _amountController . text . trim ( ) ;
if ( double . parse ( a ) = = 0 ) {
_amountFocus . requestFocus ( ) ;
return ;
}
if ( to . length < 20 ) {
_nameFocus . requestFocus ( ) ;
return ;
}
final amount = restoreBalance ( a , widget . token . decimal ) ;
final gid = context . read < AccountProvider > ( ) . activedAccount . gid ;
showShadowDialog (
context ,
Icons . security_rounded ,
lang . verifyPin ,
PinWords (
gid: gid ,
callback: ( key ) async {
Navigator . of ( context ) . pop ( ) ;
rpc . send ( ' wallet-transfer ' , [
widget . token . chain . toInt ( ) , widget . network . toInt ( ) ,
widget . address . id , to , restoreBalance ( amount , widget . token . decimal ) ,
widget . token . contract , key ,
] ) ;
Navigator . of ( context ) . pop ( ) ;
} ) ,
0.0 ,
) ;
} )
: ButtonText (
text: lang . check ,
action: ( ) {
String to = _nameController . text . trim ( ) ;
if ( _myAccount ) {
to = this . _selectAddress ;
}
final a = _amountController . text . trim ( ) ;
if ( a . length = = 0 | | double . parse ( a ) = = 0 ) {
_amountFocus . requestFocus ( ) ;
return ;
}
if ( to . length < 20 ) {
_nameFocus . requestFocus ( ) ;
return ;
}
final amount = restoreBalance ( a , widget . token . decimal ) ;
_gasPrice ( to , amount ) ;
} )
]
) ;
}
}