diff --git a/packages/espressocash_app/lib/features/country_picker/screens/country_picker_screen.dart b/packages/espressocash_app/lib/features/country_picker/screens/country_picker_screen.dart index ef989911e4..874943a192 100644 --- a/packages/espressocash_app/lib/features/country_picker/screens/country_picker_screen.dart +++ b/packages/espressocash_app/lib/features/country_picker/screens/country_picker_screen.dart @@ -1,20 +1,63 @@ +import 'package:dfunc/dfunc.dart'; import 'package:flutter/material.dart'; +import '../../../di.dart'; import '../../../l10n/l10n.dart'; import '../../../ui/app_bar.dart'; import '../../../ui/colors.dart'; +import '../../../ui/dialogs.dart'; +import '../../../ui/loader.dart'; import '../../../ui/page_spacer_wrapper.dart'; import '../../../ui/text_field.dart'; import '../../../ui/theme.dart'; +import '../../profile/service/update_profile.dart'; import '../models/country.dart'; class CountryPickerScreen extends StatelessWidget { const CountryPickerScreen({ super.key, this.initial, + required this.shouldUpdateCountry, }); + static Future open( + BuildContext context, { + Country? initial, + NavigatorState? navigator, + }) => + (navigator ?? Navigator.of(context, rootNavigator: true)) + .pushAndRemoveUntil( + PageRouteBuilder( + pageBuilder: (context, _, __) => CountryPickerScreen( + initial: initial, + shouldUpdateCountry: true, + ), + transitionDuration: Duration.zero, + ), + F, + ) + .then((country) => country); + + static Future push( + BuildContext context, { + Country? initial, + NavigatorState? navigator, + }) => + (navigator ?? Navigator.of(context, rootNavigator: true)) + .pushAndRemoveUntil( + PageRouteBuilder( + pageBuilder: (context, _, __) => CountryPickerScreen( + initial: initial, + shouldUpdateCountry: false, + ), + transitionDuration: Duration.zero, + ), + F, + ) + .then((country) => country); + final Country? initial; + final bool shouldUpdateCountry; @override Widget build(BuildContext context) => CpTheme.dark( @@ -23,15 +66,21 @@ class CountryPickerScreen extends StatelessWidget { appBar: CpAppBar( title: Text(context.l10n.selectCountryTitle.toUpperCase()), ), - body: _Wrapper(child: _Content(initial: initial)), + body: _Wrapper( + child: _Content( + initial: initial, + shouldUpdateCountry: shouldUpdateCountry, + ), + ), ), ); } class _Content extends StatefulWidget { - const _Content({this.initial}); + const _Content({this.initial, required this.shouldUpdateCountry}); final Country? initial; + final bool shouldUpdateCountry; @override State<_Content> createState() => _ContentState(); @@ -46,6 +95,24 @@ class _ContentState extends State<_Content> { final _countries = Country.all; + Future _updateCountry(Country country) => runWithLoader( + context, + () async { + await sl() + .call( + countryCode: country.code, + ) + .foldAsync((e) => throw e, ignore); + + if (!context.mounted) return; + }, + onError: (error) => showErrorDialog( + context, + context.l10n.lblProfileUpdateFailed, + error, + ), + ); + @override void initState() { super.initState(); @@ -143,7 +210,14 @@ class _ContentState extends State<_Content> { ), selectedColor: Colors.white, shape: selected ? const StadiumBorder() : null, - onTap: () => Navigator.pop(context, country), + onTap: () async { + if (widget.shouldUpdateCountry) { + await _updateCountry(country); + } + + if (!context.mounted) return; + Navigator.pop(context, country); + }, ), ); }, diff --git a/packages/espressocash_app/lib/features/country_picker/widgets/country_picker.dart b/packages/espressocash_app/lib/features/country_picker/widgets/country_picker.dart index 171d60c2e5..8ed8e2d2fb 100644 --- a/packages/espressocash_app/lib/features/country_picker/widgets/country_picker.dart +++ b/packages/espressocash_app/lib/features/country_picker/widgets/country_picker.dart @@ -25,12 +25,8 @@ class CountryPicker extends StatelessWidget { child: ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 24), onTap: () async { - final Country? updated = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => CountryPickerScreen(initial: country), - ), - ); + final Country? updated = + await CountryPickerScreen.push(context, initial: country); if (context.mounted && updated != null) { onSubmitted(updated); diff --git a/packages/espressocash_app/lib/features/onboarding/screens/onboarding_flow_screen.dart b/packages/espressocash_app/lib/features/onboarding/screens/onboarding_flow_screen.dart index ba161469c8..96ed8e5624 100644 --- a/packages/espressocash_app/lib/features/onboarding/screens/onboarding_flow_screen.dart +++ b/packages/espressocash_app/lib/features/onboarding/screens/onboarding_flow_screen.dart @@ -1,34 +1,44 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import '../../../di.dart'; +import '../../country_picker/screens/country_picker_screen.dart'; import '../data/onboarding_repository.dart'; -import 'profile_screen.dart'; import 'view_recovery_phrase_screen.dart'; class OnboardingFlowScreen { - static void open( + static Future open( BuildContext context, { required VoidCallback onConfirmed, NavigatorState? navigator, - }) { + }) async { final hasConfirmedPassphrase = sl().hasConfirmedPassphrase; if (hasConfirmedPassphrase) { - OnboardingProfileScreen.open( + await CountryPickerScreen.open( context, navigator: navigator, - onConfirmed: onConfirmed, ); + + if (context.mounted) { + unawaited(Future.microtask(() => onConfirmed())); + } } else { ViewRecoveryPhraseScreen.open( context, navigator: navigator, - onConfirmed: () => OnboardingProfileScreen.open( - context, - navigator: navigator, - onConfirmed: onConfirmed, - ), + onConfirmed: () async { + await CountryPickerScreen.open( + context, + navigator: navigator, + ); + + if (context.mounted) { + unawaited(Future.microtask(() => onConfirmed())); + } + }, ); } } diff --git a/packages/espressocash_app/lib/features/onboarding/screens/profile_screen.dart b/packages/espressocash_app/lib/features/onboarding/screens/profile_screen.dart deleted file mode 100644 index d78a9fba3c..0000000000 --- a/packages/espressocash_app/lib/features/onboarding/screens/profile_screen.dart +++ /dev/null @@ -1,196 +0,0 @@ -import 'dart:async'; - -import 'package:dfunc/dfunc.dart'; -import 'package:flutter/material.dart'; - -import '../../../di.dart'; -import '../../../gen/assets.gen.dart'; -import '../../../l10n/l10n.dart'; -import '../../../ui/button.dart'; -import '../../../ui/dialogs.dart'; -import '../../../ui/form_page.dart'; -import '../../../ui/loader.dart'; -import '../../../ui/text_field.dart'; -import '../../../utils/email.dart'; -import '../../country_picker/models/country.dart'; -import '../../country_picker/widgets/country_picker.dart'; -import '../../profile/data/profile_repository.dart'; -import '../../profile/service/update_profile.dart'; - -class OnboardingProfileScreen extends StatefulWidget { - const OnboardingProfileScreen({ - super.key, - required this.onConfirmed, - }); - - final VoidCallback onConfirmed; - - static void open( - BuildContext context, { - required VoidCallback onConfirmed, - NavigatorState? navigator, - }) => - (navigator ?? Navigator.of(context, rootNavigator: true)) - .pushAndRemoveUntil( - PageRouteBuilder( - pageBuilder: (context, _, __) => - OnboardingProfileScreen(onConfirmed: onConfirmed), - transitionDuration: Duration.zero, - ), - F, - ); - - @override - State createState() => - _OnboardingProfileScreenState(); -} - -class _OnboardingProfileScreenState extends State { - final _firstNameController = TextEditingController(); - final _lastNameController = TextEditingController(); - final _emailController = TextEditingController(); - Country? _country; - - @override - void initState() { - super.initState(); - - final repository = sl(); - - _firstNameController.text = repository.firstName; - _lastNameController.text = repository.lastName; - _emailController.text = repository.email; - - final country = repository.country; - if (country != null) { - _country = Country.findByCode(country); - } - } - - @override - void dispose() { - _firstNameController.dispose(); - _lastNameController.dispose(); - _emailController.dispose(); - super.dispose(); - } - - void _handleSubmitted() => runWithLoader( - context, - () async { - await sl() - .call( - firstName: _firstNameController.text, - lastName: _lastNameController.text, - // ignore: avoid-non-null-assertion, should not be null - countryCode: _country!.code, - photoPath: null, - email: _emailController.text, - ) - .foldAsync((e) => throw e, ignore); - - unawaited(Future.microtask(() => widget.onConfirmed())); - }, - onError: (error) => showErrorDialog( - context, - context.l10n.lblProfileUpdateFailed, - error, - ), - ); - - bool get _isValid => - _firstNameController.text.isNotEmpty && - _lastNameController.text.isNotEmpty && - _emailController.text.isValidEmail && - _country != null; - - @override - Widget build(BuildContext context) => FormPage( - title: Text(context.l10n.onboardingProfileTitle.toUpperCase()), - backgroundImage: Assets.images.blank, - colorTheme: FormPageColorTheme.gold, - header: FormPageHeader( - title: const SizedBox.shrink(), - description: Text( - context.l10n.yourEmailDisclaimer, - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400), - ), - icon: Assets.images.profileGraphic, - ), - child: Column( - children: [ - _ProfileTextField( - emailController: _firstNameController, - inputType: TextInputType.name, - placeholder: context.l10n.yourFirstNamePlaceholder, - textCapitalization: TextCapitalization.words, - ), - const SizedBox(height: 14), - _ProfileTextField( - emailController: _lastNameController, - inputType: TextInputType.name, - placeholder: context.l10n.yourLastNamePlaceholder, - textCapitalization: TextCapitalization.words, - ), - const SizedBox(height: 14), - _ProfileTextField( - emailController: _emailController, - inputType: TextInputType.emailAddress, - placeholder: context.l10n.yourEmailPlaceholder, - ), - const SizedBox(height: 14), - CountryPicker( - country: _country, - onSubmitted: (country) => setState(() => _country = country), - ), - const SizedBox(height: 28), - const Spacer(), - ListenableBuilder( - listenable: Listenable.merge([ - _firstNameController, - _lastNameController, - _emailController, - ]), - builder: (context, child) => CpButton( - width: double.infinity, - text: context.l10n.next, - onPressed: _isValid ? _handleSubmitted : null, - ), - ), - ], - ), - ); -} - -class _ProfileTextField extends StatelessWidget { - const _ProfileTextField({ - required this.emailController, - required this.inputType, - required this.placeholder, - this.textCapitalization = TextCapitalization.none, - }); - - final TextEditingController emailController; - final TextInputType inputType; - final TextCapitalization textCapitalization; - final String placeholder; - - @override - Widget build(BuildContext context) => CpTextField( - padding: const EdgeInsets.only( - top: 18, - bottom: 16, - left: 26, - right: 26, - ), - controller: emailController, - inputType: inputType, - textInputAction: TextInputAction.next, - textCapitalization: textCapitalization, - backgroundColor: const Color(0xFF9D8A59), - placeholder: placeholder, - placeholderColor: Colors.white, - textColor: Colors.white, - fontSize: 16, - ); -} diff --git a/packages/espressocash_app/lib/features/profile/data/profile_repository.dart b/packages/espressocash_app/lib/features/profile/data/profile_repository.dart index 3d61b46e90..e802599239 100644 --- a/packages/espressocash_app/lib/features/profile/data/profile_repository.dart +++ b/packages/espressocash_app/lib/features/profile/data/profile_repository.dart @@ -11,11 +11,7 @@ class ProfileRepository extends ChangeNotifier { final SharedPreferences _sharedPreferences; - bool get hasAllRequiredFields => - firstName.isNotEmpty && - lastName.isNotEmpty && - email.isNotEmpty && - country != null; + bool get hasAllRequiredFields => country != null; String get fullName => [firstName, lastName].where((it) => it.isNotEmpty).join(' ');