From 472195bbb305665620b501f952fa0ea220b33ec2 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Wed, 25 Sep 2024 16:10:58 +0300 Subject: [PATCH 01/56] Adjust the transaction details --- app/lib/models/wallet.dart | 2 +- .../screens/wallets/transaction_details.dart | 1 + app/lib/screens/wallets/transactions.dart | 30 +++++----- app/lib/widgets/wallets/transaction.dart | 6 +- .../widgets/wallets/transaction_details.dart | 57 +++++++++++-------- app/lib/widgets/wallets/wallet_card.dart | 1 + 6 files changed, 54 insertions(+), 43 deletions(-) diff --git a/app/lib/models/wallet.dart b/app/lib/models/wallet.dart index 4cbdb9dd..aa6b95df 100644 --- a/app/lib/models/wallet.dart +++ b/app/lib/models/wallet.dart @@ -60,7 +60,7 @@ class Transaction { required this.to, required this.asset, required this.amount, - // required this.memo, // check how to get it + // required this.memo, //TODO: check how to get it (transaction link) required this.type, required this.status, required this.date, diff --git a/app/lib/screens/wallets/transaction_details.dart b/app/lib/screens/wallets/transaction_details.dart index 976186ce..83fab732 100644 --- a/app/lib/screens/wallets/transaction_details.dart +++ b/app/lib/screens/wallets/transaction_details.dart @@ -13,6 +13,7 @@ class TransactionDetailsScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( + appBar: AppBar(title: const Text('Transaction Details')), body: Column( children: [ Expanded( diff --git a/app/lib/screens/wallets/transactions.dart b/app/lib/screens/wallets/transactions.dart index 730608e2..d3c9a2d0 100644 --- a/app/lib/screens/wallets/transactions.dart +++ b/app/lib/screens/wallets/transactions.dart @@ -4,7 +4,7 @@ import 'package:threebotlogin/services/stellar_service.dart'; import 'package:threebotlogin/widgets/wallets/transaction.dart'; import 'package:threebotlogin/widgets/wallets/vertical_divider.dart'; import 'package:stellar_flutter_sdk/src/responses/operations/payment_operation_response.dart'; -import 'package:stellar_flutter_sdk/src/responses/operations/path_payment_strict_receive_operation_response.dart'; +// import 'package:stellar_flutter_sdk/src/responses/operations/path_payment_strict_receive_operation_response.dart'; class WalletTransactionsWidget extends StatefulWidget { const WalletTransactionsWidget({super.key, required this.wallet}); @@ -30,21 +30,23 @@ class _WalletTransactionsWidgetState extends State { hash: tx.transactionHash!, from: tx.from!.accountId, to: tx.to!.accountId, - asset: tx.asset.toString(), + asset: tx.assetCode.toString(), amount: tx.amount!, - type: TransactionType.Payment, + type: tx.to!.accountId == widget.wallet.stellarAddress + ? TransactionType.Receive + : TransactionType.Payment, status: tx.transactionSuccessful!, - date: tx.createdAt!); - } else if (tx is PathPaymentStrictReceiveOperationResponse) { - return Transaction( - hash: tx.transactionHash!, - from: tx.from!, - to: tx.to!, - asset: tx.asset.toString(), - type: TransactionType.Payment, - status: tx.transactionSuccessful!, - amount: tx.amount!, - date: tx.createdAt!); + date: DateTime.parse(tx.createdAt!).toLocal().toString()); + // } else if (tx is PathPaymentStrictReceiveOperationResponse) { + // return Transaction( + // hash: tx.transactionHash!, + // from: tx.from!, + // to: tx.to!, + // asset: tx.assetCode.toString(), + // type: TransactionType.Receive, + // status: tx.transactionSuccessful!, + // amount: tx.amount!, + // date: tx.createdAt!); } // TODO: handle creation transaction }).toList(); diff --git a/app/lib/widgets/wallets/transaction.dart b/app/lib/widgets/wallets/transaction.dart index 204f34a6..d229661c 100644 --- a/app/lib/widgets/wallets/transaction.dart +++ b/app/lib/widgets/wallets/transaction.dart @@ -65,8 +65,7 @@ class TransactionWidget extends StatelessWidget { : Theme.of(context).colorScheme.error, )), ), - Text( - DateTime.parse(transaction.date).toLocal().toString(), + Text(transaction.date, overflow: TextOverflow.ellipsis, style: Theme.of(context) .textTheme @@ -84,9 +83,6 @@ class TransactionWidget extends StatelessWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( - // color: status == 'Successful' - // ? Theme.of(context).colorScheme.primary - // : Theme.of(context).colorScheme.error, border: Border.all( color: transaction.status ? Theme.of(context).colorScheme.primary diff --git a/app/lib/widgets/wallets/transaction_details.dart b/app/lib/widgets/wallets/transaction_details.dart index 0b173550..529e2239 100644 --- a/app/lib/widgets/wallets/transaction_details.dart +++ b/app/lib/widgets/wallets/transaction_details.dart @@ -23,11 +23,39 @@ class TransactionDetails extends StatelessWidget { children: [ Text(label, style: Theme.of(context).textTheme.titleLarge!.copyWith( - color: Theme.of(context).colorScheme.onBackground)), - const SizedBox(height: 4.0), - Text(value, - style: Theme.of(context).textTheme.bodyLarge!.copyWith( - color: Theme.of(context).colorScheme.onBackground)), + color: Theme.of(context).colorScheme.onBackground, + fontWeight: FontWeight.bold)), + const SizedBox(height: 10), + label == 'Type' + ? Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 6), + decoration: BoxDecoration( + border: Border.all( + color: value == 'Receive' + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.error, + ), + borderRadius: BorderRadius.circular(20), + ), + child: Text(value, + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: value == 'Receive' + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.error, + )), + ) + : Text(value, + style: Theme.of(context) + .textTheme + .bodyLarge! + .copyWith( + color: Theme.of(context) + .colorScheme + .onBackground)), ], ), ), @@ -41,29 +69,12 @@ class TransactionDetails extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - IconButton( - onPressed: () { - Navigator.pop(context); - }, - icon: const Icon(Icons.arrow_back)), - const Text( - 'Transaction Details', - style: TextStyle( - fontSize: 20, - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), - ], - ), const SizedBox(height: 16.0), buildDetailRow('From', transaction.from), const Divider(), buildDetailRow('To', transaction.to), const Divider(), - buildDetailRow('Type', transaction.type.toString()), + buildDetailRow('Type', transaction.type.name), const Divider(), buildDetailRow('Amount', transaction.amount), const Divider(), diff --git a/app/lib/widgets/wallets/wallet_card.dart b/app/lib/widgets/wallets/wallet_card.dart index 6664ab50..2332797f 100644 --- a/app/lib/widgets/wallets/wallet_card.dart +++ b/app/lib/widgets/wallets/wallet_card.dart @@ -37,6 +37,7 @@ class WalletCardWidget extends StatelessWidget { ), ), const SizedBox(height: 10), + // TODO: hide the balance if there is no account for this wallet Row( children: [ Text( From 9c7b5ae21adcf8f406eca3b48a8c70b3fbc0d766 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Wed, 25 Sep 2024 16:12:42 +0300 Subject: [PATCH 02/56] decrease the divider height --- app/lib/widgets/wallets/vertical_divider.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/widgets/wallets/vertical_divider.dart b/app/lib/widgets/wallets/vertical_divider.dart index f862bcd9..8ce8c24e 100644 --- a/app/lib/widgets/wallets/vertical_divider.dart +++ b/app/lib/widgets/wallets/vertical_divider.dart @@ -10,7 +10,7 @@ class CustomVerticalDivider extends StatelessWidget { child: Padding( padding: EdgeInsets.only(left: 35), child: SizedBox( - height: 40, + height: 20, child: VerticalDivider( color: Colors.grey, width: 2, From 760ac36e09b8d88ded68329a82dd006e37244aaa Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Wed, 25 Sep 2024 19:41:04 +0300 Subject: [PATCH 03/56] Add wallet assets page --- app/lib/screens/wallets/wallet_details.dart | 6 +- app/lib/services/stellar_service.dart | 8 +- app/lib/widgets/wallets/arrow_inward.dart | 9 +- app/lib/widgets/wallets/transaction.dart | 4 +- app/lib/widgets/wallets/wallet_assets.dart | 125 ++++++++++++++++++++ app/lib/widgets/wallets/wallet_balance.dart | 17 --- 6 files changed, 144 insertions(+), 25 deletions(-) create mode 100644 app/lib/widgets/wallets/wallet_assets.dart delete mode 100644 app/lib/widgets/wallets/wallet_balance.dart diff --git a/app/lib/screens/wallets/wallet_details.dart b/app/lib/screens/wallets/wallet_details.dart index f14234fd..7abfcd94 100644 --- a/app/lib/screens/wallets/wallet_details.dart +++ b/app/lib/screens/wallets/wallet_details.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:threebotlogin/models/wallet.dart'; import 'package:threebotlogin/screens/wallets/transactions.dart'; -import 'package:threebotlogin/widgets/wallets/wallet_balance.dart'; +import 'package:threebotlogin/widgets/wallets/wallet_assets.dart'; import 'package:threebotlogin/screens/wallets/wallet_info.dart'; class WalletDetailsScreen extends StatefulWidget { @@ -47,7 +47,9 @@ class _WalletDetailsScreenState extends State { onEditWallet: _onEditWallet, ); } else { - content = const WalletBalanceWidget(); + content = WalletAssetsWidget( + wallet: widget.wallet, + ); } return Scaffold( appBar: AppBar(title: Text(widget.wallet.name)), diff --git a/app/lib/services/stellar_service.dart b/app/lib/services/stellar_service.dart index 8fa2ee9a..55434ab8 100644 --- a/app/lib/services/stellar_service.dart +++ b/app/lib/services/stellar_service.dart @@ -1,3 +1,4 @@ +import 'package:stellar_client/models/vesting_account.dart'; import 'package:stellar_client/stellar_client.dart'; import 'package:stellar_flutter_sdk/stellar_flutter_sdk.dart'; @@ -28,6 +29,11 @@ Future getBalanceByClient(Client client) async { Future> listTransactions(String secret) async { final client = Client(NetworkType.PUBLIC, secret); final transactions = await client.getTransactions(assetCodeFilter: 'TFT'); - print(transactions); return transactions; } + +Future?> listVestedAccounts(String secret) async { + final client = Client(NetworkType.PUBLIC, secret); + final accounts = await client.getVestingAccounts(); + return accounts; +} diff --git a/app/lib/widgets/wallets/arrow_inward.dart b/app/lib/widgets/wallets/arrow_inward.dart index 9895b81b..63a41b1e 100644 --- a/app/lib/widgets/wallets/arrow_inward.dart +++ b/app/lib/widgets/wallets/arrow_inward.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'dart:math' as math; class ArrowInward extends StatelessWidget { - const ArrowInward({ - Key? key, - }) : super(key: key); + const ArrowInward({super.key, required this.color, this.size = 24}); + final Color color; + final double size; @override Widget build(BuildContext context) { @@ -12,7 +12,8 @@ class ArrowInward extends StatelessWidget { angle: 180 * math.pi / 180, child: Icon( Icons.arrow_outward, - color: Theme.of(context).colorScheme.onPrimary, + color: color, + size: size, ), ); } diff --git a/app/lib/widgets/wallets/transaction.dart b/app/lib/widgets/wallets/transaction.dart index d229661c..a2799ddb 100644 --- a/app/lib/widgets/wallets/transaction.dart +++ b/app/lib/widgets/wallets/transaction.dart @@ -33,7 +33,9 @@ class TransactionWidget extends StatelessWidget { ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.error, child: transaction.type == TransactionType.Receive - ? const ArrowInward() + ? ArrowInward( + color: Theme.of(context).colorScheme.onPrimary, + ) : Icon( Icons.arrow_outward, color: Theme.of(context).colorScheme.onError, diff --git a/app/lib/widgets/wallets/wallet_assets.dart b/app/lib/widgets/wallets/wallet_assets.dart new file mode 100644 index 00000000..05d180f8 --- /dev/null +++ b/app/lib/widgets/wallets/wallet_assets.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; +import 'package:stellar_client/models/vesting_account.dart'; +import 'package:threebotlogin/models/wallet.dart'; +import 'package:threebotlogin/services/stellar_service.dart'; +import 'package:threebotlogin/widgets/wallets/arrow_inward.dart'; +import 'package:threebotlogin/widgets/wallets/balance_tile.dart'; + +class WalletAssetsWidget extends StatefulWidget { + const WalletAssetsWidget({super.key, required this.wallet}); + final Wallet wallet; + + @override + State createState() => _WalletAssetsWidgetState(); +} + +class _WalletAssetsWidgetState extends State { + List? vestedWallets = []; + + _listVestedAccounts() async { + vestedWallets = await listVestedAccounts(widget.wallet.stellarSecret); + setState(() {}); + } + + @override + void initState() { + _listVestedAccounts(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + List vestWidgets = []; + if (vestedWallets != null && vestedWallets!.isNotEmpty) { + vestWidgets = [ + Text( + 'Vest', + style: Theme.of(context).textTheme.headlineSmall!.copyWith( + color: Theme.of(context).colorScheme.onSecondaryContainer, + fontWeight: FontWeight.bold), + ), + const SizedBox( + height: 20, + ), + // Use the correct balance from the vest account + WalletBalanceTileWidget( + name: 'Stellar', balance: vestedWallets![0].tft.toString()), + ]; + } + + return Padding( + padding: const EdgeInsets.all(15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 30), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Column( + children: [ + CircleAvatar( + radius: 30, + backgroundColor: Theme.of(context).colorScheme.error, + child: Icon( + Icons.arrow_outward_outlined, + color: Theme.of(context).colorScheme.onError, + size: 30, + ), + ), + const SizedBox(height: 10), + Text( + 'Send', + style: Theme.of(context) + .textTheme + .titleLarge! + .copyWith(color: Theme.of(context).colorScheme.error), + ), + ], + ), + Column( + children: [ + CircleAvatar( + radius: 30, + backgroundColor: Theme.of(context).colorScheme.primary, + child: ArrowInward( + color: Theme.of(context).colorScheme.onPrimary, + size: 30, + )), + const SizedBox(height: 10), + Text( + 'Receive', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + color: Theme.of(context).colorScheme.primary), + ), + ], + ), + ], + ), + ), + // TODO: reload balance on mount + Text( + 'Assets', + style: Theme.of(context).textTheme.headlineSmall!.copyWith( + color: Theme.of(context).colorScheme.onSecondaryContainer, + fontWeight: FontWeight.bold), + ), + const SizedBox( + height: 20, + ), + WalletBalanceTileWidget( + name: 'Stellar', balance: widget.wallet.stellarBalance), + const SizedBox(height: 10), + WalletBalanceTileWidget( + name: 'TFChain', balance: widget.wallet.tfchainBalance), + const SizedBox( + height: 30, + ), + ...vestWidgets + ], + ), + ); + } +} diff --git a/app/lib/widgets/wallets/wallet_balance.dart b/app/lib/widgets/wallets/wallet_balance.dart deleted file mode 100644 index fe30896e..00000000 --- a/app/lib/widgets/wallets/wallet_balance.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:flutter/material.dart'; - -class WalletBalanceWidget extends StatelessWidget { - const WalletBalanceWidget({super.key}); - - @override - Widget build(BuildContext context) { - return Center( - child: Text( - 'Balances', - style: Theme.of(context).textTheme.titleLarge!.copyWith( - color: Theme.of(context).colorScheme.onBackground, - ), - ), - ); - } -} From f3544d6737c55b12d54e2a69302df7c1e1e0f17f Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Wed, 25 Sep 2024 22:34:14 +0300 Subject: [PATCH 04/56] Add wallet balance tile widget --- app/assets/tft_icon.png | Bin 0 -> 7942 bytes app/lib/widgets/wallets/balance_tile.dart | 41 ++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 app/assets/tft_icon.png create mode 100644 app/lib/widgets/wallets/balance_tile.dart diff --git a/app/assets/tft_icon.png b/app/assets/tft_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..91a4480e1516c7dd5df633c3ba6ec3f6163f1422 GIT binary patch literal 7942 zcmeHL3se(V8omi2irZp~&srpo{=$I6~18%$xxq1@7MO!p9g$tAr~6TY8B|^nFUX$}6vA6c z@uT|%(CGd&dXSH=FEh}ONe`r$FHbn3n4iXs<3yULgKsR)EmEn7Nu%ZF=2CP0sX}o& z&6mMo(CB_NKR+Lc@R4lIk@E6GjI?|syIU=%@F2LFh<^Hp-js1gvJtH2t*HQ zb0p?S!I;tVcp{oFm5zBe4&-tthYAD_(GqDK@{_$*i6seJMF=epkqBktRG7Q8=dr0o z%y2QnlM2NNLSgoJAY-=(r9#OTp@`xaNM%r#Z^+2u3v(qaa0M=x8J#1M@^VsmPd2eB;!U{A^5*0RC-J0k_UAh;;mf@2I4 ze9pvHD2m;t7ReZjg5AJWB8sDIBd6flsTeJ$oGx?N#CSA>fK+TsC$sSAaP_sp1dCQbwp?1)+A9$ z>i3G=H>A?p6q&=pLwcbu;V&;7iUD2N-*ku0h|ZWohSWk1GP3$yhdGTRza| zb4@Ym9)>dozO%VUvjy_8Ipm$irM)XrI09th>H)o16eXb2HArU6JB7?az2;H`{ z1p#F<34E8cVh_`S0y)`&QyQw6fIMiv&@3~fn>8n;Dzw21n{{L<3<{qC-KdDV~#-<1fT+bj;pupadRRVC^ zR=+~RXAf~2al=zU{5~VHG4I=Qf~$la8S_~8h7nEOr{O7iOc%v!Ws%qnHH~T8^Gt3VKFYs?Ty6B>D_Zy7LF78rhRzVd3|MbsCr(HK)BaX(<5e z3P-dvhP3;K!k2X0PCU7CX!eC4lTW=;%X$jTZ>+tdSBK;|1G;K~R}7qN-$kJwKG^Wj zA`g>_SQd$5!V@~Fh}D3tT&$r}&X1^P#dXK89+wU%QK@Z+JOB+~Se*Gyg8@ zqN$^9F}rmd>!Ws%!|=e%I*s+wX(G39)i%){%WY%Cw@S{$G)yy=$OQVJBMP73 zw7U>?vRI&we0?L>l&^Gq*n||DSY$wfU)V-4R7Dg^tS(!n>G{Ju>Q?-&~PCX zzN9Fgy?HJSN9`C@=*C;ED20!?8@qB*#+*Lh7k0mXys6CD^1u~1;$BydDq#0-cDm)Jz9@T-xk+p_eq-WtO*&I* zGijx2g%|W>X>j@V^L1@^2FXOojx;I<`J*M|#24{K`9Rx8*%G%A1XPRrbsGwy3T|<4Yp5eYK&-)cZxj*U4v&62D}s#4?X-!yHwZ?jWm#wYRg> zUA_cPI5HR`A%kU?w#!$A1{tclPvmd!mfn-9j`tO`)cfS0|1;$4`DZepq!thCR2N)* zb;mA6y24A@SUh&d(Aj-U=rLw`y_XylcvK}w!u8l74Sq)zRfd4Opoi=tOL^>`yVD~A-M|IV>uJus|Hnu5GNofJF&SP~H cJ?6L&%q(KOT2+7g4a|t>h#1bvwJGoX3shF(wEzGB literal 0 HcmV?d00001 diff --git a/app/lib/widgets/wallets/balance_tile.dart b/app/lib/widgets/wallets/balance_tile.dart new file mode 100644 index 00000000..d6ea81c5 --- /dev/null +++ b/app/lib/widgets/wallets/balance_tile.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; + +class WalletBalanceTileWidget extends StatelessWidget { + const WalletBalanceTileWidget({ + super.key, + required this.balance, + required this.name, + }); + final String balance; + final String name; + + @override + Widget build(BuildContext context) { + return ListTile( + shape: RoundedRectangleBorder( + side: BorderSide( + color: Theme.of(context).colorScheme.primary, + ), + borderRadius: BorderRadius.circular(5), + ), + leading: Image.asset( + 'assets/tft_icon.png', + fit: BoxFit.cover, + color: Theme.of(context).colorScheme.onBackground, + height: 50, + ), + title: Text( + name, + style: Theme.of(context).textTheme.bodyLarge!.copyWith( + color: Theme.of(context).colorScheme.onSecondaryContainer, + ), + ), + trailing: Text( + '$balance TFT', + style: Theme.of(context).textTheme.bodyLarge!.copyWith( + color: Theme.of(context).colorScheme.onSecondaryContainer, + ), + ), + ); + } +} From a655ffef0acb6b91ff52066080e87a436f1e51ab Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Thu, 26 Sep 2024 00:27:47 +0300 Subject: [PATCH 05/56] Reload Balance when clicking on the wallet --- app/lib/services/stellar_service.dart | 5 ++ app/lib/services/tfchain_service.dart | 1 + app/lib/widgets/wallets/balance_tile.dart | 32 ++++++++---- app/lib/widgets/wallets/wallet_assets.dart | 58 +++++++++++++++++++--- 4 files changed, 78 insertions(+), 18 deletions(-) diff --git a/app/lib/services/stellar_service.dart b/app/lib/services/stellar_service.dart index 55434ab8..219d150f 100644 --- a/app/lib/services/stellar_service.dart +++ b/app/lib/services/stellar_service.dart @@ -26,6 +26,11 @@ Future getBalanceByClient(Client client) async { return '0'; } +Future getBalance(String secret) async { + final client = Client(NetworkType.PUBLIC, secret); + return getBalanceByClient(client); +} + Future> listTransactions(String secret) async { final client = Client(NetworkType.PUBLIC, secret); final transactions = await client.getTransactions(assetCodeFilter: 'TFT'); diff --git a/app/lib/services/tfchain_service.dart b/app/lib/services/tfchain_service.dart index aded665b..88c99549 100644 --- a/app/lib/services/tfchain_service.dart +++ b/app/lib/services/tfchain_service.dart @@ -15,6 +15,7 @@ import 'package:http/http.dart' as http; Future getMyTwinId() async { final chainUrl = Globals().chainUrl; if (chainUrl == '') return null; + // TODO: make sure we are using the correct phrase or needs to use derived seed final phrase = await getPhrase(); if (phrase != null) { return await compute((void _) async { diff --git a/app/lib/widgets/wallets/balance_tile.dart b/app/lib/widgets/wallets/balance_tile.dart index d6ea81c5..e057cd8d 100644 --- a/app/lib/widgets/wallets/balance_tile.dart +++ b/app/lib/widgets/wallets/balance_tile.dart @@ -5,9 +5,11 @@ class WalletBalanceTileWidget extends StatelessWidget { super.key, required this.balance, required this.name, + required this.loading, }); final String balance; final String name; + final bool loading; @override Widget build(BuildContext context) { @@ -18,11 +20,14 @@ class WalletBalanceTileWidget extends StatelessWidget { ), borderRadius: BorderRadius.circular(5), ), - leading: Image.asset( - 'assets/tft_icon.png', - fit: BoxFit.cover, - color: Theme.of(context).colorScheme.onBackground, - height: 50, + leading: SizedBox( + width: 25, + child: Image.asset( + 'assets/tft_icon.png', + fit: BoxFit.cover, + color: Theme.of(context).colorScheme.onBackground, + height: 50, + ), ), title: Text( name, @@ -30,12 +35,19 @@ class WalletBalanceTileWidget extends StatelessWidget { color: Theme.of(context).colorScheme.onSecondaryContainer, ), ), - trailing: Text( - '$balance TFT', - style: Theme.of(context).textTheme.bodyLarge!.copyWith( - color: Theme.of(context).colorScheme.onSecondaryContainer, + trailing: loading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + )) + : Text( + '$balance TFT', + style: Theme.of(context).textTheme.bodyLarge!.copyWith( + color: Theme.of(context).colorScheme.onSecondaryContainer, + ), ), - ), ); } } diff --git a/app/lib/widgets/wallets/wallet_assets.dart b/app/lib/widgets/wallets/wallet_assets.dart index 05d180f8..9390cf9b 100644 --- a/app/lib/widgets/wallets/wallet_assets.dart +++ b/app/lib/widgets/wallets/wallet_assets.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:stellar_client/models/vesting_account.dart'; +import 'package:threebotlogin/helpers/globals.dart'; import 'package:threebotlogin/models/wallet.dart'; -import 'package:threebotlogin/services/stellar_service.dart'; +import 'package:threebotlogin/services/stellar_service.dart' as Stellar; +import 'package:threebotlogin/services/tfchain_service.dart' as TFChain; import 'package:threebotlogin/widgets/wallets/arrow_inward.dart'; import 'package:threebotlogin/widgets/wallets/balance_tile.dart'; @@ -15,15 +17,44 @@ class WalletAssetsWidget extends StatefulWidget { class _WalletAssetsWidgetState extends State { List? vestedWallets = []; + bool tfchainBalaceLoading = true; + bool stellarBalaceLoading = true; _listVestedAccounts() async { - vestedWallets = await listVestedAccounts(widget.wallet.stellarSecret); + vestedWallets = + await Stellar.listVestedAccounts(widget.wallet.stellarSecret); setState(() {}); } + _loadTFChainBalance() async { + setState(() { + tfchainBalaceLoading = true; + }); + final chainUrl = Globals().chainUrl; + widget.wallet.tfchainBalance = + (await TFChain.getBalance(chainUrl, widget.wallet.tfchainAddress)) + .toString(); + setState(() { + tfchainBalaceLoading = false; + }); + } + + _loadStellarBalance() async { + setState(() { + stellarBalaceLoading = true; + }); + widget.wallet.stellarBalance = + (await Stellar.getBalance(widget.wallet.stellarSecret)).toString(); + setState(() { + stellarBalaceLoading = false; + }); + } + @override void initState() { _listVestedAccounts(); + _loadTFChainBalance(); + _loadStellarBalance(); super.initState(); } @@ -32,6 +63,8 @@ class _WalletAssetsWidgetState extends State { List vestWidgets = []; if (vestedWallets != null && vestedWallets!.isNotEmpty) { vestWidgets = [ + const Divider(), + const SizedBox(height: 10), Text( 'Vest', style: Theme.of(context).textTheme.headlineSmall!.copyWith( @@ -41,9 +74,11 @@ class _WalletAssetsWidgetState extends State { const SizedBox( height: 20, ), - // Use the correct balance from the vest account WalletBalanceTileWidget( - name: 'Stellar', balance: vestedWallets![0].tft.toString()), + name: 'Stellar', + balance: vestedWallets![0].tft.toString(), + loading: false, + ), ]; } @@ -99,7 +134,8 @@ class _WalletAssetsWidgetState extends State { ], ), ), - // TODO: reload balance on mount + const Divider(), + const SizedBox(height: 10), Text( 'Assets', style: Theme.of(context).textTheme.headlineSmall!.copyWith( @@ -110,12 +146,18 @@ class _WalletAssetsWidgetState extends State { height: 20, ), WalletBalanceTileWidget( - name: 'Stellar', balance: widget.wallet.stellarBalance), + name: 'Stellar', + balance: widget.wallet.stellarBalance, + loading: stellarBalaceLoading, + ), const SizedBox(height: 10), WalletBalanceTileWidget( - name: 'TFChain', balance: widget.wallet.tfchainBalance), + name: 'TFChain', + balance: widget.wallet.tfchainBalance, + loading: tfchainBalaceLoading, + ), const SizedBox( - height: 30, + height: 20, ), ...vestWidgets ], From 56b810042473f1b96af9032f183d863b5f4c3268 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Thu, 26 Sep 2024 00:45:02 +0300 Subject: [PATCH 06/56] Add inkwell on send and receive and init the send and receive widgets --- app/lib/screens/wallets/receive.dart | 17 +++++++ app/lib/screens/wallets/send.dart | 16 ++++++ .../wallets/wallet_assets.dart | 49 +++++++++++++------ app/lib/screens/wallets/wallet_details.dart | 2 +- 4 files changed, 69 insertions(+), 15 deletions(-) create mode 100644 app/lib/screens/wallets/receive.dart create mode 100644 app/lib/screens/wallets/send.dart rename app/lib/{widgets => screens}/wallets/wallet_assets.dart (75%) diff --git a/app/lib/screens/wallets/receive.dart b/app/lib/screens/wallets/receive.dart new file mode 100644 index 00000000..14ab8fff --- /dev/null +++ b/app/lib/screens/wallets/receive.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:threebotlogin/models/wallet.dart'; + +class WalletReceiveScreen extends StatefulWidget { + const WalletReceiveScreen({super.key, required this.wallet}); + final Wallet wallet; + + @override + State createState() => _WalletReceiveScreenState(); +} + +class _WalletReceiveScreenState extends State { + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} \ No newline at end of file diff --git a/app/lib/screens/wallets/send.dart b/app/lib/screens/wallets/send.dart new file mode 100644 index 00000000..758dde0e --- /dev/null +++ b/app/lib/screens/wallets/send.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:threebotlogin/models/wallet.dart'; + +class WalletSendScreen extends StatefulWidget { + const WalletSendScreen({super.key, required this.wallet}); + final Wallet wallet; + @override + State createState() => _WalletSendScreenState(); +} + +class _WalletSendScreenState extends State { + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} \ No newline at end of file diff --git a/app/lib/widgets/wallets/wallet_assets.dart b/app/lib/screens/wallets/wallet_assets.dart similarity index 75% rename from app/lib/widgets/wallets/wallet_assets.dart rename to app/lib/screens/wallets/wallet_assets.dart index 9390cf9b..e1634c14 100644 --- a/app/lib/widgets/wallets/wallet_assets.dart +++ b/app/lib/screens/wallets/wallet_assets.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:stellar_client/models/vesting_account.dart'; import 'package:threebotlogin/helpers/globals.dart'; import 'package:threebotlogin/models/wallet.dart'; +import 'package:threebotlogin/screens/wallets/receive.dart'; +import 'package:threebotlogin/screens/wallets/send.dart'; import 'package:threebotlogin/services/stellar_service.dart' as Stellar; import 'package:threebotlogin/services/tfchain_service.dart' as TFChain; import 'package:threebotlogin/widgets/wallets/arrow_inward.dart'; @@ -95,13 +97,22 @@ class _WalletAssetsWidgetState extends State { children: [ Column( children: [ - CircleAvatar( - radius: 30, - backgroundColor: Theme.of(context).colorScheme.error, - child: Icon( - Icons.arrow_outward_outlined, - color: Theme.of(context).colorScheme.onError, - size: 30, + InkWell( + onTap: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => WalletSendScreen( + wallet: widget.wallet, + ), + )); + }, + child: CircleAvatar( + radius: 30, + backgroundColor: Theme.of(context).colorScheme.error, + child: Icon( + Icons.arrow_outward_outlined, + color: Theme.of(context).colorScheme.onError, + size: 30, + ), ), ), const SizedBox(height: 10), @@ -116,13 +127,23 @@ class _WalletAssetsWidgetState extends State { ), Column( children: [ - CircleAvatar( - radius: 30, - backgroundColor: Theme.of(context).colorScheme.primary, - child: ArrowInward( - color: Theme.of(context).colorScheme.onPrimary, - size: 30, - )), + InkWell( + onTap: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => WalletReceiveScreen( + wallet: widget.wallet, + ), + )); + }, + child: CircleAvatar( + radius: 30, + backgroundColor: + Theme.of(context).colorScheme.primary, + child: ArrowInward( + color: Theme.of(context).colorScheme.onPrimary, + size: 30, + )), + ), const SizedBox(height: 10), Text( 'Receive', diff --git a/app/lib/screens/wallets/wallet_details.dart b/app/lib/screens/wallets/wallet_details.dart index 7abfcd94..f4571db4 100644 --- a/app/lib/screens/wallets/wallet_details.dart +++ b/app/lib/screens/wallets/wallet_details.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:threebotlogin/models/wallet.dart'; import 'package:threebotlogin/screens/wallets/transactions.dart'; -import 'package:threebotlogin/widgets/wallets/wallet_assets.dart'; +import 'package:threebotlogin/screens/wallets/wallet_assets.dart'; import 'package:threebotlogin/screens/wallets/wallet_info.dart'; class WalletDetailsScreen extends StatefulWidget { From 4c7105deae5830fdd032c90a137de0d5c0f9b0e2 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Thu, 26 Sep 2024 00:45:36 +0300 Subject: [PATCH 07/56] Change the compile version to 34 --- app/android/app/build_local | 3 ++- app/android/app/build_production | 3 ++- app/android/app/build_staging | 3 ++- app/android/app/build_testing | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/android/app/build_local b/app/android/app/build_local index edbeaf71..1ad129a2 100644 --- a/app/android/app/build_local +++ b/app/android/app/build_local @@ -26,7 +26,8 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion flutter.compileSdkVersion + // compileSdkVersion flutter.compileSdkVersion + compileSdkVersion 34 ndkVersion flutter.ndkVersion sourceSets { diff --git a/app/android/app/build_production b/app/android/app/build_production index d477a871..d7f2d032 100644 --- a/app/android/app/build_production +++ b/app/android/app/build_production @@ -26,7 +26,8 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion flutter.compileSdkVersion + // compileSdkVersion flutter.compileSdkVersion + compileSdkVersion 34 ndkVersion flutter.ndkVersion sourceSets { diff --git a/app/android/app/build_staging b/app/android/app/build_staging index edbeaf71..1ad129a2 100644 --- a/app/android/app/build_staging +++ b/app/android/app/build_staging @@ -26,7 +26,8 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion flutter.compileSdkVersion + // compileSdkVersion flutter.compileSdkVersion + compileSdkVersion 34 ndkVersion flutter.ndkVersion sourceSets { diff --git a/app/android/app/build_testing b/app/android/app/build_testing index d477a871..d7f2d032 100644 --- a/app/android/app/build_testing +++ b/app/android/app/build_testing @@ -26,7 +26,8 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion flutter.compileSdkVersion + // compileSdkVersion flutter.compileSdkVersion + compileSdkVersion 34 ndkVersion flutter.ndkVersion sourceSets { From 643614933f83b43062c944ddcd80c1900908fc6a Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Thu, 26 Sep 2024 08:13:50 +0300 Subject: [PATCH 08/56] Fix pop with back button --- .../org/jimber/threebotlogin/MainActivity.kt | 6 +- .../jimber/threebotlogin/MainActivity_local | 6 +- .../threebotlogin/MainActivity_production | 6 +- .../jimber/threebotlogin/MainActivity_staging | 6 +- .../jimber/threebotlogin/MainActivity_testing | 6 +- app/lib/widgets/layout_drawer.dart | 261 +++++++++--------- 6 files changed, 147 insertions(+), 144 deletions(-) diff --git a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity.kt b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity.kt index d621fae4..81b7f3d1 100644 --- a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity.kt +++ b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity.kt @@ -1,11 +1,11 @@ package org.jimber.threebotlogin -import io.flutter.embedding.android.FlutterFragmentActivity +import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugins.GeneratedPluginRegistrant -class MainActivity: FlutterFragmentActivity() { +class MainActivity: FlutterActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) } -} \ No newline at end of file +} diff --git a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_local b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_local index ece5499c..427ad4ef 100644 --- a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_local +++ b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_local @@ -1,11 +1,11 @@ package org.jimber.threebotlogin.local -import io.flutter.embedding.android.FlutterFragmentActivity +import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugins.GeneratedPluginRegistrant -class MainActivity: FlutterFragmentActivity() { +class MainActivity: FlutterActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) } -} \ No newline at end of file +} diff --git a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_production b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_production index d621fae4..81b7f3d1 100644 --- a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_production +++ b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_production @@ -1,11 +1,11 @@ package org.jimber.threebotlogin -import io.flutter.embedding.android.FlutterFragmentActivity +import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugins.GeneratedPluginRegistrant -class MainActivity: FlutterFragmentActivity() { +class MainActivity: FlutterActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) } -} \ No newline at end of file +} diff --git a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_staging b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_staging index c64d56e8..21ff04a8 100644 --- a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_staging +++ b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_staging @@ -1,11 +1,11 @@ package org.jimber.threebotlogin.staging -import io.flutter.embedding.android.FlutterFragmentActivity +import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugins.GeneratedPluginRegistrant -class MainActivity: FlutterFragmentActivity() { +class MainActivity: FlutterActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) } -} \ No newline at end of file +} diff --git a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_testing b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_testing index 61b8a58c..d980d466 100644 --- a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_testing +++ b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_testing @@ -1,11 +1,11 @@ package org.jimber.threebotlogin.testing -import io.flutter.embedding.android.FlutterFragmentActivity +import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugins.GeneratedPluginRegistrant -class MainActivity: FlutterFragmentActivity() { +class MainActivity: FlutterActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) } -} \ No newline at end of file +} diff --git a/app/lib/widgets/layout_drawer.dart b/app/lib/widgets/layout_drawer.dart index bf8b53b4..22674044 100644 --- a/app/lib/widgets/layout_drawer.dart +++ b/app/lib/widgets/layout_drawer.dart @@ -61,150 +61,153 @@ class _LayoutDrawerState extends State { selectedFontSize = 12; } - return Scaffold( - appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - actions: widget.appBarActions ?? [], - title: Text(widget.titleText), - toolbarHeight: 60, - ), - body: widget.content, - drawer: Drawer( - elevation: 5, - width: MediaQuery.of(context).size.width * 2 / 3, - // space to fit everything. - child: Column( - children: [ - SizedBox( - height: 70, - child: DrawerHeader( - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Theme.of(context).colorScheme.primary), + return PopScope( + canPop: false, + child: Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + actions: widget.appBarActions ?? [], + title: Text(widget.titleText), + toolbarHeight: 60, + ), + body: widget.content, + drawer: Drawer( + elevation: 5, + width: MediaQuery.of(context).size.width * 2 / 3, + // space to fit everything. + child: Column( + children: [ + SizedBox( + height: 70, + child: DrawerHeader( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Theme.of(context).colorScheme.primary), + ), + ), + child: SvgPicture.asset( + 'assets/TF_log_horizontal.svg', + colorFilter: ColorFilter.mode( + Theme.of(context).colorScheme.onBackground, + BlendMode.srcIn), ), - ), - child: SvgPicture.asset( - 'assets/TF_log_horizontal.svg', - colorFilter: ColorFilter.mode( - Theme.of(context).colorScheme.onBackground, - BlendMode.srcIn), ), ), - ), - ListTile( - minLeadingWidth: 10, - leading: const Padding( - padding: EdgeInsets.only(left: 10), - child: Icon(Icons.home, size: 18)), - title: const Text('Home'), - onTap: () { - Navigator.pop(context); - globals.tabController.animateTo(0); - }, - ), - ListTile( - minLeadingWidth: 10, - leading: const Padding( - padding: EdgeInsets.only(left: 10), - child: Icon(Icons.article, size: 18)), - title: const Text('News'), - onTap: () { - Navigator.pop(context); - globals.tabController.animateTo(1); - }, - ), - ListTile( - minLeadingWidth: 10, - leading: const Padding( - padding: EdgeInsets.only(left: 10), - child: Icon(Icons.account_balance_wallet, size: 18)), - title: const Text('Wallet'), - onTap: () { - Navigator.pop(context); - globals.tabController.animateTo(2); - }, - ), - if (Globals().canSeeFarmers) ListTile( minLeadingWidth: 10, leading: const Padding( padding: EdgeInsets.only(left: 10), - child: Icon(Icons.storage, size: 18)), - title: const Text('Farming'), + child: Icon(Icons.home, size: 18)), + title: const Text('Home'), onTap: () { Navigator.pop(context); - globals.tabController.animateTo(3); + globals.tabController.animateTo(0); }, - ) - else - Container(), - ListTile( - minLeadingWidth: 10, - leading: const Padding( - padding: EdgeInsets.only(left: 10), - child: Icon(Icons.how_to_vote_outlined, size: 18)), - title: const Text('Dao'), - onTap: () { - Navigator.pop(context); - globals.tabController.animateTo(4); - }, - ), - ListTile( - minLeadingWidth: 10, - leading: const Padding( - padding: EdgeInsets.only(left: 10), - child: Icon(Icons.build, size: 18), ), - title: const Text('Support'), - onTap: () { - Navigator.pop(context); - globals.tabController.animateTo(5); - }, - ), - ListTile( - minLeadingWidth: 10, - leading: const Padding( - padding: EdgeInsets.only(left: 10), - child: Icon(Icons.person, size: 18)), - title: const Text('Identity'), - onTap: () { - Navigator.pop(context); - globals.tabController.animateTo(6); - }, - ), - ListTile( - minLeadingWidth: 10, - leading: const Padding( + ListTile( + minLeadingWidth: 10, + leading: const Padding( + padding: EdgeInsets.only(left: 10), + child: Icon(Icons.article, size: 18)), + title: const Text('News'), + onTap: () { + Navigator.pop(context); + globals.tabController.animateTo(1); + }, + ), + ListTile( + minLeadingWidth: 10, + leading: const Padding( + padding: EdgeInsets.only(left: 10), + child: Icon(Icons.account_balance_wallet, size: 18)), + title: const Text('Wallet'), + onTap: () { + Navigator.pop(context); + globals.tabController.animateTo(2); + }, + ), + if (Globals().canSeeFarmers) + ListTile( + minLeadingWidth: 10, + leading: const Padding( + padding: EdgeInsets.only(left: 10), + child: Icon(Icons.storage, size: 18)), + title: const Text('Farming'), + onTap: () { + Navigator.pop(context); + globals.tabController.animateTo(3); + }, + ) + else + Container(), + ListTile( + minLeadingWidth: 10, + leading: const Padding( + padding: EdgeInsets.only(left: 10), + child: Icon(Icons.how_to_vote_outlined, size: 18)), + title: const Text('Dao'), + onTap: () { + Navigator.pop(context); + globals.tabController.animateTo(4); + }, + ), + ListTile( + minLeadingWidth: 10, + leading: const Padding( padding: EdgeInsets.only(left: 10), - child: Icon(Icons.settings, size: 18)), - title: const Text('Settings'), - onTap: () { - Navigator.pop(context); - globals.tabController.animateTo(7); - }, - ), + child: Icon(Icons.build, size: 18), + ), + title: const Text('Support'), + onTap: () { + Navigator.pop(context); + globals.tabController.animateTo(5); + }, + ), + ListTile( + minLeadingWidth: 10, + leading: const Padding( + padding: EdgeInsets.only(left: 10), + child: Icon(Icons.person, size: 18)), + title: const Text('Identity'), + onTap: () { + Navigator.pop(context); + globals.tabController.animateTo(6); + }, + ), + ListTile( + minLeadingWidth: 10, + leading: const Padding( + padding: EdgeInsets.only(left: 10), + child: Icon(Icons.settings, size: 18)), + title: const Text('Settings'), + onTap: () { + Navigator.pop(context); + globals.tabController.animateTo(7); + }, + ), + ], + ), + ), + bottomNavigationBar: BottomNavigationBar( + onTap: _selectScreen, + selectedIconTheme: selectedIconTheme, + selectedItemColor: selectedItemColor, + showUnselectedLabels: true, + selectedFontSize: selectedFontSize, + unselectedFontSize: 12, + currentIndex: currentScreenIndex, + items: const [ + BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'), + BottomNavigationBarItem( + icon: Icon(Icons.account_balance_wallet), label: 'Wallet'), + BottomNavigationBarItem(icon: Icon(Icons.storage), label: 'Farms'), + BottomNavigationBarItem( + icon: Icon(Icons.settings), label: 'Settings'), ], ), ), - bottomNavigationBar: BottomNavigationBar( - onTap: _selectScreen, - selectedIconTheme: selectedIconTheme, - selectedItemColor: selectedItemColor, - showUnselectedLabels: true, - selectedFontSize: selectedFontSize, - unselectedFontSize: 12, - currentIndex: currentScreenIndex, - items: const [ - BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'), - BottomNavigationBarItem( - icon: Icon(Icons.account_balance_wallet), label: 'Wallet'), - BottomNavigationBarItem(icon: Icon(Icons.storage), label: 'Farms'), - BottomNavigationBarItem( - icon: Icon(Icons.settings), label: 'Settings'), - ], - ), ); } } From 1a1a0fb6c9f61cfec86e205cce072789f883557e Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Thu, 26 Sep 2024 11:01:56 +0300 Subject: [PATCH 09/56] init send screen --- app/lib/screens/wallets/send.dart | 199 +++++++++++++++++++++++++- app/lib/services/stellar_service.dart | 1 + 2 files changed, 198 insertions(+), 2 deletions(-) diff --git a/app/lib/screens/wallets/send.dart b/app/lib/screens/wallets/send.dart index 758dde0e..a824bc87 100644 --- a/app/lib/screens/wallets/send.dart +++ b/app/lib/screens/wallets/send.dart @@ -1,6 +1,11 @@ import 'package:flutter/material.dart'; import 'package:threebotlogin/models/wallet.dart'; +enum ChainType { + Stellar, + TFChain, +} + class WalletSendScreen extends StatefulWidget { const WalletSendScreen({super.key, required this.wallet}); final Wallet wallet; @@ -9,8 +14,198 @@ class WalletSendScreen extends StatefulWidget { } class _WalletSendScreenState extends State { + final fromController = TextEditingController(); + final toController = TextEditingController(); + final amountController = TextEditingController(); + final memoController = TextEditingController(); + ChainType chainType = ChainType.Stellar; + + @override + void initState() { + fromController.text = widget.wallet.stellarAddress; + super.initState(); + } + + @override + void dispose() { + fromController.dispose(); + toController.dispose(); + amountController.dispose(); + memoController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - return const Placeholder(); + final width = MediaQuery.of(context).size.width; + return Scaffold( + appBar: AppBar(title: const Text('Send')), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column(children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + onPressed: () => setState(() { + fromController.text = widget.wallet.stellarAddress; + chainType = ChainType.Stellar; + }), + style: ElevatedButton.styleFrom( + fixedSize: Size.fromWidth(width / 3), + backgroundColor: chainType == ChainType.Stellar + ? Theme.of(context).colorScheme.primaryContainer + : Theme.of(context).colorScheme.background, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(3), + side: BorderSide( + color: chainType == ChainType.Stellar + ? Theme.of(context) + .colorScheme + .primaryContainer + : Theme.of(context) + .colorScheme + .secondaryContainer))), + child: Text( + 'Stellar', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + color: chainType == ChainType.Stellar + ? Theme.of(context).colorScheme.onPrimaryContainer + : Theme.of(context).colorScheme.onBackground), + ), + ), + ElevatedButton( + onPressed: () => setState(() { + fromController.text = widget.wallet.tfchainAddress; + chainType = ChainType.TFChain; + }), + style: ElevatedButton.styleFrom( + fixedSize: Size.fromWidth(width / 3), + backgroundColor: chainType == ChainType.TFChain + ? Theme.of(context).colorScheme.primaryContainer + : Theme.of(context).colorScheme.background, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + side: BorderSide( + color: chainType == ChainType.TFChain + ? Theme.of(context) + .colorScheme + .primaryContainer + : Theme.of(context) + .colorScheme + .secondaryContainer))), + child: Text( + 'TFChain', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + color: chainType == ChainType.TFChain + ? Theme.of(context).colorScheme.onPrimaryContainer + : Theme.of(context).colorScheme.onBackground), + ), + ), + ], + ), + ), + const SizedBox(height: 40), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), + child: TextButton( + onPressed: () { + //TODO: Scan qr code + }, + style: TextButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(2), + ), + side: BorderSide( + color: Theme.of(context).colorScheme.primary, + ), + ), + child: SizedBox( + width: double.infinity, + child: Text( + 'Scan QR code', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + ), + ), + ), + const SizedBox(height: 20), + ListTile( + title: TextField( + readOnly: true, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), + controller: fromController, + decoration: InputDecoration( + labelText: 'From (name: ${widget.wallet.name})', + )), + ), + const SizedBox(height: 10), + ListTile( + title: TextField( + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), + controller: toController, + decoration: const InputDecoration( + labelText: 'To', + )), + ), + const SizedBox(height: 10), + ListTile( + title: TextField( + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), + keyboardType: TextInputType.number, + controller: amountController, + decoration: InputDecoration( + labelText: + 'Amount (Balance: ${chainType == ChainType.Stellar ? widget.wallet.stellarBalance : widget.wallet.tfchainBalance})', + hintText: '100')), + ), + const SizedBox(height: 10), + if (chainType == ChainType.Stellar) + ListTile( + title: TextField( + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), + controller: memoController, + decoration: const InputDecoration( + labelText: 'Memo', + )), + ), + const SizedBox(height: 40), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), + child: ElevatedButton( + onPressed: () { + //TODO: Show confirmation page + }, + style: ElevatedButton.styleFrom(), + child: SizedBox( + width: double.infinity, + child: Text( + 'Transfer', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + ), + ), + ), + ]), + ), + ), + ); } -} \ No newline at end of file +} diff --git a/app/lib/services/stellar_service.dart b/app/lib/services/stellar_service.dart index 219d150f..37329b02 100644 --- a/app/lib/services/stellar_service.dart +++ b/app/lib/services/stellar_service.dart @@ -32,6 +32,7 @@ Future getBalance(String secret) async { } Future> listTransactions(String secret) async { + //TODO: try catch error in case the wallet doesn't exist and return [] final client = Client(NetworkType.PUBLIC, secret); final transactions = await client.getTransactions(assetCodeFilter: 'TFT'); return transactions; From 944b57f18193e74a5923f7aa78538a3e119cbdc4 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Thu, 26 Sep 2024 11:08:44 +0300 Subject: [PATCH 10/56] Add subtitle for amount field for max fee --- app/lib/screens/wallets/send.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/lib/screens/wallets/send.dart b/app/lib/screens/wallets/send.dart index a824bc87..35372549 100644 --- a/app/lib/screens/wallets/send.dart +++ b/app/lib/screens/wallets/send.dart @@ -170,6 +170,8 @@ class _WalletSendScreenState extends State { labelText: 'Amount (Balance: ${chainType == ChainType.Stellar ? widget.wallet.stellarBalance : widget.wallet.tfchainBalance})', hintText: '100')), + subtitle: Text( + 'Max Fee: ${chainType == ChainType.Stellar ? 0.1 : 0.01} TFT'), ), const SizedBox(height: 10), if (chainType == ChainType.Stellar) From 9731c7872a10aeeb52ddff2c8bd38eda67bafabb Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Thu, 26 Sep 2024 12:12:23 +0300 Subject: [PATCH 11/56] Add scanning QR code --- app/lib/screens/wallets/send.dart | 34 ++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/app/lib/screens/wallets/send.dart b/app/lib/screens/wallets/send.dart index 35372549..5aceb254 100644 --- a/app/lib/screens/wallets/send.dart +++ b/app/lib/screens/wallets/send.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:qr_code_scanner/qr_code_scanner.dart'; import 'package:threebotlogin/models/wallet.dart'; +import 'package:threebotlogin/screens/scan_screen.dart'; enum ChainType { Stellar, @@ -19,6 +22,7 @@ class _WalletSendScreenState extends State { final amountController = TextEditingController(); final memoController = TextEditingController(); ChainType chainType = ChainType.Stellar; + // TODO: Add validation on all fields @override void initState() { @@ -113,7 +117,7 @@ class _WalletSendScreenState extends State { padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), child: TextButton( onPressed: () { - //TODO: Scan qr code + scanQrCode(); }, style: TextButton.styleFrom( shape: RoundedRectangleBorder( @@ -210,4 +214,32 @@ class _WalletSendScreenState extends State { ), ); } + + scanQrCode() async { + await SystemChannels.textInput.invokeMethod('TextInput.hide'); + // QRCode scanner is black if we don't sleep here. + bool slept = + await Future.delayed(const Duration(milliseconds: 400), () => true); + late Barcode result; + if (slept) { + if (context.mounted) { + result = await Navigator.push(context, + MaterialPageRoute(builder: (context) => const ScanScreen())); + } + } + if (result.code != null) { + final code = Uri.parse(result.code!); + toController.text = code.path; + if (code.queryParameters.containsKey('amount')) { + amountController.text = code.queryParameters['amount']!; + } + if (chainType == ChainType.Stellar && + code.queryParameters.containsKey('message')) { + memoController.text = code.queryParameters['message']!; + } + setState(() {}); + } + + return result.code; + } } From d8c0d9ee041ce9a75057c03ae7112130a871e592 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Thu, 26 Sep 2024 12:24:04 +0300 Subject: [PATCH 12/56] Adjust the scan QR code colors --- app/lib/screens/scan_screen.dart | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/app/lib/screens/scan_screen.dart b/app/lib/screens/scan_screen.dart index 670aeaec..9d46f99f 100644 --- a/app/lib/screens/scan_screen.dart +++ b/app/lib/screens/scan_screen.dart @@ -52,7 +52,7 @@ class _ScanScreenState extends State { color: Colors.transparent, child: Container( decoration: BoxDecoration( - color: Theme.of(context).primaryColor, + color: Theme.of(context).colorScheme.primaryContainer, borderRadius: const BorderRadius.only( topLeft: Radius.circular(20.0), topRight: Radius.circular(20.0))), @@ -72,13 +72,13 @@ class _ScanScreenState extends State { }, child: const Icon(Icons.arrow_back_ios), ), - const Text( + Text( 'Scan QR', textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 21.0), + style: Theme.of(context).textTheme.titleLarge!.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + fontWeight: FontWeight.bold, + ), ), const SizedBox( width: 60.0, @@ -88,12 +88,12 @@ class _ScanScreenState extends State { ), ), Container( - color: Theme.of(context).primaryColor, + color: Theme.of(context).colorScheme.primaryContainer, child: Container( color: Colors.transparent, child: Container( decoration: BoxDecoration( - color: Theme.of(context).scaffoldBackgroundColor, + color: Theme.of(context).colorScheme.secondaryContainer, borderRadius: const BorderRadius.only( topLeft: Radius.circular(20.0), topRight: Radius.circular(20.0))), @@ -107,7 +107,10 @@ class _ScanScreenState extends State { child: Center( child: Text( helperText, - style: const TextStyle(fontSize: 16.0), + style: Theme.of(context).textTheme.bodyLarge!.copyWith( + color: Theme.of(context) + .colorScheme + .onSecondaryContainer), ), ), ), From f1323e30b91b1d11d9664c1f9b605ef0e54bc474 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Thu, 26 Sep 2024 12:47:38 +0300 Subject: [PATCH 13/56] Add Receive screen --- app/lib/screens/wallets/receive.dart | 158 ++++++++++++++++++++++++++- 1 file changed, 156 insertions(+), 2 deletions(-) diff --git a/app/lib/screens/wallets/receive.dart b/app/lib/screens/wallets/receive.dart index 14ab8fff..1a58462f 100644 --- a/app/lib/screens/wallets/receive.dart +++ b/app/lib/screens/wallets/receive.dart @@ -1,6 +1,11 @@ import 'package:flutter/material.dart'; import 'package:threebotlogin/models/wallet.dart'; +enum ChainType { + Stellar, + TFChain, +} + class WalletReceiveScreen extends StatefulWidget { const WalletReceiveScreen({super.key, required this.wallet}); final Wallet wallet; @@ -10,8 +15,157 @@ class WalletReceiveScreen extends StatefulWidget { } class _WalletReceiveScreenState extends State { + final toController = TextEditingController(); + final amountController = TextEditingController(); + final memoController = TextEditingController(); + ChainType chainType = ChainType.Stellar; + // TODO: Add validation on all fields + + @override + void initState() { + toController.text = widget.wallet.stellarAddress; + super.initState(); + } + + @override + void dispose() { + toController.dispose(); + amountController.dispose(); + memoController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - return const Placeholder(); + final width = MediaQuery.of(context).size.width; + return Scaffold( + appBar: AppBar(title: const Text('Receive')), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column(children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + onPressed: () => setState(() { + toController.text = widget.wallet.stellarAddress; + chainType = ChainType.Stellar; + }), + style: ElevatedButton.styleFrom( + fixedSize: Size.fromWidth(width / 3), + backgroundColor: chainType == ChainType.Stellar + ? Theme.of(context).colorScheme.primaryContainer + : Theme.of(context).colorScheme.background, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(3), + side: BorderSide( + color: chainType == ChainType.Stellar + ? Theme.of(context) + .colorScheme + .primaryContainer + : Theme.of(context) + .colorScheme + .secondaryContainer))), + child: Text( + 'Stellar', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + color: chainType == ChainType.Stellar + ? Theme.of(context).colorScheme.onPrimaryContainer + : Theme.of(context).colorScheme.onBackground), + ), + ), + ElevatedButton( + onPressed: () => setState(() { + toController.text = widget.wallet.tfchainAddress; + chainType = ChainType.TFChain; + }), + style: ElevatedButton.styleFrom( + fixedSize: Size.fromWidth(width / 3), + backgroundColor: chainType == ChainType.TFChain + ? Theme.of(context).colorScheme.primaryContainer + : Theme.of(context).colorScheme.background, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + side: BorderSide( + color: chainType == ChainType.TFChain + ? Theme.of(context) + .colorScheme + .primaryContainer + : Theme.of(context) + .colorScheme + .secondaryContainer))), + child: Text( + 'TFChain', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + color: chainType == ChainType.TFChain + ? Theme.of(context).colorScheme.onPrimaryContainer + : Theme.of(context).colorScheme.onBackground), + ), + ), + ], + ), + ), + const SizedBox(height: 40), + ListTile( + title: TextField( + readOnly: true, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), + controller: toController, + decoration: InputDecoration( + labelText: 'To (name: ${widget.wallet.name})', + )), + ), + const SizedBox(height: 10), + ListTile( + title: TextField( + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), + keyboardType: TextInputType.number, + controller: amountController, + decoration: const InputDecoration( + labelText: 'Amount', hintText: '100')), + ), + const SizedBox(height: 10), + if (chainType == ChainType.Stellar) + ListTile( + title: TextField( + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), + controller: memoController, + decoration: const InputDecoration( + labelText: 'Memo', + )), + ), + const SizedBox(height: 40), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), + child: ElevatedButton( + onPressed: () { + //TODO: Show qr code + }, + style: ElevatedButton.styleFrom(), + child: SizedBox( + width: double.infinity, + child: Text( + 'Generate QR code', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + ), + ), + ), + ]), + ), + ), + ); } -} \ No newline at end of file +} From 312ac0be41e54a4558b9db9f2129cb73d26349bd Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Thu, 26 Sep 2024 14:05:18 +0300 Subject: [PATCH 14/56] Add qr code screen --- app/lib/screens/qr_code_screen.dart | 59 +++++++++++++++ app/pubspec.lock | 110 +++++++++++++++++++++------- app/pubspec.yaml | 2 + 3 files changed, 146 insertions(+), 25 deletions(-) create mode 100644 app/lib/screens/qr_code_screen.dart diff --git a/app/lib/screens/qr_code_screen.dart b/app/lib/screens/qr_code_screen.dart new file mode 100644 index 00000000..13a1a82d --- /dev/null +++ b/app/lib/screens/qr_code_screen.dart @@ -0,0 +1,59 @@ +/* + * QR.Flutter + * Copyright (c) 2019 the QR.Flutter authors. + * See LICENSE for distribution and usage details. + */ + +import 'package:flutter/material.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:screen_brightness/screen_brightness.dart'; +import 'package:threebotlogin/widgets/custom_dialog.dart'; + +/// This is the screen that you'll see when the app starts +class GenerateQRCodeScreen extends StatefulWidget { + const GenerateQRCodeScreen({ + super.key, + required this.message, + }); + + final String message; + + @override + State createState() => _GenerateQRCodeScreenState(); +} + +class _GenerateQRCodeScreenState extends State { + @override + void initState() { + ScreenBrightness().setScreenBrightness(1); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return CustomDialog( + title: 'Scan QR code', + image: Icons.qr_code, + widgetDescription: Center( + child: Container( + color: Colors.white, + width: 280, + child: QrImageView( + data: widget.message, + version: QrVersions.auto, + ), + ), + ), + actions: [ + TextButton( + child: const Text('Close'), + onPressed: () { + ScreenBrightness().resetScreenBrightness(); + Navigator.pop(context); + setState(() {}); + }, + ), + ], + ); + } +} diff --git a/app/pubspec.lock b/app/pubspec.lock index 4b0cb882..9394d61f 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -401,9 +401,11 @@ packages: flutter_pkid: dependency: "direct main" description: - path: "/sandbox/code/github/threefoldtech/threefold_connect_flutter_pkid_client" - relative: false - source: path + path: "." + ref: master + resolved-ref: "48123e66597f6fb448edb14183935b0b243163a6" + url: "https://github.com/threefoldtech/threefold_connect_flutter_pkid_client" + source: git version: "0.0.1" flutter_plugin_android_lifecycle: dependency: transitive @@ -474,11 +476,9 @@ packages: gridproxy_client: dependency: "direct main" description: - path: "packages/gridproxy_client" - ref: tfchain_graphql_hotfix - resolved-ref: "314f46728e43cb86f430cda6ba756d4be3fb6e07" - url: "https://github.com/codescalers/tfgrid-sdk-dart" - source: git + path: "../../../codescalers/tfgrid-sdk-dart/packages/gridproxy_client" + relative: true + source: path version: "1.0.0" hashlib: dependency: transitive @@ -724,10 +724,10 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.3.2" path_provider_linux: dependency: transitive description: @@ -904,6 +904,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + qr: + dependency: transitive + description: + name: qr + sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" qr_code_scanner: dependency: "direct main" description: @@ -912,6 +920,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + qr_flutter: + dependency: "direct main" + description: + name: qr_flutter + sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097" + url: "https://pub.dev" + source: hosted + version: "4.1.0" quiver: dependency: transitive description: @@ -968,6 +984,54 @@ packages: url: "https://pub.dev" source: hosted version: "0.27.7" + screen_brightness: + dependency: "direct main" + description: + name: screen_brightness + sha256: "7d4ac84ae26b37c01d6f5db7123a72db7933e1f2a2a8c369a51e08f81b3178d8" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + screen_brightness_android: + dependency: transitive + description: + name: screen_brightness_android + sha256: "8c69d3ac475e4d625e7fa682a3a51a69ff59abe5b4a9e57f6ec7d830a6c69bd6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + screen_brightness_ios: + dependency: transitive + description: + name: screen_brightness_ios + sha256: f08f70ca1ac3e30719764b5cfb8b3fe1e28163065018a41b3e6f243ab146c2f1 + url: "https://pub.dev" + source: hosted + version: "1.0.1" + screen_brightness_macos: + dependency: transitive + description: + name: screen_brightness_macos + sha256: "70c2efa4534e22b927e82693488f127dd4a0f008469fccf4f0eefe9061bbdd6a" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + screen_brightness_platform_interface: + dependency: transitive + description: + name: screen_brightness_platform_interface + sha256: "9f3ebf7f22d5487e7676fe9ddaf3fc55b6ff8057707cf6dc0121c7dfda346a16" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + screen_brightness_windows: + dependency: transitive + description: + name: screen_brightness_windows + sha256: c8e12a91cf6dd912a48bd41fcf749282a51afa17f536c3460d8d05702fb89ffa + url: "https://pub.dev" + source: hosted + version: "1.0.1" secp256k1_ecdsa: dependency: transitive description: @@ -996,10 +1060,10 @@ packages: dependency: transitive description: name: shared_preferences_foundation - sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833" + sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.3.5" shared_preferences_linux: dependency: transitive description: @@ -1153,11 +1217,9 @@ packages: stellar_client: dependency: "direct main" description: - path: "packages/stellar_client" - ref: tfchain_graphql_hotfix - resolved-ref: "314f46728e43cb86f430cda6ba756d4be3fb6e07" - url: "https://github.com/codescalers/tfgrid-sdk-dart" - source: git + path: "../../../codescalers/tfgrid-sdk-dart/packages/stellar_client" + relative: true + source: path version: "0.1.0" stellar_flutter_sdk: dependency: transitive @@ -1242,11 +1304,9 @@ packages: tfchain_client: dependency: "direct main" description: - path: "packages/tfchain_client" - ref: tfchain_graphql_hotfix - resolved-ref: "314f46728e43cb86f430cda6ba756d4be3fb6e07" - url: "https://github.com/codescalers/tfgrid-sdk-dart" - source: git + path: "../../../codescalers/tfgrid-sdk-dart/packages/tfchain_client" + relative: true + source: path version: "0.1.0" timing: dependency: transitive @@ -1348,10 +1408,10 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e + sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.2.4" url_launcher_linux: dependency: transitive description: @@ -1514,4 +1574,4 @@ packages: version: "1.1.4+10" sdks: dart: ">=3.2.3 <4.0.0" - flutter: ">=3.16.6" + flutter: ">=3.16.0" diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 66269a8a..62d1d707 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -59,6 +59,8 @@ dependencies: pinenacl: ^0.5.1 pinput: 3.0.1 build_runner: ^2.4.9 + qr_flutter: ^4.1.0 + screen_brightness: ^1.0.1 dev_dependencies: flutter_test: From d8b873fd33a792f24e1fd12df6eb7e03317e7d9d Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Thu, 26 Sep 2024 14:40:45 +0300 Subject: [PATCH 15/56] Use qr code generator screen in the receive screen --- app/lib/screens/wallets/receive.dart | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/app/lib/screens/wallets/receive.dart b/app/lib/screens/wallets/receive.dart index 1a58462f..395793ec 100644 --- a/app/lib/screens/wallets/receive.dart +++ b/app/lib/screens/wallets/receive.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:threebotlogin/models/wallet.dart'; +import 'package:threebotlogin/screens/qr_code_screen.dart'; enum ChainType { Stellar, @@ -148,7 +149,21 @@ class _WalletReceiveScreenState extends State { padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), child: ElevatedButton( onPressed: () { - //TODO: Show qr code + //TODO: Validate inputs first + final quaryParams = {'amount': amountController.text}; + if (chainType == ChainType.Stellar) { + quaryParams['message'] = memoController.text; + } + final uri = Uri( + scheme: 'TFT', + path: toController.text, + queryParameters: quaryParams); + final codeMessage = uri.toString(); + showDialog( + context: context, + builder: (context) => + GenerateQRCodeScreen(message: codeMessage), + ); }, style: ElevatedButton.styleFrom(), child: SizedBox( From 87517340129994125dbe580c3f3d37ca97f7a1fc Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Thu, 26 Sep 2024 14:49:41 +0300 Subject: [PATCH 16/56] Add suffix TFT in the amount fields --- app/lib/screens/wallets/receive.dart | 2 +- app/lib/screens/wallets/send.dart | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/lib/screens/wallets/receive.dart b/app/lib/screens/wallets/receive.dart index 395793ec..ddf1d9f5 100644 --- a/app/lib/screens/wallets/receive.dart +++ b/app/lib/screens/wallets/receive.dart @@ -130,7 +130,7 @@ class _WalletReceiveScreenState extends State { keyboardType: TextInputType.number, controller: amountController, decoration: const InputDecoration( - labelText: 'Amount', hintText: '100')), + suffixText: 'TFT', labelText: 'Amount', hintText: '100')), ), const SizedBox(height: 10), if (chainType == ChainType.Stellar) diff --git a/app/lib/screens/wallets/send.dart b/app/lib/screens/wallets/send.dart index 5aceb254..f8ffcbef 100644 --- a/app/lib/screens/wallets/send.dart +++ b/app/lib/screens/wallets/send.dart @@ -173,7 +173,8 @@ class _WalletSendScreenState extends State { decoration: InputDecoration( labelText: 'Amount (Balance: ${chainType == ChainType.Stellar ? widget.wallet.stellarBalance : widget.wallet.tfchainBalance})', - hintText: '100')), + hintText: '100', + suffixText: 'TFT')), subtitle: Text( 'Max Fee: ${chainType == ChainType.Stellar ? 0.1 : 0.01} TFT'), ), From bb6fa364891e754bedcf34e9e94407bef0d32ecf Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Thu, 26 Sep 2024 21:36:21 +0300 Subject: [PATCH 17/56] Add Send confirmation bottom sheet --- app/lib/models/wallet.dart | 1 + app/lib/screens/wallets/receive.dart | 4 - app/lib/screens/wallets/send.dart | 26 +++- .../widgets/wallets/send_confirmation.dart | 132 ++++++++++++++++++ 4 files changed, 152 insertions(+), 11 deletions(-) create mode 100644 app/lib/widgets/wallets/send_confirmation.dart diff --git a/app/lib/models/wallet.dart b/app/lib/models/wallet.dart index aa6b95df..06fe1d96 100644 --- a/app/lib/models/wallet.dart +++ b/app/lib/models/wallet.dart @@ -1,4 +1,5 @@ enum WalletType { Native, Imported } +enum ChainType {Stellar, TFChain } class Wallet { Wallet({ diff --git a/app/lib/screens/wallets/receive.dart b/app/lib/screens/wallets/receive.dart index ddf1d9f5..837d2202 100644 --- a/app/lib/screens/wallets/receive.dart +++ b/app/lib/screens/wallets/receive.dart @@ -2,10 +2,6 @@ import 'package:flutter/material.dart'; import 'package:threebotlogin/models/wallet.dart'; import 'package:threebotlogin/screens/qr_code_screen.dart'; -enum ChainType { - Stellar, - TFChain, -} class WalletReceiveScreen extends StatefulWidget { const WalletReceiveScreen({super.key, required this.wallet}); diff --git a/app/lib/screens/wallets/send.dart b/app/lib/screens/wallets/send.dart index f8ffcbef..f5d1f819 100644 --- a/app/lib/screens/wallets/send.dart +++ b/app/lib/screens/wallets/send.dart @@ -3,11 +3,7 @@ import 'package:flutter/services.dart'; import 'package:qr_code_scanner/qr_code_scanner.dart'; import 'package:threebotlogin/models/wallet.dart'; import 'package:threebotlogin/screens/scan_screen.dart'; - -enum ChainType { - Stellar, - TFChain, -} +import 'package:threebotlogin/widgets/wallets/send_confirmation.dart'; class WalletSendScreen extends StatefulWidget { const WalletSendScreen({super.key, required this.wallet}); @@ -194,8 +190,9 @@ class _WalletSendScreenState extends State { Padding( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), child: ElevatedButton( - onPressed: () { - //TODO: Show confirmation page + onPressed: () async { + // TODO: Trigger validation here + await _send_confirmation(); }, style: ElevatedButton.styleFrom(), child: SizedBox( @@ -243,4 +240,19 @@ class _WalletSendScreenState extends State { return result.code; } + + _send_confirmation() async { + showModalBottomSheet( + isScrollControlled: true, + useSafeArea: true, + constraints: const BoxConstraints(maxWidth: double.infinity), + context: context, + builder: (ctx) => SendConfirmationWidget( + chainType: chainType, + from: fromController.text, + to: toController.text, + amount: amountController.text, + memo: memoController.text, + )); + } } diff --git a/app/lib/widgets/wallets/send_confirmation.dart b/app/lib/widgets/wallets/send_confirmation.dart new file mode 100644 index 00000000..89616fbb --- /dev/null +++ b/app/lib/widgets/wallets/send_confirmation.dart @@ -0,0 +1,132 @@ +import 'package:flutter/material.dart'; +import 'package:threebotlogin/models/wallet.dart'; + +class SendConfirmationWidget extends StatefulWidget { + const SendConfirmationWidget({ + super.key, + required this.chainType, + required this.from, + required this.to, + required this.amount, + required this.memo, + }); + + final ChainType chainType; + final String from; + final String to; + final String amount; + final String memo; + + @override + State createState() => _SendConfirmationWidgetState(); +} + +class _SendConfirmationWidgetState extends State { + final fromController = TextEditingController(); + final toController = TextEditingController(); + final amountController = TextEditingController(); + final memoController = TextEditingController(); + + @override + void initState() { + fromController.text = widget.from; + toController.text = widget.to; + amountController.text = widget.amount; + memoController.text = widget.memo; + super.initState(); + } + + @override + void dispose() { + fromController.dispose(); + toController.dispose(); + amountController.dispose(); + memoController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column(children: [ + Text( + 'Send Confirmation', + style: Theme.of(context).textTheme.headlineSmall!.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), + ), + ListTile( + title: TextField( + readOnly: true, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), + controller: fromController, + decoration: const InputDecoration( + labelText: 'From', + )), + ), + const SizedBox(height: 10), + ListTile( + title: TextField( + readOnly: true, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), + controller: toController, + decoration: const InputDecoration( + labelText: 'To', + )), + ), + const SizedBox(height: 10), + ListTile( + title: TextField( + readOnly: true, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), + keyboardType: TextInputType.number, + controller: amountController, + decoration: const InputDecoration( + labelText: 'Amount', hintText: '100', suffixText: 'TFT')), + subtitle: Text( + 'Max Fee: ${widget.chainType == ChainType.Stellar ? 0.1 : 0.01} TFT'), + ), + const SizedBox(height: 10), + if (widget.chainType == ChainType.Stellar) + ListTile( + title: TextField( + readOnly: true, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), + controller: memoController, + decoration: const InputDecoration( + labelText: 'Memo', + )), + ), + const SizedBox(height: 30), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), + child: ElevatedButton( + onPressed: () async {}, + style: ElevatedButton.styleFrom(), + child: SizedBox( + width: double.infinity, + child: Text( + 'Confirm', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + ), + ), + ), + ]), + ), + ); + } +} From 85b718ed31f0a1adcaf77521417fabe74502e95d Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Thu, 26 Sep 2024 21:50:42 +0300 Subject: [PATCH 18/56] Add loading to confirm send --- .../widgets/wallets/send_confirmation.dart | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/app/lib/widgets/wallets/send_confirmation.dart b/app/lib/widgets/wallets/send_confirmation.dart index 89616fbb..1b2420a3 100644 --- a/app/lib/widgets/wallets/send_confirmation.dart +++ b/app/lib/widgets/wallets/send_confirmation.dart @@ -26,6 +26,7 @@ class _SendConfirmationWidgetState extends State { final toController = TextEditingController(); final amountController = TextEditingController(); final memoController = TextEditingController(); + bool loading = false; @override void initState() { @@ -110,12 +111,16 @@ class _SendConfirmationWidgetState extends State { const SizedBox(height: 30), Padding( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), - child: ElevatedButton( - onPressed: () async {}, - style: ElevatedButton.styleFrom(), - child: SizedBox( - width: double.infinity, - child: Text( + child: SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _send, + child: loading? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 3, + )): Text( 'Confirm', style: Theme.of(context).textTheme.titleLarge!.copyWith( color: Theme.of(context).colorScheme.primary, @@ -129,4 +134,14 @@ class _SendConfirmationWidgetState extends State { ), ); } + _send() async { + setState(() { + loading = true; + }); + await Future.delayed(Duration(seconds: 5)); + + setState(() { + loading = false; + }); + } } From f6dd028ab714987757032373a531bce69a0e724c Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Thu, 26 Sep 2024 22:14:38 +0300 Subject: [PATCH 19/56] Add transfer method to stellar --- app/lib/services/stellar_service.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/lib/services/stellar_service.dart b/app/lib/services/stellar_service.dart index 37329b02..b56f45a4 100644 --- a/app/lib/services/stellar_service.dart +++ b/app/lib/services/stellar_service.dart @@ -43,3 +43,13 @@ Future?> listVestedAccounts(String secret) async { final accounts = await client.getVestingAccounts(); return accounts; } + +Future transfer(String secret, String dest, String amount, String memo) async { + final client = Client(NetworkType.PUBLIC, secret); + await client.transferThroughThreefoldService( + destinationAddress: dest, + amount: amount, + currency: 'TFT', + memoText: memo, + ); +} \ No newline at end of file From 7b54b33d4a32ed2b15637e680eee9d8163ef3313 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Thu, 26 Sep 2024 22:14:53 +0300 Subject: [PATCH 20/56] Add transfer method to tfchain --- app/lib/services/tfchain_service.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/lib/services/tfchain_service.dart b/app/lib/services/tfchain_service.dart index 88c99549..8e21085c 100644 --- a/app/lib/services/tfchain_service.dart +++ b/app/lib/services/tfchain_service.dart @@ -137,3 +137,10 @@ Future createFarm( throw Exception('Failed to create farm due to $e'); } } + +transfer(String secret, String dest, String amount) async { + final chainUrl = Globals().chainUrl; + final client = TFChain.Client(chainUrl, secret, 'sr25519'); + client.connect(); + await client.balances.transfer(address: dest, amount: BigInt.parse(amount)); +} From ffe46ec521aa04c6bc9a043a80f5f8b5a0723b4d Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Thu, 26 Sep 2024 23:02:59 +0300 Subject: [PATCH 21/56] Add transfer logic --- app/lib/screens/wallets/send.dart | 3 + app/lib/services/tfchain_service.dart | 2 +- .../widgets/wallets/send_confirmation.dart | 70 +++++++++++++++---- 3 files changed, 61 insertions(+), 14 deletions(-) diff --git a/app/lib/screens/wallets/send.dart b/app/lib/screens/wallets/send.dart index f5d1f819..0affb319 100644 --- a/app/lib/screens/wallets/send.dart +++ b/app/lib/screens/wallets/send.dart @@ -249,6 +249,9 @@ class _WalletSendScreenState extends State { context: context, builder: (ctx) => SendConfirmationWidget( chainType: chainType, + secret: chainType == ChainType.Stellar + ? widget.wallet.stellarSecret + : widget.wallet.tfchainSecret, from: fromController.text, to: toController.text, amount: amountController.text, diff --git a/app/lib/services/tfchain_service.dart b/app/lib/services/tfchain_service.dart index 8e21085c..dbd48270 100644 --- a/app/lib/services/tfchain_service.dart +++ b/app/lib/services/tfchain_service.dart @@ -138,7 +138,7 @@ Future createFarm( } } -transfer(String secret, String dest, String amount) async { +Future transfer(String secret, String dest, String amount) async { final chainUrl = Globals().chainUrl; final client = TFChain.Client(chainUrl, secret, 'sr25519'); client.connect(); diff --git a/app/lib/widgets/wallets/send_confirmation.dart b/app/lib/widgets/wallets/send_confirmation.dart index 1b2420a3..4e305e77 100644 --- a/app/lib/widgets/wallets/send_confirmation.dart +++ b/app/lib/widgets/wallets/send_confirmation.dart @@ -1,10 +1,14 @@ import 'package:flutter/material.dart'; import 'package:threebotlogin/models/wallet.dart'; +import 'package:threebotlogin/services/stellar_service.dart' as Stellar; +import 'package:threebotlogin/services/tfchain_service.dart' as TFChain; +import 'package:threebotlogin/widgets/custom_dialog.dart'; class SendConfirmationWidget extends StatefulWidget { const SendConfirmationWidget({ super.key, required this.chainType, + required this.secret, required this.from, required this.to, required this.amount, @@ -12,6 +16,7 @@ class SendConfirmationWidget extends StatefulWidget { }); final ChainType chainType; + final String secret; final String from; final String to; final String amount; @@ -115,18 +120,20 @@ class _SendConfirmationWidgetState extends State { width: double.infinity, child: ElevatedButton( onPressed: _send, - child: loading? const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - strokeWidth: 3, - )): Text( - 'Confirm', - style: Theme.of(context).textTheme.titleLarge!.copyWith( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.bold), - textAlign: TextAlign.center, - ), + child: loading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 3, + )) + : Text( + 'Confirm', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), ), ), ), @@ -134,14 +141,51 @@ class _SendConfirmationWidgetState extends State { ), ); } + _send() async { setState(() { loading = true; }); - await Future.delayed(Duration(seconds: 5)); + try { + if (widget.chainType == ChainType.Stellar) { + await Stellar.transfer( + widget.secret, widget.to, widget.amount, widget.memo); + } else { + await TFChain.transfer(widget.secret, widget.to, widget.amount); + } + await _showDialog( + 'Success!', 'Tokens have been transfered successfully', Icons.check); + } catch (e) { + _showDialog( + 'Error', 'Failed to transfer. Please try again.', Icons.error); + setState(() { + loading = false; + }); + return; + } setState(() { loading = false; }); + if (!context.mounted) return; + Navigator.pop(context); + } + + Future _showDialog(String title, String message, IconData icon) async { + showDialog( + barrierDismissible: false, + context: context, + builder: (BuildContext context) => CustomDialog( + image: icon, + title: title, + description: message, + ), + ); + await Future.delayed( + const Duration(seconds: 3), + () { + Navigator.pop(context); + }, + ); } } From c7295f902694032e6b0b32144b40368cb21ea238 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Fri, 27 Sep 2024 00:36:28 +0300 Subject: [PATCH 22/56] Add contacts service --- app/lib/services/contact_service.dart | 31 +++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 app/lib/services/contact_service.dart diff --git a/app/lib/services/contact_service.dart b/app/lib/services/contact_service.dart new file mode 100644 index 00000000..9ccbd820 --- /dev/null +++ b/app/lib/services/contact_service.dart @@ -0,0 +1,31 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter_pkid/flutter_pkid.dart'; +import 'package:threebotlogin/apps/wallet/wallet_config.dart'; +import 'package:threebotlogin/models/contact.dart'; +import 'package:threebotlogin/services/pkid_service.dart'; +import 'package:threebotlogin/services/shared_preference_service.dart'; +import 'package:bip39/bip39.dart' as bip39; +import 'package:convert/convert.dart'; + +Future _getPkidClient() async { + Uint8List seed = await getDerivedSeed(WalletConfig().appId()); + final mnemonic = bip39.entropyToMnemonic(hex.encode(seed)); + FlutterPkid client = await getPkidClient(seedPhrase: mnemonic); + return client; +} + +Future> _getPkidContacts() async { + FlutterPkid client = await _getPkidClient(); + final pKidResult = await client.getPKidDoc('contacts'); + final result = + pKidResult.containsKey('data') && pKidResult.containsKey('success') + ? jsonDecode(pKidResult['data']) + : {}; + + Map dataMap = result.asMap(); + final pkidWallets = + dataMap.values.map((e) => PkidContact.fromJson(e)).toList(); + return pkidWallets; +} \ No newline at end of file From 3c0663e511ea7b45ad5e2f92521ca787114b9801 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Fri, 27 Sep 2024 00:37:55 +0300 Subject: [PATCH 23/56] Fix pkid wallet to map --- app/lib/models/wallet.dart | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/app/lib/models/wallet.dart b/app/lib/models/wallet.dart index 06fe1d96..c80c79f6 100644 --- a/app/lib/models/wallet.dart +++ b/app/lib/models/wallet.dart @@ -1,5 +1,6 @@ enum WalletType { Native, Imported } -enum ChainType {Stellar, TFChain } + +enum ChainType { Stellar, TFChain } class Wallet { Wallet({ @@ -43,12 +44,7 @@ class PkidWallet { json['type'] == 'NATIVE' ? WalletType.Native : WalletType.Imported); } toMap() { - return { - 'name': name, - 'index': index, - 'seed': seed, - 'type': type.toString() - }; + return {'name': name, 'index': index, 'seed': seed, 'type': type.name}; } } From aaeafe6815540a9c1c5f77f6a9d4fb2be72e5979 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Fri, 27 Sep 2024 00:39:04 +0300 Subject: [PATCH 24/56] Add contact model --- app/lib/models/contact.dart | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 app/lib/models/contact.dart diff --git a/app/lib/models/contact.dart b/app/lib/models/contact.dart new file mode 100644 index 00000000..6197f3f3 --- /dev/null +++ b/app/lib/models/contact.dart @@ -0,0 +1,23 @@ +import 'package:threebotlogin/models/wallet.dart'; + +class PkidContact { + PkidContact({ + required this.name, + required this.address, + required this.type, + }); + String name; + final String address; + final ChainType type; + + factory PkidContact.fromJson(Map json) { + return PkidContact( + name: json['name'], + address: json['address'], + type: + json['type'] == 'stellar' ? ChainType.Stellar : ChainType.TFChain); + } + toMap() { + return {'name': name, 'address': address, 'type': type.name}; + } +} \ No newline at end of file From 800b166d334cf478ffb490c3141945bd5dd4c936 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Fri, 27 Sep 2024 00:40:06 +0300 Subject: [PATCH 25/56] init contact screen --- app/lib/screens/wallets/contatcs.dart | 34 +++++++++++++++++++++ app/lib/screens/wallets/send.dart | 25 ++++++++++++--- app/lib/screens/wallets/wallet_assets.dart | 5 ++- app/lib/screens/wallets/wallet_details.dart | 3 ++ app/lib/screens/wallets/wallet_screen.dart | 1 + app/lib/widgets/wallets/wallet_card.dart | 3 ++ 6 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 app/lib/screens/wallets/contatcs.dart diff --git a/app/lib/screens/wallets/contatcs.dart b/app/lib/screens/wallets/contatcs.dart new file mode 100644 index 00000000..01485f50 --- /dev/null +++ b/app/lib/screens/wallets/contatcs.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:threebotlogin/models/wallet.dart'; + +class ContractsScreen extends StatefulWidget { + const ContractsScreen( + {super.key, required this.wallets, required this.onSelectToAddress}); + + final List wallets; + final void Function(String address) onSelectToAddress; + + @override + State createState() => _ContractsScreenState(); +} + +class _ContractsScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Contacts')), + body: ListView(children: [ + for (final wallet in widget.wallets) + InkWell( + onTap: () { + widget.onSelectToAddress(wallet.stellarAddress); + Navigator.of(context).pop(); + }, + child: ListTile( + title: Text(wallet.name), + ), + ), + ]), + ); + } +} diff --git a/app/lib/screens/wallets/send.dart b/app/lib/screens/wallets/send.dart index 0affb319..c90a1f35 100644 --- a/app/lib/screens/wallets/send.dart +++ b/app/lib/screens/wallets/send.dart @@ -3,11 +3,15 @@ import 'package:flutter/services.dart'; import 'package:qr_code_scanner/qr_code_scanner.dart'; import 'package:threebotlogin/models/wallet.dart'; import 'package:threebotlogin/screens/scan_screen.dart'; +import 'package:threebotlogin/screens/wallets/contatcs.dart'; import 'package:threebotlogin/widgets/wallets/send_confirmation.dart'; class WalletSendScreen extends StatefulWidget { - const WalletSendScreen({super.key, required this.wallet}); + const WalletSendScreen( + {super.key, required this.wallet, required this.allWallets}); final Wallet wallet; + final List allWallets; + @override State createState() => _WalletSendScreenState(); } @@ -35,6 +39,11 @@ class _WalletSendScreenState extends State { super.dispose(); } + void _selectToAddress(String address) { + toController.text = address; + setState(() {}); + } + @override Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; @@ -154,9 +163,17 @@ class _WalletSendScreenState extends State { color: Theme.of(context).colorScheme.onBackground, ), controller: toController, - decoration: const InputDecoration( - labelText: 'To', - )), + decoration: InputDecoration( + labelText: 'To', + suffixIcon: IconButton( + onPressed: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => ContractsScreen( + wallets: widget.allWallets, + onSelectToAddress: _selectToAddress), + )); + }, + icon: const Icon(Icons.person)))), ), const SizedBox(height: 10), ListTile( diff --git a/app/lib/screens/wallets/wallet_assets.dart b/app/lib/screens/wallets/wallet_assets.dart index e1634c14..632ac848 100644 --- a/app/lib/screens/wallets/wallet_assets.dart +++ b/app/lib/screens/wallets/wallet_assets.dart @@ -10,8 +10,10 @@ import 'package:threebotlogin/widgets/wallets/arrow_inward.dart'; import 'package:threebotlogin/widgets/wallets/balance_tile.dart'; class WalletAssetsWidget extends StatefulWidget { - const WalletAssetsWidget({super.key, required this.wallet}); + const WalletAssetsWidget( + {super.key, required this.wallet, required this.allWallets}); final Wallet wallet; + final List allWallets; @override State createState() => _WalletAssetsWidgetState(); @@ -102,6 +104,7 @@ class _WalletAssetsWidgetState extends State { Navigator.of(context).push(MaterialPageRoute( builder: (context) => WalletSendScreen( wallet: widget.wallet, + allWallets: widget.allWallets, ), )); }, diff --git a/app/lib/screens/wallets/wallet_details.dart b/app/lib/screens/wallets/wallet_details.dart index f4571db4..b3387517 100644 --- a/app/lib/screens/wallets/wallet_details.dart +++ b/app/lib/screens/wallets/wallet_details.dart @@ -8,9 +8,11 @@ class WalletDetailsScreen extends StatefulWidget { const WalletDetailsScreen( {super.key, required this.wallet, + required this.allWallets, required this.onDeleteWallet, required this.onEditWallet}); final Wallet wallet; + final List allWallets; final void Function(String name) onDeleteWallet; final void Function(String oldName, String newName) onEditWallet; @@ -48,6 +50,7 @@ class _WalletDetailsScreenState extends State { ); } else { content = WalletAssetsWidget( + allWallets: widget.allWallets, wallet: widget.wallet, ); } diff --git a/app/lib/screens/wallets/wallet_screen.dart b/app/lib/screens/wallets/wallet_screen.dart index 88e83f44..6ad569e4 100644 --- a/app/lib/screens/wallets/wallet_screen.dart +++ b/app/lib/screens/wallets/wallet_screen.dart @@ -64,6 +64,7 @@ class _WalletScreenState extends State { for (final wallet in wallets) WalletCardWidget( wallet: wallet, + allWallets: wallets, onDeleteWallet: onDeleteWallet, onEditWallet: onEditWallet, ) diff --git a/app/lib/widgets/wallets/wallet_card.dart b/app/lib/widgets/wallets/wallet_card.dart index 2332797f..aba8c981 100644 --- a/app/lib/widgets/wallets/wallet_card.dart +++ b/app/lib/widgets/wallets/wallet_card.dart @@ -6,9 +6,11 @@ class WalletCardWidget extends StatelessWidget { const WalletCardWidget( {super.key, required this.wallet, + required this.allWallets, required this.onDeleteWallet, required this.onEditWallet}); final Wallet wallet; + final List allWallets; final void Function(String name) onDeleteWallet; final void Function(String oldName, String newName) onEditWallet; @@ -20,6 +22,7 @@ class WalletCardWidget extends StatelessWidget { Navigator.of(context).push(MaterialPageRoute( builder: (context) => WalletDetailsScreen( wallet: wallet, + allWallets: allWallets, onDeleteWallet: onDeleteWallet, onEditWallet: onEditWallet, ), From 7e10a311b6bbe828822d7ffdc3c2621c7969d424 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Fri, 27 Sep 2024 01:01:20 +0300 Subject: [PATCH 26/56] Fix wallet types --- app/lib/models/wallet.dart | 6 +++--- app/lib/screens/wallets/wallet_info.dart | 2 +- app/lib/services/wallet_service.dart | 2 +- app/lib/widgets/wallets/add_wallet.dart | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/lib/models/wallet.dart b/app/lib/models/wallet.dart index c80c79f6..290109b6 100644 --- a/app/lib/models/wallet.dart +++ b/app/lib/models/wallet.dart @@ -1,4 +1,4 @@ -enum WalletType { Native, Imported } +enum WalletType { NATIVE, IMPORTED } enum ChainType { Stellar, TFChain } @@ -33,7 +33,7 @@ class PkidWallet { String name; final int index; final String seed; - final WalletType type; + WalletType type; factory PkidWallet.fromJson(Map json) { return PkidWallet( @@ -41,7 +41,7 @@ class PkidWallet { name: json['name'], seed: json['seed'], type: - json['type'] == 'NATIVE' ? WalletType.Native : WalletType.Imported); + json['type'] == 'NATIVE' ? WalletType.NATIVE : WalletType.IMPORTED); } toMap() { return {'name': name, 'index': index, 'seed': seed, 'type': type.name}; diff --git a/app/lib/screens/wallets/wallet_info.dart b/app/lib/screens/wallets/wallet_info.dart index e0011697..eb85dbae 100644 --- a/app/lib/screens/wallets/wallet_info.dart +++ b/app/lib/screens/wallets/wallet_info.dart @@ -216,7 +216,7 @@ class _WalletDetailsWidgetState extends State { icon: edit ? const Icon(Icons.save) : const Icon(Icons.edit)), ), const SizedBox(height: 40), - if (widget.wallet.type == WalletType.Imported) + if (widget.wallet.type == WalletType.IMPORTED) Center( child: SizedBox( width: MediaQuery.of(context).size.width - 40, diff --git a/app/lib/services/wallet_service.dart b/app/lib/services/wallet_service.dart index 5b87d66c..0e946a5a 100644 --- a/app/lib/services/wallet_service.dart +++ b/app/lib/services/wallet_service.dart @@ -112,7 +112,7 @@ Future addWallet(String walletName, String walletSecret) async { name: walletName, index: -1, seed: walletSecret, - type: WalletType.Imported)); + type: WalletType.IMPORTED)); await _saveWalletsToPkid(wallets); } diff --git a/app/lib/widgets/wallets/add_wallet.dart b/app/lib/widgets/wallets/add_wallet.dart index e1e39fb5..56134609 100644 --- a/app/lib/widgets/wallets/add_wallet.dart +++ b/app/lib/widgets/wallets/add_wallet.dart @@ -195,7 +195,7 @@ Future loadAddedWallet(String walletName, String walletSecret) async { final chainUrl = Globals().chainUrl; final Wallet wallet = await compute((void _) async { final wallet = await loadWallet( - walletName, walletSecret, WalletType.Imported, chainUrl); + walletName, walletSecret, WalletType.IMPORTED, chainUrl); return wallet; }, null); return wallet; From 299f44fb9f9775c669a75d0ba4acf1f4f229e0a1 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Fri, 27 Sep 2024 15:30:23 +0300 Subject: [PATCH 27/56] Add colors to the custom dialog --- .../screens/identity_verification_screen.dart | 12 +++++++--- .../screens/mobile_registration_screen.dart | 1 + app/lib/screens/preference_screen.dart | 9 +++++--- app/lib/screens/sign_screen.dart | 1 + app/lib/screens/wallets/wallet_info.dart | 3 ++- app/lib/widgets/add_farm.dart | 7 +++--- app/lib/widgets/custom_dialog.dart | 23 +++++++++++++++++-- app/lib/widgets/dao/vote_dialog.dart | 7 +++--- .../widgets/email_verification_needed.dart | 1 + app/lib/widgets/login_dialogs.dart | 1 + app/lib/widgets/wallets/add_wallet.dart | 9 ++++---- .../widgets/wallets/send_confirmation.dart | 7 +++--- 12 files changed, 59 insertions(+), 22 deletions(-) diff --git a/app/lib/screens/identity_verification_screen.dart b/app/lib/screens/identity_verification_screen.dart index d25dad48..a1117e0b 100644 --- a/app/lib/screens/identity_verification_screen.dart +++ b/app/lib/screens/identity_verification_screen.dart @@ -333,7 +333,8 @@ class _IdentityVerificationScreenState context: context, barrierDismissible: false, builder: (BuildContext customContext) => CustomDialog( - image: Icons.info, + type: DialogType.Warning, + image: Icons.warning, title: 'Are you sure', description: 'Are you sure you want to exit the verification process', actions: [ @@ -407,6 +408,7 @@ class _IdentityVerificationScreenState context: context, barrierDismissible: false, builder: (BuildContext dialogContext) => CustomDialog( + type: DialogType.Error, image: Icons.close, title: 'Request canceled', description: 'Verification process has been canceled.', @@ -426,6 +428,7 @@ class _IdentityVerificationScreenState context: context, barrierDismissible: false, builder: (BuildContext dialogContext) => CustomDialog( + type: DialogType.Error, image: Icons.close, title: 'Request canceled', description: @@ -986,6 +989,7 @@ class _IdentityVerificationScreenState return showDialog( context: context, builder: (BuildContext context) => CustomDialog( + type: DialogType.Warning, image: Icons.warning, title: 'Maximum requests Reached', description: @@ -1009,7 +1013,8 @@ class _IdentityVerificationScreenState return showDialog( context: context, builder: (BuildContext context) => CustomDialog( - image: Icons.warning, + type: DialogType.Error, + image: Icons.error, title: "Couldn't setup verification process", description: 'Something went wrong. Please contact support if this issue persists.', @@ -1053,7 +1058,8 @@ class _IdentityVerificationScreenState return showDialog( context: context, builder: (BuildContext context) => CustomDialog( - image: Icons.warning, + type: DialogType.Error, + image: Icons.error, title: 'Failed to setup process', description: 'Something went wrong. \n If this issue persist, please contact support', diff --git a/app/lib/screens/mobile_registration_screen.dart b/app/lib/screens/mobile_registration_screen.dart index 42bc42b6..ff14c528 100644 --- a/app/lib/screens/mobile_registration_screen.dart +++ b/app/lib/screens/mobile_registration_screen.dart @@ -183,6 +183,7 @@ class _MobileRegistrationScreenState extends State { showDialog( context: context, builder: (BuildContext context) => CustomDialog( + type: DialogType.Error, image: Icons.error, title: 'Error', description: diff --git a/app/lib/screens/preference_screen.dart b/app/lib/screens/preference_screen.dart index bbeba001..e09beb9c 100644 --- a/app/lib/screens/preference_screen.dart +++ b/app/lib/screens/preference_screen.dart @@ -193,7 +193,8 @@ class _PreferenceScreenState extends State { showDialog( context: context, builder: (BuildContext context) => CustomDialog( - image: Icons.error, + type: DialogType.Warning, + image: Icons.warning, title: 'Disable Fingerprint', description: 'Are you sure you want to deactivate fingerprint as authentication method?', @@ -225,7 +226,8 @@ class _PreferenceScreenState extends State { showDialog( context: context, builder: (BuildContext context) => CustomDialog( - image: Icons.error, + type: DialogType.Warning, + image: Icons.warning, title: 'Are you sure?', description: 'If you confirm, your account will be removed from this device. You can always recover your account with your username and phrase.', @@ -263,6 +265,7 @@ class _PreferenceScreenState extends State { showDialog( context: preferenceContext!, builder: (BuildContext context) => CustomDialog( + type: DialogType.Error, title: 'Error', description: 'Something went wrong when trying to remove your account.', @@ -355,7 +358,7 @@ class _PreferenceScreenState extends State { context: context, builder: (BuildContext context) => CustomDialog( hiddenAction: copySeedPhrase, - image: Icons.create, + image: Icons.info, title: 'Please write this down on a piece of paper', description: phrase.toString(), actions: [ diff --git a/app/lib/screens/sign_screen.dart b/app/lib/screens/sign_screen.dart index d02adc4b..c88898cd 100644 --- a/app/lib/screens/sign_screen.dart +++ b/app/lib/screens/sign_screen.dart @@ -410,6 +410,7 @@ class _SignScreenState extends State with BlockAndRunMixin { context: context, barrierDismissible: false, builder: (BuildContext customContext) => CustomDialog( + type: DialogType.Warning, image: Icons.warning, title: 'Are you sure', description: diff --git a/app/lib/screens/wallets/wallet_info.dart b/app/lib/screens/wallets/wallet_info.dart index eb85dbae..48176aca 100644 --- a/app/lib/screens/wallets/wallet_info.dart +++ b/app/lib/screens/wallets/wallet_info.dart @@ -257,7 +257,8 @@ class _WalletDetailsWidgetState extends State { showDialog( context: context, builder: (BuildContext context) => CustomDialog( - image: Icons.error, + type: DialogType.Warning, + image: Icons.warning, title: 'Are you sure?', description: 'If you confirm, your wallet will be removed from this device.', diff --git a/app/lib/widgets/add_farm.dart b/app/lib/widgets/add_farm.dart index 14689b10..9b57910c 100644 --- a/app/lib/widgets/add_farm.dart +++ b/app/lib/widgets/add_farm.dart @@ -20,11 +20,12 @@ class _NewFarmState extends State { Map _selectedWallet = {}; bool saveLoading = false; String? nameError; - Future _showDialog(String title, String message, IconData icon) async { + Future _showDialog(String title, String message, IconData icon, DialogType type) async { showDialog( barrierDismissible: false, context: context, builder: (BuildContext context) => CustomDialog( + type: type, image: icon, title: title, description: message, @@ -72,11 +73,11 @@ class _NewFarmState extends State { farmId: f.id, nodes: []); await _showDialog('Farm Created!', - 'Farm $farmName has been added successfully', Icons.check); + 'Farm $farmName has been added successfully', Icons.check, DialogType.Info); } catch (e) { print(e); _showDialog( - 'Error', 'Failed to create farm. Please try again.', Icons.error); + 'Error', 'Failed to create farm. Please try again.', Icons.error, DialogType.Error); saveLoading = false; setState(() {}); return; diff --git a/app/lib/widgets/custom_dialog.dart b/app/lib/widgets/custom_dialog.dart index 20cc5f11..b57a90ed 100644 --- a/app/lib/widgets/custom_dialog.dart +++ b/app/lib/widgets/custom_dialog.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:threebotlogin/main.dart'; + +enum DialogType {Info, Warning, Error} class CustomDialog extends StatefulWidget { final String? description; @@ -7,6 +10,7 @@ class CustomDialog extends StatefulWidget { final String title; final IconData image; final dynamic hiddenAction; + final DialogType type; const CustomDialog({ super.key, @@ -16,6 +20,7 @@ class CustomDialog extends StatefulWidget { this.actions, this.image = Icons.person, this.hiddenAction, + this.type = DialogType.Info, }); show(context) { @@ -24,6 +29,7 @@ class CustomDialog extends StatefulWidget { barrierDismissible: false, builder: (BuildContext context) => CustomDialog( image: Icons.error, + type: DialogType.Error, title: title, description: description, widgetDescription: widgetDescription, @@ -50,6 +56,7 @@ class _CustomDialogState extends State { context: context, barrierDismissible: false, builder: (BuildContext context) => CustomDialog( + type: DialogType.Error, image: Icons.error, title: widget.title, description: widget.description, @@ -88,6 +95,18 @@ class _CustomDialogState extends State { circularImage(context) { int timesPressed = 0; const int timesPressedToReveal = 5; + Color backgroundColor; + Color color; + if (widget.type == DialogType.Error){ + backgroundColor = Theme.of(context).colorScheme.error; + color = Theme.of(context).colorScheme.onError; + } else if (widget.type == DialogType.Warning){ + backgroundColor = Theme.of(context).colorScheme.warning; + color = Theme.of(context).colorScheme.onWarning; + } else { + backgroundColor = Theme.of(context).colorScheme.primary; + color = Theme.of(context).colorScheme.onPrimary; + } return Positioned( left: 20.0, right: 20.0, @@ -107,12 +126,12 @@ class _CustomDialogState extends State { } }, child: CircleAvatar( - backgroundColor: Theme.of(context).colorScheme.primary, + backgroundColor: backgroundColor, radius: 30.0, child: Icon( widget.image, size: 42.0, - color: Theme.of(context).colorScheme.onPrimary, + color: color, ), ), ), diff --git a/app/lib/widgets/dao/vote_dialog.dart b/app/lib/widgets/dao/vote_dialog.dart index 8cbb44e9..571e9547 100644 --- a/app/lib/widgets/dao/vote_dialog.dart +++ b/app/lib/widgets/dao/vote_dialog.dart @@ -202,9 +202,9 @@ class _VoteDialogState extends State { try { await vote(approve, widget.proposalHash, farmId!, seed!); - _showDialog('Voted!', 'You have voted successfully.', Icons.check); + _showDialog('Voted!', 'You have voted successfully.', Icons.check, DialogType.Info); } catch (e) { - _showDialog('Error', 'Failed to Vote.', Icons.error); + _showDialog('Error', 'Failed to Vote.', Icons.error, DialogType.Error); } finally { setState(() { yesLoading = false; @@ -213,12 +213,13 @@ class _VoteDialogState extends State { } } - _showDialog(String title, String description, IconData icon) async { + _showDialog(String title, String description, IconData icon, DialogType type) async { if (context.mounted) { showDialog( barrierDismissible: false, context: context, builder: (BuildContext context) => CustomDialog( + type: type, image: icon, title: title, description: description, diff --git a/app/lib/widgets/email_verification_needed.dart b/app/lib/widgets/email_verification_needed.dart index 426f62cb..463cf4ef 100644 --- a/app/lib/widgets/email_verification_needed.dart +++ b/app/lib/widgets/email_verification_needed.dart @@ -8,6 +8,7 @@ emailVerificationDialog(context) { barrierDismissible: false, builder: (BuildContext context) => CustomDialog( image: Icons.error, + type: DialogType.Error, title: 'Please verify email', description: 'Please verify email before using this app', actions: [ diff --git a/app/lib/widgets/login_dialogs.dart b/app/lib/widgets/login_dialogs.dart index 9c585897..5e08bf82 100644 --- a/app/lib/widgets/login_dialogs.dart +++ b/app/lib/widgets/login_dialogs.dart @@ -26,6 +26,7 @@ Future showWrongEmojiDialog(BuildContext ctx) async { await showDialog( context: ctx, builder: (BuildContext context) => CustomDialog( + type: DialogType.Warning, image: Icons.warning, title: 'Wrong emoji', description: diff --git a/app/lib/widgets/wallets/add_wallet.dart b/app/lib/widgets/wallets/add_wallet.dart index 56134609..14e78507 100644 --- a/app/lib/widgets/wallets/add_wallet.dart +++ b/app/lib/widgets/wallets/add_wallet.dart @@ -28,11 +28,12 @@ class _NewWalletState extends State { bool saveLoading = false; String? nameError; String? secretError; - Future _showDialog(String title, String message, IconData icon) async { + Future _showDialog(String title, String message, IconData icon, DialogType type) async { showDialog( barrierDismissible: false, context: context, builder: (BuildContext context) => CustomDialog( + type: type, image: icon, title: title, description: message, @@ -91,7 +92,7 @@ class _NewWalletState extends State { } catch (e) { print(e); _showDialog( - 'Error', 'Failed to load wallet. Please try again.', Icons.error); + 'Error', 'Failed to load wallet. Please try again.', Icons.error, DialogType.Error); saveLoading = false; setState(() {}); return; @@ -99,11 +100,11 @@ class _NewWalletState extends State { try { await addWallet(walletName, walletSecret); await _showDialog('Wallet Added!', - 'Wallet $walletName has been added successfully', Icons.check); + 'Wallet $walletName has been added successfully', Icons.check, DialogType.Info); } catch (e) { print(e); _showDialog( - 'Error', 'Failed to save wallet. Please try again.', Icons.error); + 'Error', 'Failed to save wallet. Please try again.', Icons.error, DialogType.Error); saveLoading = false; setState(() {}); return; diff --git a/app/lib/widgets/wallets/send_confirmation.dart b/app/lib/widgets/wallets/send_confirmation.dart index 4e305e77..ec2a37d8 100644 --- a/app/lib/widgets/wallets/send_confirmation.dart +++ b/app/lib/widgets/wallets/send_confirmation.dart @@ -154,10 +154,10 @@ class _SendConfirmationWidgetState extends State { await TFChain.transfer(widget.secret, widget.to, widget.amount); } await _showDialog( - 'Success!', 'Tokens have been transfered successfully', Icons.check); + 'Success!', 'Tokens have been transfered successfully', Icons.check, DialogType.Info); } catch (e) { _showDialog( - 'Error', 'Failed to transfer. Please try again.', Icons.error); + 'Error', 'Failed to transfer. Please try again.', Icons.error, DialogType.Error); setState(() { loading = false; }); @@ -171,11 +171,12 @@ class _SendConfirmationWidgetState extends State { Navigator.pop(context); } - Future _showDialog(String title, String message, IconData icon) async { + Future _showDialog(String title, String message, IconData icon, DialogType type) async { showDialog( barrierDismissible: false, context: context, builder: (BuildContext context) => CustomDialog( + type: type, image: icon, title: title, description: message, From 42548b21b8b4c7f4fd09153be9f7aef1de1c1c34 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Fri, 27 Sep 2024 15:38:09 +0300 Subject: [PATCH 28/56] Adjust the send and receive colors --- app/lib/screens/wallets/wallet_assets.dart | 14 ++++++-------- app/lib/services/socket_service.dart | 4 +++- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/lib/screens/wallets/wallet_assets.dart b/app/lib/screens/wallets/wallet_assets.dart index 632ac848..16bfdb2d 100644 --- a/app/lib/screens/wallets/wallet_assets.dart +++ b/app/lib/screens/wallets/wallet_assets.dart @@ -110,10 +110,10 @@ class _WalletAssetsWidgetState extends State { }, child: CircleAvatar( radius: 30, - backgroundColor: Theme.of(context).colorScheme.error, + backgroundColor: Theme.of(context).colorScheme.primaryContainer, child: Icon( Icons.arrow_outward_outlined, - color: Theme.of(context).colorScheme.onError, + color: Theme.of(context).colorScheme.onPrimaryContainer, size: 30, ), ), @@ -121,10 +121,8 @@ class _WalletAssetsWidgetState extends State { const SizedBox(height: 10), Text( 'Send', - style: Theme.of(context) - .textTheme - .titleLarge! - .copyWith(color: Theme.of(context).colorScheme.error), + style: Theme.of(context).textTheme.titleLarge!.copyWith( + color: Theme.of(context).colorScheme.primary), ), ], ), @@ -141,9 +139,9 @@ class _WalletAssetsWidgetState extends State { child: CircleAvatar( radius: 30, backgroundColor: - Theme.of(context).colorScheme.primary, + Theme.of(context).colorScheme.primaryContainer, child: ArrowInward( - color: Theme.of(context).colorScheme.onPrimary, + color: Theme.of(context).colorScheme.onPrimaryContainer, size: 30, )), ), diff --git a/app/lib/services/socket_service.dart b/app/lib/services/socket_service.dart index 9e208a7a..c5df0ff9 100644 --- a/app/lib/services/socket_service.dart +++ b/app/lib/services/socket_service.dart @@ -207,6 +207,7 @@ Future showIdentityMessage(BuildContext context, String type) async { return showDialog( context: context, builder: (BuildContext context) => CustomDialog( + type: DialogType.Warning, image: Icons.warning, title: 'Identity verify timed out', description: @@ -226,7 +227,8 @@ Future showIdentityMessage(BuildContext context, String type) async { return showDialog( context: context, builder: (BuildContext context) => CustomDialog( - image: Icons.warning, + type: DialogType.Error, + image: Icons.error, title: 'Identity verify failed', description: 'Something went wrong.\nIf this issue persist, please contact support', From 6a346d1c6ec3c0e02e353da4c4aea58bc1a0ec11 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Fri, 27 Sep 2024 16:03:17 +0300 Subject: [PATCH 29/56] Add TFT logo before the wallet chain on the wallet card --- app/lib/widgets/wallets/wallet_card.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/lib/widgets/wallets/wallet_card.dart b/app/lib/widgets/wallets/wallet_card.dart index aba8c981..279895d4 100644 --- a/app/lib/widgets/wallets/wallet_card.dart +++ b/app/lib/widgets/wallets/wallet_card.dart @@ -43,6 +43,12 @@ class WalletCardWidget extends StatelessWidget { // TODO: hide the balance if there is no account for this wallet Row( children: [ + SizedBox( + width: 35, + child: Image.asset( + 'assets/tft_icon.png', + color: Theme.of(context).colorScheme.onBackground, + )), Text( 'Stellar', style: Theme.of(context).textTheme.bodyLarge!.copyWith( @@ -64,6 +70,13 @@ class WalletCardWidget extends StatelessWidget { ), Row( children: [ + SizedBox( + width: 35, + child: Image.asset( + 'assets/tft_icon.png', + fit: BoxFit.contain, + color: Theme.of(context).colorScheme.onBackground, + )), Text( 'TFChain', style: Theme.of(context).textTheme.bodyLarge!.copyWith( From 5cd34c5ab00325751a467bbd8c73d23bbc605fa0 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Fri, 27 Sep 2024 16:47:55 +0300 Subject: [PATCH 30/56] Add contact card --- app/lib/screens/wallets/contatcs.dart | 41 +++++++++++++++++------ app/lib/screens/wallets/send.dart | 2 ++ app/lib/widgets/wallets/contact_card.dart | 40 ++++++++++++++++++++++ 3 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 app/lib/widgets/wallets/contact_card.dart diff --git a/app/lib/screens/wallets/contatcs.dart b/app/lib/screens/wallets/contatcs.dart index 01485f50..f862cb27 100644 --- a/app/lib/screens/wallets/contatcs.dart +++ b/app/lib/screens/wallets/contatcs.dart @@ -1,10 +1,17 @@ import 'package:flutter/material.dart'; import 'package:threebotlogin/models/wallet.dart'; +import 'package:threebotlogin/widgets/wallets/contact_card.dart'; class ContractsScreen extends StatefulWidget { const ContractsScreen( - {super.key, required this.wallets, required this.onSelectToAddress}); + {super.key, + required this.chainType, + required this.currentWalletAddress, + required this.wallets, + required this.onSelectToAddress}); + final ChainType chainType; + final String currentWalletAddress; final List wallets; final void Function(String address) onSelectToAddress; @@ -15,19 +22,33 @@ class ContractsScreen extends StatefulWidget { class _ContractsScreenState extends State { @override Widget build(BuildContext context) { + final List wallets = widget.wallets.where((w) { + if (widget.chainType ==ChainType.Stellar && w.stellarAddress != widget.currentWalletAddress){ + return true; + } + if (widget.chainType ==ChainType.TFChain && w.tfchainAddress != widget.currentWalletAddress){ + return true; + } + return false; + }).toList(); return Scaffold( appBar: AppBar(title: const Text('Contacts')), body: ListView(children: [ - for (final wallet in widget.wallets) + for (final wallet in wallets) InkWell( - onTap: () { - widget.onSelectToAddress(wallet.stellarAddress); - Navigator.of(context).pop(); - }, - child: ListTile( - title: Text(wallet.name), - ), - ), + onTap: () { + widget.chainType == ChainType.Stellar + ? widget.onSelectToAddress(wallet.stellarAddress) + : widget.onSelectToAddress(wallet.tfchainAddress); + + Navigator.of(context).pop(); + }, + child: ContactCardWidget( + name: wallet.name, + address: widget.chainType == ChainType.Stellar + ? wallet.stellarAddress + : wallet.tfchainAddress, + )), ]), ); } diff --git a/app/lib/screens/wallets/send.dart b/app/lib/screens/wallets/send.dart index c90a1f35..091b38e1 100644 --- a/app/lib/screens/wallets/send.dart +++ b/app/lib/screens/wallets/send.dart @@ -169,6 +169,8 @@ class _WalletSendScreenState extends State { onPressed: () { Navigator.of(context).push(MaterialPageRoute( builder: (context) => ContractsScreen( + chainType: chainType, + currentWalletAddress: fromController.text, wallets: widget.allWallets, onSelectToAddress: _selectToAddress), )); diff --git a/app/lib/widgets/wallets/contact_card.dart b/app/lib/widgets/wallets/contact_card.dart new file mode 100644 index 00000000..bc18c5b7 --- /dev/null +++ b/app/lib/widgets/wallets/contact_card.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; + +class ContactCardWidget extends StatelessWidget { + const ContactCardWidget( + {super.key, required this.name, required this.address}); + final String name; + final String address; + + @override + Widget build(BuildContext context) { + return Card( + // color: Theme.of(context).colorScheme.background, + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(5), + // side: BorderSide(color: Theme.of(context).colorScheme.primary)), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + name, + style: Theme.of(context).textTheme.titleLarge!.copyWith( + color: Theme.of(context).colorScheme.onSecondaryContainer, + ), + ), + const SizedBox(height: 10), + Text( + address, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onSecondaryContainer, + ), + ), + ], + ), + ), + ); + } +} From d495d5cea825218717ee03470a9ec819b13eda30 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Fri, 27 Sep 2024 18:31:49 +0300 Subject: [PATCH 31/56] Add tabs to the contacts screen and load other contacts from pkid --- app/lib/screens/wallets/contatcs.dart | 101 +++++++++++++------ app/lib/services/contact_service.dart | 2 +- app/lib/widgets/wallets/contacts_widget.dart | 29 ++++++ 3 files changed, 102 insertions(+), 30 deletions(-) create mode 100644 app/lib/widgets/wallets/contacts_widget.dart diff --git a/app/lib/screens/wallets/contatcs.dart b/app/lib/screens/wallets/contatcs.dart index f862cb27..b2391aed 100644 --- a/app/lib/screens/wallets/contatcs.dart +++ b/app/lib/screens/wallets/contatcs.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:threebotlogin/models/contact.dart'; import 'package:threebotlogin/models/wallet.dart'; -import 'package:threebotlogin/widgets/wallets/contact_card.dart'; +import 'package:threebotlogin/services/contact_service.dart'; +import 'package:threebotlogin/widgets/wallets/contacts_widget.dart'; class ContractsScreen extends StatefulWidget { const ContractsScreen( @@ -20,36 +22,77 @@ class ContractsScreen extends StatefulWidget { } class _ContractsScreenState extends State { - @override - Widget build(BuildContext context) { - final List wallets = widget.wallets.where((w) { - if (widget.chainType ==ChainType.Stellar && w.stellarAddress != widget.currentWalletAddress){ - return true; + List myWalletContacts = []; + List myPkidContacts = []; + + _loadMyWalletContacts() { + for (final w in widget.wallets) { + if (widget.chainType == ChainType.Stellar && + w.stellarAddress != widget.currentWalletAddress) { + myWalletContacts.add(PkidContact( + name: w.name, address: w.stellarAddress, type: ChainType.Stellar)); } - if (widget.chainType ==ChainType.TFChain && w.tfchainAddress != widget.currentWalletAddress){ - return true; + if (widget.chainType == ChainType.TFChain && + w.tfchainAddress != widget.currentWalletAddress) { + myWalletContacts.add(PkidContact( + name: w.name, address: w.tfchainAddress, type: ChainType.TFChain)); } - return false; - }).toList(); + } + } + + _loadOtherContacts() async { + myPkidContacts = await getPkidContacts(); + myPkidContacts = + myPkidContacts.where((c) => c.type == widget.chainType).toList(); + setState(() {}); + } + + @override + void initState() { + _loadMyWalletContacts(); + _loadOtherContacts(); + super.initState(); + } + + @override + Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Contacts')), - body: ListView(children: [ - for (final wallet in wallets) - InkWell( - onTap: () { - widget.chainType == ChainType.Stellar - ? widget.onSelectToAddress(wallet.stellarAddress) - : widget.onSelectToAddress(wallet.tfchainAddress); - - Navigator.of(context).pop(); - }, - child: ContactCardWidget( - name: wallet.name, - address: widget.chainType == ChainType.Stellar - ? wallet.stellarAddress - : wallet.tfchainAddress, - )), - ]), - ); + appBar: AppBar(title: const Text('Contacts')), + body: DefaultTabController( + length: 2, + child: Column( + children: [ + PreferredSize( + preferredSize: const Size.fromHeight(50.0), + child: Container( + color: Theme.of(context).scaffoldBackgroundColor, + child: TabBar( + labelColor: Theme.of(context).colorScheme.primary, + indicatorColor: Theme.of(context).colorScheme.primary, + unselectedLabelColor: + Theme.of(context).colorScheme.onBackground, + dividerColor: Theme.of(context).scaffoldBackgroundColor, + tabs: const [ + Tab(text: 'My Wallets'), + Tab(text: 'Others'), + ], + ), + ), + ), + Expanded( + child: TabBarView( + children: [ + ContactsWidget( + contacts: myWalletContacts, + onSelectToAddress: widget.onSelectToAddress), + ContactsWidget( + contacts: myPkidContacts, + onSelectToAddress: widget.onSelectToAddress), + ], + ), + ), + ], + ), + )); } } diff --git a/app/lib/services/contact_service.dart b/app/lib/services/contact_service.dart index 9ccbd820..784bc47b 100644 --- a/app/lib/services/contact_service.dart +++ b/app/lib/services/contact_service.dart @@ -16,7 +16,7 @@ Future _getPkidClient() async { return client; } -Future> _getPkidContacts() async { +Future> getPkidContacts() async { FlutterPkid client = await _getPkidClient(); final pKidResult = await client.getPKidDoc('contacts'); final result = diff --git a/app/lib/widgets/wallets/contacts_widget.dart b/app/lib/widgets/wallets/contacts_widget.dart new file mode 100644 index 00000000..273a8004 --- /dev/null +++ b/app/lib/widgets/wallets/contacts_widget.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:threebotlogin/models/contact.dart'; +import 'package:threebotlogin/widgets/wallets/contact_card.dart'; + +class ContactsWidget extends StatelessWidget { + const ContactsWidget({ + super.key, + required this.contacts, + required this.onSelectToAddress, + }); + final List contacts; + final void Function(String address) onSelectToAddress; + + @override + Widget build(BuildContext context) { + return ListView(children: [ + for (final contact in contacts) + InkWell( + onTap: () { + onSelectToAddress(contact.address); + Navigator.of(context).pop(); + }, + child: ContactCardWidget( + name: contact.name, + address: contact.address, + )), + ]); + } +} From c256d6ae4ba94b970f2077f5acca999fc9130d4a Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Fri, 27 Sep 2024 19:19:42 +0300 Subject: [PATCH 32/56] Add crud methods to contacts service --- app/lib/models/contact.dart | 2 +- app/lib/services/contact_service.dart | 35 ++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/app/lib/models/contact.dart b/app/lib/models/contact.dart index 6197f3f3..1ba7ef14 100644 --- a/app/lib/models/contact.dart +++ b/app/lib/models/contact.dart @@ -7,7 +7,7 @@ class PkidContact { required this.type, }); String name; - final String address; + String address; final ChainType type; factory PkidContact.fromJson(Map json) { diff --git a/app/lib/services/contact_service.dart b/app/lib/services/contact_service.dart index 784bc47b..6f6be78c 100644 --- a/app/lib/services/contact_service.dart +++ b/app/lib/services/contact_service.dart @@ -4,6 +4,7 @@ import 'dart:typed_data'; import 'package:flutter_pkid/flutter_pkid.dart'; import 'package:threebotlogin/apps/wallet/wallet_config.dart'; import 'package:threebotlogin/models/contact.dart'; +import 'package:threebotlogin/models/wallet.dart'; import 'package:threebotlogin/services/pkid_service.dart'; import 'package:threebotlogin/services/shared_preference_service.dart'; import 'package:bip39/bip39.dart' as bip39; @@ -28,4 +29,36 @@ Future> getPkidContacts() async { final pkidWallets = dataMap.values.map((e) => PkidContact.fromJson(e)).toList(); return pkidWallets; -} \ No newline at end of file +} + +Future addContact(String name, String address, ChainType type) async { + List contacts = await getPkidContacts(); + contacts.add(PkidContact(name: name, address: address, type: type)); + + await _saveContactsToPkid(contacts); +} + +Future editContact( + String oldName, String newName, String newAddress) async { + List contacts = await getPkidContacts(); + for (final w in contacts) { + if (w.name == oldName) { + w.name = newName; + w.address = newAddress; + break; + } + } + await _saveContactsToPkid(contacts); +} + +Future deleteContact(String walletName) async { + List contacts = await getPkidContacts(); + contacts = contacts.where((w) => w.name != walletName).toList(); + await _saveContactsToPkid(contacts); +} + +Future _saveContactsToPkid(List contacts) async { + FlutterPkid client = await _getPkidClient(); + final encodedContacts = json.encode(contacts.map((w) => w.toMap()).toList()); + await client.setPKidDoc('contacts', encodedContacts); +} From 107ffb0b7f15bb469558ba8dec9c99e8c1b970ea Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Sat, 28 Sep 2024 01:09:46 +0300 Subject: [PATCH 33/56] Support add contact --- app/lib/models/contact.dart | 4 +- .../wallets/{contatcs.dart => contacts.dart} | 36 +++- app/lib/screens/wallets/send.dart | 2 +- app/lib/widgets/wallets/add_contact.dart | 183 ++++++++++++++++++ 4 files changed, 218 insertions(+), 7 deletions(-) rename app/lib/screens/wallets/{contatcs.dart => contacts.dart} (72%) create mode 100644 app/lib/widgets/wallets/add_contact.dart diff --git a/app/lib/models/contact.dart b/app/lib/models/contact.dart index 1ba7ef14..fc49ddbd 100644 --- a/app/lib/models/contact.dart +++ b/app/lib/models/contact.dart @@ -18,6 +18,6 @@ class PkidContact { json['type'] == 'stellar' ? ChainType.Stellar : ChainType.TFChain); } toMap() { - return {'name': name, 'address': address, 'type': type.name}; + return {'name': name, 'address': address, 'type': type.name.toLowerCase()}; } -} \ No newline at end of file +} diff --git a/app/lib/screens/wallets/contatcs.dart b/app/lib/screens/wallets/contacts.dart similarity index 72% rename from app/lib/screens/wallets/contatcs.dart rename to app/lib/screens/wallets/contacts.dart index b2391aed..920fb23b 100644 --- a/app/lib/screens/wallets/contatcs.dart +++ b/app/lib/screens/wallets/contacts.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:threebotlogin/models/contact.dart'; import 'package:threebotlogin/models/wallet.dart'; import 'package:threebotlogin/services/contact_service.dart'; +import 'package:threebotlogin/widgets/wallets/add_contact.dart'; import 'package:threebotlogin/widgets/wallets/contacts_widget.dart'; class ContractsScreen extends StatefulWidget { @@ -40,24 +41,51 @@ class _ContractsScreenState extends State { } } - _loadOtherContacts() async { + _loadFavouriteContacts() async { myPkidContacts = await getPkidContacts(); myPkidContacts = myPkidContacts.where((c) => c.type == widget.chainType).toList(); setState(() {}); } + _onAddContact(PkidContact contact) async { + myPkidContacts.add(contact); + setState(() {}); + } + + _openAddContactOverlay() { + showModalBottomSheet( + isScrollControlled: true, + useSafeArea: true, + constraints: const BoxConstraints(maxWidth: double.infinity), + context: context, + builder: (ctx) => NewContact( + onAddContact: _onAddContact, + chainType: widget.chainType, + contacts: [...myPkidContacts, ...myWalletContacts], + )); + } + @override void initState() { _loadMyWalletContacts(); - _loadOtherContacts(); + _loadFavouriteContacts(); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Contacts')), + appBar: AppBar( + title: const Text('Contacts'), + actions: DefaultTabController.of(context).index == 1 + ? [] + : [ + IconButton( + onPressed: _openAddContactOverlay, + icon: const Icon(Icons.add)) + ], + ), body: DefaultTabController( length: 2, child: Column( @@ -74,7 +102,7 @@ class _ContractsScreenState extends State { dividerColor: Theme.of(context).scaffoldBackgroundColor, tabs: const [ Tab(text: 'My Wallets'), - Tab(text: 'Others'), + Tab(text: 'Favourite'), ], ), ), diff --git a/app/lib/screens/wallets/send.dart b/app/lib/screens/wallets/send.dart index 091b38e1..b2504ae1 100644 --- a/app/lib/screens/wallets/send.dart +++ b/app/lib/screens/wallets/send.dart @@ -3,7 +3,7 @@ import 'package:flutter/services.dart'; import 'package:qr_code_scanner/qr_code_scanner.dart'; import 'package:threebotlogin/models/wallet.dart'; import 'package:threebotlogin/screens/scan_screen.dart'; -import 'package:threebotlogin/screens/wallets/contatcs.dart'; +import 'package:threebotlogin/screens/wallets/contacts.dart'; import 'package:threebotlogin/widgets/wallets/send_confirmation.dart'; class WalletSendScreen extends StatefulWidget { diff --git a/app/lib/widgets/wallets/add_contact.dart b/app/lib/widgets/wallets/add_contact.dart new file mode 100644 index 00000000..d918558e --- /dev/null +++ b/app/lib/widgets/wallets/add_contact.dart @@ -0,0 +1,183 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:threebotlogin/models/contact.dart'; +import 'package:threebotlogin/models/wallet.dart'; +import 'package:threebotlogin/services/contact_service.dart'; +import 'package:threebotlogin/widgets/custom_dialog.dart'; + +class NewContact extends StatefulWidget { + const NewContact( + {super.key, + required this.onAddContact, + required this.contacts, + required this.chainType}); + final void Function(PkidContact addedContact) onAddContact; + final List contacts; + final ChainType chainType; + + @override + State createState() { + return _NewContactState(); + } +} + +class _NewContactState extends State { + final _nameController = TextEditingController(); + final _addressController = TextEditingController(); + bool saveLoading = false; + String? nameError; + String? addressError; + Future _showDialog( + String title, String message, IconData icon, DialogType type) async { + showDialog( + barrierDismissible: false, + context: context, + builder: (BuildContext context) => CustomDialog( + type: type, + image: icon, + title: title, + description: message, + ), + ); + await Future.delayed( + const Duration(seconds: 3), + () { + Navigator.pop(context); + }, + ); + } + + Future _validateAddSubmitData() async { + final contactName = _nameController.text.trim(); + final contactAddress = _addressController.text.trim(); + nameError = null; + addressError = null; + saveLoading = true; + setState(() {}); + + if (contactName.isEmpty) { + nameError = "Name can't be empty"; + saveLoading = false; + setState(() {}); + return; + } + final c = widget.contacts.where((element) => element.name == contactName); + if (c.isNotEmpty) { + nameError = 'Name exists'; + saveLoading = false; + setState(() {}); + return; + } + if (contactAddress.isEmpty) { + addressError = "Address can't be empty"; + saveLoading = false; + setState(() {}); + return; + } + final contacts = widget.contacts.where((c) => c.address == contactAddress); + if (contacts.isNotEmpty) { + addressError = 'Address exists'; + saveLoading = false; + setState(() {}); + return; + } + // TODO: add address validation based on the chain type + try { + await addContact(contactName, contactAddress, widget.chainType); + await _showDialog( + 'Contact Added!', + 'Contact $contactName has been added successfully', + Icons.check, + DialogType.Info); + } catch (e) { + print(e); + _showDialog('Error', 'Failed to save contact. Please try again.', + Icons.error, DialogType.Error); + saveLoading = false; + setState(() {}); + return; + } + widget.onAddContact(PkidContact( + name: contactName, address: contactAddress, type: widget.chainType)); + saveLoading = false; + setState(() {}); + if (!context.mounted) return; + Navigator.pop(context); + } + + @override + void dispose() { + _nameController.dispose(); + _addressController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final keyboardSpace = MediaQuery.of(context).viewInsets.bottom; + return LayoutBuilder(builder: (ctx, constraints) { + return SizedBox( + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.fromLTRB(16, 16, 16, keyboardSpace + 16), + child: Column( + children: [ + TextField( + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onBackground, + decorationColor: + Theme.of(context).colorScheme.onBackground), + maxLength: 50, + decoration: InputDecoration( + label: const Text('Name'), errorText: nameError), + controller: _nameController, + ), + TextField( + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onBackground, + decorationColor: + Theme.of(context).colorScheme.onBackground), + keyboardType: TextInputType.multiline, + maxLines: null, + decoration: InputDecoration( + label: const Text('Address'), + errorText: addressError, + ), + controller: _addressController, + ), + const SizedBox( + height: 30, + ), + Row( + children: [ + const Spacer(), + ElevatedButton( + onPressed: () { + if (saveLoading) return; + Navigator.pop(context); + }, + child: const Text('Close')), + const SizedBox( + width: 5, + ), + ElevatedButton( + onPressed: _validateAddSubmitData, + child: saveLoading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + )) + : const Text('Save')) + ], + ), + ], + ), + ), + ), + ); + }); + } +} From aa2cf3ac97a4602a5f52757bb25f5f66edfddebe Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Sat, 28 Sep 2024 01:23:33 +0300 Subject: [PATCH 34/56] Adjust pkid return value in case of empty --- app/lib/screens/wallets/contacts.dart | 11 ++++------- app/lib/services/contact_service.dart | 4 +++- app/lib/services/wallet_service.dart | 4 ++++ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/lib/screens/wallets/contacts.dart b/app/lib/screens/wallets/contacts.dart index 920fb23b..f05c18fd 100644 --- a/app/lib/screens/wallets/contacts.dart +++ b/app/lib/screens/wallets/contacts.dart @@ -78,13 +78,10 @@ class _ContractsScreenState extends State { return Scaffold( appBar: AppBar( title: const Text('Contacts'), - actions: DefaultTabController.of(context).index == 1 - ? [] - : [ - IconButton( - onPressed: _openAddContactOverlay, - icon: const Icon(Icons.add)) - ], + actions: [ + IconButton( + onPressed: _openAddContactOverlay, icon: const Icon(Icons.add)) + ], ), body: DefaultTabController( length: 2, diff --git a/app/lib/services/contact_service.dart b/app/lib/services/contact_service.dart index 6f6be78c..0bc5305d 100644 --- a/app/lib/services/contact_service.dart +++ b/app/lib/services/contact_service.dart @@ -24,7 +24,9 @@ Future> getPkidContacts() async { pKidResult.containsKey('data') && pKidResult.containsKey('success') ? jsonDecode(pKidResult['data']) : {}; - + if (pKidResult.containsKey('success') && result.isEmpty) { + return []; + } Map dataMap = result.asMap(); final pkidWallets = dataMap.values.map((e) => PkidContact.fromJson(e)).toList(); diff --git a/app/lib/services/wallet_service.dart b/app/lib/services/wallet_service.dart index 0e946a5a..a5fc612e 100644 --- a/app/lib/services/wallet_service.dart +++ b/app/lib/services/wallet_service.dart @@ -31,6 +31,10 @@ Future> _getPkidWallets() async { ? jsonDecode(pKidResult['data']) : {}; + if (pKidResult.containsKey('success') && result.isEmpty) { + return []; + } + Map dataMap = result.asMap(); final pkidWallets = dataMap.values.map((e) => PkidWallet.fromJson(e)).toList(); From 62b4efd8e09f7921431d62650dfab253ecd0f792 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Sat, 28 Sep 2024 02:02:11 +0300 Subject: [PATCH 35/56] Add option to view edit and delete icons on the contact card --- app/lib/screens/wallets/contacts.dart | 6 ++- app/lib/widgets/wallets/contact_card.dart | 43 +++++++++++++++++--- app/lib/widgets/wallets/contacts_widget.dart | 3 ++ 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/app/lib/screens/wallets/contacts.dart b/app/lib/screens/wallets/contacts.dart index f05c18fd..23d78a4c 100644 --- a/app/lib/screens/wallets/contacts.dart +++ b/app/lib/screens/wallets/contacts.dart @@ -111,8 +111,10 @@ class _ContractsScreenState extends State { contacts: myWalletContacts, onSelectToAddress: widget.onSelectToAddress), ContactsWidget( - contacts: myPkidContacts, - onSelectToAddress: widget.onSelectToAddress), + contacts: myPkidContacts, + onSelectToAddress: widget.onSelectToAddress, + canEditAndDelete: true, + ), ], ), ), diff --git a/app/lib/widgets/wallets/contact_card.dart b/app/lib/widgets/wallets/contact_card.dart index bc18c5b7..af7f9c72 100644 --- a/app/lib/widgets/wallets/contact_card.dart +++ b/app/lib/widgets/wallets/contact_card.dart @@ -2,9 +2,13 @@ import 'package:flutter/material.dart'; class ContactCardWidget extends StatelessWidget { const ContactCardWidget( - {super.key, required this.name, required this.address}); + {super.key, + required this.name, + required this.address, + required this.canEditAndDelete}); final String name; final String address; + final bool canEditAndDelete; @override Widget build(BuildContext context) { @@ -18,13 +22,40 @@ class ContactCardWidget extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - name, - style: Theme.of(context).textTheme.titleLarge!.copyWith( - color: Theme.of(context).colorScheme.onSecondaryContainer, + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Expanded( + child: Text( + name, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.titleLarge!.copyWith( + color: Theme.of(context) + .colorScheme + .onSecondaryContainer, + ), + ), + ), + if (canEditAndDelete) + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + IconButton( + onPressed: () {}, + icon: const Icon( + Icons.edit, + )), + IconButton( + onPressed: () {}, + icon: Icon( + Icons.delete, + color: Theme.of(context).colorScheme.error, + )), + ], ), + ], ), - const SizedBox(height: 10), Text( address, overflow: TextOverflow.ellipsis, diff --git a/app/lib/widgets/wallets/contacts_widget.dart b/app/lib/widgets/wallets/contacts_widget.dart index 273a8004..3432d92d 100644 --- a/app/lib/widgets/wallets/contacts_widget.dart +++ b/app/lib/widgets/wallets/contacts_widget.dart @@ -7,9 +7,11 @@ class ContactsWidget extends StatelessWidget { super.key, required this.contacts, required this.onSelectToAddress, + this.canEditAndDelete = false, }); final List contacts; final void Function(String address) onSelectToAddress; + final bool canEditAndDelete; @override Widget build(BuildContext context) { @@ -23,6 +25,7 @@ class ContactsWidget extends StatelessWidget { child: ContactCardWidget( name: contact.name, address: contact.address, + canEditAndDelete: canEditAndDelete, )), ]); } From 481b0bf924426f87d44e70c20d5de0294cc2882d Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Sat, 28 Sep 2024 02:11:24 +0300 Subject: [PATCH 36/56] Add title to bottom sheets --- app/lib/widgets/add_farm.dart | 20 +++++++++++++++----- app/lib/widgets/wallets/add_contact.dart | 6 ++++++ app/lib/widgets/wallets/add_wallet.dart | 24 +++++++++++++++++------- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/app/lib/widgets/add_farm.dart b/app/lib/widgets/add_farm.dart index 9b57910c..3661d6b0 100644 --- a/app/lib/widgets/add_farm.dart +++ b/app/lib/widgets/add_farm.dart @@ -20,7 +20,8 @@ class _NewFarmState extends State { Map _selectedWallet = {}; bool saveLoading = false; String? nameError; - Future _showDialog(String title, String message, IconData icon, DialogType type) async { + Future _showDialog( + String title, String message, IconData icon, DialogType type) async { showDialog( barrierDismissible: false, context: context, @@ -72,12 +73,15 @@ class _NewFarmState extends State { twinId: f!.twinId, farmId: f.id, nodes: []); - await _showDialog('Farm Created!', - 'Farm $farmName has been added successfully', Icons.check, DialogType.Info); + await _showDialog( + 'Farm Created!', + 'Farm $farmName has been added successfully', + Icons.check, + DialogType.Info); } catch (e) { print(e); - _showDialog( - 'Error', 'Failed to create farm. Please try again.', Icons.error, DialogType.Error); + _showDialog('Error', 'Failed to create farm. Please try again.', + Icons.error, DialogType.Error); saveLoading = false; setState(() {}); return; @@ -118,6 +122,12 @@ class _NewFarmState extends State { padding: EdgeInsets.fromLTRB(16, 16, 16, keyboardSpace + 16), child: Column( children: [ + Text( + 'Create Farm', + style: Theme.of(context).textTheme.headlineSmall!.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), + ), TextField( style: Theme.of(context).textTheme.bodyMedium!.copyWith( color: Theme.of(context).colorScheme.onBackground, diff --git a/app/lib/widgets/wallets/add_contact.dart b/app/lib/widgets/wallets/add_contact.dart index d918558e..d2b9f0e1 100644 --- a/app/lib/widgets/wallets/add_contact.dart +++ b/app/lib/widgets/wallets/add_contact.dart @@ -123,6 +123,12 @@ class _NewContactState extends State { padding: EdgeInsets.fromLTRB(16, 16, 16, keyboardSpace + 16), child: Column( children: [ + Text( + 'Add Contact', + style: Theme.of(context).textTheme.headlineSmall!.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), + ), TextField( style: Theme.of(context).textTheme.bodyMedium!.copyWith( color: Theme.of(context).colorScheme.onBackground, diff --git a/app/lib/widgets/wallets/add_wallet.dart b/app/lib/widgets/wallets/add_wallet.dart index 14e78507..4b0c0260 100644 --- a/app/lib/widgets/wallets/add_wallet.dart +++ b/app/lib/widgets/wallets/add_wallet.dart @@ -28,7 +28,8 @@ class _NewWalletState extends State { bool saveLoading = false; String? nameError; String? secretError; - Future _showDialog(String title, String message, IconData icon, DialogType type) async { + Future _showDialog( + String title, String message, IconData icon, DialogType type) async { showDialog( barrierDismissible: false, context: context, @@ -91,20 +92,23 @@ class _NewWalletState extends State { wallet = await loadAddedWallet(walletName, walletSecret); } catch (e) { print(e); - _showDialog( - 'Error', 'Failed to load wallet. Please try again.', Icons.error, DialogType.Error); + _showDialog('Error', 'Failed to load wallet. Please try again.', + Icons.error, DialogType.Error); saveLoading = false; setState(() {}); return; } try { await addWallet(walletName, walletSecret); - await _showDialog('Wallet Added!', - 'Wallet $walletName has been added successfully', Icons.check, DialogType.Info); + await _showDialog( + 'Wallet Added!', + 'Wallet $walletName has been added successfully', + Icons.check, + DialogType.Info); } catch (e) { print(e); - _showDialog( - 'Error', 'Failed to save wallet. Please try again.', Icons.error, DialogType.Error); + _showDialog('Error', 'Failed to save wallet. Please try again.', + Icons.error, DialogType.Error); saveLoading = false; setState(() {}); return; @@ -133,6 +137,12 @@ class _NewWalletState extends State { padding: EdgeInsets.fromLTRB(16, 16, 16, keyboardSpace + 16), child: Column( children: [ + Text( + 'Import Wallet', + style: Theme.of(context).textTheme.headlineSmall!.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), + ), TextField( style: Theme.of(context).textTheme.bodyMedium!.copyWith( color: Theme.of(context).colorScheme.onBackground, From dc72c94b4b043ff56c3bee0bf875dcef75eb41ea Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Sat, 28 Sep 2024 02:45:01 +0300 Subject: [PATCH 37/56] Fix home screen sizes --- app/lib/screens/registered_screen.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/screens/registered_screen.dart b/app/lib/screens/registered_screen.dart index 97a8d1d6..aeb92d32 100644 --- a/app/lib/screens/registered_screen.dart +++ b/app/lib/screens/registered_screen.dart @@ -48,8 +48,8 @@ class _RegisteredScreenState extends State ), ), Container( - margin: const EdgeInsets.symmetric(vertical: 50, horizontal: 10), - height: MediaQuery.of(context).size.height * 0.5, + padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 10), + height: MediaQuery.of(context).size.height * 0.6, width: MediaQuery.of(context).size.width, child: Column( mainAxisAlignment: MainAxisAlignment.start, From 2003d8da6ed65028c985e401ebf03e91e994d64d Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Sat, 28 Sep 2024 02:48:05 +0300 Subject: [PATCH 38/56] Fix farming name --- app/lib/screens/farm_screen.dart | 2 +- app/lib/widgets/layout_drawer.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/screens/farm_screen.dart b/app/lib/screens/farm_screen.dart index 75e85eaa..ae03220e 100644 --- a/app/lib/screens/farm_screen.dart +++ b/app/lib/screens/farm_screen.dart @@ -79,7 +79,7 @@ class _FarmScreenState extends State { children: [for (final farm in farms) FarmItemWidget(farm: farm)]); } return LayoutDrawer( - titleText: 'Farms', + titleText: 'Farming', content: mainWidget, appBarActions: loading ? [] diff --git a/app/lib/widgets/layout_drawer.dart b/app/lib/widgets/layout_drawer.dart index 22674044..701a7582 100644 --- a/app/lib/widgets/layout_drawer.dart +++ b/app/lib/widgets/layout_drawer.dart @@ -202,7 +202,7 @@ class _LayoutDrawerState extends State { BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'), BottomNavigationBarItem( icon: Icon(Icons.account_balance_wallet), label: 'Wallet'), - BottomNavigationBarItem(icon: Icon(Icons.storage), label: 'Farms'), + BottomNavigationBarItem(icon: Icon(Icons.storage), label: 'Farming'), BottomNavigationBarItem( icon: Icon(Icons.settings), label: 'Settings'), ], From d611a39b0ed0a610658c51587483a125c80d0a9b Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Sat, 28 Sep 2024 13:07:30 +0300 Subject: [PATCH 39/56] Fix showing home after screen with keyboard was active --- app/lib/screens/registered_screen.dart | 187 +++++++++++++------------ 1 file changed, 96 insertions(+), 91 deletions(-) diff --git a/app/lib/screens/registered_screen.dart b/app/lib/screens/registered_screen.dart index aeb92d32..d76162e1 100644 --- a/app/lib/screens/registered_screen.dart +++ b/app/lib/screens/registered_screen.dart @@ -27,99 +27,104 @@ class _RegisteredScreenState extends State @override Widget build(BuildContext context) { return Scaffold( - body: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - SizedBox( - height: MediaQuery.of(context).size.height * 0.3, - width: MediaQuery.of(context).size.width, - child: Stack( - alignment: Alignment.center, - children: [ - Image.asset( - 'assets/map.png', - fit: BoxFit.cover, - ), - const Hero( - tag: 'logo', - child: HomeLogoWidget(), - ), - ], - ), - ), - Container( - padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 10), - height: MediaQuery.of(context).size.height * 0.6, - width: MediaQuery.of(context).size.width, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - SizedBox( - width: MediaQuery.of(context).size.width / 1.2, - child: RichText( - textAlign: TextAlign.center, - text: TextSpan( - style: Theme.of(context).textTheme.titleMedium!.copyWith( - color: Theme.of(context).colorScheme.onBackground, - ), - children: const [ - TextSpan( - text: - 'ThreeFold Connect App is 2FA authenticator. '), - TextSpan( - text: - 'By using ThreeFold Connect you can ensure that a user is who the say they are.'), - ]), + body: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox( + height: MediaQuery.of(context).size.height * 0.3, + width: MediaQuery.of(context).size.width, + child: Stack( + alignment: Alignment.center, + children: [ + Image.asset( + 'assets/map.png', + fit: BoxFit.cover, + ), + const Hero( + tag: 'logo', + child: HomeLogoWidget(), ), - ), - const Spacer(), - const Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - HomeCardWidget( - name: 'Wallet', - icon: Icons.account_balance_wallet, - pageNumber: 2, - fullWidth: true), - ], - ), - const Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - HomeCardWidget( - name: 'Farming', icon: Icons.storage, pageNumber: 3), - HomeCardWidget( - name: 'Dao', - icon: Icons.how_to_vote_outlined, - pageNumber: 4), - ], - ), - const Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - HomeCardWidget( - name: 'News', icon: Icons.article, pageNumber: 1), - HomeCardWidget( - name: 'Support', icon: Icons.build, pageNumber: 5), - ], - ), - const Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - HomeCardWidget( - name: 'Identity', icon: Icons.person, pageNumber: 6), - HomeCardWidget( - name: 'Settings', icon: Icons.settings, pageNumber: 7), - ], - ), - ], + ], + ), ), - ) - ], + Container( + padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 10), + height: MediaQuery.of(context).size.height * 0.6, + width: MediaQuery.of(context).size.width, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width / 1.2, + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), + children: const [ + TextSpan( + text: + 'ThreeFold Connect App is 2FA authenticator. '), + TextSpan( + text: + 'By using ThreeFold Connect you can ensure that a user is who the say they are.'), + ]), + ), + ), + const Spacer(), + const Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + HomeCardWidget( + name: 'Wallet', + icon: Icons.account_balance_wallet, + pageNumber: 2, + fullWidth: true), + ], + ), + const Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + HomeCardWidget( + name: 'Farming', icon: Icons.storage, pageNumber: 3), + HomeCardWidget( + name: 'Dao', + icon: Icons.how_to_vote_outlined, + pageNumber: 4), + ], + ), + const Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + HomeCardWidget( + name: 'News', icon: Icons.article, pageNumber: 1), + HomeCardWidget( + name: 'Support', icon: Icons.build, pageNumber: 5), + ], + ), + const Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + HomeCardWidget( + name: 'Identity', icon: Icons.person, pageNumber: 6), + HomeCardWidget( + name: 'Settings', icon: Icons.settings, pageNumber: 7), + ], + ), + ], + ), + ) + ], + ), )); } From 819e23a6f6129d295497fe227d706328d6ca5e19 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Sat, 28 Sep 2024 13:16:50 +0300 Subject: [PATCH 40/56] Add dao as app to authenticate on it --- app/lib/apps/dao/dao.dart | 42 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 app/lib/apps/dao/dao.dart diff --git a/app/lib/apps/dao/dao.dart b/app/lib/apps/dao/dao.dart new file mode 100644 index 00000000..704dd6f6 --- /dev/null +++ b/app/lib/apps/dao/dao.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:threebotlogin/app.dart'; +import 'package:threebotlogin/apps/farmers/farmers_user_data.dart'; +import 'package:threebotlogin/events/events.dart'; +import 'package:threebotlogin/events/go_home_event.dart'; +import 'package:threebotlogin/screens/dao_screen.dart'; + +class Dao implements App { + static final Dao _singleton = Dao._internal(); + static const Widget _daoWidget = DaoPage(); + + factory Dao() { + return _singleton; + } + + Dao._internal(); + + @override + Future widget() async { + return _daoWidget; + } + + @override + void clearData() { + clearAllData(); + } + + @override + bool emailVerificationRequired() { + return false; + } + + @override + bool pinRequired() { + return true; + } + + @override + void back() { + Events().emit(GoHomeEvent()); + } +} From eae1cc19c1edeace09d3159adc7d2f91884a4609 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Sat, 28 Sep 2024 13:20:39 +0300 Subject: [PATCH 41/56] Adjust the router apps --- app/lib/apps/farmers/farmers.dart | 8 ++++---- app/lib/apps/wallet/wallet.dart | 8 ++++---- app/lib/jrouter.dart | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/lib/apps/farmers/farmers.dart b/app/lib/apps/farmers/farmers.dart index b8085795..8e46fa05 100644 --- a/app/lib/apps/farmers/farmers.dart +++ b/app/lib/apps/farmers/farmers.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:threebotlogin/app.dart'; -import 'package:threebotlogin/apps/farmers/farmers_events.dart'; import 'package:threebotlogin/apps/farmers/farmers_user_data.dart'; -import 'package:threebotlogin/apps/farmers/farmers_widget.dart'; import 'package:threebotlogin/events/events.dart'; +import 'package:threebotlogin/events/go_home_event.dart'; +import 'package:threebotlogin/screens/farm_screen.dart'; class Farmers implements App { static final Farmers _singleton = Farmers._internal(); - static const FarmersWidget _farmersWidget = FarmersWidget(); + static const Widget _farmersWidget = FarmScreen(); factory Farmers() { return _singleton; @@ -37,6 +37,6 @@ class Farmers implements App { @override void back() { - Events().emit(FarmersBackEvent()); + Events().emit(GoHomeEvent()); } } diff --git a/app/lib/apps/wallet/wallet.dart b/app/lib/apps/wallet/wallet.dart index 8ce9c923..1fbe1321 100644 --- a/app/lib/apps/wallet/wallet.dart +++ b/app/lib/apps/wallet/wallet.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:threebotlogin/app.dart'; -import 'package:threebotlogin/apps/wallet/wallet_events.dart'; import 'package:threebotlogin/apps/wallet/wallet_user_data.dart'; -import 'package:threebotlogin/apps/wallet/wallet_widget.dart'; import 'package:threebotlogin/events/events.dart'; +import 'package:threebotlogin/events/go_home_event.dart'; +import 'package:threebotlogin/screens/wallets/wallet_screen.dart'; class Wallet implements App { static final Wallet _singleton = Wallet._internal(); - static const WalletWidget _walletWidget = WalletWidget(); + static const Widget _walletWidget = WalletScreen(); factory Wallet() { return _singleton; @@ -37,6 +37,6 @@ class Wallet implements App { @override void back() { - Events().emit(WalletBackEvent()); + Events().emit(GoHomeEvent()); } } diff --git a/app/lib/jrouter.dart b/app/lib/jrouter.dart index d425d63a..f6be41c3 100644 --- a/app/lib/jrouter.dart +++ b/app/lib/jrouter.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:threebotlogin/app.dart'; import 'package:threebotlogin/apps/chatbot/chatbot.dart'; +import 'package:threebotlogin/apps/dao/dao.dart'; import 'package:threebotlogin/apps/wallet/wallet.dart'; -import 'package:threebotlogin/screens/dao_screen.dart'; import 'package:threebotlogin/screens/identity_verification_screen.dart'; import 'package:threebotlogin/screens/preference_screen.dart'; import 'package:threebotlogin/screens/registered_screen.dart'; @@ -59,9 +59,9 @@ class JRouter { path: '/dao', name: 'Dao', icon: Icons.how_to_vote_outlined, - view: const DaoPage(), + view: await Dao().widget(), ), - app: null), + app: Dao()), AppInfo( route: Route( path: '/chatbot', From b05427a7948942242b46b14d56b246c32328ed48 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Sat, 28 Sep 2024 14:29:25 +0300 Subject: [PATCH 42/56] Fix fingerprint authentication --- .../src/main/kotlin/org/jimber/threebotlogin/MainActivity.kt | 4 ++-- .../main/kotlin/org/jimber/threebotlogin/MainActivity_local | 4 ++-- .../kotlin/org/jimber/threebotlogin/MainActivity_production | 4 ++-- .../main/kotlin/org/jimber/threebotlogin/MainActivity_staging | 4 ++-- .../main/kotlin/org/jimber/threebotlogin/MainActivity_testing | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity.kt b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity.kt index 81b7f3d1..35f37c04 100644 --- a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity.kt +++ b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity.kt @@ -1,10 +1,10 @@ package org.jimber.threebotlogin -import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugins.GeneratedPluginRegistrant -class MainActivity: FlutterActivity() { +class MainActivity: FlutterFragmentActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) } diff --git a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_local b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_local index 427ad4ef..78f68439 100644 --- a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_local +++ b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_local @@ -1,10 +1,10 @@ package org.jimber.threebotlogin.local -import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugins.GeneratedPluginRegistrant -class MainActivity: FlutterActivity() { +class MainActivity: FlutterFragmentActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) } diff --git a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_production b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_production index 81b7f3d1..35f37c04 100644 --- a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_production +++ b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_production @@ -1,10 +1,10 @@ package org.jimber.threebotlogin -import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugins.GeneratedPluginRegistrant -class MainActivity: FlutterActivity() { +class MainActivity: FlutterFragmentActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) } diff --git a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_staging b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_staging index 21ff04a8..435c13df 100644 --- a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_staging +++ b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_staging @@ -1,10 +1,10 @@ package org.jimber.threebotlogin.staging -import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugins.GeneratedPluginRegistrant -class MainActivity: FlutterActivity() { +class MainActivity: FlutterFragmentActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) } diff --git a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_testing b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_testing index d980d466..369e8b9d 100644 --- a/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_testing +++ b/app/android/app/src/main/kotlin/org/jimber/threebotlogin/MainActivity_testing @@ -1,10 +1,10 @@ package org.jimber.threebotlogin.testing -import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugins.GeneratedPluginRegistrant -class MainActivity: FlutterActivity() { +class MainActivity: FlutterFragmentActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) } From 47aeea08e712a8ea93e2bf34647f73cde5f73969 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Sat, 28 Sep 2024 14:35:25 +0300 Subject: [PATCH 43/56] Fix back button navigation on android --- app/android/app/src/main/AndroidManifest_local | 2 +- app/android/app/src/main/AndroidManifest_production | 2 +- app/android/app/src/main/AndroidManifest_staging | 2 +- app/android/app/src/main/AndroidManifest_testing | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/android/app/src/main/AndroidManifest_local b/app/android/app/src/main/AndroidManifest_local index ee4786bc..ce9dba7a 100644 --- a/app/android/app/src/main/AndroidManifest_local +++ b/app/android/app/src/main/AndroidManifest_local @@ -1,7 +1,7 @@ + android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize" android:enableOnBackInvokedCallback="false"> diff --git a/app/android/app/src/main/AndroidManifest_production b/app/android/app/src/main/AndroidManifest_production index 936dedac..01a6a985 100644 --- a/app/android/app/src/main/AndroidManifest_production +++ b/app/android/app/src/main/AndroidManifest_production @@ -1,7 +1,7 @@ + android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize" android:enableOnBackInvokedCallback="false"> diff --git a/app/android/app/src/main/AndroidManifest_staging b/app/android/app/src/main/AndroidManifest_staging index cea04629..47c590d8 100644 --- a/app/android/app/src/main/AndroidManifest_staging +++ b/app/android/app/src/main/AndroidManifest_staging @@ -1,7 +1,7 @@ + android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize" android:enableOnBackInvokedCallback="false"> diff --git a/app/android/app/src/main/AndroidManifest_testing b/app/android/app/src/main/AndroidManifest_testing index 5e3d3efa..5baaf0b4 100644 --- a/app/android/app/src/main/AndroidManifest_testing +++ b/app/android/app/src/main/AndroidManifest_testing @@ -1,7 +1,7 @@ + android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize" android:enableOnBackInvokedCallback="false"> From c1edd40a2ddf43c0bbdedf6af0546255a578891b Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Sat, 28 Sep 2024 18:09:53 +0300 Subject: [PATCH 44/56] Support deleting contacts --- app/lib/screens/wallets/contacts.dart | 6 ++ app/lib/widgets/wallets/contact_card.dart | 91 +++++++++++++++++--- app/lib/widgets/wallets/contacts_widget.dart | 11 ++- 3 files changed, 93 insertions(+), 15 deletions(-) diff --git a/app/lib/screens/wallets/contacts.dart b/app/lib/screens/wallets/contacts.dart index 23d78a4c..b3a0a623 100644 --- a/app/lib/screens/wallets/contacts.dart +++ b/app/lib/screens/wallets/contacts.dart @@ -66,6 +66,11 @@ class _ContractsScreenState extends State { )); } + _onDeleteContact(String name) { + myPkidContacts = myPkidContacts.where((c) => c.name != name).toList(); + setState(() {}); + } + @override void initState() { _loadMyWalletContacts(); @@ -113,6 +118,7 @@ class _ContractsScreenState extends State { ContactsWidget( contacts: myPkidContacts, onSelectToAddress: widget.onSelectToAddress, + onDeleteContact: _onDeleteContact, canEditAndDelete: true, ), ], diff --git a/app/lib/widgets/wallets/contact_card.dart b/app/lib/widgets/wallets/contact_card.dart index af7f9c72..9ab07f71 100644 --- a/app/lib/widgets/wallets/contact_card.dart +++ b/app/lib/widgets/wallets/contact_card.dart @@ -1,14 +1,75 @@ import 'package:flutter/material.dart'; +import 'package:threebotlogin/services/contact_service.dart'; +import 'package:threebotlogin/widgets/custom_dialog.dart'; -class ContactCardWidget extends StatelessWidget { +class ContactCardWidget extends StatefulWidget { const ContactCardWidget( {super.key, required this.name, required this.address, - required this.canEditAndDelete}); + required this.canEditAndDelete, + this.onDeleteContact}); final String name; final String address; final bool canEditAndDelete; + final void Function(String name)? onDeleteContact; + + @override + State createState() => _ContactCardWidgetState(); +} + +class _ContactCardWidgetState extends State { + bool deleteLoading = false; + _deleteWallet() async { + setState(() { + deleteLoading = true; + }); + //TODO: Show snack in case of failure + await deleteContact(widget.name); + widget.onDeleteContact!(widget.name); + + setState(() { + deleteLoading = false; + }); + } + + void _showDeleteConfirmationDialog() { + showDialog( + context: context, + builder: (BuildContext context) => CustomDialog( + type: DialogType.Warning, + image: Icons.warning, + title: 'Are you sure?', + description: + 'If you confirm, your wallet will be removed from this device.', + actions: [ + TextButton( + child: const Text('Cancel'), + onPressed: () { + Navigator.pop(context); + }, + ), + TextButton( + onPressed: () async { + await _deleteWallet(); + if (context.mounted) { + Navigator.pop(context); + Navigator.pop(context); + } + }, + //TODO: show loading when press yes + child: Text( + 'Yes', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Theme.of(context).colorScheme.error), + ), + ), + ], + ), + ); + } @override Widget build(BuildContext context) { @@ -27,7 +88,7 @@ class ContactCardWidget extends StatelessWidget { children: [ Expanded( child: Text( - name, + widget.name, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.titleLarge!.copyWith( color: Theme.of(context) @@ -36,7 +97,7 @@ class ContactCardWidget extends StatelessWidget { ), ), ), - if (canEditAndDelete) + if (widget.canEditAndDelete) Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, @@ -46,18 +107,26 @@ class ContactCardWidget extends StatelessWidget { icon: const Icon( Icons.edit, )), - IconButton( - onPressed: () {}, - icon: Icon( - Icons.delete, - color: Theme.of(context).colorScheme.error, - )), + deleteLoading + ? SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Theme.of(context).colorScheme.error, + )) + : IconButton( + onPressed: _showDeleteConfirmationDialog, + icon: Icon( + Icons.delete, + color: Theme.of(context).colorScheme.error, + )), ], ), ], ), Text( - address, + widget.address, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.bodyMedium!.copyWith( color: Theme.of(context).colorScheme.onSecondaryContainer, diff --git a/app/lib/widgets/wallets/contacts_widget.dart b/app/lib/widgets/wallets/contacts_widget.dart index 3432d92d..dd8b26fb 100644 --- a/app/lib/widgets/wallets/contacts_widget.dart +++ b/app/lib/widgets/wallets/contacts_widget.dart @@ -7,11 +7,14 @@ class ContactsWidget extends StatelessWidget { super.key, required this.contacts, required this.onSelectToAddress, + this.onDeleteContact, this.canEditAndDelete = false, }); + final List contacts; final void Function(String address) onSelectToAddress; final bool canEditAndDelete; + final void Function(String name)? onDeleteContact; @override Widget build(BuildContext context) { @@ -23,10 +26,10 @@ class ContactsWidget extends StatelessWidget { Navigator.of(context).pop(); }, child: ContactCardWidget( - name: contact.name, - address: contact.address, - canEditAndDelete: canEditAndDelete, - )), + name: contact.name, + address: contact.address, + canEditAndDelete: canEditAndDelete, + onDeleteContact: onDeleteContact)), ]); } } From 1f881b56215994af5754458dac8ea0ad76257573 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Sat, 28 Sep 2024 19:25:20 +0300 Subject: [PATCH 45/56] Support Edit contacts --- app/lib/models/contact.dart | 2 + app/lib/screens/wallets/contacts.dart | 30 ++++- ...add_contact.dart => add_edit_contact.dart} | 103 ++++++++++++++++-- app/lib/widgets/wallets/contact_card.dart | 19 ++-- app/lib/widgets/wallets/contacts_widget.dart | 6 +- 5 files changed, 138 insertions(+), 22 deletions(-) rename app/lib/widgets/wallets/{add_contact.dart => add_edit_contact.dart} (64%) diff --git a/app/lib/models/contact.dart b/app/lib/models/contact.dart index fc49ddbd..44c52904 100644 --- a/app/lib/models/contact.dart +++ b/app/lib/models/contact.dart @@ -1,5 +1,7 @@ import 'package:threebotlogin/models/wallet.dart'; +enum ContactOperation { Add, Edit } + class PkidContact { PkidContact({ required this.name, diff --git a/app/lib/screens/wallets/contacts.dart b/app/lib/screens/wallets/contacts.dart index b3a0a623..35682ce6 100644 --- a/app/lib/screens/wallets/contacts.dart +++ b/app/lib/screens/wallets/contacts.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:threebotlogin/models/contact.dart'; import 'package:threebotlogin/models/wallet.dart'; import 'package:threebotlogin/services/contact_service.dart'; -import 'package:threebotlogin/widgets/wallets/add_contact.dart'; +import 'package:threebotlogin/widgets/wallets/add_edit_contact.dart'; import 'package:threebotlogin/widgets/wallets/contacts_widget.dart'; class ContractsScreen extends StatefulWidget { @@ -59,13 +59,38 @@ class _ContractsScreenState extends State { useSafeArea: true, constraints: const BoxConstraints(maxWidth: double.infinity), context: context, - builder: (ctx) => NewContact( + builder: (ctx) => AddEditContact( onAddContact: _onAddContact, chainType: widget.chainType, contacts: [...myPkidContacts, ...myWalletContacts], )); } + _openEditContactOverlay(String name, String address) { + showModalBottomSheet( + isScrollControlled: true, + useSafeArea: true, + constraints: const BoxConstraints(maxWidth: double.infinity), + context: context, + builder: (ctx) => AddEditContact( + chainType: widget.chainType, + contacts: [...myPkidContacts, ...myWalletContacts], + name: name, + address: address, + onEditContact: _onEditContact, + )); + } + + _onEditContact(String oldName, String newName, String newAddress) { + for (final c in myPkidContacts) { + if (c.name == oldName) { + c.name = newName; + c.address = newAddress; + } + } + setState(() {}); + } + _onDeleteContact(String name) { myPkidContacts = myPkidContacts.where((c) => c.name != name).toList(); setState(() {}); @@ -119,6 +144,7 @@ class _ContractsScreenState extends State { contacts: myPkidContacts, onSelectToAddress: widget.onSelectToAddress, onDeleteContact: _onDeleteContact, + onEditContact: _openEditContactOverlay, canEditAndDelete: true, ), ], diff --git a/app/lib/widgets/wallets/add_contact.dart b/app/lib/widgets/wallets/add_edit_contact.dart similarity index 64% rename from app/lib/widgets/wallets/add_contact.dart rename to app/lib/widgets/wallets/add_edit_contact.dart index d2b9f0e1..4cd620e5 100644 --- a/app/lib/widgets/wallets/add_contact.dart +++ b/app/lib/widgets/wallets/add_edit_contact.dart @@ -6,23 +6,34 @@ import 'package:threebotlogin/models/wallet.dart'; import 'package:threebotlogin/services/contact_service.dart'; import 'package:threebotlogin/widgets/custom_dialog.dart'; -class NewContact extends StatefulWidget { - const NewContact( - {super.key, - required this.onAddContact, - required this.contacts, - required this.chainType}); - final void Function(PkidContact addedContact) onAddContact; +class AddEditContact extends StatefulWidget { + const AddEditContact({ + super.key, + required this.contacts, + required this.chainType, + this.operation = ContactOperation.Add, + this.onAddContact, + this.name = '', + this.address = '', + this.onEditContact, + }); + + final void Function(PkidContact addedContact)? onAddContact; final List contacts; final ChainType chainType; + final ContactOperation operation; + final String name; + final String address; + final void Function(String oldName, String newName, String newAddress)? + onEditContact; @override State createState() { - return _NewContactState(); + return _AddEditContactState(); } } -class _NewContactState extends State { +class _AddEditContactState extends State { final _nameController = TextEditingController(); final _addressController = TextEditingController(); bool saveLoading = false; @@ -48,7 +59,7 @@ class _NewContactState extends State { ); } - Future _validateAddSubmitData() async { + Future _validateAndAdd() async { final contactName = _nameController.text.trim(); final contactAddress = _addressController.text.trim(); nameError = null; @@ -98,7 +109,7 @@ class _NewContactState extends State { setState(() {}); return; } - widget.onAddContact(PkidContact( + widget.onAddContact!(PkidContact( name: contactName, address: contactAddress, type: widget.chainType)); saveLoading = false; setState(() {}); @@ -106,6 +117,72 @@ class _NewContactState extends State { Navigator.pop(context); } + Future _validateAndEdit() async { + final contactName = _nameController.text.trim(); + final contactAddress = _addressController.text.trim(); + nameError = null; + addressError = null; + saveLoading = true; + setState(() {}); + + if (contactName.isEmpty) { + nameError = "Name can't be empty"; + saveLoading = false; + setState(() {}); + return; + } + final c = widget.contacts.where((element) => element.name == contactName); + if (contactName != widget.name && c.isNotEmpty) { + nameError = 'Name is used for another contact'; + saveLoading = false; + setState(() {}); + return; + } + if (contactAddress.isEmpty) { + addressError = "Address can't be empty"; + saveLoading = false; + setState(() {}); + return; + } + final contacts = widget.contacts.where((c) => c.address == contactAddress); + if (contactAddress != widget.address && contacts.isNotEmpty) { + addressError = 'Address is used in another contact'; + saveLoading = false; + setState(() {}); + return; + } + // TODO: add address validation based on the chain type + try { + await editContact(widget.name, contactAddress, contactAddress); + await _showDialog( + 'Contact Modified!', + 'Contact $contactName has been modified successfully', + Icons.check, + DialogType.Info); + } catch (e) { + print(e); + _showDialog('Error', 'Failed to modify contact. Please try again.', + Icons.error, DialogType.Error); + saveLoading = false; + setState(() {}); + return; + } + widget.onEditContact!(widget.name, contactAddress, contactAddress); + saveLoading = false; + setState(() {}); + if (!context.mounted) return; + Navigator.pop(context); + } + + @override + void initState() { + if (widget.operation == ContactOperation.Edit) { + _nameController.text = widget.name; + _addressController.text = widget.address; + } + super.initState(); + } + @override void dispose() { _nameController.dispose(); @@ -168,7 +245,9 @@ class _NewContactState extends State { width: 5, ), ElevatedButton( - onPressed: _validateAddSubmitData, + onPressed: widget.operation == ContactOperation.Add + ? _validateAndAdd + : _validateAndEdit, child: saveLoading ? const SizedBox( width: 20, diff --git a/app/lib/widgets/wallets/contact_card.dart b/app/lib/widgets/wallets/contact_card.dart index 9ab07f71..fff42ed9 100644 --- a/app/lib/widgets/wallets/contact_card.dart +++ b/app/lib/widgets/wallets/contact_card.dart @@ -3,16 +3,19 @@ import 'package:threebotlogin/services/contact_service.dart'; import 'package:threebotlogin/widgets/custom_dialog.dart'; class ContactCardWidget extends StatefulWidget { - const ContactCardWidget( - {super.key, - required this.name, - required this.address, - required this.canEditAndDelete, - this.onDeleteContact}); + const ContactCardWidget({ + super.key, + required this.name, + required this.address, + required this.canEditAndDelete, + this.onDeleteContact, + this.onEditContact, + }); final String name; final String address; final bool canEditAndDelete; final void Function(String name)? onDeleteContact; + final void Function(String oldName, String oldAddress)? onEditContact; @override State createState() => _ContactCardWidgetState(); @@ -103,7 +106,9 @@ class _ContactCardWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.end, children: [ IconButton( - onPressed: () {}, + onPressed: () { + widget.onEditContact!(widget.name, widget.address); + }, icon: const Icon( Icons.edit, )), diff --git a/app/lib/widgets/wallets/contacts_widget.dart b/app/lib/widgets/wallets/contacts_widget.dart index dd8b26fb..8b7a7c48 100644 --- a/app/lib/widgets/wallets/contacts_widget.dart +++ b/app/lib/widgets/wallets/contacts_widget.dart @@ -8,6 +8,7 @@ class ContactsWidget extends StatelessWidget { required this.contacts, required this.onSelectToAddress, this.onDeleteContact, + this.onEditContact, this.canEditAndDelete = false, }); @@ -15,6 +16,7 @@ class ContactsWidget extends StatelessWidget { final void Function(String address) onSelectToAddress; final bool canEditAndDelete; final void Function(String name)? onDeleteContact; + final void Function(String oldName, String oldAddress)? onEditContact; @override Widget build(BuildContext context) { @@ -29,7 +31,9 @@ class ContactsWidget extends StatelessWidget { name: contact.name, address: contact.address, canEditAndDelete: canEditAndDelete, - onDeleteContact: onDeleteContact)), + onDeleteContact: onDeleteContact, + onEditContact: onEditContact, + )), ]); } } From 0fe6096c6c4003a02fcc90b914f67f5db1522b45 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Sat, 28 Sep 2024 21:45:50 +0300 Subject: [PATCH 46/56] Fix contact addition, modification, and deletion --- app/lib/screens/wallets/contacts.dart | 12 +++++++----- app/lib/widgets/wallets/add_edit_contact.dart | 6 ++++-- app/lib/widgets/wallets/contact_card.dart | 3 +-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/app/lib/screens/wallets/contacts.dart b/app/lib/screens/wallets/contacts.dart index 35682ce6..7a3d03f4 100644 --- a/app/lib/screens/wallets/contacts.dart +++ b/app/lib/screens/wallets/contacts.dart @@ -28,13 +28,11 @@ class _ContractsScreenState extends State { _loadMyWalletContacts() { for (final w in widget.wallets) { - if (widget.chainType == ChainType.Stellar && - w.stellarAddress != widget.currentWalletAddress) { + if (widget.chainType == ChainType.Stellar) { myWalletContacts.add(PkidContact( name: w.name, address: w.stellarAddress, type: ChainType.Stellar)); } - if (widget.chainType == ChainType.TFChain && - w.tfchainAddress != widget.currentWalletAddress) { + if (widget.chainType == ChainType.TFChain) { myWalletContacts.add(PkidContact( name: w.name, address: w.tfchainAddress, type: ChainType.TFChain)); } @@ -75,6 +73,7 @@ class _ContractsScreenState extends State { builder: (ctx) => AddEditContact( chainType: widget.chainType, contacts: [...myPkidContacts, ...myWalletContacts], + operation: ContactOperation.Edit, name: name, address: address, onEditContact: _onEditContact, @@ -138,7 +137,10 @@ class _ContractsScreenState extends State { child: TabBarView( children: [ ContactsWidget( - contacts: myWalletContacts, + contacts: myWalletContacts + .where( + (c) => c.address != widget.currentWalletAddress) + .toList(), onSelectToAddress: widget.onSelectToAddress), ContactsWidget( contacts: myPkidContacts, diff --git a/app/lib/widgets/wallets/add_edit_contact.dart b/app/lib/widgets/wallets/add_edit_contact.dart index 4cd620e5..12eeeeea 100644 --- a/app/lib/widgets/wallets/add_edit_contact.dart +++ b/app/lib/widgets/wallets/add_edit_contact.dart @@ -167,7 +167,7 @@ class _AddEditContactState extends State { setState(() {}); return; } - widget.onEditContact!(widget.name, contactAddress, contactAddress); + widget.onEditContact!(widget.name, contactName, contactAddress); saveLoading = false; setState(() {}); if (!context.mounted) return; @@ -201,7 +201,9 @@ class _AddEditContactState extends State { child: Column( children: [ Text( - 'Add Contact', + widget.operation == ContactOperation.Add + ? 'Add Contact' + : 'Edit Contact', style: Theme.of(context).textTheme.headlineSmall!.copyWith( color: Theme.of(context).colorScheme.onBackground, ), diff --git a/app/lib/widgets/wallets/contact_card.dart b/app/lib/widgets/wallets/contact_card.dart index fff42ed9..9cdf5c0f 100644 --- a/app/lib/widgets/wallets/contact_card.dart +++ b/app/lib/widgets/wallets/contact_card.dart @@ -44,7 +44,7 @@ class _ContactCardWidgetState extends State { image: Icons.warning, title: 'Are you sure?', description: - 'If you confirm, your wallet will be removed from this device.', + 'If you confirm, your contact will be removed from this device.', actions: [ TextButton( child: const Text('Cancel'), @@ -57,7 +57,6 @@ class _ContactCardWidgetState extends State { await _deleteWallet(); if (context.mounted) { Navigator.pop(context); - Navigator.pop(context); } }, //TODO: show loading when press yes From 79b0123dd830ffdee607d35fa7602c06824fc3ba Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Sun, 29 Sep 2024 21:02:00 +0300 Subject: [PATCH 47/56] Hide wallet if it doesn't have TFT asset --- app/lib/screens/wallets/wallet_assets.dart | 37 ++++--- app/lib/services/stellar_service.dart | 10 +- app/lib/widgets/wallets/wallet_card.dart | 113 +++++++++++---------- 3 files changed, 85 insertions(+), 75 deletions(-) diff --git a/app/lib/screens/wallets/wallet_assets.dart b/app/lib/screens/wallets/wallet_assets.dart index 16bfdb2d..2dd2bdc9 100644 --- a/app/lib/screens/wallets/wallet_assets.dart +++ b/app/lib/screens/wallets/wallet_assets.dart @@ -35,9 +35,10 @@ class _WalletAssetsWidgetState extends State { tfchainBalaceLoading = true; }); final chainUrl = Globals().chainUrl; + final balance = + await TFChain.getBalance(chainUrl, widget.wallet.tfchainAddress); widget.wallet.tfchainBalance = - (await TFChain.getBalance(chainUrl, widget.wallet.tfchainAddress)) - .toString(); + balance.toString() == '0.0' ? '0' : balance.toString(); setState(() { tfchainBalaceLoading = false; }); @@ -110,10 +111,12 @@ class _WalletAssetsWidgetState extends State { }, child: CircleAvatar( radius: 30, - backgroundColor: Theme.of(context).colorScheme.primaryContainer, + backgroundColor: + Theme.of(context).colorScheme.primaryContainer, child: Icon( Icons.arrow_outward_outlined, - color: Theme.of(context).colorScheme.onPrimaryContainer, + color: + Theme.of(context).colorScheme.onPrimaryContainer, size: 30, ), ), @@ -141,7 +144,9 @@ class _WalletAssetsWidgetState extends State { backgroundColor: Theme.of(context).colorScheme.primaryContainer, child: ArrowInward( - color: Theme.of(context).colorScheme.onPrimaryContainer, + color: Theme.of(context) + .colorScheme + .onPrimaryContainer, size: 30, )), ), @@ -167,17 +172,19 @@ class _WalletAssetsWidgetState extends State { const SizedBox( height: 20, ), - WalletBalanceTileWidget( - name: 'Stellar', - balance: widget.wallet.stellarBalance, - loading: stellarBalaceLoading, - ), + if (double.parse(widget.wallet.stellarBalance) >= 0) + WalletBalanceTileWidget( + name: 'Stellar', + balance: widget.wallet.stellarBalance, + loading: stellarBalaceLoading, + ), const SizedBox(height: 10), - WalletBalanceTileWidget( - name: 'TFChain', - balance: widget.wallet.tfchainBalance, - loading: tfchainBalaceLoading, - ), + if (double.parse(widget.wallet.tfchainBalance) >= 0) + WalletBalanceTileWidget( + name: 'TFChain', + balance: widget.wallet.tfchainBalance, + loading: tfchainBalaceLoading, + ), const SizedBox( height: 20, ), diff --git a/app/lib/services/stellar_service.dart b/app/lib/services/stellar_service.dart index b56f45a4..547bdd74 100644 --- a/app/lib/services/stellar_service.dart +++ b/app/lib/services/stellar_service.dart @@ -17,13 +17,14 @@ Future getBalanceByClient(Client client) async { final stellarBalances = await client.getBalance(); for (final balance in stellarBalances) { if (balance.assetCode == 'TFT') { + if (double.parse(balance.balance) == 0) return '0'; return balance.balance; } } } catch (e) { print("Couldn't load the account balance."); } - return '0'; + return '-1'; } Future getBalance(String secret) async { @@ -44,12 +45,13 @@ Future?> listVestedAccounts(String secret) async { return accounts; } -Future transfer(String secret, String dest, String amount, String memo) async { +Future transfer( + String secret, String dest, String amount, String memo) async { final client = Client(NetworkType.PUBLIC, secret); - await client.transferThroughThreefoldService( + await client.transferThroughThreefoldService( destinationAddress: dest, amount: amount, currency: 'TFT', memoText: memo, ); -} \ No newline at end of file +} diff --git a/app/lib/widgets/wallets/wallet_card.dart b/app/lib/widgets/wallets/wallet_card.dart index 279895d4..4b1f35f1 100644 --- a/app/lib/widgets/wallets/wallet_card.dart +++ b/app/lib/widgets/wallets/wallet_card.dart @@ -40,62 +40,63 @@ class WalletCardWidget extends StatelessWidget { ), ), const SizedBox(height: 10), - // TODO: hide the balance if there is no account for this wallet - Row( - children: [ - SizedBox( - width: 35, - child: Image.asset( - 'assets/tft_icon.png', - color: Theme.of(context).colorScheme.onBackground, - )), - Text( - 'Stellar', - style: Theme.of(context).textTheme.bodyLarge!.copyWith( - color: Theme.of(context) - .colorScheme - .onSecondaryContainer, - ), - ), - const Spacer(), - Text( - '${wallet.stellarBalance} TFT', - style: Theme.of(context).textTheme.bodyLarge!.copyWith( - color: Theme.of(context) - .colorScheme - .onSecondaryContainer, - ), - ), - ], - ), - Row( - children: [ - SizedBox( - width: 35, - child: Image.asset( - 'assets/tft_icon.png', - fit: BoxFit.contain, - color: Theme.of(context).colorScheme.onBackground, - )), - Text( - 'TFChain', - style: Theme.of(context).textTheme.bodyLarge!.copyWith( - color: Theme.of(context) - .colorScheme - .onSecondaryContainer, - ), - ), - const Spacer(), - Text( - '${wallet.tfchainBalance} TFT', - style: Theme.of(context).textTheme.bodyLarge!.copyWith( - color: Theme.of(context) - .colorScheme - .onSecondaryContainer, - ), - ), - ], - ) + if (double.parse(wallet.stellarBalance) >= 0) + Row( + children: [ + SizedBox( + width: 35, + child: Image.asset( + 'assets/tft_icon.png', + color: Theme.of(context).colorScheme.onBackground, + )), + Text( + 'Stellar', + style: Theme.of(context).textTheme.bodyLarge!.copyWith( + color: Theme.of(context) + .colorScheme + .onSecondaryContainer, + ), + ), + const Spacer(), + Text( + '${wallet.stellarBalance} TFT', + style: Theme.of(context).textTheme.bodyLarge!.copyWith( + color: Theme.of(context) + .colorScheme + .onSecondaryContainer, + ), + ), + ], + ), + if (double.parse(wallet.tfchainBalance) >= 0) + Row( + children: [ + SizedBox( + width: 35, + child: Image.asset( + 'assets/tft_icon.png', + fit: BoxFit.contain, + color: Theme.of(context).colorScheme.onBackground, + )), + Text( + 'TFChain', + style: Theme.of(context).textTheme.bodyLarge!.copyWith( + color: Theme.of(context) + .colorScheme + .onSecondaryContainer, + ), + ), + const Spacer(), + Text( + '${wallet.tfchainBalance} TFT', + style: Theme.of(context).textTheme.bodyLarge!.copyWith( + color: Theme.of(context) + .colorScheme + .onSecondaryContainer, + ), + ), + ], + ) ], ), ), From 6309cfbe454edd783de046142ad9b85220b33e5a Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Sun, 29 Sep 2024 23:22:13 +0300 Subject: [PATCH 48/56] Add the ability to create farm from any wallet imported on the app --- app/lib/screens/farm_screen.dart | 23 +++++++++++++++------- app/lib/services/tfchain_service.dart | 19 ++++++++++++------ app/lib/services/wallet_service.dart | 2 +- app/lib/widgets/add_farm.dart | 28 ++++++++++++++------------- 4 files changed, 45 insertions(+), 27 deletions(-) diff --git a/app/lib/screens/farm_screen.dart b/app/lib/screens/farm_screen.dart index ae03220e..f4321ff8 100644 --- a/app/lib/screens/farm_screen.dart +++ b/app/lib/screens/farm_screen.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:threebotlogin/models/farm.dart'; +import 'package:threebotlogin/models/wallet.dart'; import 'package:threebotlogin/services/gridproxy_service.dart'; +import 'package:threebotlogin/services/tfchain_service.dart'; import 'package:threebotlogin/services/wallet_service.dart'; import 'package:threebotlogin/widgets/add_farm.dart'; import 'package:threebotlogin/widgets/farm_item.dart'; @@ -15,7 +17,7 @@ class FarmScreen extends StatefulWidget { class _FarmScreenState extends State { List farms = []; - Map> twinIdWallets = {}; + List wallets = []; bool loading = true; @@ -29,17 +31,24 @@ class _FarmScreenState extends State { setState(() { loading = true; }); - twinIdWallets = await getWalletsTwinIds(); + wallets = await listWallets(); + final Map twinIdWallets = {}; + for (final w in wallets) { + final twinId = await getTwinId(w.tfchainSecret); + if (twinId != 0) { + twinIdWallets[twinId] = w; + } + } final farmsList = await getFarmsByTwinIds(twinIdWallets.keys.toList()); for (final f in farmsList) { - final seed = twinIdWallets[f.twinId]!['tfchainSeed']; - final walletName = twinIdWallets[f.twinId]!['name']; + final seed = twinIdWallets[f.twinId]!.tfchainSecret; + final walletName = twinIdWallets[f.twinId]!.name; final nodes = await getNodesByFarmId(f.farmID); farms.add(Farm( name: f.name, walletAddress: f.stellarAddress, - tfchainWalletSecret: seed!, - walletName: walletName!, + tfchainWalletSecret: seed, + walletName: walletName, twinId: f.twinId, farmId: f.farmID, nodes: nodes.map((n) { @@ -101,7 +110,7 @@ class _FarmScreenState extends State { context: context, builder: (ctx) => NewFarm( onAddFarm: _addFarm, - wallets: twinIdWallets.values.toList(), + wallets: wallets, )); } diff --git a/app/lib/services/tfchain_service.dart b/app/lib/services/tfchain_service.dart index dbd48270..bad937ad 100644 --- a/app/lib/services/tfchain_service.dart +++ b/app/lib/services/tfchain_service.dart @@ -56,6 +56,12 @@ Future getTwinIdByClient(TFChain.Client client) async { return twinId ?? 0; } +Future getTwinId(String seed) async { + final chainUrl = Globals().chainUrl; + final client = TFChain.Client(chainUrl, seed, 'sr25519'); + return getTwinIdByClient(client); +} + Future>> getProposals() async { try { final chainUrl = Globals().chainUrl; @@ -97,16 +103,17 @@ _activateAccount(String tfchainSeed) async { final activationUrl = Globals().activationUrl; final chainUrl = Globals().chainUrl; final client = TFChain.Client(chainUrl, tfchainSeed, 'sr25519'); - client.connect(); + await client.connect(); - final activationUri = Uri.https(activationUrl); + final activationUri = Uri.parse(activationUrl); final activationResponse = await http .post(activationUri, body: {'substrateAccountID': client.address}); if (activationResponse.statusCode != 200) { - throw Exception('Failed to activate accont'); + throw Exception('Failed to activate account'); } + // TODO: Add T&C and relay urls in flagsmith const documentUrl = 'https://library.threefold.me/info/legal/'; - final documentUri = Uri.https(documentUrl); + final documentUri = Uri.parse(documentUrl); final response = await http.get(documentUri); final bytes = utf8.encode(response.body); final digest = md5.convert(bytes); @@ -115,7 +122,7 @@ _activateAccount(String tfchainSeed) async { await client.termsAndConditions .accept(documentLink: documentUrl, documentHash: hashString.codeUnits); - await client.twins.create(relay: '', pk: []); + await client.twins.create(relay: 'relay.dev.grid.tf', pk: []); } Future createFarm( @@ -126,7 +133,7 @@ Future createFarm( client.connect(); final twinId = await getTwinIdByClient(client); if (twinId == 0) { - _activateAccount(tfchainSeed); + await _activateAccount(tfchainSeed); } final farmId = await client.farms.create(name: name, publicIps: []); final farm = await client.farms.get(id: farmId!); diff --git a/app/lib/services/wallet_service.dart b/app/lib/services/wallet_service.dart index a5fc612e..51509396 100644 --- a/app/lib/services/wallet_service.dart +++ b/app/lib/services/wallet_service.dart @@ -178,7 +178,7 @@ Future>> getWalletsTwinIds() async { }); return twinWallets; }, null); - // TODO: return all wallets in case creating new farm + twinWallets.removeWhere((key, value) => key == 0); return twinWallets; } diff --git a/app/lib/widgets/add_farm.dart b/app/lib/widgets/add_farm.dart index 3661d6b0..a43d41f3 100644 --- a/app/lib/widgets/add_farm.dart +++ b/app/lib/widgets/add_farm.dart @@ -1,13 +1,14 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:threebotlogin/models/farm.dart'; +import 'package:threebotlogin/models/wallet.dart'; import 'package:threebotlogin/services/tfchain_service.dart'; import 'package:threebotlogin/widgets/custom_dialog.dart'; class NewFarm extends StatefulWidget { const NewFarm({super.key, required this.onAddFarm, required this.wallets}); final void Function(Farm addedFarm) onAddFarm; - final List> wallets; + final List wallets; @override State createState() { @@ -17,7 +18,7 @@ class NewFarm extends StatefulWidget { class _NewFarmState extends State { final _nameController = TextEditingController(); - Map _selectedWallet = {}; + Wallet? _selectedWallet; bool saveLoading = false; String? nameError; Future _showDialog( @@ -41,6 +42,7 @@ class _NewFarmState extends State { } Future _validateSubmittedData() async { + print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); final farmName = _nameController.text.trim(); nameError = null; saveLoading = true; @@ -55,7 +57,7 @@ class _NewFarmState extends State { //TODO: check if the farm name is used from gridproxy //TODO: show error for the drop down menu - if (_selectedWallet.isEmpty) { + if (_selectedWallet == null) { saveLoading = false; setState(() {}); return; @@ -63,13 +65,13 @@ class _NewFarmState extends State { Farm farm; print(_selectedWallet); try { - final f = await createFarm(farmName, _selectedWallet['tfchainSeed']!, - _selectedWallet['stellarAddress']!); + final f = await createFarm(farmName, _selectedWallet!.tfchainSecret, + _selectedWallet!.stellarAddress); farm = Farm( name: farmName, - walletAddress: _selectedWallet['stellarAddress']!, - tfchainWalletSecret: _selectedWallet['tfchainSeed']!, - walletName: _selectedWallet['name']!, + walletAddress: _selectedWallet!.stellarAddress, + tfchainWalletSecret: _selectedWallet!.tfchainSecret, + walletName: _selectedWallet!.name, twinId: f!.twinId, farmId: f.id, nodes: []); @@ -93,12 +95,12 @@ class _NewFarmState extends State { Navigator.pop(context); } - List>> _buildDropdownMenuEntries() { + List> _buildDropdownMenuEntries() { return widget.wallets.map((wallet) { - return DropdownMenuEntry>( + return DropdownMenuEntry( value: wallet, - label: wallet['name']!, - labelWidget: Text(wallet['name']!, + label: wallet.name, + labelWidget: Text(wallet.name, style: Theme.of(context).textTheme.bodyLarge!.copyWith( color: Theme.of(context).colorScheme.onBackground, )), @@ -183,7 +185,7 @@ class _NewFarmState extends State { ), ), dropdownMenuEntries: _buildDropdownMenuEntries(), - onSelected: (Map? value) { + onSelected: (Wallet? value) { if (value != null) { _selectedWallet = value; } From dbb75b92d77deb1f3751fc8c823237c031373b38 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Mon, 30 Sep 2024 09:39:02 +0300 Subject: [PATCH 49/56] Adjust tabs font size --- app/lib/screens/dao_screen.dart | 2 ++ app/lib/screens/wallets/contacts.dart | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/lib/screens/dao_screen.dart b/app/lib/screens/dao_screen.dart index 3f7127a3..629ce2e2 100644 --- a/app/lib/screens/dao_screen.dart +++ b/app/lib/screens/dao_screen.dart @@ -68,6 +68,8 @@ class _DaoPageState extends State { unselectedLabelColor: Theme.of(context).colorScheme.onBackground, dividerColor: Theme.of(context).scaffoldBackgroundColor, + labelStyle: Theme.of(context).textTheme.titleLarge, + unselectedLabelStyle: Theme.of(context).textTheme.titleMedium, tabs: const [ Tab(text: 'Active'), Tab(text: 'Executable'), diff --git a/app/lib/screens/wallets/contacts.dart b/app/lib/screens/wallets/contacts.dart index 7a3d03f4..63840da1 100644 --- a/app/lib/screens/wallets/contacts.dart +++ b/app/lib/screens/wallets/contacts.dart @@ -126,9 +126,12 @@ class _ContractsScreenState extends State { unselectedLabelColor: Theme.of(context).colorScheme.onBackground, dividerColor: Theme.of(context).scaffoldBackgroundColor, + labelStyle: Theme.of(context).textTheme.titleLarge, + unselectedLabelStyle: + Theme.of(context).textTheme.titleMedium, tabs: const [ Tab(text: 'My Wallets'), - Tab(text: 'Favourite'), + Tab(text: 'Favourites'), ], ), ), From c5d209acd7cf88590e5c3c5e12da053d9aa5455b Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Mon, 30 Sep 2024 11:36:40 +0300 Subject: [PATCH 50/56] Add error in case of inability to get proposals --- app/lib/screens/dao_screen.dart | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/app/lib/screens/dao_screen.dart b/app/lib/screens/dao_screen.dart index 629ce2e2..31babc9a 100644 --- a/app/lib/screens/dao_screen.dart +++ b/app/lib/screens/dao_screen.dart @@ -20,13 +20,31 @@ class _DaoPageState extends State { setState(() { loading = true; }); - // TODO: show error in case of failure - final proposals = await getProposals(); - activeList.addAll(proposals['activeProposals']!); - inactiveList.addAll(proposals['inactiveProposals']!); - setState(() { - loading = false; - }); + try { + final proposals = await getProposals(); + activeList.addAll(proposals['activeProposals']!); + inactiveList.addAll(proposals['inactiveProposals']!); + } catch (e) { + print('Failed to load proposals due to $e'); + if (context.mounted) { + final loadingProposalFailure = SnackBar( + content: Text( + 'Failed to load proposals', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Theme.of(context).colorScheme.errorContainer), + ), + duration: const Duration(seconds: 3), + ); + ScaffoldMessenger.of(context).clearSnackBars(); + ScaffoldMessenger.of(context).showSnackBar(loadingProposalFailure); + } + } finally { + setState(() { + loading = false; + }); + } } @override From b4ba59cc0a34f8968618e70b18055539b2466a0b Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Mon, 30 Sep 2024 11:39:24 +0300 Subject: [PATCH 51/56] Show message if there are no proposals --- app/lib/widgets/dao/proposals.dart | 91 +++++++++++++++--------------- 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/app/lib/widgets/dao/proposals.dart b/app/lib/widgets/dao/proposals.dart index cc9d0d92..b945511c 100644 --- a/app/lib/widgets/dao/proposals.dart +++ b/app/lib/widgets/dao/proposals.dart @@ -52,58 +52,61 @@ class _ProposalsWidgetState extends State { @override Widget build(BuildContext context) { + final daoCards = _buildDaoCardList(proposals, widget.active); return Scaffold( - appBar: PreferredSize( - preferredSize: const Size.fromHeight(60.0), - child: Padding( - padding: const EdgeInsets.all(10), - child: SizedBox( - height: 40, - child: SearchBar( - backgroundColor: MaterialStateProperty.all( - Theme.of(context).colorScheme.background), - onChanged: search, - trailing: [ - Icon( - Icons.search, - color: Theme.of(context).colorScheme.onBackground, - ) - ], - hintText: 'Search by proposal description', - hintStyle: MaterialStateProperty.all( - Theme.of(context).textTheme.bodyLarge!.copyWith( - color: Theme.of(context).colorScheme.onSecondaryContainer, - ), - ), - textStyle: MaterialStateProperty.all( - Theme.of(context).textTheme.bodyLarge!.copyWith( - color: Theme.of(context).colorScheme.onSecondaryContainer, - decorationThickness: 0, - ), - ), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15), + appBar: PreferredSize( + preferredSize: const Size.fromHeight(60.0), + child: Padding( + padding: const EdgeInsets.all(10), + child: SizedBox( + height: 40, + child: SearchBar( + backgroundColor: MaterialStateProperty.all( + Theme.of(context).colorScheme.background), + onChanged: search, + trailing: [ + Icon( + Icons.search, + color: Theme.of(context).colorScheme.onBackground, + ) + ], + hintText: 'Search by proposal description', + hintStyle: MaterialStateProperty.all( + Theme.of(context).textTheme.bodyLarge!.copyWith( + color: + Theme.of(context).colorScheme.onSecondaryContainer, + ), + ), + textStyle: MaterialStateProperty.all( + Theme.of(context).textTheme.bodyLarge!.copyWith( + color: + Theme.of(context).colorScheme.onSecondaryContainer, + decorationThickness: 0, + ), + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + ), ), ), ), ), ), - ), - body: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: _buildDaoCardList(proposals, widget.active) ?? - [ - Text( - 'No active proposal at the moment', + body: daoCards!.isNotEmpty + ? SingleChildScrollView( + child: + Column(mainAxisSize: MainAxisSize.min, children: daoCards), + ) + : Center( + child: Text( + widget.proposals!.isEmpty + ? 'No active proposal at the moment' + : 'No result was found', style: Theme.of(context).textTheme.bodyLarge!.copyWith( color: Theme.of(context).colorScheme.onBackground), - ) - ], - ), - ), - ); + ), + )); } } From 72c52c27585d1cad3505760d57029d3bf00f8e4b Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Mon, 30 Sep 2024 11:45:16 +0300 Subject: [PATCH 52/56] Adjust the proposal search --- app/lib/widgets/dao/proposals.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/lib/widgets/dao/proposals.dart b/app/lib/widgets/dao/proposals.dart index b945511c..e90c0cc2 100644 --- a/app/lib/widgets/dao/proposals.dart +++ b/app/lib/widgets/dao/proposals.dart @@ -4,7 +4,7 @@ import 'package:tfchain_client/models/dao.dart'; import 'dao_card.dart'; class ProposalsWidget extends StatefulWidget { - final List? proposals; + final List proposals; final bool active; const ProposalsWidget( {super.key, required this.proposals, this.active = false}); @@ -14,7 +14,7 @@ class ProposalsWidget extends StatefulWidget { } class _ProposalsWidgetState extends State { - List? proposals = []; + List proposals = []; @override void initState() { @@ -34,15 +34,15 @@ class _ProposalsWidgetState extends State { void search(String searchWord) { setState(() { - final String filterText = searchWord.toLowerCase(); + final String filterText = searchWord.toLowerCase().trim(); if (searchWord == '') { setState(() { proposals = widget.proposals; }); - } else if (widget.proposals != null) { + } else { setState(() { proposals = widget.proposals - ?.where((Proposal entry) => + .where((Proposal entry) => entry.description.toLowerCase().contains(filterText)) .toList(); }); @@ -100,7 +100,7 @@ class _ProposalsWidgetState extends State { ) : Center( child: Text( - widget.proposals!.isEmpty + widget.proposals.isEmpty ? 'No active proposal at the moment' : 'No result was found', style: Theme.of(context).textTheme.bodyLarge!.copyWith( @@ -110,8 +110,8 @@ class _ProposalsWidgetState extends State { } } -List? _buildDaoCardList(List? list, bool active) { - return list?.map((item) { +List? _buildDaoCardList(List list, bool active) { + return list.map((item) { return DaoCard( proposal: item, active: active, From 3f7db03b3140d31d1d08dd2ab5b6078745c7e71d Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Mon, 30 Sep 2024 11:54:23 +0300 Subject: [PATCH 53/56] Show error message when an error occur while loading farms --- app/lib/screens/farm_screen.dart | 77 ++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/app/lib/screens/farm_screen.dart b/app/lib/screens/farm_screen.dart index f4321ff8..367f9986 100644 --- a/app/lib/screens/farm_screen.dart +++ b/app/lib/screens/farm_screen.dart @@ -31,37 +31,56 @@ class _FarmScreenState extends State { setState(() { loading = true; }); - wallets = await listWallets(); - final Map twinIdWallets = {}; - for (final w in wallets) { - final twinId = await getTwinId(w.tfchainSecret); - if (twinId != 0) { - twinIdWallets[twinId] = w; + try { + wallets = await listWallets(); + final Map twinIdWallets = {}; + for (final w in wallets) { + final twinId = await getTwinId(w.tfchainSecret); + if (twinId != 0) { + twinIdWallets[twinId] = w; + } } + final farmsList = await getFarmsByTwinIds(twinIdWallets.keys.toList()); + for (final f in farmsList) { + final seed = twinIdWallets[f.twinId]!.tfchainSecret; + final walletName = twinIdWallets[f.twinId]!.name; + final nodes = await getNodesByFarmId(f.farmID); + farms.add(Farm( + name: f.name, + walletAddress: f.stellarAddress, + tfchainWalletSecret: seed, + walletName: walletName, + twinId: f.twinId, + farmId: f.farmID, + nodes: nodes.map((n) { + return Node( + nodeId: n.nodeId, + status: NodeStatus.values.firstWhere((e) => + e.toString().toLowerCase() == 'nodestatus.${n.status}'), + ); + }).toList())); + } + } catch (e) { + print('Failed to get farms due to $e'); + if (context.mounted) { + final loadingFarmsFailure = SnackBar( + content: Text( + 'Failed to load farms', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Theme.of(context).colorScheme.errorContainer), + ), + duration: const Duration(seconds: 3), + ); + ScaffoldMessenger.of(context).clearSnackBars(); + ScaffoldMessenger.of(context).showSnackBar(loadingFarmsFailure); + } + } finally { + setState(() { + loading = false; + }); } - final farmsList = await getFarmsByTwinIds(twinIdWallets.keys.toList()); - for (final f in farmsList) { - final seed = twinIdWallets[f.twinId]!.tfchainSecret; - final walletName = twinIdWallets[f.twinId]!.name; - final nodes = await getNodesByFarmId(f.farmID); - farms.add(Farm( - name: f.name, - walletAddress: f.stellarAddress, - tfchainWalletSecret: seed, - walletName: walletName, - twinId: f.twinId, - farmId: f.farmID, - nodes: nodes.map((n) { - return Node( - nodeId: n.nodeId, - status: NodeStatus.values.firstWhere((e) => - e.toString().toLowerCase() == 'nodestatus.${n.status}'), - ); - }).toList())); - } - setState(() { - loading = false; - }); } @override From a4fb10fc76390f1a2f6870caf518c0ce976f6ff0 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Mon, 30 Sep 2024 12:00:48 +0300 Subject: [PATCH 54/56] Show message if there are no farms --- app/lib/screens/farm_screen.dart | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/lib/screens/farm_screen.dart b/app/lib/screens/farm_screen.dart index 367f9986..b9f71f5d 100644 --- a/app/lib/screens/farm_screen.dart +++ b/app/lib/screens/farm_screen.dart @@ -85,7 +85,6 @@ class _FarmScreenState extends State { @override Widget build(BuildContext context) { - // TODO: handle empty farms Widget mainWidget; if (loading) { mainWidget = Center( @@ -102,6 +101,16 @@ class _FarmScreenState extends State { ), ], )); + } else if (farms.isEmpty) { + mainWidget = Center( + child: Text( + 'No farms yet.', + style: Theme.of(context) + .textTheme + .bodyLarge! + .copyWith(color: Theme.of(context).colorScheme.onBackground), + ), + ); } else { mainWidget = ListView( children: [for (final farm in farms) FarmItemWidget(farm: farm)]); From 675bfb2d48b976c89ef53a63408cb90981630a38 Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Mon, 30 Sep 2024 17:50:03 +0300 Subject: [PATCH 55/56] Add validation to send screen --- app/lib/screens/wallets/send.dart | 80 +++++++++++++++++++++++++-- app/lib/services/stellar_service.dart | 10 ++++ app/pubspec.lock | 8 +++ app/pubspec.yaml | 1 + 4 files changed, 95 insertions(+), 4 deletions(-) diff --git a/app/lib/screens/wallets/send.dart b/app/lib/screens/wallets/send.dart index b2504ae1..7ad01e7e 100644 --- a/app/lib/screens/wallets/send.dart +++ b/app/lib/screens/wallets/send.dart @@ -4,7 +4,9 @@ import 'package:qr_code_scanner/qr_code_scanner.dart'; import 'package:threebotlogin/models/wallet.dart'; import 'package:threebotlogin/screens/scan_screen.dart'; import 'package:threebotlogin/screens/wallets/contacts.dart'; +import 'package:threebotlogin/services/stellar_service.dart'; import 'package:threebotlogin/widgets/wallets/send_confirmation.dart'; +import 'package:validators/validators.dart'; class WalletSendScreen extends StatefulWidget { const WalletSendScreen( @@ -22,7 +24,8 @@ class _WalletSendScreenState extends State { final amountController = TextEditingController(); final memoController = TextEditingController(); ChainType chainType = ChainType.Stellar; - // TODO: Add validation on all fields + String? toAddressError; + String? amountError; @override void initState() { @@ -39,6 +42,72 @@ class _WalletSendScreenState extends State { super.dispose(); } + bool _validate() { + final toAddress = toController.text.trim(); + final amount = amountController.text.trim(); + amountError = null; + toAddressError = null; + if (toAddress.isEmpty) { + toAddressError = "Address can't be empty"; + setState(() {}); + return false; + } + if (amount.isEmpty) { + amountError = "Amount can't be empty"; + setState(() {}); + return false; + } + if (!isFloat(amount)) { + amountError = 'Amount should have numeric values only'; + setState(() {}); + return false; + } + if (chainType == ChainType.TFChain) { + if (toAddress.length != 48) { + toAddressError = 'Address length should be 48 characters'; + setState(() {}); + return false; + } + + if (double.parse(amount) < 0.01) { + amountError = "Amount can't be less than 0.01"; + setState(() {}); + return false; + } + if (double.parse(widget.wallet.tfchainBalance) - + double.parse(amount) - + 0.01 < + 0) { + amountError = "Amount shouldn't be more than the wallet balance"; + setState(() {}); + return false; + } + } + if (chainType == ChainType.Stellar) { + if (!isValidStellarAddress(toAddress)) { + toAddressError = 'Invaild Stellar address'; + setState(() {}); + return false; + } + + if (double.parse(amount) < 0.1) { + amountError = "Amount can't be less than 0.1"; + setState(() {}); + return false; + } + if (double.parse(widget.wallet.stellarBalance) - + double.parse(amount) - + 0.1 < + 0) { + amountError = "Amount shouldn't be more than the wallet balance"; + setState(() {}); + return false; + } + } + setState(() {}); + return true; + } + void _selectToAddress(String address) { toController.text = address; setState(() {}); @@ -165,6 +234,7 @@ class _WalletSendScreenState extends State { controller: toController, decoration: InputDecoration( labelText: 'To', + errorText: toAddressError, suffixIcon: IconButton( onPressed: () { Navigator.of(context).push(MaterialPageRoute( @@ -189,7 +259,8 @@ class _WalletSendScreenState extends State { labelText: 'Amount (Balance: ${chainType == ChainType.Stellar ? widget.wallet.stellarBalance : widget.wallet.tfchainBalance})', hintText: '100', - suffixText: 'TFT')), + suffixText: 'TFT', + errorText: amountError)), subtitle: Text( 'Max Fee: ${chainType == ChainType.Stellar ? 0.1 : 0.01} TFT'), ), @@ -210,8 +281,9 @@ class _WalletSendScreenState extends State { padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), child: ElevatedButton( onPressed: () async { - // TODO: Trigger validation here - await _send_confirmation(); + if (_validate()) { + await _send_confirmation(); + } }, style: ElevatedButton.styleFrom(), child: SizedBox( diff --git a/app/lib/services/stellar_service.dart b/app/lib/services/stellar_service.dart index 547bdd74..903267a0 100644 --- a/app/lib/services/stellar_service.dart +++ b/app/lib/services/stellar_service.dart @@ -12,6 +12,16 @@ bool isValidStellarSecret(String seed) { return false; } +bool isValidStellarAddress(String address) { + try { + StrKey.decodeStellarAccountId(address); + return true; + } catch (e) { + print('Address is invalid. $e'); + } + return false; +} + Future getBalanceByClient(Client client) async { try { final stellarBalances = await client.getBalance(); diff --git a/app/pubspec.lock b/app/pubspec.lock index 9394d61f..41a4285f 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -1468,6 +1468,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.5.0" + validators: + dependency: "direct main" + description: + name: validators + sha256: "884515951f831a9c669a41ed6c4d3c61c2a0e8ec6bca761a4480b28e99cecf5d" + url: "https://pub.dev" + source: hosted + version: "3.0.0" vector_graphics: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 62d1d707..df049d7c 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -61,6 +61,7 @@ dependencies: build_runner: ^2.4.9 qr_flutter: ^4.1.0 screen_brightness: ^1.0.1 + validators: ^3.0.0 dev_dependencies: flutter_test: From 8b7c18cf2bfd93727674056696fce7cc60fbb2ce Mon Sep 17 00:00:00 2001 From: ahmedhanafy725 Date: Mon, 30 Sep 2024 17:50:31 +0300 Subject: [PATCH 56/56] Add validation to receive screen --- app/lib/screens/wallets/receive.dart | 64 +++++++++++++++++++--------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/app/lib/screens/wallets/receive.dart b/app/lib/screens/wallets/receive.dart index 837d2202..384f919f 100644 --- a/app/lib/screens/wallets/receive.dart +++ b/app/lib/screens/wallets/receive.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:threebotlogin/models/wallet.dart'; import 'package:threebotlogin/screens/qr_code_screen.dart'; - +import 'package:validators/validators.dart'; class WalletReceiveScreen extends StatefulWidget { const WalletReceiveScreen({super.key, required this.wallet}); @@ -16,7 +16,7 @@ class _WalletReceiveScreenState extends State { final amountController = TextEditingController(); final memoController = TextEditingController(); ChainType chainType = ChainType.Stellar; - // TODO: Add validation on all fields + String? amountError; @override void initState() { @@ -32,6 +32,41 @@ class _WalletReceiveScreenState extends State { super.dispose(); } + bool _validate() { + final amount = amountController.text.trim(); + amountError = null; + + if (amount.isEmpty) { + amountError = "Amount can't be empty"; + setState(() {}); + return false; + } + + if (!isFloat(amount)) { + amountError = 'Amount should have numeric values only'; + setState(() {}); + return false; + } + + return true; + } + + _showQRCode() { + final quaryParams = {'amount': amountController.text.trim()}; + if (chainType == ChainType.Stellar) { + quaryParams['message'] = memoController.text.trim(); + } + final uri = Uri( + scheme: 'TFT', + path: toController.text.trim(), + queryParameters: quaryParams); + final codeMessage = uri.toString(); + showDialog( + context: context, + builder: (context) => GenerateQRCodeScreen(message: codeMessage), + ); + } + @override Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; @@ -125,8 +160,11 @@ class _WalletReceiveScreenState extends State { ), keyboardType: TextInputType.number, controller: amountController, - decoration: const InputDecoration( - suffixText: 'TFT', labelText: 'Amount', hintText: '100')), + decoration: InputDecoration( + suffixText: 'TFT', + labelText: 'Amount', + hintText: '100', + errorText: amountError)), ), const SizedBox(height: 10), if (chainType == ChainType.Stellar) @@ -145,21 +183,9 @@ class _WalletReceiveScreenState extends State { padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), child: ElevatedButton( onPressed: () { - //TODO: Validate inputs first - final quaryParams = {'amount': amountController.text}; - if (chainType == ChainType.Stellar) { - quaryParams['message'] = memoController.text; - } - final uri = Uri( - scheme: 'TFT', - path: toController.text, - queryParameters: quaryParams); - final codeMessage = uri.toString(); - showDialog( - context: context, - builder: (context) => - GenerateQRCodeScreen(message: codeMessage), - ); + final valid = _validate(); + print(valid); + if (valid) _showQRCode(); }, style: ElevatedButton.styleFrom(), child: SizedBox(