From ca0b68b8cc28e53596de7e372f7f3af057dd50fd Mon Sep 17 00:00:00 2001 From: monsieurtanuki Date: Fri, 10 May 2024 10:24:19 +0200 Subject: [PATCH] feat: 5200 - currency selector New files: * `currency_selector.dart`: A selector for selecting user's currency. * `user_preferences_currency_selector.dart`: Currency selector within user preferences. Impacted files: * `app_en.arb`: added 2 labels for the currency selector * `country_selector.dart`: fixed discrepancies between selector display * `user_preferences.dart`: new "user currency code" getter/setter * `user_preferences_settings.dart`: added currency selector * `welcome_page.dart`: fixed discrepancies between selector display --- .../preferences/user_preferences.dart | 10 + packages/smooth_app/lib/l10n/app_en.arb | 8 + .../pages/onboarding/country_selector.dart | 15 +- .../pages/onboarding/currency_selector.dart | 198 ++++++++++++++++++ .../lib/pages/onboarding/welcome_page.dart | 20 +- .../user_preferences_currency_selector.dart | 49 +++++ .../user_preferences_settings.dart | 3 + 7 files changed, 283 insertions(+), 20 deletions(-) create mode 100644 packages/smooth_app/lib/pages/onboarding/currency_selector.dart create mode 100644 packages/smooth_app/lib/pages/preferences/user_preferences_currency_selector.dart diff --git a/packages/smooth_app/lib/data_models/preferences/user_preferences.dart b/packages/smooth_app/lib/data_models/preferences/user_preferences.dart index 0876509b572..b1b3b6888b7 100644 --- a/packages/smooth_app/lib/data_models/preferences/user_preferences.dart +++ b/packages/smooth_app/lib/data_models/preferences/user_preferences.dart @@ -67,6 +67,7 @@ class UserPreferences extends ChangeNotifier { static const String _TAG_CURRENT_COLOR_SCHEME = 'currentColorScheme'; static const String _TAG_CURRENT_CONTRAST_MODE = 'contrastMode'; static const String _TAG_USER_COUNTRY_CODE = 'userCountry'; + static const String _TAG_USER_CURRENCY_CODE = 'userCurrency'; static const String _TAG_LAST_VISITED_ONBOARDING_PAGE = 'lastVisitedOnboardingPage'; static const String _TAG_PREFIX_FLAG = 'FLAG_PREFIX_'; @@ -213,6 +214,14 @@ class UserPreferences extends ChangeNotifier { String? get userCountryCode => _sharedPreferences.getString(_TAG_USER_COUNTRY_CODE); + Future setUserCurrencyCode(final String code) async { + await _sharedPreferences.setString(_TAG_USER_CURRENCY_CODE, code); + notifyListeners(); + } + + String? get userCurrencyCode => + _sharedPreferences.getString(_TAG_USER_CURRENCY_CODE); + Future setLastVisitedOnboardingPage(final OnboardingPage page) async { await _sharedPreferences.setInt( _TAG_LAST_VISITED_ONBOARDING_PAGE, page.index); @@ -223,6 +232,7 @@ class UserPreferences extends ChangeNotifier { await setLastVisitedOnboardingPage(OnboardingPage.NOT_STARTED); // for tests with a fresh null country await _sharedPreferences.remove(_TAG_USER_COUNTRY_CODE); + await _sharedPreferences.remove(_TAG_USER_CURRENCY_CODE); notifyListeners(); } diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb index 0582593abcc..113845369e8 100644 --- a/packages/smooth_app/lib/l10n/app_en.arb +++ b/packages/smooth_app/lib/l10n/app_en.arb @@ -798,6 +798,10 @@ "@country_chooser_label": { "description": "Label shown above a selector where the user can select their country (in the preferences)" }, + "currency_chooser_label": "Please choose a currency", + "@currency_chooser_label": { + "description": "Label shown above a selector where the user can select their currency (in the preferences)" + }, "onboarding_country_chooser_label": "Please choose a country:", "@onboarding_country_chooser_label": { "description": "The label shown above a selector where the user can select their country (in the onboarding)" @@ -2462,6 +2466,10 @@ "@country_selector_title": { "description": "Label written as the title of the dialog to select the user country" }, + "currency_selector_title": "Select your currency:", + "@currency_selector_title": { + "description": "Label written as the title of the dialog to select the user currency" + }, "language_selector_title": "Select your language:", "@language_selector_title": { "description": "Label written as the title of the dialog to select the user language" diff --git a/packages/smooth_app/lib/pages/onboarding/country_selector.dart b/packages/smooth_app/lib/pages/onboarding/country_selector.dart index 9b9fb087910..6d561081468 100644 --- a/packages/smooth_app/lib/pages/onboarding/country_selector.dart +++ b/packages/smooth_app/lib/pages/onboarding/country_selector.dart @@ -17,15 +17,13 @@ class CountrySelector extends StatefulWidget { this.textStyle, this.padding, this.icon, - this.iconDecoration, this.inkWellBorderRadius, }); final TextStyle? textStyle; final EdgeInsetsGeometry? padding; final BorderRadius? inkWellBorderRadius; - final Icon? icon; - final BoxDecoration? iconDecoration; + final Widget? icon; @override State createState() => _CountrySelectorState(); @@ -199,16 +197,7 @@ class _CountrySelectorState extends State { ), ), ), - Container( - height: double.infinity, - decoration: - widget.iconDecoration ?? const BoxDecoration(), - child: AspectRatio( - aspectRatio: 1.0, - child: - widget.icon ?? const Icon(Icons.arrow_drop_down), - ), - ), + widget.icon ?? const Icon(Icons.arrow_drop_down), ], ), ), diff --git a/packages/smooth_app/lib/pages/onboarding/currency_selector.dart b/packages/smooth_app/lib/pages/onboarding/currency_selector.dart new file mode 100644 index 00000000000..e825f2a0985 --- /dev/null +++ b/packages/smooth_app/lib/pages/onboarding/currency_selector.dart @@ -0,0 +1,198 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:provider/provider.dart'; +import 'package:smooth_app/data_models/preferences/user_preferences.dart'; +import 'package:smooth_app/generic_lib/design_constants.dart'; +import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart'; +import 'package:smooth_app/generic_lib/widgets/smooth_text_form_field.dart'; +import 'package:smooth_app/widgets/smooth_text.dart'; + +/// A selector for selecting user's currency. +class CurrencySelector extends StatefulWidget { + const CurrencySelector({ + this.textStyle, + this.padding, + this.icon, + }); + + final TextStyle? textStyle; + final EdgeInsetsGeometry? padding; + final Icon? icon; + + @override + State createState() => _CurrencySelectorState(); +} + +class _CurrencySelectorState extends State { + final ScrollController _scrollController = ScrollController(); + final TextEditingController _currencyController = TextEditingController(); + final List _currencyList = List.from(Currency.values); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + return Selector( + selector: (BuildContext buildContext, UserPreferences userPreferences) => + userPreferences.appLanguageCode, + builder: (BuildContext context, String? appLanguageCode, _) { + final UserPreferences userPreferences = + context.watch(); + final Currency selected = _getSelected( + userPreferences.userCurrencyCode, + ); + final EdgeInsetsGeometry innerPadding = const EdgeInsets.symmetric( + vertical: SMALL_SPACE, + ).add(widget.padding ?? EdgeInsets.zero); + + return InkWell( + borderRadius: ANGULAR_BORDER_RADIUS, + onTap: () async { + _reorderCurrencies(selected); + List filteredList = List.from(_currencyList); + final Currency? currency = await showDialog( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (BuildContext context, + void Function(VoidCallback fn) setState) { + const double horizontalPadding = 16.0 + SMALL_SPACE; + + return SmoothListAlertDialog( + title: appLocalizations.currency_selector_title, + header: SmoothTextFormField( + type: TextFieldTypes.PLAIN_TEXT, + prefixIcon: const Icon(Icons.search), + controller: _currencyController, + onChanged: (String? query) { + query = query!.trim()..getComparisonSafeString(); + + setState( + () { + filteredList = _currencyList + .where((Currency item) => item.name + .getComparisonSafeString() + .contains( + query!, + )) + .toList(growable: false); + }, + ); + }, + hintText: appLocalizations.search, + ), + scrollController: _scrollController, + list: ListView.separated( + controller: _scrollController, + itemBuilder: (BuildContext context, int index) { + final Currency currency = filteredList[index]; + final bool isSelected = currency == selected; + return ListTile( + dense: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: horizontalPadding, + ), + trailing: + isSelected ? const Icon(Icons.check) : null, + title: TextHighlighter( + text: currency.name, + filter: _currencyController.text, + selected: isSelected, + ), + onTap: () { + Navigator.of(context).pop(currency); + _currencyController.clear(); + }, + ); + }, + separatorBuilder: (_, __) => const Divider( + height: 1.0, + ), + itemCount: filteredList.length, + shrinkWrap: true, + ), + positiveAction: SmoothActionButton( + onPressed: () { + Navigator.pop(context); + _currencyController.clear(); + }, + text: appLocalizations.cancel, + ), + ); + }, + ); + }, + ); + if (currency != null) { + await userPreferences.setUserCurrencyCode(currency.name); + } + }, + child: DecoratedBox( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + child: IntrinsicHeight( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: innerPadding, + child: const Icon(Icons.currency_exchange), + ), + Expanded( + flex: 1, + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: LARGE_SPACE), + child: Text( + selected.name, + style: Theme.of(context) + .textTheme + .displaySmall + ?.merge(widget.textStyle), + ), + ), + ), + widget.icon ?? const Icon(Icons.arrow_drop_down), + ], + ), + ), + ), + ); + }, + ); + } + + Currency _getSelected(final String? code) { + if (code != null) { + for (final Currency currency in _currencyList) { + if (currency.name == code) { + return currency; + } + } + } + return _currencyList[0]; + } + + /// Reorder currencies alphabetically, bring user's selected one to top. + void _reorderCurrencies(final Currency selected) { + _currencyList.sort( + (final Currency a, final Currency b) { + if (a == selected) { + return -1; + } + if (b == selected) { + return 1; + } + return a.name.compareTo(b.name); + }, + ); + } + + @override + void dispose() { + _currencyController.dispose(); + super.dispose(); + } +} diff --git a/packages/smooth_app/lib/pages/onboarding/welcome_page.dart b/packages/smooth_app/lib/pages/onboarding/welcome_page.dart index cc59c5da71b..58535061740 100644 --- a/packages/smooth_app/lib/pages/onboarding/welcome_page.dart +++ b/packages/smooth_app/lib/pages/onboarding/welcome_page.dart @@ -97,13 +97,19 @@ class WelcomePage extends StatelessWidget { horizontal: SMALL_SPACE, ), inkWellBorderRadius: ROUNDED_BORDER_RADIUS, - icon: Icon( - Icons.edit, - color: Colors.white.withOpacity(0.9), - ), - iconDecoration: BoxDecoration( - color: theme.primaryColor, - borderRadius: ROUNDED_BORDER_RADIUS, + icon: Container( + height: double.infinity, + decoration: BoxDecoration( + color: theme.primaryColor, + borderRadius: ROUNDED_BORDER_RADIUS, + ), + child: AspectRatio( + aspectRatio: 1.0, + child: Icon( + Icons.edit, + color: Colors.white.withOpacity(0.9), + ), + ), ), textStyle: TextStyle(color: theme.primaryColor), ), diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_currency_selector.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_currency_selector.dart new file mode 100644 index 00000000000..dcf755977b9 --- /dev/null +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_currency_selector.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:smooth_app/generic_lib/design_constants.dart'; +import 'package:smooth_app/pages/onboarding/currency_selector.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_item.dart'; + +/// Currency selector within user preferences. +class UserPreferencesCurrencySelector extends StatelessWidget { + const UserPreferencesCurrencySelector(); + + static UserPreferencesItem getUserPreferencesItem( + final BuildContext context, + ) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + return UserPreferencesItemSimple( + labels: [_getLabel(appLocalizations)], + builder: (_) => const UserPreferencesCurrencySelector(), + ); + } + + static String _getLabel(final AppLocalizations appLocalizations) => + appLocalizations.currency_chooser_label; + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final ThemeData themeData = Theme.of(context); + return ListTile( + title: Text( + _getLabel(appLocalizations), + style: themeData.textTheme.headlineMedium, + ), + subtitle: Padding( + padding: const EdgeInsetsDirectional.only( + top: SMALL_SPACE, + bottom: SMALL_SPACE, + ), + child: CurrencySelector( + textStyle: themeData.textTheme.bodyMedium, + icon: const Icon(Icons.edit), + padding: const EdgeInsetsDirectional.only( + start: SMALL_SPACE, + ), + ), + ), + minVerticalPadding: MEDIUM_SPACE, + ); + } +} diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_settings.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_settings.dart index 937cdc94908..8488f03e269 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_settings.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_settings.dart @@ -11,6 +11,7 @@ import 'package:smooth_app/pages/preferences/user_preferences_choose_accent_colo import 'package:smooth_app/pages/preferences/user_preferences_choose_app_theme.dart'; import 'package:smooth_app/pages/preferences/user_preferences_choose_text_color_contrast.dart'; import 'package:smooth_app/pages/preferences/user_preferences_country_selector.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_currency_selector.dart'; import 'package:smooth_app/pages/preferences/user_preferences_image_source.dart'; import 'package:smooth_app/pages/preferences/user_preferences_item.dart'; import 'package:smooth_app/pages/preferences/user_preferences_language_selector.dart'; @@ -66,6 +67,8 @@ class UserPreferencesSettings extends AbstractUserPreferences { _getDivider(), UserPreferencesCountrySelector.getUserPreferencesItem(context), _getDivider(), + UserPreferencesCurrencySelector.getUserPreferencesItem(context), + _getDivider(), UserPreferencesLanguageSelector.getUserPreferencesItem(context), _getDivider(), UserPreferencesImageSource.getUserPreferencesItem(context),