CW-400 investigate cupertino nav bar null exception (#943)

* Fix null error on back navigation after pushReplacementNamed

* Fix null error on back navigation after pushReplacementNamed

* Close all visible keyboard for page navigation context

* Fix issue with market place navigation

* Remove focus before back navigation

* Fix background color

* Fix background color

* Fix background color
This commit is contained in:
Godwin Asuquo 2023-06-08 02:16:52 +03:00 committed by GitHub
parent 3b073d9751
commit e8446f0c98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 183 additions and 137 deletions

View file

@ -39,6 +39,7 @@ import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
import 'package:cake_wallet/view_model/anonpay_details_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_buy_card_view_model.dart';
@ -873,6 +874,8 @@ Future setup({
getIt.registerFactory(() => IoniaGiftCardsListViewModel(ioniaService: getIt.get<IoniaService>()));
getIt.registerFactory(()=> MarketPlaceViewModel(getIt.get<IoniaService>()));
getIt.registerFactory(() => IoniaAuthViewModel(ioniaService: getIt.get<IoniaService>()));
getIt.registerFactoryParam<IoniaMerchPurchaseViewModel, double, IoniaMerchant>(
@ -902,7 +905,7 @@ Future setup({
return IoniaVerifyIoniaOtp(getIt.get<IoniaAuthViewModel>(), email, isSignIn);
});
getIt.registerFactory(() => IoniaWelcomePage(getIt.get<IoniaGiftCardsListViewModel>()));
getIt.registerFactory(() => IoniaWelcomePage());
getIt.registerFactoryParam<IoniaBuyGiftCardPage, List, void>((List args, _) {
final merchant = args.first as IoniaMerchant;

View file

@ -175,7 +175,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
fullscreenDialog: true);
} else if (isSingleCoin) {
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<WalletRestorePage>(
param1: availableWalletTypes.first
));
@ -196,7 +195,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.restoreWallet:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<WalletRestorePage>(
param1: settings.arguments as WalletType));
@ -206,6 +204,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.dashboard:
return CupertinoPageRoute<void>(
settings: settings,
builder: (_) => getIt.get<DashboardPage>());
case Routes.send:
@ -468,7 +467,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaCreateAccountPage>());
case Routes.ioniaManageCardsPage:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaManageCardsPage>());
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<IoniaManageCardsPage>());
case Routes.ioniaBuyGiftCardPage:
final args = settings.arguments as List;
@ -536,7 +536,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.anonPayInvoicePage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<AnonPayInvoicePage>(param1: args));
case Routes.anonPayReceivePage:

View file

@ -5,7 +5,7 @@ import 'package:cake_wallet/entities/main_actions.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/market_place_page.dart';
import 'package:cake_wallet/utils/version_comparator.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/yat_emoji_id.dart';
@ -27,8 +27,6 @@ import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:cake_wallet/src/screens/release_notes/release_notes_screen.dart';
class DashboardPage extends StatelessWidget {
@ -251,7 +249,12 @@ class _DashboardPageView extends BasePage {
if (dashboardViewModel.shouldShowMarketPlaceInDashboard) {
pages.add(Semantics(
label: 'Marketplace Page',
child: MarketPlacePage(dashboardViewModel: dashboardViewModel)));
child: MarketPlacePage(
dashboardViewModel: dashboardViewModel,
marketPlaceViewModel: getIt.get<MarketPlaceViewModel>(),
),
),
);
}
pages.add(Semantics(label: 'Balance Page', child: balancePage));
pages.add(Semantics(

View file

@ -1,7 +1,9 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/main_actions.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_action_button.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/market_place_page.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -70,7 +72,10 @@ class DesktopDashboardActions extends StatelessWidget {
],
),
Expanded(
child: MarketPlacePage(dashboardViewModel: dashboardViewModel),
child: MarketPlacePage(
dashboardViewModel: dashboardViewModel,
marketPlaceViewModel: getIt.get<MarketPlaceViewModel>(),
),
),
],
);

View file

@ -273,7 +273,7 @@ class AddressPage extends BasePage {
reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) {
switch (option) {
case ReceivePageOption.anonPayInvoice:
Navigator.pushReplacementNamed(
Navigator.pushNamed(
context,
Routes.anonPayInvoicePage,
arguments: [addressListViewModel.address.address, option],
@ -285,7 +285,7 @@ class AddressPage extends BasePage {
final onionUrl = sharedPreferences.getString(PreferencesKey.onionDonationLink);
if (clearnetUrl != null && onionUrl != null) {
Navigator.pushReplacementNamed(
Navigator.pushNamed(
context,
Routes.anonPayReceivePage,
arguments: AnonpayDonationLinkInfo(
@ -295,7 +295,7 @@ class AddressPage extends BasePage {
),
);
} else {
Navigator.pushReplacementNamed(
Navigator.pushNamed(
context,
Routes.anonPayInvoicePage,
arguments: [addressListViewModel.address.address, option],

View file

@ -3,16 +3,20 @@ import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/market_place_item.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:url_launcher/url_launcher.dart';
class MarketPlacePage extends StatelessWidget {
MarketPlacePage({required this.dashboardViewModel});
MarketPlacePage({
required this.dashboardViewModel,
required this.marketPlaceViewModel,
});
final DashboardViewModel dashboardViewModel;
final MarketPlaceViewModel marketPlaceViewModel;
final _scrollController = ScrollController();
@override
@ -48,7 +52,7 @@ class MarketPlacePage extends StatelessWidget {
children: <Widget>[
SizedBox(height: 20),
MarketPlaceItem(
onTap: () =>_navigatorToGiftCardsPage(context),
onTap: () => _navigatorToGiftCardsPage(context),
title: S.of(context).cake_pay_title,
subTitle: S.of(context).cake_pay_subtitle,
),
@ -70,12 +74,13 @@ class MarketPlacePage extends StatelessWidget {
),
);
}
void _navigatorToGiftCardsPage(BuildContext context) {
final walletType = dashboardViewModel.type;
switch (walletType) {
case WalletType.haven:
showPopUp<void>(
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
@ -85,9 +90,14 @@ class MarketPlacePage extends StatelessWidget {
buttonAction: () => Navigator.of(context).pop());
});
break;
default:
Navigator.of(context).pushNamed(Routes.ioniaWelcomePage);
default:
marketPlaceViewModel.isIoniaUserAuthenticated().then((value) {
if (value) {
Navigator.pushNamed(context, Routes.ioniaManageCardsPage);
return;
}
Navigator.of(context).pushNamed(Routes.ioniaWelcomePage);
});
}
}
}

View file

@ -3,14 +3,11 @@ import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:mobx/mobx.dart';
class IoniaWelcomePage extends BasePage {
IoniaWelcomePage(this._cardsListViewModel);
IoniaWelcomePage();
@override
Widget middle(BuildContext context) {
@ -25,15 +22,8 @@ class IoniaWelcomePage extends BasePage {
);
}
final IoniaGiftCardsListViewModel _cardsListViewModel;
@override
Widget body(BuildContext context) {
reaction((_) => _cardsListViewModel.isLoggedIn, (bool state) {
if (state) {
Navigator.pushReplacementNamed(context, Routes.ioniaManageCardsPage);
}
});
return Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
@ -41,7 +31,7 @@ class IoniaWelcomePage extends BasePage {
children: [
Column(
children: [
SizedBox(height: 100),
SizedBox(height: 90),
Text(
S.of(context).about_cake_pay,
style: TextStyle(

View file

@ -17,7 +17,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class IoniaManageCardsPage extends BasePage {
IoniaManageCardsPage(this._cardsListViewModel) {
IoniaManageCardsPage(this._cardsListViewModel): searchFocusNode = FocusNode() {
_searchController.addListener(() {
if (_searchController.text != _cardsListViewModel.searchString) {
_searchDebounce.run(() {
@ -29,6 +29,7 @@ class IoniaManageCardsPage extends BasePage {
_cardsListViewModel.getMerchants();
}
final FocusNode searchFocusNode;
final IoniaGiftCardsListViewModel _cardsListViewModel;
final _searchDebounce = Debounce(Duration(milliseconds: 500));
@ -86,7 +87,14 @@ class IoniaManageCardsPage extends BasePage {
//highlightColor: Colors.transparent,
//splashColor: Colors.transparent,
//padding: EdgeInsets.all(0),
onPressed: () => Navigator.pop(context),
onPressed: (){
if (searchFocusNode.hasFocus) {
searchFocusNode.unfocus();
return;
}
Navigator.of(context).pop();
},
child: _backButton),
),
);
@ -149,6 +157,7 @@ class IoniaManageCardsPage extends BasePage {
Expanded(
child: _SearchWidget(
controller: _searchController,
focusNode: searchFocusNode,
)),
SizedBox(width: 10),
filterButton
@ -266,9 +275,10 @@ class _SearchWidget extends StatelessWidget {
const _SearchWidget({
Key? key,
required this.controller,
required this.focusNode,
}) : super(key: key);
final TextEditingController controller;
final FocusNode focusNode;
@override
Widget build(BuildContext context) {
final searchIcon = Padding(
@ -280,6 +290,7 @@ class _SearchWidget extends StatelessWidget {
);
return TextField(
focusNode: focusNode,
style: TextStyle(color: Theme.of(context).accentTextTheme!.displayMedium!.backgroundColor!),
controller: controller,
decoration: InputDecoration(

View file

@ -27,8 +27,7 @@ class AnonPayInvoicePage extends BasePage {
AnonPayInvoicePage(
this.anonInvoicePageViewModel,
this.receiveOptionViewModel,
) : _amountFocusNode = FocusNode() {
}
) : _amountFocusNode = FocusNode() {}
final _nameController = TextEditingController();
final _emailController = TextEditingController();
@ -53,6 +52,11 @@ class AnonPayInvoicePage extends BasePage {
@override
AppBarStyle get appBarStyle => AppBarStyle.transparent;
@override
void onClose(BuildContext context) {
Navigator.popUntil(context, ModalRoute.withName(Routes.dashboard));
}
@override
Widget middle(BuildContext context) =>
PresentReceiveOptionPicker(receiveOptionViewModel: receiveOptionViewModel);
@ -65,114 +69,125 @@ class AnonPayInvoicePage extends BasePage {
anonInvoicePageViewModel.reset();
});
Future<bool> _onNavigateBack(BuildContext context) async {
onClose(context);
return false;
}
@override
Widget body(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) => _setReactions(context));
return KeyboardActions(
disableScroll: true,
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context)
return WillPopScope(
onWillPop: () => _onNavigateBack(context),
child: KeyboardActions(
disableScroll: true,
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context)
.accentTextTheme!
.bodyLarge!
.backgroundColor!,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _amountFocusNode,
toolbarButtons: [(_) => KeyboardDoneButton()],
),
]),
child: Container(
color: Theme.of(context).colorScheme.background,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: Container(
decoration: DeviceInfo.instance.isMobile ? BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
gradient: LinearGradient(
colors: [
Theme.of(context).primaryTextTheme!.titleSmall!.color!,
Theme.of(context).primaryTextTheme!.titleSmall!.decorationColor!,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _amountFocusNode,
toolbarButtons: [(_) => KeyboardDoneButton()],
),
) : null,
child: Observer(builder: (_) {
return Padding(
padding: EdgeInsets.fromLTRB(24, 120, 24, 0),
child: AnonInvoiceForm(
nameController: _nameController,
descriptionController: _descriptionController,
amountController: _amountController,
emailController: _emailController,
depositAmountFocus: _amountFocusNode,
formKey: _formKey,
isInvoice: receiveOptionViewModel.selectedReceiveOption ==
ReceivePageOption.anonPayInvoice,
anonInvoicePageViewModel: anonInvoicePageViewModel,
),
);
}),
),
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Observer(builder: (_) {
final isInvoice =
receiveOptionViewModel.selectedReceiveOption == ReceivePageOption.anonPayInvoice;
return Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 15),
child: Center(
child: Text(
isInvoice
? S.of(context).anonpay_description("an invoice", "pay")
: S.of(context).anonpay_description("a donation link", "donate"),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context)
]),
child: Container(
color: Theme.of(context).colorScheme.background,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: Container(
decoration: DeviceInfo.instance.isMobile
? BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
gradient: LinearGradient(
colors: [
Theme.of(context).primaryTextTheme!.titleSmall!.color!,
Theme.of(context).primaryTextTheme!.titleSmall!.decorationColor!,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
)
: null,
child: Observer(builder: (_) {
return Padding(
padding: EdgeInsets.fromLTRB(24, 120, 24, 0),
child: AnonInvoiceForm(
nameController: _nameController,
descriptionController: _descriptionController,
amountController: _amountController,
emailController: _emailController,
depositAmountFocus: _amountFocusNode,
formKey: _formKey,
isInvoice: receiveOptionViewModel.selectedReceiveOption ==
ReceivePageOption.anonPayInvoice,
anonInvoicePageViewModel: anonInvoicePageViewModel,
),
);
}),
),
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Observer(builder: (_) {
final isInvoice =
receiveOptionViewModel.selectedReceiveOption == ReceivePageOption.anonPayInvoice;
return Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 15),
child: Center(
child: Text(
isInvoice
? S.of(context).anonpay_description("an invoice", "pay")
: S.of(context).anonpay_description("a donation link", "donate"),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context)
.primaryTextTheme!
.displayLarge!
.decorationColor!,
fontWeight: FontWeight.w500,
fontSize: 12),
),
),
),
),
LoadingPrimaryButton(
text:
isInvoice ? S.of(context).create_invoice : S.of(context).create_donation_link,
onPressed: () {
anonInvoicePageViewModel.setRequestParams(
inputAmount: _amountController.text,
inputName: _nameController.text,
inputEmail: _emailController.text,
inputDescription: _descriptionController.text,
);
if (anonInvoicePageViewModel.receipientEmail.isNotEmpty &&
_formKey.currentState != null &&
!_formKey.currentState!.validate()) {
return;
}
if (isInvoice) {
anonInvoicePageViewModel.createInvoice();
} else {
anonInvoicePageViewModel.generateDonationLink();
}
},
color: Theme.of(context)
LoadingPrimaryButton(
text: isInvoice
? S.of(context).create_invoice
: S.of(context).create_donation_link,
onPressed: () {
anonInvoicePageViewModel.setRequestParams(
inputAmount: _amountController.text,
inputName: _nameController.text,
inputEmail: _emailController.text,
inputDescription: _descriptionController.text,
);
if (anonInvoicePageViewModel.receipientEmail.isNotEmpty &&
_formKey.currentState != null &&
!_formKey.currentState!.validate()) {
return;
}
if (isInvoice) {
anonInvoicePageViewModel.createInvoice();
} else {
anonInvoicePageViewModel.generateDonationLink();
}
},
color: Theme.of(context)
.accentTextTheme!
.bodyLarge!
.color!,
textColor: Colors.white,
isLoading: anonInvoicePageViewModel.state is IsExecutingState,
),
],
);
}),
textColor: Colors.white,
isLoading: anonInvoicePageViewModel.state is IsExecutingState,
),
],
);
}),
),
),
),
);

View file

@ -266,6 +266,8 @@ class WalletRestorePage extends BasePage {
}
void _confirmForm() {
// Dismissing all visible keyboard to provide context for navigation
FocusManager.instance.primaryFocus?.unfocus();
final formContext = walletRestoreViewModel.mode == WalletRestoreMode.seed
? walletRestoreFromSeedFormKey.currentContext
: walletRestoreFromKeysFormKey.currentContext;

View file

@ -0,0 +1,17 @@
import 'package:cake_wallet/ionia/ionia_service.dart';
import 'package:mobx/mobx.dart';
part 'market_place_view_model.g.dart';
class MarketPlaceViewModel = MarketPlaceViewModelBase with _$MarketPlaceViewModel;
abstract class MarketPlaceViewModelBase with Store {
final IoniaService _ioniaService;
MarketPlaceViewModelBase(this._ioniaService);
Future<bool> isIoniaUserAuthenticated() async {
return await _ioniaService.isLogined();
}
}

View file

@ -16,12 +16,10 @@ abstract class IoniaGiftCardsListViewModelBase with Store {
ioniaCategories = IoniaCategory.allCategories,
selectedIndices = ObservableList<IoniaCategory>.of([IoniaCategory.all]),
scrollOffsetFromTop = 0.0,
isLoggedIn = false,
merchantState = InitialIoniaMerchantLoadingState(),
createCardState = IoniaCreateCardState(),
searchString = '',
ioniaMerchantList = <IoniaMerchant>[] {
_getAuthStatus().then((value) => isLoggedIn = value);
}
final IoniaService ioniaService;
@ -45,24 +43,17 @@ abstract class IoniaGiftCardsListViewModelBase with Store {
@observable
List<IoniaMerchant> ioniaMerchants;
@observable
bool isLoggedIn;
@observable
List<IoniaCategory> ioniaCategories;
@observable
ObservableList<IoniaCategory> selectedIndices;
Future<bool> _getAuthStatus() async {
return await ioniaService.isLogined();
}
@action
Future<void> createCard() async {
try {
createCardState = IoniaCreateCardLoading();
final card = await ioniaService.createCard();
await ioniaService.createCard();
createCardState = IoniaCreateCardSuccess();
} catch (e) {
createCardState = IoniaCreateCardFailure(error: e.toString());