Handle Network Connection Errors (#2213)

* 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
This commit is contained in:
David Adegoke 2025-04-24 23:12:56 +01:00 committed by GitHub
parent 02e74b5997
commit b5ba9385e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 64 additions and 26 deletions

View file

@ -651,6 +651,7 @@ Future<void> setup({
return walletKitService;
});
getIt.registerFactory(() => NFTViewModel(appStore, getIt.get<BottomSheetService>()));
getIt.registerFactory(() => BalancePage(
nftViewModel: getIt.get<NFTViewModel>(),
dashboardViewModel: getIt.get<DashboardViewModel>(),
@ -1451,7 +1452,6 @@ Future<void> setup({
() => WalletConnectConnectionsView(walletKitService: getIt.get<WalletKitService>()),
);
getIt.registerFactory(() => NFTViewModel(appStore, getIt.get<BottomSheetService>()));
getIt.registerFactory<TorPage>(() => TorPage(getIt.get<AppStore>()));
getIt.registerFactory(() => SignViewModel(getIt.get<AppStore>().wallet!));

View file

@ -9,8 +9,7 @@ class MainActions {
final bool Function(DashboardViewModel viewModel)? isEnabled;
final bool Function(DashboardViewModel viewModel)? canShow;
final Future<void> Function(
BuildContext context, DashboardViewModel viewModel) onTap;
final Future<void> 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);
},
);
}
}

View file

@ -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<NFTListingPage> createState() => _NFTListingPageState();
}
class _NFTListingPageState extends State<NFTListingPage> {
@override
void initState() {
super.initState();
fetchNFTsForWallet();
}
Future<void> fetchNFTsForWallet() async {
await widget.nftViewModel.getNFTAssetByWallet();
}
@override
Widget build(BuildContext context) {
final dashboardTheme = Theme.of(context).extension<DashboardPageTheme>()!;
@ -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),
),
],
);

View file

@ -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<CakeTextTheme>()!.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<CakeTextTheme>()!.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),
],
);
}

View file

@ -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),
);
},
);

View file

@ -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;
}
}