[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
This commit is contained in:
Brandon Maharaj 2023-01-12 13:27:10 -05:00 committed by GitHub
parent 4f4953206e
commit 6102a0c115
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 43 additions and 10 deletions

View file

@ -14,7 +14,7 @@ namespace Bit.App.Controls
{ {
AccountView = accountView; AccountView = accountView;
AvatarImageSource = ServiceContainer.Resolve<IAvatarImageSourcePool>("avatarImageSourcePool") AvatarImageSource = ServiceContainer.Resolve<IAvatarImageSourcePool>("avatarImageSourcePool")
?.GetOrCreateAvatar(AccountView.UserId, AccountView.Name, AccountView.Email); ?.GetOrCreateAvatar(AccountView.UserId, AccountView.Name, AccountView.Email, AccountView.AvatarColor);
} }
public AccountView AccountView public AccountView AccountView

View file

@ -13,6 +13,7 @@ namespace Bit.App.Controls
{ {
private readonly string _text; private readonly string _text;
private readonly string _id; private readonly string _id;
private readonly string _color;
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
@ -23,7 +24,7 @@ namespace Bit.App.Controls
if (obj is AvatarImageSource avatar) 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); return base.Equals(obj);
@ -31,7 +32,7 @@ namespace Bit.App.Controls
public override int GetHashCode() => _id?.GetHashCode() ?? _text?.GetHashCode() ?? -1; 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; _id = userId;
_text = name; _text = name;
@ -39,6 +40,7 @@ namespace Bit.App.Controls
{ {
_text = email; _text = email;
} }
_color = color;
} }
public override Func<CancellationToken, Task<Stream>> Stream => GetStreamAsync; public override Func<CancellationToken, Task<Stream>> Stream => GetStreamAsync;
@ -71,7 +73,7 @@ namespace Bit.App.Controls
chars = upperCaseText = _text.ToUpper(); 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 textColor = CoreHelpers.TextColorFromBgColor(bgColor);
var size = 50; var size = 50;

View file

@ -5,19 +5,19 @@ namespace Bit.App.Controls
{ {
public interface IAvatarImageSourcePool 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 public class AvatarImageSourcePool : IAvatarImageSourcePool
{ {
private readonly ConcurrentDictionary<string, AvatarImageSource> _cache = new ConcurrentDictionary<string, AvatarImageSource>(); private readonly ConcurrentDictionary<string, AvatarImageSource> _cache = new ConcurrentDictionary<string, AvatarImageSource>();
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)) 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) 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. !_cache.TryGetValue(key, out avatar)) // If add fails another thread created the avatar in between the first try get and the try add.

View file

@ -129,8 +129,8 @@ namespace Bit.App.Pages
{ {
if (useCurrentActiveAccount) if (useCurrentActiveAccount)
{ {
return new AvatarImageSource(await _stateService.GetActiveUserIdAsync(), var user = await _stateService.GetActiveUserCustomDataAsync(a => (a?.Profile?.UserId, a?.Profile?.Name, a?.Profile?.Email, a?.Profile?.AvatarColor));
await _stateService.GetNameAsync(), await _stateService.GetEmailAsync()); return new AvatarImageSource(user.UserId, user.Name, user.Email, user.AvatarColor);
} }
return new AvatarImageSource(); return new AvatarImageSource();
} }

View file

@ -108,6 +108,10 @@ namespace Bit.App.Pages
else if (message.Command == "syncCompleted") else if (message.Command == "syncCompleted")
{ {
await Task.Delay(500); await Task.Delay(500);
if (_vm.MainPage)
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
}
Device.BeginInvokeOnMainThread(() => Device.BeginInvokeOnMainThread(() =>
{ {
IsBusy = false; IsBusy = false;

View file

@ -163,5 +163,9 @@ namespace Bit.Core.Abstractions
Task<bool> GetShouldConnectToWatchAsync(string userId = null); Task<bool> GetShouldConnectToWatchAsync(string userId = null);
Task SetShouldConnectToWatchAsync(bool shouldConnect, string userId = null); Task SetShouldConnectToWatchAsync(bool shouldConnect, string userId = null);
Task<bool> GetLastUserShouldConnectToWatchAsync(); Task<bool> GetLastUserShouldConnectToWatchAsync();
Task SetAvatarColorAsync(string value, string userId = null);
Task<string> GetAvatarColorAsync(string userId = null);
} }
} }

View file

@ -48,6 +48,7 @@ namespace Bit.Core.Models.Domain
KdfIterations = copy.KdfIterations; KdfIterations = copy.KdfIterations;
EmailVerified = copy.EmailVerified; EmailVerified = copy.EmailVerified;
HasPremiumPersonally = copy.HasPremiumPersonally; HasPremiumPersonally = copy.HasPremiumPersonally;
AvatarColor = copy.AvatarColor;
} }
public string UserId; public string UserId;
@ -55,6 +56,7 @@ namespace Bit.Core.Models.Domain
public string Name; public string Name;
public string Stamp; public string Stamp;
public string OrgIdentifier; public string OrgIdentifier;
public string AvatarColor;
public KdfType? KdfType; public KdfType? KdfType;
public int? KdfIterations; public int? KdfIterations;
public bool? EmailVerified; public bool? EmailVerified;

View file

@ -19,5 +19,6 @@ namespace Bit.Core.Models.Response
public bool ForcePasswordReset { get; set; } public bool ForcePasswordReset { get; set; }
public List<ProfileOrganizationResponse> Organizations { get; set; } public List<ProfileOrganizationResponse> Organizations { get; set; }
public bool UsesKeyConnector { get; set; } public bool UsesKeyConnector { get; set; }
public string AvatarColor { get; set; }
} }
} }

View file

@ -20,6 +20,7 @@ namespace Bit.Core.Models.View
UserId = a.Profile?.UserId; UserId = a.Profile?.UserId;
Email = a.Profile?.Email; Email = a.Profile?.Email;
Name = a.Profile?.Name; Name = a.Profile?.Name;
AvatarColor = a.Profile?.AvatarColor;
if (!string.IsNullOrWhiteSpace(a.Settings?.EnvironmentUrls?.WebVault)) if (!string.IsNullOrWhiteSpace(a.Settings?.EnvironmentUrls?.WebVault))
{ {
Hostname = CoreHelpers.GetHostname(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 Email { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Hostname { get; set; } public string Hostname { get; set; }
public string AvatarColor { get; set; }
} }
} }

View file

@ -1305,6 +1305,23 @@ namespace Bit.Core.Services
var key = Constants.PasswordlessLoginNotificationKey; var key = Constants.PasswordlessLoginNotificationKey;
await SetValueAsync(key, value, options); 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<string> GetAvatarColorAsync(string userId = null)
{
return (await GetAccountAsync(
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync())
))?.Profile?.AvatarColor;
}
// Helpers // Helpers
private async Task<T> GetValueAsync<T>(string key, StorageOptions options) private async Task<T> GetValueAsync<T>(string key, StorageOptions options)

View file

@ -335,6 +335,7 @@ namespace Bit.Core.Services
await _organizationService.ReplaceAsync(organizations); await _organizationService.ReplaceAsync(organizations);
await _stateService.SetEmailVerifiedAsync(response.EmailVerified); await _stateService.SetEmailVerifiedAsync(response.EmailVerified);
await _stateService.SetNameAsync(response.Name); await _stateService.SetNameAsync(response.Name);
await _stateService.SetAvatarColorAsync(response.AvatarColor);
await _keyConnectorService.SetUsesKeyConnector(response.UsesKeyConnector); await _keyConnectorService.SetUsesKeyConnector(response.UsesKeyConnector);
} }