Skip to content

Commit

Permalink
Updated avatar color selection logic (bitwarden#2151)
Browse files Browse the repository at this point in the history
* updated avatar color selection logic

* tweaks

* more tweaks

* formatting
  • Loading branch information
mpbw2 authored Oct 26, 2022
1 parent 505426c commit 5deba15
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public AccountViewCellViewModel(AccountView accountView)
{
AccountView = accountView;
AvatarImageSource = ServiceContainer.Resolve<IAvatarImageSourcePool>("avatarImageSourcePool")
?.GetOrCreateAvatar(AccountView.Name, AccountView.Email);
?.GetOrCreateAvatar(AccountView.UserId, AccountView.Name, AccountView.Email);
}

public AccountView AccountView
Expand Down
39 changes: 21 additions & 18 deletions src/App/Controls/AvatarImageSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Bit.Core.Utilities;
using SkiaSharp;
using Xamarin.Forms;

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)
{
Expand All @@ -21,20 +23,21 @@ public override bool Equals(object obj)

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;
}
}

Expand All @@ -52,24 +55,24 @@ public AvatarImageSource(string name = null, string email = null)
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,
Expand All @@ -85,7 +88,7 @@ private Stream Draw()
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;
Expand All @@ -97,7 +100,7 @@ private Stream Draw()
IsAntialias = true,
Style = SKPaintStyle.Fill,
StrokeJoin = SKStrokeJoin.Miter,
Color = SKColor.Parse(bgColor.ToHex())
Color = SKColor.Parse(bgColor)
})
{
canvas.DrawCircle(midX, midY, radius, circlePaint);
Expand All @@ -108,7 +111,7 @@ private Stream Draw()
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
Color = SKColor.Parse(textColor.ToHex()),
Color = SKColor.Parse(textColor),
TextSize = textSize,
TextAlign = SKTextAlign.Center,
Typeface = typeface
Expand Down
8 changes: 4 additions & 4 deletions src/App/Controls/AvatarImageSourcePool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, AvatarImageSource> _cache = new ConcurrentDictionary<string, AvatarImageSource>();

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.
Expand Down
3 changes: 2 additions & 1 deletion src/App/Pages/BaseContentPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ protected async Task<AvatarImageSource> GetAvatarImageSourceAsync(bool useCurren
{
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();
}
Expand Down
32 changes: 32 additions & 0 deletions src/Core/Utilities/CoreHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
Expand Down Expand Up @@ -264,5 +265,36 @@ public static T Clone<T>(T obj)
{
return JsonConvert.DeserializeObject<T>(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;
}
}
}
5 changes: 3 additions & 2 deletions src/iOS.Core/Utilities/AccountSwitchingOverlayHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ public async Task<UIImage> CreateAvatarImageAsync()
{
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);
Expand Down

0 comments on commit 5deba15

Please sign in to comment.