From 6102a0c11578e90c4161aad1f4e6997610f3f2c5 Mon Sep 17 00:00:00 2001 From: Brandon Maharaj Date: Thu, 12 Jan 2023 13:27:10 -0500 Subject: [PATCH] [SG-912] Modify the mobile app to retrieve the user's avatar color (#2284) * [SG-912] Modify the mobile app to retrieve the user's avatar color (#2277) * work: baseline * fix: dont use profile for store * fiix: use userid in key * fix: lookup on AccountView list create * fix my own bad advice + tweaks * Autosync the updated translations (#2279) * fix my own bad advice + tweaks * fiix: use userid in key * [PS-1352] Fix ignore diacritics in search (#2044) * Fix ignore diacritics in search This change updates the search function to ignore diacritical marks in search results. Marks are stripped from both the search input and results. * Removed logs, added null or whitespace validation and improved formatting * [PS-2145] add rainsee browser series support (#2272) * fix: lookup on AccountView list create * Autosync the updated translations (#2279) * fix my own bad advice + tweaks * fix: single state grab is cool --- .../AccountViewCell/AccountViewCellViewModel.cs | 2 +- src/App/Controls/AvatarImageSource.cs | 8 +++++--- src/App/Controls/AvatarImageSourcePool.cs | 8 ++++---- src/App/Pages/BaseContentPage.cs | 4 ++-- .../Vault/GroupingsPage/GroupingsPage.xaml.cs | 4 ++++ src/Core/Abstractions/IStateService.cs | 4 ++++ src/Core/Models/Domain/Account.cs | 2 ++ src/Core/Models/Response/ProfileResponse.cs | 1 + src/Core/Models/View/AccountView.cs | 2 ++ src/Core/Services/StateService.cs | 17 +++++++++++++++++ src/Core/Services/SyncService.cs | 1 + 11 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/App/Controls/AccountViewCell/AccountViewCellViewModel.cs b/src/App/Controls/AccountViewCell/AccountViewCellViewModel.cs index 4f4aa6625..45e2f2455 100644 --- a/src/App/Controls/AccountViewCell/AccountViewCellViewModel.cs +++ b/src/App/Controls/AccountViewCell/AccountViewCellViewModel.cs @@ -14,7 +14,7 @@ namespace Bit.App.Controls { AccountView = accountView; AvatarImageSource = ServiceContainer.Resolve("avatarImageSourcePool") - ?.GetOrCreateAvatar(AccountView.UserId, AccountView.Name, AccountView.Email); + ?.GetOrCreateAvatar(AccountView.UserId, AccountView.Name, AccountView.Email, AccountView.AvatarColor); } public AccountView AccountView diff --git a/src/App/Controls/AvatarImageSource.cs b/src/App/Controls/AvatarImageSource.cs index 44d867378..38f36e309 100644 --- a/src/App/Controls/AvatarImageSource.cs +++ b/src/App/Controls/AvatarImageSource.cs @@ -13,6 +13,7 @@ namespace Bit.App.Controls { private readonly string _text; private readonly string _id; + private readonly string _color; public override bool Equals(object obj) { @@ -23,7 +24,7 @@ namespace Bit.App.Controls if (obj is AvatarImageSource avatar) { - return avatar._id == _id && avatar._text == _text; + return avatar._id == _id && avatar._text == _text && avatar._color == _color; } return base.Equals(obj); @@ -31,7 +32,7 @@ namespace Bit.App.Controls public override int GetHashCode() => _id?.GetHashCode() ?? _text?.GetHashCode() ?? -1; - public AvatarImageSource(string userId = null, string name = null, string email = null) + public AvatarImageSource(string userId = null, string name = null, string email = null, string color = null) { _id = userId; _text = name; @@ -39,6 +40,7 @@ namespace Bit.App.Controls { _text = email; } + _color = color; } public override Func> Stream => GetStreamAsync; @@ -71,7 +73,7 @@ namespace Bit.App.Controls chars = upperCaseText = _text.ToUpper(); } - var bgColor = CoreHelpers.StringToColor(_id ?? upperCaseText, "#33ffffff"); + var bgColor = _color ?? CoreHelpers.StringToColor(_id ?? upperCaseText, "#33ffffff"); var textColor = CoreHelpers.TextColorFromBgColor(bgColor); var size = 50; diff --git a/src/App/Controls/AvatarImageSourcePool.cs b/src/App/Controls/AvatarImageSourcePool.cs index 8b80eeac7..56fcbe886 100644 --- a/src/App/Controls/AvatarImageSourcePool.cs +++ b/src/App/Controls/AvatarImageSourcePool.cs @@ -5,19 +5,19 @@ namespace Bit.App.Controls { public interface IAvatarImageSourcePool { - AvatarImageSource GetOrCreateAvatar(string userId, string name, string email); + AvatarImageSource GetOrCreateAvatar(string userId, string name, string email, string color); } public class AvatarImageSourcePool : IAvatarImageSourcePool { private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); - public AvatarImageSource GetOrCreateAvatar(string userId, string name, string email) + public AvatarImageSource GetOrCreateAvatar(string userId, string name, string email, string color) { - var key = $"{userId}{name}{email}"; + var key = $"{userId}{name}{email}{color}"; if (!_cache.TryGetValue(key, out var avatar)) { - avatar = new AvatarImageSource(userId, name, email); + avatar = new AvatarImageSource(userId, name, email, color); if (!_cache.TryAdd(key, avatar) && !_cache.TryGetValue(key, out avatar)) // If add fails another thread created the avatar in between the first try get and the try add. diff --git a/src/App/Pages/BaseContentPage.cs b/src/App/Pages/BaseContentPage.cs index 4ee8e3029..fc4e502ac 100644 --- a/src/App/Pages/BaseContentPage.cs +++ b/src/App/Pages/BaseContentPage.cs @@ -129,8 +129,8 @@ namespace Bit.App.Pages { if (useCurrentActiveAccount) { - return new AvatarImageSource(await _stateService.GetActiveUserIdAsync(), - await _stateService.GetNameAsync(), await _stateService.GetEmailAsync()); + var user = await _stateService.GetActiveUserCustomDataAsync(a => (a?.Profile?.UserId, a?.Profile?.Name, a?.Profile?.Email, a?.Profile?.AvatarColor)); + return new AvatarImageSource(user.UserId, user.Name, user.Email, user.AvatarColor); } return new AvatarImageSource(); } diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs index 0ec1cb374..501678f5c 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs @@ -108,6 +108,10 @@ namespace Bit.App.Pages else if (message.Command == "syncCompleted") { await Task.Delay(500); + if (_vm.MainPage) + { + _vm.AvatarImageSource = await GetAvatarImageSourceAsync(); + } Device.BeginInvokeOnMainThread(() => { IsBusy = false; diff --git a/src/Core/Abstractions/IStateService.cs b/src/Core/Abstractions/IStateService.cs index f849c8178..86e8c404b 100644 --- a/src/Core/Abstractions/IStateService.cs +++ b/src/Core/Abstractions/IStateService.cs @@ -163,5 +163,9 @@ namespace Bit.Core.Abstractions Task GetShouldConnectToWatchAsync(string userId = null); Task SetShouldConnectToWatchAsync(bool shouldConnect, string userId = null); Task GetLastUserShouldConnectToWatchAsync(); + Task SetAvatarColorAsync(string value, string userId = null); + Task GetAvatarColorAsync(string userId = null); + + } } diff --git a/src/Core/Models/Domain/Account.cs b/src/Core/Models/Domain/Account.cs index 75a493ccc..ed0b4cc39 100644 --- a/src/Core/Models/Domain/Account.cs +++ b/src/Core/Models/Domain/Account.cs @@ -48,6 +48,7 @@ namespace Bit.Core.Models.Domain KdfIterations = copy.KdfIterations; EmailVerified = copy.EmailVerified; HasPremiumPersonally = copy.HasPremiumPersonally; + AvatarColor = copy.AvatarColor; } public string UserId; @@ -55,6 +56,7 @@ namespace Bit.Core.Models.Domain public string Name; public string Stamp; public string OrgIdentifier; + public string AvatarColor; public KdfType? KdfType; public int? KdfIterations; public bool? EmailVerified; diff --git a/src/Core/Models/Response/ProfileResponse.cs b/src/Core/Models/Response/ProfileResponse.cs index a6ff2297b..9abc4584e 100644 --- a/src/Core/Models/Response/ProfileResponse.cs +++ b/src/Core/Models/Response/ProfileResponse.cs @@ -19,5 +19,6 @@ namespace Bit.Core.Models.Response public bool ForcePasswordReset { get; set; } public List Organizations { get; set; } public bool UsesKeyConnector { get; set; } + public string AvatarColor { get; set; } } } diff --git a/src/Core/Models/View/AccountView.cs b/src/Core/Models/View/AccountView.cs index e89afc511..7224d51bd 100644 --- a/src/Core/Models/View/AccountView.cs +++ b/src/Core/Models/View/AccountView.cs @@ -20,6 +20,7 @@ namespace Bit.Core.Models.View UserId = a.Profile?.UserId; Email = a.Profile?.Email; Name = a.Profile?.Name; + AvatarColor = a.Profile?.AvatarColor; if (!string.IsNullOrWhiteSpace(a.Settings?.EnvironmentUrls?.WebVault)) { Hostname = CoreHelpers.GetHostname(a.Settings?.EnvironmentUrls?.WebVault); @@ -37,5 +38,6 @@ namespace Bit.Core.Models.View public string Email { get; set; } public string Name { get; set; } public string Hostname { get; set; } + public string AvatarColor { get; set; } } } diff --git a/src/Core/Services/StateService.cs b/src/Core/Services/StateService.cs index 1390cf351..7cd1716d6 100644 --- a/src/Core/Services/StateService.cs +++ b/src/Core/Services/StateService.cs @@ -1305,6 +1305,23 @@ namespace Bit.Core.Services var key = Constants.PasswordlessLoginNotificationKey; await SetValueAsync(key, value, options); } + + public async Task SetAvatarColorAsync(string value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Profile.AvatarColor = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetAvatarColorAsync(string userId = null) + { + return (await GetAccountAsync( + ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) + ))?.Profile?.AvatarColor; + } + // Helpers private async Task GetValueAsync(string key, StorageOptions options) diff --git a/src/Core/Services/SyncService.cs b/src/Core/Services/SyncService.cs index 7eeaeda31..db24b5338 100644 --- a/src/Core/Services/SyncService.cs +++ b/src/Core/Services/SyncService.cs @@ -335,6 +335,7 @@ namespace Bit.Core.Services await _organizationService.ReplaceAsync(organizations); await _stateService.SetEmailVerifiedAsync(response.EmailVerified); await _stateService.SetNameAsync(response.Name); + await _stateService.SetAvatarColorAsync(response.AvatarColor); await _keyConnectorService.SetUsesKeyConnector(response.UsesKeyConnector); }