diff --git a/src/App/Controls/AccountViewCell/AccountViewCellViewModel.cs b/src/App/Controls/AccountViewCell/AccountViewCellViewModel.cs index e99b80f44..4f4aa6625 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.Name, AccountView.Email); + ?.GetOrCreateAvatar(AccountView.UserId, AccountView.Name, AccountView.Email); } public AccountView AccountView diff --git a/src/App/Controls/AvatarImageSource.cs b/src/App/Controls/AvatarImageSource.cs index 38f3df31c..068e5d570 100644 --- a/src/App/Controls/AvatarImageSource.cs +++ b/src/App/Controls/AvatarImageSource.cs @@ -3,6 +3,7 @@ using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; +using Bit.Core.Utilities; using SkiaSharp; using Xamarin.Forms; @@ -10,7 +11,8 @@ namespace Bit.App.Controls { public class AvatarImageSource : StreamImageSource { - private string _data; + private readonly string _text; + private readonly string _id; public override bool Equals(object obj) { @@ -21,20 +23,21 @@ namespace Bit.App.Controls if (obj is AvatarImageSource avatar) { - return avatar._data == _data; + return avatar._text == _text; } return base.Equals(obj); } - public override int GetHashCode() => _data?.GetHashCode() ?? -1; + public override int GetHashCode() => _text?.GetHashCode() ?? -1; - public AvatarImageSource(string name = null, string email = null) + public AvatarImageSource(string userId = null, string name = null, string email = null) { - _data = name; - if (string.IsNullOrWhiteSpace(_data)) + _id = userId; + _text = name; + if (string.IsNullOrWhiteSpace(_text)) { - _data = email; + _text = email; } } @@ -52,24 +55,24 @@ namespace Bit.App.Controls private Stream Draw() { string chars; - string upperData = null; + string upperCaseText = null; - if (string.IsNullOrEmpty(_data)) + if (string.IsNullOrEmpty(_text)) { chars = ".."; } - else if (_data?.Length > 1) + else if (_text?.Length > 1) { - upperData = _data.ToUpper(); - chars = GetFirstLetters(upperData, 2); + upperCaseText = _text.ToUpper(); + chars = GetFirstLetters(upperCaseText, 2); } else { - chars = upperData = _data.ToUpper(); + chars = upperCaseText = _text.ToUpper(); } - var bgColor = StringToColor(upperData); - var textColor = Color.White; + var bgColor = CoreHelpers.StringToColor(_id ?? upperCaseText, "#33ffffff"); + var textColor = CoreHelpers.TextColorFromBgColor(bgColor); var size = 50; using (var bitmap = new SKBitmap(size * 2, @@ -85,7 +88,7 @@ namespace Bit.App.Controls IsAntialias = true, Style = SKPaintStyle.Fill, StrokeJoin = SKStrokeJoin.Miter, - Color = SKColor.Parse(bgColor.ToHex()) + Color = SKColor.Parse(bgColor) }) { var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2; @@ -97,7 +100,7 @@ namespace Bit.App.Controls IsAntialias = true, Style = SKPaintStyle.Fill, StrokeJoin = SKStrokeJoin.Miter, - Color = SKColor.Parse(bgColor.ToHex()) + Color = SKColor.Parse(bgColor) }) { canvas.DrawCircle(midX, midY, radius, circlePaint); @@ -108,7 +111,7 @@ namespace Bit.App.Controls { IsAntialias = true, Style = SKPaintStyle.Fill, - Color = SKColor.Parse(textColor.ToHex()), + Color = SKColor.Parse(textColor), TextSize = textSize, TextAlign = SKTextAlign.Center, Typeface = typeface diff --git a/src/App/Controls/AvatarImageSourcePool.cs b/src/App/Controls/AvatarImageSourcePool.cs index 53e463e80..8b80eeac7 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 name, string email); + AvatarImageSource GetOrCreateAvatar(string userId, string name, string email); } public class AvatarImageSourcePool : IAvatarImageSourcePool { private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); - public AvatarImageSource GetOrCreateAvatar(string name, string email) + public AvatarImageSource GetOrCreateAvatar(string userId, string name, string email) { - var key = $"{name}{email}"; + var key = $"{userId}{name}{email}"; if (!_cache.TryGetValue(key, out var avatar)) { - avatar = new AvatarImageSource(name, email); + avatar = new AvatarImageSource(userId, name, email); 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 c6e5b6faf..4ee8e3029 100644 --- a/src/App/Pages/BaseContentPage.cs +++ b/src/App/Pages/BaseContentPage.cs @@ -129,7 +129,8 @@ namespace Bit.App.Pages { if (useCurrentActiveAccount) { - return new AvatarImageSource(await _stateService.GetNameAsync(), await _stateService.GetEmailAsync()); + return new AvatarImageSource(await _stateService.GetActiveUserIdAsync(), + await _stateService.GetNameAsync(), await _stateService.GetEmailAsync()); } return new AvatarImageSource(); } diff --git a/src/Core/Utilities/CoreHelpers.cs b/src/Core/Utilities/CoreHelpers.cs index 027b7a757..0ae2c559c 100644 --- a/src/Core/Utilities/CoreHelpers.cs +++ b/src/Core/Utilities/CoreHelpers.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Text.RegularExpressions; using System.Web; @@ -264,5 +265,36 @@ namespace Bit.Core.Utilities { return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj)); } + + public static string TextColorFromBgColor(string hexColor, int threshold = 166) + { + if (new ColorConverter().ConvertFromString(hexColor) is Color bgColor) + { + var luminance = bgColor.R * 0.299 + bgColor.G * 0.587 + bgColor.B * 0.114; + return luminance > threshold ? "#ff000000" : "#ffffffff"; + } + + return "#ff000000"; + } + + public static string StringToColor(string str, string fallback) + { + if (str == null) + { + return fallback; + } + var hash = 0; + for (var i = 0; i < str.Length; i++) + { + hash = str[i] + ((hash << 5) - hash); + } + var color = "#FF"; + for (var i = 0; i < 3; i++) + { + var value = (hash >> (i * 8)) & 0xff; + color += Convert.ToString(value, 16).PadLeft(2, '0'); + } + return color; + } } } diff --git a/src/iOS.Core/Utilities/AccountSwitchingOverlayHelper.cs b/src/iOS.Core/Utilities/AccountSwitchingOverlayHelper.cs index a732edaaf..e4e635e14 100644 --- a/src/iOS.Core/Utilities/AccountSwitchingOverlayHelper.cs +++ b/src/iOS.Core/Utilities/AccountSwitchingOverlayHelper.cs @@ -32,8 +32,9 @@ namespace Bit.iOS.Core.Utilities { throw new NullReferenceException(nameof(_stateService)); } - - var avatarImageSource = new AvatarImageSource(await _stateService.GetNameAsync(), await _stateService.GetEmailAsync()); + + var avatarImageSource = new AvatarImageSource(await _stateService.GetActiveUserIdAsync(), + await _stateService.GetNameAsync(), await _stateService.GetEmailAsync()); using (var avatarUIImage = await avatarImageSource.GetNativeImageAsync()) { return avatarUIImage?.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal) ?? UIImage.GetSystemImage(DEFAULT_SYSTEM_AVATAR_IMAGE);