Contacts management.

This commit is contained in:
Burak Kaan Köse
2026-03-01 21:07:10 +01:00
parent bdd32786d6
commit e816e87f61
19 changed files with 855 additions and 32 deletions
@@ -23,7 +23,7 @@ namespace Wino.Controls;
/// </summary>
public sealed partial class ImagePreviewControl : PersonPicture
{
private sealed record RefreshSnapshot(string DisplayName, string Address, string Base64Picture);
private sealed record RefreshSnapshot(string DisplayName, string Address, Guid? ContactPictureFileId, string Base64Picture);
private static readonly TimeSpan RefreshDebounceDuration = TimeSpan.FromMilliseconds(40);
@@ -32,6 +32,7 @@ public sealed partial class ImagePreviewControl : PersonPicture
private readonly IThumbnailService? _thumbnailService;
private readonly IPreferencesService? _preferencesService;
private readonly IContactPictureFileService? _contactPictureFileService;
private INotifyPropertyChanged? _mailItemInformationPropertySource;
private CancellationTokenSource? _refreshCancellationTokenSource;
private CancellationTokenSource? _scheduledRefreshCancellationTokenSource;
@@ -45,6 +46,7 @@ public sealed partial class ImagePreviewControl : PersonPicture
{
_thumbnailService = App.Current.Services.GetService<IThumbnailService>();
_preferencesService = App.Current.Services.GetService<IPreferencesService>();
_contactPictureFileService = App.Current.Services.GetService<IContactPictureFileService>();
}
catch
{
@@ -187,7 +189,26 @@ public sealed partial class ImagePreviewControl : PersonPicture
await ApplyInitialVisualStateAsync(snapshot.DisplayName, refreshVersion, cancellationToken).ConfigureAwait(false);
// 1) Explicit contact picture.
// Skip all picture loading if the user has disabled sender pictures.
if (_preferencesService?.IsShowSenderPicturesEnabled == false)
return;
// 1) File-based contact picture (preferred — native WIC decode, no base64 overhead).
if (snapshot.ContactPictureFileId.HasValue && _contactPictureFileService != null)
{
var filePath = _contactPictureFileService.GetContactPicturePath(snapshot.ContactPictureFileId.Value);
if (!string.IsNullOrEmpty(filePath))
{
var fileBitmap = await CreateBitmapFromFileAsync(filePath, cancellationToken).ConfigureAwait(false);
if (fileBitmap != null)
{
await ApplyProfilePictureAsync(fileBitmap, refreshVersion, cancellationToken).ConfigureAwait(false);
return;
}
}
}
// 2) Legacy base64 contact picture (used until migration completes or for fallback).
if (!string.IsNullOrWhiteSpace(snapshot.Base64Picture))
{
var localBitmap = await CreateBitmapFromBase64Async(snapshot.Base64Picture, cancellationToken).ConfigureAwait(false);
@@ -198,7 +219,7 @@ public sealed partial class ImagePreviewControl : PersonPicture
}
}
// 2) Gravatar lookup through thumbnail service (if enabled).
// 3) Gravatar lookup through thumbnail service (if enabled).
if (_preferencesService?.IsGravatarEnabled == true &&
_thumbnailService != null &&
!string.IsNullOrWhiteSpace(snapshot.Address) &&
@@ -219,7 +240,7 @@ public sealed partial class ImagePreviewControl : PersonPicture
}
}
// 3) Initials fallback is already in place via DisplayName + ProfilePicture = null.
// 4) Initials fallback is already in place via DisplayName + ProfilePicture = null.
}
catch (OperationCanceledException)
{
@@ -242,8 +263,9 @@ public sealed partial class ImagePreviewControl : PersonPicture
var address = ResolveAddress();
var displayName = ResolveDisplayName(address);
var base64Picture = ResolveBase64Picture();
var contactPictureFileId = MailItemInformation?.SenderContact?.ContactPictureFileId;
return new RefreshSnapshot(displayName, address, base64Picture);
return new RefreshSnapshot(displayName, address, contactPictureFileId, base64Picture);
}).ConfigureAwait(false);
}
@@ -373,6 +395,31 @@ public sealed partial class ImagePreviewControl : PersonPicture
return await completion.Task.ConfigureAwait(false);
}
private async Task<BitmapImage?> CreateBitmapFromFileAsync(string filePath, CancellationToken cancellationToken)
{
byte[] bytes;
try
{
bytes = await File.ReadAllBytesAsync(filePath, cancellationToken).ConfigureAwait(false);
}
catch
{
return null;
}
cancellationToken.ThrowIfCancellationRequested();
return await ExecuteOnUiThreadAsync(() =>
{
cancellationToken.ThrowIfCancellationRequested();
using var memoryStream = new MemoryStream(bytes);
var bitmapImage = new BitmapImage();
bitmapImage.SetSource(memoryStream.AsRandomAccessStream());
return bitmapImage;
}).ConfigureAwait(false);
}
private async Task<BitmapImage?> CreateBitmapFromBase64Async(string base64, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(base64))