-
-
Notifications
You must be signed in to change notification settings - Fork 440
feat: display user's real name with time-based greeting in header. #7421
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 5 commits
98f0502
cf8562c
6f14253
d7fc6fd
4e11cb0
0f5995a
4b2d2d3
9154a73
e06c556
951d4e9
af7688a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<LoginResult> 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<void> _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<bool> 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; | ||
|
Comment on lines
55
to
61
|
||
|
|
@@ -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'); | ||
|
Comment on lines
84
to
88
|
||
| } | ||
|
|
||
|
|
@@ -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<String, dynamic> json = jsonDecode(userDetailsJson); | ||
| globalUserDetails = UserDetails.fromJson(json); | ||
| } catch (e) { | ||
| Logs.e('Failed to parse UserDetails: $e'); | ||
| DaoSecuredString.remove(key: _USER_DETAILS); | ||
| } | ||
|
Comment on lines
+108
to
+110
|
||
| } | ||
| } | ||
|
|
||
| /// 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); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) { | ||
| final int hour = DateTime.now().hour; | ||
| if (hour < 12) { | ||
| return appLocalizations.greet_good_morning; | ||
| } else if (hour < 17) { | ||
| return appLocalizations.greet_good_afternoon; | ||
| } else if (hour < 21) { | ||
| return appLocalizations.greet_good_evening; | ||
| } else { | ||
| return appLocalizations.greet_good_night; | ||
| } | ||
| } | ||
|
|
||
| @override | ||
| Widget build(BuildContext context) { | ||
| final AppLocalizations appLocalizations = AppLocalizations.of(context); | ||
| final SmoothColorsThemeExtension themeExtension = context | ||
| .extension<SmoothColorsThemeExtension>(); | ||
|
|
||
| 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: <Widget>[ | ||
| Text( | ||
| userId, | ||
| AutoSizeText( | ||
| '${_getGreeting(appLocalizations)} $name', | ||
| style: TextStyle( | ||
|
Comment on lines
+54
to
56
|
||
| color: themeExtension.secondaryNormal, | ||
| fontSize: 18.0, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DaoSecuredString.remove returns a Future, but this call isn’t awaited. That can leave user_details undeleted when subsequent logic runs (and also hides storage errors). Await the remove call (and consider propagating/handling the returned bool) before notifying listeners.