Settings home refactoring.
This commit is contained in:
@@ -74,4 +74,5 @@ public interface IStatePersistanceService : INotifyPropertyChanged
|
|||||||
/// Setting: Calendar display count for the day view.
|
/// Setting: Calendar display count for the day view.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int DayDisplayCount { get; set; }
|
int DayDisplayCount { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,16 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.Navigation;
|
using Wino.Core.Domain.Models.Navigation;
|
||||||
|
using Wino.Core.Domain.Models.Personalization;
|
||||||
using Wino.Core.Domain.Models.Settings;
|
using Wino.Core.Domain.Models.Settings;
|
||||||
|
using Wino.Core.Domain.Models.Translations;
|
||||||
using Wino.Core.Extensions;
|
using Wino.Core.Extensions;
|
||||||
|
using Wino.Core.ViewModels.Data;
|
||||||
|
using Wino.Mail.ViewModels.Data;
|
||||||
using Wino.Messaging.Client.Navigation;
|
using Wino.Messaging.Client.Navigation;
|
||||||
|
|
||||||
namespace Wino.Core.ViewModels;
|
namespace Wino.Core.ViewModels;
|
||||||
@@ -22,6 +27,12 @@ public partial class SettingOptionsPageViewModel : CoreBaseViewModel
|
|||||||
private readonly IAccountService _accountService;
|
private readonly IAccountService _accountService;
|
||||||
private readonly IMimeStorageService _mimeStorageService;
|
private readonly IMimeStorageService _mimeStorageService;
|
||||||
private readonly IStoreRatingService _storeRatingService;
|
private readonly IStoreRatingService _storeRatingService;
|
||||||
|
private readonly ITranslationService _translationService;
|
||||||
|
private readonly INewThemeService _newThemeService;
|
||||||
|
private readonly IPreferencesService _preferencesService;
|
||||||
|
private readonly IProviderService _providerService;
|
||||||
|
private bool _isInitializingSettings;
|
||||||
|
private bool _isAppearanceSelectionPaused;
|
||||||
|
|
||||||
public string GitHubUrl => AppUrls.GitHub;
|
public string GitHubUrl => AppUrls.GitHub;
|
||||||
public string PaypalUrl => AppUrls.Paypal;
|
public string PaypalUrl => AppUrls.Paypal;
|
||||||
@@ -29,6 +40,17 @@ public partial class SettingOptionsPageViewModel : CoreBaseViewModel
|
|||||||
public string PrivacyPolicyUrl => AppUrls.PrivacyPolicy;
|
public string PrivacyPolicyUrl => AppUrls.PrivacyPolicy;
|
||||||
|
|
||||||
public ObservableCollection<SettingsNavigationItemInfo> SearchSuggestions { get; } = [];
|
public ObservableCollection<SettingsNavigationItemInfo> SearchSuggestions { get; } = [];
|
||||||
|
public ObservableCollection<IAccountProviderDetailViewModel> Accounts { get; } = [];
|
||||||
|
public ObservableCollection<AppColorViewModel> Colors { get; } = [];
|
||||||
|
|
||||||
|
public List<ElementThemeContainer> ElementThemes { get; } =
|
||||||
|
[
|
||||||
|
new(ApplicationElementTheme.Light, Translator.ElementTheme_Light),
|
||||||
|
new(ApplicationElementTheme.Dark, Translator.ElementTheme_Dark),
|
||||||
|
new(ApplicationElementTheme.Default, Translator.ElementTheme_Default),
|
||||||
|
];
|
||||||
|
|
||||||
|
public bool HasAccounts => Accounts.Count > 0;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial string VersionText { get; set; } = string.Empty;
|
public partial string VersionText { get; set; } = string.Empty;
|
||||||
@@ -45,15 +67,38 @@ public partial class SettingOptionsPageViewModel : CoreBaseViewModel
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial string SearchQuery { get; set; } = string.Empty;
|
public partial string SearchQuery { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial List<AppLanguageModel> AvailableLanguages { get; set; } = [];
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial AppLanguageModel SelectedLanguage { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial ElementThemeContainer SelectedElementTheme { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial AppColorViewModel SelectedAppColor { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial bool UseAccentColor { get; set; }
|
||||||
|
|
||||||
public SettingOptionsPageViewModel(INativeAppService nativeAppService,
|
public SettingOptionsPageViewModel(INativeAppService nativeAppService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
IMimeStorageService mimeStorageService,
|
IMimeStorageService mimeStorageService,
|
||||||
IStoreRatingService storeRatingService)
|
IStoreRatingService storeRatingService,
|
||||||
|
ITranslationService translationService,
|
||||||
|
INewThemeService newThemeService,
|
||||||
|
IPreferencesService preferencesService,
|
||||||
|
IProviderService providerService)
|
||||||
{
|
{
|
||||||
_nativeAppService = nativeAppService;
|
_nativeAppService = nativeAppService;
|
||||||
_accountService = accountService;
|
_accountService = accountService;
|
||||||
_mimeStorageService = mimeStorageService;
|
_mimeStorageService = mimeStorageService;
|
||||||
_storeRatingService = storeRatingService;
|
_storeRatingService = storeRatingService;
|
||||||
|
_translationService = translationService;
|
||||||
|
_newThemeService = newThemeService;
|
||||||
|
_preferencesService = preferencesService;
|
||||||
|
_providerService = providerService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnNavigatedTo(NavigationMode mode, object parameters)
|
public override void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||||
@@ -64,6 +109,7 @@ public partial class SettingOptionsPageViewModel : CoreBaseViewModel
|
|||||||
SearchQuery = string.Empty;
|
SearchQuery = string.Empty;
|
||||||
SearchSuggestions.Clear();
|
SearchSuggestions.Clear();
|
||||||
StorageSummaryText = Translator.SettingsHome_StorageLoading;
|
StorageSummaryText = Translator.SettingsHome_StorageLoading;
|
||||||
|
InitializeQuickSettings();
|
||||||
|
|
||||||
_ = LoadDashboardAsync();
|
_ = LoadDashboardAsync();
|
||||||
}
|
}
|
||||||
@@ -91,19 +137,54 @@ public partial class SettingOptionsPageViewModel : CoreBaseViewModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void NavigateToAccount(IAccountProviderDetailViewModel account)
|
||||||
|
{
|
||||||
|
if (account == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Messenger.Send(new BreadcrumbNavigationRequested(Translator.SettingsManageAccountSettings_Title, WinoPage.ManageAccountsPage));
|
||||||
|
|
||||||
|
switch (account)
|
||||||
|
{
|
||||||
|
case AccountProviderDetailViewModel accountDetails:
|
||||||
|
Messenger.Send(new BreadcrumbNavigationRequested(accountDetails.Account.Name, WinoPage.AccountDetailsPage, accountDetails.Account.Id));
|
||||||
|
break;
|
||||||
|
case MergedAccountProviderDetailViewModel mergedAccount:
|
||||||
|
Messenger.Send(new BreadcrumbNavigationRequested(mergedAccount.MergedInbox.Name, WinoPage.MergedAccountDetailsPage, mergedAccount));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NavigateToAddAccount()
|
||||||
|
{
|
||||||
|
Messenger.Send(new BreadcrumbNavigationRequested(Translator.SettingsManageAccountSettings_Title, WinoPage.ManageAccountsPage));
|
||||||
|
Messenger.Send(new BreadcrumbNavigationRequested(Translator.WelcomeWizard_Step2Title, WinoPage.ProviderSelectionPage));
|
||||||
|
}
|
||||||
|
|
||||||
private async Task LoadDashboardAsync()
|
private async Task LoadDashboardAsync()
|
||||||
{
|
{
|
||||||
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false) ?? [];
|
var accounts = (await _accountService.GetAccountsAsync().ConfigureAwait(false) ?? []).ToList();
|
||||||
var count = accounts.Count;
|
var count = accounts.Count;
|
||||||
Dictionary<Guid, long> storageSizeMap = count == 0
|
Dictionary<Guid, long> storageSizeMap = count == 0
|
||||||
? []
|
? []
|
||||||
: await _mimeStorageService.GetAccountsMimeStorageSizesAsync(accounts.Select(account => account.Id)).ConfigureAwait(false);
|
: await _mimeStorageService.GetAccountsMimeStorageSizesAsync(accounts.Select(account => account.Id)).ConfigureAwait(false);
|
||||||
var totalStorageBytes = storageSizeMap.Values.Sum();
|
var totalStorageBytes = storageSizeMap.Values.Sum();
|
||||||
|
var groupedAccountItems = CreateAccountItems(accounts);
|
||||||
|
|
||||||
await ExecuteUIThread(() =>
|
await ExecuteUIThread(() =>
|
||||||
{
|
{
|
||||||
AccountCount = count;
|
AccountCount = count;
|
||||||
AccountSummaryText = string.Format(Translator.SettingsOptions_AccountsSummary, count);
|
AccountSummaryText = string.Format(Translator.SettingsOptions_AccountsSummary, count);
|
||||||
|
Accounts.Clear();
|
||||||
|
|
||||||
|
foreach (var account in groupedAccountItems)
|
||||||
|
{
|
||||||
|
Accounts.Add(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(HasAccounts));
|
||||||
StorageSummaryText = totalStorageBytes == 0
|
StorageSummaryText = totalStorageBytes == 0
|
||||||
? Translator.SettingsHome_StorageEmptySummary
|
? Translator.SettingsHome_StorageEmptySummary
|
||||||
: string.Format(Translator.SettingsStorage_TotalUsage, totalStorageBytes.GetBytesReadable());
|
: string.Format(Translator.SettingsStorage_TotalUsage, totalStorageBytes.GetBytesReadable());
|
||||||
@@ -115,6 +196,175 @@ public partial class SettingOptionsPageViewModel : CoreBaseViewModel
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void InitializeQuickSettings()
|
||||||
|
{
|
||||||
|
_isInitializingSettings = true;
|
||||||
|
InitializeColors();
|
||||||
|
InitializeLanguageOptions();
|
||||||
|
InitializeAppearanceOptions();
|
||||||
|
_isInitializingSettings = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeLanguageOptions()
|
||||||
|
{
|
||||||
|
AvailableLanguages = _translationService.GetAvailableLanguages();
|
||||||
|
SelectedLanguage = AvailableLanguages.FirstOrDefault(language => language.Language == _preferencesService.CurrentLanguage)
|
||||||
|
?? AvailableLanguages.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeColors()
|
||||||
|
{
|
||||||
|
Colors.Clear();
|
||||||
|
|
||||||
|
foreach (var color in _newThemeService.GetAvailableAccountColors().Distinct(StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
Colors.Add(new AppColorViewModel(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
var systemAccentColor = _newThemeService.GetSystemAccentColorHex();
|
||||||
|
|
||||||
|
if (Colors.All(color => !string.Equals(color.Hex, systemAccentColor, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
Colors.Add(new AppColorViewModel(systemAccentColor, true));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var matchingAccentColor = Colors.First(color => string.Equals(color.Hex, systemAccentColor, StringComparison.OrdinalIgnoreCase));
|
||||||
|
Colors.Remove(matchingAccentColor);
|
||||||
|
Colors.Add(new AppColorViewModel(systemAccentColor, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeAppearanceOptions()
|
||||||
|
{
|
||||||
|
_isAppearanceSelectionPaused = true;
|
||||||
|
|
||||||
|
var currentAccentColor = _newThemeService.AccentColor;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(currentAccentColor) &&
|
||||||
|
Colors.All(color => !string.Equals(color.Hex, currentAccentColor, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
Colors.Insert(0, new AppColorViewModel(currentAccentColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectedElementTheme = ElementThemes.FirstOrDefault(theme => theme.NativeTheme == _newThemeService.RootTheme)
|
||||||
|
?? ElementThemes.LastOrDefault();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(currentAccentColor))
|
||||||
|
{
|
||||||
|
SelectedAppColor = Colors.LastOrDefault(color => color.IsAccentColor) ?? Colors.LastOrDefault();
|
||||||
|
UseAccentColor = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SelectedAppColor = Colors.FirstOrDefault(color => string.Equals(color.Hex, currentAccentColor, StringComparison.OrdinalIgnoreCase))
|
||||||
|
?? Colors.FirstOrDefault();
|
||||||
|
UseAccentColor = SelectedAppColor?.IsAccentColor == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isAppearanceSelectionPaused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<IAccountProviderDetailViewModel> CreateAccountItems(List<MailAccount> accounts)
|
||||||
|
{
|
||||||
|
var groupedAccounts = accounts
|
||||||
|
.OrderBy(account => account.MergedInboxId == null ? 1 : 0)
|
||||||
|
.ThenBy(account => account.Order)
|
||||||
|
.ThenBy(account => account.Name)
|
||||||
|
.GroupBy(account => account.MergedInboxId);
|
||||||
|
var accountItems = new List<IAccountProviderDetailViewModel>();
|
||||||
|
|
||||||
|
foreach (var accountGroup in groupedAccounts)
|
||||||
|
{
|
||||||
|
if (accountGroup.Key == null)
|
||||||
|
{
|
||||||
|
accountItems.AddRange(accountGroup.Select(CreateAccountProviderDetails));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mergedInbox = accountGroup.First().MergedInbox;
|
||||||
|
var holdingAccounts = accountGroup
|
||||||
|
.Select(CreateAccountProviderDetails)
|
||||||
|
.ToList();
|
||||||
|
var mergedAccount = new MergedAccountProviderDetailViewModel(mergedInbox, holdingAccounts)
|
||||||
|
{
|
||||||
|
ProviderDetail = holdingAccounts.FirstOrDefault()?.ProviderDetail
|
||||||
|
};
|
||||||
|
|
||||||
|
accountItems.Add(mergedAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AccountProviderDetailViewModel CreateAccountProviderDetails(MailAccount account)
|
||||||
|
{
|
||||||
|
var provider = _providerService.GetProviderDetail(account.ProviderType);
|
||||||
|
return new AccountProviderDetailViewModel(provider, account);
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnSelectedLanguageChanged(AppLanguageModel value)
|
||||||
|
{
|
||||||
|
if (_isInitializingSettings || value == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = ApplyLanguageAsync(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnSelectedElementThemeChanged(ElementThemeContainer value)
|
||||||
|
{
|
||||||
|
if (_isInitializingSettings || value == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_newThemeService.RootTheme = value.NativeTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnSelectedAppColorChanged(AppColorViewModel value)
|
||||||
|
{
|
||||||
|
if (_isInitializingSettings || _isAppearanceSelectionPaused || value == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isAppearanceSelectionPaused = true;
|
||||||
|
UseAccentColor = value.IsAccentColor;
|
||||||
|
_isAppearanceSelectionPaused = false;
|
||||||
|
|
||||||
|
_newThemeService.AccentColor = value.Hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnUseAccentColorChanged(bool value)
|
||||||
|
{
|
||||||
|
if (_isInitializingSettings || _isAppearanceSelectionPaused || Colors.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var accentColor = Colors.LastOrDefault(color => color.IsAccentColor);
|
||||||
|
var fallbackColor = Colors.FirstOrDefault(color => !color.IsAccentColor) ?? Colors.FirstOrDefault();
|
||||||
|
var targetColor = value ? accentColor : SelectedAppColor?.IsAccentColor == true ? fallbackColor : SelectedAppColor;
|
||||||
|
|
||||||
|
if (targetColor == null || ReferenceEquals(targetColor, SelectedAppColor))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isAppearanceSelectionPaused = true;
|
||||||
|
SelectedAppColor = targetColor;
|
||||||
|
_isAppearanceSelectionPaused = false;
|
||||||
|
|
||||||
|
_newThemeService.AccentColor = targetColor.Hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ApplyLanguageAsync(AppLanguageModel language)
|
||||||
|
{
|
||||||
|
await _translationService.InitializeLanguageAsync(language.Language);
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public void NavigateSubDetail(object type)
|
public void NavigateSubDetail(object type)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Wino.Core.Domain.Entities.Mail;
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Mail.ViewModels.Collections;
|
using Wino.Mail.ViewModels.Collections;
|
||||||
using Wino.Mail.ViewModels.Data;
|
using Wino.Mail.ViewModels.Data;
|
||||||
@@ -112,6 +113,48 @@ public class WinoMailCollectionTests
|
|||||||
threadItems.Should().Contain(item => item.ThreadId == "thread-c" && item.EmailCount == 2);
|
threadItems.Should().Contain(item => item.ThreadId == "thread-c" && item.EmailCount == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateMailCopy_ShouldMergeExistingSingles_WhenThreadIdChangesToMatch()
|
||||||
|
{
|
||||||
|
var sut = CreateCollection();
|
||||||
|
var first = CreateMailCopy(threadId: "shared-thread", creationDate: DateTime.UtcNow.AddMinutes(-1));
|
||||||
|
var second = CreateMailCopy(threadId: string.Empty, creationDate: DateTime.UtcNow);
|
||||||
|
|
||||||
|
await sut.AddAsync(first);
|
||||||
|
await sut.AddAsync(second);
|
||||||
|
|
||||||
|
var updatedSecond = CloneMailCopy(second);
|
||||||
|
updatedSecond.ThreadId = "shared-thread";
|
||||||
|
|
||||||
|
await sut.UpdateMailCopy(updatedSecond, MailUpdateSource.Server, MailCopyChangeFlags.ThreadId);
|
||||||
|
|
||||||
|
var items = FlattenItems(sut);
|
||||||
|
var threadItem = items.Should().ContainSingle().Which.Should().BeOfType<ThreadMailItemViewModel>().Subject;
|
||||||
|
threadItem.EmailCount.Should().Be(2);
|
||||||
|
threadItem.GetContainingIds().Should().BeEquivalentTo([first.UniqueId, second.UniqueId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AddAsync_ShouldThreadWithUpdatedItem_WhenThreadIdWasSetByPriorUpdate()
|
||||||
|
{
|
||||||
|
var sut = CreateCollection();
|
||||||
|
var existing = CreateMailCopy(threadId: string.Empty, creationDate: DateTime.UtcNow.AddMinutes(-1));
|
||||||
|
var incoming = CreateMailCopy(threadId: "shared-thread", creationDate: DateTime.UtcNow);
|
||||||
|
|
||||||
|
await sut.AddAsync(existing);
|
||||||
|
|
||||||
|
var updatedExisting = CloneMailCopy(existing);
|
||||||
|
updatedExisting.ThreadId = "shared-thread";
|
||||||
|
|
||||||
|
await sut.UpdateMailCopy(updatedExisting, MailUpdateSource.Server, MailCopyChangeFlags.ThreadId);
|
||||||
|
await sut.AddAsync(incoming);
|
||||||
|
|
||||||
|
var items = FlattenItems(sut);
|
||||||
|
var threadItem = items.Should().ContainSingle().Which.Should().BeOfType<ThreadMailItemViewModel>().Subject;
|
||||||
|
threadItem.EmailCount.Should().Be(2);
|
||||||
|
threadItem.GetContainingIds().Should().BeEquivalentTo([existing.UniqueId, incoming.UniqueId]);
|
||||||
|
}
|
||||||
|
|
||||||
private static WinoMailCollection CreateCollection() => new()
|
private static WinoMailCollection CreateCollection() => new()
|
||||||
{
|
{
|
||||||
CoreDispatcher = new ImmediateDispatcher()
|
CoreDispatcher = new ImmediateDispatcher()
|
||||||
@@ -146,6 +189,35 @@ public class WinoMailCollectionTests
|
|||||||
FolderId = Guid.NewGuid()
|
FolderId = Guid.NewGuid()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static MailCopy CloneMailCopy(MailCopy source)
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
UniqueId = source.UniqueId,
|
||||||
|
Id = source.Id,
|
||||||
|
FolderId = source.FolderId,
|
||||||
|
ThreadId = source.ThreadId,
|
||||||
|
MessageId = source.MessageId,
|
||||||
|
References = source.References,
|
||||||
|
InReplyTo = source.InReplyTo,
|
||||||
|
FromName = source.FromName,
|
||||||
|
FromAddress = source.FromAddress,
|
||||||
|
Subject = source.Subject,
|
||||||
|
PreviewText = source.PreviewText,
|
||||||
|
CreationDate = source.CreationDate,
|
||||||
|
Importance = source.Importance,
|
||||||
|
IsRead = source.IsRead,
|
||||||
|
IsFlagged = source.IsFlagged,
|
||||||
|
IsFocused = source.IsFocused,
|
||||||
|
HasAttachments = source.HasAttachments,
|
||||||
|
ItemType = source.ItemType,
|
||||||
|
DraftId = source.DraftId,
|
||||||
|
IsDraft = source.IsDraft,
|
||||||
|
FileId = source.FileId,
|
||||||
|
SenderContact = source.SenderContact,
|
||||||
|
AssignedAccount = source.AssignedAccount,
|
||||||
|
AssignedFolder = source.AssignedFolder
|
||||||
|
};
|
||||||
|
|
||||||
private sealed class ImmediateDispatcher : IDispatcher
|
private sealed class ImmediateDispatcher : IDispatcher
|
||||||
{
|
{
|
||||||
public Task ExecuteOnUIThread(Action action)
|
public Task ExecuteOnUIThread(Action action)
|
||||||
|
|||||||
@@ -213,14 +213,37 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IMailListItem FindThreadableItem(string threadId)
|
private IMailListItem FindThreadableItem(string threadId, Guid? excludedUniqueId = null, IMailListItem excludedItem = null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(threadId) || !_threadIdToItemsMap.TryGetValue(threadId, out var items))
|
if (string.IsNullOrEmpty(threadId) || !_threadIdToItemsMap.TryGetValue(threadId, out var items))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return items.FirstOrDefault();
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(item, excludedItem))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (excludedUniqueId.HasValue)
|
||||||
|
{
|
||||||
|
if (item is MailItemViewModel mailItem && mailItem.MailCopy.UniqueId == excludedUniqueId.Value)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item is ThreadMailItemViewModel threadItem && threadItem.HasUniqueId(excludedUniqueId.Value))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -380,7 +403,8 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
{
|
{
|
||||||
if (item.MailCopy.UniqueId == addedItem.UniqueId)
|
if (item.MailCopy.UniqueId == addedItem.UniqueId)
|
||||||
{
|
{
|
||||||
await UpdateExistingItemAsync(item, addedItem);
|
var existingItemContainer = GetMailItemContainer(addedItem.UniqueId);
|
||||||
|
await UpdateExistingItemAsync(existingItemContainer, addedItem);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -419,7 +443,7 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
var existingItemContainer = GetMailItemContainer(addedItem.UniqueId);
|
var existingItemContainer = GetMailItemContainer(addedItem.UniqueId);
|
||||||
if (existingItemContainer?.ItemViewModel != null)
|
if (existingItemContainer?.ItemViewModel != null)
|
||||||
{
|
{
|
||||||
await UpdateExistingItemAsync(existingItemContainer.ItemViewModel, addedItem);
|
await UpdateExistingItemAsync(existingItemContainer, addedItem);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -478,16 +502,83 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
await InsertItemInternalAsync(groupKey, newMailItem);
|
await InsertItemInternalAsync(groupKey, newMailItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateExistingItemAsync(MailItemViewModel existingItem, MailCopy updatedItem)
|
private async Task ReinsertUpdatedItemAsync(MailCopy updatedItem, bool isSelected, bool isBusy)
|
||||||
{
|
{
|
||||||
UpdateUniqueIdHashes(existingItem, false);
|
await RemoveAsync(updatedItem);
|
||||||
|
await AddAsync(updatedItem);
|
||||||
|
|
||||||
|
var updatedContainer = GetMailItemContainer(updatedItem.UniqueId);
|
||||||
|
if (updatedContainer?.ItemViewModel == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await ExecuteUIThread(() =>
|
await ExecuteUIThread(() =>
|
||||||
{
|
{
|
||||||
existingItem.UpdateFrom(updatedItem);
|
updatedContainer.ItemViewModel.IsSelected = isSelected;
|
||||||
|
updatedContainer.ItemViewModel.IsBusy = isBusy;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateExistingItemAsync(MailItemContainer itemContainer,
|
||||||
|
MailCopy updatedItem,
|
||||||
|
MailUpdateSource mailUpdateSource = MailUpdateSource.Server,
|
||||||
|
MailCopyChangeFlags changeHint = MailCopyChangeFlags.None)
|
||||||
|
{
|
||||||
|
if (itemContainer?.ItemViewModel == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var existingItem = itemContainer.ItemViewModel;
|
||||||
|
var threadOwner = itemContainer.ThreadViewModel as IMailListItem ?? existingItem;
|
||||||
|
var wasSelected = existingItem.IsSelected;
|
||||||
|
MailCopyChangeFlags appliedChanges = MailCopyChangeFlags.None;
|
||||||
|
|
||||||
|
await ExecuteUIThread(() =>
|
||||||
|
{
|
||||||
|
UpdateUniqueIdHashes(existingItem, false);
|
||||||
|
UpdateThreadIdCache(threadOwner, false);
|
||||||
|
|
||||||
|
itemContainer.ThreadViewModel?.SuspendChildPropertyNotifications();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
appliedChanges = existingItem.UpdateFrom(updatedItem, changeHint);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
itemContainer.ThreadViewModel?.ResumeChildPropertyNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
existingItem.IsBusy = mailUpdateSource == MailUpdateSource.ClientUpdated;
|
||||||
|
|
||||||
|
UpdateUniqueIdHashes(existingItem, true);
|
||||||
|
UpdateThreadIdCache(threadOwner, true);
|
||||||
|
|
||||||
|
if (itemContainer.ThreadViewModel != null)
|
||||||
|
{
|
||||||
|
_uniqueIdToThreadMap[existingItem.MailCopy.UniqueId] = itemContainer.ThreadViewModel;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_uniqueIdToThreadMap.TryRemove(existingItem.MailCopy.UniqueId, out _);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
UpdateUniqueIdHashes(existingItem, true);
|
if ((appliedChanges & MailCopyChangeFlags.ThreadId) != 0)
|
||||||
|
{
|
||||||
|
await ReinsertUpdatedItemAsync(updatedItem, wasSelected, existingItem.IsBusy);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemContainer.ThreadViewModel != null && appliedChanges != MailCopyChangeFlags.None)
|
||||||
|
{
|
||||||
|
await ExecuteUIThread(() =>
|
||||||
|
{
|
||||||
|
itemContainer.ThreadViewModel.NotifyMailItemUpdated(existingItem, appliedChanges);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -755,45 +846,14 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Task UpdateMailCopy(MailCopy updatedMailCopy, MailUpdateSource mailUpdateSource, MailCopyChangeFlags changedProperties = MailCopyChangeFlags.None)
|
public Task UpdateMailCopy(MailCopy updatedMailCopy, MailUpdateSource mailUpdateSource, MailCopyChangeFlags changedProperties = MailCopyChangeFlags.None)
|
||||||
{
|
{
|
||||||
return ExecuteUIThread(() =>
|
var itemContainer = GetMailItemContainer(updatedMailCopy.UniqueId);
|
||||||
|
|
||||||
|
if (itemContainer?.ItemViewModel == null)
|
||||||
{
|
{
|
||||||
var itemContainer = GetMailItemContainer(updatedMailCopy.UniqueId);
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
if (itemContainer == null) return;
|
return UpdateExistingItemAsync(itemContainer, updatedMailCopy, mailUpdateSource, changedProperties);
|
||||||
MailCopyChangeFlags appliedChanges = MailCopyChangeFlags.None;
|
|
||||||
|
|
||||||
if (itemContainer.ItemViewModel != null)
|
|
||||||
{
|
|
||||||
UpdateUniqueIdHashes(itemContainer.ItemViewModel, false);
|
|
||||||
|
|
||||||
itemContainer.ThreadViewModel?.SuspendChildPropertyNotifications();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
appliedChanges = itemContainer.ItemViewModel.UpdateFrom(updatedMailCopy, changedProperties);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
itemContainer.ThreadViewModel?.ResumeChildPropertyNotifications();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark the item view model as busy until the network operation is completed.
|
|
||||||
itemContainer.ItemViewModel.IsBusy = mailUpdateSource == MailUpdateSource.ClientUpdated;
|
|
||||||
|
|
||||||
UpdateUniqueIdHashes(itemContainer.ItemViewModel, true);
|
|
||||||
|
|
||||||
// Keep thread membership cache in sync for items rendered inside thread containers.
|
|
||||||
if (itemContainer.ThreadViewModel != null)
|
|
||||||
{
|
|
||||||
_uniqueIdToThreadMap[itemContainer.ItemViewModel.MailCopy.UniqueId] = itemContainer.ThreadViewModel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itemContainer.ThreadViewModel != null && appliedChanges != MailCopyChangeFlags.None)
|
|
||||||
{
|
|
||||||
itemContainer.ThreadViewModel.NotifyMailItemUpdated(itemContainer.ItemViewModel, appliedChanges);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MailItemViewModel GetFirst() => AllItems.ElementAtOrDefault(0);
|
public MailItemViewModel GetFirst() => AllItems.ElementAtOrDefault(0);
|
||||||
|
|||||||
@@ -12,22 +12,8 @@ public partial class MessageListPageViewModel : MailBaseViewModel
|
|||||||
{
|
{
|
||||||
public IPreferencesService PreferencesService { get; }
|
public IPreferencesService PreferencesService { get; }
|
||||||
private readonly IThumbnailService _thumbnailService;
|
private readonly IThumbnailService _thumbnailService;
|
||||||
|
private readonly IStatePersistanceService _statePersistenceService;
|
||||||
private int selectedMarkAsOptionIndex;
|
private readonly IDialogServiceBase _dialogService;
|
||||||
public int SelectedMarkAsOptionIndex
|
|
||||||
{
|
|
||||||
get => selectedMarkAsOptionIndex;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (SetProperty(ref selectedMarkAsOptionIndex, value))
|
|
||||||
{
|
|
||||||
if (value >= 0)
|
|
||||||
{
|
|
||||||
PreferencesService.MarkAsPreference = (MailMarkAsOption)Enum.GetValues<MailMarkAsOption>().GetValue(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly List<MailOperation> availableHoverActions =
|
private readonly List<MailOperation> availableHoverActions =
|
||||||
[
|
[
|
||||||
@@ -38,6 +24,13 @@ public partial class MessageListPageViewModel : MailBaseViewModel
|
|||||||
MailOperation.MoveToJunk
|
MailOperation.MoveToJunk
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private readonly List<MailListDisplayMode> availableMailSpacingOptions =
|
||||||
|
[
|
||||||
|
MailListDisplayMode.Compact,
|
||||||
|
MailListDisplayMode.Medium,
|
||||||
|
MailListDisplayMode.Spacious
|
||||||
|
];
|
||||||
|
|
||||||
public List<string> AvailableHoverActionsTranslations { get; set; } =
|
public List<string> AvailableHoverActionsTranslations { get; set; } =
|
||||||
[
|
[
|
||||||
Translator.HoverActionOption_Archive,
|
Translator.HoverActionOption_Archive,
|
||||||
@@ -47,6 +40,32 @@ public partial class MessageListPageViewModel : MailBaseViewModel
|
|||||||
Translator.HoverActionOption_MoveJunk
|
Translator.HoverActionOption_MoveJunk
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private int selectedMarkAsOptionIndex;
|
||||||
|
public int SelectedMarkAsOptionIndex
|
||||||
|
{
|
||||||
|
get => selectedMarkAsOptionIndex;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref selectedMarkAsOptionIndex, value) && value >= 0)
|
||||||
|
{
|
||||||
|
PreferencesService.MarkAsPreference = (MailMarkAsOption)Enum.GetValues<MailMarkAsOption>().GetValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int selectedMailSpacingIndex;
|
||||||
|
public int SelectedMailSpacingIndex
|
||||||
|
{
|
||||||
|
get => selectedMailSpacingIndex;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref selectedMailSpacingIndex, value) && value >= 0 && value < availableMailSpacingOptions.Count)
|
||||||
|
{
|
||||||
|
PreferencesService.MailItemDisplayMode = availableMailSpacingOptions[value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
private int leftHoverActionIndex;
|
private int leftHoverActionIndex;
|
||||||
public int LeftHoverActionIndex
|
public int LeftHoverActionIndex
|
||||||
@@ -88,13 +107,19 @@ public partial class MessageListPageViewModel : MailBaseViewModel
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public MessageListPageViewModel(IPreferencesService preferencesService, IThumbnailService thumbnailService)
|
public MessageListPageViewModel(IPreferencesService preferencesService,
|
||||||
|
IThumbnailService thumbnailService,
|
||||||
|
IStatePersistanceService statePersistenceService,
|
||||||
|
IDialogServiceBase dialogService)
|
||||||
{
|
{
|
||||||
PreferencesService = preferencesService;
|
PreferencesService = preferencesService;
|
||||||
_thumbnailService = thumbnailService;
|
_thumbnailService = thumbnailService;
|
||||||
|
_statePersistenceService = statePersistenceService;
|
||||||
|
_dialogService = dialogService;
|
||||||
leftHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.LeftHoverAction);
|
leftHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.LeftHoverAction);
|
||||||
centerHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.CenterHoverAction);
|
centerHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.CenterHoverAction);
|
||||||
rightHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.RightHoverAction);
|
rightHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.RightHoverAction);
|
||||||
|
selectedMailSpacingIndex = availableMailSpacingOptions.IndexOf(PreferencesService.MailItemDisplayMode);
|
||||||
SelectedMarkAsOptionIndex = Array.IndexOf(Enum.GetValues<MailMarkAsOption>(), PreferencesService.MarkAsPreference);
|
SelectedMarkAsOptionIndex = Array.IndexOf(Enum.GetValues<MailMarkAsOption>(), PreferencesService.MarkAsPreference);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,4 +128,11 @@ public partial class MessageListPageViewModel : MailBaseViewModel
|
|||||||
{
|
{
|
||||||
await _thumbnailService.ClearCache();
|
await _thumbnailService.ClearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void ResetMailListPaneLength()
|
||||||
|
{
|
||||||
|
_statePersistenceService.MailListPaneLength = 420;
|
||||||
|
_dialogService.InfoBarMessage(Translator.GeneralTitle_Info, Translator.Info_MailListSizeResetSuccessMessage, InfoBarMessageType.Success);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,10 @@
|
|||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using CommunityToolkit.WinUI.Controls;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Models.Settings;
|
using Wino.Core.Domain.Models.Settings;
|
||||||
|
using Wino.Core.ViewModels.Data;
|
||||||
|
using Wino.Mail.ViewModels.Data;
|
||||||
using Wino.Views.Abstract;
|
using Wino.Views.Abstract;
|
||||||
|
|
||||||
namespace Wino.Views.Settings;
|
namespace Wino.Views.Settings;
|
||||||
@@ -17,7 +20,7 @@ public sealed partial class SettingOptionsPage : SettingOptionsPageAbstract
|
|||||||
{
|
{
|
||||||
WinoPage? page = sender switch
|
WinoPage? page = sender switch
|
||||||
{
|
{
|
||||||
Button button when button.Tag is WinoPage p => p,
|
FrameworkElement element when element.Tag is WinoPage p => p,
|
||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -27,6 +30,27 @@ public sealed partial class SettingOptionsPage : SettingOptionsPageAbstract
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AccountSettingClicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is SettingsCard settingsCard && settingsCard.CommandParameter is AccountProviderDetailViewModel account)
|
||||||
|
{
|
||||||
|
ViewModel.NavigateToAccount(account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MergedAccountSettingClicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is SettingsCard settingsCard && settingsCard.CommandParameter is MergedAccountProviderDetailViewModel account)
|
||||||
|
{
|
||||||
|
ViewModel.NavigateToAccount(account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddAccountSettingClicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
ViewModel.NavigateToAddAccount();
|
||||||
|
}
|
||||||
|
|
||||||
private void SettingsSearchTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
|
private void SettingsSearchTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
|
||||||
{
|
{
|
||||||
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput || string.IsNullOrWhiteSpace(sender.Text))
|
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput || string.IsNullOrWhiteSpace(sender.Text))
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -184,37 +184,6 @@
|
|||||||
Foreground="{ThemeResource InfoBarWarningSeverityIconBackground}"
|
Foreground="{ThemeResource InfoBarWarningSeverityIconBackground}"
|
||||||
Text="{x:Bind domain:Translator.SettingsWindowBackdrop_Disabled}" />
|
Text="{x:Bind domain:Translator.SettingsWindowBackdrop_Disabled}" />
|
||||||
|
|
||||||
<!-- Mail spacing. -->
|
|
||||||
<controls:SettingsExpander Description="{x:Bind domain:Translator.SettingsMailSpacing_Description}" Header="{x:Bind domain:Translator.SettingsMailSpacing_Title}">
|
|
||||||
<controls:SettingsExpander.HeaderIcon>
|
|
||||||
<PathIcon Data="F1 M 17.5 9.375 C 17.832031 9.375 18.149414 9.440104 18.452148 9.570312 C 18.754883 9.700521 19.020182 9.876303 19.248047 10.097656 C 19.47591 10.319011 19.658203 10.579428 19.794922 10.878906 C 19.931641 11.178386 20 11.494141 20 11.826172 L 20 15.048828 C 20 15.37435 19.933268 15.685222 19.799805 15.981445 C 19.66634 16.27767 19.487305 16.538086 19.262695 16.762695 C 19.038086 16.987305 18.777668 17.166342 18.481445 17.299805 C 18.185221 17.433268 17.874348 17.5 17.548828 17.5 L 2.451172 17.5 C 2.125651 17.5 1.814779 17.433268 1.518555 17.299805 C 1.222331 17.166342 0.961914 16.987305 0.737305 16.762695 C 0.512695 16.538086 0.333659 16.27767 0.200195 15.981445 C 0.066732 15.685222 0 15.37435 0 15.048828 L 0 4.951172 C 0 4.625651 0.066732 4.314779 0.200195 4.018555 C 0.333659 3.722332 0.512695 3.461914 0.737305 3.237305 C 0.961914 3.012695 1.222331 2.83366 1.518555 2.700195 C 1.814779 2.566732 2.125651 2.5 2.451172 2.5 L 12.548828 2.5 C 12.887369 2.5 13.203125 2.565105 13.496094 2.695312 C 13.789062 2.825521 14.044596 3.00293 14.262695 3.227539 C 14.480793 3.452148 14.654947 3.712566 14.785156 4.008789 C 14.915364 4.305014 14.986979 4.619141 15 4.951172 C 15.00651 5.022787 15.009766 5.094402 15.009766 5.166016 C 15.009766 5.237631 15.009766 5.309245 15.009766 5.380859 C 15.009766 5.5306 15.008138 5.677084 15.004883 5.820312 C 15.001627 5.963542 14.999999 6.106771 15 6.25 C 15.33203 6.25 15.649413 6.315104 15.952148 6.445312 C 16.254883 6.575521 16.520182 6.751303 16.748047 6.972656 C 16.97591 7.194011 17.156574 7.4528 17.290039 7.749023 C 17.423502 8.045248 17.493488 8.362631 17.5 8.701172 Z M 1.25 15 C 1.25 15.175781 1.282552 15.34017 1.347656 15.493164 C 1.41276 15.646159 1.500651 15.777995 1.611328 15.888672 C 1.722005 15.99935 1.853841 16.08724 2.006836 16.152344 C 2.159831 16.217449 2.324219 16.25 2.5 16.25 L 6.582031 16.25 C 6.360677 15.859375 6.25 15.442709 6.25 15 L 6.25 8.701172 C 6.25 8.362631 6.318359 8.045248 6.455078 7.749023 C 6.591797 7.4528 6.774088 7.194011 7.001953 6.972656 C 7.229817 6.751303 7.495117 6.575521 7.797852 6.445312 C 8.100586 6.315104 8.417969 6.25 8.75 6.25 L 13.75 6.25 L 13.75 5 C 13.75 4.830729 13.717447 4.669597 13.652344 4.516602 C 13.587239 4.363607 13.497721 4.230144 13.383789 4.116211 C 13.269855 4.002279 13.136393 3.912762 12.983398 3.847656 C 12.830403 3.782553 12.669271 3.75 12.5 3.75 L 2.5 3.75 C 2.324219 3.75 2.161458 3.782553 2.011719 3.847656 C 1.861979 3.912762 1.730143 4.002279 1.616211 4.116211 C 1.502279 4.230144 1.41276 4.361979 1.347656 4.511719 C 1.282552 4.661459 1.25 4.82422 1.25 5 Z M 12.207031 16.25 C 11.985677 15.859375 11.875 15.442709 11.875 15 L 11.875 11.826172 C 11.875 11.487631 11.943359 11.170248 12.080078 10.874023 C 12.216797 10.5778 12.399088 10.319011 12.626953 10.097656 C 12.854817 9.876303 13.120117 9.700521 13.422852 9.570312 C 13.725586 9.440104 14.042969 9.375 14.375 9.375 L 16.25 9.375 L 16.25 8.75 C 16.25 8.580729 16.217447 8.419597 16.152344 8.266602 C 16.087238 8.113607 15.997721 7.980144 15.883789 7.866211 C 15.769856 7.752279 15.636393 7.662762 15.483398 7.597656 C 15.330403 7.532553 15.169271 7.5 15 7.5 L 8.75 7.5 C 8.574219 7.5 8.411458 7.532553 8.261719 7.597656 C 8.111979 7.662762 7.980143 7.752279 7.866211 7.866211 C 7.752278 7.980144 7.66276 8.111979 7.597656 8.261719 C 7.532552 8.411459 7.5 8.574219 7.5 8.75 L 7.5 15 C 7.5 15.175781 7.532552 15.34017 7.597656 15.493164 C 7.66276 15.646159 7.750651 15.777995 7.861328 15.888672 C 7.972005 15.99935 8.103841 16.08724 8.256836 16.152344 C 8.40983 16.217449 8.574219 16.25 8.75 16.25 Z M 18.75 11.875 C 18.75 11.705729 18.717447 11.544597 18.652344 11.391602 C 18.587238 11.238607 18.497721 11.105144 18.383789 10.991211 C 18.269855 10.877279 18.136393 10.787761 17.983398 10.722656 C 17.830402 10.657553 17.66927 10.625 17.5 10.625 L 14.375 10.625 C 14.199219 10.625 14.036458 10.657553 13.886719 10.722656 C 13.736979 10.787761 13.605143 10.877279 13.491211 10.991211 C 13.377278 11.105144 13.28776 11.236979 13.222656 11.386719 C 13.157551 11.536459 13.124999 11.699219 13.125 11.875 L 13.125 15 C 13.124999 15.175781 13.157551 15.34017 13.222656 15.493164 C 13.28776 15.646159 13.37565 15.777995 13.486328 15.888672 C 13.597004 15.99935 13.72884 16.08724 13.881836 16.152344 C 14.03483 16.217449 14.199219 16.25 14.375 16.25 L 17.5 16.25 C 17.675781 16.25 17.838541 16.217449 17.988281 16.152344 C 18.13802 16.08724 18.269855 15.997722 18.383789 15.883789 C 18.497721 15.769857 18.587238 15.638021 18.652344 15.488281 C 18.717447 15.338542 18.75 15.175781 18.75 15 Z " />
|
|
||||||
</controls:SettingsExpander.HeaderIcon>
|
|
||||||
|
|
||||||
<controls:SettingsExpander.Items>
|
|
||||||
<controls:SettingsCard
|
|
||||||
HorizontalContentAlignment="Stretch"
|
|
||||||
VerticalContentAlignment="Stretch"
|
|
||||||
ContentAlignment="Vertical">
|
|
||||||
<ListView
|
|
||||||
Margin="-18,0"
|
|
||||||
toolkitExt:ListViewExtensions.ItemContainerStretchDirection="Horizontal"
|
|
||||||
ItemTemplateSelector="{StaticResource MailItemDisplayModePreviewTemplateSelector}"
|
|
||||||
ItemsSource="{x:Bind ViewModel.InformationDisplayModes}"
|
|
||||||
SelectedItem="{x:Bind ViewModel.SelectedInfoDisplayMode, Mode=TwoWay}" />
|
|
||||||
</controls:SettingsCard>
|
|
||||||
</controls:SettingsExpander.Items>
|
|
||||||
</controls:SettingsExpander>
|
|
||||||
|
|
||||||
<!-- Pane Length Reset -->
|
|
||||||
<controls:SettingsCard Description="{x:Bind domain:Translator.SettingsPaneLengthReset_Description}" Header="{x:Bind domain:Translator.SettingsPaneLengthReset_Title}">
|
|
||||||
<controls:SettingsCard.HeaderIcon>
|
|
||||||
<PathIcon Data="F1 M 3.056641 17.5 C 2.646484 17.5 2.255859 17.416992 1.884766 17.250977 C 1.513672 17.084961 1.189779 16.863607 0.913086 16.586914 C 0.636393 16.310221 0.415039 15.986328 0.249023 15.615234 C 0.083008 15.244141 0 14.853516 0 14.443359 L 0 5.556641 C 0 5.146485 0.083008 4.75586 0.249023 4.384766 C 0.415039 4.013673 0.636393 3.689779 0.913086 3.413086 C 1.189779 3.136395 1.513672 2.915039 1.884766 2.749023 C 2.255859 2.583008 2.646484 2.5 3.056641 2.5 L 16.943359 2.5 C 17.353516 2.5 17.744141 2.583008 18.115234 2.749023 C 18.486328 2.915039 18.810221 3.136395 19.086914 3.413086 C 19.363605 3.689779 19.584961 4.013673 19.750977 4.384766 C 19.916992 4.75586 20 5.146485 20 5.556641 L 20 14.443359 C 20 14.853516 19.916992 15.244141 19.750977 15.615234 C 19.584961 15.986328 19.363605 16.310221 19.086914 16.586914 C 18.810221 16.863607 18.486328 17.084961 18.115234 17.250977 C 17.744141 17.416992 17.353516 17.5 16.943359 17.5 Z M 12.5 16.25 L 12.5 3.75 L 3.125 3.75 C 2.871094 3.75 2.630208 3.798828 2.402344 3.896484 C 2.174479 3.994141 1.974284 4.129232 1.801758 4.301758 C 1.629232 4.474285 1.494141 4.67448 1.396484 4.902344 C 1.298828 5.130209 1.25 5.371094 1.25 5.625 L 1.25 14.375 C 1.25 14.628906 1.298828 14.869792 1.396484 15.097656 C 1.494141 15.325521 1.629232 15.525717 1.801758 15.698242 C 1.974284 15.870769 2.174479 16.005859 2.402344 16.103516 C 2.630208 16.201172 2.871094 16.25 3.125 16.25 Z M 16.875 16.25 C 17.128906 16.25 17.369791 16.201172 17.597656 16.103516 C 17.82552 16.005859 18.025715 15.870769 18.198242 15.698242 C 18.370768 15.525717 18.505859 15.325521 18.603516 15.097656 C 18.701172 14.869792 18.75 14.628906 18.75 14.375 L 18.75 5.625 C 18.75 5.371094 18.701172 5.130209 18.603516 4.902344 C 18.505859 4.67448 18.370768 4.474285 18.198242 4.301758 C 18.025715 4.129232 17.82552 3.994141 17.597656 3.896484 C 17.369791 3.798828 17.128906 3.75 16.875 3.75 L 13.75 3.75 L 13.75 16.25 Z M 7.5 11.875 C 7.5 11.705729 7.561849 11.559245 7.685547 11.435547 L 9.121094 10 L 3.125 10 C 2.955729 10 2.809245 9.938151 2.685547 9.814453 C 2.561849 9.690756 2.5 9.544271 2.5 9.375 C 2.5 9.205729 2.561849 9.059245 2.685547 8.935547 C 2.809245 8.81185 2.955729 8.75 3.125 8.75 L 9.121094 8.75 L 7.685547 7.314453 C 7.561849 7.190756 7.5 7.044271 7.5 6.875 C 7.5 6.705729 7.561849 6.559245 7.685547 6.435547 C 7.809245 6.31185 7.955729 6.25 8.125 6.25 C 8.294271 6.25 8.440755 6.31185 8.564453 6.435547 L 11.064453 8.935547 C 11.18815 9.059245 11.25 9.205729 11.25 9.375 C 11.25 9.544271 11.18815 9.690756 11.064453 9.814453 L 8.564453 12.314453 C 8.440755 12.438151 8.294271 12.5 8.125 12.5 C 7.955729 12.5 7.809245 12.438151 7.685547 12.314453 C 7.561849 12.190756 7.5 12.044271 7.5 11.875 Z " />
|
|
||||||
</controls:SettingsCard.HeaderIcon>
|
|
||||||
|
|
||||||
<controls:SettingsCard.Content>
|
|
||||||
<Button Command="{x:Bind ViewModel.ResetMailListPaneLengthCommand}" Content="{x:Bind domain:Translator.Buttons_Reset}" />
|
|
||||||
</controls:SettingsCard.Content>
|
|
||||||
</controls:SettingsCard>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</abstract:PersonalizationPageAbstract>
|
</abstract:PersonalizationPageAbstract>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
MaxWidth="900"
|
MaxWidth="900"
|
||||||
Padding="20"
|
Padding="20"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
RowSpacing="20">
|
RowSpacing="4">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
|
|||||||
Reference in New Issue
Block a user