Remove sqlite base64 contact store from AccountContact.
This commit is contained in:
@@ -27,17 +27,9 @@ public class AccountContact : IEquatable<AccountContact>
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// File ID for the contact picture stored on disk.
|
/// File ID for the contact picture stored on disk.
|
||||||
/// The actual file lives at {ApplicationDataFolderPath}/contacts/{ContactPictureFileId}.jpg.
|
/// The actual file lives at {ApplicationDataFolderPath}/contacts/{ContactPictureFileId}.jpg.
|
||||||
/// Preferred over Base64ContactPicture — allows native BitmapImage file loading and avoids SQLite bloat.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Guid? ContactPictureFileId { get; set; }
|
public Guid? ContactPictureFileId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Legacy base64 encoded profile image of the contact.
|
|
||||||
/// For user-set contact pictures: migrate to file storage via ContactPictureFileId instead.
|
|
||||||
/// Still used for OAuth account profile pictures (MailAccount.Base64ProfilePictureData).
|
|
||||||
/// </summary>
|
|
||||||
public string Base64ContactPicture { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All registered accounts have their contacts registered as root.
|
/// All registered accounts have their contacts registered as root.
|
||||||
/// Root contacts must not be overridden by any configuration.
|
/// Root contacts must not be overridden by any configuration.
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public interface IMailItemDisplayInformation : INotifyPropertyChanged
|
|||||||
bool IsCalendarEvent { get; }
|
bool IsCalendarEvent { get; }
|
||||||
bool IsFlagged { get; }
|
bool IsFlagged { get; }
|
||||||
DateTime CreationDate { get; }
|
DateTime CreationDate { get; }
|
||||||
string Base64ContactPicture { get; }
|
Guid? ContactPictureFileId { get; }
|
||||||
bool ThumbnailUpdatedEvent { get; }
|
bool ThumbnailUpdatedEvent { get; }
|
||||||
bool IsThreadExpanded { get; }
|
bool IsThreadExpanded { get; }
|
||||||
AccountContact SenderContact { get; }
|
AccountContact SenderContact { get; }
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ public partial class PersonalizationPageViewModel : CoreBaseViewModel
|
|||||||
public bool IsCalendarEvent { get; } = false;
|
public bool IsCalendarEvent { get; } = false;
|
||||||
public bool IsFlagged { get; } = false;
|
public bool IsFlagged { get; } = false;
|
||||||
public DateTime CreationDate { get; } = DateTime.Now;
|
public DateTime CreationDate { get; } = DateTime.Now;
|
||||||
public string Base64ContactPicture { get; } = string.Empty;
|
public Guid? ContactPictureFileId { get; } = null;
|
||||||
public bool ThumbnailUpdatedEvent { get; } = false;
|
public bool ThumbnailUpdatedEvent { get; } = false;
|
||||||
public bool IsBusy { get; } = false;
|
public bool IsBusy { get; } = false;
|
||||||
public bool IsThreadExpanded { get; } = false;
|
public bool IsThreadExpanded { get; } = false;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ public partial class ContactsPageViewModel : MailBaseViewModel
|
|||||||
|
|
||||||
private readonly IContactService _contactService;
|
private readonly IContactService _contactService;
|
||||||
private readonly IMailDialogService _dialogService;
|
private readonly IMailDialogService _dialogService;
|
||||||
|
private readonly IContactPictureFileService _contactPictureFileService;
|
||||||
|
|
||||||
private CancellationTokenSource _searchDebounceCancellationTokenSource;
|
private CancellationTokenSource _searchDebounceCancellationTokenSource;
|
||||||
private int _currentOffset = 0;
|
private int _currentOffset = 0;
|
||||||
@@ -60,10 +61,11 @@ public partial class ContactsPageViewModel : MailBaseViewModel
|
|||||||
public ObservableCollection<AccountContactViewModel> Contacts { get; } = new();
|
public ObservableCollection<AccountContactViewModel> Contacts { get; } = new();
|
||||||
public ObservableCollection<AccountContactViewModel> SelectedContacts { get; } = new();
|
public ObservableCollection<AccountContactViewModel> SelectedContacts { get; } = new();
|
||||||
|
|
||||||
public ContactsPageViewModel(IContactService contactService, IMailDialogService dialogService)
|
public ContactsPageViewModel(IContactService contactService, IMailDialogService dialogService, IContactPictureFileService contactPictureFileService)
|
||||||
{
|
{
|
||||||
_contactService = contactService;
|
_contactService = contactService;
|
||||||
_dialogService = dialogService;
|
_dialogService = dialogService;
|
||||||
|
_contactPictureFileService = contactPictureFileService;
|
||||||
|
|
||||||
Contacts.CollectionChanged += ContactsCollectionChanged;
|
Contacts.CollectionChanged += ContactsCollectionChanged;
|
||||||
}
|
}
|
||||||
@@ -195,9 +197,9 @@ public partial class ContactsPageViewModel : MailBaseViewModel
|
|||||||
{
|
{
|
||||||
var newContact = await _contactService.CreateNewContactAsync(result.Address, result.Name);
|
var newContact = await _contactService.CreateNewContactAsync(result.Address, result.Name);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(result.Base64ContactPicture))
|
if (result.ContactPictureFileId.HasValue)
|
||||||
{
|
{
|
||||||
newContact.Base64ContactPicture = result.Base64ContactPicture;
|
newContact.ContactPictureFileId = result.ContactPictureFileId;
|
||||||
await _contactService.UpdateContactAsync(newContact);
|
await _contactService.UpdateContactAsync(newContact);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +232,7 @@ public partial class ContactsPageViewModel : MailBaseViewModel
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
contact.Name = result.Name;
|
contact.Name = result.Name;
|
||||||
contact.Base64ContactPicture = result.Base64ContactPicture;
|
contact.ContactPictureFileId = result.ContactPictureFileId;
|
||||||
contact.IsOverridden = result.IsOverridden;
|
contact.IsOverridden = result.IsOverridden;
|
||||||
|
|
||||||
await _contactService.UpdateContactAsync(contact);
|
await _contactService.UpdateContactAsync(contact);
|
||||||
@@ -382,9 +384,14 @@ public partial class ContactsPageViewModel : MailBaseViewModel
|
|||||||
if (files?.Any() == true)
|
if (files?.Any() == true)
|
||||||
{
|
{
|
||||||
var file = files.First();
|
var file = files.First();
|
||||||
var base64Image = Convert.ToBase64String(file.Data);
|
|
||||||
|
|
||||||
contact.Base64ContactPicture = base64Image;
|
if (contact.ContactPictureFileId.HasValue)
|
||||||
|
await _contactPictureFileService.DeleteContactPictureAsync(contact.ContactPictureFileId.Value);
|
||||||
|
|
||||||
|
contact.ContactPictureFileId = await _contactPictureFileService
|
||||||
|
.SaveContactPictureAsync(file.Data)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
await _contactService.UpdateContactAsync(contact);
|
await _contactService.UpdateContactAsync(contact);
|
||||||
await RefreshContactInUiAsync(contact);
|
await RefreshContactInUiAsync(contact);
|
||||||
|
|
||||||
@@ -432,7 +439,7 @@ public partial class ContactsPageViewModel : MailBaseViewModel
|
|||||||
{
|
{
|
||||||
Address = contact.Address,
|
Address = contact.Address,
|
||||||
Name = contact.Name,
|
Name = contact.Name,
|
||||||
Base64ContactPicture = contact.Base64ContactPicture,
|
ContactPictureFileId = contact.ContactPictureFileId,
|
||||||
IsRootContact = contact.IsRootContact,
|
IsRootContact = contact.IsRootContact,
|
||||||
IsOverridden = contact.IsOverridden
|
IsOverridden = contact.IsOverridden
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ public partial class AccountContactViewModel : ObservableObject, IMailItemDispla
|
|||||||
public string Address { get; set; }
|
public string Address { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public Guid? ContactPictureFileId { get; set; }
|
public Guid? ContactPictureFileId { get; set; }
|
||||||
public string Base64ContactPicture { get; set; }
|
|
||||||
public bool IsRootContact { get; set; }
|
public bool IsRootContact { get; set; }
|
||||||
public bool IsOverridden { get; set; }
|
public bool IsOverridden { get; set; }
|
||||||
|
|
||||||
@@ -22,7 +21,6 @@ public partial class AccountContactViewModel : ObservableObject, IMailItemDispla
|
|||||||
Address = contact.Address;
|
Address = contact.Address;
|
||||||
Name = contact.Name;
|
Name = contact.Name;
|
||||||
ContactPictureFileId = contact.ContactPictureFileId;
|
ContactPictureFileId = contact.ContactPictureFileId;
|
||||||
Base64ContactPicture = contact.Base64ContactPicture;
|
|
||||||
IsRootContact = contact.IsRootContact;
|
IsRootContact = contact.IsRootContact;
|
||||||
IsOverridden = contact.IsOverridden;
|
IsOverridden = contact.IsOverridden;
|
||||||
}
|
}
|
||||||
@@ -75,7 +73,6 @@ public partial class AccountContactViewModel : ObservableObject, IMailItemDispla
|
|||||||
Address = Address,
|
Address = Address,
|
||||||
Name = Name,
|
Name = Name,
|
||||||
ContactPictureFileId = ContactPictureFileId,
|
ContactPictureFileId = ContactPictureFileId,
|
||||||
Base64ContactPicture = Base64ContactPicture,
|
|
||||||
IsRootContact = IsRootContact,
|
IsRootContact = IsRootContact,
|
||||||
IsOverridden = IsOverridden
|
IsOverridden = IsOverridden
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public partial class MailItemViewModel(MailCopy mailCopy) : ObservableRecipient,
|
|||||||
[NotifyPropertyChangedFor(nameof(FileId))]
|
[NotifyPropertyChangedFor(nameof(FileId))]
|
||||||
[NotifyPropertyChangedFor(nameof(FolderId))]
|
[NotifyPropertyChangedFor(nameof(FolderId))]
|
||||||
[NotifyPropertyChangedFor(nameof(UniqueId))]
|
[NotifyPropertyChangedFor(nameof(UniqueId))]
|
||||||
[NotifyPropertyChangedFor(nameof(Base64ContactPicture))]
|
[NotifyPropertyChangedFor(nameof(ContactPictureFileId))]
|
||||||
[NotifyPropertyChangedFor(nameof(SenderContact))]
|
[NotifyPropertyChangedFor(nameof(SenderContact))]
|
||||||
public partial MailCopy MailCopy { get; set; } = mailCopy;
|
public partial MailCopy MailCopy { get; set; } = mailCopy;
|
||||||
|
|
||||||
@@ -191,10 +191,14 @@ public partial class MailItemViewModel(MailCopy mailCopy) : ObservableRecipient,
|
|||||||
set => SetProperty(MailCopy.UniqueId, value, MailCopy, (u, n) => u.UniqueId = n);
|
set => SetProperty(MailCopy.UniqueId, value, MailCopy, (u, n) => u.UniqueId = n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Base64ContactPicture
|
public Guid? ContactPictureFileId
|
||||||
{
|
{
|
||||||
get => MailCopy.SenderContact?.Base64ContactPicture ?? string.Empty;
|
get => MailCopy.SenderContact?.ContactPictureFileId;
|
||||||
set => SetProperty(MailCopy.SenderContact.Base64ContactPicture, value, MailCopy, (u, n) => u.SenderContact.Base64ContactPicture = n);
|
set => SetProperty(MailCopy.SenderContact?.ContactPictureFileId, value, MailCopy, (u, n) =>
|
||||||
|
{
|
||||||
|
if (u.SenderContact != null)
|
||||||
|
u.SenderContact.ContactPictureFileId = n;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTime SortingDate => CreationDate;
|
public DateTime SortingDate => CreationDate;
|
||||||
@@ -236,7 +240,7 @@ public partial class MailItemViewModel(MailCopy mailCopy) : ObservableRecipient,
|
|||||||
nameof(FileId) => MailCopyChangeFlags.FileId,
|
nameof(FileId) => MailCopyChangeFlags.FileId,
|
||||||
nameof(FolderId) => MailCopyChangeFlags.FolderId,
|
nameof(FolderId) => MailCopyChangeFlags.FolderId,
|
||||||
nameof(UniqueId) => MailCopyChangeFlags.UniqueId,
|
nameof(UniqueId) => MailCopyChangeFlags.UniqueId,
|
||||||
nameof(Base64ContactPicture) or nameof(SenderContact) => MailCopyChangeFlags.SenderContact,
|
nameof(ContactPictureFileId) or nameof(SenderContact) => MailCopyChangeFlags.SenderContact,
|
||||||
_ => MailCopyChangeFlags.None
|
_ => MailCopyChangeFlags.None
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -398,7 +402,7 @@ public partial class MailItemViewModel(MailCopy mailCopy) : ObservableRecipient,
|
|||||||
|
|
||||||
if ((changedFlags & MailCopyChangeFlags.SenderContact) != 0)
|
if ((changedFlags & MailCopyChangeFlags.SenderContact) != 0)
|
||||||
{
|
{
|
||||||
Queue(nameof(Base64ContactPicture));
|
Queue(nameof(ContactPictureFileId));
|
||||||
Queue(nameof(SenderContact));
|
Queue(nameof(SenderContact));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IMailListIte
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Guid UniqueId => latestMailViewModel?.UniqueId ?? Guid.Empty;
|
public Guid UniqueId => latestMailViewModel?.UniqueId ?? Guid.Empty;
|
||||||
|
|
||||||
public string Base64ContactPicture => latestMailViewModel?.MailCopy?.SenderContact?.Base64ContactPicture ?? string.Empty;
|
public Guid? ContactPictureFileId => latestMailViewModel?.MailCopy?.SenderContact?.ContactPictureFileId;
|
||||||
|
|
||||||
public bool ThumbnailUpdatedEvent => latestMailViewModel?.ThumbnailUpdatedEvent ?? false;
|
public bool ThumbnailUpdatedEvent => latestMailViewModel?.ThumbnailUpdatedEvent ?? false;
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IMailListIte
|
|||||||
[NotifyPropertyChangedFor(nameof(FileId))]
|
[NotifyPropertyChangedFor(nameof(FileId))]
|
||||||
[NotifyPropertyChangedFor(nameof(FolderId))]
|
[NotifyPropertyChangedFor(nameof(FolderId))]
|
||||||
[NotifyPropertyChangedFor(nameof(UniqueId))]
|
[NotifyPropertyChangedFor(nameof(UniqueId))]
|
||||||
[NotifyPropertyChangedFor(nameof(Base64ContactPicture))]
|
[NotifyPropertyChangedFor(nameof(ContactPictureFileId))]
|
||||||
[NotifyPropertyChangedFor(nameof(SenderContact))]
|
[NotifyPropertyChangedFor(nameof(SenderContact))]
|
||||||
public partial ObservableCollection<MailItemViewModel> ThreadEmails { get; set; } = [];
|
public partial ObservableCollection<MailItemViewModel> ThreadEmails { get; set; } = [];
|
||||||
|
|
||||||
@@ -369,7 +369,7 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IMailListIte
|
|||||||
Queue(nameof(FileId));
|
Queue(nameof(FileId));
|
||||||
Queue(nameof(FolderId));
|
Queue(nameof(FolderId));
|
||||||
Queue(nameof(UniqueId));
|
Queue(nameof(UniqueId));
|
||||||
Queue(nameof(Base64ContactPicture));
|
Queue(nameof(ContactPictureFileId));
|
||||||
Queue(nameof(SenderContact));
|
Queue(nameof(SenderContact));
|
||||||
Queue(nameof(ThumbnailUpdatedEvent));
|
Queue(nameof(ThumbnailUpdatedEvent));
|
||||||
Queue(nameof(SortingDate));
|
Queue(nameof(SortingDate));
|
||||||
@@ -433,7 +433,7 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IMailListIte
|
|||||||
|
|
||||||
if ((changedFlags & MailCopyChangeFlags.SenderContact) != 0)
|
if ((changedFlags & MailCopyChangeFlags.SenderContact) != 0)
|
||||||
{
|
{
|
||||||
Queue(nameof(Base64ContactPicture));
|
Queue(nameof(ContactPictureFileId));
|
||||||
Queue(nameof(SenderContact));
|
Queue(nameof(SenderContact));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,9 +124,6 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial string FromName { get; set; }
|
public partial string FromName { get; set; }
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial string ContactPicture { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial IMailItemDisplayInformation CurrentMailItemDisplayInformation { get; set; }
|
public partial IMailItemDisplayInformation CurrentMailItemDisplayInformation { get; set; }
|
||||||
|
|
||||||
@@ -497,8 +494,6 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
// Use the received date from MailCopy if available, otherwise fall back to the sent date from MIME message
|
// Use the received date from MailCopy if available, otherwise fall back to the sent date from MIME message
|
||||||
CreationDate = initializedMailItemViewModel?.MailCopy.CreationDate ?? message.Date.DateTime;
|
CreationDate = initializedMailItemViewModel?.MailCopy.CreationDate ?? message.Date.DateTime;
|
||||||
|
|
||||||
ContactPicture = initializedMailItemViewModel?.MailCopy.SenderContact?.Base64ContactPicture;
|
|
||||||
|
|
||||||
// Automatically disable images for Junk folder to prevent pixel tracking.
|
// Automatically disable images for Junk folder to prevent pixel tracking.
|
||||||
// This can only work for selected mail item rendering, not for EML file rendering.
|
// This can only work for selected mail item rendering, not for EML file rendering.
|
||||||
if (initializedMailItemViewModel != null &&
|
if (initializedMailItemViewModel != null &&
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ namespace Wino.Controls;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contact avatar control built on top of PersonPicture.
|
/// Contact avatar control built on top of PersonPicture.
|
||||||
/// Priority:
|
/// Priority:
|
||||||
/// 1) AccountContact/Base64 picture
|
/// 1) AccountContact file-based picture
|
||||||
/// 2) Gravatar thumbnail (if enabled)
|
/// 2) Gravatar thumbnail (if enabled)
|
||||||
/// 3) Initials from display name fallback
|
/// 3) Initials from display name fallback
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class ImagePreviewControl : PersonPicture
|
public sealed partial class ImagePreviewControl : PersonPicture
|
||||||
{
|
{
|
||||||
private sealed record RefreshSnapshot(string DisplayName, string Address, Guid? ContactPictureFileId, string Base64Picture);
|
private sealed record RefreshSnapshot(string DisplayName, string Address, Guid? ContactPictureFileId);
|
||||||
|
|
||||||
private static readonly TimeSpan RefreshDebounceDuration = TimeSpan.FromMilliseconds(40);
|
private static readonly TimeSpan RefreshDebounceDuration = TimeSpan.FromMilliseconds(40);
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ public sealed partial class ImagePreviewControl : PersonPicture
|
|||||||
{
|
{
|
||||||
// Refresh only for fields that affect avatar image or initials.
|
// Refresh only for fields that affect avatar image or initials.
|
||||||
if (string.IsNullOrEmpty(e.PropertyName)
|
if (string.IsNullOrEmpty(e.PropertyName)
|
||||||
|| e.PropertyName == nameof(IMailItemDisplayInformation.Base64ContactPicture)
|
|| e.PropertyName == nameof(IMailItemDisplayInformation.ContactPictureFileId)
|
||||||
|| e.PropertyName == nameof(IMailItemDisplayInformation.SenderContact)
|
|| e.PropertyName == nameof(IMailItemDisplayInformation.SenderContact)
|
||||||
|| e.PropertyName == nameof(IMailItemDisplayInformation.FromName)
|
|| e.PropertyName == nameof(IMailItemDisplayInformation.FromName)
|
||||||
|| e.PropertyName == nameof(IMailItemDisplayInformation.FromAddress)
|
|| e.PropertyName == nameof(IMailItemDisplayInformation.FromAddress)
|
||||||
@@ -222,18 +222,7 @@ public sealed partial class ImagePreviewControl : PersonPicture
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Legacy base64 contact picture (used until migration completes or for fallback).
|
// 2) Gravatar lookup through thumbnail service (if enabled).
|
||||||
if (!string.IsNullOrWhiteSpace(snapshot.Base64Picture))
|
|
||||||
{
|
|
||||||
var localBitmap = await CreateBitmapFromBase64Async(snapshot.Base64Picture, cancellationToken).ConfigureAwait(false);
|
|
||||||
if (localBitmap != null)
|
|
||||||
{
|
|
||||||
await ApplyProfilePictureAsync(localBitmap, refreshVersion, cancellationToken).ConfigureAwait(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3) Gravatar lookup through thumbnail service (if enabled).
|
|
||||||
if (_preferencesService?.IsGravatarEnabled == true &&
|
if (_preferencesService?.IsGravatarEnabled == true &&
|
||||||
_thumbnailService != null &&
|
_thumbnailService != null &&
|
||||||
!string.IsNullOrWhiteSpace(snapshot.Address) &&
|
!string.IsNullOrWhiteSpace(snapshot.Address) &&
|
||||||
@@ -254,7 +243,7 @@ public sealed partial class ImagePreviewControl : PersonPicture
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4) Initials fallback is already in place via DisplayName + ProfilePicture = null.
|
// 3) Initials fallback is already in place via DisplayName + ProfilePicture = null.
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@@ -276,10 +265,11 @@ public sealed partial class ImagePreviewControl : PersonPicture
|
|||||||
|
|
||||||
var address = ResolveAddress();
|
var address = ResolveAddress();
|
||||||
var displayName = ResolveDisplayName(address);
|
var displayName = ResolveDisplayName(address);
|
||||||
var base64Picture = ResolveBase64Picture();
|
var contactPictureFileId = PreviewContact?.ContactPictureFileId
|
||||||
var contactPictureFileId = PreviewContact?.ContactPictureFileId ?? MailItemInformation?.SenderContact?.ContactPictureFileId;
|
?? MailItemInformation?.SenderContact?.ContactPictureFileId
|
||||||
|
?? MailItemInformation?.ContactPictureFileId;
|
||||||
|
|
||||||
return new RefreshSnapshot(displayName, address, contactPictureFileId, base64Picture);
|
return new RefreshSnapshot(displayName, address, contactPictureFileId);
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,20 +311,6 @@ public sealed partial class ImagePreviewControl : PersonPicture
|
|||||||
|
|
||||||
return resolvedAddress.Trim();
|
return resolvedAddress.Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ResolveBase64Picture()
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(PreviewContact?.Base64ContactPicture))
|
|
||||||
return PreviewContact.Base64ContactPicture;
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(MailItemInformation?.SenderContact?.Base64ContactPicture))
|
|
||||||
return MailItemInformation.SenderContact.Base64ContactPicture;
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(MailItemInformation?.Base64ContactPicture))
|
|
||||||
return MailItemInformation.Base64ContactPicture;
|
|
||||||
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
private async Task ApplyInitialVisualStateAsync(string displayName, long refreshVersion, CancellationToken cancellationToken)
|
private async Task ApplyInitialVisualStateAsync(string displayName, long refreshVersion, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await ExecuteOnUiThreadAsync(() =>
|
await ExecuteOnUiThreadAsync(() =>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
x:Class="Wino.Dialogs.ContactEditDialog"
|
x:Class="Wino.Dialogs.ContactEditDialog"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:controls="using:Wino.Controls"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:domain="using:Wino.Core.Domain"
|
xmlns:domain="using:Wino.Core.Domain"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<PersonPicture
|
<controls:ImagePreviewControl
|
||||||
x:Name="ContactPhotoPersonPicture"
|
x:Name="ContactPhotoPersonPicture"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Width="64"
|
Width="64"
|
||||||
|
|||||||
@@ -61,11 +61,6 @@ public sealed partial class ContactEditDialog : ContentDialog
|
|||||||
RemovePhotoButton.Visibility = Visibility.Visible;
|
RemovePhotoButton.Visibility = Visibility.Visible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrEmpty(_contact.Base64ContactPicture))
|
|
||||||
{
|
|
||||||
LoadContactPhotoFromBase64(_contact.Base64ContactPicture);
|
|
||||||
RemovePhotoButton.Visibility = Visibility.Visible;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ContactPhotoPersonPicture.DisplayName = _contact.Name ?? string.Empty;
|
ContactPhotoPersonPicture.DisplayName = _contact.Name ?? string.Empty;
|
||||||
@@ -101,9 +96,7 @@ public sealed partial class ContactEditDialog : ContentDialog
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Fallback to legacy base64 when service is unavailable (e.g. design-time).
|
LoadContactPhoto(file.Data);
|
||||||
_contact.Base64ContactPicture = Convert.ToBase64String(file.Data);
|
|
||||||
LoadContactPhotoFromBase64(_contact.Base64ContactPicture);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RemovePhotoButton.Visibility = Visibility.Visible;
|
RemovePhotoButton.Visibility = Visibility.Visible;
|
||||||
@@ -121,7 +114,6 @@ public sealed partial class ContactEditDialog : ContentDialog
|
|||||||
_ = _contactPictureFileService.DeleteContactPictureAsync(_contact.ContactPictureFileId.Value);
|
_ = _contactPictureFileService.DeleteContactPictureAsync(_contact.ContactPictureFileId.Value);
|
||||||
|
|
||||||
_contact.ContactPictureFileId = null;
|
_contact.ContactPictureFileId = null;
|
||||||
_contact.Base64ContactPicture = null;
|
|
||||||
ContactPhotoPersonPicture.ProfilePicture = null;
|
ContactPhotoPersonPicture.ProfilePicture = null;
|
||||||
ContactPhotoPersonPicture.DisplayName = ContactNameTextBox.Text;
|
ContactPhotoPersonPicture.DisplayName = ContactNameTextBox.Text;
|
||||||
RemovePhotoButton.Visibility = Visibility.Collapsed;
|
RemovePhotoButton.Visibility = Visibility.Collapsed;
|
||||||
@@ -143,11 +135,10 @@ public sealed partial class ContactEditDialog : ContentDialog
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadContactPhotoFromBase64(string base64String)
|
private void LoadContactPhoto(byte[] imageBytes)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var imageBytes = Convert.FromBase64String(base64String);
|
|
||||||
using var stream = new MemoryStream(imageBytes);
|
using var stream = new MemoryStream(imageBytes);
|
||||||
var bitmap = new BitmapImage();
|
var bitmap = new BitmapImage();
|
||||||
bitmap.SetSource(stream.AsRandomAccessStream());
|
bitmap.SetSource(stream.AsRandomAccessStream());
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
xmlns:local="using:Wino.Calendar.Views"
|
xmlns:local="using:Wino.Calendar.Views"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||||
|
xmlns:controls="using:Wino.Controls"
|
||||||
xmlns:shared="using:Wino.Core.Domain.Entities.Shared"
|
xmlns:shared="using:Wino.Core.Domain.Entities.Shared"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
@@ -338,10 +339,10 @@
|
|||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<muxc:PersonPicture
|
<controls:ImagePreviewControl
|
||||||
Width="46"
|
Width="46"
|
||||||
Height="46"
|
Height="46"
|
||||||
DisplayName="BK" />
|
DisplayNameOverride="{x:Bind ViewModel.DisplayDetailsCalendarItemViewModel.CalendarItem.OrganizerDisplayName, Mode=OneWay}" />
|
||||||
|
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
xmlns:calendarHelpers="using:Wino.Calendar.Helpers"
|
xmlns:calendarHelpers="using:Wino.Calendar.Helpers"
|
||||||
xmlns:calendarViewModels="using:Wino.Calendar.ViewModels"
|
xmlns:calendarViewModels="using:Wino.Calendar.ViewModels"
|
||||||
xmlns:coreControls="using:Wino.Mail.WinUI.Controls"
|
xmlns:coreControls="using:Wino.Mail.WinUI.Controls"
|
||||||
|
xmlns:controls="using:Wino.Controls"
|
||||||
xmlns:ctControls="using:CommunityToolkit.WinUI.Controls"
|
xmlns:ctControls="using:CommunityToolkit.WinUI.Controls"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:data="using:Wino.Calendar.ViewModels.Data"
|
xmlns:data="using:Wino.Calendar.ViewModels.Data"
|
||||||
@@ -453,10 +454,11 @@
|
|||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<PersonPicture
|
<controls:ImagePreviewControl
|
||||||
Width="40"
|
Width="40"
|
||||||
Height="40"
|
Height="40"
|
||||||
DisplayName="{x:Bind Name}" />
|
Address="{x:Bind Email}"
|
||||||
|
DisplayNameOverride="{x:Bind Name}" />
|
||||||
|
|
||||||
<Grid Grid.Column="1" RowSpacing="4">
|
<Grid Grid.Column="1" RowSpacing="4">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
</Grid.ContextFlyout>-->
|
</Grid.ContextFlyout>-->
|
||||||
|
|
||||||
<Viewbox Width="24">
|
<Viewbox Width="24">
|
||||||
<PersonPicture DisplayName="{x:Bind Name, Mode=OneTime}" />
|
<controls:ImagePreviewControl PreviewContact="{x:Bind}" />
|
||||||
</Viewbox>
|
</Viewbox>
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<PersonPicture DisplayName="{x:Bind Name, Mode=OneTime}" />
|
<controls:ImagePreviewControl PreviewContact="{x:Bind}" />
|
||||||
<TextBlock Grid.Column="1">
|
<TextBlock Grid.Column="1">
|
||||||
<Run FontWeight="SemiBold" Text="{x:Bind Name}" /><LineBreak /><Run Text="{x:Bind Address}" />
|
<Run FontWeight="SemiBold" Text="{x:Bind Name}" /><LineBreak /><Run Text="{x:Bind Address}" />
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
|
|||||||
@@ -151,7 +151,10 @@
|
|||||||
</Page.Resources>
|
</Page.Resources>
|
||||||
|
|
||||||
<!-- Attachments and WebView2 -->
|
<!-- Attachments and WebView2 -->
|
||||||
<Grid x:Name="RendererGridFrame" RowSpacing="7">
|
<Grid
|
||||||
|
x:Name="RendererGridFrame"
|
||||||
|
Margin="0,2,0,0"
|
||||||
|
RowSpacing="7">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public class AccountService : BaseDatabaseService, IAccountService
|
|||||||
private readonly IAuthenticationProvider _authenticationProvider;
|
private readonly IAuthenticationProvider _authenticationProvider;
|
||||||
private readonly IMimeFileService _mimeFileService;
|
private readonly IMimeFileService _mimeFileService;
|
||||||
private readonly IPreferencesService _preferencesService;
|
private readonly IPreferencesService _preferencesService;
|
||||||
|
private readonly IContactPictureFileService _contactPictureFileService;
|
||||||
|
|
||||||
private readonly ILogger _logger = Log.ForContext<AccountService>();
|
private readonly ILogger _logger = Log.ForContext<AccountService>();
|
||||||
|
|
||||||
@@ -49,12 +50,14 @@ public class AccountService : BaseDatabaseService, IAccountService
|
|||||||
ISignatureService signatureService,
|
ISignatureService signatureService,
|
||||||
IAuthenticationProvider authenticationProvider,
|
IAuthenticationProvider authenticationProvider,
|
||||||
IMimeFileService mimeFileService,
|
IMimeFileService mimeFileService,
|
||||||
IPreferencesService preferencesService) : base(databaseService)
|
IPreferencesService preferencesService,
|
||||||
|
IContactPictureFileService contactPictureFileService) : base(databaseService)
|
||||||
{
|
{
|
||||||
_signatureService = signatureService;
|
_signatureService = signatureService;
|
||||||
_authenticationProvider = authenticationProvider;
|
_authenticationProvider = authenticationProvider;
|
||||||
_mimeFileService = mimeFileService;
|
_mimeFileService = mimeFileService;
|
||||||
_preferencesService = preferencesService;
|
_preferencesService = preferencesService;
|
||||||
|
_contactPictureFileService = contactPictureFileService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -399,11 +402,19 @@ public class AccountService : BaseDatabaseService, IAccountService
|
|||||||
}
|
}
|
||||||
// Forcefully add or update a contact data with the provided information.
|
// Forcefully add or update a contact data with the provided information.
|
||||||
|
|
||||||
|
var existingContact = await Connection.Table<AccountContact>()
|
||||||
|
.FirstOrDefaultAsync(a => a.Address == account.Address)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
var contactPictureFileId = await SaveProfilePictureAsync(
|
||||||
|
account.Base64ProfilePictureData,
|
||||||
|
existingContact?.ContactPictureFileId).ConfigureAwait(false);
|
||||||
|
|
||||||
var accountContact = new AccountContact()
|
var accountContact = new AccountContact()
|
||||||
{
|
{
|
||||||
Address = account.Address,
|
Address = account.Address,
|
||||||
Name = account.SenderName,
|
Name = account.SenderName,
|
||||||
Base64ContactPicture = account.Base64ProfilePictureData,
|
ContactPictureFileId = contactPictureFileId,
|
||||||
IsRootContact = true
|
IsRootContact = true
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -413,6 +424,35 @@ public class AccountService : BaseDatabaseService, IAccountService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<Guid?> SaveProfilePictureAsync(string base64ProfilePictureData, Guid? existingFileId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(base64ProfilePictureData))
|
||||||
|
{
|
||||||
|
if (existingFileId.HasValue)
|
||||||
|
await _contactPictureFileService.DeleteContactPictureAsync(existingFileId.Value).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bytes;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bytes = Convert.FromBase64String(base64ProfilePictureData);
|
||||||
|
}
|
||||||
|
catch (FormatException ex)
|
||||||
|
{
|
||||||
|
_logger.Warning(ex, "Failed to decode account profile picture for contact migration.");
|
||||||
|
return existingFileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newFileId = await _contactPictureFileService.SaveContactPictureAsync(bytes).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (existingFileId.HasValue)
|
||||||
|
await _contactPictureFileService.DeleteContactPictureAsync(existingFileId.Value).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return newFileId;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<MailAccount> GetAccountAsync(Guid accountId)
|
public async Task<MailAccount> GetAccountAsync(Guid accountId)
|
||||||
{
|
{
|
||||||
var account = await Connection.Table<MailAccount>().FirstOrDefaultAsync(a => a.Id == accountId);
|
var account = await Connection.Table<MailAccount>().FirstOrDefaultAsync(a => a.Id == accountId);
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ namespace Wino.Services;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ContactPictureFileService : BaseDatabaseService, IContactPictureFileService
|
public class ContactPictureFileService : BaseDatabaseService, IContactPictureFileService
|
||||||
{
|
{
|
||||||
|
private sealed class LegacyAccountContactPictureRow
|
||||||
|
{
|
||||||
|
public string Address { get; set; }
|
||||||
|
public string Base64ContactPicture { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
private const string ContactsSubFolder = "contacts";
|
private const string ContactsSubFolder = "contacts";
|
||||||
|
|
||||||
private readonly string _contactPicturesFolder;
|
private readonly string _contactPicturesFolder;
|
||||||
@@ -52,8 +58,8 @@ public class ContactPictureFileService : BaseDatabaseService, IContactPictureFil
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var contacts = await Connection
|
var contacts = await Connection
|
||||||
.QueryAsync<AccountContact>(
|
.QueryAsync<LegacyAccountContactPictureRow>(
|
||||||
"SELECT * FROM AccountContact WHERE Base64ContactPicture IS NOT NULL AND ContactPictureFileId IS NULL")
|
"SELECT Address, Base64ContactPicture FROM AccountContact WHERE Base64ContactPicture IS NOT NULL AND ContactPictureFileId IS NULL")
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
foreach (var contact in contacts)
|
foreach (var contact in contacts)
|
||||||
@@ -67,10 +73,10 @@ public class ContactPictureFileService : BaseDatabaseService, IContactPictureFil
|
|||||||
var bytes = Convert.FromBase64String(base64);
|
var bytes = Convert.FromBase64String(base64);
|
||||||
var fileId = await SaveContactPictureAsync(bytes).ConfigureAwait(false);
|
var fileId = await SaveContactPictureAsync(bytes).ConfigureAwait(false);
|
||||||
|
|
||||||
contact.ContactPictureFileId = fileId;
|
await Connection.ExecuteAsync(
|
||||||
contact.Base64ContactPicture = null;
|
"UPDATE AccountContact SET ContactPictureFileId = ?, Base64ContactPicture = NULL WHERE Address = ?",
|
||||||
|
fileId,
|
||||||
await Connection.UpdateAsync(contact, typeof(AccountContact)).ConfigureAwait(false);
|
contact.Address).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -465,14 +465,21 @@ public class MailService : BaseDatabaseService, IMailService
|
|||||||
|
|
||||||
// Self-sent mails (e.g. Sent folder): construct contact from account meta
|
// Self-sent mails (e.g. Sent folder): construct contact from account meta
|
||||||
// to get the up-to-date profile picture without a DB roundtrip.
|
// to get the up-to-date profile picture without a DB roundtrip.
|
||||||
if (!string.IsNullOrEmpty(mail.FromAddress) && mail.FromAddress == account.Address)
|
if (!string.IsNullOrEmpty(mail.FromAddress) &&
|
||||||
|
string.Equals(mail.FromAddress, account.Address, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
mail.SenderContact = new AccountContact
|
if (contactCache.TryGetValue(mail.FromAddress, out var ownContact))
|
||||||
{
|
{
|
||||||
Address = account.Address,
|
mail.SenderContact = ownContact;
|
||||||
Name = account.SenderName,
|
}
|
||||||
Base64ContactPicture = account.Base64ProfilePictureData
|
else
|
||||||
};
|
{
|
||||||
|
mail.SenderContact = new AccountContact
|
||||||
|
{
|
||||||
|
Address = account.Address,
|
||||||
|
Name = account.SenderName
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -543,9 +550,9 @@ public class MailService : BaseDatabaseService, IMailService
|
|||||||
private Task<AccountContact> GetSenderContactForAccountAsync(MailAccount account, string fromAddress)
|
private Task<AccountContact> GetSenderContactForAccountAsync(MailAccount account, string fromAddress)
|
||||||
{
|
{
|
||||||
// Make sure to return the latest up to date contact information for the original account.
|
// Make sure to return the latest up to date contact information for the original account.
|
||||||
if (fromAddress == account.Address)
|
if (string.Equals(fromAddress, account.Address, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return Task.FromResult(new AccountContact() { Address = account.Address, Name = account.SenderName, Base64ContactPicture = account.Base64ProfilePictureData });
|
return GetOwnSenderContactAsync(account);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -553,6 +560,17 @@ public class MailService : BaseDatabaseService, IMailService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<AccountContact> GetOwnSenderContactAsync(MailAccount account)
|
||||||
|
{
|
||||||
|
var contact = await _contactService.GetAddressInformationByAddressAsync(account.Address).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return contact ?? new AccountContact
|
||||||
|
{
|
||||||
|
Address = account.Address,
|
||||||
|
Name = account.SenderName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async Task LoadAssignedPropertiesAsync(MailCopy mailCopy)
|
private async Task LoadAssignedPropertiesAsync(MailCopy mailCopy)
|
||||||
{
|
{
|
||||||
if (mailCopy == null) return;
|
if (mailCopy == null) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user