From b5ba9385e8f7861d8baf6a9dd5a7059250963a2e Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Thu, 24 Apr 2025 23:12:56 +0100 Subject: [PATCH] Handle Network Connection Errors (#2213) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(moralis-nft-errors): guard against concurrent NFT fetches and adjusts the message being presented to the user. Previously multiple calls to get NFTs for the currently opened wallet could overlap and queue the error bottom sheet multiple times. This change: - Registers the NFTViewModel as a lazySingleton so its isLoading flag persists. - Adds an early return in the call to fetch the wallet NFTs when isLoading is already true. - Cleans up the error message being displayed to the user when there is an error. * feat(moralis-nft-error): localize error message in NFTViewModel * feat(nft/wc-bottom-sheet): Revamped the flow, service, theme, and UI for smoother UX Revamps bottom‑sheet handling end‑to‑end to deliver a much more smoother experience. This change: - Refactors the BottomSheetService queueing logic to prevent races and ensure strict sequencing - Update theme extensions and styling for the bottom‑sheet components - Adds the option to either auto dismiss or allow user manually dismiss the bottomsheet * fix: Context clash when entering the wallets on airplane mode. The flushbar clashes with the bottomSheet and results in it blocking entry to the selected wallet. This change: - Moves the logic for fetching nft to the listing page, no need fetching if the user does not route to the page, - Routes to balance page when entering from wallet list page - Adds a fade transition when entering the dashboard - Reverts nftViewModel registeration to be a factory * fix: Revert animation for now, prior to when the UX overhaul for the app is done * fix: Remove duplicate registration --- lib/di.dart | 2 +- lib/entities/main_actions.dart | 13 +++++--- .../dashboard/pages/nft_listing_page.dart | 24 +++++++++++--- .../bottom_sheet_message_display_widget.dart | 32 +++++++++++++++---- lib/src/widgets/setting_actions.dart | 6 +++- lib/view_model/dashboard/nft_view_model.dart | 13 +++----- 6 files changed, 64 insertions(+), 26 deletions(-) diff --git a/lib/di.dart b/lib/di.dart index 05f2c3f77..60a0f240d 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -651,6 +651,7 @@ Future setup({ return walletKitService; }); + getIt.registerFactory(() => NFTViewModel(appStore, getIt.get())); getIt.registerFactory(() => BalancePage( nftViewModel: getIt.get(), dashboardViewModel: getIt.get(), @@ -1451,7 +1452,6 @@ Future setup({ () => WalletConnectConnectionsView(walletKitService: getIt.get()), ); - getIt.registerFactory(() => NFTViewModel(appStore, getIt.get())); getIt.registerFactory(() => TorPage(getIt.get())); getIt.registerFactory(() => SignViewModel(getIt.get().wallet!)); diff --git a/lib/entities/main_actions.dart b/lib/entities/main_actions.dart index 2e633bce8..0cb2e4058 100644 --- a/lib/entities/main_actions.dart +++ b/lib/entities/main_actions.dart @@ -9,8 +9,7 @@ class MainActions { final bool Function(DashboardViewModel viewModel)? isEnabled; final bool Function(DashboardViewModel viewModel)? canShow; - final Future Function( - BuildContext context, DashboardViewModel viewModel) onTap; + final Future Function(BuildContext context, DashboardViewModel viewModel) onTap; MainActions._({ required this.name, @@ -32,7 +31,12 @@ class MainActions { name: (context) => S.of(context).wallets, image: 'assets/images/wallet_new.png', onTap: (BuildContext context, DashboardViewModel viewModel) async { - Navigator.pushNamed(context, Routes.walletList); + Navigator.pushNamed( + context, + Routes.walletList, + arguments: (BuildContext context) => + Navigator.of(context).pushNamedAndRemoveUntil(Routes.dashboard, (route) => false), + ); }, ); @@ -65,7 +69,6 @@ class MainActions { }, ); - static MainActions tradeAction = MainActions._( name: (context) => S.of(context).exchange, image: 'assets/images/buy_sell.png', @@ -76,4 +79,4 @@ class MainActions { await Navigator.of(context).pushNamed(Routes.buySellPage, arguments: false); }, ); -} \ No newline at end of file +} diff --git a/lib/src/screens/dashboard/pages/nft_listing_page.dart b/lib/src/screens/dashboard/pages/nft_listing_page.dart index 8da63fce6..67af337e6 100644 --- a/lib/src/screens/dashboard/pages/nft_listing_page.dart +++ b/lib/src/screens/dashboard/pages/nft_listing_page.dart @@ -11,11 +11,27 @@ import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart'; import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; import 'package:cw_core/wallet_type.dart'; -class NFTListingPage extends StatelessWidget { +class NFTListingPage extends StatefulWidget { final NFTViewModel nftViewModel; const NFTListingPage({super.key, required this.nftViewModel}); + @override + State createState() => _NFTListingPageState(); +} + +class _NFTListingPageState extends State { + @override + void initState() { + super.initState(); + + fetchNFTsForWallet(); + } + + Future fetchNFTsForWallet() async { + await widget.nftViewModel.getNFTAssetByWallet(); + } + @override Widget build(BuildContext context) { final dashboardTheme = Theme.of(context).extension()!; @@ -36,11 +52,11 @@ class NFTListingPage extends StatelessWidget { onPressed: () => Navigator.pushNamed( context, Routes.importNFTPage, - arguments: nftViewModel, + arguments: widget.nftViewModel, ), ), ), - if (nftViewModel.isLoading) + if (widget.nftViewModel.isLoading) Expanded( child: Center( child: CircularProgressIndicator( @@ -53,7 +69,7 @@ class NFTListingPage extends StatelessWidget { ) else Expanded( - child: NFTListWidget(nftViewModel: nftViewModel), + child: NFTListWidget(nftViewModel: widget.nftViewModel), ), ], ); diff --git a/lib/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart b/lib/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart index 1f5dd6768..9c4016333 100644 --- a/lib/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart +++ b/lib/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart @@ -14,13 +14,30 @@ class BottomSheetMessageDisplayWidget extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - isError ? S.current.error : S.current.successful, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.normal, - color: Theme.of(context).extension()!.titleColor, - ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + isError ? S.current.error : S.current.successful, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.titleColor, + ), + ), + IconButton( + color: Theme.of(context).appBarTheme.titleTextStyle!.color!, + padding: const EdgeInsets.all(0.0), + visualDensity: VisualDensity.compact, + onPressed: () { + if (Navigator.canPop(context)) { + Navigator.pop(context); + } + }, + icon: const Icon(Icons.close_sharp), + ), + ], ), SizedBox(height: 8), Row( @@ -37,6 +54,7 @@ class BottomSheetMessageDisplayWidget extends StatelessWidget { ), ], ), + SizedBox(height: 16), ], ); } diff --git a/lib/src/widgets/setting_actions.dart b/lib/src/widgets/setting_actions.dart index d383129cc..e170d61e6 100644 --- a/lib/src/widgets/setting_actions.dart +++ b/lib/src/widgets/setting_actions.dart @@ -73,7 +73,11 @@ class SettingActions { image: 'assets/images/wallet_menu.png', onTap: (BuildContext context) { Navigator.pop(context); - Navigator.of(context).pushNamed(Routes.walletList); + Navigator.of(context).pushNamed( + Routes.walletList, + arguments: (BuildContext context) => + Navigator.of(context).pushNamedAndRemoveUntil(Routes.dashboard, (route) => false), + ); }, ); diff --git a/lib/view_model/dashboard/nft_view_model.dart b/lib/view_model/dashboard/nft_view_model.dart index 766e588e7..7da63d399 100644 --- a/lib/view_model/dashboard/nft_view_model.dart +++ b/lib/view_model/dashboard/nft_view_model.dart @@ -23,11 +23,7 @@ abstract class NFTViewModelBase with Store { : isLoading = false, isImportNFTLoading = false, nftAssetByWalletModels = ObservableList(), - solanaNftAssetModels = ObservableList() { - getNFTAssetByWallet(); - - reaction((_) => appStore.wallet, (_) => getNFTAssetByWallet()); - } + solanaNftAssetModels = ObservableList(); final AppStore appStore; final BottomSheetService bottomSheetService; @@ -80,6 +76,8 @@ abstract class NFTViewModelBase with Store { } try { + if (isLoading) return; + isLoading = true; final response = await http.get( @@ -114,10 +112,7 @@ abstract class NFTViewModelBase with Store { nftAssetByWalletModels.addAll(result); } - - isLoading = false; } catch (e) { - isLoading = false; log(e.toString()); bottomSheetService.queueBottomSheet( isModalDismissible: true, @@ -125,6 +120,8 @@ abstract class NFTViewModelBase with Store { message: S.current.moralis_nft_error, ), ); + } finally { + isLoading = false; } }