diff --git a/packages/smooth_app/lib/data_models/login_result.dart b/packages/smooth_app/lib/data_models/login_result.dart index d2cb0fb970f3..bf830fc7f640 100644 --- a/packages/smooth_app/lib/data_models/login_result.dart +++ b/packages/smooth_app/lib/data_models/login_result.dart @@ -9,11 +9,12 @@ enum LoginResultType { successful, unsuccessful, serverIssue, exception } /// Result of a log in attempt, more subtle than a `bool`. class LoginResult { - const LoginResult(this.type, {this.user, this.text}); + const LoginResult(this.type, {this.user, this.userDetails, this.text}); final LoginResultType type; final User? user; final String? text; + final UserDetails? userDetails; String getErrorMessage(final AppLocalizations appLocalizations) => switch (type) { @@ -62,6 +63,7 @@ class LoginResult { password: user.password, cookie: loginStatus.cookie, ), + userDetails: loginStatus.userDetails, ); } catch (e) { return LoginResult(LoginResultType.exception, text: e.toString()); diff --git a/packages/smooth_app/lib/data_models/user_management_provider.dart b/packages/smooth_app/lib/data_models/user_management_provider.dart index fbb93e9fa2fa..c1fa9562edbd 100644 --- a/packages/smooth_app/lib/data_models/user_management_provider.dart +++ b/packages/smooth_app/lib/data_models/user_management_provider.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -13,6 +14,9 @@ class UserManagementProvider with ChangeNotifier { static const String _USER_ID = 'user_id'; static const String _PASSWORD = 'pasword'; static const String _COOKIE = 'user_cookie'; + static const String _USER_DETAILS = 'user_details'; + + static UserDetails? globalUserDetails; /// Checks credentials and conditionally saves them. Future login( @@ -27,16 +31,31 @@ class UserManagementProvider with ChangeNotifier { return loginResult; } await putUser(loginResult.user!); + await _saveUserDetails(loginResult.userDetails); await credentialsInStorage(); return loginResult; } + /// Saves user details to storage + Future _saveUserDetails(UserDetails? userDetails) async { + globalUserDetails = userDetails; + if (userDetails != null) { + final String jsonString = jsonEncode(userDetails.toJson()); + await DaoSecuredString.put(key: _USER_DETAILS, value: jsonString); + } else { + DaoSecuredString.remove(key: _USER_DETAILS); + } + notifyListeners(); + } + /// Deletes saved credentials from storage Future logout() async { OpenFoodAPIConfiguration.globalUser = null; + globalUserDetails = null; DaoSecuredString.remove(key: _USER_ID); DaoSecuredString.remove(key: _PASSWORD); DaoSecuredString.remove(key: _COOKIE); + DaoSecuredString.remove(key: _USER_DETAILS); notifyListeners(); final bool contains = await credentialsInStorage(); return !contains; @@ -52,17 +71,20 @@ class UserManagementProvider with ChangeNotifier { String? effectiveUserId; String? effectivePassword; String? effectiveCookie; + String? userDetailsJson; try { effectiveUserId = userId ?? await DaoSecuredString.get(_USER_ID); effectivePassword = password ?? await DaoSecuredString.get(_PASSWORD); effectiveCookie = await DaoSecuredString.get(_COOKIE); + userDetailsJson = await DaoSecuredString.get(_USER_DETAILS); } on PlatformException { /// Decrypting the values can go wrong if, for example, the app was /// manually overwritten from an external apk. DaoSecuredString.remove(key: _USER_ID); DaoSecuredString.remove(key: _PASSWORD); DaoSecuredString.remove(key: _COOKIE); + DaoSecuredString.remove(key: _USER_DETAILS); Logs.e('Credentials query failed, you have been logged out'); } @@ -76,6 +98,17 @@ class UserManagementProvider with ChangeNotifier { cookie: effectiveCookie, ); OpenFoodAPIConfiguration.globalUser = user; + + // Restore complete UserDetails from JSON + if (userDetailsJson != null && userDetailsJson.isNotEmpty) { + try { + final Map json = jsonDecode(userDetailsJson); + globalUserDetails = UserDetails.fromJson(json); + } catch (e) { + Logs.e('Failed to parse UserDetails: $e'); + DaoSecuredString.remove(key: _USER_DETAILS); + } + } } /// Checks if any credentials exist in storage @@ -117,9 +150,10 @@ class UserManagementProvider with ChangeNotifier { return; } - /// Save the cookie if necessary + /// Save the cookie and user details if necessary if (user.cookie == null && loginResult.user?.cookie != null) { - putUser(loginResult.user!); + await putUser(loginResult.user!); + await _saveUserDetails(loginResult.userDetails); } } } diff --git a/packages/smooth_app/lib/generic_lib/widgets/app_bars/logged_in/logged_in_app_bar_header.dart b/packages/smooth_app/lib/generic_lib/widgets/app_bars/logged_in/logged_in_app_bar_header.dart index aa1538886032..8a9af3da146c 100644 --- a/packages/smooth_app/lib/generic_lib/widgets/app_bars/logged_in/logged_in_app_bar_header.dart +++ b/packages/smooth_app/lib/generic_lib/widgets/app_bars/logged_in/logged_in_app_bar_header.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.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/app_bars/app_bar_constanst.dart'; import 'package:smooth_app/l10n/app_localizations.dart'; @@ -16,12 +18,28 @@ class LoggedInAppBarHeader extends StatelessWidget { final String userId; + String _getGreeting(AppLocalizations appLocalizations, String name) { + final int hour = DateTime.now().hour; + if (hour < 12) { + return appLocalizations.greet_good_morning(name); + } else if (hour < 17) { + return appLocalizations.greet_good_afternoon(name); + } else if (hour < 21) { + return appLocalizations.greet_good_evening(name); + } else { + return appLocalizations.greet_good_night(name); + } + } + @override Widget build(BuildContext context) { final AppLocalizations appLocalizations = AppLocalizations.of(context); final SmoothColorsThemeExtension themeExtension = context .extension(); + final UserDetails? userDetails = UserManagementProvider.globalUserDetails; + final String name = userDetails?.name ?? userId; + return ConstrainedBox( constraints: const BoxConstraints(minHeight: PROFILE_PICTURE_SIZE), child: Row( @@ -33,8 +51,8 @@ class LoggedInAppBarHeader extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: VERY_SMALL_SPACE, children: [ - Text( - userId, + AutoSizeText( + _getGreeting(appLocalizations, name), style: TextStyle( color: themeExtension.secondaryNormal, fontSize: 18.0, diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb index 80ce78326b59..51bb2c17ca54 100644 --- a/packages/smooth_app/lib/l10n/app_en.arb +++ b/packages/smooth_app/lib/l10n/app_en.arb @@ -5753,5 +5753,41 @@ "type": "String" } } + }, + "greet_good_morning": "Good morning, {name}", + "@greet_good_morning": { + "description": "Short greeting shown in the app during the morning (for example, roughly 05:00–11:59).", + "placeholders": { + "name": { + "type": "String" + } + } + }, + "greet_good_afternoon": "Good afternoon, {name}", + "@greet_good_afternoon": { + "description": "Short greeting shown in the app during the afternoon (for example, roughly 12:00–17:59).", + "placeholders": { + "name": { + "type": "String" + } + } + }, + "greet_good_evening": "Good evening, {name}", + "@greet_good_evening": { + "description": "Short greeting shown in the app during the evening (for example, roughly 18:00–21:59.)", + "placeholders": { + "name": { + "type": "String" + } + } + }, + "greet_good_night": "Good night, {name}", + "@greet_good_night": { + "description": "Short greeting shown in the app late at night or before going to sleep.", + "placeholders": { + "name": { + "type": "String" + } + } } } \ No newline at end of file diff --git a/packages/smooth_app/lib/l10n/app_localizations.dart b/packages/smooth_app/lib/l10n/app_localizations.dart index 8e5ec15dc2d1..1cec12aee8b0 100644 --- a/packages/smooth_app/lib/l10n/app_localizations.dart +++ b/packages/smooth_app/lib/l10n/app_localizations.dart @@ -10839,6 +10839,30 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'{percent}%'** String percent_value(String percent); + + /// Short greeting shown in the app during the morning (for example, roughly 05:00–11:59). + /// + /// In en, this message translates to: + /// **'Good morning, {name}'** + String greet_good_morning(String name); + + /// Short greeting shown in the app during the afternoon (for example, roughly 12:00–17:59). + /// + /// In en, this message translates to: + /// **'Good afternoon, {name}'** + String greet_good_afternoon(String name); + + /// Short greeting shown in the app during the evening (for example, roughly 18:00–21:59.) + /// + /// In en, this message translates to: + /// **'Good evening, {name}'** + String greet_good_evening(String name); + + /// Short greeting shown in the app late at night or before going to sleep. + /// + /// In en, this message translates to: + /// **'Good night, {name}'** + String greet_good_night(String name); } class _AppLocalizationsDelegate