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 c3c913368ce..d95bec1d60d 100644 --- a/packages/smooth_app/lib/data_models/preferences/user_preferences.dart +++ b/packages/smooth_app/lib/data_models/preferences/user_preferences.dart @@ -314,8 +314,10 @@ class UserPreferences extends ChangeNotifier { String? getDevModeString(final String tag) => _sharedPreferences.getString(tag); - Future setActiveAttributeGroup(final String value) async => - _sharedPreferences.setString(_TAG_ACTIVE_ATTRIBUTE_GROUP, value); + Future setActiveAttributeGroup(final String value) async { + await _sharedPreferences.setString(_TAG_ACTIVE_ATTRIBUTE_GROUP, value); + notifyListeners(); + } String get activeAttributeGroup => _sharedPreferences.getString(_TAG_ACTIVE_ATTRIBUTE_GROUP) ?? diff --git a/packages/smooth_app/lib/pages/onboarding/preferences_page.dart b/packages/smooth_app/lib/pages/onboarding/preferences_page.dart index 881edc9de3b..7fba594ab08 100644 --- a/packages/smooth_app/lib/pages/onboarding/preferences_page.dart +++ b/packages/smooth_app/lib/pages/onboarding/preferences_page.dart @@ -115,7 +115,6 @@ class _HelperState extends State<_Helper> { pageData.addAll( UserPreferencesFood( productPreferences: productPreferences, - setState: setState, context: context, userPreferences: userPreferences, appLocalizations: appLocalizations, diff --git a/packages/smooth_app/lib/pages/preferences/abstract_user_preferences.dart b/packages/smooth_app/lib/pages/preferences/abstract_user_preferences.dart index ebe79a19c93..b6ab289763c 100644 --- a/packages/smooth_app/lib/pages/preferences/abstract_user_preferences.dart +++ b/packages/smooth_app/lib/pages/preferences/abstract_user_preferences.dart @@ -8,23 +8,20 @@ import 'package:smooth_app/themes/constant_icons.dart'; /// Abstraction of a display for the preference pages. abstract class AbstractUserPreferences { AbstractUserPreferences({ - required this.setState, required this.context, required this.userPreferences, required this.appLocalizations, required this.themeData, }); - /// Function that refreshes the page. - final Function(Function()) setState; - final BuildContext context; final UserPreferences userPreferences; final AppLocalizations appLocalizations; final ThemeData themeData; /// Returns the type of the corresponding page if relevant, or else null. - PreferencePageType? getPreferencePageType(); + @protected + PreferencePageType getPreferencePageType(); /// Title of the header, always visible. String getTitleString(); @@ -45,6 +42,7 @@ abstract class AbstractUserPreferences { child: getHeaderHelper(false), ); + @protected Icon? getForwardIcon() => UserPreferencesListTile.getTintedIcon( ConstantIcons.instance.getForwardIcon(), context, @@ -71,25 +69,10 @@ abstract class AbstractUserPreferences { IconData getLeadingIconData(); /// Body of the content. - @protected List getBody(); - /// Returns possibly the header and the body. - List getContent({ - final bool withHeader = true, - final bool withBody = true, - }) { - final List result = []; - if (withHeader) { - result.add(getHeader()); - } - if (withBody) { - result.addAll(getBody()); - } - return result; - } - /// Returns the action when we tap on the header. + @protected Future runHeaderAction() async => Navigator.push( context, MaterialPageRoute( diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_account.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_account.dart index 2c935c68e70..36e46572e23 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_account.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_account.dart @@ -25,13 +25,11 @@ import 'package:smooth_app/services/smooth_services.dart'; class UserPreferencesAccount extends AbstractUserPreferences { UserPreferencesAccount({ - required final Function(Function()) setState, required final BuildContext context, required final UserPreferences userPreferences, required final AppLocalizations appLocalizations, required final ThemeData themeData, }) : super( - setState: setState, context: context, userPreferences: userPreferences, appLocalizations: appLocalizations, @@ -39,18 +37,7 @@ class UserPreferencesAccount extends AbstractUserPreferences { ); @override - List getBody() { - return [ - UserPreferencesSection( - userPreferences: userPreferences, - appLocalizations: appLocalizations, - themeData: themeData, - ), - ]; - } - - @override - PreferencePageType? getPreferencePageType() => PreferencePageType.ACCOUNT; + PreferencePageType getPreferencePageType() => PreferencePageType.ACCOUNT; String? _getUserId() => OpenFoodAPIConfiguration.globalUser?.userId; @@ -74,47 +61,24 @@ class UserPreferencesAccount extends AbstractUserPreferences { } @override - String getTitleString() { - return appLocalizations.myPreferences_profile_title; - } + String getTitleString() => appLocalizations.myPreferences_profile_title; @override - Widget? getSubtitle() { - if (!_isUserConnected()) { - return const _UserPreferencesAccountSubTitleSignOut(); - } else { - return Text(appLocalizations.myPreferences_profile_subtitle); - } - } + Widget? getSubtitle() => _isUserConnected() + ? Text(appLocalizations.myPreferences_profile_subtitle) + : Text(appLocalizations.user_profile_subtitle_guest); @override IconData getLeadingIconData() => Icons.face; // No arrow @override - Icon? getForwardIcon() { - if (_isUserConnected()) { - return super.getForwardIcon(); - } else { - return null; - } - } + Icon? getForwardIcon() => _isUserConnected() ? super.getForwardIcon() : null; @override - Future runHeaderAction() async { - if (_isUserConnected(readOnly: true)) { - return super.runHeaderAction(); - } else { - return Navigator.of( - context, - rootNavigator: true, - ).push( - MaterialPageRoute( - builder: (BuildContext context) => const LoginPage(), - ), - ); - } - } + Future runHeaderAction() async => _isUserConnected(readOnly: true) + ? super.runHeaderAction() + : _goToLoginPage(); bool _isUserConnected({bool readOnly = false}) { // Ensure to be notified after a sign-in/sign-out @@ -126,10 +90,10 @@ class UserPreferencesAccount extends AbstractUserPreferences { } @override - Widget getAdditionalSubtitle() { + Widget? getAdditionalSubtitle() { if (_getUserId() != null) { // we are already connected: no "LOGIN" button - return EMPTY_WIDGET; + return null; } final ThemeData theme = Theme.of(context); final Size size = MediaQuery.of(context).size; @@ -148,199 +112,32 @@ class UserPreferencesAccount extends AbstractUserPreferences { color: theme.colorScheme.onPrimary, ), ), - onPressed: () async { - Navigator.of( - context, - rootNavigator: true, - ).push( - MaterialPageRoute( - builder: (BuildContext context) => const LoginPage(), - ), - ); - }, + onPressed: () async => _goToLoginPage(), ), ); } -} - -class _UserPreferencesAccountSubTitleSignOut extends StatelessWidget { - const _UserPreferencesAccountSubTitleSignOut({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - - return Text(appLocalizations.user_profile_subtitle_guest); - } -} - -// Put into it's own widget in order for provider.watch() to work -class UserPreferencesSection extends StatefulWidget { - const UserPreferencesSection({ - Key? key, - required this.userPreferences, - required this.appLocalizations, - required this.themeData, - }) : super(key: key); - - final UserPreferences userPreferences; - final AppLocalizations appLocalizations; - final ThemeData themeData; - - @override - State createState() => _UserPreferencesPageState(); -} -class _UserPreferencesPageState extends State { - Future _confirmLogout(BuildContext context) { - final AppLocalizations localizations = AppLocalizations.of(context); - - return showDialog( - context: context, - builder: (BuildContext context) { - return SmoothAlertDialog( - title: localizations.sign_out, - body: Text( - localizations.sign_out_confirmation, - ), - positiveAction: SmoothActionButton( - text: localizations.yes, - onPressed: () async { - context.read().logout(); - AnalyticsHelper.trackEvent(AnalyticsEvent.logoutAction); - Navigator.pop(context, true); - }, - ), - negativeAction: SmoothActionButton( - text: localizations.no, - onPressed: () { - Navigator.pop(context); - }, - ), - ); - }, - ); - } + Future _goToLoginPage() async => Navigator.of( + context, + rootNavigator: true, + ).push( + MaterialPageRoute( + builder: (BuildContext context) => const LoginPage(), + ), + ); @override - Widget build(BuildContext context) { - // We need to listen to reflect login's from outside of the preferences page - // e.g. question card, ... - context.watch(); - final LocalDatabase localDatabase = context.read(); - - final ThemeData theme = Theme.of(context); - final AppLocalizations appLocalizations = AppLocalizations.of(context); - final Size size = MediaQuery.of(context).size; - - final List result; - - if (OpenFoodAPIConfiguration.globalUser != null) { - // Credentials - final String userId = OpenFoodAPIConfiguration.globalUser!.userId; - - result = [ - _buildProductQueryTile( - productQuery: PagedUserProductQuery( - userId: userId, - type: UserSearchType.CONTRIBUTOR, - ), - title: appLocalizations.user_search_contributor_title, - iconData: Icons.add_circle_outline, - context: context, - localDatabase: localDatabase, - myCount: _getMyCount(UserSearchType.CONTRIBUTOR), - ), - _buildProductQueryTile( - productQuery: PagedUserProductQuery( - userId: userId, - type: UserSearchType.INFORMER, - ), - title: appLocalizations.user_search_informer_title, - iconData: Icons.edit, - context: context, - localDatabase: localDatabase, - myCount: _getMyCount(UserSearchType.INFORMER), - ), - _buildProductQueryTile( - productQuery: PagedUserProductQuery( - userId: userId, - type: UserSearchType.PHOTOGRAPHER, - ), - title: appLocalizations.user_search_photographer_title, - iconData: Icons.add_a_photo, - context: context, - localDatabase: localDatabase, - myCount: _getMyCount(UserSearchType.PHOTOGRAPHER), - ), - _buildProductQueryTile( - productQuery: PagedUserProductQuery( - userId: userId, - type: UserSearchType.TO_BE_COMPLETED, - ), - title: appLocalizations.user_search_to_be_completed_title, - iconData: Icons.more_horiz, - context: context, - localDatabase: localDatabase, - myCount: _getMyCount(UserSearchType.TO_BE_COMPLETED), - ), - _buildProductQueryTile( - productQuery: PagedToBeCompletedProductQuery(), - title: appLocalizations.all_search_to_be_completed_title, - iconData: Icons.more_outlined, - context: context, - localDatabase: localDatabase, - ), - _getListTile( - appLocalizations.view_profile, - () async => LaunchUrlHelper.launchURL( - 'https://openfoodfacts.org/editor/$userId', - true, - ), - Icons.open_in_new, - ), - _getListTile( - appLocalizations.account_delete, - () { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => AccountDeletionWebview(), - ), - ); - }, - Icons.delete, - ), - _getListTile( - appLocalizations.sign_out, - () async { - // ignore: use_build_context_synchronously - if (await _confirmLogout(context) == true) { - // ignore: use_build_context_synchronously - Navigator.pop(context); - } - }, - Icons.clear, - ), - ]; - } else { + List getBody() { + if (OpenFoodAPIConfiguration.globalUser == null) { // No credentials - result = [ + final Size size = MediaQuery.of(context).size; + return [ Center( child: ElevatedButton( - onPressed: () async { - Navigator.of( - context, - rootNavigator: true, - ).push( - MaterialPageRoute( - builder: (BuildContext context) => const LoginPage(), - ), - ); - }, + onPressed: () async => _goToLoginPage(), style: ButtonStyle( minimumSize: MaterialStateProperty.all( - Size(size.width * 0.5, theme.buttonTheme.height + 10), + Size(size.width * 0.5, themeData.buttonTheme.height + 10), ), shape: MaterialStateProperty.all( const RoundedRectangleBorder( @@ -350,10 +147,10 @@ class _UserPreferencesPageState extends State { ), child: Text( appLocalizations.sign_in, - style: theme.textTheme.bodyMedium?.copyWith( + style: themeData.textTheme.bodyMedium?.copyWith( fontSize: 18.0, fontWeight: FontWeight.bold, - color: theme.colorScheme.onPrimary, + color: themeData.colorScheme.onPrimary, ), ), ), @@ -361,9 +158,117 @@ class _UserPreferencesPageState extends State { ]; } - return Column(children: result); + final LocalDatabase localDatabase = context.read(); + // Credentials + final String userId = OpenFoodAPIConfiguration.globalUser!.userId; + return [ + _buildProductQueryTile( + productQuery: PagedUserProductQuery( + userId: userId, + type: UserSearchType.CONTRIBUTOR, + ), + title: appLocalizations.user_search_contributor_title, + iconData: Icons.add_circle_outline, + context: context, + localDatabase: localDatabase, + myCount: _getMyCount(UserSearchType.CONTRIBUTOR), + ), + _buildProductQueryTile( + productQuery: PagedUserProductQuery( + userId: userId, + type: UserSearchType.INFORMER, + ), + title: appLocalizations.user_search_informer_title, + iconData: Icons.edit, + context: context, + localDatabase: localDatabase, + myCount: _getMyCount(UserSearchType.INFORMER), + ), + _buildProductQueryTile( + productQuery: PagedUserProductQuery( + userId: userId, + type: UserSearchType.PHOTOGRAPHER, + ), + title: appLocalizations.user_search_photographer_title, + iconData: Icons.add_a_photo, + context: context, + localDatabase: localDatabase, + myCount: _getMyCount(UserSearchType.PHOTOGRAPHER), + ), + _buildProductQueryTile( + productQuery: PagedUserProductQuery( + userId: userId, + type: UserSearchType.TO_BE_COMPLETED, + ), + title: appLocalizations.user_search_to_be_completed_title, + iconData: Icons.more_horiz, + context: context, + localDatabase: localDatabase, + myCount: _getMyCount(UserSearchType.TO_BE_COMPLETED), + ), + _buildProductQueryTile( + productQuery: PagedToBeCompletedProductQuery(), + title: appLocalizations.all_search_to_be_completed_title, + iconData: Icons.more_outlined, + context: context, + localDatabase: localDatabase, + ), + _getListTile( + appLocalizations.view_profile, + () async => LaunchUrlHelper.launchURL( + 'https://openfoodfacts.org/editor/$userId', + true, + ), + Icons.open_in_new, + ), + _getListTile( + appLocalizations.account_delete, + () async => Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => AccountDeletionWebview(), + ), + ), + Icons.delete, + ), + _getListTile( + appLocalizations.sign_out, + () async { + if (await _confirmLogout() == true) { + if (context.mounted) { + await context.read().logout(); + AnalyticsHelper.trackEvent(AnalyticsEvent.logoutAction); + if (context.mounted) { + Navigator.pop(context); + } + } + } + }, + Icons.clear, + ), + ]; } + Future _confirmLogout() async => showDialog( + context: context, + builder: (BuildContext context) { + return SmoothAlertDialog( + title: appLocalizations.sign_out, + body: Text( + appLocalizations.sign_out_confirmation, + ), + positiveAction: SmoothActionButton( + text: appLocalizations.yes, + onPressed: () async => Navigator.pop(context, true), + ), + negativeAction: SmoothActionButton( + text: appLocalizations.no, + onPressed: () => Navigator.pop(context, false), + ), + ); + }, + ); + Future _getMyCount( final UserSearchType type, ) async { diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_advanced_settings.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_advanced_settings.dart new file mode 100644 index 00000000000..223bd4da68e --- /dev/null +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_advanced_settings.dart @@ -0,0 +1,20 @@ +import 'package:app_settings/app_settings.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_widgets.dart'; + +class UserPreferencesAdvancedSettings extends StatelessWidget { + const UserPreferencesAdvancedSettings(); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + return UserPreferenceListTile( + onTap: (_) async => AppSettings.openAppSettings(), + title: appLocalizations.native_app_settings, + subTitle: appLocalizations.native_app_description, + leading: const Icon(CupertinoIcons.settings_solid), + showDivider: true, + ); + } +} diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_attribute_group.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_attribute_group.dart index b65ac09e171..074fbdd7ead 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_attribute_group.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_attribute_group.dart @@ -4,55 +4,49 @@ import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:smooth_app/data_models/preferences/user_preferences.dart'; import 'package:smooth_app/data_models/product_preferences.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; -import 'package:smooth_app/pages/preferences/abstract_user_preferences.dart'; import 'package:smooth_app/pages/preferences/attribute_group_list_tile.dart'; -import 'package:smooth_app/pages/preferences/user_preferences_page.dart'; import 'package:smooth_app/widgets/attribute_button.dart'; /// Collapsed/expanded display of an attribute group for the preferences page. -class UserPreferencesAttributeGroup extends AbstractUserPreferences { +class UserPreferencesAttributeGroup { UserPreferencesAttributeGroup({ required this.productPreferences, required this.group, - required final Function(Function()) setState, - required final BuildContext context, - required final UserPreferences userPreferences, - required final AppLocalizations appLocalizations, - required final ThemeData themeData, - }) : super( - setState: setState, - context: context, - userPreferences: userPreferences, - appLocalizations: appLocalizations, - themeData: themeData, - ); + required this.context, + required this.userPreferences, + required this.appLocalizations, + required this.themeData, + }); + + final BuildContext context; + final UserPreferences userPreferences; + final AppLocalizations appLocalizations; + final ThemeData themeData; final ProductPreferences productPreferences; final AttributeGroup group; - @override - PreferencePageType? getPreferencePageType() => null; - - @override - String getTitleString() => group.name ?? appLocalizations.unknown; - - @override - Widget getTitle() => Text( - getTitleString(), - style: themeData.textTheme.titleLarge, - ); - - @override - Widget? getSubtitle() => - null; // TODO(monsieurtanuki): useless here, we should refactor, one day + bool get _isCollapsed => userPreferences.activeAttributeGroup != group.id; - @override - IconData getLeadingIconData() => Icons - .question_mark; // TODO(monsieurtanuki): useless here, we should refactor, one day - - @override - List getBody() { + List getContent() { final List result = []; + result.add( + InkWell( + onTap: () async => userPreferences.setActiveAttributeGroup(group.id!), + child: AttributeGroupListTile( + title: Text( + group.name ?? appLocalizations.unknown, + style: themeData.textTheme.titleLarge, + ), + icon: _isCollapsed + ? const Icon(Icons.keyboard_arrow_right) + : const Icon(Icons.keyboard_arrow_down), + ), + ), + ); + if (_isCollapsed) { + return result; + } if (group.warning != null) { result.add( Container( @@ -79,34 +73,4 @@ class UserPreferencesAttributeGroup extends AbstractUserPreferences { } return result; } - - @override - Widget getHeader() => - _isCollapsed() ? super.getHeader() : getHeaderHelper(false); - - @override - Widget getHeaderHelper(final bool? collapsed) => AttributeGroupListTile( - title: getTitle(), - icon: collapsed! - ? const Icon(Icons.keyboard_arrow_right) - : const Icon(Icons.keyboard_arrow_down), - ); - - bool _isCollapsed() => userPreferences.activeAttributeGroup != group.id; - - @override - List getContent({ - final bool withHeader = true, - final bool withBody = true, - }) => - super.getContent( - withHeader: withHeader, - withBody: !_isCollapsed(), - ); - - @override - Future runHeaderAction() async { - await userPreferences.setActiveAttributeGroup(group.id!); - setState(() {}); - } } diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_camera_sound.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_camera_sound.dart new file mode 100644 index 00000000000..b4540e4f877 --- /dev/null +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_camera_sound.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import 'package:smooth_app/data_models/preferences/user_preferences.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_widgets.dart'; + +class UserPreferencesCameraSound extends StatelessWidget { + const UserPreferencesCameraSound(); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final UserPreferences userPreferences = context.watch(); + return UserPreferencesSwitchItem( + title: appLocalizations.camera_play_sound_title, + subtitle: appLocalizations.camera_play_sound_subtitle, + value: userPreferences.playCameraSound, + onChanged: (final bool value) async => + userPreferences.setPlayCameraSound(value), + ); + } +} diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_choose_accent_color.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_choose_accent_color.dart new file mode 100644 index 00000000000..981599144a0 --- /dev/null +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_choose_accent_color.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import 'package:smooth_app/generic_lib/design_constants.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_widgets.dart'; +import 'package:smooth_app/themes/color_provider.dart'; +import 'package:smooth_app/themes/color_schemes.dart'; + +class UserPreferencesChooseAccentColor extends StatelessWidget { + const UserPreferencesChooseAccentColor(); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final ColorProvider colorProvider = context.watch(); + final Map labels = _localizedNames(appLocalizations); + + return UserPreferencesMultipleChoicesItem( + title: appLocalizations.select_accent_color, + leadingBuilder: labels.keys.map( + (String key) => (_) => CircleAvatar( + backgroundColor: getColorValue(key), + radius: SMALL_SPACE, + ), + ), + labels: labels.values, + values: labels.keys, + currentValue: colorProvider.currentColor, + onChanged: (String? newValue) => colorProvider.setColor(newValue!), + ); + } + + Map _localizedNames(AppLocalizations appLocalizations) => + { + 'Blue': appLocalizations.color_blue, + 'Cyan': appLocalizations.color_cyan, + 'Green': appLocalizations.color_green, + 'Default': appLocalizations.color_light_brown, + 'Magenta': appLocalizations.color_magenta, + 'Orange': appLocalizations.color_orange, + 'Pink': appLocalizations.color_pink, + 'Red': appLocalizations.color_red, + 'Rust': appLocalizations.color_rust, + 'Teal': appLocalizations.color_teal, + }; +} diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_choose_app_theme.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_choose_app_theme.dart new file mode 100644 index 00000000000..6fe37c5b1e7 --- /dev/null +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_choose_app_theme.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_widgets.dart'; +import 'package:smooth_app/themes/theme_provider.dart'; + +class UserPreferencesChooseAppTheme extends StatelessWidget { + const UserPreferencesChooseAppTheme(); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final ThemeProvider themeProvider = context.watch(); + + return UserPreferencesMultipleChoicesItem( + title: appLocalizations.darkmode, + leadingBuilder: [ + (_) => const Icon(Icons.brightness_medium), + (_) => const Icon(Icons.light_mode), + (_) => const Icon(Icons.dark_mode_outlined), + (_) => const Icon(Icons.dark_mode), + ], + labels: [ + appLocalizations.darkmode_system_default, + appLocalizations.darkmode_light, + appLocalizations.darkmode_dark, + appLocalizations.theme_amoled, + ], + values: const [ + THEME_SYSTEM_DEFAULT, + THEME_LIGHT, + THEME_DARK, + THEME_AMOLED, + ], + currentValue: themeProvider.currentTheme, + onChanged: (String? newValue) => themeProvider.setTheme(newValue!), + ); + } +} diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_choose_text_color_contrast.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_choose_text_color_contrast.dart new file mode 100644 index 00000000000..49d3780ca5e --- /dev/null +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_choose_text_color_contrast.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_widgets.dart'; +import 'package:smooth_app/themes/color_schemes.dart'; +import 'package:smooth_app/themes/contrast_provider.dart'; + +class UserPreferencesChooseTextColorContrast extends StatelessWidget { + const UserPreferencesChooseTextColorContrast(); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final TextContrastProvider textContrastProvider = + context.watch(); + + return UserPreferencesMultipleChoicesItem( + title: appLocalizations.text_contrast_mode, + values: const [ + CONTRAST_HIGH, + CONTRAST_MEDIUM, + CONTRAST_LOW, + ], + labels: [ + appLocalizations.contrast_high, + appLocalizations.contrast_medium, + appLocalizations.contrast_low, + ], + currentValue: textContrastProvider.currentContrastLevel, + onChanged: (String? contrast) => + textContrastProvider.setContrast(contrast!), + ); + } +} diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_connect.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_connect.dart index ad50f98f064..84d019635f6 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_connect.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_connect.dart @@ -23,13 +23,11 @@ import 'package:smooth_app/services/smooth_services.dart'; /// Display of "Connect" for the preferences page. class UserPreferencesConnect extends AbstractUserPreferences { UserPreferencesConnect({ - required final Function(Function()) setState, required final BuildContext context, required final UserPreferences userPreferences, required final AppLocalizations appLocalizations, required final ThemeData themeData, }) : super( - setState: setState, context: context, userPreferences: userPreferences, appLocalizations: appLocalizations, @@ -37,7 +35,7 @@ class UserPreferencesConnect extends AbstractUserPreferences { ); @override - PreferencePageType? getPreferencePageType() => PreferencePageType.CONNECT; + PreferencePageType getPreferencePageType() => PreferencePageType.CONNECT; @override String getTitleString() => appLocalizations.connect_with_us; diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_contribute.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_contribute.dart index cacfcae94ba..a8ecdcf7dfe 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_contribute.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_contribute.dart @@ -23,13 +23,11 @@ import 'package:smooth_app/query/product_query.dart'; /// Display of "Contribute" for the preferences page. class UserPreferencesContribute extends AbstractUserPreferences { UserPreferencesContribute({ - required final Function(Function()) setState, required final BuildContext context, required final UserPreferences userPreferences, required final AppLocalizations appLocalizations, required final ThemeData themeData, }) : super( - setState: setState, context: context, userPreferences: userPreferences, appLocalizations: appLocalizations, @@ -37,7 +35,7 @@ class UserPreferencesContribute extends AbstractUserPreferences { ); @override - PreferencePageType? getPreferencePageType() => PreferencePageType.CONTRIBUTE; + PreferencePageType getPreferencePageType() => PreferencePageType.CONTRIBUTE; @override String getTitleString() => appLocalizations.contribute; diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_country_selector.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_country_selector.dart new file mode 100644 index 00000000000..9717452d791 --- /dev/null +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_country_selector.dart @@ -0,0 +1,34 @@ +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/country_selector.dart'; + +class UserPreferencesCountrySelector extends StatelessWidget { + const UserPreferencesCountrySelector(); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final ThemeData themeData = Theme.of(context); + return ListTile( + title: Text( + appLocalizations.country_chooser_label, + style: themeData.textTheme.headlineMedium, + ), + subtitle: Padding( + padding: const EdgeInsetsDirectional.only( + top: SMALL_SPACE, + bottom: SMALL_SPACE, + ), + child: CountrySelector( + textStyle: themeData.textTheme.bodyMedium, + icon: Icons.edit, + padding: const EdgeInsetsDirectional.only( + start: SMALL_SPACE, + ), + ), + ), + minVerticalPadding: MEDIUM_SPACE, + ); + } +} diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_crash_reporting.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_crash_reporting.dart new file mode 100644 index 00000000000..edc55bc336b --- /dev/null +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_crash_reporting.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import 'package:smooth_app/data_models/preferences/user_preferences.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_widgets.dart'; + +class UserPreferencesCrashReporting extends StatelessWidget { + const UserPreferencesCrashReporting(); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final UserPreferences userPreferences = context.watch(); + return UserPreferencesSwitchItem( + title: appLocalizations.crash_reporting_toggle_title, + subtitle: appLocalizations.crash_reporting_toggle_subtitle, + value: userPreferences.crashReports, + onChanged: (final bool value) async => + userPreferences.setCrashReports(value), + ); + } +} diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_dev_mode.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_dev_mode.dart index e0b56a42476..051fb988a88 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_dev_mode.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_dev_mode.dart @@ -26,13 +26,11 @@ import 'package:smooth_app/query/product_query.dart'; /// Settings => FAQ => Develop => Clicking switch class UserPreferencesDevMode extends AbstractUserPreferences { UserPreferencesDevMode({ - required final Function(Function()) setState, required final BuildContext context, required final UserPreferences userPreferences, required final AppLocalizations appLocalizations, required final ThemeData themeData, }) : super( - setState: setState, context: context, userPreferences: userPreferences, appLocalizations: appLocalizations, @@ -57,7 +55,7 @@ class UserPreferencesDevMode extends AbstractUserPreferences { GlobalMaterialLocalizations.delegate; @override - PreferencePageType? getPreferencePageType() => PreferencePageType.DEV_MODE; + PreferencePageType getPreferencePageType() => PreferencePageType.DEV_MODE; @override String getTitleString() => appLocalizations.dev_preferences_screen_title; @@ -117,7 +115,6 @@ class UserPreferencesDevMode extends AbstractUserPreferences { onChanged: (bool? newValue) async { await userPreferences.setFlag(userPreferencesFlagProd, newValue); ProductQuery.setQueryType(userPreferences); - setState(() {}); }, items: const >[ DropdownMenuItem( @@ -329,7 +326,6 @@ class UserPreferencesDevMode extends AbstractUserPreferences { list.add(tag); } await userPreferences.setExcludedAttributeIds(list); - setState(() {}); }, ), ListTile( @@ -383,14 +379,11 @@ class UserPreferencesDevMode extends AbstractUserPreferences { } ScaffoldFeatureController - _showSuccessMessage() { - setState(() {}); - return ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(appLocalizations.dev_preferences_button_positive), - ), - ); - } + _showSuccessMessage() => ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(appLocalizations.dev_preferences_button_positive), + ), + ); Future _changeTestEnvHost() async { _textFieldController.text = @@ -415,7 +408,6 @@ class UserPreferencesDevMode extends AbstractUserPreferences { await userPreferences.setDevModeString( userPreferencesTestEnvHost, _textFieldController.text); ProductQuery.setQueryType(userPreferences); - setState(() {}); } } } diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_faq.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_faq.dart index 15121bd39ac..dc639c19e18 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_faq.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_faq.dart @@ -19,13 +19,11 @@ import 'package:smooth_app/query/product_query.dart'; /// Display of "FAQ" for the preferences page. class UserPreferencesFaq extends AbstractUserPreferences { UserPreferencesFaq({ - required final Function(Function()) setState, required final BuildContext context, required final UserPreferences userPreferences, required final AppLocalizations appLocalizations, required final ThemeData themeData, }) : super( - setState: setState, context: context, userPreferences: userPreferences, appLocalizations: appLocalizations, @@ -33,7 +31,7 @@ class UserPreferencesFaq extends AbstractUserPreferences { ); @override - PreferencePageType? getPreferencePageType() => PreferencePageType.FAQ; + PreferencePageType getPreferencePageType() => PreferencePageType.FAQ; @override String getTitleString() => appLocalizations.faq; diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_food.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_food.dart index 9f6d774e0b6..203d3ac0053 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_food.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_food.dart @@ -16,13 +16,11 @@ import 'package:smooth_app/widgets/smooth_text.dart'; class UserPreferencesFood extends AbstractUserPreferences { UserPreferencesFood({ required this.productPreferences, - required final Function(Function()) setState, required final BuildContext context, required final UserPreferences userPreferences, required final AppLocalizations appLocalizations, required final ThemeData themeData, }) : super( - setState: setState, context: context, userPreferences: userPreferences, appLocalizations: appLocalizations, @@ -41,7 +39,7 @@ class UserPreferencesFood extends AbstractUserPreferences { ]; @override - PreferencePageType? getPreferencePageType() => PreferencePageType.FOOD; + PreferencePageType getPreferencePageType() => PreferencePageType.FOOD; @override String getTitleString() => appLocalizations.myPreferences_food_title; @@ -131,18 +129,16 @@ class UserPreferencesFood extends AbstractUserPreferences { ), ]; for (final AttributeGroup group in groups) { - final AbstractUserPreferences abstractUserPreferences = - UserPreferencesAttributeGroup( - productPreferences: productPreferences, - group: group, - setState: setState, - context: context, - userPreferences: userPreferences, - appLocalizations: appLocalizations, - themeData: themeData, + result.addAll( + UserPreferencesAttributeGroup( + productPreferences: productPreferences, + group: group, + context: context, + userPreferences: userPreferences, + appLocalizations: appLocalizations, + themeData: themeData, + ).getContent(), ); - - result.addAll(abstractUserPreferences.getContent()); } return result; } diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_haptic_feedback.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_haptic_feedback.dart new file mode 100644 index 00000000000..f7f49f2c954 --- /dev/null +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_haptic_feedback.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import 'package:smooth_app/data_models/preferences/user_preferences.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_widgets.dart'; + +class UserPreferencesHapticFeedback extends StatelessWidget { + const UserPreferencesHapticFeedback(); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final UserPreferences userPreferences = context.watch(); + return UserPreferencesSwitchItem( + title: appLocalizations.app_haptic_feedback_title, + subtitle: appLocalizations.app_haptic_feedback_subtitle, + value: userPreferences.hapticFeedbackEnabled, + onChanged: (final bool value) async => + userPreferences.setHapticFeedbackEnabled(value), + ); + } +} diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_image_source.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_image_source.dart new file mode 100644 index 00000000000..602759bbbdd --- /dev/null +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_image_source.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import 'package:smooth_app/data_models/preferences/user_preferences.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_widgets.dart'; + +class UserPreferencesImageSource extends StatelessWidget { + const UserPreferencesImageSource(); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final UserPreferences userPreferences = context.watch(); + return UserPreferencesMultipleChoicesItem( + title: appLocalizations.choose_image_source_title, + leadingBuilder: [ + (_) => const Icon(Icons.edit_note_rounded), + (_) => const Icon(Icons.camera), + (_) => const Icon(Icons.image), + ], + labels: [ + appLocalizations.user_picture_source_select, + appLocalizations.settings_app_camera, + appLocalizations.gallery_source_label, + ], + values: const [ + UserPictureSource.SELECT, + UserPictureSource.CAMERA, + UserPictureSource.GALLERY, + ], + currentValue: userPreferences.userPictureSource, + onChanged: (final UserPictureSource? newValue) async => + userPreferences.setUserPictureSource(newValue!), + ); + } +} diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_language_selector.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_language_selector.dart new file mode 100644 index 00000000000..99fd79c6c7e --- /dev/null +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_language_selector.dart @@ -0,0 +1,49 @@ +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/widgets/language_selector.dart'; +import 'package:smooth_app/query/product_query.dart'; + +class UserPreferencesLanguageSelector extends StatelessWidget { + const UserPreferencesLanguageSelector(); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final UserPreferences userPreferences = context.watch(); + return ListTile( + title: Text( + appLocalizations.language_picker_label, + style: Theme.of(context).textTheme.headlineMedium, + ), + subtitle: Padding( + padding: const EdgeInsetsDirectional.only( + top: SMALL_SPACE, + bottom: SMALL_SPACE, + ), + child: LanguageSelector( + setLanguage: (final OpenFoodFactsLanguage? language) async { + if (language != null) { + ProductQuery.setLanguage( + context, + userPreferences, + languageCode: language.code, + ); + } + }, + selectedLanguages: [ + ProductQuery.getLanguage(), + ], + icon: Icons.edit, + padding: const EdgeInsetsDirectional.only( + start: SMALL_SPACE, + ), + ), + ), + minVerticalPadding: MEDIUM_SPACE, + ); + } +} diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_page.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_page.dart index efdd2528399..a2cd5041df0 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_page.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_page.dart @@ -7,6 +7,7 @@ import 'package:matomo_tracker/matomo_tracker.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/data_models/preferences/user_preferences.dart'; import 'package:smooth_app/data_models/product_preferences.dart'; +import 'package:smooth_app/data_models/user_management_provider.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_back_button.dart'; import 'package:smooth_app/helpers/app_helper.dart'; @@ -108,7 +109,7 @@ class _UserPreferencesPageState extends State userPreferences: userPreferences, ); - children.addAll(abstractUserPreferences.getContent(withHeader: false)); + children.addAll(abstractUserPreferences.getBody()); appBarTitle = abstractUserPreferences.getTitleString(); addDividers = false; @@ -197,11 +198,11 @@ class _UserPreferencesPageState extends State final ThemeData themeData = Theme.of(context); final ProductPreferences productPreferences = context.watch(); + context.watch(); switch (type) { case PreferencePageType.ACCOUNT: return UserPreferencesAccount( - setState: setState, context: context, userPreferences: userPreferences, appLocalizations: appLocalizations, @@ -210,7 +211,6 @@ class _UserPreferencesPageState extends State case PreferencePageType.FOOD: return UserPreferencesFood( productPreferences: productPreferences, - setState: setState, context: context, userPreferences: userPreferences, appLocalizations: appLocalizations, @@ -219,7 +219,6 @@ class _UserPreferencesPageState extends State case PreferencePageType.SETTINGS: return UserPreferencesSettings( themeProvider: themeProvider, - setState: setState, context: context, userPreferences: userPreferences, appLocalizations: appLocalizations, @@ -227,7 +226,6 @@ class _UserPreferencesPageState extends State ); case PreferencePageType.DEV_MODE: return UserPreferencesDevMode( - setState: setState, context: context, userPreferences: userPreferences, appLocalizations: appLocalizations, @@ -235,7 +233,6 @@ class _UserPreferencesPageState extends State ); case PreferencePageType.CONTRIBUTE: return UserPreferencesContribute( - setState: setState, context: context, userPreferences: userPreferences, appLocalizations: appLocalizations, @@ -243,7 +240,6 @@ class _UserPreferencesPageState extends State ); case PreferencePageType.FAQ: return UserPreferencesFaq( - setState: setState, context: context, userPreferences: userPreferences, appLocalizations: appLocalizations, @@ -251,7 +247,6 @@ class _UserPreferencesPageState extends State ); case PreferencePageType.CONNECT: return UserPreferencesConnect( - setState: setState, context: context, userPreferences: userPreferences, appLocalizations: appLocalizations, diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_rate_us.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_rate_us.dart new file mode 100644 index 00000000000..c9502b344f9 --- /dev/null +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_rate_us.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:smooth_app/generic_lib/design_constants.dart'; +import 'package:smooth_app/helpers/entry_points_helper.dart'; +import 'package:smooth_app/helpers/global_vars.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_widgets.dart'; +import 'package:smooth_app/services/smooth_services.dart'; + +class UserPreferencesRateUs extends StatelessWidget { + const UserPreferencesRateUs(); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + + return UserPreferenceListTile( + title: appLocalizations.rate_app, + leading: SizedBox( + key: const Key('settings.rate_us'), + height: DEFAULT_ICON_SIZE, + width: DEFAULT_ICON_SIZE, + child: Image.asset(_getImagePath()), + ), + showDivider: true, + onTap: (BuildContext context) async { + try { + await ApplicationStore.openAppDetails(); + } on PlatformException { + final AppLocalizations appLocalizations = + AppLocalizations.of(context); + final ThemeData themeData = Theme.of(context); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + appLocalizations.error_occurred, + textAlign: TextAlign.center, + style: TextStyle(color: themeData.colorScheme.background), + ), + behavior: SnackBarBehavior.floating, + backgroundColor: themeData.colorScheme.onBackground, + ), + ); + } + }, + ); + } + + String _getImagePath() { + switch (GlobalVars.storeLabel) { + case StoreLabel.FDroid: + return 'assets/app/f-droid.png'; + case StoreLabel.AppleAppStore: + return 'assets/app/app-store.png'; + default: + return 'assets/app/playstore.png'; + } + } +} diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_send_anonymous.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_send_anonymous.dart new file mode 100644 index 00000000000..fba29329641 --- /dev/null +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_send_anonymous.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import 'package:smooth_app/data_models/preferences/user_preferences.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_widgets.dart'; + +class UserPreferencesSendAnonymous extends StatelessWidget { + const UserPreferencesSendAnonymous(); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final UserPreferences userPreferences = context.watch(); + return UserPreferencesSwitchItem( + title: appLocalizations.send_anonymous_data_toggle_title, + subtitle: appLocalizations.send_anonymous_data_toggle_subtitle, + value: userPreferences.userTracking, + onChanged: (final bool allow) async => + userPreferences.setUserTracking(allow), + ); + } +} 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 6a129e69b0d..6eb0733e806 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_settings.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_settings.dart @@ -1,40 +1,36 @@ -import 'package:app_settings/app_settings.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.dart'; -import 'package:share_plus/share_plus.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/widgets/language_selector.dart'; import 'package:smooth_app/helpers/camera_helper.dart'; -import 'package:smooth_app/helpers/entry_points_helper.dart'; -import 'package:smooth_app/helpers/global_vars.dart'; import 'package:smooth_app/knowledge_panel/knowledge_panels/knowledge_panel_card.dart'; -import 'package:smooth_app/pages/onboarding/country_selector.dart'; import 'package:smooth_app/pages/preferences/abstract_user_preferences.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_advanced_settings.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_camera_sound.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_choose_accent_color.dart'; +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_crash_reporting.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_haptic_feedback.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_image_source.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_language_selector.dart'; import 'package:smooth_app/pages/preferences/user_preferences_page.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_rate_us.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_send_anonymous.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_share_with_friends.dart'; import 'package:smooth_app/pages/preferences/user_preferences_widgets.dart'; -import 'package:smooth_app/query/product_query.dart'; -import 'package:smooth_app/services/smooth_services.dart'; -import 'package:smooth_app/themes/color_provider.dart'; -import 'package:smooth_app/themes/color_schemes.dart'; -import 'package:smooth_app/themes/contrast_provider.dart'; import 'package:smooth_app/themes/theme_provider.dart'; /// Collapsed/expanded display of settings for the preferences page. class UserPreferencesSettings extends AbstractUserPreferences { UserPreferencesSettings({ - required final Function(Function()) setState, required final BuildContext context, required final UserPreferences userPreferences, required final AppLocalizations appLocalizations, required final ThemeData themeData, required this.themeProvider, }) : super( - setState: setState, context: context, userPreferences: userPreferences, appLocalizations: appLocalizations, @@ -44,7 +40,7 @@ class UserPreferencesSettings extends AbstractUserPreferences { final ThemeProvider themeProvider; @override - PreferencePageType? getPreferencePageType() => PreferencePageType.SETTINGS; + PreferencePageType getPreferencePageType() => PreferencePageType.SETTINGS; @override String getTitleString() => appLocalizations.myPreferences_settings_title; @@ -57,485 +53,26 @@ class UserPreferencesSettings extends AbstractUserPreferences { IconData getLeadingIconData() => Icons.handyman; @override - List getBody() => const [ - _ApplicationSettings(), - _CameraSettings(), - _ProductsSettings(), - _MiscellaneousSettings(), - _PrivacySettings(), - _RateUs(), - _ShareWithFriends(), - ]; -} - -class _RateUs extends StatelessWidget { - const _RateUs(); - - Future _redirect(BuildContext context) async { - try { - await ApplicationStore.openAppDetails(); - } on PlatformException { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - final ThemeData themeData = Theme.of(context); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - appLocalizations.error_occurred, - textAlign: TextAlign.center, - style: TextStyle(color: themeData.colorScheme.background), - ), - behavior: SnackBarBehavior.floating, - backgroundColor: themeData.colorScheme.onBackground, - ), - ); - } - } - - String getImagePath() { - String imagePath = ''; - switch (GlobalVars.storeLabel) { - case StoreLabel.FDroid: - imagePath = 'assets/app/f-droid.png'; - break; - case StoreLabel.AppleAppStore: - imagePath = 'assets/app/app-store.png'; - break; - default: - imagePath = 'assets/app/playstore.png'; - } - return imagePath; - } - - @override - Widget build(BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - - final Widget leading = SizedBox( - key: const Key('settings.rate_us'), - height: DEFAULT_ICON_SIZE, - width: DEFAULT_ICON_SIZE, - child: Image.asset(getImagePath()), - ); - - final String title = appLocalizations.rate_app; - - return UserPreferenceListTile( - title: title, - leading: leading, - onTap: _redirect, - showDivider: true, - ); - } -} - -class _ShareWithFriends extends StatelessWidget { - const _ShareWithFriends(); - - Future _shareApp(BuildContext context) async { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - final ThemeData themeData = Theme.of(context); - try { - await Share.share(appLocalizations.contribute_share_content); - } on PlatformException { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - appLocalizations.error, - textAlign: TextAlign.center, - style: TextStyle( - color: themeData.colorScheme.background, - ), - ), - behavior: SnackBarBehavior.floating, - backgroundColor: themeData.colorScheme.onBackground, - ), - ); - } - } - - @override - Widget build(BuildContext context) { - final Widget leading = Icon( - key: const Key('settings.share_app'), - Icons.adaptive.share, - ); - - final String title = AppLocalizations.of(context).contribute_share_header; - - return UserPreferenceListTile( - title: title, - leading: leading, - onTap: _shareApp, - showDivider: false, - ); - } -} - -class _ApplicationSettings extends StatelessWidget { - const _ApplicationSettings({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - final ThemeData themeData = Theme.of(context); - final UserPreferences userPreferences = context.watch(); - - return Column( - children: [ + List getBody() => [ UserPreferencesTitle.firstItem( label: appLocalizations.settings_app_app, ), - const _ChooseAppTheme(), + const UserPreferencesChooseAppTheme(), + if (themeProvider.currentTheme == THEME_AMOLED) + const UserPreferencesChooseAccentColor(), + if (themeProvider.currentTheme == THEME_AMOLED) + const UserPreferencesChooseTextColorContrast(), const UserPreferencesListItemDivider(), - ListTile( - title: Text( - appLocalizations.country_chooser_label, - style: themeData.textTheme.headlineMedium, - ), - subtitle: Padding( - padding: const EdgeInsetsDirectional.only( - top: SMALL_SPACE, - bottom: SMALL_SPACE, - ), - child: CountrySelector( - textStyle: themeData.textTheme.bodyMedium, - icon: Icons.edit, - padding: const EdgeInsetsDirectional.only( - start: SMALL_SPACE, - ), - ), - ), - minVerticalPadding: MEDIUM_SPACE, - ), + const UserPreferencesCountrySelector(), const UserPreferencesListItemDivider(), - ListTile( - title: Text( - appLocalizations.language_picker_label, - style: themeData.textTheme.headlineMedium, - ), - subtitle: Padding( - padding: const EdgeInsetsDirectional.only( - top: SMALL_SPACE, - bottom: SMALL_SPACE, - ), - child: LanguageSelector( - setLanguage: (final OpenFoodFactsLanguage? language) async { - if (language != null) { - ProductQuery.setLanguage( - context, - userPreferences, - languageCode: language.code, - ); - } - }, - selectedLanguages: [ - ProductQuery.getLanguage(), - ], - icon: Icons.edit, - padding: const EdgeInsetsDirectional.only( - start: SMALL_SPACE, - ), - ), - ), - minVerticalPadding: MEDIUM_SPACE, - ), - const UserPreferencesListItemDivider(), - UserPreferencesMultipleChoicesItem( - title: appLocalizations.choose_image_source_title, - leadingBuilder: [ - (_) => const Icon(Icons.edit_note_rounded), - (_) => const Icon(Icons.camera), - (_) => const Icon(Icons.image), - ], - labels: [ - appLocalizations.user_picture_source_select, - appLocalizations.settings_app_camera, - appLocalizations.gallery_source_label, - ], - values: const [ - UserPictureSource.SELECT, - UserPictureSource.CAMERA, - UserPictureSource.GALLERY, - ], - currentValue: userPreferences.userPictureSource, - onChanged: (final UserPictureSource? newValue) async => - userPreferences.setUserPictureSource(newValue!), - ), - ], - ); - } -} - -class _ChooseAppTheme extends StatelessWidget { - const _ChooseAppTheme(); - - @override - Widget build(BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - final ThemeProvider themeProvider = context.watch(); - - final Widget child = UserPreferencesMultipleChoicesItem( - title: appLocalizations.darkmode, - leadingBuilder: [ - (_) => const Icon(Icons.brightness_medium), - (_) => const Icon(Icons.light_mode), - (_) => const Icon(Icons.dark_mode_outlined), - (_) => const Icon(Icons.dark_mode), - ], - labels: [ - appLocalizations.darkmode_system_default, - appLocalizations.darkmode_light, - appLocalizations.darkmode_dark, - appLocalizations.theme_amoled, - ], - values: const [ - THEME_SYSTEM_DEFAULT, - THEME_LIGHT, - THEME_DARK, - THEME_AMOLED, - ], - currentValue: themeProvider.currentTheme, - onChanged: (String? newValue) { - themeProvider.setTheme(newValue!); - }, - ); - - if (themeProvider.currentTheme == THEME_AMOLED) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - child, - const _ChooseAccentColor(), - const _ChooseTextColorContrast(), - ], - ); - } else { - return child; - } - } -} - -class _ChooseAccentColor extends StatelessWidget { - const _ChooseAccentColor(); - - @override - Widget build(BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - final ColorProvider colorProvider = context.watch(); - final Map labels = localizedNames(appLocalizations); - - return UserPreferencesMultipleChoicesItem( - title: appLocalizations.select_accent_color, - leadingBuilder: labels.keys.map( - (String key) => (_) => CircleAvatar( - backgroundColor: getColorValue(key), - radius: SMALL_SPACE, - ), - ), - labels: labels.values, - values: labels.keys, - currentValue: colorProvider.currentColor, - onChanged: (String? newValue) { - colorProvider.setColor(newValue!); - }, - ); - } - - Map localizedNames(AppLocalizations appLocalizations) => - { - 'Blue': appLocalizations.color_blue, - 'Cyan': appLocalizations.color_cyan, - 'Green': appLocalizations.color_green, - 'Default': appLocalizations.color_light_brown, - 'Magenta': appLocalizations.color_magenta, - 'Orange': appLocalizations.color_orange, - 'Pink': appLocalizations.color_pink, - 'Red': appLocalizations.color_red, - 'Rust': appLocalizations.color_rust, - 'Teal': appLocalizations.color_teal, - }; -} - -class _ChooseTextColorContrast extends StatelessWidget { - const _ChooseTextColorContrast(); - - @override - Widget build(BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - final TextContrastProvider textContrastProvider = - context.watch(); - - return UserPreferencesMultipleChoicesItem( - title: appLocalizations.text_contrast_mode, - values: const [ - CONTRAST_HIGH, - CONTRAST_MEDIUM, - CONTRAST_LOW, - ], - labels: [ - appLocalizations.contrast_high, - appLocalizations.contrast_medium, - appLocalizations.contrast_low, - ], - currentValue: textContrastProvider.currentContrastLevel, - onChanged: (String? contrast) => - textContrastProvider.setContrast(contrast!), - ); - } -} - -class _PrivacySettings extends StatelessWidget { - const _PrivacySettings({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - UserPreferencesTitle( - label: appLocalizations.settings_app_data, - ), - const _CrashReportingSetting(), - const UserPreferencesListItemDivider(), - const _SendAnonymousDataSetting(), - const UserPreferencesListItemDivider(), - const _AdvancedSettings(), + const UserPreferencesLanguageSelector(), const UserPreferencesListItemDivider(), - ], - ); - } -} - -class _AdvancedSettings extends StatelessWidget { - const _AdvancedSettings({ - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: UserPreferenceListTile( - onTap: (_) async { - await AppSettings.openAppSettings(); - }, - title: appLocalizations.native_app_settings, - subTitle: appLocalizations.native_app_description, - leading: const Icon(CupertinoIcons.settings_solid), - showDivider: true, + const UserPreferencesImageSource(), + if (CameraHelper.hasACamera) + UserPreferencesTitle( + label: appLocalizations.settings_app_camera, ), - ), - ], - ); - } -} - -class _SendAnonymousDataSetting extends StatefulWidget { - const _SendAnonymousDataSetting({Key? key}) : super(key: key); - - @override - State<_SendAnonymousDataSetting> createState() => - _SendAnonymousDataSettingState(); -} - -class _SendAnonymousDataSettingState extends State<_SendAnonymousDataSetting> { - @override - Widget build(BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - final UserPreferences userPreferences = context.watch(); - - return UserPreferencesSwitchItem( - title: appLocalizations.send_anonymous_data_toggle_title, - subtitle: appLocalizations.send_anonymous_data_toggle_subtitle, - value: userPreferences.userTracking, - onChanged: (final bool allow) async { - await userPreferences.setUserTracking(allow); - }, - ); - } -} - -class _CrashReportingSetting extends StatelessWidget { - const _CrashReportingSetting({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - final UserPreferences userPreferences = context.watch(); - - return UserPreferencesSwitchItem( - title: appLocalizations.crash_reporting_toggle_title, - subtitle: appLocalizations.crash_reporting_toggle_subtitle, - value: userPreferences.crashReports, - onChanged: (final bool value) async { - await userPreferences.setCrashReports(value); - }, - ); - } -} - -class _CameraSettings extends StatelessWidget { - const _CameraSettings({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - if (!CameraHelper.hasACamera) { - return EMPTY_WIDGET; - } - - final AppLocalizations appLocalizations = AppLocalizations.of(context); - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - UserPreferencesTitle( - label: appLocalizations.settings_app_camera, - ), - const _CameraPlayScanSoundSetting(), - ], - ); - } -} - -class _CameraPlayScanSoundSetting extends StatelessWidget { - const _CameraPlayScanSoundSetting({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - final UserPreferences userPreferences = context.watch(); - - return UserPreferencesSwitchItem( - title: appLocalizations.camera_play_sound_title, - subtitle: appLocalizations.camera_play_sound_subtitle, - value: userPreferences.playCameraSound, - onChanged: (final bool value) async { - await userPreferences.setPlayCameraSound(value); - }, - ); - } -} - -class _ProductsSettings extends StatelessWidget { - const _ProductsSettings({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - if (!CameraHelper.hasACamera) { - return EMPTY_WIDGET; - } - - final AppLocalizations appLocalizations = AppLocalizations.of(context); - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ + if (CameraHelper.hasACamera) const UserPreferencesCameraSound(), UserPreferencesTitle( label: appLocalizations.settings_app_products, ), @@ -550,51 +87,21 @@ class _ProductsSettings extends StatelessWidget { subtitle: appLocalizations.expand_ingredients_body, panelId: KnowledgePanelCard.PANEL_INGREDIENTS_ID, ), - ], - ); - } -} - -class _MiscellaneousSettings extends StatelessWidget { - const _MiscellaneousSettings({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - if (!CameraHelper.hasACamera) { - return EMPTY_WIDGET; - } - - final AppLocalizations appLocalizations = AppLocalizations.of(context); - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - UserPreferencesTitle( - label: appLocalizations.settings_app_miscellaneous, - ), - const _HapticFeedbackSetting(), - ], - ); - } -} - -class _HapticFeedbackSetting extends StatelessWidget { - const _HapticFeedbackSetting(); - - @override - Widget build(BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - final UserPreferences userPreferences = context.watch(); - - return UserPreferencesSwitchItem( - title: appLocalizations.app_haptic_feedback_title, - subtitle: appLocalizations.app_haptic_feedback_subtitle, - value: userPreferences.hapticFeedbackEnabled, - onChanged: (final bool value) async { - await userPreferences.setHapticFeedbackEnabled(value); - }, - ); - } + if (CameraHelper.hasACamera) + UserPreferencesTitle( + label: appLocalizations.settings_app_miscellaneous, + ), + if (CameraHelper.hasACamera) const UserPreferencesHapticFeedback(), + UserPreferencesTitle(label: appLocalizations.settings_app_data), + const UserPreferencesCrashReporting(), + const UserPreferencesListItemDivider(), + const UserPreferencesSendAnonymous(), + const UserPreferencesListItemDivider(), + const UserPreferencesAdvancedSettings(), + const UserPreferencesListItemDivider(), + const UserPreferencesRateUs(), + const UserPreferencesShareWithFriends(), + ]; } class _ExpandPanelHelper extends StatelessWidget { diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_share_with_friends.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_share_with_friends.dart new file mode 100644 index 00000000000..060773d9ae1 --- /dev/null +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_share_with_friends.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_widgets.dart'; + +class UserPreferencesShareWithFriends extends StatelessWidget { + const UserPreferencesShareWithFriends(); + + @override + Widget build(BuildContext context) => UserPreferenceListTile( + title: AppLocalizations.of(context).contribute_share_header, + leading: Icon( + key: const Key('settings.share_app'), + Icons.adaptive.share, + ), + showDivider: false, + onTap: (final BuildContext context) async { + final AppLocalizations appLocalizations = + AppLocalizations.of(context); + final ThemeData themeData = Theme.of(context); + try { + await Share.share(appLocalizations.contribute_share_content); + } on PlatformException { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + appLocalizations.error, + textAlign: TextAlign.center, + style: TextStyle( + color: themeData.colorScheme.background, + ), + ), + behavior: SnackBarBehavior.floating, + backgroundColor: themeData.colorScheme.onBackground, + ), + ); + } + }, + ); +}