Revert "File scoped namespaces"

This reverts commit d31d8f574e.
This commit is contained in:
Burak Kaan Köse
2025-02-16 11:43:30 +01:00
parent d31d8f574e
commit cf9869b71e
617 changed files with 32097 additions and 31478 deletions

View File

@@ -5,18 +5,19 @@ using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Animation;
using Wino.Views;
namespace Wino.Activation;
internal class DefaultActivationHandler : ActivationHandler<IActivatedEventArgs>
namespace Wino.Activation
{
protected override Task HandleInternalAsync(IActivatedEventArgs args)
internal class DefaultActivationHandler : ActivationHandler<IActivatedEventArgs>
{
(Window.Current.Content as Frame).Navigate(typeof(AppShell), null, new DrillInNavigationTransitionInfo());
protected override Task HandleInternalAsync(IActivatedEventArgs args)
{
(Window.Current.Content as Frame).Navigate(typeof(AppShell), null, new DrillInNavigationTransitionInfo());
return Task.CompletedTask;
return Task.CompletedTask;
}
// Only navigate if Frame content doesn't exist.
protected override bool CanHandleInternal(IActivatedEventArgs args)
=> (Window.Current?.Content as Frame)?.Content == null;
}
// Only navigate if Frame content doesn't exist.
protected override bool CanHandleInternal(IActivatedEventArgs args)
=> (Window.Current?.Content as Frame)?.Content == null;
}

View File

@@ -12,56 +12,57 @@ using Wino.Core.Domain.Interfaces;
using Wino.Core.UWP.Extensions;
using Wino.Views;
namespace Wino.Activation;
internal class FileActivationHandler : ActivationHandler<FileActivatedEventArgs>
namespace Wino.Activation
{
private readonly INativeAppService _nativeAppService;
private readonly IMimeFileService _mimeFileService;
private readonly IStatePersistanceService _statePersistanceService;
private readonly INavigationService _winoNavigationService;
public FileActivationHandler(INativeAppService nativeAppService,
IMimeFileService mimeFileService,
IStatePersistanceService statePersistanceService,
INavigationService winoNavigationService)
internal class FileActivationHandler : ActivationHandler<FileActivatedEventArgs>
{
_nativeAppService = nativeAppService;
_mimeFileService = mimeFileService;
_statePersistanceService = statePersistanceService;
_winoNavigationService = winoNavigationService;
}
private readonly INativeAppService _nativeAppService;
private readonly IMimeFileService _mimeFileService;
private readonly IStatePersistanceService _statePersistanceService;
private readonly INavigationService _winoNavigationService;
protected override async Task HandleInternalAsync(FileActivatedEventArgs args)
{
// Always handle the last item passed.
// Multiple files are not supported.
var file = args.Files.Last() as StorageFile;
// Only EML files are supported now.
var fileExtension = Path.GetExtension(file.Path);
if (string.Equals(fileExtension, ".eml", StringComparison.OrdinalIgnoreCase))
public FileActivationHandler(INativeAppService nativeAppService,
IMimeFileService mimeFileService,
IStatePersistanceService statePersistanceService,
INavigationService winoNavigationService)
{
var fileBytes = await file.ToByteArrayAsync();
var directoryName = Path.GetDirectoryName(file.Path);
_nativeAppService = nativeAppService;
_mimeFileService = mimeFileService;
_statePersistanceService = statePersistanceService;
_winoNavigationService = winoNavigationService;
}
var messageInformation = await _mimeFileService.GetMimeMessageInformationAsync(fileBytes, directoryName).ConfigureAwait(false);
protected override async Task HandleInternalAsync(FileActivatedEventArgs args)
{
// Always handle the last item passed.
// Multiple files are not supported.
if (_nativeAppService.IsAppRunning())
var file = args.Files.Last() as StorageFile;
// Only EML files are supported now.
var fileExtension = Path.GetExtension(file.Path);
if (string.Equals(fileExtension, ".eml", StringComparison.OrdinalIgnoreCase))
{
// TODO: Activate another Window and go to mail rendering page.
_winoNavigationService.Navigate(WinoPage.MailRenderingPage, messageInformation, NavigationReferenceFrame.RenderingFrame);
}
else
{
_statePersistanceService.ShouldShiftMailRenderingDesign = true;
(Window.Current.Content as Frame).Navigate(typeof(MailRenderingPage), messageInformation, new DrillInNavigationTransitionInfo());
var fileBytes = await file.ToByteArrayAsync();
var directoryName = Path.GetDirectoryName(file.Path);
var messageInformation = await _mimeFileService.GetMimeMessageInformationAsync(fileBytes, directoryName).ConfigureAwait(false);
if (_nativeAppService.IsAppRunning())
{
// TODO: Activate another Window and go to mail rendering page.
_winoNavigationService.Navigate(WinoPage.MailRenderingPage, messageInformation, NavigationReferenceFrame.RenderingFrame);
}
else
{
_statePersistanceService.ShouldShiftMailRenderingDesign = true;
(Window.Current.Content as Frame).Navigate(typeof(MailRenderingPage), messageInformation, new DrillInNavigationTransitionInfo());
}
}
}
protected override bool CanHandleInternal(FileActivatedEventArgs args) => args.Files.Any();
}
protected override bool CanHandleInternal(FileActivatedEventArgs args) => args.Files.Any();
}

View File

@@ -6,54 +6,55 @@ using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Launch;
using Wino.Messaging.Client.Shell;
namespace Wino.Activation;
internal class ProtocolActivationHandler : ActivationHandler<ProtocolActivatedEventArgs>
namespace Wino.Activation
{
private const string MailtoProtocolTag = "mailto:";
private readonly INativeAppService _nativeAppService;
private readonly ILaunchProtocolService _launchProtocolService;
public ProtocolActivationHandler(INativeAppService nativeAppService, ILaunchProtocolService launchProtocolService)
internal class ProtocolActivationHandler : ActivationHandler<ProtocolActivatedEventArgs>
{
_nativeAppService = nativeAppService;
_launchProtocolService = launchProtocolService;
}
private const string MailtoProtocolTag = "mailto:";
protected override Task HandleInternalAsync(ProtocolActivatedEventArgs args)
{
// Check URI prefix.
var protocolString = args.Uri.AbsoluteUri;
private readonly INativeAppService _nativeAppService;
private readonly ILaunchProtocolService _launchProtocolService;
if (protocolString.StartsWith(MailtoProtocolTag))
public ProtocolActivationHandler(INativeAppService nativeAppService, ILaunchProtocolService launchProtocolService)
{
// mailto activation. Try to parse params.
_launchProtocolService.MailToUri = new MailToUri(protocolString);
_nativeAppService = nativeAppService;
_launchProtocolService = launchProtocolService;
}
if (_nativeAppService.IsAppRunning())
protected override Task HandleInternalAsync(ProtocolActivatedEventArgs args)
{
// Check URI prefix.
var protocolString = args.Uri.AbsoluteUri;
if (protocolString.StartsWith(MailtoProtocolTag))
{
// Just send publish a message. Shell will continue.
WeakReferenceMessenger.Default.Send(new MailtoProtocolMessageRequested());
// mailto activation. Try to parse params.
_launchProtocolService.MailToUri = new MailToUri(protocolString);
if (_nativeAppService.IsAppRunning())
{
// Just send publish a message. Shell will continue.
WeakReferenceMessenger.Default.Send(new MailtoProtocolMessageRequested());
}
}
return Task.CompletedTask;
}
return Task.CompletedTask;
}
protected override bool CanHandleInternal(ProtocolActivatedEventArgs args)
{
// Validate the URI scheme.
try
protected override bool CanHandleInternal(ProtocolActivatedEventArgs args)
{
var uriGet = args.Uri;
}
catch (UriFormatException)
{
return false;
}
// Validate the URI scheme.
return base.CanHandleInternal(args);
try
{
var uriGet = args.Uri;
}
catch (UriFormatException)
{
return false;
}
return base.CanHandleInternal(args);
}
}
}

View File

@@ -9,67 +9,68 @@ using Wino.Core.Domain;
using Wino.Core.Domain.Interfaces;
using Wino.Messaging.Client.Accounts;
namespace Wino.Activation;
/// <summary>
/// This handler will only handle the toasts that runs on foreground.
/// Background executions are not handled here like mark as read or delete.
/// </summary>
internal class ToastNotificationActivationHandler : ActivationHandler<ToastNotificationActivatedEventArgs>
namespace Wino.Activation
{
private readonly IMailService _mailService;
private readonly IFolderService _folderService;
private ToastArguments _toastArguments;
public ToastNotificationActivationHandler(IMailService mailService,
IFolderService folderService)
/// <summary>
/// This handler will only handle the toasts that runs on foreground.
/// Background executions are not handled here like mark as read or delete.
/// </summary>
internal class ToastNotificationActivationHandler : ActivationHandler<ToastNotificationActivatedEventArgs>
{
_mailService = mailService;
_folderService = folderService;
}
private readonly IMailService _mailService;
private readonly IFolderService _folderService;
protected override async Task HandleInternalAsync(ToastNotificationActivatedEventArgs args)
{
// Create the mail item navigation event.
// If the app is running, it'll be picked up by the Messenger.
// Otherwise we'll save it and handle it when the shell loads all accounts.
private ToastArguments _toastArguments;
// Parse the mail unique id and perform above actions.
if (Guid.TryParse(_toastArguments[Constants.ToastMailUniqueIdKey], out Guid mailItemUniqueId))
public ToastNotificationActivationHandler(IMailService mailService,
IFolderService folderService)
{
var account = await _mailService.GetMailAccountByUniqueIdAsync(mailItemUniqueId).ConfigureAwait(false);
if (account == null) return;
var mailItem = await _mailService.GetSingleMailItemAsync(mailItemUniqueId).ConfigureAwait(false);
if (mailItem == null) return;
var message = new AccountMenuItemExtended(mailItem.AssignedFolder.Id, mailItem);
// Delegate this event to LaunchProtocolService so app shell can pick it up on launch if app doesn't work.
var launchProtocolService = App.Current.Services.GetService<ILaunchProtocolService>();
launchProtocolService.LaunchParameter = message;
// Send the messsage anyways. Launch protocol service will be ignored if the message is picked up by subscriber shell.
WeakReferenceMessenger.Default.Send(message);
}
}
protected override bool CanHandleInternal(ToastNotificationActivatedEventArgs args)
{
try
{
_toastArguments = ToastArguments.Parse(args.Argument);
return
_toastArguments.Contains(Constants.ToastMailUniqueIdKey) &&
_toastArguments.Contains(Constants.ToastActionKey);
}
catch (Exception ex)
{
Log.Error(ex, "Couldn't handle parsing toast notification arguments for foreground navigate.");
_mailService = mailService;
_folderService = folderService;
}
return false;
protected override async Task HandleInternalAsync(ToastNotificationActivatedEventArgs args)
{
// Create the mail item navigation event.
// If the app is running, it'll be picked up by the Messenger.
// Otherwise we'll save it and handle it when the shell loads all accounts.
// Parse the mail unique id and perform above actions.
if (Guid.TryParse(_toastArguments[Constants.ToastMailUniqueIdKey], out Guid mailItemUniqueId))
{
var account = await _mailService.GetMailAccountByUniqueIdAsync(mailItemUniqueId).ConfigureAwait(false);
if (account == null) return;
var mailItem = await _mailService.GetSingleMailItemAsync(mailItemUniqueId).ConfigureAwait(false);
if (mailItem == null) return;
var message = new AccountMenuItemExtended(mailItem.AssignedFolder.Id, mailItem);
// Delegate this event to LaunchProtocolService so app shell can pick it up on launch if app doesn't work.
var launchProtocolService = App.Current.Services.GetService<ILaunchProtocolService>();
launchProtocolService.LaunchParameter = message;
// Send the messsage anyways. Launch protocol service will be ignored if the message is picked up by subscriber shell.
WeakReferenceMessenger.Default.Send(message);
}
}
protected override bool CanHandleInternal(ToastNotificationActivatedEventArgs args)
{
try
{
_toastArguments = ToastArguments.Parse(args.Argument);
return
_toastArguments.Contains(Constants.ToastMailUniqueIdKey) &&
_toastArguments.Contains(Constants.ToastActionKey);
}
catch (Exception ex)
{
Log.Error(ex, "Couldn't handle parsing toast notification arguments for foreground navigate.");
}
return false;
}
}
}

View File

@@ -27,288 +27,287 @@ using Wino.Messaging.Client.Navigation;
using Wino.Messaging.Server;
using Wino.Services;
namespace Wino;
public sealed partial class App : WinoApplication, IRecipient<NewMailSynchronizationRequested>
namespace Wino
{
public override string AppCenterKey { get; } = "90deb1d0-a77f-47d0-8a6b-7eaf111c6b72";
private BackgroundTaskDeferral connectionBackgroundTaskDeferral;
private BackgroundTaskDeferral toastActionBackgroundTaskDeferral;
public App()
public sealed partial class App : WinoApplication, IRecipient<NewMailSynchronizationRequested>
{
InitializeComponent();
private BackgroundTaskDeferral connectionBackgroundTaskDeferral;
private BackgroundTaskDeferral toastActionBackgroundTaskDeferral;
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
WeakReferenceMessenger.Default.Register(this);
}
public override async void OnResuming(object sender, object e)
{
// App Service connection was lost on suspension.
// We must restore it.
// Server might be running already, but re-launching it will trigger a new connection attempt.
try
public App()
{
await AppServiceConnectionManager.ConnectAsync();
}
catch (OperationCanceledException)
{
// Ignore
}
catch (Exception ex)
{
Log.Error(ex, "Failed to connect to server after resuming the app.");
}
}
InitializeComponent();
public override IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
services.RegisterViewModelService();
services.RegisterSharedServices();
services.RegisterCoreUWPServices();
services.RegisterCoreViewModels();
RegisterUWPServices(services);
RegisterViewModels(services);
RegisterActivationHandlers(services);
return services.BuildServiceProvider();
}
#region Dependency Injection
private void RegisterActivationHandlers(IServiceCollection services)
{
services.AddTransient<ProtocolActivationHandler>();
services.AddTransient<ToastNotificationActivationHandler>();
services.AddTransient<FileActivationHandler>();
}
private void RegisterUWPServices(IServiceCollection services)
{
services.AddSingleton<INavigationService, NavigationService>();
services.AddSingleton<IMailDialogService, DialogService>();
services.AddTransient<ISettingsBuilderService, SettingsBuilderService>();
services.AddTransient<IProviderService, ProviderService>();
services.AddSingleton<IAuthenticatorConfig, MailAuthenticatorConfiguration>();
}
private void RegisterViewModels(IServiceCollection services)
{
services.AddSingleton(typeof(AppShellViewModel));
services.AddTransient(typeof(MailListPageViewModel));
services.AddTransient(typeof(MailRenderingPageViewModel));
services.AddTransient(typeof(AccountManagementViewModel));
services.AddTransient(typeof(WelcomePageViewModel));
services.AddTransient(typeof(ComposePageViewModel));
services.AddTransient(typeof(IdlePageViewModel));
services.AddTransient(typeof(AccountDetailsPageViewModel));
services.AddTransient(typeof(SignatureManagementPageViewModel));
services.AddTransient(typeof(MessageListPageViewModel));
services.AddTransient(typeof(ReadComposePanePageViewModel));
services.AddTransient(typeof(MergedAccountDetailsPageViewModel));
services.AddTransient(typeof(LanguageTimePageViewModel));
services.AddTransient(typeof(AppPreferencesPageViewModel));
services.AddTransient(typeof(AliasManagementPageViewModel));
}
#endregion
protected override async void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
base.OnBackgroundActivated(args);
if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails appServiceTriggerDetails)
{
LogActivation("OnBackgroundActivated -> AppServiceTriggerDetails received.");
// Only accept connections from callers in the same package
if (appServiceTriggerDetails.CallerPackageFamilyName == Package.Current.Id.FamilyName)
{
// Connection established from the fulltrust process
connectionBackgroundTaskDeferral = args.TaskInstance.GetDeferral();
args.TaskInstance.Canceled += OnConnectionBackgroundTaskCanceled;
AppServiceConnectionManager.Connection = appServiceTriggerDetails.AppServiceConnection;
WeakReferenceMessenger.Default.Send(new WinoServerConnectionEstablished());
}
}
else if (args.TaskInstance.TriggerDetails is ToastNotificationActionTriggerDetail toastNotificationActionTriggerDetail)
{
// Notification action is triggered and the app is not running.
LogActivation("OnBackgroundActivated -> ToastNotificationActionTriggerDetail received.");
toastActionBackgroundTaskDeferral = args.TaskInstance.GetDeferral();
args.TaskInstance.Canceled += OnToastActionClickedBackgroundTaskCanceled;
await InitializeServicesAsync();
var toastArguments = ToastArguments.Parse(toastNotificationActionTriggerDetail.Argument);
// All toast activation mail actions are handled here like mark as read or delete.
// This should not launch the application on the foreground.
// Get the action and mail item id.
// Prepare package and send to delegator.
if (toastArguments.TryGetValue(Constants.ToastActionKey, out MailOperation action) &&
toastArguments.TryGetValue(Constants.ToastMailUniqueIdKey, out string mailUniqueIdString) &&
Guid.TryParse(mailUniqueIdString, out Guid mailUniqueId))
{
// At this point server should've already been connected.
var processor = base.Services.GetService<IWinoRequestProcessor>();
var delegator = base.Services.GetService<IWinoRequestDelegator>();
var mailService = base.Services.GetService<IMailService>();
var mailItem = await mailService.GetSingleMailItemAsync(mailUniqueId);
if (mailItem != null)
{
var package = new MailOperationPreperationRequest(action, mailItem);
await delegator.ExecuteAsync(package);
}
}
toastActionBackgroundTaskDeferral.Complete();
}
else
{
// Other background activations might have handlers.
// AppServiceTrigger is handled here because delegating it to handlers somehow make it not work...
await ActivateWinoAsync(args);
}
}
private void OnToastActionClickedBackgroundTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
sender.Canceled -= OnToastActionClickedBackgroundTaskCanceled;
Log.Information($"Toast action background task was canceled. Reason: {reason}");
toastActionBackgroundTaskDeferral?.Complete();
toastActionBackgroundTaskDeferral = null;
}
protected override IEnumerable<ActivationHandler> GetActivationHandlers()
{
yield return Services.GetService<ProtocolActivationHandler>();
yield return Services.GetService<ToastNotificationActivationHandler>();
yield return Services.GetService<FileActivationHandler>();
}
public void OnConnectionBackgroundTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
sender.Canceled -= OnConnectionBackgroundTaskCanceled;
Log.Information($"Server connection background task was canceled. Reason: {reason}");
connectionBackgroundTaskDeferral?.Complete();
connectionBackgroundTaskDeferral = null;
AppServiceConnectionManager.Connection = null;
}
public async void Receive(NewMailSynchronizationRequested message)
{
try
{
var synchronizationResultResponse = await AppServiceConnectionManager.GetResponseAsync<MailSynchronizationResult, NewMailSynchronizationRequested>(message);
synchronizationResultResponse.ThrowIfFailed();
}
catch (WinoServerException serverException)
{
// TODO: Exception context is lost.
var dialogService = Services.GetService<IMailDialogService>();
dialogService.InfoBarMessage(Translator.Info_SyncFailedTitle, serverException.Message, InfoBarMessageType.Error);
}
}
protected override async void OnApplicationCloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e)
{
Log.Information("App close requested.");
var deferral = e.GetDeferral();
// Wino should notify user on app close if:
// 1. Startup behavior is not Enabled.
// 2. Server terminate behavior is set to Terminate.
// User has some accounts. Check if Wino Server runs on system startup.
var dialogService = base.Services.GetService<IMailDialogService>();
var startupBehaviorService = base.Services.GetService<IStartupBehaviorService>();
var preferencesService = base.Services.GetService<IPreferencesService>();
var currentStartupBehavior = await startupBehaviorService.GetCurrentStartupBehaviorAsync();
bool? isGoToAppPreferencesRequested = null;
if (preferencesService.ServerTerminationBehavior == ServerBackgroundMode.Terminate)
{
// Starting the server is fine, but check if server termination behavior is set to terminate.
// This state will kill the server once the app is terminated.
isGoToAppPreferencesRequested = await dialogService.ShowWinoCustomMessageDialogAsync(Translator.AppCloseBackgroundSynchronizationWarningTitle,
$"{Translator.AppCloseTerminateBehaviorWarningMessageFirstLine}\n{Translator.AppCloseTerminateBehaviorWarningMessageSecondLine}\n\n{Translator.AppCloseTerminateBehaviorWarningMessageThirdLine}",
Translator.Buttons_Yes,
WinoCustomMessageDialogIcon.Warning,
Translator.Buttons_No,
"DontAskTerminateServerBehavior");
WeakReferenceMessenger.Default.Register(this);
}
if (isGoToAppPreferencesRequested == null && currentStartupBehavior != StartupBehaviorResult.Enabled)
public override async void OnResuming(object sender, object e)
{
// Startup behavior is not enabled.
// App Service connection was lost on suspension.
// We must restore it.
// Server might be running already, but re-launching it will trigger a new connection attempt.
isGoToAppPreferencesRequested = await dialogService.ShowWinoCustomMessageDialogAsync(Translator.AppCloseBackgroundSynchronizationWarningTitle,
$"{Translator.AppCloseStartupLaunchDisabledWarningMessageFirstLine}\n{Translator.AppCloseStartupLaunchDisabledWarningMessageSecondLine}\n\n{Translator.AppCloseStartupLaunchDisabledWarningMessageThirdLine}",
Translator.Buttons_Yes,
WinoCustomMessageDialogIcon.Warning,
Translator.Buttons_No,
"DontAskDisabledStartup");
}
if (isGoToAppPreferencesRequested == true)
{
WeakReferenceMessenger.Default.Send(new NavigateAppPreferencesRequested());
e.Handled = true;
}
else if (preferencesService.ServerTerminationBehavior == ServerBackgroundMode.Terminate)
{
try
{
var isServerKilled = await AppServiceConnectionManager.GetResponseAsync<bool, TerminateServerRequested>(new TerminateServerRequested());
isServerKilled.ThrowIfFailed();
Log.Information("Server is killed.");
await AppServiceConnectionManager.ConnectAsync();
}
catch (OperationCanceledException)
{
// Ignore
}
catch (Exception ex)
{
Log.Error(ex, "Failed to kill server.");
Log.Error(ex, "Failed to connect to server after resuming the app.");
}
}
deferral.Complete();
}
public override IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
protected override ActivationHandler<IActivatedEventArgs> GetDefaultActivationHandler()
=> new DefaultActivationHandler();
services.RegisterViewModelService();
services.RegisterSharedServices();
services.RegisterCoreUWPServices();
services.RegisterCoreViewModels();
RegisterUWPServices(services);
RegisterViewModels(services);
RegisterActivationHandlers(services);
return services.BuildServiceProvider();
}
#region Dependency Injection
private void RegisterActivationHandlers(IServiceCollection services)
{
services.AddTransient<ProtocolActivationHandler>();
services.AddTransient<ToastNotificationActivationHandler>();
services.AddTransient<FileActivationHandler>();
}
private void RegisterUWPServices(IServiceCollection services)
{
services.AddSingleton<INavigationService, NavigationService>();
services.AddSingleton<IMailDialogService, DialogService>();
services.AddTransient<ISettingsBuilderService, SettingsBuilderService>();
services.AddTransient<IProviderService, ProviderService>();
services.AddSingleton<IAuthenticatorConfig, MailAuthenticatorConfiguration>();
}
private void RegisterViewModels(IServiceCollection services)
{
services.AddSingleton(typeof(AppShellViewModel));
services.AddTransient(typeof(MailListPageViewModel));
services.AddTransient(typeof(MailRenderingPageViewModel));
services.AddTransient(typeof(AccountManagementViewModel));
services.AddTransient(typeof(WelcomePageViewModel));
services.AddTransient(typeof(ComposePageViewModel));
services.AddTransient(typeof(IdlePageViewModel));
services.AddTransient(typeof(AccountDetailsPageViewModel));
services.AddTransient(typeof(SignatureManagementPageViewModel));
services.AddTransient(typeof(MessageListPageViewModel));
services.AddTransient(typeof(ReadComposePanePageViewModel));
services.AddTransient(typeof(MergedAccountDetailsPageViewModel));
services.AddTransient(typeof(LanguageTimePageViewModel));
services.AddTransient(typeof(AppPreferencesPageViewModel));
services.AddTransient(typeof(AliasManagementPageViewModel));
}
#endregion
protected override async void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
base.OnBackgroundActivated(args);
if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails appServiceTriggerDetails)
{
LogActivation("OnBackgroundActivated -> AppServiceTriggerDetails received.");
// Only accept connections from callers in the same package
if (appServiceTriggerDetails.CallerPackageFamilyName == Package.Current.Id.FamilyName)
{
// Connection established from the fulltrust process
connectionBackgroundTaskDeferral = args.TaskInstance.GetDeferral();
args.TaskInstance.Canceled += OnConnectionBackgroundTaskCanceled;
AppServiceConnectionManager.Connection = appServiceTriggerDetails.AppServiceConnection;
WeakReferenceMessenger.Default.Send(new WinoServerConnectionEstablished());
}
}
else if (args.TaskInstance.TriggerDetails is ToastNotificationActionTriggerDetail toastNotificationActionTriggerDetail)
{
// Notification action is triggered and the app is not running.
LogActivation("OnBackgroundActivated -> ToastNotificationActionTriggerDetail received.");
toastActionBackgroundTaskDeferral = args.TaskInstance.GetDeferral();
args.TaskInstance.Canceled += OnToastActionClickedBackgroundTaskCanceled;
await InitializeServicesAsync();
var toastArguments = ToastArguments.Parse(toastNotificationActionTriggerDetail.Argument);
// All toast activation mail actions are handled here like mark as read or delete.
// This should not launch the application on the foreground.
// Get the action and mail item id.
// Prepare package and send to delegator.
if (toastArguments.TryGetValue(Constants.ToastActionKey, out MailOperation action) &&
toastArguments.TryGetValue(Constants.ToastMailUniqueIdKey, out string mailUniqueIdString) &&
Guid.TryParse(mailUniqueIdString, out Guid mailUniqueId))
{
// At this point server should've already been connected.
var processor = base.Services.GetService<IWinoRequestProcessor>();
var delegator = base.Services.GetService<IWinoRequestDelegator>();
var mailService = base.Services.GetService<IMailService>();
var mailItem = await mailService.GetSingleMailItemAsync(mailUniqueId);
if (mailItem != null)
{
var package = new MailOperationPreperationRequest(action, mailItem);
await delegator.ExecuteAsync(package);
}
}
toastActionBackgroundTaskDeferral.Complete();
}
else
{
// Other background activations might have handlers.
// AppServiceTrigger is handled here because delegating it to handlers somehow make it not work...
await ActivateWinoAsync(args);
}
}
private void OnToastActionClickedBackgroundTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
sender.Canceled -= OnToastActionClickedBackgroundTaskCanceled;
Log.Information($"Toast action background task was canceled. Reason: {reason}");
toastActionBackgroundTaskDeferral?.Complete();
toastActionBackgroundTaskDeferral = null;
}
protected override IEnumerable<ActivationHandler> GetActivationHandlers()
{
yield return Services.GetService<ProtocolActivationHandler>();
yield return Services.GetService<ToastNotificationActivationHandler>();
yield return Services.GetService<FileActivationHandler>();
}
public void OnConnectionBackgroundTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
sender.Canceled -= OnConnectionBackgroundTaskCanceled;
Log.Information($"Server connection background task was canceled. Reason: {reason}");
connectionBackgroundTaskDeferral?.Complete();
connectionBackgroundTaskDeferral = null;
AppServiceConnectionManager.Connection = null;
}
public async void Receive(NewMailSynchronizationRequested message)
{
try
{
var synchronizationResultResponse = await AppServiceConnectionManager.GetResponseAsync<MailSynchronizationResult, NewMailSynchronizationRequested>(message);
synchronizationResultResponse.ThrowIfFailed();
}
catch (WinoServerException serverException)
{
// TODO: Exception context is lost.
var dialogService = Services.GetService<IMailDialogService>();
dialogService.InfoBarMessage(Translator.Info_SyncFailedTitle, serverException.Message, InfoBarMessageType.Error);
}
}
protected override async void OnApplicationCloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e)
{
Log.Information("App close requested.");
var deferral = e.GetDeferral();
// Wino should notify user on app close if:
// 1. Startup behavior is not Enabled.
// 2. Server terminate behavior is set to Terminate.
// User has some accounts. Check if Wino Server runs on system startup.
var dialogService = base.Services.GetService<IMailDialogService>();
var startupBehaviorService = base.Services.GetService<IStartupBehaviorService>();
var preferencesService = base.Services.GetService<IPreferencesService>();
var currentStartupBehavior = await startupBehaviorService.GetCurrentStartupBehaviorAsync();
bool? isGoToAppPreferencesRequested = null;
if (preferencesService.ServerTerminationBehavior == ServerBackgroundMode.Terminate)
{
// Starting the server is fine, but check if server termination behavior is set to terminate.
// This state will kill the server once the app is terminated.
isGoToAppPreferencesRequested = await dialogService.ShowWinoCustomMessageDialogAsync(Translator.AppCloseBackgroundSynchronizationWarningTitle,
$"{Translator.AppCloseTerminateBehaviorWarningMessageFirstLine}\n{Translator.AppCloseTerminateBehaviorWarningMessageSecondLine}\n\n{Translator.AppCloseTerminateBehaviorWarningMessageThirdLine}",
Translator.Buttons_Yes,
WinoCustomMessageDialogIcon.Warning,
Translator.Buttons_No,
"DontAskTerminateServerBehavior");
}
if (isGoToAppPreferencesRequested == null && currentStartupBehavior != StartupBehaviorResult.Enabled)
{
// Startup behavior is not enabled.
isGoToAppPreferencesRequested = await dialogService.ShowWinoCustomMessageDialogAsync(Translator.AppCloseBackgroundSynchronizationWarningTitle,
$"{Translator.AppCloseStartupLaunchDisabledWarningMessageFirstLine}\n{Translator.AppCloseStartupLaunchDisabledWarningMessageSecondLine}\n\n{Translator.AppCloseStartupLaunchDisabledWarningMessageThirdLine}",
Translator.Buttons_Yes,
WinoCustomMessageDialogIcon.Warning,
Translator.Buttons_No,
"DontAskDisabledStartup");
}
if (isGoToAppPreferencesRequested == true)
{
WeakReferenceMessenger.Default.Send(new NavigateAppPreferencesRequested());
e.Handled = true;
}
else if (preferencesService.ServerTerminationBehavior == ServerBackgroundMode.Terminate)
{
try
{
var isServerKilled = await AppServiceConnectionManager.GetResponseAsync<bool, TerminateServerRequested>(new TerminateServerRequested());
isServerKilled.ThrowIfFailed();
Log.Information("Server is killed.");
}
catch (Exception ex)
{
Log.Error(ex, "Failed to kill server.");
}
}
deferral.Complete();
}
protected override ActivationHandler<IActivatedEventArgs> GetDefaultActivationHandler()
=> new DefaultActivationHandler();
}
}

View File

@@ -28,308 +28,309 @@ using Wino.Messaging.Client.Mails;
using Wino.Messaging.Client.Shell;
using Wino.Views.Abstract;
namespace Wino.Views;
public sealed partial class AppShell : AppShellAbstract,
IRecipient<AccountMenuItemExtended>,
IRecipient<NavigateMailFolderEvent>,
IRecipient<CreateNewMailWithMultipleAccountsRequested>,
IRecipient<InfoBarMessageRequested>
namespace Wino.Views
{
public AppShell() : base()
public sealed partial class AppShell : AppShellAbstract,
IRecipient<AccountMenuItemExtended>,
IRecipient<NavigateMailFolderEvent>,
IRecipient<CreateNewMailWithMultipleAccountsRequested>,
IRecipient<InfoBarMessageRequested>
{
InitializeComponent();
var coreTitleBar = CoreApplication.GetCurrentView().TitleBar;
coreTitleBar.LayoutMetricsChanged += TitleBarLayoutUpdated;
}
private void TitleBarLayoutUpdated(CoreApplicationViewTitleBar sender, object args) => UpdateTitleBarLayout(sender);
private void UpdateTitleBarLayout(CoreApplicationViewTitleBar coreTitleBar) => RealAppBar.SystemReserved = coreTitleBar.SystemOverlayRightInset;
private async void ItemDroppedOnFolder(object sender, DragEventArgs e)
{
// Validate package content.
if (sender is WinoNavigationViewItem droppedContainer)
public AppShell() : base()
{
droppedContainer.IsDraggingItemOver = false;
InitializeComponent();
if (CanContinueDragDrop(droppedContainer, e))
var coreTitleBar = CoreApplication.GetCurrentView().TitleBar;
coreTitleBar.LayoutMetricsChanged += TitleBarLayoutUpdated;
}
private void TitleBarLayoutUpdated(CoreApplicationViewTitleBar sender, object args) => UpdateTitleBarLayout(sender);
private void UpdateTitleBarLayout(CoreApplicationViewTitleBar coreTitleBar) => RealAppBar.SystemReserved = coreTitleBar.SystemOverlayRightInset;
private async void ItemDroppedOnFolder(object sender, DragEventArgs e)
{
// Validate package content.
if (sender is WinoNavigationViewItem droppedContainer)
{
if (droppedContainer.DataContext is IBaseFolderMenuItem draggingFolder)
droppedContainer.IsDraggingItemOver = false;
if (CanContinueDragDrop(droppedContainer, e))
{
var mailCopies = new List<MailCopy>();
var dragPackage = e.DataView.Properties[nameof(MailDragPackage)] as MailDragPackage;
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move;
// Extract mail copies from IMailItem.
// ThreadViewModels will be divided into pieces.
foreach (var item in dragPackage.DraggingMails)
if (droppedContainer.DataContext is IBaseFolderMenuItem draggingFolder)
{
if (item is MailItemViewModel singleMailItemViewModel)
{
mailCopies.Add(singleMailItemViewModel.MailCopy);
}
else if (item is ThreadMailItemViewModel threadViewModel)
{
mailCopies.AddRange(threadViewModel.GetMailCopies());
}
}
var mailCopies = new List<MailCopy>();
await ViewModel.PerformMoveOperationAsync(mailCopies, draggingFolder);
var dragPackage = e.DataView.Properties[nameof(MailDragPackage)] as MailDragPackage;
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move;
// Extract mail copies from IMailItem.
// ThreadViewModels will be divided into pieces.
foreach (var item in dragPackage.DraggingMails)
{
if (item is MailItemViewModel singleMailItemViewModel)
{
mailCopies.Add(singleMailItemViewModel.MailCopy);
}
else if (item is ThreadMailItemViewModel threadViewModel)
{
mailCopies.AddRange(threadViewModel.GetMailCopies());
}
}
await ViewModel.PerformMoveOperationAsync(mailCopies, draggingFolder);
}
}
}
}
}
private void ItemDragLeaveFromFolder(object sender, DragEventArgs e)
{
if (sender is WinoNavigationViewItem leavingContainer)
private void ItemDragLeaveFromFolder(object sender, DragEventArgs e)
{
leavingContainer.IsDraggingItemOver = false;
}
}
private bool CanContinueDragDrop(WinoNavigationViewItem interactingContainer, DragEventArgs args)
{
// TODO: Maybe override caption with some information why the validation failed?
// Note: Caption has a max length. It may be trimmed in some languages.
if (interactingContainer == null || !args.DataView.Properties.ContainsKey(nameof(MailDragPackage))) return false;
var dragPackage = args.DataView.Properties[nameof(MailDragPackage)] as MailDragPackage;
// Invalid package.
if (!dragPackage.DraggingMails.Any()) return false;
// Check whether source and target folder are the same.
if (interactingContainer.IsSelected) return false;
// Check if the interacting container is a folder.
if (!(interactingContainer.DataContext is IBaseFolderMenuItem folderMenuItem)) return false;
// Check if the folder is a move target.
if (!folderMenuItem.IsMoveTarget) return false;
// Check whether the moving item's account has at least one same as the target folder's account.
var draggedAccountIds = folderMenuItem.HandlingFolders.Select(a => a.MailAccountId);
if (!dragPackage.DraggingMails.Any(a => draggedAccountIds.Contains(a.AssignedAccount.Id))) return false;
return true;
}
private void ItemDragEnterOnFolder(object sender, DragEventArgs e)
{
// Validate package content.
if (sender is WinoNavigationViewItem droppedContainer && CanContinueDragDrop(droppedContainer, e))
{
droppedContainer.IsDraggingItemOver = true;
var draggingFolder = droppedContainer.DataContext as IBaseFolderMenuItem;
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move;
e.DragUIOverride.Caption = string.Format(Translator.DragMoveToFolderCaption, draggingFolder.FolderName);
}
}
public async void Receive(AccountMenuItemExtended message)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, async () =>
{
if (message.FolderId == default) return;
if (ViewModel.MenuItems.TryGetFolderMenuItem(message.FolderId, out IBaseFolderMenuItem foundMenuItem))
if (sender is WinoNavigationViewItem leavingContainer)
{
foundMenuItem.Expand();
await ViewModel.NavigateFolderAsync(foundMenuItem);
navigationView.SelectedItem = foundMenuItem;
if (message.NavigateMailItem == null) return;
// At this point folder is navigated and items are loaded.
WeakReferenceMessenger.Default.Send(new MailItemNavigationRequested(message.NavigateMailItem.UniqueId, ScrollToItem: true));
leavingContainer.IsDraggingItemOver = false;
}
else if (ViewModel.MenuItems.TryGetAccountMenuItem(message.NavigateMailItem.AssignedAccount.Id, out IAccountMenuItem accountMenuItem))
}
private bool CanContinueDragDrop(WinoNavigationViewItem interactingContainer, DragEventArgs args)
{
// TODO: Maybe override caption with some information why the validation failed?
// Note: Caption has a max length. It may be trimmed in some languages.
if (interactingContainer == null || !args.DataView.Properties.ContainsKey(nameof(MailDragPackage))) return false;
var dragPackage = args.DataView.Properties[nameof(MailDragPackage)] as MailDragPackage;
// Invalid package.
if (!dragPackage.DraggingMails.Any()) return false;
// Check whether source and target folder are the same.
if (interactingContainer.IsSelected) return false;
// Check if the interacting container is a folder.
if (!(interactingContainer.DataContext is IBaseFolderMenuItem folderMenuItem)) return false;
// Check if the folder is a move target.
if (!folderMenuItem.IsMoveTarget) return false;
// Check whether the moving item's account has at least one same as the target folder's account.
var draggedAccountIds = folderMenuItem.HandlingFolders.Select(a => a.MailAccountId);
if (!dragPackage.DraggingMails.Any(a => draggedAccountIds.Contains(a.AssignedAccount.Id))) return false;
return true;
}
private void ItemDragEnterOnFolder(object sender, DragEventArgs e)
{
// Validate package content.
if (sender is WinoNavigationViewItem droppedContainer && CanContinueDragDrop(droppedContainer, e))
{
// Loaded account is different. First change the folder items and navigate.
droppedContainer.IsDraggingItemOver = true;
await ViewModel.ChangeLoadedAccountAsync(accountMenuItem, navigateInbox: false);
var draggingFolder = droppedContainer.DataContext as IBaseFolderMenuItem;
// Find the folder.
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move;
e.DragUIOverride.Caption = string.Format(Translator.DragMoveToFolderCaption, draggingFolder.FolderName);
}
}
if (ViewModel.MenuItems.TryGetFolderMenuItem(message.FolderId, out IBaseFolderMenuItem accountFolderMenuItem))
public async void Receive(AccountMenuItemExtended message)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, async () =>
{
if (message.FolderId == default) return;
if (ViewModel.MenuItems.TryGetFolderMenuItem(message.FolderId, out IBaseFolderMenuItem foundMenuItem))
{
accountFolderMenuItem.Expand();
foundMenuItem.Expand();
await ViewModel.NavigateFolderAsync(accountFolderMenuItem);
await ViewModel.NavigateFolderAsync(foundMenuItem);
navigationView.SelectedItem = accountFolderMenuItem;
navigationView.SelectedItem = foundMenuItem;
if (message.NavigateMailItem == null) return;
// At this point folder is navigated and items are loaded.
WeakReferenceMessenger.Default.Send(new MailItemNavigationRequested(message.NavigateMailItem.UniqueId, ScrollToItem: true));
}
}
});
}
else if (ViewModel.MenuItems.TryGetAccountMenuItem(message.NavigateMailItem.AssignedAccount.Id, out IAccountMenuItem accountMenuItem))
{
// Loaded account is different. First change the folder items and navigate.
private async void MenuSelectionChanged(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewSelectionChangedEventArgs args)
{
if (args.SelectedItem is IMenuItem invokedMenuItem)
{
await ViewModel.MenuItemInvokedOrSelectedAsync(invokedMenuItem);
}
}
await ViewModel.ChangeLoadedAccountAsync(accountMenuItem, navigateInbox: false);
private async void NavigationViewItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args)
{
// SelectsOnInvoked is handled in MenuSelectionChanged.
// This part is only for the items that are not selectable.
if (args.InvokedItemContainer is WinoNavigationViewItem winoNavigationViewItem)
{
if (winoNavigationViewItem.SelectsOnInvoked) return;
// Find the folder.
await ViewModel.MenuItemInvokedOrSelectedAsync(winoNavigationViewItem.DataContext as IMenuItem);
}
}
if (ViewModel.MenuItems.TryGetFolderMenuItem(message.FolderId, out IBaseFolderMenuItem accountFolderMenuItem))
{
accountFolderMenuItem.Expand();
public void Receive(NavigateMailFolderEvent message)
{
if (message.BaseFolderMenuItem == null) return;
await ViewModel.NavigateFolderAsync(accountFolderMenuItem);
if (navigationView.SelectedItem != message.BaseFolderMenuItem)
{
var navigateFolderArgs = new NavigateMailFolderEventArgs(message.BaseFolderMenuItem, message.FolderInitLoadAwaitTask);
navigationView.SelectedItem = accountFolderMenuItem;
ViewModel.NavigationService.Navigate(WinoPage.MailListPage, navigateFolderArgs, NavigationReferenceFrame.ShellFrame);
// Prevent double navigation.
navigationView.SelectionChanged -= MenuSelectionChanged;
navigationView.SelectedItem = message.BaseFolderMenuItem;
navigationView.SelectionChanged += MenuSelectionChanged;
}
else
{
// Complete the init task since we are already on the right page.
message.FolderInitLoadAwaitTask?.TrySetResult(true);
}
}
private void ShellFrameContentNavigated(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e)
=> RealAppBar.ShellFrameContent = (e.Content as BasePage).ShellContent;
private void BackButtonClicked(WinoAppTitleBar sender, RoutedEventArgs args)
{
WeakReferenceMessenger.Default.Send(new ClearMailSelectionsRequested());
WeakReferenceMessenger.Default.Send(new DisposeRenderingFrameRequested());
}
private async void MenuItemContextRequested(UIElement sender, ContextRequestedEventArgs args)
{
// Delegate this request to ViewModel.
// VM will prepare available actions for this folder and show Menu Flyout.
if (sender is WinoNavigationViewItem menuItem &&
menuItem.DataContext is IBaseFolderMenuItem baseFolderMenuItem &&
baseFolderMenuItem.IsMoveTarget &&
args.TryGetPosition(sender, out Point p))
{
args.Handled = true;
var source = new TaskCompletionSource<FolderOperationMenuItem>();
var actions = ViewModel.GetFolderContextMenuActions(baseFolderMenuItem);
var flyout = new FolderOperationFlyout(actions, source);
flyout.ShowAt(menuItem, new FlyoutShowOptions()
{
ShowMode = FlyoutShowMode.Standard,
Position = new Point(p.X + 30, p.Y - 20)
// At this point folder is navigated and items are loaded.
WeakReferenceMessenger.Default.Send(new MailItemNavigationRequested(message.NavigateMailItem.UniqueId, ScrollToItem: true));
}
}
});
var operation = await source.Task;
flyout.Dispose();
// No action selected.
if (operation == null) return;
await ViewModel.PerformFolderOperationAsync(operation.Operation, baseFolderMenuItem);
}
}
public void Receive(CreateNewMailWithMultipleAccountsRequested message)
{
// Find the NewMail menu item container.
var container = navigationView.ContainerFromMenuItem(ViewModel.CreateMailMenuItem);
var flyout = new AccountSelectorFlyout(message.AllAccounts, ViewModel.CreateNewMailForAsync);
flyout.ShowAt(container, new FlyoutShowOptions()
private async void MenuSelectionChanged(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewSelectionChangedEventArgs args)
{
ShowMode = FlyoutShowMode.Auto,
Placement = FlyoutPlacementMode.Right
});
}
private void NavigationPaneOpening(Microsoft.UI.Xaml.Controls.NavigationView sender, object args)
{
// It's annoying that NavigationView doesn't respect expansion state of the items in Minimal display mode.
// Expanded items are collaped, and users need to expand them again.
// Regardless of the reason, we will expand the selected item if it's a folder with parent account for visibility.
if (sender.DisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Minimal && sender.SelectedItem is IFolderMenuItem selectedFolderMenuItem)
{
selectedFolderMenuItem.Expand();
}
}
/// <summary>
/// InfoBar message is requested.
/// </summary>
public async void Receive(InfoBarMessageRequested message)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
if (string.IsNullOrEmpty(message.ActionButtonTitle) || message.Action == null)
if (args.SelectedItem is IMenuItem invokedMenuItem)
{
ShellInfoBar.ActionButton = null;
await ViewModel.MenuItemInvokedOrSelectedAsync(invokedMenuItem);
}
}
private async void NavigationViewItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args)
{
// SelectsOnInvoked is handled in MenuSelectionChanged.
// This part is only for the items that are not selectable.
if (args.InvokedItemContainer is WinoNavigationViewItem winoNavigationViewItem)
{
if (winoNavigationViewItem.SelectsOnInvoked) return;
await ViewModel.MenuItemInvokedOrSelectedAsync(winoNavigationViewItem.DataContext as IMenuItem);
}
}
public void Receive(NavigateMailFolderEvent message)
{
if (message.BaseFolderMenuItem == null) return;
if (navigationView.SelectedItem != message.BaseFolderMenuItem)
{
var navigateFolderArgs = new NavigateMailFolderEventArgs(message.BaseFolderMenuItem, message.FolderInitLoadAwaitTask);
ViewModel.NavigationService.Navigate(WinoPage.MailListPage, navigateFolderArgs, NavigationReferenceFrame.ShellFrame);
// Prevent double navigation.
navigationView.SelectionChanged -= MenuSelectionChanged;
navigationView.SelectedItem = message.BaseFolderMenuItem;
navigationView.SelectionChanged += MenuSelectionChanged;
}
else
{
ShellInfoBar.ActionButton = new Button()
{
Content = message.ActionButtonTitle,
Command = new RelayCommand(message.Action)
};
// Complete the init task since we are already on the right page.
message.FolderInitLoadAwaitTask?.TrySetResult(true);
}
ShellInfoBar.Message = message.Message;
ShellInfoBar.Title = message.Title;
ShellInfoBar.Severity = message.Severity.AsMUXCInfoBarSeverity();
ShellInfoBar.IsOpen = true;
});
}
private void NavigationViewDisplayModeChanged(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewDisplayModeChangedEventArgs args)
{
if (args.DisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Minimal)
{
ShellFrame.Margin = new Thickness(7, 0, 0, 0);
}
else
private void ShellFrameContentNavigated(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e)
=> RealAppBar.ShellFrameContent = (e.Content as BasePage).ShellContent;
private void BackButtonClicked(WinoAppTitleBar sender, RoutedEventArgs args)
{
ShellFrame.Margin = new Thickness(0);
WeakReferenceMessenger.Default.Send(new ClearMailSelectionsRequested());
WeakReferenceMessenger.Default.Send(new DisposeRenderingFrameRequested());
}
private async void MenuItemContextRequested(UIElement sender, ContextRequestedEventArgs args)
{
// Delegate this request to ViewModel.
// VM will prepare available actions for this folder and show Menu Flyout.
if (sender is WinoNavigationViewItem menuItem &&
menuItem.DataContext is IBaseFolderMenuItem baseFolderMenuItem &&
baseFolderMenuItem.IsMoveTarget &&
args.TryGetPosition(sender, out Point p))
{
args.Handled = true;
var source = new TaskCompletionSource<FolderOperationMenuItem>();
var actions = ViewModel.GetFolderContextMenuActions(baseFolderMenuItem);
var flyout = new FolderOperationFlyout(actions, source);
flyout.ShowAt(menuItem, new FlyoutShowOptions()
{
ShowMode = FlyoutShowMode.Standard,
Position = new Point(p.X + 30, p.Y - 20)
});
var operation = await source.Task;
flyout.Dispose();
// No action selected.
if (operation == null) return;
await ViewModel.PerformFolderOperationAsync(operation.Operation, baseFolderMenuItem);
}
}
public void Receive(CreateNewMailWithMultipleAccountsRequested message)
{
// Find the NewMail menu item container.
var container = navigationView.ContainerFromMenuItem(ViewModel.CreateMailMenuItem);
var flyout = new AccountSelectorFlyout(message.AllAccounts, ViewModel.CreateNewMailForAsync);
flyout.ShowAt(container, new FlyoutShowOptions()
{
ShowMode = FlyoutShowMode.Auto,
Placement = FlyoutPlacementMode.Right
});
}
private void NavigationPaneOpening(Microsoft.UI.Xaml.Controls.NavigationView sender, object args)
{
// It's annoying that NavigationView doesn't respect expansion state of the items in Minimal display mode.
// Expanded items are collaped, and users need to expand them again.
// Regardless of the reason, we will expand the selected item if it's a folder with parent account for visibility.
if (sender.DisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Minimal && sender.SelectedItem is IFolderMenuItem selectedFolderMenuItem)
{
selectedFolderMenuItem.Expand();
}
}
/// <summary>
/// InfoBar message is requested.
/// </summary>
public async void Receive(InfoBarMessageRequested message)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
if (string.IsNullOrEmpty(message.ActionButtonTitle) || message.Action == null)
{
ShellInfoBar.ActionButton = null;
}
else
{
ShellInfoBar.ActionButton = new Button()
{
Content = message.ActionButtonTitle,
Command = new RelayCommand(message.Action)
};
}
ShellInfoBar.Message = message.Message;
ShellInfoBar.Title = message.Title;
ShellInfoBar.Severity = message.Severity.AsMUXCInfoBarSeverity();
ShellInfoBar.IsOpen = true;
});
}
private void NavigationViewDisplayModeChanged(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewDisplayModeChangedEventArgs args)
{
if (args.DisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Minimal)
{
ShellFrame.Margin = new Thickness(7, 0, 0, 0);
}
else
{
ShellFrame.Margin = new Thickness(0);
}
}
}
}

View File

@@ -10,184 +10,185 @@ using Wino.Core.Domain.Models.Menus;
using Wino.Core.UWP.Controls;
using Wino.Helpers;
namespace Wino.Behaviors;
public class BindableCommandBarBehavior : Behavior<CommandBar>
namespace Wino.Behaviors
{
public static readonly DependencyProperty PrimaryCommandsProperty = DependencyProperty.Register(
"PrimaryCommands", typeof(object), typeof(BindableCommandBarBehavior),
new PropertyMetadata(null, UpdateCommands));
public ICommand ItemClickedCommand
public class BindableCommandBarBehavior : Behavior<CommandBar>
{
get { return (ICommand)GetValue(ItemClickedCommandProperty); }
set { SetValue(ItemClickedCommandProperty, value); }
}
public static readonly DependencyProperty PrimaryCommandsProperty = DependencyProperty.Register(
"PrimaryCommands", typeof(object), typeof(BindableCommandBarBehavior),
new PropertyMetadata(null, UpdateCommands));
public static readonly DependencyProperty ItemClickedCommandProperty = DependencyProperty.Register(nameof(ItemClickedCommand), typeof(ICommand), typeof(BindableCommandBarBehavior), new PropertyMetadata(null));
public object PrimaryCommands
{
get { return GetValue(PrimaryCommandsProperty); }
set { SetValue(PrimaryCommandsProperty, value); }
}
protected override void OnDetaching()
{
base.OnDetaching();
AttachChanges(false);
if (PrimaryCommands is IEnumerable enumerable)
public ICommand ItemClickedCommand
{
foreach (var item in enumerable)
{
if (item is ButtonBase button)
{
button.Click -= Button_Click;
}
}
get { return (ICommand)GetValue(ItemClickedCommandProperty); }
set { SetValue(ItemClickedCommandProperty, value); }
}
}
private void UpdatePrimaryCommands()
{
if (AssociatedObject == null)
return;
public static readonly DependencyProperty ItemClickedCommandProperty = DependencyProperty.Register(nameof(ItemClickedCommand), typeof(ICommand), typeof(BindableCommandBarBehavior), new PropertyMetadata(null));
if (PrimaryCommands == null)
return;
if (AssociatedObject.PrimaryCommands is IEnumerable enumerableObjects)
public object PrimaryCommands
{
foreach (var item in enumerableObjects)
get { return GetValue(PrimaryCommandsProperty); }
set { SetValue(PrimaryCommandsProperty, value); }
}
protected override void OnDetaching()
{
base.OnDetaching();
AttachChanges(false);
if (PrimaryCommands is IEnumerable enumerable)
{
if (item is ButtonBase button)
foreach (var item in enumerable)
{
button.Click -= Button_Click;
if (item is ButtonBase button)
{
button.Click -= Button_Click;
}
}
}
}
if (AssociatedObject.SecondaryCommands is IEnumerable secondaryObject)
private void UpdatePrimaryCommands()
{
foreach (var item in secondaryObject)
if (AssociatedObject == null)
return;
if (PrimaryCommands == null)
return;
if (AssociatedObject.PrimaryCommands is IEnumerable enumerableObjects)
{
if (item is ButtonBase button)
foreach (var item in enumerableObjects)
{
button.Click -= Button_Click;
if (item is ButtonBase button)
{
button.Click -= Button_Click;
}
}
}
if (AssociatedObject.SecondaryCommands is IEnumerable secondaryObject)
{
foreach (var item in secondaryObject)
{
if (item is ButtonBase button)
{
button.Click -= Button_Click;
}
}
}
AssociatedObject.PrimaryCommands.Clear();
AssociatedObject.SecondaryCommands.Clear();
if (!(PrimaryCommands is IEnumerable enumerable)) return;
foreach (var command in enumerable)
{
if (command is MailOperationMenuItem mailOperationMenuItem)
{
ICommandBarElement menuItem = null;
if (mailOperationMenuItem.Operation == Core.Domain.Enums.MailOperation.Seperator)
{
menuItem = new AppBarSeparator();
}
else
{
var label = XamlHelpers.GetOperationString(mailOperationMenuItem.Operation);
menuItem = new AppBarButton
{
Icon = new WinoFontIcon() { Glyph = ControlConstants.WinoIconFontDictionary[XamlHelpers.GetWinoIconGlyph(mailOperationMenuItem.Operation)] },
Label = label,
LabelPosition = string.IsNullOrWhiteSpace(label) ? CommandBarLabelPosition.Collapsed : CommandBarLabelPosition.Default,
DataContext = mailOperationMenuItem,
};
ToolTip toolTip = new ToolTip
{
Content = label
};
ToolTipService.SetToolTip((DependencyObject)menuItem, toolTip);
((AppBarButton)menuItem).Click -= Button_Click;
((AppBarButton)menuItem).Click += Button_Click;
}
if (mailOperationMenuItem.IsSecondaryMenuPreferred)
{
AssociatedObject.SecondaryCommands.Add(menuItem);
}
else
{
AssociatedObject.PrimaryCommands.Add(menuItem);
}
}
//if (dependencyObject is ICommandBarElement icommandBarElement)
//{
// if (dependencyObject is ButtonBase button)
// {
// button.Click -= Button_Click;
// button.Click += Button_Click;
// }
// if (command is MailOperationMenuItem mailOperationMenuItem)
// {
// }
//}
}
}
AssociatedObject.PrimaryCommands.Clear();
AssociatedObject.SecondaryCommands.Clear();
if (!(PrimaryCommands is IEnumerable enumerable)) return;
foreach (var command in enumerable)
private void Button_Click(object sender, RoutedEventArgs e)
{
if (command is MailOperationMenuItem mailOperationMenuItem)
{
ICommandBarElement menuItem = null;
ItemClickedCommand?.Execute(((ButtonBase)sender).DataContext);
}
if (mailOperationMenuItem.Operation == Core.Domain.Enums.MailOperation.Seperator)
protected override void OnAttached()
{
base.OnAttached();
AttachChanges(true);
UpdatePrimaryCommands();
}
private void PrimaryCommandsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdatePrimaryCommands();
}
private static void UpdateCommands(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
if (!(dependencyObject is BindableCommandBarBehavior behavior)) return;
if (dependencyPropertyChangedEventArgs.OldValue is INotifyCollectionChanged oldList)
{
oldList.CollectionChanged -= behavior.PrimaryCommandsCollectionChanged;
}
behavior.AttachChanges(true);
behavior.UpdatePrimaryCommands();
}
private void AttachChanges(bool register)
{
if (PrimaryCommands is null) return;
if (PrimaryCommands is INotifyCollectionChanged collectionChanged)
{
if (register)
{
menuItem = new AppBarSeparator();
collectionChanged.CollectionChanged -= PrimaryCommandsCollectionChanged;
collectionChanged.CollectionChanged += PrimaryCommandsCollectionChanged;
}
else
{
var label = XamlHelpers.GetOperationString(mailOperationMenuItem.Operation);
menuItem = new AppBarButton
{
Icon = new WinoFontIcon() { Glyph = ControlConstants.WinoIconFontDictionary[XamlHelpers.GetWinoIconGlyph(mailOperationMenuItem.Operation)] },
Label = label,
LabelPosition = string.IsNullOrWhiteSpace(label) ? CommandBarLabelPosition.Collapsed : CommandBarLabelPosition.Default,
DataContext = mailOperationMenuItem,
};
ToolTip toolTip = new ToolTip
{
Content = label
};
ToolTipService.SetToolTip((DependencyObject)menuItem, toolTip);
((AppBarButton)menuItem).Click -= Button_Click;
((AppBarButton)menuItem).Click += Button_Click;
}
if (mailOperationMenuItem.IsSecondaryMenuPreferred)
{
AssociatedObject.SecondaryCommands.Add(menuItem);
}
else
{
AssociatedObject.PrimaryCommands.Add(menuItem);
}
collectionChanged.CollectionChanged -= PrimaryCommandsCollectionChanged;
}
//if (dependencyObject is ICommandBarElement icommandBarElement)
//{
// if (dependencyObject is ButtonBase button)
// {
// button.Click -= Button_Click;
// button.Click += Button_Click;
// }
// if (command is MailOperationMenuItem mailOperationMenuItem)
// {
// }
//}
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ItemClickedCommand?.Execute(((ButtonBase)sender).DataContext);
}
protected override void OnAttached()
{
base.OnAttached();
AttachChanges(true);
UpdatePrimaryCommands();
}
private void PrimaryCommandsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdatePrimaryCommands();
}
private static void UpdateCommands(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
if (!(dependencyObject is BindableCommandBarBehavior behavior)) return;
if (dependencyPropertyChangedEventArgs.OldValue is INotifyCollectionChanged oldList)
{
oldList.CollectionChanged -= behavior.PrimaryCommandsCollectionChanged;
}
behavior.AttachChanges(true);
behavior.UpdatePrimaryCommands();
}
private void AttachChanges(bool register)
{
if (PrimaryCommands is null) return;
if (PrimaryCommands is INotifyCollectionChanged collectionChanged)
{
if (register)
{
collectionChanged.CollectionChanged -= PrimaryCommandsCollectionChanged;
collectionChanged.CollectionChanged += PrimaryCommandsCollectionChanged;
}
else
collectionChanged.CollectionChanged -= PrimaryCommandsCollectionChanged;
}
}
}

View File

@@ -4,82 +4,83 @@ using Windows.UI.Xaml;
using Wino.Core.Domain.Interfaces;
using Wino.Core.UWP.Controls;
namespace Wino.Behaviors;
public class CreateMailNavigationItemBehavior : Behavior<WinoNavigationViewItem>
namespace Wino.Behaviors
{
public IMenuItem SelectedMenuItem
public class CreateMailNavigationItemBehavior : Behavior<WinoNavigationViewItem>
{
get { return (IMenuItem)GetValue(SelectedMenuItemProperty); }
set { SetValue(SelectedMenuItemProperty, value); }
}
public ObservableCollection<IMenuItem> MenuItems
{
get { return (ObservableCollection<IMenuItem>)GetValue(MenuItemsProperty); }
set { SetValue(MenuItemsProperty, value); }
}
public static readonly DependencyProperty MenuItemsProperty = DependencyProperty.Register(nameof(MenuItems), typeof(ObservableCollection<IMenuItem>), typeof(CreateMailNavigationItemBehavior), new PropertyMetadata(null, OnMenuItemsChanged));
public static readonly DependencyProperty SelectedMenuItemProperty = DependencyProperty.Register(nameof(SelectedMenuItem), typeof(IMenuItem), typeof(CreateMailNavigationItemBehavior), new PropertyMetadata(null, OnSelectedMenuItemChanged));
public CreateMailNavigationItemBehavior()
{
}
protected override void OnAttached()
{
base.OnAttached();
}
protected override void OnDetaching()
{
base.OnDetaching();
}
private static void OnMenuItemsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
if (dependencyObject is CreateMailNavigationItemBehavior behavior)
public IMenuItem SelectedMenuItem
{
if (dependencyPropertyChangedEventArgs.NewValue != null)
behavior.RegisterMenuItemChanges();
behavior.ManageAccounts();
get { return (IMenuItem)GetValue(SelectedMenuItemProperty); }
set { SetValue(SelectedMenuItemProperty, value); }
}
}
private void RegisterMenuItemChanges()
{
if (MenuItems != null)
public ObservableCollection<IMenuItem> MenuItems
{
MenuItems.CollectionChanged -= MenuCollectionUpdated;
MenuItems.CollectionChanged += MenuCollectionUpdated;
get { return (ObservableCollection<IMenuItem>)GetValue(MenuItemsProperty); }
set { SetValue(MenuItemsProperty, value); }
}
}
private void MenuCollectionUpdated(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
ManageAccounts();
}
public static readonly DependencyProperty MenuItemsProperty = DependencyProperty.Register(nameof(MenuItems), typeof(ObservableCollection<IMenuItem>), typeof(CreateMailNavigationItemBehavior), new PropertyMetadata(null, OnMenuItemsChanged));
public static readonly DependencyProperty SelectedMenuItemProperty = DependencyProperty.Register(nameof(SelectedMenuItem), typeof(IMenuItem), typeof(CreateMailNavigationItemBehavior), new PropertyMetadata(null, OnSelectedMenuItemChanged));
private static void OnSelectedMenuItemChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
if (dependencyObject is CreateMailNavigationItemBehavior behavior)
public CreateMailNavigationItemBehavior()
{
behavior.ManageAccounts();
}
}
private void ManageAccounts()
{
if (MenuItems == null) return;
AssociatedObject.MenuItems.Clear();
if (SelectedMenuItem == null)
protected override void OnAttached()
{
// ??
base.OnAttached();
}
protected override void OnDetaching()
{
base.OnDetaching();
}
private static void OnMenuItemsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
if (dependencyObject is CreateMailNavigationItemBehavior behavior)
{
if (dependencyPropertyChangedEventArgs.NewValue != null)
behavior.RegisterMenuItemChanges();
behavior.ManageAccounts();
}
}
private void RegisterMenuItemChanges()
{
if (MenuItems != null)
{
MenuItems.CollectionChanged -= MenuCollectionUpdated;
MenuItems.CollectionChanged += MenuCollectionUpdated;
}
}
private void MenuCollectionUpdated(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
ManageAccounts();
}
private static void OnSelectedMenuItemChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
if (dependencyObject is CreateMailNavigationItemBehavior behavior)
{
behavior.ManageAccounts();
}
}
private void ManageAccounts()
{
if (MenuItems == null) return;
AssociatedObject.MenuItems.Clear();
if (SelectedMenuItem == null)
{
// ??
}
}
}
}

View File

@@ -4,69 +4,70 @@ using Windows.UI.Xaml;
using Wino.Core.Domain.Interfaces;
using Wino.Core.UWP.Controls;
namespace Wino.Controls;
public partial class AccountNavigationItem : WinoNavigationViewItem
namespace Wino.Controls
{
public static readonly DependencyProperty IsActiveAccountProperty = DependencyProperty.Register(nameof(IsActiveAccount), typeof(bool), typeof(AccountNavigationItem), new PropertyMetadata(false, new PropertyChangedCallback(OnIsActiveAccountChanged)));
public static readonly DependencyProperty BindingDataProperty = DependencyProperty.Register(nameof(BindingData), typeof(IAccountMenuItem), typeof(AccountNavigationItem), new PropertyMetadata(null));
public bool IsActiveAccount
public partial class AccountNavigationItem : WinoNavigationViewItem
{
get { return (bool)GetValue(IsActiveAccountProperty); }
set { SetValue(IsActiveAccountProperty, value); }
}
public IAccountMenuItem BindingData
{
get { return (IAccountMenuItem)GetValue(BindingDataProperty); }
set { SetValue(BindingDataProperty, value); }
}
public static readonly DependencyProperty IsActiveAccountProperty = DependencyProperty.Register(nameof(IsActiveAccount), typeof(bool), typeof(AccountNavigationItem), new PropertyMetadata(false, new PropertyChangedCallback(OnIsActiveAccountChanged)));
public static readonly DependencyProperty BindingDataProperty = DependencyProperty.Register(nameof(BindingData), typeof(IAccountMenuItem), typeof(AccountNavigationItem), new PropertyMetadata(null));
private const string PART_NavigationViewItemMenuItemsHost = "NavigationViewItemMenuItemsHost";
private const string PART_SelectionIndicator = "CustomSelectionIndicator";
private ItemsRepeater _itemsRepeater;
private Windows.UI.Xaml.Shapes.Rectangle _selectionIndicator;
public bool IsActiveAccount
{
get { return (bool)GetValue(IsActiveAccountProperty); }
set { SetValue(IsActiveAccountProperty, value); }
}
public AccountNavigationItem()
{
DefaultStyleKey = typeof(AccountNavigationItem);
}
public IAccountMenuItem BindingData
{
get { return (IAccountMenuItem)GetValue(BindingDataProperty); }
set { SetValue(BindingDataProperty, value); }
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
private const string PART_NavigationViewItemMenuItemsHost = "NavigationViewItemMenuItemsHost";
private const string PART_SelectionIndicator = "CustomSelectionIndicator";
_itemsRepeater = GetTemplateChild(PART_NavigationViewItemMenuItemsHost) as ItemsRepeater;
_selectionIndicator = GetTemplateChild(PART_SelectionIndicator) as Windows.UI.Xaml.Shapes.Rectangle;
private ItemsRepeater _itemsRepeater;
private Windows.UI.Xaml.Shapes.Rectangle _selectionIndicator;
if (_itemsRepeater == null) return;
public AccountNavigationItem()
{
DefaultStyleKey = typeof(AccountNavigationItem);
}
(_itemsRepeater.Layout as StackLayout).Spacing = 0;
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
UpdateSelectionBorder();
}
_itemsRepeater = GetTemplateChild(PART_NavigationViewItemMenuItemsHost) as ItemsRepeater;
_selectionIndicator = GetTemplateChild(PART_SelectionIndicator) as Windows.UI.Xaml.Shapes.Rectangle;
private static void OnIsActiveAccountChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is AccountNavigationItem control)
control.UpdateSelectionBorder();
}
if (_itemsRepeater == null) return;
private void UpdateSelectionBorder()
{
if (_selectionIndicator == null) return;
(_itemsRepeater.Layout as StackLayout).Spacing = 0;
// Adjsuting Margin in the styles are not possible due to the fact that we use the same tempalte for different types of menu items.
// Account templates listed under merged accounts will have Padding of 44. We must adopt to that.
UpdateSelectionBorder();
}
bool hasParentMenuItem = BindingData is IAccountMenuItem accountMenuItem && accountMenuItem.ParentMenuItem != null;
private static void OnIsActiveAccountChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is AccountNavigationItem control)
control.UpdateSelectionBorder();
}
_selectionIndicator.Margin = !hasParentMenuItem ? new Thickness(-44, 12, 0, 12) : new Thickness(-60, 12, -60, 12);
_selectionIndicator.Scale = IsActiveAccount ? new Vector3(1, 1, 1) : new Vector3(0, 0, 0);
_selectionIndicator.Visibility = IsActiveAccount ? Visibility.Visible : Visibility.Collapsed;
private void UpdateSelectionBorder()
{
if (_selectionIndicator == null) return;
// Adjsuting Margin in the styles are not possible due to the fact that we use the same tempalte for different types of menu items.
// Account templates listed under merged accounts will have Padding of 44. We must adopt to that.
bool hasParentMenuItem = BindingData is IAccountMenuItem accountMenuItem && accountMenuItem.ParentMenuItem != null;
_selectionIndicator.Margin = !hasParentMenuItem ? new Thickness(-44, 12, 0, 12) : new Thickness(-60, 12, -60, 12);
_selectionIndicator.Scale = IsActiveAccount ? new Vector3(1, 1, 1) : new Vector3(0, 0, 0);
_selectionIndicator.Visibility = IsActiveAccount ? Visibility.Visible : Visibility.Collapsed;
}
}
}

View File

@@ -13,244 +13,254 @@ using Wino.Extensions;
using Wino.Mail.ViewModels.Data;
using Wino.Mail.ViewModels.Messages;
namespace Wino.Controls.Advanced;
/// <summary>
/// Custom ListView control that handles multiple selection with Extended/Multiple selection mode
/// and supports threads.
/// </summary>
public partial class WinoListView : ListView, IDisposable
namespace Wino.Controls.Advanced
{
private ILogger logger = Log.ForContext<WinoListView>();
private const string PART_ScrollViewer = "ScrollViewer";
private ScrollViewer internalScrollviewer;
/// <summary>
/// Gets or sets whether this ListView belongs to thread items.
/// This is important for detecting selected items etc.
/// Custom ListView control that handles multiple selection with Extended/Multiple selection mode
/// and supports threads.
/// </summary>
public bool IsThreadListView
public partial class WinoListView : ListView, IDisposable
{
get { return (bool)GetValue(IsThreadListViewProperty); }
set { SetValue(IsThreadListViewProperty, value); }
}
private ILogger logger = Log.ForContext<WinoListView>();
public ICommand ItemDeletedCommand
{
get { return (ICommand)GetValue(ItemDeletedCommandProperty); }
set { SetValue(ItemDeletedCommandProperty, value); }
}
private const string PART_ScrollViewer = "ScrollViewer";
private ScrollViewer internalScrollviewer;
public ICommand LoadMoreCommand
{
get { return (ICommand)GetValue(LoadMoreCommandProperty); }
set { SetValue(LoadMoreCommandProperty, value); }
}
public bool IsThreadScrollingEnabled
{
get { return (bool)GetValue(IsThreadScrollingEnabledProperty); }
set { SetValue(IsThreadScrollingEnabledProperty, value); }
}
public static readonly DependencyProperty IsThreadScrollingEnabledProperty = DependencyProperty.Register(nameof(IsThreadScrollingEnabled), typeof(bool), typeof(WinoListView), new PropertyMetadata(false));
public static readonly DependencyProperty LoadMoreCommandProperty = DependencyProperty.Register(nameof(LoadMoreCommand), typeof(ICommand), typeof(WinoListView), new PropertyMetadata(null));
public static readonly DependencyProperty IsThreadListViewProperty = DependencyProperty.Register(nameof(IsThreadListView), typeof(bool), typeof(WinoListView), new PropertyMetadata(false, new PropertyChangedCallback(OnIsThreadViewChanged)));
public static readonly DependencyProperty ItemDeletedCommandProperty = DependencyProperty.Register(nameof(ItemDeletedCommand), typeof(ICommand), typeof(WinoListView), new PropertyMetadata(null));
public WinoListView()
{
CanDragItems = true;
IsItemClickEnabled = true;
IsMultiSelectCheckBoxEnabled = true;
IsRightTapEnabled = true;
SelectionMode = ListViewSelectionMode.Extended;
ShowsScrollingPlaceholders = false;
SingleSelectionFollowsFocus = true;
DragItemsCompleted += ItemDragCompleted;
DragItemsStarting += ItemDragStarting;
SelectionChanged += SelectedItemsChanged;
ProcessKeyboardAccelerators += ProcessDelKey;
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
internalScrollviewer = GetTemplateChild(PART_ScrollViewer) as ScrollViewer;
if (internalScrollviewer == null)
/// <summary>
/// Gets or sets whether this ListView belongs to thread items.
/// This is important for detecting selected items etc.
/// </summary>
public bool IsThreadListView
{
logger.Warning("WinoListView does not have an internal ScrollViewer. Infinite scrolling behavior might be effected.");
return;
get { return (bool)GetValue(IsThreadListViewProperty); }
set { SetValue(IsThreadListViewProperty, value); }
}
internalScrollviewer.ViewChanged -= InternalScrollVeiwerViewChanged;
internalScrollviewer.ViewChanged += InternalScrollVeiwerViewChanged;
}
private static void OnIsThreadViewChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoListView winoListView)
public ICommand ItemDeletedCommand
{
winoListView.AdjustThreadViewContainerVisuals();
get { return (ICommand)GetValue(ItemDeletedCommandProperty); }
set { SetValue(ItemDeletedCommandProperty, value); }
}
}
private void AdjustThreadViewContainerVisuals()
{
if (IsThreadListView)
public ICommand LoadMoreCommand
{
ItemContainerTransitions.Clear();
get { return (ICommand)GetValue(LoadMoreCommandProperty); }
set { SetValue(LoadMoreCommandProperty, value); }
}
}
private double lastestRaisedOffset = 0;
private int lastItemSize = 0;
// TODO: This is buggy. Does not work all the time. Debug.
private void InternalScrollVeiwerViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
if (internalScrollviewer == null) return;
// No need to raise init request if there are no items in the list.
if (Items.Count == 0) return;
// If the scrolling is finished, check the current viewport height.
if (e.IsIntermediate)
public bool IsThreadScrollingEnabled
{
var currentOffset = internalScrollviewer.VerticalOffset;
var maxOffset = internalScrollviewer.ScrollableHeight;
get { return (bool)GetValue(IsThreadScrollingEnabledProperty); }
set { SetValue(IsThreadScrollingEnabledProperty, value); }
}
if (currentOffset + 10 >= maxOffset && lastestRaisedOffset != maxOffset && Items.Count != lastItemSize)
public static readonly DependencyProperty IsThreadScrollingEnabledProperty = DependencyProperty.Register(nameof(IsThreadScrollingEnabled), typeof(bool), typeof(WinoListView), new PropertyMetadata(false));
public static readonly DependencyProperty LoadMoreCommandProperty = DependencyProperty.Register(nameof(LoadMoreCommand), typeof(ICommand), typeof(WinoListView), new PropertyMetadata(null));
public static readonly DependencyProperty IsThreadListViewProperty = DependencyProperty.Register(nameof(IsThreadListView), typeof(bool), typeof(WinoListView), new PropertyMetadata(false, new PropertyChangedCallback(OnIsThreadViewChanged)));
public static readonly DependencyProperty ItemDeletedCommandProperty = DependencyProperty.Register(nameof(ItemDeletedCommand), typeof(ICommand), typeof(WinoListView), new PropertyMetadata(null));
public WinoListView()
{
CanDragItems = true;
IsItemClickEnabled = true;
IsMultiSelectCheckBoxEnabled = true;
IsRightTapEnabled = true;
SelectionMode = ListViewSelectionMode.Extended;
ShowsScrollingPlaceholders = false;
SingleSelectionFollowsFocus = true;
DragItemsCompleted += ItemDragCompleted;
DragItemsStarting += ItemDragStarting;
SelectionChanged += SelectedItemsChanged;
ProcessKeyboardAccelerators += ProcessDelKey;
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
internalScrollviewer = GetTemplateChild(PART_ScrollViewer) as ScrollViewer;
if (internalScrollviewer == null)
{
// We must load more.
lastestRaisedOffset = maxOffset;
lastItemSize = Items.Count;
logger.Warning("WinoListView does not have an internal ScrollViewer. Infinite scrolling behavior might be effected.");
return;
}
LoadMoreCommand?.Execute(null);
internalScrollviewer.ViewChanged -= InternalScrollVeiwerViewChanged;
internalScrollviewer.ViewChanged += InternalScrollVeiwerViewChanged;
}
private static void OnIsThreadViewChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoListView winoListView)
{
winoListView.AdjustThreadViewContainerVisuals();
}
}
}
private void ProcessDelKey(UIElement sender, Windows.UI.Xaml.Input.ProcessKeyboardAcceleratorEventArgs args)
{
if (args.Key == Windows.System.VirtualKey.Delete)
private void AdjustThreadViewContainerVisuals()
{
args.Handled = true;
ItemDeletedCommand?.Execute(MailOperation.SoftDelete);
}
}
private void ItemDragCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
{
if (args.Items.Any(a => a is MailItemViewModel))
{
args.Items.Cast<MailItemViewModel>().ForEach(a => a.IsCustomFocused = false);
}
}
private void ItemDragStarting(object sender, DragItemsStartingEventArgs args)
{
// Dragging multiple mails from different accounts/folders are supported with the condition below:
// All mails belongs to the drag will be matched on the dropped folder's account.
// Meaning that if users drag 1 mail from Account A/Inbox and 1 mail from Account B/Inbox,
// and drop to Account A/Inbox, the mail from Account B/Inbox will NOT be moved.
if (IsThreadListView)
{
var allItems = args.Items.Cast<MailItemViewModel>();
// Highlight all items
allItems.ForEach(a => a.IsCustomFocused = true);
// Set native drag arg properties.
var dragPackage = new MailDragPackage(allItems.Cast<IMailItem>());
args.Data.Properties.Add(nameof(MailDragPackage), dragPackage);
}
else
{
var dragPackage = new MailDragPackage(args.Items.Cast<IMailItem>());
args.Data.Properties.Add(nameof(MailDragPackage), dragPackage);
}
}
public void ChangeSelectionMode(ListViewSelectionMode selectionMode)
{
SelectionMode = selectionMode;
if (!IsThreadListView)
{
Items.Where(a => a is ThreadMailItemViewModel).Cast<ThreadMailItemViewModel>().ForEach(c =>
if (IsThreadListView)
{
var threadListView = GetThreadInternalListView(c);
ItemContainerTransitions.Clear();
}
}
if (threadListView != null)
private double lastestRaisedOffset = 0;
private int lastItemSize = 0;
// TODO: This is buggy. Does not work all the time. Debug.
private void InternalScrollVeiwerViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
if (internalScrollviewer == null) return;
// No need to raise init request if there are no items in the list.
if (Items.Count == 0) return;
// If the scrolling is finished, check the current viewport height.
if (e.IsIntermediate)
{
var currentOffset = internalScrollviewer.VerticalOffset;
var maxOffset = internalScrollviewer.ScrollableHeight;
if (currentOffset + 10 >= maxOffset && lastestRaisedOffset != maxOffset && Items.Count != lastItemSize)
{
threadListView.SelectionMode = selectionMode;
// We must load more.
lastestRaisedOffset = maxOffset;
lastItemSize = Items.Count;
LoadMoreCommand?.Execute(null);
}
});
}
}
}
/// <summary>
/// Finds the container for given mail item and adds it to selected items.
/// </summary>
/// <param name="mailItemViewModel">Mail to be added to selected items.</param>
/// <returns>Whether selection was successful or not.</returns>
public bool SelectMailItemContainer(MailItemViewModel mailItemViewModel)
{
var itemContainer = ContainerFromItem(mailItemViewModel);
// This item might be in thread container.
if (itemContainer == null)
private void ProcessDelKey(UIElement sender, Windows.UI.Xaml.Input.ProcessKeyboardAcceleratorEventArgs args)
{
bool found = false;
Items.OfType<ThreadMailItemViewModel>().ForEach(c =>
if (args.Key == Windows.System.VirtualKey.Delete)
{
if (!found)
args.Handled = true;
ItemDeletedCommand?.Execute(MailOperation.SoftDelete);
}
}
private void ItemDragCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
{
if (args.Items.Any(a => a is MailItemViewModel))
{
args.Items.Cast<MailItemViewModel>().ForEach(a => a.IsCustomFocused = false);
}
}
private void ItemDragStarting(object sender, DragItemsStartingEventArgs args)
{
// Dragging multiple mails from different accounts/folders are supported with the condition below:
// All mails belongs to the drag will be matched on the dropped folder's account.
// Meaning that if users drag 1 mail from Account A/Inbox and 1 mail from Account B/Inbox,
// and drop to Account A/Inbox, the mail from Account B/Inbox will NOT be moved.
if (IsThreadListView)
{
var allItems = args.Items.Cast<MailItemViewModel>();
// Highlight all items
allItems.ForEach(a => a.IsCustomFocused = true);
// Set native drag arg properties.
var dragPackage = new MailDragPackage(allItems.Cast<IMailItem>());
args.Data.Properties.Add(nameof(MailDragPackage), dragPackage);
}
else
{
var dragPackage = new MailDragPackage(args.Items.Cast<IMailItem>());
args.Data.Properties.Add(nameof(MailDragPackage), dragPackage);
}
}
public void ChangeSelectionMode(ListViewSelectionMode selectionMode)
{
SelectionMode = selectionMode;
if (!IsThreadListView)
{
Items.Where(a => a is ThreadMailItemViewModel).Cast<ThreadMailItemViewModel>().ForEach(c =>
{
var threadListView = GetThreadInternalListView(c);
if (threadListView != null)
found = threadListView.SelectMailItemContainer(mailItemViewModel);
}
});
return found;
{
threadListView.SelectionMode = selectionMode;
}
});
}
}
SelectedItems.Add(mailItemViewModel);
return true;
}
/// <summary>
/// Recursively clears all selections except the given mail.
/// </summary>
/// <param name="exceptViewModel">Exceptional mail item to be not unselected.</param>
/// <param name="preserveThreadExpanding">Whether expansion states of thread containers should stay as it is or not.</param>
public void ClearSelections(MailItemViewModel exceptViewModel = null, bool preserveThreadExpanding = false)
{
SelectedItems.Clear();
Items.Where(a => a is ThreadMailItemViewModel).Cast<ThreadMailItemViewModel>().ForEach(c =>
/// <summary>
/// Finds the container for given mail item and adds it to selected items.
/// </summary>
/// <param name="mailItemViewModel">Mail to be added to selected items.</param>
/// <returns>Whether selection was successful or not.</returns>
public bool SelectMailItemContainer(MailItemViewModel mailItemViewModel)
{
var threadListView = GetThreadInternalListView(c);
var itemContainer = ContainerFromItem(mailItemViewModel);
if (threadListView == null)
return;
if (exceptViewModel != null)
// This item might be in thread container.
if (itemContainer == null)
{
if (!threadListView.SelectedItems.Contains(exceptViewModel))
bool found = false;
Items.OfType<ThreadMailItemViewModel>().ForEach(c =>
{
if (!found)
{
var threadListView = GetThreadInternalListView(c);
if (threadListView != null)
found = threadListView.SelectMailItemContainer(mailItemViewModel);
}
});
return found;
}
SelectedItems.Add(mailItemViewModel);
return true;
}
/// <summary>
/// Recursively clears all selections except the given mail.
/// </summary>
/// <param name="exceptViewModel">Exceptional mail item to be not unselected.</param>
/// <param name="preserveThreadExpanding">Whether expansion states of thread containers should stay as it is or not.</param>
public void ClearSelections(MailItemViewModel exceptViewModel = null, bool preserveThreadExpanding = false)
{
SelectedItems.Clear();
Items.Where(a => a is ThreadMailItemViewModel).Cast<ThreadMailItemViewModel>().ForEach(c =>
{
var threadListView = GetThreadInternalListView(c);
if (threadListView == null)
return;
if (exceptViewModel != null)
{
if (!threadListView.SelectedItems.Contains(exceptViewModel))
{
if (!preserveThreadExpanding)
{
c.IsThreadExpanded = false;
}
threadListView.SelectedItems.Clear();
}
}
else
{
if (!preserveThreadExpanding)
{
@@ -259,159 +269,150 @@ public partial class WinoListView : ListView, IDisposable
threadListView.SelectedItems.Clear();
}
}
else
{
if (!preserveThreadExpanding)
{
c.IsThreadExpanded = false;
}
threadListView.SelectedItems.Clear();
}
});
}
/// <summary>
/// Recursively selects all mails, including thread items.
/// </summary>
public void SelectAllWino()
{
SelectAll();
Items.Where(a => a is ThreadMailItemViewModel).Cast<ThreadMailItemViewModel>().ForEach(c =>
{
c.IsThreadExpanded = true;
var threadListView = GetThreadInternalListView(c);
threadListView?.SelectAll();
});
}
// SelectedItems changed.
private void SelectedItemsChanged(object sender, SelectionChangedEventArgs e)
{
if (e.RemovedItems != null)
{
foreach (var removedItem in e.RemovedItems)
{
if (removedItem is MailItemViewModel removedMailItemViewModel)
{
// Mail item un-selected.
removedMailItemViewModel.IsSelected = false;
WeakReferenceMessenger.Default.Send(new MailItemSelectionRemovedEvent(removedMailItemViewModel));
}
else if (removedItem is ThreadMailItemViewModel removedThreadItemViewModel)
{
removedThreadItemViewModel.IsThreadExpanded = false;
}
}
});
}
if (e.AddedItems != null)
/// <summary>
/// Recursively selects all mails, including thread items.
/// </summary>
public void SelectAllWino()
{
foreach (var addedItem in e.AddedItems)
SelectAll();
Items.Where(a => a is ThreadMailItemViewModel).Cast<ThreadMailItemViewModel>().ForEach(c =>
{
if (addedItem is MailItemViewModel addedMailItemViewModel)
{
// Mail item selected.
c.IsThreadExpanded = true;
addedMailItemViewModel.IsSelected = true;
var threadListView = GetThreadInternalListView(c);
WeakReferenceMessenger.Default.Send(new MailItemSelectedEvent(addedMailItemViewModel));
}
else if (addedItem is ThreadMailItemViewModel threadMailItemViewModel)
threadListView?.SelectAll();
});
}
// SelectedItems changed.
private void SelectedItemsChanged(object sender, SelectionChangedEventArgs e)
{
if (e.RemovedItems != null)
{
foreach (var removedItem in e.RemovedItems)
{
if (IsThreadScrollingEnabled)
if (removedItem is MailItemViewModel removedMailItemViewModel)
{
if (internalScrollviewer != null && ContainerFromItem(threadMailItemViewModel) is FrameworkElement threadFrameworkElement)
// Mail item un-selected.
removedMailItemViewModel.IsSelected = false;
WeakReferenceMessenger.Default.Send(new MailItemSelectionRemovedEvent(removedMailItemViewModel));
}
else if (removedItem is ThreadMailItemViewModel removedThreadItemViewModel)
{
removedThreadItemViewModel.IsThreadExpanded = false;
}
}
}
if (e.AddedItems != null)
{
foreach (var addedItem in e.AddedItems)
{
if (addedItem is MailItemViewModel addedMailItemViewModel)
{
// Mail item selected.
addedMailItemViewModel.IsSelected = true;
WeakReferenceMessenger.Default.Send(new MailItemSelectedEvent(addedMailItemViewModel));
}
else if (addedItem is ThreadMailItemViewModel threadMailItemViewModel)
{
if (IsThreadScrollingEnabled)
{
internalScrollviewer.ScrollToElement(threadFrameworkElement, true, true, bringToTopOrLeft: true);
if (internalScrollviewer != null && ContainerFromItem(threadMailItemViewModel) is FrameworkElement threadFrameworkElement)
{
internalScrollviewer.ScrollToElement(threadFrameworkElement, true, true, bringToTopOrLeft: true);
}
}
// Try to select first item.
if (GetThreadInternalListView(threadMailItemViewModel) is WinoListView internalListView)
{
internalListView.SelectFirstItem();
}
}
}
}
// Try to select first item.
if (GetThreadInternalListView(threadMailItemViewModel) is WinoListView internalListView)
if (!IsThreadListView)
{
if (SelectionMode == ListViewSelectionMode.Extended && SelectedItems.Count == 1)
{
// Only 1 single item is selected in extended mode for main list view.
// We should un-select all thread items.
Items.Where(a => a is ThreadMailItemViewModel).Cast<ThreadMailItemViewModel>().ForEach(c =>
{
internalListView.SelectFirstItem();
// c.IsThreadExpanded = false;
var threadListView = GetThreadInternalListView(c);
threadListView?.SelectedItems.Clear();
});
}
}
}
public async void SelectFirstItem()
{
if (Items.Count > 0)
{
if (Items[0] is MailItemViewModel firstMailItemViewModel)
{
// Make sure the invisible container is realized.
await Task.Delay(250);
if (ContainerFromItem(firstMailItemViewModel) is ListViewItem firstItemContainer)
{
firstItemContainer.IsSelected = true;
}
firstMailItemViewModel.IsSelected = true;
}
}
}
if (!IsThreadListView)
private WinoListView GetThreadInternalListView(ThreadMailItemViewModel threadMailItemViewModel)
{
if (SelectionMode == ListViewSelectionMode.Extended && SelectedItems.Count == 1)
var itemContainer = ContainerFromItem(threadMailItemViewModel);
if (itemContainer is ListViewItem listItem)
{
// Only 1 single item is selected in extended mode for main list view.
// We should un-select all thread items.
var expander = listItem.GetChildByName<WinoExpander>("ThreadExpander");
Items.Where(a => a is ThreadMailItemViewModel).Cast<ThreadMailItemViewModel>().ForEach(c =>
{
// c.IsThreadExpanded = false;
var threadListView = GetThreadInternalListView(c);
threadListView?.SelectedItems.Clear();
});
if (expander != null)
return expander.Content as WinoListView;
}
return null;
}
}
public async void SelectFirstItem()
{
if (Items.Count > 0)
public void Dispose()
{
if (Items[0] is MailItemViewModel firstMailItemViewModel)
{
// Make sure the invisible container is realized.
await Task.Delay(250);
DragItemsCompleted -= ItemDragCompleted;
DragItemsStarting -= ItemDragStarting;
SelectionChanged -= SelectedItemsChanged;
ProcessKeyboardAccelerators -= ProcessDelKey;
if (ContainerFromItem(firstMailItemViewModel) is ListViewItem firstItemContainer)
if (internalScrollviewer != null)
{
internalScrollviewer.ViewChanged -= InternalScrollVeiwerViewChanged;
}
foreach (var item in Items)
{
if (item is ThreadMailItemViewModel threadMailItemViewModel)
{
firstItemContainer.IsSelected = true;
var threadListView = GetThreadInternalListView(threadMailItemViewModel);
threadListView?.Dispose();
}
firstMailItemViewModel.IsSelected = true;
}
}
}
private WinoListView GetThreadInternalListView(ThreadMailItemViewModel threadMailItemViewModel)
{
var itemContainer = ContainerFromItem(threadMailItemViewModel);
if (itemContainer is ListViewItem listItem)
{
var expander = listItem.GetChildByName<WinoExpander>("ThreadExpander");
if (expander != null)
return expander.Content as WinoListView;
}
return null;
}
public void Dispose()
{
DragItemsCompleted -= ItemDragCompleted;
DragItemsStarting -= ItemDragStarting;
SelectionChanged -= SelectedItemsChanged;
ProcessKeyboardAccelerators -= ProcessDelKey;
if (internalScrollviewer != null)
{
internalScrollviewer.ViewChanged -= InternalScrollVeiwerViewChanged;
}
foreach (var item in Items)
{
if (item is ThreadMailItemViewModel threadMailItemViewModel)
{
var threadListView = GetThreadInternalListView(threadMailItemViewModel);
threadListView?.Dispose();
}
}
}

View File

@@ -13,185 +13,186 @@ using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Shapes;
using Wino.Core.UWP.Services;
namespace Wino.Controls;
public partial class ImagePreviewControl : Control
namespace Wino.Controls
{
private const string PART_EllipseInitialsGrid = "EllipseInitialsGrid";
private const string PART_InitialsTextBlock = "InitialsTextBlock";
private const string PART_KnownHostImage = "KnownHostImage";
private const string PART_Ellipse = "Ellipse";
#region Dependency Properties
public static readonly DependencyProperty FromNameProperty = DependencyProperty.Register(nameof(FromName), typeof(string), typeof(ImagePreviewControl), new PropertyMetadata(string.Empty, OnAddressInformationChanged));
public static readonly DependencyProperty FromAddressProperty = DependencyProperty.Register(nameof(FromAddress), typeof(string), typeof(ImagePreviewControl), new PropertyMetadata(string.Empty, OnAddressInformationChanged));
public static readonly DependencyProperty SenderContactPictureProperty = DependencyProperty.Register(nameof(SenderContactPicture), typeof(string), typeof(ImagePreviewControl), new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnAddressInformationChanged)));
/// <summary>
/// Gets or sets base64 string of the sender contact picture.
/// </summary>
public string SenderContactPicture
public partial class ImagePreviewControl : Control
{
get { return (string)GetValue(SenderContactPictureProperty); }
set { SetValue(SenderContactPictureProperty, value); }
}
private const string PART_EllipseInitialsGrid = "EllipseInitialsGrid";
private const string PART_InitialsTextBlock = "InitialsTextBlock";
private const string PART_KnownHostImage = "KnownHostImage";
private const string PART_Ellipse = "Ellipse";
public string FromName
{
get { return (string)GetValue(FromNameProperty); }
set { SetValue(FromNameProperty, value); }
}
#region Dependency Properties
public string FromAddress
{
get { return (string)GetValue(FromAddressProperty); }
set { SetValue(FromAddressProperty, value); }
}
public static readonly DependencyProperty FromNameProperty = DependencyProperty.Register(nameof(FromName), typeof(string), typeof(ImagePreviewControl), new PropertyMetadata(string.Empty, OnAddressInformationChanged));
public static readonly DependencyProperty FromAddressProperty = DependencyProperty.Register(nameof(FromAddress), typeof(string), typeof(ImagePreviewControl), new PropertyMetadata(string.Empty, OnAddressInformationChanged));
public static readonly DependencyProperty SenderContactPictureProperty = DependencyProperty.Register(nameof(SenderContactPicture), typeof(string), typeof(ImagePreviewControl), new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnAddressInformationChanged)));
#endregion
private Ellipse Ellipse;
private Grid InitialsGrid;
private TextBlock InitialsTextblock;
private Image KnownHostImage;
private CancellationTokenSource contactPictureLoadingCancellationTokenSource;
public ImagePreviewControl()
{
DefaultStyleKey = nameof(ImagePreviewControl);
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
InitialsGrid = GetTemplateChild(PART_EllipseInitialsGrid) as Grid;
InitialsTextblock = GetTemplateChild(PART_InitialsTextBlock) as TextBlock;
KnownHostImage = GetTemplateChild(PART_KnownHostImage) as Image;
Ellipse = GetTemplateChild(PART_Ellipse) as Ellipse;
UpdateInformation();
}
private static void OnAddressInformationChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is ImagePreviewControl control)
control.UpdateInformation();
}
private async void UpdateInformation()
{
if (KnownHostImage == null || InitialsGrid == null || InitialsTextblock == null || (string.IsNullOrEmpty(FromName) && string.IsNullOrEmpty(FromAddress)))
return;
// Cancel active image loading if exists.
if (!contactPictureLoadingCancellationTokenSource?.IsCancellationRequested ?? false)
/// <summary>
/// Gets or sets base64 string of the sender contact picture.
/// </summary>
public string SenderContactPicture
{
contactPictureLoadingCancellationTokenSource.Cancel();
get { return (string)GetValue(SenderContactPictureProperty); }
set { SetValue(SenderContactPictureProperty, value); }
}
var host = ThumbnailService.GetHost(FromAddress);
bool isKnownHost = false;
if (!string.IsNullOrEmpty(host))
public string FromName
{
var tuple = ThumbnailService.CheckIsKnown(host);
isKnownHost = tuple.Item1;
host = tuple.Item2;
get { return (string)GetValue(FromNameProperty); }
set { SetValue(FromNameProperty, value); }
}
if (isKnownHost)
public string FromAddress
{
// Unrealize others.
KnownHostImage.Visibility = Visibility.Visible;
InitialsGrid.Visibility = Visibility.Collapsed;
// Apply company logo.
KnownHostImage.Source = new BitmapImage(new Uri(ThumbnailService.GetKnownHostImage(host)));
get { return (string)GetValue(FromAddressProperty); }
set { SetValue(FromAddressProperty, value); }
}
else
{
KnownHostImage.Visibility = Visibility.Collapsed;
InitialsGrid.Visibility = Visibility.Visible;
if (!string.IsNullOrEmpty(SenderContactPicture))
#endregion
private Ellipse Ellipse;
private Grid InitialsGrid;
private TextBlock InitialsTextblock;
private Image KnownHostImage;
private CancellationTokenSource contactPictureLoadingCancellationTokenSource;
public ImagePreviewControl()
{
DefaultStyleKey = nameof(ImagePreviewControl);
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
InitialsGrid = GetTemplateChild(PART_EllipseInitialsGrid) as Grid;
InitialsTextblock = GetTemplateChild(PART_InitialsTextBlock) as TextBlock;
KnownHostImage = GetTemplateChild(PART_KnownHostImage) as Image;
Ellipse = GetTemplateChild(PART_Ellipse) as Ellipse;
UpdateInformation();
}
private static void OnAddressInformationChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is ImagePreviewControl control)
control.UpdateInformation();
}
private async void UpdateInformation()
{
if (KnownHostImage == null || InitialsGrid == null || InitialsTextblock == null || (string.IsNullOrEmpty(FromName) && string.IsNullOrEmpty(FromAddress)))
return;
// Cancel active image loading if exists.
if (!contactPictureLoadingCancellationTokenSource?.IsCancellationRequested ?? false)
{
contactPictureLoadingCancellationTokenSource = new CancellationTokenSource();
contactPictureLoadingCancellationTokenSource.Cancel();
}
try
{
var brush = await GetContactImageBrushAsync();
var host = ThumbnailService.GetHost(FromAddress);
if (!contactPictureLoadingCancellationTokenSource?.Token.IsCancellationRequested ?? false)
{
Ellipse.Fill = brush;
InitialsTextblock.Text = string.Empty;
}
}
catch (Exception)
{
// Log exception.
Debugger.Break();
}
bool isKnownHost = false;
if (!string.IsNullOrEmpty(host))
{
var tuple = ThumbnailService.CheckIsKnown(host);
isKnownHost = tuple.Item1;
host = tuple.Item2;
}
if (isKnownHost)
{
// Unrealize others.
KnownHostImage.Visibility = Visibility.Visible;
InitialsGrid.Visibility = Visibility.Collapsed;
// Apply company logo.
KnownHostImage.Source = new BitmapImage(new Uri(ThumbnailService.GetKnownHostImage(host)));
}
else
{
var colorHash = new ColorHash();
var rgb = colorHash.Rgb(FromAddress);
KnownHostImage.Visibility = Visibility.Collapsed;
InitialsGrid.Visibility = Visibility.Visible;
Ellipse.Fill = new SolidColorBrush(Color.FromArgb(rgb.A, rgb.R, rgb.G, rgb.B));
InitialsTextblock.Text = ExtractInitialsFromName(FromName);
if (!string.IsNullOrEmpty(SenderContactPicture))
{
contactPictureLoadingCancellationTokenSource = new CancellationTokenSource();
try
{
var brush = await GetContactImageBrushAsync();
if (!contactPictureLoadingCancellationTokenSource?.Token.IsCancellationRequested ?? false)
{
Ellipse.Fill = brush;
InitialsTextblock.Text = string.Empty;
}
}
catch (Exception)
{
// Log exception.
Debugger.Break();
}
}
else
{
var colorHash = new ColorHash();
var rgb = colorHash.Rgb(FromAddress);
Ellipse.Fill = new SolidColorBrush(Color.FromArgb(rgb.A, rgb.R, rgb.G, rgb.B));
InitialsTextblock.Text = ExtractInitialsFromName(FromName);
}
}
}
}
private async Task<ImageBrush> GetContactImageBrushAsync()
{
// Load the image from base64 string.
var bitmapImage = new BitmapImage();
var imageArray = Convert.FromBase64String(SenderContactPicture);
var imageStream = new MemoryStream(imageArray);
var randomAccessImageStream = imageStream.AsRandomAccessStream();
randomAccessImageStream.Seek(0);
await bitmapImage.SetSourceAsync(randomAccessImageStream);
return new ImageBrush() { ImageSource = bitmapImage };
}
public string ExtractInitialsFromName(string name)
{
// Change from name to from address in case of name doesn't exists.
if (string.IsNullOrEmpty(name))
private async Task<ImageBrush> GetContactImageBrushAsync()
{
name = FromAddress;
// Load the image from base64 string.
var bitmapImage = new BitmapImage();
var imageArray = Convert.FromBase64String(SenderContactPicture);
var imageStream = new MemoryStream(imageArray);
var randomAccessImageStream = imageStream.AsRandomAccessStream();
randomAccessImageStream.Seek(0);
await bitmapImage.SetSourceAsync(randomAccessImageStream);
return new ImageBrush() { ImageSource = bitmapImage };
}
// first remove all: punctuation, separator chars, control chars, and numbers (unicode style regexes)
string initials = Regex.Replace(name, @"[\p{P}\p{S}\p{C}\p{N}]+", "");
// Replacing all possible whitespace/separator characters (unicode style), with a single, regular ascii space.
initials = Regex.Replace(initials, @"\p{Z}+", " ");
// Remove all Sr, Jr, I, II, III, IV, V, VI, VII, VIII, IX at the end of names
initials = Regex.Replace(initials.Trim(), @"\s+(?:[JS]R|I{1,3}|I[VX]|VI{0,3})$", "", RegexOptions.IgnoreCase);
// Extract up to 2 initials from the remaining cleaned name.
initials = Regex.Replace(initials, @"^(\p{L})[^\s]*(?:\s+(?:\p{L}+\s+(?=\p{L}))?(?:(\p{L})\p{L}*)?)?$", "$1$2").Trim();
if (initials.Length > 2)
public string ExtractInitialsFromName(string name)
{
// Worst case scenario, everything failed, just grab the first two letters of what we have left.
initials = initials.Substring(0, 2);
}
// Change from name to from address in case of name doesn't exists.
if (string.IsNullOrEmpty(name))
{
name = FromAddress;
}
return initials.ToUpperInvariant();
// first remove all: punctuation, separator chars, control chars, and numbers (unicode style regexes)
string initials = Regex.Replace(name, @"[\p{P}\p{S}\p{C}\p{N}]+", "");
// Replacing all possible whitespace/separator characters (unicode style), with a single, regular ascii space.
initials = Regex.Replace(initials, @"\p{Z}+", " ");
// Remove all Sr, Jr, I, II, III, IV, V, VI, VII, VIII, IX at the end of names
initials = Regex.Replace(initials.Trim(), @"\s+(?:[JS]R|I{1,3}|I[VX]|VI{0,3})$", "", RegexOptions.IgnoreCase);
// Extract up to 2 initials from the remaining cleaned name.
initials = Regex.Replace(initials, @"^(\p{L})[^\s]*(?:\s+(?:\p{L}+\s+(?=\p{L}))?(?:(\p{L})\p{L}*)?)?$", "$1$2").Trim();
if (initials.Length > 2)
{
// Worst case scenario, everything failed, just grab the first two letters of what we have left.
initials = initials.Substring(0, 2);
}
return initials.ToUpperInvariant();
}
}
}

View File

@@ -10,199 +10,200 @@ using Wino.Core.Domain.Models.MailItem;
using Wino.Extensions;
using Wino.Mail.ViewModels.Data;
namespace Wino.Controls;
public sealed partial class MailItemDisplayInformationControl : UserControl
namespace Wino.Controls
{
public ImagePreviewControl GetImagePreviewControl() => ContactImage;
public bool IsRunningHoverAction { get; set; }
public static readonly DependencyProperty DisplayModeProperty = DependencyProperty.Register(nameof(DisplayMode), typeof(MailListDisplayMode), typeof(MailItemDisplayInformationControl), new PropertyMetadata(MailListDisplayMode.Spacious));
public static readonly DependencyProperty ShowPreviewTextProperty = DependencyProperty.Register(nameof(ShowPreviewText), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(true));
public static readonly DependencyProperty IsCustomFocusedProperty = DependencyProperty.Register(nameof(IsCustomFocused), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
public static readonly DependencyProperty IsAvatarVisibleProperty = DependencyProperty.Register(nameof(IsAvatarVisible), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(true));
public static readonly DependencyProperty IsSubjectVisibleProperty = DependencyProperty.Register(nameof(IsSubjectVisible), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(true));
public static readonly DependencyProperty ConnectedExpanderProperty = DependencyProperty.Register(nameof(ConnectedExpander), typeof(WinoExpander), typeof(MailItemDisplayInformationControl), new PropertyMetadata(null));
public static readonly DependencyProperty LeftHoverActionProperty = DependencyProperty.Register(nameof(LeftHoverAction), typeof(MailOperation), typeof(MailItemDisplayInformationControl), new PropertyMetadata(MailOperation.None));
public static readonly DependencyProperty CenterHoverActionProperty = DependencyProperty.Register(nameof(CenterHoverAction), typeof(MailOperation), typeof(MailItemDisplayInformationControl), new PropertyMetadata(MailOperation.None));
public static readonly DependencyProperty RightHoverActionProperty = DependencyProperty.Register(nameof(RightHoverAction), typeof(MailOperation), typeof(MailItemDisplayInformationControl), new PropertyMetadata(MailOperation.None));
public static readonly DependencyProperty HoverActionExecutedCommandProperty = DependencyProperty.Register(nameof(HoverActionExecutedCommand), typeof(ICommand), typeof(MailItemDisplayInformationControl), new PropertyMetadata(null));
public static readonly DependencyProperty MailItemProperty = DependencyProperty.Register(nameof(MailItem), typeof(IMailItem), typeof(MailItemDisplayInformationControl), new PropertyMetadata(null, new PropertyChangedCallback(OnMailItemChanged)));
public static readonly DependencyProperty IsHoverActionsEnabledProperty = DependencyProperty.Register(nameof(IsHoverActionsEnabled), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(true));
public static readonly DependencyProperty Prefer24HourTimeFormatProperty = DependencyProperty.Register(nameof(Prefer24HourTimeFormat), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
public static readonly DependencyProperty IsThreadExpanderVisibleProperty = DependencyProperty.Register(nameof(IsThreadExpanderVisible), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
public static readonly DependencyProperty IsThreadExpandedProperty = DependencyProperty.Register(nameof(IsThreadExpanded), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
public bool IsThreadExpanded
public sealed partial class MailItemDisplayInformationControl : UserControl
{
get { return (bool)GetValue(IsThreadExpandedProperty); }
set { SetValue(IsThreadExpandedProperty, value); }
}
public ImagePreviewControl GetImagePreviewControl() => ContactImage;
public bool IsThreadExpanderVisible
{
get { return (bool)GetValue(IsThreadExpanderVisibleProperty); }
set { SetValue(IsThreadExpanderVisibleProperty, value); }
}
public bool IsRunningHoverAction { get; set; }
public bool Prefer24HourTimeFormat
{
get { return (bool)GetValue(Prefer24HourTimeFormatProperty); }
set { SetValue(Prefer24HourTimeFormatProperty, value); }
}
public static readonly DependencyProperty DisplayModeProperty = DependencyProperty.Register(nameof(DisplayMode), typeof(MailListDisplayMode), typeof(MailItemDisplayInformationControl), new PropertyMetadata(MailListDisplayMode.Spacious));
public static readonly DependencyProperty ShowPreviewTextProperty = DependencyProperty.Register(nameof(ShowPreviewText), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(true));
public static readonly DependencyProperty IsCustomFocusedProperty = DependencyProperty.Register(nameof(IsCustomFocused), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
public static readonly DependencyProperty IsAvatarVisibleProperty = DependencyProperty.Register(nameof(IsAvatarVisible), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(true));
public static readonly DependencyProperty IsSubjectVisibleProperty = DependencyProperty.Register(nameof(IsSubjectVisible), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(true));
public static readonly DependencyProperty ConnectedExpanderProperty = DependencyProperty.Register(nameof(ConnectedExpander), typeof(WinoExpander), typeof(MailItemDisplayInformationControl), new PropertyMetadata(null));
public static readonly DependencyProperty LeftHoverActionProperty = DependencyProperty.Register(nameof(LeftHoverAction), typeof(MailOperation), typeof(MailItemDisplayInformationControl), new PropertyMetadata(MailOperation.None));
public static readonly DependencyProperty CenterHoverActionProperty = DependencyProperty.Register(nameof(CenterHoverAction), typeof(MailOperation), typeof(MailItemDisplayInformationControl), new PropertyMetadata(MailOperation.None));
public static readonly DependencyProperty RightHoverActionProperty = DependencyProperty.Register(nameof(RightHoverAction), typeof(MailOperation), typeof(MailItemDisplayInformationControl), new PropertyMetadata(MailOperation.None));
public static readonly DependencyProperty HoverActionExecutedCommandProperty = DependencyProperty.Register(nameof(HoverActionExecutedCommand), typeof(ICommand), typeof(MailItemDisplayInformationControl), new PropertyMetadata(null));
public static readonly DependencyProperty MailItemProperty = DependencyProperty.Register(nameof(MailItem), typeof(IMailItem), typeof(MailItemDisplayInformationControl), new PropertyMetadata(null, new PropertyChangedCallback(OnMailItemChanged)));
public static readonly DependencyProperty IsHoverActionsEnabledProperty = DependencyProperty.Register(nameof(IsHoverActionsEnabled), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(true));
public static readonly DependencyProperty Prefer24HourTimeFormatProperty = DependencyProperty.Register(nameof(Prefer24HourTimeFormat), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
public static readonly DependencyProperty IsThreadExpanderVisibleProperty = DependencyProperty.Register(nameof(IsThreadExpanderVisible), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
public static readonly DependencyProperty IsThreadExpandedProperty = DependencyProperty.Register(nameof(IsThreadExpanded), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
public bool IsHoverActionsEnabled
{
get { return (bool)GetValue(IsHoverActionsEnabledProperty); }
set { SetValue(IsHoverActionsEnabledProperty, value); }
}
public IMailItem MailItem
{
get { return (IMailItem)GetValue(MailItemProperty); }
set { SetValue(MailItemProperty, value); }
}
public ICommand HoverActionExecutedCommand
{
get { return (ICommand)GetValue(HoverActionExecutedCommandProperty); }
set { SetValue(HoverActionExecutedCommandProperty, value); }
}
public MailOperation LeftHoverAction
{
get { return (MailOperation)GetValue(LeftHoverActionProperty); }
set { SetValue(LeftHoverActionProperty, value); }
}
public MailOperation CenterHoverAction
{
get { return (MailOperation)GetValue(CenterHoverActionProperty); }
set { SetValue(CenterHoverActionProperty, value); }
}
public MailOperation RightHoverAction
{
get { return (MailOperation)GetValue(RightHoverActionProperty); }
set { SetValue(RightHoverActionProperty, value); }
}
public WinoExpander ConnectedExpander
{
get { return (WinoExpander)GetValue(ConnectedExpanderProperty); }
set { SetValue(ConnectedExpanderProperty, value); }
}
public bool IsSubjectVisible
{
get { return (bool)GetValue(IsSubjectVisibleProperty); }
set { SetValue(IsSubjectVisibleProperty, value); }
}
public bool IsAvatarVisible
{
get { return (bool)GetValue(IsAvatarVisibleProperty); }
set { SetValue(IsAvatarVisibleProperty, value); }
}
public bool IsCustomFocused
{
get { return (bool)GetValue(IsCustomFocusedProperty); }
set { SetValue(IsCustomFocusedProperty, value); }
}
public bool ShowPreviewText
{
get { return (bool)GetValue(ShowPreviewTextProperty); }
set { SetValue(ShowPreviewTextProperty, value); }
}
public MailListDisplayMode DisplayMode
{
get { return (MailListDisplayMode)GetValue(DisplayModeProperty); }
set { SetValue(DisplayModeProperty, value); }
}
public MailItemDisplayInformationControl()
{
this.InitializeComponent();
var compositor = this.Visual().Compositor;
var leftBackgroundVisual = compositor.CreateSpriteVisual();
RootContainerVisualWrapper.SetChildVisual(leftBackgroundVisual);
MainContentContainer.EnableImplicitAnimation(VisualPropertyType.Offset, 400);
RootContainer.EnableImplicitAnimation(VisualPropertyType.Offset, 400);
ContentGrid.EnableImplicitAnimation(VisualPropertyType.Offset, 400);
ContentStackpanel.EnableImplicitAnimation(VisualPropertyType.Offset, 400);
IconsContainer.EnableImplicitAnimation(VisualPropertyType.Offset, 400);
RootContainerVisualWrapper.SizeChanged += (s, e) => leftBackgroundVisual.Size = e.NewSize.ToVector2();
}
private static void OnMailItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is MailItemDisplayInformationControl control)
public bool IsThreadExpanded
{
control.UpdateInformation();
get { return (bool)GetValue(IsThreadExpandedProperty); }
set { SetValue(IsThreadExpandedProperty, value); }
}
}
private void UpdateInformation()
{
if (MailItem == null) return;
TitleText.Text = string.IsNullOrWhiteSpace(MailItem.Subject) ? Translator.MailItemNoSubject : MailItem.Subject;
}
private void ControlPointerEntered(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
{
if (IsHoverActionsEnabled)
public bool IsThreadExpanderVisible
{
HoverActionButtons.Visibility = Visibility.Visible;
get { return (bool)GetValue(IsThreadExpanderVisibleProperty); }
set { SetValue(IsThreadExpanderVisibleProperty, value); }
}
}
private void ControlPointerExited(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
{
if (IsHoverActionsEnabled)
public bool Prefer24HourTimeFormat
{
HoverActionButtons.Visibility = Visibility.Collapsed;
get { return (bool)GetValue(Prefer24HourTimeFormatProperty); }
set { SetValue(Prefer24HourTimeFormatProperty, value); }
}
}
private void ExecuteHoverAction(MailOperation operation)
{
IsRunningHoverAction = true;
public bool IsHoverActionsEnabled
{
get { return (bool)GetValue(IsHoverActionsEnabledProperty); }
set { SetValue(IsHoverActionsEnabledProperty, value); }
}
MailOperationPreperationRequest package = null;
public IMailItem MailItem
{
get { return (IMailItem)GetValue(MailItemProperty); }
set { SetValue(MailItemProperty, value); }
}
if (MailItem is MailCopy mailCopy)
package = new MailOperationPreperationRequest(operation, mailCopy, toggleExecution: true);
else if (MailItem is ThreadMailItemViewModel threadMailItemViewModel)
package = new MailOperationPreperationRequest(operation, threadMailItemViewModel.GetMailCopies(), toggleExecution: true);
else if (MailItem is ThreadMailItem threadMailItem)
package = new MailOperationPreperationRequest(operation, threadMailItem.ThreadItems.Cast<MailItemViewModel>().Select(a => a.MailCopy), toggleExecution: true);
public ICommand HoverActionExecutedCommand
{
get { return (ICommand)GetValue(HoverActionExecutedCommandProperty); }
set { SetValue(HoverActionExecutedCommandProperty, value); }
}
if (package == null) return;
public MailOperation LeftHoverAction
{
get { return (MailOperation)GetValue(LeftHoverActionProperty); }
set { SetValue(LeftHoverActionProperty, value); }
}
HoverActionExecutedCommand?.Execute(package);
}
public MailOperation CenterHoverAction
{
get { return (MailOperation)GetValue(CenterHoverActionProperty); }
set { SetValue(CenterHoverActionProperty, value); }
}
private void FirstActionClicked(object sender, RoutedEventArgs e)
{
ExecuteHoverAction(LeftHoverAction);
}
public MailOperation RightHoverAction
{
get { return (MailOperation)GetValue(RightHoverActionProperty); }
set { SetValue(RightHoverActionProperty, value); }
}
private void SecondActionClicked(object sender, RoutedEventArgs e)
{
ExecuteHoverAction(CenterHoverAction);
}
public WinoExpander ConnectedExpander
{
get { return (WinoExpander)GetValue(ConnectedExpanderProperty); }
set { SetValue(ConnectedExpanderProperty, value); }
}
private void ThirdActionClicked(object sender, RoutedEventArgs e)
{
ExecuteHoverAction(RightHoverAction);
public bool IsSubjectVisible
{
get { return (bool)GetValue(IsSubjectVisibleProperty); }
set { SetValue(IsSubjectVisibleProperty, value); }
}
public bool IsAvatarVisible
{
get { return (bool)GetValue(IsAvatarVisibleProperty); }
set { SetValue(IsAvatarVisibleProperty, value); }
}
public bool IsCustomFocused
{
get { return (bool)GetValue(IsCustomFocusedProperty); }
set { SetValue(IsCustomFocusedProperty, value); }
}
public bool ShowPreviewText
{
get { return (bool)GetValue(ShowPreviewTextProperty); }
set { SetValue(ShowPreviewTextProperty, value); }
}
public MailListDisplayMode DisplayMode
{
get { return (MailListDisplayMode)GetValue(DisplayModeProperty); }
set { SetValue(DisplayModeProperty, value); }
}
public MailItemDisplayInformationControl()
{
this.InitializeComponent();
var compositor = this.Visual().Compositor;
var leftBackgroundVisual = compositor.CreateSpriteVisual();
RootContainerVisualWrapper.SetChildVisual(leftBackgroundVisual);
MainContentContainer.EnableImplicitAnimation(VisualPropertyType.Offset, 400);
RootContainer.EnableImplicitAnimation(VisualPropertyType.Offset, 400);
ContentGrid.EnableImplicitAnimation(VisualPropertyType.Offset, 400);
ContentStackpanel.EnableImplicitAnimation(VisualPropertyType.Offset, 400);
IconsContainer.EnableImplicitAnimation(VisualPropertyType.Offset, 400);
RootContainerVisualWrapper.SizeChanged += (s, e) => leftBackgroundVisual.Size = e.NewSize.ToVector2();
}
private static void OnMailItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is MailItemDisplayInformationControl control)
{
control.UpdateInformation();
}
}
private void UpdateInformation()
{
if (MailItem == null) return;
TitleText.Text = string.IsNullOrWhiteSpace(MailItem.Subject) ? Translator.MailItemNoSubject : MailItem.Subject;
}
private void ControlPointerEntered(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
{
if (IsHoverActionsEnabled)
{
HoverActionButtons.Visibility = Visibility.Visible;
}
}
private void ControlPointerExited(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
{
if (IsHoverActionsEnabled)
{
HoverActionButtons.Visibility = Visibility.Collapsed;
}
}
private void ExecuteHoverAction(MailOperation operation)
{
IsRunningHoverAction = true;
MailOperationPreperationRequest package = null;
if (MailItem is MailCopy mailCopy)
package = new MailOperationPreperationRequest(operation, mailCopy, toggleExecution: true);
else if (MailItem is ThreadMailItemViewModel threadMailItemViewModel)
package = new MailOperationPreperationRequest(operation, threadMailItemViewModel.GetMailCopies(), toggleExecution: true);
else if (MailItem is ThreadMailItem threadMailItem)
package = new MailOperationPreperationRequest(operation, threadMailItem.ThreadItems.Cast<MailItemViewModel>().Select(a => a.MailCopy), toggleExecution: true);
if (package == null) return;
HoverActionExecutedCommand?.Execute(package);
}
private void FirstActionClicked(object sender, RoutedEventArgs e)
{
ExecuteHoverAction(LeftHoverAction);
}
private void SecondActionClicked(object sender, RoutedEventArgs e)
{
ExecuteHoverAction(CenterHoverAction);
}
private void ThirdActionClicked(object sender, RoutedEventArgs e)
{
ExecuteHoverAction(RightHoverAction);
}
}
}

View File

@@ -3,70 +3,71 @@ using System.Windows.Input;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace Wino.Controls;
/// <summary>
/// Templated button for each setting in Settings Dialog.
/// </summary>
public partial class SettingsMenuItemControl : Control
namespace Wino.Controls
{
public string Title
/// <summary>
/// Templated button for each setting in Settings Dialog.
/// </summary>
public partial class SettingsMenuItemControl : Control
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public string Description
{
get { return (string)GetValue(DescriptionProperty); }
set { SetValue(DescriptionProperty, value); }
}
public FrameworkElement Icon
{
get { return (FrameworkElement)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public bool IsClickable
{
get { return (bool)GetValue(IsClickableProperty); }
set { SetValue(IsClickableProperty, value); }
}
public bool IsNavigateIconVisible
{
get { return (bool)GetValue(IsNavigateIconVisibleProperty); }
set { SetValue(IsNavigateIconVisibleProperty, value); }
}
public FrameworkElement SideContent
{
get { return (FrameworkElement)GetValue(SideContentProperty); }
set { SetValue(SideContentProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register(nameof(CommandParameter), typeof(object), typeof(SettingsMenuItemControl), new PropertyMetadata(null));
public static readonly DependencyProperty SideContentProperty = DependencyProperty.Register(nameof(SideContent), typeof(FrameworkElement), typeof(SettingsMenuItemControl), new PropertyMetadata(null));
public static readonly DependencyProperty IsClickableProperty = DependencyProperty.Register(nameof(IsClickable), typeof(bool), typeof(SettingsMenuItemControl), new PropertyMetadata(true));
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(SettingsMenuItemControl), new PropertyMetadata(null));
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(nameof(Icon), typeof(FrameworkElement), typeof(SettingsMenuItemControl), new PropertyMetadata(null));
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(nameof(Description), typeof(string), typeof(SettingsMenuItemControl), new PropertyMetadata(string.Empty));
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(SettingsMenuItemControl), new PropertyMetadata(string.Empty));
public static readonly DependencyProperty IsNavigateIconVisibleProperty = DependencyProperty.Register(nameof(IsNavigateIconVisible), typeof(bool), typeof(SettingsMenuItemControl), new PropertyMetadata(true));
}
public string Description
{
get { return (string)GetValue(DescriptionProperty); }
set { SetValue(DescriptionProperty, value); }
}
public FrameworkElement Icon
{
get { return (FrameworkElement)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public bool IsClickable
{
get { return (bool)GetValue(IsClickableProperty); }
set { SetValue(IsClickableProperty, value); }
}
public bool IsNavigateIconVisible
{
get { return (bool)GetValue(IsNavigateIconVisibleProperty); }
set { SetValue(IsNavigateIconVisibleProperty, value); }
}
public FrameworkElement SideContent
{
get { return (FrameworkElement)GetValue(SideContentProperty); }
set { SetValue(SideContentProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register(nameof(CommandParameter), typeof(object), typeof(SettingsMenuItemControl), new PropertyMetadata(null));
public static readonly DependencyProperty SideContentProperty = DependencyProperty.Register(nameof(SideContent), typeof(FrameworkElement), typeof(SettingsMenuItemControl), new PropertyMetadata(null));
public static readonly DependencyProperty IsClickableProperty = DependencyProperty.Register(nameof(IsClickable), typeof(bool), typeof(SettingsMenuItemControl), new PropertyMetadata(true));
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(SettingsMenuItemControl), new PropertyMetadata(null));
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(nameof(Icon), typeof(FrameworkElement), typeof(SettingsMenuItemControl), new PropertyMetadata(null));
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(nameof(Description), typeof(string), typeof(SettingsMenuItemControl), new PropertyMetadata(string.Empty));
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(SettingsMenuItemControl), new PropertyMetadata(string.Empty));
public static readonly DependencyProperty IsNavigateIconVisibleProperty = DependencyProperty.Register(nameof(IsNavigateIconVisible), typeof(bool), typeof(SettingsMenuItemControl), new PropertyMetadata(true));
}

View File

@@ -4,125 +4,126 @@ using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Hosting;
using Windows.UI.Xaml.Markup;
namespace Wino.Controls;
[ContentProperty(Name = nameof(Content))]
public partial class WinoExpander : Control
namespace Wino.Controls
{
private const string PART_HeaderGrid = "HeaderGrid";
private const string PART_ContentAreaWrapper = "ContentAreaWrapper";
private const string PART_ContentArea = "ContentArea";
private ContentControl HeaderGrid;
private ContentControl ContentArea;
private Grid ContentAreaWrapper;
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(nameof(Header), typeof(UIElement), typeof(WinoExpander), new PropertyMetadata(null));
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(UIElement), typeof(WinoExpander), new PropertyMetadata(null));
public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register(nameof(IsExpanded), typeof(bool), typeof(WinoExpander), new PropertyMetadata(false, new PropertyChangedCallback(OnIsExpandedChanged)));
public static readonly DependencyProperty TemplateSettingsProperty = DependencyProperty.Register(nameof(TemplateSettings), typeof(WinoExpanderTemplateSettings), typeof(WinoExpander), new PropertyMetadata(new WinoExpanderTemplateSettings()));
public UIElement Content
[ContentProperty(Name = nameof(Content))]
public partial class WinoExpander : Control
{
get { return (UIElement)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
private const string PART_HeaderGrid = "HeaderGrid";
private const string PART_ContentAreaWrapper = "ContentAreaWrapper";
private const string PART_ContentArea = "ContentArea";
public WinoExpanderTemplateSettings TemplateSettings
{
get { return (WinoExpanderTemplateSettings)GetValue(TemplateSettingsProperty); }
set { SetValue(TemplateSettingsProperty, value); }
}
private ContentControl HeaderGrid;
private ContentControl ContentArea;
private Grid ContentAreaWrapper;
public bool IsExpanded
{
get { return (bool)GetValue(IsExpandedProperty); }
set { SetValue(IsExpandedProperty, value); }
}
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(nameof(Header), typeof(UIElement), typeof(WinoExpander), new PropertyMetadata(null));
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(UIElement), typeof(WinoExpander), new PropertyMetadata(null));
public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register(nameof(IsExpanded), typeof(bool), typeof(WinoExpander), new PropertyMetadata(false, new PropertyChangedCallback(OnIsExpandedChanged)));
public static readonly DependencyProperty TemplateSettingsProperty = DependencyProperty.Register(nameof(TemplateSettings), typeof(WinoExpanderTemplateSettings), typeof(WinoExpander), new PropertyMetadata(new WinoExpanderTemplateSettings()));
public UIElement Header
{
get { return (UIElement)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
HeaderGrid = GetTemplateChild(PART_HeaderGrid) as ContentControl;
ContentAreaWrapper = GetTemplateChild(PART_ContentAreaWrapper) as Grid;
ContentArea = GetTemplateChild(PART_ContentArea) as ContentControl;
Guard.IsNotNull(HeaderGrid, nameof(HeaderGrid));
Guard.IsNotNull(ContentAreaWrapper, nameof(ContentAreaWrapper));
Guard.IsNotNull(ContentArea, nameof(ContentArea));
var clipComposition = ElementCompositionPreview.GetElementVisual(ContentAreaWrapper);
clipComposition.Clip = clipComposition.Compositor.CreateInsetClip();
ContentAreaWrapper.SizeChanged += ContentSizeChanged;
HeaderGrid.Tapped += HeaderTapped;
}
private void ContentSizeChanged(object sender, SizeChangedEventArgs e)
{
TemplateSettings.ContentHeight = e.NewSize.Height;
TemplateSettings.NegativeContentHeight = -1 * (double)e.NewSize.Height;
}
private void HeaderTapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
{
// Tapped is delegated from executing hover action like flag or delete.
// No need to toggle the expander.
if (Header is MailItemDisplayInformationControl itemDisplayInformationControl &&
itemDisplayInformationControl.IsRunningHoverAction)
public UIElement Content
{
itemDisplayInformationControl.IsRunningHoverAction = false;
return;
get { return (UIElement)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
IsExpanded = !IsExpanded;
public WinoExpanderTemplateSettings TemplateSettings
{
get { return (WinoExpanderTemplateSettings)GetValue(TemplateSettingsProperty); }
set { SetValue(TemplateSettingsProperty, value); }
}
public bool IsExpanded
{
get { return (bool)GetValue(IsExpandedProperty); }
set { SetValue(IsExpandedProperty, value); }
}
public UIElement Header
{
get { return (UIElement)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
HeaderGrid = GetTemplateChild(PART_HeaderGrid) as ContentControl;
ContentAreaWrapper = GetTemplateChild(PART_ContentAreaWrapper) as Grid;
ContentArea = GetTemplateChild(PART_ContentArea) as ContentControl;
Guard.IsNotNull(HeaderGrid, nameof(HeaderGrid));
Guard.IsNotNull(ContentAreaWrapper, nameof(ContentAreaWrapper));
Guard.IsNotNull(ContentArea, nameof(ContentArea));
var clipComposition = ElementCompositionPreview.GetElementVisual(ContentAreaWrapper);
clipComposition.Clip = clipComposition.Compositor.CreateInsetClip();
ContentAreaWrapper.SizeChanged += ContentSizeChanged;
HeaderGrid.Tapped += HeaderTapped;
}
private void ContentSizeChanged(object sender, SizeChangedEventArgs e)
{
TemplateSettings.ContentHeight = e.NewSize.Height;
TemplateSettings.NegativeContentHeight = -1 * (double)e.NewSize.Height;
}
private void HeaderTapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
{
// Tapped is delegated from executing hover action like flag or delete.
// No need to toggle the expander.
if (Header is MailItemDisplayInformationControl itemDisplayInformationControl &&
itemDisplayInformationControl.IsRunningHoverAction)
{
itemDisplayInformationControl.IsRunningHoverAction = false;
return;
}
IsExpanded = !IsExpanded;
}
private static void OnIsExpandedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoExpander control)
control.UpdateVisualStates();
}
private void UpdateVisualStates()
{
VisualStateManager.GoToState(this, IsExpanded ? "Expanded" : "Collapsed", true);
}
}
private static void OnIsExpandedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
#region Settings
public class WinoExpanderTemplateSettings : DependencyObject
{
if (obj is WinoExpander control)
control.UpdateVisualStates();
public static readonly DependencyProperty HeaderHeightProperty = DependencyProperty.Register(nameof(HeaderHeight), typeof(double), typeof(WinoExpanderTemplateSettings), new PropertyMetadata(0.0));
public static readonly DependencyProperty ContentHeightProperty = DependencyProperty.Register(nameof(ContentHeight), typeof(double), typeof(WinoExpanderTemplateSettings), new PropertyMetadata(0.0));
public static readonly DependencyProperty NegativeContentHeightProperty = DependencyProperty.Register(nameof(NegativeContentHeight), typeof(double), typeof(WinoExpanderTemplateSettings), new PropertyMetadata(0.0));
public double NegativeContentHeight
{
get { return (double)GetValue(NegativeContentHeightProperty); }
set { SetValue(NegativeContentHeightProperty, value); }
}
public double HeaderHeight
{
get { return (double)GetValue(HeaderHeightProperty); }
set { SetValue(HeaderHeightProperty, value); }
}
public double ContentHeight
{
get { return (double)GetValue(ContentHeightProperty); }
set { SetValue(ContentHeightProperty, value); }
}
}
private void UpdateVisualStates()
{
VisualStateManager.GoToState(this, IsExpanded ? "Expanded" : "Collapsed", true);
}
#endregion
}
#region Settings
public class WinoExpanderTemplateSettings : DependencyObject
{
public static readonly DependencyProperty HeaderHeightProperty = DependencyProperty.Register(nameof(HeaderHeight), typeof(double), typeof(WinoExpanderTemplateSettings), new PropertyMetadata(0.0));
public static readonly DependencyProperty ContentHeightProperty = DependencyProperty.Register(nameof(ContentHeight), typeof(double), typeof(WinoExpanderTemplateSettings), new PropertyMetadata(0.0));
public static readonly DependencyProperty NegativeContentHeightProperty = DependencyProperty.Register(nameof(NegativeContentHeight), typeof(double), typeof(WinoExpanderTemplateSettings), new PropertyMetadata(0.0));
public double NegativeContentHeight
{
get { return (double)GetValue(NegativeContentHeightProperty); }
set { SetValue(NegativeContentHeightProperty, value); }
}
public double HeaderHeight
{
get { return (double)GetValue(HeaderHeightProperty); }
set { SetValue(HeaderHeightProperty, value); }
}
public double ContentHeight
{
get { return (double)GetValue(ContentHeightProperty); }
set { SetValue(ContentHeightProperty, value); }
}
}
#endregion

View File

@@ -6,76 +6,77 @@ using Wino.Core.Domain.Models.MailItem;
using Wino.Helpers;
using Wino.Mail.ViewModels.Data;
namespace Wino.Controls;
public partial class WinoSwipeControlItems : SwipeItems
namespace Wino.Controls
{
public static readonly DependencyProperty SwipeOperationProperty = DependencyProperty.Register(nameof(SwipeOperation), typeof(MailOperation), typeof(WinoSwipeControlItems), new PropertyMetadata(default(MailOperation), new PropertyChangedCallback(OnItemsChanged)));
public static readonly DependencyProperty MailItemProperty = DependencyProperty.Register(nameof(MailItem), typeof(IMailItem), typeof(WinoSwipeControlItems), new PropertyMetadata(null));
public IMailItem MailItem
public partial class WinoSwipeControlItems : SwipeItems
{
get { return (IMailItem)GetValue(MailItemProperty); }
set { SetValue(MailItemProperty, value); }
}
public static readonly DependencyProperty SwipeOperationProperty = DependencyProperty.Register(nameof(SwipeOperation), typeof(MailOperation), typeof(WinoSwipeControlItems), new PropertyMetadata(default(MailOperation), new PropertyChangedCallback(OnItemsChanged)));
public static readonly DependencyProperty MailItemProperty = DependencyProperty.Register(nameof(MailItem), typeof(IMailItem), typeof(WinoSwipeControlItems), new PropertyMetadata(null));
public MailOperation SwipeOperation
{
get { return (MailOperation)GetValue(SwipeOperationProperty); }
set { SetValue(SwipeOperationProperty, value); }
}
private static void OnItemsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoSwipeControlItems control)
public IMailItem MailItem
{
control.BuildSwipeItems();
}
}
private void BuildSwipeItems()
{
this.Clear();
var swipeItem = GetSwipeItem(SwipeOperation);
this.Add(swipeItem);
}
private SwipeItem GetSwipeItem(MailOperation operation)
{
if (MailItem == null) return null;
var finalOperation = operation;
bool isSingleItem = MailItem is MailItemViewModel;
if (isSingleItem)
{
var singleItem = MailItem as MailItemViewModel;
if (operation == MailOperation.MarkAsRead && singleItem.IsRead)
finalOperation = MailOperation.MarkAsUnread;
else if (operation == MailOperation.MarkAsUnread && !singleItem.IsRead)
finalOperation = MailOperation.MarkAsRead;
}
else
{
var threadItem = MailItem as ThreadMailItemViewModel;
if (operation == MailOperation.MarkAsRead && threadItem.ThreadItems.All(a => a.IsRead))
finalOperation = MailOperation.MarkAsUnread;
else if (operation == MailOperation.MarkAsUnread && threadItem.ThreadItems.All(a => !a.IsRead))
finalOperation = MailOperation.MarkAsRead;
get { return (IMailItem)GetValue(MailItemProperty); }
set { SetValue(MailItemProperty, value); }
}
var item = new SwipeItem()
{
IconSource = new WinoFontIconSource() { Icon = XamlHelpers.GetWinoIconGlyph(finalOperation) },
Text = XamlHelpers.GetOperationString(finalOperation),
};
return item;
public MailOperation SwipeOperation
{
get { return (MailOperation)GetValue(SwipeOperationProperty); }
set { SetValue(SwipeOperationProperty, value); }
}
private static void OnItemsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoSwipeControlItems control)
{
control.BuildSwipeItems();
}
}
private void BuildSwipeItems()
{
this.Clear();
var swipeItem = GetSwipeItem(SwipeOperation);
this.Add(swipeItem);
}
private SwipeItem GetSwipeItem(MailOperation operation)
{
if (MailItem == null) return null;
var finalOperation = operation;
bool isSingleItem = MailItem is MailItemViewModel;
if (isSingleItem)
{
var singleItem = MailItem as MailItemViewModel;
if (operation == MailOperation.MarkAsRead && singleItem.IsRead)
finalOperation = MailOperation.MarkAsUnread;
else if (operation == MailOperation.MarkAsUnread && !singleItem.IsRead)
finalOperation = MailOperation.MarkAsRead;
}
else
{
var threadItem = MailItem as ThreadMailItemViewModel;
if (operation == MailOperation.MarkAsRead && threadItem.ThreadItems.All(a => a.IsRead))
finalOperation = MailOperation.MarkAsUnread;
else if (operation == MailOperation.MarkAsUnread && threadItem.ThreadItems.All(a => !a.IsRead))
finalOperation = MailOperation.MarkAsRead;
}
var item = new SwipeItem()
{
IconSource = new WinoFontIconSource() { Icon = XamlHelpers.GetWinoIconGlyph(finalOperation) },
Text = XamlHelpers.GetOperationString(finalOperation),
};
return item;
}
}
}

View File

@@ -4,48 +4,49 @@ using Microsoft.Extensions.DependencyInjection;
using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Interfaces;
namespace Wino.Dialogs;
public sealed partial class AccountReorderDialog : ContentDialog
namespace Wino.Dialogs
{
public ObservableCollection<IAccountProviderDetailViewModel> Accounts { get; }
private int count;
private bool isOrdering = false;
private readonly IAccountService _accountService = App.Current.Services.GetService<IAccountService>();
public AccountReorderDialog(ObservableCollection<IAccountProviderDetailViewModel> accounts)
public sealed partial class AccountReorderDialog : ContentDialog
{
Accounts = accounts;
public ObservableCollection<IAccountProviderDetailViewModel> Accounts { get; }
count = accounts.Count;
private int count;
private bool isOrdering = false;
InitializeComponent();
}
private readonly IAccountService _accountService = App.Current.Services.GetService<IAccountService>();
private void DialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args)
{
Accounts.CollectionChanged -= AccountsChanged;
Accounts.CollectionChanged += AccountsChanged;
}
private void DialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args) => Accounts.CollectionChanged -= AccountsChanged;
private async void AccountsChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (count - 1 == Accounts.Count)
isOrdering = true;
if (count == Accounts.Count && isOrdering)
public AccountReorderDialog(ObservableCollection<IAccountProviderDetailViewModel> accounts)
{
// Order is completed. Apply changes.
Accounts = accounts;
var dict = Accounts.ToDictionary(a => a.StartupEntityId, a => Accounts.IndexOf(a));
count = accounts.Count;
await _accountService.UpdateAccountOrdersAsync(dict);
InitializeComponent();
}
isOrdering = false;
private void DialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args)
{
Accounts.CollectionChanged -= AccountsChanged;
Accounts.CollectionChanged += AccountsChanged;
}
private void DialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args) => Accounts.CollectionChanged -= AccountsChanged;
private async void AccountsChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (count - 1 == Accounts.Count)
isOrdering = true;
if (count == Accounts.Count && isOrdering)
{
// Order is completed. Apply changes.
var dict = Accounts.ToDictionary(a => a.StartupEntityId, a => Accounts.IndexOf(a));
await _accountService.UpdateAccountOrdersAsync(dict);
isOrdering = false;
}
}
}
}

View File

@@ -3,27 +3,28 @@ using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Interfaces;
namespace Wino.Dialogs;
public sealed partial class CreateAccountAliasDialog : ContentDialog, ICreateAccountAliasDialog
namespace Wino.Dialogs
{
public MailAccountAlias CreatedAccountAlias { get; set; }
public CreateAccountAliasDialog()
public sealed partial class CreateAccountAliasDialog : ContentDialog, ICreateAccountAliasDialog
{
InitializeComponent();
}
private void CreateClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
CreatedAccountAlias = new MailAccountAlias
public MailAccountAlias CreatedAccountAlias { get; set; }
public CreateAccountAliasDialog()
{
AliasAddress = AliasTextBox.Text.Trim(),
ReplyToAddress = ReplyToTextBox.Text.Trim(),
Id = Guid.NewGuid(),
IsPrimary = false,
IsVerified = false
};
InitializeComponent();
}
Hide();
private void CreateClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
CreatedAccountAlias = new MailAccountAlias
{
AliasAddress = AliasTextBox.Text.Trim(),
ReplyToAddress = ReplyToTextBox.Text.Trim(),
Id = Guid.NewGuid(),
IsPrimary = false,
IsVerified = false
};
Hide();
}
}
}

View File

@@ -3,21 +3,22 @@ using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Interfaces;
namespace Wino.Mail.Dialogs;
public sealed partial class MessageSourceDialog : ContentDialog
namespace Wino.Mail.Dialogs
{
private readonly IClipboardService _clipboardService = App.Current.Services.GetService<IClipboardService>();
public string MessageSource { get; set; }
public bool Copied { get; set; }
public MessageSourceDialog()
public sealed partial class MessageSourceDialog : ContentDialog
{
this.InitializeComponent();
}
private readonly IClipboardService _clipboardService = App.Current.Services.GetService<IClipboardService>();
public string MessageSource { get; set; }
public bool Copied { get; set; }
public MessageSourceDialog()
{
this.InitializeComponent();
}
private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
_clipboardService.CopyClipboardAsync(MessageSource);
Copied = true;
private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
_clipboardService.CopyClipboardAsync(MessageSource);
Copied = true;
}
}
}

View File

@@ -4,71 +4,72 @@ using Windows.UI.Xaml.Controls;
using Wino.Core.Domain;
using Wino.Core.Domain.Models.Folders;
namespace Wino.Dialogs;
public sealed partial class MoveMailDialog : ContentDialog
namespace Wino.Dialogs
{
public IMailItemFolder SelectedFolder
public sealed partial class MoveMailDialog : ContentDialog
{
get { return (IMailItemFolder)GetValue(SelectedFolderProperty); }
set { SetValue(SelectedFolderProperty, value); }
}
public static readonly DependencyProperty SelectedFolderProperty = DependencyProperty.Register(nameof(SelectedFolder), typeof(IMailItemFolder), typeof(MoveMailDialog), new PropertyMetadata(null, OnSelectedFolderChanged));
public List<IMailItemFolder> FolderList { get; set; }
public MoveMailDialog(List<IMailItemFolder> allFolders)
{
InitializeComponent();
if (allFolders == null) return;
FolderList = allFolders;
}
private static void OnSelectedFolderChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is MoveMailDialog dialog)
public IMailItemFolder SelectedFolder
{
dialog.VerifySelection();
get { return (IMailItemFolder)GetValue(SelectedFolderProperty); }
set { SetValue(SelectedFolderProperty, value); }
}
}
private void VerifySelection()
{
if (SelectedFolder != null)
public static readonly DependencyProperty SelectedFolderProperty = DependencyProperty.Register(nameof(SelectedFolder), typeof(IMailItemFolder), typeof(MoveMailDialog), new PropertyMetadata(null, OnSelectedFolderChanged));
public List<IMailItemFolder> FolderList { get; set; }
public MoveMailDialog(List<IMailItemFolder> allFolders)
{
// Don't select non-move capable folders like Categories or More.
InitializeComponent();
if (!SelectedFolder.IsMoveTarget)
if (allFolders == null) return;
FolderList = allFolders;
}
private static void OnSelectedFolderChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is MoveMailDialog dialog)
{
// Warn users for only proper mail folders. Not ghost folders.
InvalidFolderText.Visibility = Visibility.Visible;
InvalidFolderText.Text = string.Format(Translator.MoveMailDialog_InvalidFolderMessage, SelectedFolder.FolderName);
dialog.VerifySelection();
}
}
if (FolderTreeView.SelectedItem != null)
private void VerifySelection()
{
if (SelectedFolder != null)
{
// Don't select non-move capable folders like Categories or More.
if (!SelectedFolder.IsMoveTarget)
{
// Toggle the expansion for the selected container if available.
// I don't like the expand arrow touch area. It's better this way.
// Warn users for only proper mail folders. Not ghost folders.
InvalidFolderText.Visibility = Visibility.Visible;
InvalidFolderText.Text = string.Format(Translator.MoveMailDialog_InvalidFolderMessage, SelectedFolder.FolderName);
if (FolderTreeView.ContainerFromItem(FolderTreeView.SelectedItem) is Microsoft.UI.Xaml.Controls.TreeViewItem container)
if (FolderTreeView.SelectedItem != null)
{
container.IsExpanded = !container.IsExpanded;
// Toggle the expansion for the selected container if available.
// I don't like the expand arrow touch area. It's better this way.
if (FolderTreeView.ContainerFromItem(FolderTreeView.SelectedItem) is Microsoft.UI.Xaml.Controls.TreeViewItem container)
{
container.IsExpanded = !container.IsExpanded;
}
}
SelectedFolder = null;
}
else
{
Hide();
}
SelectedFolder = null;
}
else
{
Hide();
}
}
}
private void CancelClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
Hide();
private void CancelClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
Hide();
}
}
}

View File

@@ -11,101 +11,102 @@ using Wino.Core.Domain.Models.Accounts;
using Wino.Messaging.Client.Mails;
using Wino.Views.ImapSetup;
namespace Wino.Dialogs;
public enum ImapSetupState
namespace Wino.Dialogs
{
Welcome,
AutoDiscovery,
TestingConnection,
PreparingFolder
}
public sealed partial class NewImapSetupDialog : ContentDialog,
IRecipient<ImapSetupNavigationRequested>,
IRecipient<ImapSetupBackNavigationRequested>,
IRecipient<ImapSetupDismissRequested>,
IImapAccountCreationDialog
{
private TaskCompletionSource<CustomServerInformation> _getServerInfoTaskCompletionSource = new TaskCompletionSource<CustomServerInformation>();
private TaskCompletionSource<bool> dialogOpened = new TaskCompletionSource<bool>();
private bool isDismissRequested = false;
public NewImapSetupDialog()
public enum ImapSetupState
{
InitializeComponent();
Welcome,
AutoDiscovery,
TestingConnection,
PreparingFolder
}
// Not used for now.
public AccountCreationDialogState State { get; set; }
public void Complete(bool cancel)
public sealed partial class NewImapSetupDialog : ContentDialog,
IRecipient<ImapSetupNavigationRequested>,
IRecipient<ImapSetupBackNavigationRequested>,
IRecipient<ImapSetupDismissRequested>,
IImapAccountCreationDialog
{
if (!_getServerInfoTaskCompletionSource.Task.IsCompleted)
_getServerInfoTaskCompletionSource.TrySetResult(null);
private TaskCompletionSource<CustomServerInformation> _getServerInfoTaskCompletionSource = new TaskCompletionSource<CustomServerInformation>();
private TaskCompletionSource<bool> dialogOpened = new TaskCompletionSource<bool>();
private bool isDismissRequested = false;
isDismissRequested = true;
Hide();
}
public Task<CustomServerInformation> GetCustomServerInformationAsync() => _getServerInfoTaskCompletionSource.Task;
public async void Receive(ImapSetupBackNavigationRequested message)
{
// Frame go back
if (message.PageType == null)
public NewImapSetupDialog()
{
if (ImapFrame.CanGoBack)
InitializeComponent();
}
// Not used for now.
public AccountCreationDialogState State { get; set; }
public void Complete(bool cancel)
{
if (!_getServerInfoTaskCompletionSource.Task.IsCompleted)
_getServerInfoTaskCompletionSource.TrySetResult(null);
isDismissRequested = true;
Hide();
}
public Task<CustomServerInformation> GetCustomServerInformationAsync() => _getServerInfoTaskCompletionSource.Task;
public async void Receive(ImapSetupBackNavigationRequested message)
{
// Frame go back
if (message.PageType == null)
{
// Go back using Dispatcher to allow navigations in OnNavigatedTo.
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
if (ImapFrame.CanGoBack)
{
ImapFrame.GoBack();
});
// Go back using Dispatcher to allow navigations in OnNavigatedTo.
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
ImapFrame.GoBack();
});
}
}
else
{
ImapFrame.Navigate(message.PageType, message.Parameter, new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromLeft });
}
}
else
public void Receive(ImapSetupNavigationRequested message)
{
ImapFrame.Navigate(message.PageType, message.Parameter, new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromLeft });
ImapFrame.Navigate(message.PageType, message.Parameter, new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromRight });
}
public void Receive(ImapSetupDismissRequested message) => _getServerInfoTaskCompletionSource.TrySetResult(message.CompletedServerInformation);
public async Task ShowDialogAsync(CancellationTokenSource cancellationTokenSource)
{
Opened += DialogOpened;
_ = ShowAsync();
await dialogOpened.Task;
}
private void DialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args)
{
Opened -= DialogOpened;
dialogOpened?.SetResult(true);
}
public void ShowPreparingFolders()
{
ImapFrame.Navigate(typeof(PreparingImapFoldersPage), new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromLeft });
}
public void StartImapConnectionSetup(MailAccount account) => ImapFrame.Navigate(typeof(WelcomeImapSetupPage), account, new DrillInNavigationTransitionInfo());
public void StartImapConnectionSetup(AccountCreationDialogResult accountCreationDialogResult) => ImapFrame.Navigate(typeof(WelcomeImapSetupPage), accountCreationDialogResult, new DrillInNavigationTransitionInfo());
private void ImapSetupDialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args) => WeakReferenceMessenger.Default.UnregisterAll(this);
private void ImapSetupDialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args) => WeakReferenceMessenger.Default.RegisterAll(this);
// Don't hide the dialog unless dismiss is requested from the inner pages specifically.
private void OnDialogClosing(ContentDialog sender, ContentDialogClosingEventArgs args) => args.Cancel = !isDismissRequested;
}
public void Receive(ImapSetupNavigationRequested message)
{
ImapFrame.Navigate(message.PageType, message.Parameter, new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromRight });
}
public void Receive(ImapSetupDismissRequested message) => _getServerInfoTaskCompletionSource.TrySetResult(message.CompletedServerInformation);
public async Task ShowDialogAsync(CancellationTokenSource cancellationTokenSource)
{
Opened += DialogOpened;
_ = ShowAsync();
await dialogOpened.Task;
}
private void DialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args)
{
Opened -= DialogOpened;
dialogOpened?.SetResult(true);
}
public void ShowPreparingFolders()
{
ImapFrame.Navigate(typeof(PreparingImapFoldersPage), new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromLeft });
}
public void StartImapConnectionSetup(MailAccount account) => ImapFrame.Navigate(typeof(WelcomeImapSetupPage), account, new DrillInNavigationTransitionInfo());
public void StartImapConnectionSetup(AccountCreationDialogResult accountCreationDialogResult) => ImapFrame.Navigate(typeof(WelcomeImapSetupPage), accountCreationDialogResult, new DrillInNavigationTransitionInfo());
private void ImapSetupDialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args) => WeakReferenceMessenger.Default.UnregisterAll(this);
private void ImapSetupDialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args) => WeakReferenceMessenger.Default.RegisterAll(this);
// Don't hide the dialog unless dismiss is requested from the inner pages specifically.
private void OnDialogClosing(ContentDialog sender, ContentDialogClosingEventArgs args) => args.Cancel = !isDismissRequested;
}

View File

@@ -15,362 +15,363 @@ using Wino.Core.Domain.Models.Reader;
using Wino.Core.UWP.Extensions;
using Wino.Views.Settings;
namespace Wino.Dialogs;
public sealed partial class SignatureEditorDialog : ContentDialog
namespace Wino.Dialogs
{
private Func<Task<string>> _getHTMLBodyFunction;
private readonly TaskCompletionSource<bool> _domLoadedTask = new TaskCompletionSource<bool>();
private readonly INativeAppService _nativeAppService = App.Current.Services.GetService<INativeAppService>();
private readonly IFontService _fontService = App.Current.Services.GetService<IFontService>();
private readonly IPreferencesService _preferencesService = App.Current.Services.GetService<IPreferencesService>();
public AccountSignature Result;
public bool IsComposerDarkMode
public sealed partial class SignatureEditorDialog : ContentDialog
{
get { return (bool)GetValue(IsComposerDarkModeProperty); }
set { SetValue(IsComposerDarkModeProperty, value); }
}
private Func<Task<string>> _getHTMLBodyFunction;
private readonly TaskCompletionSource<bool> _domLoadedTask = new TaskCompletionSource<bool>();
public static readonly DependencyProperty IsComposerDarkModeProperty = DependencyProperty.Register(nameof(IsComposerDarkMode), typeof(bool), typeof(SignatureManagementPage), new PropertyMetadata(false, OnIsComposerDarkModeChanged));
private readonly INativeAppService _nativeAppService = App.Current.Services.GetService<INativeAppService>();
private readonly IFontService _fontService = App.Current.Services.GetService<IFontService>();
private readonly IPreferencesService _preferencesService = App.Current.Services.GetService<IPreferencesService>();
public SignatureEditorDialog()
{
InitializeComponent();
public AccountSignature Result;
SignatureNameTextBox.Header = Translator.SignatureEditorDialog_SignatureName_TitleNew;
Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF");
Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation,FontAccess");
// TODO: Should be added additional logic to enable/disable primary button when webview content changed.
IsPrimaryButtonEnabled = true;
}
public SignatureEditorDialog(AccountSignature signatureModel)
{
InitializeComponent();
SignatureNameTextBox.Text = signatureModel.Name.Trim();
SignatureNameTextBox.Header = string.Format(Translator.SignatureEditorDialog_SignatureName_TitleEdit, signatureModel.Name);
Result = new AccountSignature
public bool IsComposerDarkMode
{
Id = signatureModel.Id,
Name = signatureModel.Name,
MailAccountId = signatureModel.MailAccountId,
HtmlBody = signatureModel.HtmlBody
};
Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF");
Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation");
// TODO: Should be added additional logic to enable/disable primary button when webview content changed.
IsPrimaryButtonEnabled = true;
}
private async void SignatureDialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args)
{
Chromium.CoreWebView2Initialized -= ChromiumInitialized;
Chromium.CoreWebView2Initialized += ChromiumInitialized;
await Chromium.EnsureCoreWebView2Async();
_getHTMLBodyFunction = new Func<Task<string>>(async () =>
{
var editorContent = await Chromium.ExecuteScriptFunctionSafeAsync("GetHTMLContent");
return JsonSerializer.Deserialize(editorContent, BasicTypesJsonContext.Default.String);
});
var underlyingThemeService = App.Current.Services.GetService<IUnderlyingThemeService>();
IsComposerDarkMode = underlyingThemeService.IsUnderlyingThemeDark();
await RenderInternalAsync(Result?.HtmlBody ?? string.Empty);
}
private void DialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args)
{
Chromium.CoreWebView2Initialized -= ChromiumInitialized;
if (Chromium.CoreWebView2 != null)
{
Chromium.CoreWebView2.DOMContentLoaded -= DOMLoaded;
Chromium.CoreWebView2.WebMessageReceived -= ScriptMessageReceived;
get { return (bool)GetValue(IsComposerDarkModeProperty); }
set { SetValue(IsComposerDarkModeProperty, value); }
}
}
private async void SaveClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
var newSignature = Regex.Unescape(await _getHTMLBodyFunction());
public static readonly DependencyProperty IsComposerDarkModeProperty = DependencyProperty.Register(nameof(IsComposerDarkMode), typeof(bool), typeof(SignatureManagementPage), new PropertyMetadata(false, OnIsComposerDarkModeChanged));
if (Result == null)
public SignatureEditorDialog()
{
InitializeComponent();
SignatureNameTextBox.Header = Translator.SignatureEditorDialog_SignatureName_TitleNew;
Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF");
Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation,FontAccess");
// TODO: Should be added additional logic to enable/disable primary button when webview content changed.
IsPrimaryButtonEnabled = true;
}
public SignatureEditorDialog(AccountSignature signatureModel)
{
InitializeComponent();
SignatureNameTextBox.Text = signatureModel.Name.Trim();
SignatureNameTextBox.Header = string.Format(Translator.SignatureEditorDialog_SignatureName_TitleEdit, signatureModel.Name);
Result = new AccountSignature
{
Id = Guid.NewGuid(),
Name = SignatureNameTextBox.Text.Trim(),
HtmlBody = newSignature
Id = signatureModel.Id,
Name = signatureModel.Name,
MailAccountId = signatureModel.MailAccountId,
HtmlBody = signatureModel.HtmlBody
};
}
else
{
Result.Name = SignatureNameTextBox.Text.Trim();
Result.HtmlBody = newSignature;
Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF");
Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation");
// TODO: Should be added additional logic to enable/disable primary button when webview content changed.
IsPrimaryButtonEnabled = true;
}
Hide();
}
private void CancelClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
Hide();
}
private async void BoldButtonClicked(object sender, RoutedEventArgs e)
{
await InvokeScriptSafeAsync("editor.execCommand('bold')");
}
private async void ItalicButtonClicked(object sender, RoutedEventArgs e)
{
await InvokeScriptSafeAsync("editor.execCommand('italic')");
}
private async void UnderlineButtonClicked(object sender, RoutedEventArgs e)
{
await InvokeScriptSafeAsync("editor.execCommand('underline')");
}
private async void StrokeButtonClicked(object sender, RoutedEventArgs e)
{
await InvokeScriptSafeAsync("editor.execCommand('strikethrough')");
}
private async void BulletListButtonClicked(object sender, RoutedEventArgs e)
{
await InvokeScriptSafeAsync("editor.execCommand('insertunorderedlist')");
}
private async void OrderedListButtonClicked(object sender, RoutedEventArgs e)
{
await InvokeScriptSafeAsync("editor.execCommand('insertorderedlist')");
}
private async void IncreaseIndentClicked(object sender, RoutedEventArgs e)
{
await InvokeScriptSafeAsync("editor.execCommand('indent')");
}
private async void DecreaseIndentClicked(object sender, RoutedEventArgs e)
{
await InvokeScriptSafeAsync("editor.execCommand('outdent')");
}
private async void AlignmentChanged(object sender, SelectionChangedEventArgs e)
{
var selectedItem = AlignmentListView.SelectedItem as ComboBoxItem;
var alignment = selectedItem.Tag.ToString();
switch (alignment)
private async void SignatureDialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args)
{
case "left":
await InvokeScriptSafeAsync("editor.execCommand('justifyleft')");
break;
case "center":
await InvokeScriptSafeAsync("editor.execCommand('justifycenter')");
break;
case "right":
await InvokeScriptSafeAsync("editor.execCommand('justifyright')");
break;
case "justify":
await InvokeScriptSafeAsync("editor.execCommand('justifyfull')");
break;
}
}
Chromium.CoreWebView2Initialized -= ChromiumInitialized;
Chromium.CoreWebView2Initialized += ChromiumInitialized;
private async Task<string> InvokeScriptSafeAsync(string function)
{
if (Chromium == null) return string.Empty;
await Chromium.EnsureCoreWebView2Async();
try
{
_getHTMLBodyFunction = new Func<Task<string>>(async () =>
{
var editorContent = await Chromium.ExecuteScriptFunctionSafeAsync("GetHTMLContent");
return await Chromium.ExecuteScriptAsync(function);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return JsonSerializer.Deserialize(editorContent, BasicTypesJsonContext.Default.String);
});
var underlyingThemeService = App.Current.Services.GetService<IUnderlyingThemeService>();
IsComposerDarkMode = underlyingThemeService.IsUnderlyingThemeDark();
await RenderInternalAsync(Result?.HtmlBody ?? string.Empty);
}
return string.Empty;
}
private async void AddImageClicked(object sender, RoutedEventArgs e)
{
await InvokeScriptSafeAsync("imageInput.click();");
}
private async Task FocusEditorAsync()
{
await InvokeScriptSafeAsync("editor.selection.focus();");
Chromium.Focus(FocusState.Keyboard);
Chromium.Focus(FocusState.Programmatic);
}
private async void EmojiButtonClicked(object sender, RoutedEventArgs e)
{
CoreInputView.GetForCurrentView().TryShow(CoreInputViewKind.Emoji);
await FocusEditorAsync();
}
private async Task<string> TryGetSelectedTextAsync()
{
try
private void DialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args)
{
return await Chromium.ExecuteScriptAsync("getSelectedText();");
Chromium.CoreWebView2Initialized -= ChromiumInitialized;
if (Chromium.CoreWebView2 != null)
{
Chromium.CoreWebView2.DOMContentLoaded -= DOMLoaded;
Chromium.CoreWebView2.WebMessageReceived -= ScriptMessageReceived;
}
}
catch { }
return string.Empty;
}
private async void WebViewToggleButtonClicked(object sender, RoutedEventArgs e)
{
var enable = WebviewToolBarButton.IsChecked == true ? "true" : "false";
await InvokeScriptSafeAsync($"toggleToolbar('{enable}');");
}
private async Task UpdateEditorThemeAsync()
{
await _domLoadedTask.Task;
if (IsComposerDarkMode)
private async void SaveClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark;
await InvokeScriptSafeAsync("SetDarkEditor();");
var newSignature = Regex.Unescape(await _getHTMLBodyFunction());
if (Result == null)
{
Result = new AccountSignature
{
Id = Guid.NewGuid(),
Name = SignatureNameTextBox.Text.Trim(),
HtmlBody = newSignature
};
}
else
{
Result.Name = SignatureNameTextBox.Text.Trim();
Result.HtmlBody = newSignature;
}
Hide();
}
else
private void CancelClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Light;
await InvokeScriptSafeAsync("SetLightEditor();");
Hide();
}
}
private async Task RenderInternalAsync(string htmlBody)
{
await _domLoadedTask.Task;
await UpdateEditorThemeAsync();
await InitializeEditorAsync();
if (string.IsNullOrEmpty(htmlBody))
private async void BoldButtonClicked(object sender, RoutedEventArgs e)
{
await Chromium.ExecuteScriptFunctionAsync("RenderHTML", parameters: JsonSerializer.Serialize(" ", BasicTypesJsonContext.Default.String));
await InvokeScriptSafeAsync("editor.execCommand('bold')");
}
else
private async void ItalicButtonClicked(object sender, RoutedEventArgs e)
{
await Chromium.ExecuteScriptFunctionAsync("RenderHTML", parameters: JsonSerializer.Serialize(htmlBody, BasicTypesJsonContext.Default.String));
await InvokeScriptSafeAsync("editor.execCommand('italic')");
}
private async void UnderlineButtonClicked(object sender, RoutedEventArgs e)
{
await InvokeScriptSafeAsync("editor.execCommand('underline')");
}
private async void StrokeButtonClicked(object sender, RoutedEventArgs e)
{
await InvokeScriptSafeAsync("editor.execCommand('strikethrough')");
}
private async void BulletListButtonClicked(object sender, RoutedEventArgs e)
{
await InvokeScriptSafeAsync("editor.execCommand('insertunorderedlist')");
}
private async void OrderedListButtonClicked(object sender, RoutedEventArgs e)
{
await InvokeScriptSafeAsync("editor.execCommand('insertorderedlist')");
}
private async void IncreaseIndentClicked(object sender, RoutedEventArgs e)
{
await InvokeScriptSafeAsync("editor.execCommand('indent')");
}
private async void DecreaseIndentClicked(object sender, RoutedEventArgs e)
{
await InvokeScriptSafeAsync("editor.execCommand('outdent')");
}
private async void AlignmentChanged(object sender, SelectionChangedEventArgs e)
{
var selectedItem = AlignmentListView.SelectedItem as ComboBoxItem;
var alignment = selectedItem.Tag.ToString();
switch (alignment)
{
case "left":
await InvokeScriptSafeAsync("editor.execCommand('justifyleft')");
break;
case "center":
await InvokeScriptSafeAsync("editor.execCommand('justifycenter')");
break;
case "right":
await InvokeScriptSafeAsync("editor.execCommand('justifyright')");
break;
case "justify":
await InvokeScriptSafeAsync("editor.execCommand('justifyfull')");
break;
}
}
private async Task<string> InvokeScriptSafeAsync(string function)
{
if (Chromium == null) return string.Empty;
try
{
return await Chromium.ExecuteScriptAsync(function);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return string.Empty;
}
private async void AddImageClicked(object sender, RoutedEventArgs e)
{
await InvokeScriptSafeAsync("imageInput.click();");
}
private async Task FocusEditorAsync()
{
await InvokeScriptSafeAsync("editor.selection.focus();");
Chromium.Focus(FocusState.Keyboard);
Chromium.Focus(FocusState.Programmatic);
}
private async void EmojiButtonClicked(object sender, RoutedEventArgs e)
{
CoreInputView.GetForCurrentView().TryShow(CoreInputViewKind.Emoji);
await FocusEditorAsync();
}
}
private async Task<string> InitializeEditorAsync()
{
var fonts = _fontService.GetFonts();
var composerFont = _preferencesService.ComposerFont;
int composerFontSize = _preferencesService.ComposerFontSize;
var readerFont = _preferencesService.ReaderFont;
int readerFontSize = _preferencesService.ReaderFontSize;
return await Chromium.ExecuteScriptFunctionAsync("initializeJodit", false,
JsonSerializer.Serialize(fonts, BasicTypesJsonContext.Default.ListString),
JsonSerializer.Serialize(composerFont, BasicTypesJsonContext.Default.String),
JsonSerializer.Serialize(composerFontSize, BasicTypesJsonContext.Default.Int32),
JsonSerializer.Serialize(readerFont, BasicTypesJsonContext.Default.String),
JsonSerializer.Serialize(readerFontSize, BasicTypesJsonContext.Default.Int32));
}
private async void ChromiumInitialized(Microsoft.UI.Xaml.Controls.WebView2 sender, Microsoft.UI.Xaml.Controls.CoreWebView2InitializedEventArgs args)
{
var editorBundlePath = (await _nativeAppService.GetEditorBundlePathAsync()).Replace("editor.html", string.Empty);
Chromium.CoreWebView2.SetVirtualHostNameToFolderMapping("app.editor", editorBundlePath, CoreWebView2HostResourceAccessKind.Allow);
Chromium.Source = new Uri("https://app.editor/editor.html");
Chromium.CoreWebView2.DOMContentLoaded -= DOMLoaded;
Chromium.CoreWebView2.DOMContentLoaded += DOMLoaded;
Chromium.CoreWebView2.WebMessageReceived -= ScriptMessageReceived;
Chromium.CoreWebView2.WebMessageReceived += ScriptMessageReceived;
}
private static async void OnIsComposerDarkModeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is SignatureEditorDialog dialog)
private async Task<string> TryGetSelectedTextAsync()
{
await dialog.UpdateEditorThemeAsync();
}
}
private void ScriptMessageReceived(CoreWebView2 sender, CoreWebView2WebMessageReceivedEventArgs args)
{
var change = JsonSerializer.Deserialize(args.WebMessageAsJson, DomainModelsJsonContext.Default.WebViewMessage);
if (change.Type == "bold")
{
BoldButton.IsChecked = change.Value == "true";
}
else if (change.Type == "italic")
{
ItalicButton.IsChecked = change.Value == "true";
}
else if (change.Type == "underline")
{
UnderlineButton.IsChecked = change.Value == "true";
}
else if (change.Type == "strikethrough")
{
StrokeButton.IsChecked = change.Value == "true";
}
else if (change.Type == "ol")
{
OrderedListButton.IsChecked = change.Value == "true";
}
else if (change.Type == "ul")
{
BulletListButton.IsChecked = change.Value == "true";
}
else if (change.Type == "indent")
{
IncreaseIndentButton.IsEnabled = change.Value == "disabled" ? false : true;
}
else if (change.Type == "outdent")
{
DecreaseIndentButton.IsEnabled = change.Value == "disabled" ? false : true;
}
else if (change.Type == "alignment")
{
var parsedValue = change.Value switch
try
{
"jodit-icon_left" => 0,
"jodit-icon_center" => 1,
"jodit-icon_right" => 2,
"jodit-icon_justify" => 3,
_ => 0
};
AlignmentListView.SelectionChanged -= AlignmentChanged;
AlignmentListView.SelectedIndex = parsedValue;
AlignmentListView.SelectionChanged += AlignmentChanged;
return await Chromium.ExecuteScriptAsync("getSelectedText();");
}
catch { }
return string.Empty;
}
private async void WebViewToggleButtonClicked(object sender, RoutedEventArgs e)
{
var enable = WebviewToolBarButton.IsChecked == true ? "true" : "false";
await InvokeScriptSafeAsync($"toggleToolbar('{enable}');");
}
private async Task UpdateEditorThemeAsync()
{
await _domLoadedTask.Task;
if (IsComposerDarkMode)
{
Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark;
await InvokeScriptSafeAsync("SetDarkEditor();");
}
else
{
Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Light;
await InvokeScriptSafeAsync("SetLightEditor();");
}
}
private async Task RenderInternalAsync(string htmlBody)
{
await _domLoadedTask.Task;
await UpdateEditorThemeAsync();
await InitializeEditorAsync();
if (string.IsNullOrEmpty(htmlBody))
{
await Chromium.ExecuteScriptFunctionAsync("RenderHTML", parameters: JsonSerializer.Serialize(" ", BasicTypesJsonContext.Default.String));
}
else
{
await Chromium.ExecuteScriptFunctionAsync("RenderHTML", parameters: JsonSerializer.Serialize(htmlBody, BasicTypesJsonContext.Default.String));
await FocusEditorAsync();
}
}
private async Task<string> InitializeEditorAsync()
{
var fonts = _fontService.GetFonts();
var composerFont = _preferencesService.ComposerFont;
int composerFontSize = _preferencesService.ComposerFontSize;
var readerFont = _preferencesService.ReaderFont;
int readerFontSize = _preferencesService.ReaderFontSize;
return await Chromium.ExecuteScriptFunctionAsync("initializeJodit", false,
JsonSerializer.Serialize(fonts, BasicTypesJsonContext.Default.ListString),
JsonSerializer.Serialize(composerFont, BasicTypesJsonContext.Default.String),
JsonSerializer.Serialize(composerFontSize, BasicTypesJsonContext.Default.Int32),
JsonSerializer.Serialize(readerFont, BasicTypesJsonContext.Default.String),
JsonSerializer.Serialize(readerFontSize, BasicTypesJsonContext.Default.Int32));
}
private async void ChromiumInitialized(Microsoft.UI.Xaml.Controls.WebView2 sender, Microsoft.UI.Xaml.Controls.CoreWebView2InitializedEventArgs args)
{
var editorBundlePath = (await _nativeAppService.GetEditorBundlePathAsync()).Replace("editor.html", string.Empty);
Chromium.CoreWebView2.SetVirtualHostNameToFolderMapping("app.editor", editorBundlePath, CoreWebView2HostResourceAccessKind.Allow);
Chromium.Source = new Uri("https://app.editor/editor.html");
Chromium.CoreWebView2.DOMContentLoaded -= DOMLoaded;
Chromium.CoreWebView2.DOMContentLoaded += DOMLoaded;
Chromium.CoreWebView2.WebMessageReceived -= ScriptMessageReceived;
Chromium.CoreWebView2.WebMessageReceived += ScriptMessageReceived;
}
private static async void OnIsComposerDarkModeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is SignatureEditorDialog dialog)
{
await dialog.UpdateEditorThemeAsync();
}
}
private void ScriptMessageReceived(CoreWebView2 sender, CoreWebView2WebMessageReceivedEventArgs args)
{
var change = JsonSerializer.Deserialize(args.WebMessageAsJson, DomainModelsJsonContext.Default.WebViewMessage);
if (change.Type == "bold")
{
BoldButton.IsChecked = change.Value == "true";
}
else if (change.Type == "italic")
{
ItalicButton.IsChecked = change.Value == "true";
}
else if (change.Type == "underline")
{
UnderlineButton.IsChecked = change.Value == "true";
}
else if (change.Type == "strikethrough")
{
StrokeButton.IsChecked = change.Value == "true";
}
else if (change.Type == "ol")
{
OrderedListButton.IsChecked = change.Value == "true";
}
else if (change.Type == "ul")
{
BulletListButton.IsChecked = change.Value == "true";
}
else if (change.Type == "indent")
{
IncreaseIndentButton.IsEnabled = change.Value == "disabled" ? false : true;
}
else if (change.Type == "outdent")
{
DecreaseIndentButton.IsEnabled = change.Value == "disabled" ? false : true;
}
else if (change.Type == "alignment")
{
var parsedValue = change.Value switch
{
"jodit-icon_left" => 0,
"jodit-icon_center" => 1,
"jodit-icon_right" => 2,
"jodit-icon_justify" => 3,
_ => 0
};
AlignmentListView.SelectionChanged -= AlignmentChanged;
AlignmentListView.SelectedIndex = parsedValue;
AlignmentListView.SelectionChanged += AlignmentChanged;
}
}
private void DOMLoaded(CoreWebView2 sender, CoreWebView2DOMContentLoadedEventArgs args) => _domLoadedTask.TrySetResult(true);
private void SignatureNameTextBoxTextChanged(object sender, TextChangedEventArgs e) => IsPrimaryButtonEnabled = !string.IsNullOrWhiteSpace(SignatureNameTextBox.Text);
private void InvertComposerThemeClicked(object sender, RoutedEventArgs e) => IsComposerDarkMode = !IsComposerDarkMode;
}
private void DOMLoaded(CoreWebView2 sender, CoreWebView2DOMContentLoadedEventArgs args) => _domLoadedTask.TrySetResult(true);
private void SignatureNameTextBoxTextChanged(object sender, TextChangedEventArgs e) => IsPrimaryButtonEnabled = !string.IsNullOrWhiteSpace(SignatureNameTextBox.Text);
private void InvertComposerThemeClicked(object sender, RoutedEventArgs e) => IsComposerDarkMode = !IsComposerDarkMode;
}

View File

@@ -8,64 +8,65 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Models.Folders;
namespace Wino.Dialogs;
public sealed partial class SystemFolderConfigurationDialog : ContentDialog
namespace Wino.Dialogs
{
private bool canDismissDialog = false;
public SystemFolderConfiguration Configuration { get; set; }
public List<MailItemFolder> AvailableFolders { get; }
public MailItemFolder Sent { get; set; }
public MailItemFolder Draft { get; set; }
public MailItemFolder Archive { get; set; }
public MailItemFolder Junk { get; set; }
public MailItemFolder Trash { get; set; }
public SystemFolderConfigurationDialog(List<MailItemFolder> availableFolders)
public sealed partial class SystemFolderConfigurationDialog : ContentDialog
{
InitializeComponent();
private bool canDismissDialog = false;
AvailableFolders = availableFolders;
public SystemFolderConfiguration Configuration { get; set; }
public List<MailItemFolder> AvailableFolders { get; }
Sent = AvailableFolders.Find(a => a.SpecialFolderType == Core.Domain.Enums.SpecialFolderType.Sent);
Draft = AvailableFolders.Find(a => a.SpecialFolderType == Core.Domain.Enums.SpecialFolderType.Draft);
Archive = AvailableFolders.Find(a => a.SpecialFolderType == Core.Domain.Enums.SpecialFolderType.Archive);
Junk = AvailableFolders.Find(a => a.SpecialFolderType == Core.Domain.Enums.SpecialFolderType.Junk);
Trash = AvailableFolders.Find(a => a.SpecialFolderType == Core.Domain.Enums.SpecialFolderType.Deleted);
}
public MailItemFolder Sent { get; set; }
public MailItemFolder Draft { get; set; }
public MailItemFolder Archive { get; set; }
public MailItemFolder Junk { get; set; }
public MailItemFolder Trash { get; set; }
private void DialogClosing(ContentDialog sender, ContentDialogClosingEventArgs args)
{
args.Cancel = !canDismissDialog;
}
private void CancelClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
=> canDismissDialog = true;
private void SaveClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
ValidationErrorTextBlock.Text = string.Empty;
var allSpecialFolders = new List<MailItemFolder>()
public SystemFolderConfigurationDialog(List<MailItemFolder> availableFolders)
{
Sent, Draft, Archive, Trash, Junk
};
InitializeComponent();
if (allSpecialFolders.Any(a => a != null && a.SpecialFolderType == SpecialFolderType.Inbox))
ValidationErrorTextBlock.Text = Translator.SystemFolderConfigDialogValidation_InboxSelected;
AvailableFolders = availableFolders;
if (new HashSet<Guid>(allSpecialFolders.Where(a => a != null).Select(x => x.Id)).Count != allSpecialFolders.Where(a => a != null).Count())
ValidationErrorTextBlock.Text = Translator.SystemFolderConfigDialogValidation_DuplicateSystemFolders;
Sent = AvailableFolders.Find(a => a.SpecialFolderType == Core.Domain.Enums.SpecialFolderType.Sent);
Draft = AvailableFolders.Find(a => a.SpecialFolderType == Core.Domain.Enums.SpecialFolderType.Draft);
Archive = AvailableFolders.Find(a => a.SpecialFolderType == Core.Domain.Enums.SpecialFolderType.Archive);
Junk = AvailableFolders.Find(a => a.SpecialFolderType == Core.Domain.Enums.SpecialFolderType.Junk);
Trash = AvailableFolders.Find(a => a.SpecialFolderType == Core.Domain.Enums.SpecialFolderType.Deleted);
}
// Check if we can save.
if (string.IsNullOrEmpty(ValidationErrorTextBlock.Text))
private void DialogClosing(ContentDialog sender, ContentDialogClosingEventArgs args)
{
var configuration = new SystemFolderConfiguration(Sent, Draft, Archive, Trash, Junk);
args.Cancel = !canDismissDialog;
}
canDismissDialog = true;
Configuration = configuration;
private void CancelClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
=> canDismissDialog = true;
private void SaveClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
ValidationErrorTextBlock.Text = string.Empty;
var allSpecialFolders = new List<MailItemFolder>()
{
Sent, Draft, Archive, Trash, Junk
};
if (allSpecialFolders.Any(a => a != null && a.SpecialFolderType == SpecialFolderType.Inbox))
ValidationErrorTextBlock.Text = Translator.SystemFolderConfigDialogValidation_InboxSelected;
if (new HashSet<Guid>(allSpecialFolders.Where(a => a != null).Select(x => x.Id)).Count != allSpecialFolders.Where(a => a != null).Count())
ValidationErrorTextBlock.Text = Translator.SystemFolderConfigDialogValidation_DuplicateSystemFolders;
// Check if we can save.
if (string.IsNullOrEmpty(ValidationErrorTextBlock.Text))
{
var configuration = new SystemFolderConfiguration(Sent, Draft, Archive, Trash, Junk);
canDismissDialog = true;
Configuration = configuration;
}
}
}
}

View File

@@ -7,52 +7,53 @@ using Wino.Core.Domain.Entities.Shared;
using Wino.Core.UWP.Controls;
using Wino.Helpers;
namespace Wino.MenuFlyouts;
public partial class AccountSelectorFlyout : MenuFlyout, IDisposable
namespace Wino.MenuFlyouts
{
private readonly IEnumerable<MailAccount> _accounts;
private readonly Func<MailAccount, Task> _onItemSelection;
public AccountSelectorFlyout(IEnumerable<MailAccount> accounts, Func<MailAccount, Task> onItemSelection)
public partial class AccountSelectorFlyout : MenuFlyout, IDisposable
{
_accounts = accounts;
_onItemSelection = onItemSelection;
private readonly IEnumerable<MailAccount> _accounts;
private readonly Func<MailAccount, Task> _onItemSelection;
foreach (var account in _accounts)
public AccountSelectorFlyout(IEnumerable<MailAccount> accounts, Func<MailAccount, Task> onItemSelection)
{
var pathData = new WinoFontIcon() { Icon = XamlHelpers.GetProviderIcon(account) };
var menuItem = new MenuFlyoutItem() { Tag = account.Address, Icon = pathData, Text = $"{account.Name} ({account.Address})", MinHeight = 55 };
_accounts = accounts;
_onItemSelection = onItemSelection;
menuItem.Click += AccountClicked;
Items.Add(menuItem);
}
}
public void Dispose()
{
foreach (var menuItem in Items)
{
if (menuItem is MenuFlyoutItem flyoutItem)
foreach (var account in _accounts)
{
flyoutItem.Click -= AccountClicked;
}
}
}
var pathData = new WinoFontIcon() { Icon = XamlHelpers.GetProviderIcon(account) };
var menuItem = new MenuFlyoutItem() { Tag = account.Address, Icon = pathData, Text = $"{account.Name} ({account.Address})", MinHeight = 55 };
private async void AccountClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
if (sender is MenuFlyoutItem menuItem && menuItem.Tag is string accountAddress)
{
var selectedMenuItem = _accounts.FirstOrDefault(a => a.Address == accountAddress);
if (selectedMenuItem != null)
{
await _onItemSelection(selectedMenuItem);
menuItem.Click += AccountClicked;
Items.Add(menuItem);
}
}
Dispose();
Hide();
public void Dispose()
{
foreach (var menuItem in Items)
{
if (menuItem is MenuFlyoutItem flyoutItem)
{
flyoutItem.Click -= AccountClicked;
}
}
}
private async void AccountClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
if (sender is MenuFlyoutItem menuItem && menuItem.Tag is string accountAddress)
{
var selectedMenuItem = _accounts.FirstOrDefault(a => a.Address == accountAddress);
if (selectedMenuItem != null)
{
await _onItemSelection(selectedMenuItem);
}
}
Dispose();
Hide();
}
}
}

View File

@@ -7,212 +7,213 @@ using Wino.Core.Domain.Models.Reader;
using Wino.Core.UWP.Controls;
using Wino.Helpers;
namespace Wino.MenuFlyouts;
public partial class FilterMenuFlyout : MenuFlyout
namespace Wino.MenuFlyouts
{
public static readonly DependencyProperty SelectedFilterChangedCommandProperty = DependencyProperty.Register(nameof(SelectedFilterChangedCommand), typeof(IRelayCommand<FilterOption>), typeof(FilterMenuFlyout), new PropertyMetadata(null));
public static readonly DependencyProperty FilterOptionsProperty = DependencyProperty.Register(nameof(FilterOptions), typeof(List<FilterOption>), typeof(FilterMenuFlyout), new PropertyMetadata(null, new PropertyChangedCallback(OnOptionsChanged)));
public static readonly DependencyProperty SelectedFilterOptionProperty = DependencyProperty.Register(nameof(SelectedFilterOption), typeof(FilterOption), typeof(FilterMenuFlyout), new PropertyMetadata(null, OnSelectedFilterOptionChanged));
public static readonly DependencyProperty SelectedSortingOptionProperty = DependencyProperty.Register(nameof(SelectedSortingOption), typeof(SortingOption), typeof(FilterMenuFlyout), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedSortingOptionChanged)));
public static readonly DependencyProperty SortingOptionsProperty = DependencyProperty.Register(nameof(SortingOptions), typeof(List<SortingOption>), typeof(FilterMenuFlyout), new PropertyMetadata(null, new PropertyChangedCallback(OnOptionsChanged)));
public static readonly DependencyProperty SelectedSortingOptionChangedCommandProperty = DependencyProperty.Register(nameof(SelectedSortingOptionChangedCommand), typeof(IRelayCommand<SortingOption>), typeof(FilterMenuFlyout), new PropertyMetadata(null));
public IRelayCommand<FilterOption> SelectedFilterChangedCommand
public partial class FilterMenuFlyout : MenuFlyout
{
get { return (IRelayCommand<FilterOption>)GetValue(SelectedFilterChangedCommandProperty); }
set { SetValue(SelectedFilterChangedCommandProperty, value); }
}
public static readonly DependencyProperty SelectedFilterChangedCommandProperty = DependencyProperty.Register(nameof(SelectedFilterChangedCommand), typeof(IRelayCommand<FilterOption>), typeof(FilterMenuFlyout), new PropertyMetadata(null));
public static readonly DependencyProperty FilterOptionsProperty = DependencyProperty.Register(nameof(FilterOptions), typeof(List<FilterOption>), typeof(FilterMenuFlyout), new PropertyMetadata(null, new PropertyChangedCallback(OnOptionsChanged)));
public static readonly DependencyProperty SelectedFilterOptionProperty = DependencyProperty.Register(nameof(SelectedFilterOption), typeof(FilterOption), typeof(FilterMenuFlyout), new PropertyMetadata(null, OnSelectedFilterOptionChanged));
public static readonly DependencyProperty SelectedSortingOptionProperty = DependencyProperty.Register(nameof(SelectedSortingOption), typeof(SortingOption), typeof(FilterMenuFlyout), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedSortingOptionChanged)));
public static readonly DependencyProperty SortingOptionsProperty = DependencyProperty.Register(nameof(SortingOptions), typeof(List<SortingOption>), typeof(FilterMenuFlyout), new PropertyMetadata(null, new PropertyChangedCallback(OnOptionsChanged)));
public static readonly DependencyProperty SelectedSortingOptionChangedCommandProperty = DependencyProperty.Register(nameof(SelectedSortingOptionChangedCommand), typeof(IRelayCommand<SortingOption>), typeof(FilterMenuFlyout), new PropertyMetadata(null));
public IRelayCommand<SortingOption> SelectedSortingOptionChangedCommand
{
get { return (IRelayCommand<SortingOption>)GetValue(SelectedSortingOptionChangedCommandProperty); }
set { SetValue(SelectedSortingOptionChangedCommandProperty, value); }
}
public List<FilterOption> FilterOptions
{
get { return (List<FilterOption>)GetValue(FilterOptionsProperty); }
set { SetValue(FilterOptionsProperty, value); }
}
public List<SortingOption> SortingOptions
{
get { return (List<SortingOption>)GetValue(SortingOptionsProperty); }
set { SetValue(SortingOptionsProperty, value); }
}
public FilterOption SelectedFilterOption
{
get { return (FilterOption)GetValue(SelectedFilterOptionProperty); }
set { SetValue(SelectedFilterOptionProperty, value); }
}
public SortingOption SelectedSortingOption
{
get { return (SortingOption)GetValue(SelectedSortingOptionProperty); }
set { SetValue(SelectedSortingOptionProperty, value); }
}
private static void OnSelectedFilterOptionChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is FilterMenuFlyout bar)
public IRelayCommand<FilterOption> SelectedFilterChangedCommand
{
bar.SelectFilterOption(bar.SelectedFilterOption);
bar.SelectedFilterChangedCommand?.Execute(bar.SelectedFilterOption);
get { return (IRelayCommand<FilterOption>)GetValue(SelectedFilterChangedCommandProperty); }
set { SetValue(SelectedFilterChangedCommandProperty, value); }
}
}
private static void OnSelectedSortingOptionChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is FilterMenuFlyout bar)
public IRelayCommand<SortingOption> SelectedSortingOptionChangedCommand
{
bar.SelectSortingOption(bar.SelectedSortingOption);
bar.SelectedSortingOptionChangedCommand?.Execute(bar.SelectedSortingOption);
get { return (IRelayCommand<SortingOption>)GetValue(SelectedSortingOptionChangedCommandProperty); }
set { SetValue(SelectedSortingOptionChangedCommandProperty, value); }
}
}
private ToggleMenuFlyoutItem CreateFilterToggleButton(FilterOption option)
{
var button = new ToggleMenuFlyoutItem()
public List<FilterOption> FilterOptions
{
Text = option.Title,
Tag = option,
Icon = new WinoFontIcon() { Icon = XamlHelpers.GetWinoIconGlyph(option.Type) },
IsChecked = option == SelectedFilterOption
};
button.Click += FilterToggleChecked;
return button;
}
private ToggleMenuFlyoutItem CreateSortingToggleButton(SortingOption option)
{
var button = new ToggleMenuFlyoutItem()
{
Text = option.Title,
Tag = option,
Icon = new WinoFontIcon() { Icon = XamlHelpers.GetWinoIconGlyph(option.Type)},
IsChecked = option == SelectedSortingOption
};
button.Click += SortingOptionChecked;
return button;
}
private void SortingOptionChecked(object sender, RoutedEventArgs e)
{
if (sender is ToggleMenuFlyoutItem button)
{
button.IsHitTestVisible = false;
var optionModel = button.Tag as SortingOption;
SelectSortingOption(optionModel);
get { return (List<FilterOption>)GetValue(FilterOptionsProperty); }
set { SetValue(FilterOptionsProperty, value); }
}
}
private void FilterToggleChecked(object sender, RoutedEventArgs e)
{
if (sender is ToggleMenuFlyoutItem button)
public List<SortingOption> SortingOptions
{
button.IsHitTestVisible = false;
var optionModel = button.Tag as FilterOption;
SelectFilterOption(optionModel);
get { return (List<SortingOption>)GetValue(SortingOptionsProperty); }
set { SetValue(SortingOptionsProperty, value); }
}
}
private void SelectFilterOption(FilterOption option)
{
SelectedFilterOption = option;
public FilterOption SelectedFilterOption
{
get { return (FilterOption)GetValue(SelectedFilterOptionProperty); }
set { SetValue(SelectedFilterOptionProperty, value); }
}
UncheckOtherFilterOptions();
}
public SortingOption SelectedSortingOption
{
get { return (SortingOption)GetValue(SelectedSortingOptionProperty); }
set { SetValue(SelectedSortingOptionProperty, value); }
}
private void SelectSortingOption(SortingOption option)
{
SelectedSortingOption = option;
private static void OnSelectedFilterOptionChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is FilterMenuFlyout bar)
{
bar.SelectFilterOption(bar.SelectedFilterOption);
bar.SelectedFilterChangedCommand?.Execute(bar.SelectedFilterOption);
}
}
UncheckOtherSortingOptions();
}
private static void OnSelectedSortingOptionChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is FilterMenuFlyout bar)
{
bar.SelectSortingOption(bar.SelectedSortingOption);
bar.SelectedSortingOptionChangedCommand?.Execute(bar.SelectedSortingOption);
}
}
private void UnregisterCheckedHandler(ToggleMenuFlyoutItem button)
{
button.Click -= FilterToggleChecked;
}
private ToggleMenuFlyoutItem CreateFilterToggleButton(FilterOption option)
{
var button = new ToggleMenuFlyoutItem()
{
Text = option.Title,
Tag = option,
Icon = new WinoFontIcon() { Icon = XamlHelpers.GetWinoIconGlyph(option.Type) },
IsChecked = option == SelectedFilterOption
};
private void UncheckOtherFilterOptions()
{
if (Items.Any())
button.Click += FilterToggleChecked;
return button;
}
private ToggleMenuFlyoutItem CreateSortingToggleButton(SortingOption option)
{
var button = new ToggleMenuFlyoutItem()
{
Text = option.Title,
Tag = option,
Icon = new WinoFontIcon() { Icon = XamlHelpers.GetWinoIconGlyph(option.Type)},
IsChecked = option == SelectedSortingOption
};
button.Click += SortingOptionChecked;
return button;
}
private void SortingOptionChecked(object sender, RoutedEventArgs e)
{
if (sender is ToggleMenuFlyoutItem button)
{
button.IsHitTestVisible = false;
var optionModel = button.Tag as SortingOption;
SelectSortingOption(optionModel);
}
}
private void FilterToggleChecked(object sender, RoutedEventArgs e)
{
if (sender is ToggleMenuFlyoutItem button)
{
button.IsHitTestVisible = false;
var optionModel = button.Tag as FilterOption;
SelectFilterOption(optionModel);
}
}
private void SelectFilterOption(FilterOption option)
{
SelectedFilterOption = option;
UncheckOtherFilterOptions();
}
private void SelectSortingOption(SortingOption option)
{
SelectedSortingOption = option;
UncheckOtherSortingOptions();
}
private void UnregisterCheckedHandler(ToggleMenuFlyoutItem button)
{
button.Click -= FilterToggleChecked;
}
private void UncheckOtherFilterOptions()
{
if (Items.Any())
{
foreach (var item in Items)
{
if (item is ToggleMenuFlyoutItem toggleButton && toggleButton.Tag is FilterOption option && option != SelectedFilterOption)
{
toggleButton.IsChecked = false;
toggleButton.IsHitTestVisible = true;
}
}
}
}
private void UncheckOtherSortingOptions()
{
if (Items.Any())
{
foreach (var item in Items)
{
if (item is ToggleMenuFlyoutItem toggleButton && toggleButton.Tag is SortingOption option && option != SelectedSortingOption)
{
toggleButton.IsChecked = false;
toggleButton.IsHitTestVisible = true;
}
}
}
}
public void Dispose()
{
foreach (var item in Items)
{
if (item is ToggleMenuFlyoutItem toggleButton && toggleButton.Tag is FilterOption option && option != SelectedFilterOption)
if (item is ToggleMenuFlyoutItem toggleButton)
{
toggleButton.IsChecked = false;
toggleButton.IsHitTestVisible = true;
UnregisterCheckedHandler(toggleButton);
}
}
}
}
private void UncheckOtherSortingOptions()
{
if (Items.Any())
private static void OnOptionsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
foreach (var item in Items)
if (obj is FilterMenuFlyout bar && bar.SortingOptions != null && bar.FilterOptions != null)
{
if (item is ToggleMenuFlyoutItem toggleButton && toggleButton.Tag is SortingOption option && option != SelectedSortingOption)
bar.Dispose();
bar.Items.Clear();
if (bar.FilterOptions != null)
{
toggleButton.IsChecked = false;
toggleButton.IsHitTestVisible = true;
foreach (var item in bar.FilterOptions)
{
bar.Items.Add(bar.CreateFilterToggleButton(item));
}
}
}
}
}
public void Dispose()
{
foreach (var item in Items)
{
if (item is ToggleMenuFlyoutItem toggleButton)
{
UnregisterCheckedHandler(toggleButton);
}
}
}
bar.Items.Add(new MenuFlyoutSeparator());
private static void OnOptionsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is FilterMenuFlyout bar && bar.SortingOptions != null && bar.FilterOptions != null)
{
bar.Dispose();
// Sorting options.
bar.Items.Clear();
if (bar.FilterOptions != null)
{
foreach (var item in bar.FilterOptions)
if (bar.SortingOptions != null)
{
bar.Items.Add(bar.CreateFilterToggleButton(item));
}
}
bar.Items.Add(new MenuFlyoutSeparator());
// Sorting options.
if (bar.SortingOptions != null)
{
foreach (var item in bar.SortingOptions)
{
bar.Items.Add(bar.CreateSortingToggleButton(item));
foreach (var item in bar.SortingOptions)
{
bar.Items.Add(bar.CreateSortingToggleButton(item));
}
}
}
}

View File

@@ -4,23 +4,24 @@ using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Folders;
namespace Wino.MenuFlyouts.Context;
public partial class FolderOperationFlyout : WinoOperationFlyout<FolderOperationMenuItem>
namespace Wino.MenuFlyouts.Context
{
public FolderOperationFlyout(IEnumerable<FolderOperationMenuItem> availableActions, TaskCompletionSource<FolderOperationMenuItem> completionSource) : base(availableActions, completionSource)
public partial class FolderOperationFlyout : WinoOperationFlyout<FolderOperationMenuItem>
{
if (AvailableActions == null) return;
foreach (var action in AvailableActions)
public FolderOperationFlyout(IEnumerable<FolderOperationMenuItem> availableActions, TaskCompletionSource<FolderOperationMenuItem> completionSource) : base(availableActions, completionSource)
{
if (action.Operation == FolderOperation.Seperator)
Items.Add(new MenuFlyoutSeparator());
else
{
var menuFlyoutItem = new FolderOperationMenuFlyoutItem(action, (c) => MenuItemClicked(c));
if (AvailableActions == null) return;
Items.Add(menuFlyoutItem);
foreach (var action in AvailableActions)
{
if (action.Operation == FolderOperation.Seperator)
Items.Add(new MenuFlyoutSeparator());
else
{
var menuFlyoutItem = new FolderOperationMenuFlyoutItem(action, (c) => MenuItemClicked(c));
Items.Add(menuFlyoutItem);
}
}
}
}

View File

@@ -1,11 +1,12 @@
using System;
using Wino.Core.Domain.Models.Folders;
namespace Wino.MenuFlyouts;
public partial class FolderOperationMenuFlyoutItem : WinoOperationFlyoutItem<FolderOperationMenuItem>
namespace Wino.MenuFlyouts
{
public FolderOperationMenuFlyoutItem(FolderOperationMenuItem operationMenuItem, Action<FolderOperationMenuItem> clicked) : base(operationMenuItem, clicked)
public partial class FolderOperationMenuFlyoutItem : WinoOperationFlyoutItem<FolderOperationMenuItem>
{
public FolderOperationMenuFlyoutItem(FolderOperationMenuItem operationMenuItem, Action<FolderOperationMenuItem> clicked) : base(operationMenuItem, clicked)
{
}
}
}

View File

@@ -4,23 +4,24 @@ using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Menus;
namespace Wino.MenuFlyouts.Context;
public partial class MailOperationFlyout : WinoOperationFlyout<MailOperationMenuItem>
namespace Wino.MenuFlyouts.Context
{
public MailOperationFlyout(IEnumerable<MailOperationMenuItem> availableActions, TaskCompletionSource<MailOperationMenuItem> completionSource) : base(availableActions, completionSource)
public partial class MailOperationFlyout : WinoOperationFlyout<MailOperationMenuItem>
{
if (AvailableActions == null) return;
foreach (var action in AvailableActions)
public MailOperationFlyout(IEnumerable<MailOperationMenuItem> availableActions, TaskCompletionSource<MailOperationMenuItem> completionSource) : base(availableActions, completionSource)
{
if (action.Operation == MailOperation.Seperator)
Items.Add(new MenuFlyoutSeparator());
else
{
var menuFlyoutItem = new MailOperationMenuFlyoutItem(action, (c) => MenuItemClicked(c));
if (AvailableActions == null) return;
Items.Add(menuFlyoutItem);
foreach (var action in AvailableActions)
{
if (action.Operation == MailOperation.Seperator)
Items.Add(new MenuFlyoutSeparator());
else
{
var menuFlyoutItem = new MailOperationMenuFlyoutItem(action, (c) => MenuItemClicked(c));
Items.Add(menuFlyoutItem);
}
}
}
}

View File

@@ -1,11 +1,12 @@
using System;
using Wino.Core.Domain.Models.Menus;
namespace Wino.MenuFlyouts.Context;
public partial class MailOperationMenuFlyoutItem : WinoOperationFlyoutItem<MailOperationMenuItem>
namespace Wino.MenuFlyouts.Context
{
public MailOperationMenuFlyoutItem(MailOperationMenuItem operationMenuItem, Action<MailOperationMenuItem> clicked) : base(operationMenuItem, clicked)
public partial class MailOperationMenuFlyoutItem : WinoOperationFlyoutItem<MailOperationMenuItem>
{
public MailOperationMenuFlyoutItem(MailOperationMenuItem operationMenuItem, Action<MailOperationMenuItem> clicked) : base(operationMenuItem, clicked)
{
}
}
}

View File

@@ -6,81 +6,82 @@ using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Entities.Mail;
namespace Wino.MenuFlyouts;
public class MoveButtonMenuItemClickedEventArgs
namespace Wino.MenuFlyouts
{
public Guid ClickedFolderId { get; set; }
}
public partial class MoveButtonFlyout : MenuFlyout
{
public event TypedEventHandler<MoveButtonFlyout, MoveButtonMenuItemClickedEventArgs> MenuItemClick;
public static readonly DependencyProperty FoldersProperty = DependencyProperty.Register(nameof(Folders), typeof(List<MailItemFolder>), typeof(MoveButtonFlyout), new PropertyMetadata(null, new PropertyChangedCallback(OnFoldersChanged)));
public List<MailItemFolder> Folders
public class MoveButtonMenuItemClickedEventArgs
{
get { return (List<MailItemFolder>)GetValue(FoldersProperty); }
set { SetValue(FoldersProperty, value); }
public Guid ClickedFolderId { get; set; }
}
private static void OnFoldersChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
public partial class MoveButtonFlyout : MenuFlyout
{
if (obj is MoveButtonFlyout menu)
public event TypedEventHandler<MoveButtonFlyout, MoveButtonMenuItemClickedEventArgs> MenuItemClick;
public static readonly DependencyProperty FoldersProperty = DependencyProperty.Register(nameof(Folders), typeof(List<MailItemFolder>), typeof(MoveButtonFlyout), new PropertyMetadata(null, new PropertyChangedCallback(OnFoldersChanged)));
public List<MailItemFolder> Folders
{
menu.InitializeMenu();
get { return (List<MailItemFolder>)GetValue(FoldersProperty); }
set { SetValue(FoldersProperty, value); }
}
}
private void InitializeMenu()
{
Dispose();
Items.Clear();
if (Folders == null || !Folders.Any())
return;
// TODO: Child folders.
foreach (var item in Folders)
private static void OnFoldersChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
// We don't expect this, but it crashes startup.
// Just to be on the safe side.
if (item.FolderName != null)
if (obj is MoveButtonFlyout menu)
{
var folderMenuItem = new MenuFlyoutItem()
menu.InitializeMenu();
}
}
private void InitializeMenu()
{
Dispose();
Items.Clear();
if (Folders == null || !Folders.Any())
return;
// TODO: Child folders.
foreach (var item in Folders)
{
// We don't expect this, but it crashes startup.
// Just to be on the safe side.
if (item.FolderName != null)
{
Tag = item,
Text = item.FolderName
};
var folderMenuItem = new MenuFlyoutItem()
{
Tag = item,
Text = item.FolderName
};
folderMenuItem.Click += MenuItemClicked;
folderMenuItem.Click += MenuItemClicked;
Items.Add(folderMenuItem);
Items.Add(folderMenuItem);
}
}
}
}
private void MenuItemClicked(object sender, RoutedEventArgs e)
{
var clickedFolder = (sender as MenuFlyoutItem).Tag as MailItemFolder;
MenuItemClick?.Invoke(this, new MoveButtonMenuItemClickedEventArgs()
private void MenuItemClicked(object sender, RoutedEventArgs e)
{
ClickedFolderId = clickedFolder.Id
});
}
var clickedFolder = (sender as MenuFlyoutItem).Tag as MailItemFolder;
public void Dispose()
{
foreach (var item in Items)
{
if (item is MenuFlyoutItem menuItem)
MenuItemClick?.Invoke(this, new MoveButtonMenuItemClickedEventArgs()
{
menuItem.Click -= MenuItemClicked;
ClickedFolderId = clickedFolder.Id
});
}
public void Dispose()
{
foreach (var item in Items)
{
if (item is MenuFlyoutItem menuItem)
{
menuItem.Click -= MenuItemClicked;
}
}
}
}

View File

@@ -3,46 +3,47 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Windows.UI.Xaml.Controls;
namespace Wino.MenuFlyouts;
public partial class WinoOperationFlyout<TActionType> : MenuFlyout, IDisposable where TActionType : class
namespace Wino.MenuFlyouts
{
public TActionType ClickedOperation { get; set; }
protected readonly IEnumerable<TActionType> AvailableActions;
private readonly TaskCompletionSource<TActionType> _completionSource;
public WinoOperationFlyout(IEnumerable<TActionType> availableActions, TaskCompletionSource<TActionType> completionSource)
public partial class WinoOperationFlyout<TActionType> : MenuFlyout, IDisposable where TActionType : class
{
_completionSource = completionSource;
public TActionType ClickedOperation { get; set; }
AvailableActions = availableActions;
protected readonly IEnumerable<TActionType> AvailableActions;
Closing += FlyoutClosing;
}
private readonly TaskCompletionSource<TActionType> _completionSource;
private void FlyoutClosing(Windows.UI.Xaml.Controls.Primitives.FlyoutBase sender, Windows.UI.Xaml.Controls.Primitives.FlyoutBaseClosingEventArgs args)
{
Closing -= FlyoutClosing;
_completionSource.TrySetResult(ClickedOperation);
}
protected void MenuItemClicked(TActionType operation)
{
ClickedOperation = operation;
Hide();
}
public void Dispose()
{
foreach (var item in Items)
public WinoOperationFlyout(IEnumerable<TActionType> availableActions, TaskCompletionSource<TActionType> completionSource)
{
if (item is IDisposable disposableItem)
_completionSource = completionSource;
AvailableActions = availableActions;
Closing += FlyoutClosing;
}
private void FlyoutClosing(Windows.UI.Xaml.Controls.Primitives.FlyoutBase sender, Windows.UI.Xaml.Controls.Primitives.FlyoutBaseClosingEventArgs args)
{
Closing -= FlyoutClosing;
_completionSource.TrySetResult(ClickedOperation);
}
protected void MenuItemClicked(TActionType operation)
{
ClickedOperation = operation;
Hide();
}
public void Dispose()
{
foreach (var item in Items)
{
disposableItem.Dispose();
if (item is IDisposable disposableItem)
{
disposableItem.Dispose();
}
}
}
}

View File

@@ -7,51 +7,52 @@ using Wino.Core.Domain.Models.Menus;
using Wino.Core.UWP.Controls;
using Wino.Helpers;
namespace Wino.MenuFlyouts;
public partial class WinoOperationFlyoutItem<TOperationMenuItem> : MenuFlyoutItem, IDisposable where TOperationMenuItem : IMenuOperation
namespace Wino.MenuFlyouts
{
private const double CustomHeight = 35;
public TOperationMenuItem Operation { get; set; }
Action<TOperationMenuItem> Clicked { get; set; }
public WinoOperationFlyoutItem(TOperationMenuItem operationMenuItem, Action<TOperationMenuItem> clicked)
public partial class WinoOperationFlyoutItem<TOperationMenuItem> : MenuFlyoutItem, IDisposable where TOperationMenuItem : IMenuOperation
{
Margin = new Thickness(4, 2, 4, 2);
CornerRadius = new CornerRadius(6, 6, 6, 6);
private const double CustomHeight = 35;
MinHeight = CustomHeight;
public TOperationMenuItem Operation { get; set; }
Action<TOperationMenuItem> Clicked { get; set; }
Operation = operationMenuItem;
IsEnabled = operationMenuItem.IsEnabled;
if (Operation is FolderOperationMenuItem folderOperationMenuItem)
public WinoOperationFlyoutItem(TOperationMenuItem operationMenuItem, Action<TOperationMenuItem> clicked)
{
var internalOperation = folderOperationMenuItem.Operation;
Margin = new Thickness(4, 2, 4, 2);
CornerRadius = new CornerRadius(6, 6, 6, 6);
Icon = new WinoFontIcon() { Icon = XamlHelpers.GetPathGeometry(internalOperation) };
Text = XamlHelpers.GetOperationString(internalOperation);
}
else if (Operation is MailOperationMenuItem mailOperationMenuItem)
{
var internalOperation = mailOperationMenuItem.Operation;
MinHeight = CustomHeight;
Icon = new WinoFontIcon() { Icon = XamlHelpers.GetWinoIconGlyph(internalOperation) };
Text = XamlHelpers.GetOperationString(internalOperation);
Operation = operationMenuItem;
IsEnabled = operationMenuItem.IsEnabled;
if (Operation is FolderOperationMenuItem folderOperationMenuItem)
{
var internalOperation = folderOperationMenuItem.Operation;
Icon = new WinoFontIcon() { Icon = XamlHelpers.GetPathGeometry(internalOperation) };
Text = XamlHelpers.GetOperationString(internalOperation);
}
else if (Operation is MailOperationMenuItem mailOperationMenuItem)
{
var internalOperation = mailOperationMenuItem.Operation;
Icon = new WinoFontIcon() { Icon = XamlHelpers.GetWinoIconGlyph(internalOperation) };
Text = XamlHelpers.GetOperationString(internalOperation);
}
Clicked = clicked;
Click += MenuClicked;
}
Clicked = clicked;
Click += MenuClicked;
}
private void MenuClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
Clicked(Operation);
}
private void MenuClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
Clicked(Operation);
}
public void Dispose()
{
Click -= MenuClicked;
public void Dispose()
{
Click -= MenuClicked;
}
}
}

View File

@@ -2,18 +2,19 @@
using Windows.UI.Xaml.Controls;
using Wino.Core.ViewModels.Data;
namespace Wino.Selectors;
public partial class AccountProviderViewModelTemplateSelector : DataTemplateSelector
namespace Wino.Selectors
{
public DataTemplate RootAccountTemplate { get; set; }
public DataTemplate MergedAccountTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
public partial class AccountProviderViewModelTemplateSelector : DataTemplateSelector
{
if (item is MergedAccountProviderDetailViewModel)
return MergedAccountTemplate;
else
return RootAccountTemplate;
public DataTemplate RootAccountTemplate { get; set; }
public DataTemplate MergedAccountTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
if (item is MergedAccountProviderDetailViewModel)
return MergedAccountTemplate;
else
return RootAccountTemplate;
}
}
}

View File

@@ -2,20 +2,21 @@
using Windows.UI.Xaml.Controls;
using Wino.Core.ViewModels.Data;
namespace Wino.Selectors;
public partial class AccountReorderTemplateSelector : DataTemplateSelector
namespace Wino.Selectors
{
public DataTemplate MergedAccountReorderTemplate { get; set; }
public DataTemplate RootAccountReorderTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
public partial class AccountReorderTemplateSelector : DataTemplateSelector
{
if (item is MergedAccountProviderDetailViewModel)
{
return MergedAccountReorderTemplate;
}
public DataTemplate MergedAccountReorderTemplate { get; set; }
public DataTemplate RootAccountReorderTemplate { get; set; }
return RootAccountReorderTemplate;
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
if (item is MergedAccountProviderDetailViewModel)
{
return MergedAccountReorderTemplate;
}
return RootAccountReorderTemplate;
}
}
}

View File

@@ -2,17 +2,18 @@
using Windows.UI.Xaml.Controls;
using Wino.Mail.ViewModels.Data;
namespace Wino.Selectors;
public partial class MailItemContainerStyleSelector : StyleSelector
namespace Wino.Selectors
{
public Style Thread { get; set; }
protected override Style SelectStyleCore(object item, DependencyObject container)
public partial class MailItemContainerStyleSelector : StyleSelector
{
if (item is ThreadMailItemViewModel)
return Thread;
else
return base.SelectStyleCore(item, container);
public Style Thread { get; set; }
protected override Style SelectStyleCore(object item, DependencyObject container)
{
if (item is ThreadMailItemViewModel)
return Thread;
else
return base.SelectStyleCore(item, container);
}
}
}

View File

@@ -2,32 +2,33 @@
using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Enums;
namespace Wino.Selectors;
/// <summary>
/// Template selector for previewing mail item display modes in Settings->Personalization page.
/// </summary>
public partial class MailItemDisplayModePreviewTemplateSelector : DataTemplateSelector
namespace Wino.Selectors
{
public DataTemplate CompactTemplate { get; set; }
public DataTemplate MediumTemplate { get; set; }
public DataTemplate SpaciousTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
/// <summary>
/// Template selector for previewing mail item display modes in Settings->Personalization page.
/// </summary>
public partial class MailItemDisplayModePreviewTemplateSelector : DataTemplateSelector
{
if (item is MailListDisplayMode mode)
{
switch (mode)
{
case MailListDisplayMode.Spacious:
return SpaciousTemplate;
case MailListDisplayMode.Medium:
return MediumTemplate;
case MailListDisplayMode.Compact:
return CompactTemplate;
}
}
public DataTemplate CompactTemplate { get; set; }
public DataTemplate MediumTemplate { get; set; }
public DataTemplate SpaciousTemplate { get; set; }
return base.SelectTemplateCore(item, container);
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
if (item is MailListDisplayMode mode)
{
switch (mode)
{
case MailListDisplayMode.Spacious:
return SpaciousTemplate;
case MailListDisplayMode.Medium:
return MediumTemplate;
case MailListDisplayMode.Compact:
return CompactTemplate;
}
}
return base.SelectTemplateCore(item, container);
}
}
}

View File

@@ -2,20 +2,21 @@
using Windows.UI.Xaml.Controls;
using Wino.Mail.ViewModels.Data;
namespace Wino.Selectors;
public partial class MailItemDisplaySelector : DataTemplateSelector
namespace Wino.Selectors
{
public DataTemplate SingleMailItemTemplate { get; set; }
public DataTemplate ThreadMailItemTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
public partial class MailItemDisplaySelector : DataTemplateSelector
{
if (item is MailItemViewModel)
return SingleMailItemTemplate;
else if (item is ThreadMailItemViewModel)
return ThreadMailItemTemplate;
public DataTemplate SingleMailItemTemplate { get; set; }
public DataTemplate ThreadMailItemTemplate { get; set; }
return base.SelectTemplateCore(item, container);
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
if (item is MailItemViewModel)
return SingleMailItemTemplate;
else if (item is ThreadMailItemViewModel)
return ThreadMailItemTemplate;
return base.SelectTemplateCore(item, container);
}
}
}

View File

@@ -20,187 +20,188 @@ using Wino.Mail.Dialogs;
using Wino.Messaging.Server;
using Wino.Messaging.UI;
namespace Wino.Services;
public class DialogService : DialogServiceBase, IMailDialogService
namespace Wino.Services
{
public DialogService(IThemeService themeService,
IConfigurationService configurationService,
IApplicationResourceManager<ResourceDictionary> applicationResourceManager) : base(themeService, configurationService, applicationResourceManager)
public class DialogService : DialogServiceBase, IMailDialogService
{
}
public override IAccountCreationDialog GetAccountCreationDialog(AccountCreationDialogResult accountCreationDialogResult)
{
if (accountCreationDialogResult.SpecialImapProviderDetails == null)
public DialogService(IThemeService themeService,
IConfigurationService configurationService,
IApplicationResourceManager<ResourceDictionary> applicationResourceManager) : base(themeService, configurationService, applicationResourceManager)
{
if (accountCreationDialogResult.ProviderType == MailProviderType.IMAP4)
}
public override IAccountCreationDialog GetAccountCreationDialog(AccountCreationDialogResult accountCreationDialogResult)
{
if (accountCreationDialogResult.SpecialImapProviderDetails == null)
{
return new NewImapSetupDialog
if (accountCreationDialogResult.ProviderType == MailProviderType.IMAP4)
{
return new NewImapSetupDialog
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
}
else
{
return base.GetAccountCreationDialog(accountCreationDialogResult);
}
}
else
{
// Special IMAP provider like iCloud or Yahoo.
return base.GetAccountCreationDialog(accountCreationDialogResult);
}
}
public async Task<MailAccount> ShowEditAccountDialogAsync(MailAccount account)
{
var editAccountDialog = new AccountEditDialog(account)
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(editAccountDialog);
return editAccountDialog.IsSaved ? editAccountDialog.Account : null;
}
public async Task<ICreateAccountAliasDialog> ShowCreateAccountAliasDialogAsync()
{
var createAccountAliasDialog = new CreateAccountAliasDialog()
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(createAccountAliasDialog);
return createAccountAliasDialog;
}
public async Task HandleSystemFolderConfigurationDialogAsync(Guid accountId, IFolderService folderService)
{
try
{
var configurableFolder = await folderService.GetFoldersAsync(accountId);
var systemFolderConfigurationDialog = new SystemFolderConfigurationDialog(configurableFolder)
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(systemFolderConfigurationDialog);
var configuration = systemFolderConfigurationDialog.Configuration;
if (configuration != null)
{
await folderService.UpdateSystemFolderConfigurationAsync(accountId, configuration);
InfoBarMessage(Translator.SystemFolderConfigSetupSuccess_Title, Translator.SystemFolderConfigSetupSuccess_Message, InfoBarMessageType.Success);
WeakReferenceMessenger.Default.Send(new AccountFolderConfigurationUpdated(accountId));
var options = new MailSynchronizationOptions()
{
AccountId = accountId,
Type = MailSynchronizationType.FullFolders,
};
WeakReferenceMessenger.Default.Send(new NewMailSynchronizationRequested(options, SynchronizationSource.Client));
}
}
catch (Exception ex)
{
InfoBarMessage(Translator.Error_FailedToSetupSystemFolders_Title, ex.Message, InfoBarMessageType.Error);
}
}
public async Task<IMailItemFolder> ShowMoveMailFolderDialogAsync(List<IMailItemFolder> availableFolders)
{
var moveDialog = new MoveMailDialog(availableFolders)
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(moveDialog);
return moveDialog.SelectedFolder;
}
public async Task<IMailItemFolder> PickFolderAsync(Guid accountId, PickFolderReason reason, IFolderService folderService)
{
var allFolders = await folderService.GetFolderStructureForAccountAsync(accountId, true);
return await ShowMoveMailFolderDialogAsync(allFolders.Folders);
}
public Task<bool> ShowHardDeleteConfirmationAsync()
=> ShowWinoCustomMessageDialogAsync(Translator.DialogMessage_HardDeleteConfirmationMessage,
Translator.DialogMessage_HardDeleteConfirmationTitle,
Translator.Buttons_Yes,
WinoCustomMessageDialogIcon.Warning,
Translator.Buttons_No);
public async Task<MailAccount> ShowAccountPickerDialogAsync(List<MailAccount> availableAccounts)
{
var accountPicker = new AccountPickerDialog(availableAccounts)
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(accountPicker);
return accountPicker.PickedAccount;
}
public async Task<AccountSignature> ShowSignatureEditorDialog(AccountSignature signatureModel = null)
{
SignatureEditorDialog signatureEditorDialog;
if (signatureModel != null)
{
signatureEditorDialog = new SignatureEditorDialog(signatureModel)
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
}
else
{
return base.GetAccountCreationDialog(accountCreationDialogResult);
}
}
else
{
// Special IMAP provider like iCloud or Yahoo.
return base.GetAccountCreationDialog(accountCreationDialogResult);
}
}
public async Task<MailAccount> ShowEditAccountDialogAsync(MailAccount account)
{
var editAccountDialog = new AccountEditDialog(account)
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(editAccountDialog);
return editAccountDialog.IsSaved ? editAccountDialog.Account : null;
}
public async Task<ICreateAccountAliasDialog> ShowCreateAccountAliasDialogAsync()
{
var createAccountAliasDialog = new CreateAccountAliasDialog()
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(createAccountAliasDialog);
return createAccountAliasDialog;
}
public async Task HandleSystemFolderConfigurationDialogAsync(Guid accountId, IFolderService folderService)
{
try
{
var configurableFolder = await folderService.GetFoldersAsync(accountId);
var systemFolderConfigurationDialog = new SystemFolderConfigurationDialog(configurableFolder)
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(systemFolderConfigurationDialog);
var configuration = systemFolderConfigurationDialog.Configuration;
if (configuration != null)
{
await folderService.UpdateSystemFolderConfigurationAsync(accountId, configuration);
InfoBarMessage(Translator.SystemFolderConfigSetupSuccess_Title, Translator.SystemFolderConfigSetupSuccess_Message, InfoBarMessageType.Success);
WeakReferenceMessenger.Default.Send(new AccountFolderConfigurationUpdated(accountId));
var options = new MailSynchronizationOptions()
signatureEditorDialog = new SignatureEditorDialog()
{
AccountId = accountId,
Type = MailSynchronizationType.FullFolders,
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
WeakReferenceMessenger.Default.Send(new NewMailSynchronizationRequested(options, SynchronizationSource.Client));
}
var result = await HandleDialogPresentationAsync(signatureEditorDialog);
return result == ContentDialogResult.Primary ? signatureEditorDialog.Result : null;
}
catch (Exception ex)
public async Task ShowMessageSourceDialogAsync(string messageSource)
{
InfoBarMessage(Translator.Error_FailedToSetupSystemFolders_Title, ex.Message, InfoBarMessageType.Error);
var dialog = new MessageSourceDialog()
{
MessageSource = messageSource,
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(dialog);
if(dialog.Copied)
InfoBarMessage(Translator.ClipboardTextCopied_Title, string.Format(Translator.ClipboardTextCopied_Message, Translator.MessageSourceDialog_Title), InfoBarMessageType.Information);
}
}
public async Task<IMailItemFolder> ShowMoveMailFolderDialogAsync(List<IMailItemFolder> availableFolders)
{
var moveDialog = new MoveMailDialog(availableFolders)
public async Task ShowAccountReorderDialogAsync(ObservableCollection<IAccountProviderDetailViewModel> availableAccounts)
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(moveDialog);
return moveDialog.SelectedFolder;
}
public async Task<IMailItemFolder> PickFolderAsync(Guid accountId, PickFolderReason reason, IFolderService folderService)
{
var allFolders = await folderService.GetFolderStructureForAccountAsync(accountId, true);
return await ShowMoveMailFolderDialogAsync(allFolders.Folders);
}
public Task<bool> ShowHardDeleteConfirmationAsync()
=> ShowWinoCustomMessageDialogAsync(Translator.DialogMessage_HardDeleteConfirmationMessage,
Translator.DialogMessage_HardDeleteConfirmationTitle,
Translator.Buttons_Yes,
WinoCustomMessageDialogIcon.Warning,
Translator.Buttons_No);
public async Task<MailAccount> ShowAccountPickerDialogAsync(List<MailAccount> availableAccounts)
{
var accountPicker = new AccountPickerDialog(availableAccounts)
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(accountPicker);
return accountPicker.PickedAccount;
}
public async Task<AccountSignature> ShowSignatureEditorDialog(AccountSignature signatureModel = null)
{
SignatureEditorDialog signatureEditorDialog;
if (signatureModel != null)
{
signatureEditorDialog = new SignatureEditorDialog(signatureModel)
var accountReorderDialog = new AccountReorderDialog(availableAccounts)
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(accountReorderDialog);
}
else
{
signatureEditorDialog = new SignatureEditorDialog()
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
}
var result = await HandleDialogPresentationAsync(signatureEditorDialog);
return result == ContentDialogResult.Primary ? signatureEditorDialog.Result : null;
}
public async Task ShowMessageSourceDialogAsync(string messageSource)
{
var dialog = new MessageSourceDialog()
{
MessageSource = messageSource,
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(dialog);
if(dialog.Copied)
InfoBarMessage(Translator.ClipboardTextCopied_Title, string.Format(Translator.ClipboardTextCopied_Message, Translator.MessageSourceDialog_Title), InfoBarMessageType.Information);
}
public async Task ShowAccountReorderDialogAsync(ObservableCollection<IAccountProviderDetailViewModel> availableAccounts)
{
var accountReorderDialog = new AccountReorderDialog(availableAccounts)
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(accountReorderDialog);
}
}

View File

@@ -1,31 +1,32 @@
using Wino.Core.Domain.Interfaces;
namespace Wino.Services;
public class MailAuthenticatorConfiguration : IAuthenticatorConfig
namespace Wino.Services
{
public string OutlookAuthenticatorClientId => "b19c2035-d740-49ff-b297-de6ec561b208";
public string[] OutlookScope => new string[]
public class MailAuthenticatorConfiguration : IAuthenticatorConfig
{
"email",
"mail.readwrite",
"offline_access",
"mail.send",
"Mail.Send.Shared",
"Mail.ReadWrite.Shared",
"User.Read"
};
public string OutlookAuthenticatorClientId => "b19c2035-d740-49ff-b297-de6ec561b208";
public string GmailAuthenticatorClientId => "973025879644-s7b4ur9p3rlgop6a22u7iuptdc0brnrn.apps.googleusercontent.com";
public string[] OutlookScope => new string[]
{
"email",
"mail.readwrite",
"offline_access",
"mail.send",
"Mail.Send.Shared",
"Mail.ReadWrite.Shared",
"User.Read"
};
public string[] GmailScope => new string[]
{
"https://mail.google.com/",
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/gmail.labels",
"https://www.googleapis.com/auth/userinfo.email"
};
public string GmailAuthenticatorClientId => "973025879644-s7b4ur9p3rlgop6a22u7iuptdc0brnrn.apps.googleusercontent.com";
public string GmailTokenStoreIdentifier => "WinoMailGmailTokenStore";
public string[] GmailScope => new string[]
{
"https://mail.google.com/",
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/gmail.labels",
"https://www.googleapis.com/auth/userinfo.email"
};
public string GmailTokenStoreIdentifier => "WinoMailGmailTokenStore";
}
}

View File

@@ -15,147 +15,148 @@ using Wino.Views;
using Wino.Views.Account;
using Wino.Views.Settings;
namespace Wino.Services;
public class NavigationService : NavigationServiceBase, INavigationService
namespace Wino.Services
{
private readonly IStatePersistanceService _statePersistanceService;
private WinoPage[] _renderingPageTypes = new WinoPage[]
public class NavigationService : NavigationServiceBase, INavigationService
{
WinoPage.MailRenderingPage,
WinoPage.ComposePage
};
private readonly IStatePersistanceService _statePersistanceService;
public NavigationService(IStatePersistanceService statePersistanceService)
{
_statePersistanceService = statePersistanceService;
}
public Type GetPageType(WinoPage winoPage)
{
return winoPage switch
private WinoPage[] _renderingPageTypes = new WinoPage[]
{
WinoPage.None => null,
WinoPage.IdlePage => typeof(IdlePage),
WinoPage.AccountDetailsPage => typeof(AccountDetailsPage),
WinoPage.MergedAccountDetailsPage => typeof(MergedAccountDetailsPage),
WinoPage.AccountManagementPage => typeof(AccountManagementPage),
WinoPage.ManageAccountsPage => typeof(ManageAccountsPage),
WinoPage.SignatureManagementPage => typeof(SignatureManagementPage),
WinoPage.AboutPage => typeof(AboutPage),
WinoPage.PersonalizationPage => typeof(PersonalizationPage),
WinoPage.MessageListPage => typeof(MessageListPage),
WinoPage.ReadComposePanePage => typeof(ReadComposePanePage),
WinoPage.MailRenderingPage => typeof(MailRenderingPage),
WinoPage.ComposePage => typeof(ComposePage),
WinoPage.MailListPage => typeof(MailListPage),
WinoPage.SettingsPage => typeof(SettingsPage),
WinoPage.WelcomePage => typeof(WelcomePage),
WinoPage.SettingOptionsPage => typeof(SettingOptionsPage),
WinoPage.AppPreferencesPage => typeof(AppPreferencesPage),
WinoPage.AliasManagementPage => typeof(AliasManagementPage),
WinoPage.LanguageTimePage => typeof(LanguageTimePage),
_ => null,
WinoPage.MailRenderingPage,
WinoPage.ComposePage
};
}
public Frame GetCoreFrame(NavigationReferenceFrame frameType)
{
if (Window.Current.Content is Frame appFrame)
return WinoVisualTreeHelper.GetChildObject<Frame>(appFrame.Content as UIElement, frameType.ToString());
return null;
}
public bool Navigate(WinoPage page,
object parameter = null,
NavigationReferenceFrame frame = NavigationReferenceFrame.ShellFrame,
NavigationTransitionType transition = NavigationTransitionType.None)
{
var pageType = GetPageType(page);
Frame shellFrame = GetCoreFrame(NavigationReferenceFrame.ShellFrame);
_statePersistanceService.IsReadingMail = _renderingPageTypes.Contains(page);
if (shellFrame != null)
public NavigationService(IStatePersistanceService statePersistanceService)
{
var currentFrameType = GetCurrentFrameType(ref shellFrame);
bool isMailListingPageActive = currentFrameType != null && currentFrameType == typeof(MailListPage);
// Active page is mail list page and we are refreshing the folder.
if (isMailListingPageActive && currentFrameType == pageType && parameter is NavigateMailFolderEventArgs folderNavigationArgs)
{
// No need for new navigation, just refresh the folder.
WeakReferenceMessenger.Default.Send(new ActiveMailFolderChangedEvent(folderNavigationArgs.BaseFolderMenuItem, folderNavigationArgs.FolderInitLoadAwaitTask));
WeakReferenceMessenger.Default.Send(new DisposeRenderingFrameRequested());
return true;
}
var transitionInfo = GetNavigationTransitionInfo(transition);
// This page must be opened in the Frame placed in MailListingPage.
if (isMailListingPageActive && frame == NavigationReferenceFrame.RenderingFrame)
{
var listingFrame = GetCoreFrame(NavigationReferenceFrame.RenderingFrame);
if (listingFrame == null) return false;
// Active page is mail list page and we are opening a mail item.
// No navigation needed, just refresh the rendered mail item.
if (listingFrame.Content != null
&& listingFrame.Content.GetType() == GetPageType(WinoPage.MailRenderingPage)
&& parameter is MailItemViewModel mailItemViewModel
&& page != WinoPage.ComposePage)
{
WeakReferenceMessenger.Default.Send(new NewMailItemRenderingRequestedEvent(mailItemViewModel));
}
else if (listingFrame.Content != null
&& listingFrame.Content.GetType() == GetPageType(WinoPage.IdlePage)
&& pageType == typeof(IdlePage))
{
// Idle -> Idle navigation. Ignore.
return true;
}
else
{
listingFrame.Navigate(pageType, parameter, transitionInfo);
}
return true;
}
if ((currentFrameType != null && currentFrameType != pageType) || currentFrameType == null)
{
return shellFrame.Navigate(pageType, parameter, transitionInfo);
}
_statePersistanceService = statePersistanceService;
}
return false;
public Type GetPageType(WinoPage winoPage)
{
return winoPage switch
{
WinoPage.None => null,
WinoPage.IdlePage => typeof(IdlePage),
WinoPage.AccountDetailsPage => typeof(AccountDetailsPage),
WinoPage.MergedAccountDetailsPage => typeof(MergedAccountDetailsPage),
WinoPage.AccountManagementPage => typeof(AccountManagementPage),
WinoPage.ManageAccountsPage => typeof(ManageAccountsPage),
WinoPage.SignatureManagementPage => typeof(SignatureManagementPage),
WinoPage.AboutPage => typeof(AboutPage),
WinoPage.PersonalizationPage => typeof(PersonalizationPage),
WinoPage.MessageListPage => typeof(MessageListPage),
WinoPage.ReadComposePanePage => typeof(ReadComposePanePage),
WinoPage.MailRenderingPage => typeof(MailRenderingPage),
WinoPage.ComposePage => typeof(ComposePage),
WinoPage.MailListPage => typeof(MailListPage),
WinoPage.SettingsPage => typeof(SettingsPage),
WinoPage.WelcomePage => typeof(WelcomePage),
WinoPage.SettingOptionsPage => typeof(SettingOptionsPage),
WinoPage.AppPreferencesPage => typeof(AppPreferencesPage),
WinoPage.AliasManagementPage => typeof(AliasManagementPage),
WinoPage.LanguageTimePage => typeof(LanguageTimePage),
_ => null,
};
}
public Frame GetCoreFrame(NavigationReferenceFrame frameType)
{
if (Window.Current.Content is Frame appFrame)
return WinoVisualTreeHelper.GetChildObject<Frame>(appFrame.Content as UIElement, frameType.ToString());
return null;
}
public bool Navigate(WinoPage page,
object parameter = null,
NavigationReferenceFrame frame = NavigationReferenceFrame.ShellFrame,
NavigationTransitionType transition = NavigationTransitionType.None)
{
var pageType = GetPageType(page);
Frame shellFrame = GetCoreFrame(NavigationReferenceFrame.ShellFrame);
_statePersistanceService.IsReadingMail = _renderingPageTypes.Contains(page);
if (shellFrame != null)
{
var currentFrameType = GetCurrentFrameType(ref shellFrame);
bool isMailListingPageActive = currentFrameType != null && currentFrameType == typeof(MailListPage);
// Active page is mail list page and we are refreshing the folder.
if (isMailListingPageActive && currentFrameType == pageType && parameter is NavigateMailFolderEventArgs folderNavigationArgs)
{
// No need for new navigation, just refresh the folder.
WeakReferenceMessenger.Default.Send(new ActiveMailFolderChangedEvent(folderNavigationArgs.BaseFolderMenuItem, folderNavigationArgs.FolderInitLoadAwaitTask));
WeakReferenceMessenger.Default.Send(new DisposeRenderingFrameRequested());
return true;
}
var transitionInfo = GetNavigationTransitionInfo(transition);
// This page must be opened in the Frame placed in MailListingPage.
if (isMailListingPageActive && frame == NavigationReferenceFrame.RenderingFrame)
{
var listingFrame = GetCoreFrame(NavigationReferenceFrame.RenderingFrame);
if (listingFrame == null) return false;
// Active page is mail list page and we are opening a mail item.
// No navigation needed, just refresh the rendered mail item.
if (listingFrame.Content != null
&& listingFrame.Content.GetType() == GetPageType(WinoPage.MailRenderingPage)
&& parameter is MailItemViewModel mailItemViewModel
&& page != WinoPage.ComposePage)
{
WeakReferenceMessenger.Default.Send(new NewMailItemRenderingRequestedEvent(mailItemViewModel));
}
else if (listingFrame.Content != null
&& listingFrame.Content.GetType() == GetPageType(WinoPage.IdlePage)
&& pageType == typeof(IdlePage))
{
// Idle -> Idle navigation. Ignore.
return true;
}
else
{
listingFrame.Navigate(pageType, parameter, transitionInfo);
}
return true;
}
if ((currentFrameType != null && currentFrameType != pageType) || currentFrameType == null)
{
return shellFrame.Navigate(pageType, parameter, transitionInfo);
}
}
return false;
}
public void GoBack() => throw new NotImplementedException("GoBack method is not implemented in Wino Mail.");
// Standalone EML viewer.
//public void NavigateRendering(MimeMessageInformation mimeMessageInformation, NavigationTransitionType transition = NavigationTransitionType.None)
//{
// if (mimeMessageInformation == null)
// throw new ArgumentException("MimeMessage cannot be null.");
// Navigate(WinoPage.MailRenderingPage, mimeMessageInformation, NavigationReferenceFrame.RenderingFrame, transition);
//}
//// Mail item view model clicked handler.
//public void NavigateRendering(IMailItem mailItem, NavigationTransitionType transition = NavigationTransitionType.None)
//{
// if (mailItem is MailItemViewModel mailItemViewModel)
// Navigate(WinoPage.MailRenderingPage, mailItemViewModel, NavigationReferenceFrame.RenderingFrame, transition);
// else
// throw new ArgumentException("MailItem must be of type MailItemViewModel.");
//}
//public void NavigateFolder(NavigateMailFolderEventArgs args)
// => Navigate(WinoPage.MailListPage, args, NavigationReferenceFrame.ShellFrame);
}
public void GoBack() => throw new NotImplementedException("GoBack method is not implemented in Wino Mail.");
// Standalone EML viewer.
//public void NavigateRendering(MimeMessageInformation mimeMessageInformation, NavigationTransitionType transition = NavigationTransitionType.None)
//{
// if (mimeMessageInformation == null)
// throw new ArgumentException("MimeMessage cannot be null.");
// Navigate(WinoPage.MailRenderingPage, mimeMessageInformation, NavigationReferenceFrame.RenderingFrame, transition);
//}
//// Mail item view model clicked handler.
//public void NavigateRendering(IMailItem mailItem, NavigationTransitionType transition = NavigationTransitionType.None)
//{
// if (mailItem is MailItemViewModel mailItemViewModel)
// Navigate(WinoPage.MailRenderingPage, mailItemViewModel, NavigationReferenceFrame.RenderingFrame, transition);
// else
// throw new ArgumentException("MailItem must be of type MailItemViewModel.");
//}
//public void NavigateFolder(NavigateMailFolderEventArgs args)
// => Navigate(WinoPage.MailListPage, args, NavigationReferenceFrame.ShellFrame);
}

View File

@@ -4,31 +4,32 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Accounts;
namespace Wino.Mail.Services;
/// <summary>
/// Service that is returning available provider details.
/// </summary>
public class ProviderService : IProviderService
namespace Wino.Mail.Services
{
public IProviderDetail GetProviderDetail(MailProviderType type)
/// <summary>
/// Service that is returning available provider details.
/// </summary>
public class ProviderService : IProviderService
{
var details = GetAvailableProviders();
return details.FirstOrDefault(a => a.Type == type);
}
public List<IProviderDetail> GetAvailableProviders()
{
var providerList = new List<IProviderDetail>
public IProviderDetail GetProviderDetail(MailProviderType type)
{
new ProviderDetail(MailProviderType.Outlook, SpecialImapProvider.None),
new ProviderDetail(MailProviderType.Gmail, SpecialImapProvider.None),
new ProviderDetail(MailProviderType.IMAP4, SpecialImapProvider.iCloud),
new ProviderDetail(MailProviderType.IMAP4, SpecialImapProvider.Yahoo),
new ProviderDetail(MailProviderType.IMAP4, SpecialImapProvider.None)
};
var details = GetAvailableProviders();
return providerList;
return details.FirstOrDefault(a => a.Type == type);
}
public List<IProviderDetail> GetAvailableProviders()
{
var providerList = new List<IProviderDetail>
{
new ProviderDetail(MailProviderType.Outlook, SpecialImapProvider.None),
new ProviderDetail(MailProviderType.Gmail, SpecialImapProvider.None),
new ProviderDetail(MailProviderType.IMAP4, SpecialImapProvider.iCloud),
new ProviderDetail(MailProviderType.IMAP4, SpecialImapProvider.Yahoo),
new ProviderDetail(MailProviderType.IMAP4, SpecialImapProvider.None)
};
return providerList;
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,11 +1,12 @@
using Windows.UI.Xaml;
namespace Wino.Styles;
partial class WinoExpanderStyle : ResourceDictionary
namespace Wino.Styles
{
public WinoExpanderStyle()
partial class WinoExpanderStyle : ResourceDictionary
{
InitializeComponent();
public WinoExpanderStyle()
{
InitializeComponent();
}
}
}

View File

@@ -1,11 +1,12 @@
using Wino.Views.Abstract;
namespace Wino.Views.Settings;
public sealed partial class AboutPage : AboutPageAbstract
namespace Wino.Views.Settings
{
public AboutPage()
public sealed partial class AboutPage : AboutPageAbstract
{
InitializeComponent();
public AboutPage()
{
InitializeComponent();
}
}
}

View File

@@ -1,8 +1,9 @@
using Wino.Core.UWP;
using Wino.Core.ViewModels;
namespace Wino.Views.Abstract;
public abstract class AboutPageAbstract : BasePage<AboutPageViewModel>
namespace Wino.Views.Abstract
{
public abstract class AboutPageAbstract : BasePage<AboutPageViewModel>
{
}
}

View File

@@ -1,8 +1,9 @@
using Wino.Core.UWP;
using Wino.Mail.ViewModels;
namespace Wino.Views.Abstract;
public abstract class AccountDetailsPageAbstract : BasePage<AccountDetailsPageViewModel>
namespace Wino.Views.Abstract
{
public abstract class AccountDetailsPageAbstract : BasePage<AccountDetailsPageViewModel>
{
}
}

View File

@@ -1,9 +1,10 @@
using Wino.Core.UWP;
using Wino.Mail.ViewModels;
namespace Wino.Views.Abstract;
public abstract class AccountManagementPageAbstract : BasePage<AccountManagementViewModel>
namespace Wino.Views.Abstract
{
public abstract class AccountManagementPageAbstract : BasePage<AccountManagementViewModel>
{
}
}

View File

@@ -1,6 +1,7 @@
using Wino.Core.UWP;
using Wino.Mail.ViewModels;
namespace Wino.Views.Abstract;
public abstract class AliasManagementPageAbstract : BasePage<AliasManagementPageViewModel> { }
namespace Wino.Views.Abstract
{
public abstract class AliasManagementPageAbstract : BasePage<AliasManagementPageViewModel> { }
}

View File

@@ -1,6 +1,7 @@
using Wino.Core.UWP;
using Wino.Mail.ViewModels;
namespace Wino.Views.Abstract;
public abstract class AppPreferencesPageAbstract : BasePage<AppPreferencesPageViewModel> { }
namespace Wino.Views.Abstract
{
public abstract class AppPreferencesPageAbstract : BasePage<AppPreferencesPageViewModel> { }
}

View File

@@ -1,8 +1,9 @@
using Wino.Core.UWP;
using Wino.Mail.ViewModels;
namespace Wino.Views.Abstract;
public abstract class AppShellAbstract : BasePage<AppShellViewModel>
namespace Wino.Views.Abstract
{
public abstract class AppShellAbstract : BasePage<AppShellViewModel>
{
}
}

View File

@@ -1,8 +1,9 @@
using Wino.Core.UWP;
using Wino.Mail.ViewModels;
namespace Wino.Views.Abstract;
public abstract class ComposePageAbstract : BasePage<ComposePageViewModel>
namespace Wino.Views.Abstract
{
public abstract class ComposePageAbstract : BasePage<ComposePageViewModel>
{
}
}

View File

@@ -1,8 +1,9 @@
using Wino.Core.UWP;
using Wino.Mail.ViewModels;
namespace Wino.Views.Abstract;
public abstract class IdlePageAbstract : BasePage<IdlePageViewModel>
namespace Wino.Views.Abstract
{
public abstract class IdlePageAbstract : BasePage<IdlePageViewModel>
{
}
}

View File

@@ -1,6 +1,7 @@
using Wino.Core.UWP;
using Wino.Mail.ViewModels;
namespace Wino.Views.Abstract;
public abstract class LanguageTimePageAbstract : BasePage<LanguageTimePageViewModel> { }
namespace Wino.Views.Abstract
{
public abstract class LanguageTimePageAbstract : BasePage<LanguageTimePageViewModel> { }
}

View File

@@ -2,25 +2,26 @@
using Wino.Core.UWP;
using Wino.Mail.ViewModels;
namespace Wino.Views.Abstract;
public abstract class MailRenderingPageAbstract : BasePage<MailRenderingPageViewModel>
namespace Wino.Views.Abstract
{
public bool IsDarkEditor
public abstract class MailRenderingPageAbstract : BasePage<MailRenderingPageViewModel>
{
get { return (bool)GetValue(IsDarkEditorProperty); }
set { SetValue(IsDarkEditorProperty, value); }
}
public static readonly DependencyProperty IsDarkEditorProperty = DependencyProperty.Register(nameof(IsDarkEditor), typeof(bool), typeof(MailRenderingPageAbstract), new PropertyMetadata(false, OnIsComposerDarkModeChanged));
private static void OnIsComposerDarkModeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is MailRenderingPageAbstract page)
public bool IsDarkEditor
{
page.OnEditorThemeChanged();
get { return (bool)GetValue(IsDarkEditorProperty); }
set { SetValue(IsDarkEditorProperty, value); }
}
}
public virtual void OnEditorThemeChanged() { }
public static readonly DependencyProperty IsDarkEditorProperty = DependencyProperty.Register(nameof(IsDarkEditor), typeof(bool), typeof(MailRenderingPageAbstract), new PropertyMetadata(false, OnIsComposerDarkModeChanged));
private static void OnIsComposerDarkModeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is MailRenderingPageAbstract page)
{
page.OnEditorThemeChanged();
}
}
public virtual void OnEditorThemeChanged() { }
}
}

View File

@@ -1,8 +1,9 @@
using Wino.Core.UWP;
using Wino.Mail.ViewModels;
namespace Wino.Views.Abstract;
public abstract class MergedAccountDetailsPageAbstract : BasePage<MergedAccountDetailsPageViewModel>
namespace Wino.Views.Abstract
{
public abstract class MergedAccountDetailsPageAbstract : BasePage<MergedAccountDetailsPageViewModel>
{
}
}

View File

@@ -1,6 +1,7 @@
using Wino.Core.UWP;
using Wino.Mail.ViewModels;
namespace Wino.Views.Abstract;
public abstract class MessageListPageAbstract : BasePage<MessageListPageViewModel> { }
namespace Wino.Views.Abstract
{
public abstract class MessageListPageAbstract : BasePage<MessageListPageViewModel> { }
}

View File

@@ -1,8 +1,9 @@
using Wino.Core.ViewModels;
namespace Wino.Views.Abstract;
public abstract class PersonalizationPageAbstract : SettingsPageBase<PersonalizationPageViewModel>
namespace Wino.Views.Abstract
{
public abstract class PersonalizationPageAbstract : SettingsPageBase<PersonalizationPageViewModel>
{
}
}

View File

@@ -1,6 +1,7 @@
using Wino.Core.UWP;
using Wino.Mail.ViewModels;
namespace Wino.Views.Abstract;
public abstract class ReadComposePanePageAbstract : BasePage<ReadComposePanePageViewModel> { }
namespace Wino.Views.Abstract
{
public abstract class ReadComposePanePageAbstract : BasePage<ReadComposePanePageViewModel> { }
}

View File

@@ -1,6 +1,7 @@
using Wino.Core.UWP;
using Wino.Mail.ViewModels;
namespace Wino.Views.Abstract;
public abstract class SignatureManagementPageAbstract : BasePage<SignatureManagementPageViewModel> { }
namespace Wino.Views.Abstract
{
public abstract class SignatureManagementPageAbstract : BasePage<SignatureManagementPageViewModel> { }
}

View File

@@ -1,9 +1,10 @@
using Wino.Core.UWP;
using Wino.Mail.ViewModels;
namespace Wino.Views.Abstract;
public abstract class WelcomePageAbstract : BasePage<WelcomePageViewModel>
namespace Wino.Views.Abstract
{
public abstract class WelcomePageAbstract : BasePage<WelcomePageViewModel>
{
}
}

View File

@@ -2,28 +2,29 @@
using Wino.Core.Domain.Models.Folders;
using Wino.Views.Abstract;
namespace Wino.Views;
public sealed partial class AccountDetailsPage : AccountDetailsPageAbstract
namespace Wino.Views
{
public AccountDetailsPage()
public sealed partial class AccountDetailsPage : AccountDetailsPageAbstract
{
InitializeComponent();
}
private async void SyncFolderToggled(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
if (sender is CheckBox checkBox && checkBox.Tag is IMailItemFolder folder)
public AccountDetailsPage()
{
await ViewModel.FolderSyncToggledAsync(folder, checkBox.IsChecked.GetValueOrDefault());
InitializeComponent();
}
}
private async void UnreadBadgeCheckboxToggled(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
if (sender is CheckBox checkBox && checkBox.Tag is IMailItemFolder folder)
private async void SyncFolderToggled(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
await ViewModel.FolderShowUnreadToggled(folder, checkBox.IsChecked.GetValueOrDefault());
if (sender is CheckBox checkBox && checkBox.Tag is IMailItemFolder folder)
{
await ViewModel.FolderSyncToggledAsync(folder, checkBox.IsChecked.GetValueOrDefault());
}
}
private async void UnreadBadgeCheckboxToggled(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
if (sender is CheckBox checkBox && checkBox.Tag is IMailItemFolder folder)
{
await ViewModel.FolderShowUnreadToggled(folder, checkBox.IsChecked.GetValueOrDefault());
}
}
}
}

View File

@@ -2,14 +2,15 @@
using Windows.UI.Xaml.Navigation;
using Wino.Views.Abstract;
namespace Wino.Views;
public sealed partial class AccountManagementPage : AccountManagementPageAbstract
namespace Wino.Views
{
public AccountManagementPage()
public sealed partial class AccountManagementPage : AccountManagementPageAbstract
{
InitializeComponent();
public AccountManagementPage()
{
InitializeComponent();
NavigationCacheMode = NavigationCacheMode.Enabled;
NavigationCacheMode = NavigationCacheMode.Enabled;
}
}
}

View File

@@ -1,12 +1,13 @@
using Wino.Views.Abstract;
namespace Wino.Views.Account;
public sealed partial class MergedAccountDetailsPage : MergedAccountDetailsPageAbstract
namespace Wino.Views.Account
{
public MergedAccountDetailsPage()
public sealed partial class MergedAccountDetailsPage : MergedAccountDetailsPageAbstract
{
InitializeComponent();
public MergedAccountDetailsPage()
{
InitializeComponent();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,12 @@
using Wino.Views.Abstract;
namespace Wino.Views;
public sealed partial class IdlePage : IdlePageAbstract
namespace Wino.Views
{
public IdlePage()
public sealed partial class IdlePage : IdlePageAbstract
{
InitializeComponent();
public IdlePage()
{
InitializeComponent();
}
}
}

View File

@@ -11,204 +11,205 @@ using Wino.Core.Domain.Models.AutoDiscovery;
using Wino.Messaging.Client.Mails;
namespace Wino.Views.ImapSetup;
public sealed partial class AdvancedImapSetupPage : Page
namespace Wino.Views.ImapSetup
{
public List<ImapAuthenticationMethodModel> AvailableAuthenticationMethods { get; } = new List<ImapAuthenticationMethodModel>()
public sealed partial class AdvancedImapSetupPage : Page
{
new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.Auto, Translator.ImapAuthenticationMethod_Auto),
new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.None, Translator.ImapAuthenticationMethod_None),
new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.NormalPassword, Translator.ImapAuthenticationMethod_Plain),
new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.EncryptedPassword, Translator.ImapAuthenticationMethod_EncryptedPassword),
new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.Ntlm, Translator.ImapAuthenticationMethod_Ntlm),
new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.CramMd5, Translator.ImapAuthenticationMethod_CramMD5),
new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.DigestMd5, Translator.ImapAuthenticationMethod_DigestMD5)
};
public List<ImapConnectionSecurityModel> AvailableConnectionSecurities { get; set; } = new List<ImapConnectionSecurityModel>()
{
new ImapConnectionSecurityModel(Core.Domain.Enums.ImapConnectionSecurity.Auto, Translator.ImapConnectionSecurity_Auto),
new ImapConnectionSecurityModel(Core.Domain.Enums.ImapConnectionSecurity.SslTls, Translator.ImapConnectionSecurity_SslTls),
new ImapConnectionSecurityModel(Core.Domain.Enums.ImapConnectionSecurity.StartTls, Translator.ImapConnectionSecurity_StartTls),
new ImapConnectionSecurityModel(Core.Domain.Enums.ImapConnectionSecurity.None, Translator.ImapConnectionSecurity_None)
};
public bool UseSameCredentialsForSending
{
get { return (bool)GetValue(UseSameCredentialsForSendingProperty); }
set { SetValue(UseSameCredentialsForSendingProperty, value); }
}
public static readonly DependencyProperty UseSameCredentialsForSendingProperty = DependencyProperty.Register(nameof(UseSameCredentialsForSending), typeof(bool), typeof(AdvancedImapSetupPage), new PropertyMetadata(true, OnUseSameCredentialsForSendingChanged));
public AdvancedImapSetupPage()
{
InitializeComponent();
NavigationCacheMode = NavigationCacheMode.Enabled;
}
private static void OnUseSameCredentialsForSendingChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is AdvancedImapSetupPage page)
public List<ImapAuthenticationMethodModel> AvailableAuthenticationMethods { get; } = new List<ImapAuthenticationMethodModel>()
{
page.UpdateOutgoingAuthenticationPanel();
}
}
private void UpdateOutgoingAuthenticationPanel()
{
if (UseSameCredentialsForSending)
{
OutgoingUsernameBox.Text = UsernameBox.Text;
OutgoingPasswordBox.Password = PasswordBox.Password;
}
else
{
OutgoingUsernameBox.Text = string.Empty;
OutgoingPasswordBox.Password = string.Empty;
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Don't override settings on back scenarios.
// User is trying to try again the same configuration.
if (e.NavigationMode == NavigationMode.Back) return;
// Connection is succesfull but error occurred.
// Imap and Smptp settings exists here at this point.
if (e.Parameter is AutoDiscoverySettings preDefinedSettings && preDefinedSettings.UserMinimalSettings != null)
{
// TODO: Auto discovery settings adjustments.
UsernameBox.Text = preDefinedSettings.UserMinimalSettings.Email;
AddressBox.Text = preDefinedSettings.UserMinimalSettings.Email;
DisplayNameBox.Text = preDefinedSettings.UserMinimalSettings.DisplayName;
PasswordBox.Password = preDefinedSettings.UserMinimalSettings.Password;
var serverInfo = preDefinedSettings.ToServerInformation();
IncomingServerBox.Text = serverInfo.IncomingServer;
IncomingServerPortBox.Text = serverInfo.IncomingServerPort;
OutgoingPasswordBox.Password = serverInfo.OutgoingServerPassword;
OutgoingServerPort.Text = serverInfo.OutgoingServerPort;
OutgoingUsernameBox.Text = serverInfo.OutgoingServerUsername;
UseSameCredentialsForSending = OutgoingUsernameBox.Text == UsernameBox.Text;
}
else if (e.Parameter is AutoDiscoveryMinimalSettings autoDiscoveryMinimalSettings)
{
// Auto discovery failed. Only minimal settings are passed.
UsernameBox.Text = autoDiscoveryMinimalSettings.Email;
AddressBox.Text = autoDiscoveryMinimalSettings.Email;
DisplayNameBox.Text = autoDiscoveryMinimalSettings.DisplayName;
PasswordBox.Password = autoDiscoveryMinimalSettings.Password;
}
}
private void CancelClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new ImapSetupDismissRequested(null));
private string GetServerWithoutPort(string server)
{
var splitted = server.Split(':');
if (splitted.Length > 1)
{
return splitted[0];
}
return server;
}
private void SignInClicked(object sender, RoutedEventArgs e)
{
var info = new CustomServerInformation()
{
IncomingServer = GetServerWithoutPort(IncomingServerBox.Text),
Id = Guid.NewGuid(),
IncomingServerPassword = PasswordBox.Password,
IncomingServerType = Core.Domain.Enums.CustomIncomingServerType.IMAP4,
IncomingServerUsername = UsernameBox.Text,
IncomingAuthenticationMethod = (IncomingAuthenticationMethod.SelectedItem as ImapAuthenticationMethodModel).ImapAuthenticationMethod,
IncomingServerSocketOption = (IncomingConnectionSecurity.SelectedItem as ImapConnectionSecurityModel).ImapConnectionSecurity,
IncomingServerPort = IncomingServerPortBox.Text,
OutgoingServer = GetServerWithoutPort(OutgoingServerBox.Text),
OutgoingServerPort = OutgoingServerPort.Text,
OutgoingServerPassword = OutgoingPasswordBox.Password,
OutgoingAuthenticationMethod = (OutgoingAuthenticationMethod.SelectedItem as ImapAuthenticationMethodModel).ImapAuthenticationMethod,
OutgoingServerSocketOption = (OutgoingConnectionSecurity.SelectedItem as ImapConnectionSecurityModel).ImapConnectionSecurity,
OutgoingServerUsername = OutgoingUsernameBox.Text,
ProxyServer = ProxyServerBox.Text,
ProxyServerPort = ProxyServerPortBox.Text,
Address = AddressBox.Text,
DisplayName = DisplayNameBox.Text,
MaxConcurrentClients = 5
new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.Auto, Translator.ImapAuthenticationMethod_Auto),
new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.None, Translator.ImapAuthenticationMethod_None),
new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.NormalPassword, Translator.ImapAuthenticationMethod_Plain),
new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.EncryptedPassword, Translator.ImapAuthenticationMethod_EncryptedPassword),
new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.Ntlm, Translator.ImapAuthenticationMethod_Ntlm),
new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.CramMd5, Translator.ImapAuthenticationMethod_CramMD5),
new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.DigestMd5, Translator.ImapAuthenticationMethod_DigestMD5)
};
if (UseSameCredentialsForSending)
public List<ImapConnectionSecurityModel> AvailableConnectionSecurities { get; set; } = new List<ImapConnectionSecurityModel>()
{
info.OutgoingServerUsername = info.IncomingServerUsername;
info.OutgoingServerPassword = info.IncomingServerPassword;
}
else
new ImapConnectionSecurityModel(Core.Domain.Enums.ImapConnectionSecurity.Auto, Translator.ImapConnectionSecurity_Auto),
new ImapConnectionSecurityModel(Core.Domain.Enums.ImapConnectionSecurity.SslTls, Translator.ImapConnectionSecurity_SslTls),
new ImapConnectionSecurityModel(Core.Domain.Enums.ImapConnectionSecurity.StartTls, Translator.ImapConnectionSecurity_StartTls),
new ImapConnectionSecurityModel(Core.Domain.Enums.ImapConnectionSecurity.None, Translator.ImapConnectionSecurity_None)
};
public bool UseSameCredentialsForSending
{
info.OutgoingServerUsername = OutgoingUsernameBox.Text;
info.OutgoingServerPassword = OutgoingPasswordBox.Password;
get { return (bool)GetValue(UseSameCredentialsForSendingProperty); }
set { SetValue(UseSameCredentialsForSendingProperty, value); }
}
WeakReferenceMessenger.Default.Send(new ImapSetupNavigationRequested(typeof(TestingImapConnectionPage), info));
}
public static readonly DependencyProperty UseSameCredentialsForSendingProperty = DependencyProperty.Register(nameof(UseSameCredentialsForSending), typeof(bool), typeof(AdvancedImapSetupPage), new PropertyMetadata(true, OnUseSameCredentialsForSendingChanged));
private void IncomingServerChanged(object sender, TextChangedEventArgs e)
{
if (sender is TextBox senderTextBox)
public AdvancedImapSetupPage()
{
var splitted = senderTextBox.Text.Split(':');
InitializeComponent();
NavigationCacheMode = NavigationCacheMode.Enabled;
}
private static void OnUseSameCredentialsForSendingChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is AdvancedImapSetupPage page)
{
page.UpdateOutgoingAuthenticationPanel();
}
}
private void UpdateOutgoingAuthenticationPanel()
{
if (UseSameCredentialsForSending)
{
OutgoingUsernameBox.Text = UsernameBox.Text;
OutgoingPasswordBox.Password = PasswordBox.Password;
}
else
{
OutgoingUsernameBox.Text = string.Empty;
OutgoingPasswordBox.Password = string.Empty;
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Don't override settings on back scenarios.
// User is trying to try again the same configuration.
if (e.NavigationMode == NavigationMode.Back) return;
// Connection is succesfull but error occurred.
// Imap and Smptp settings exists here at this point.
if (e.Parameter is AutoDiscoverySettings preDefinedSettings && preDefinedSettings.UserMinimalSettings != null)
{
// TODO: Auto discovery settings adjustments.
UsernameBox.Text = preDefinedSettings.UserMinimalSettings.Email;
AddressBox.Text = preDefinedSettings.UserMinimalSettings.Email;
DisplayNameBox.Text = preDefinedSettings.UserMinimalSettings.DisplayName;
PasswordBox.Password = preDefinedSettings.UserMinimalSettings.Password;
var serverInfo = preDefinedSettings.ToServerInformation();
IncomingServerBox.Text = serverInfo.IncomingServer;
IncomingServerPortBox.Text = serverInfo.IncomingServerPort;
OutgoingPasswordBox.Password = serverInfo.OutgoingServerPassword;
OutgoingServerPort.Text = serverInfo.OutgoingServerPort;
OutgoingUsernameBox.Text = serverInfo.OutgoingServerUsername;
UseSameCredentialsForSending = OutgoingUsernameBox.Text == UsernameBox.Text;
}
else if (e.Parameter is AutoDiscoveryMinimalSettings autoDiscoveryMinimalSettings)
{
// Auto discovery failed. Only minimal settings are passed.
UsernameBox.Text = autoDiscoveryMinimalSettings.Email;
AddressBox.Text = autoDiscoveryMinimalSettings.Email;
DisplayNameBox.Text = autoDiscoveryMinimalSettings.DisplayName;
PasswordBox.Password = autoDiscoveryMinimalSettings.Password;
}
}
private void CancelClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new ImapSetupDismissRequested(null));
private string GetServerWithoutPort(string server)
{
var splitted = server.Split(':');
if (splitted.Length > 1)
{
IncomingServerPortBox.Text = splitted[splitted.Length - 1];
return splitted[0];
}
return server;
}
}
private void OutgoingServerChanged(object sender, TextChangedEventArgs e)
{
if (sender is TextBox senderTextBox)
private void SignInClicked(object sender, RoutedEventArgs e)
{
var splitted = senderTextBox.Text.Split(':');
if (splitted.Length > 1)
var info = new CustomServerInformation()
{
OutgoingServerPort.Text = splitted[splitted.Length - 1];
IncomingServer = GetServerWithoutPort(IncomingServerBox.Text),
Id = Guid.NewGuid(),
IncomingServerPassword = PasswordBox.Password,
IncomingServerType = Core.Domain.Enums.CustomIncomingServerType.IMAP4,
IncomingServerUsername = UsernameBox.Text,
IncomingAuthenticationMethod = (IncomingAuthenticationMethod.SelectedItem as ImapAuthenticationMethodModel).ImapAuthenticationMethod,
IncomingServerSocketOption = (IncomingConnectionSecurity.SelectedItem as ImapConnectionSecurityModel).ImapConnectionSecurity,
IncomingServerPort = IncomingServerPortBox.Text,
OutgoingServer = GetServerWithoutPort(OutgoingServerBox.Text),
OutgoingServerPort = OutgoingServerPort.Text,
OutgoingServerPassword = OutgoingPasswordBox.Password,
OutgoingAuthenticationMethod = (OutgoingAuthenticationMethod.SelectedItem as ImapAuthenticationMethodModel).ImapAuthenticationMethod,
OutgoingServerSocketOption = (OutgoingConnectionSecurity.SelectedItem as ImapConnectionSecurityModel).ImapConnectionSecurity,
OutgoingServerUsername = OutgoingUsernameBox.Text,
ProxyServer = ProxyServerBox.Text,
ProxyServerPort = ProxyServerPortBox.Text,
Address = AddressBox.Text,
DisplayName = DisplayNameBox.Text,
MaxConcurrentClients = 5
};
if (UseSameCredentialsForSending)
{
info.OutgoingServerUsername = info.IncomingServerUsername;
info.OutgoingServerPassword = info.IncomingServerPassword;
}
else
{
info.OutgoingServerUsername = OutgoingUsernameBox.Text;
info.OutgoingServerPassword = OutgoingPasswordBox.Password;
}
WeakReferenceMessenger.Default.Send(new ImapSetupNavigationRequested(typeof(TestingImapConnectionPage), info));
}
private void IncomingServerChanged(object sender, TextChangedEventArgs e)
{
if (sender is TextBox senderTextBox)
{
var splitted = senderTextBox.Text.Split(':');
if (splitted.Length > 1)
{
IncomingServerPortBox.Text = splitted[splitted.Length - 1];
}
}
}
}
private void IncomingUsernameChanged(object sender, TextChangedEventArgs e)
{
if (UseSameCredentialsForSending)
private void OutgoingServerChanged(object sender, TextChangedEventArgs e)
{
OutgoingUsernameBox.Text = UsernameBox.Text;
if (sender is TextBox senderTextBox)
{
var splitted = senderTextBox.Text.Split(':');
if (splitted.Length > 1)
{
OutgoingServerPort.Text = splitted[splitted.Length - 1];
}
}
}
}
private void IncomingPasswordChanged(object sender, RoutedEventArgs e)
{
if (UseSameCredentialsForSending)
private void IncomingUsernameChanged(object sender, TextChangedEventArgs e)
{
OutgoingPasswordBox.Password = PasswordBox.Password;
if (UseSameCredentialsForSending)
{
OutgoingUsernameBox.Text = UsernameBox.Text;
}
}
private void IncomingPasswordChanged(object sender, RoutedEventArgs e)
{
if (UseSameCredentialsForSending)
{
OutgoingPasswordBox.Password = PasswordBox.Password;
}
}
}
}

View File

@@ -8,41 +8,42 @@ using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Interfaces;
using Wino.Messaging.Client.Mails;
namespace Wino.Views.ImapSetup;
public sealed partial class ImapConnectionFailedPage : Page
namespace Wino.Views.ImapSetup
{
private string _protocolLog;
private readonly IClipboardService _clipboardService = App.Current.Services.GetService<IClipboardService>();
private readonly IMailDialogService _dialogService = App.Current.Services.GetService<IMailDialogService>();
public ImapConnectionFailedPage()
public sealed partial class ImapConnectionFailedPage : Page
{
InitializeComponent();
}
private string _protocolLog;
private async void CopyProtocolLogButtonClicked(object sender, RoutedEventArgs e)
{
await _clipboardService.CopyClipboardAsync(_protocolLog);
private readonly IClipboardService _clipboardService = App.Current.Services.GetService<IClipboardService>();
private readonly IMailDialogService _dialogService = App.Current.Services.GetService<IMailDialogService>();
_dialogService.InfoBarMessage(Translator.ClipboardTextCopied_Title, string.Format(Translator.ClipboardTextCopied_Message, "Log"), Core.Domain.Enums.InfoBarMessageType.Information);
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (e.Parameter is ImapConnectionFailedPackage failedPackage)
public ImapConnectionFailedPage()
{
ConnectionFailedMessage.Text = failedPackage.ErrorMessage;
ProtocolLogGrid.Visibility = !string.IsNullOrEmpty(failedPackage.ProtocolLog) ? Visibility.Visible : Visibility.Collapsed;
_protocolLog = failedPackage.ProtocolLog;
InitializeComponent();
}
private async void CopyProtocolLogButtonClicked(object sender, RoutedEventArgs e)
{
await _clipboardService.CopyClipboardAsync(_protocolLog);
_dialogService.InfoBarMessage(Translator.ClipboardTextCopied_Title, string.Format(Translator.ClipboardTextCopied_Message, "Log"), Core.Domain.Enums.InfoBarMessageType.Information);
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (e.Parameter is ImapConnectionFailedPackage failedPackage)
{
ConnectionFailedMessage.Text = failedPackage.ErrorMessage;
ProtocolLogGrid.Visibility = !string.IsNullOrEmpty(failedPackage.ProtocolLog) ? Visibility.Visible : Visibility.Collapsed;
_protocolLog = failedPackage.ProtocolLog;
}
}
private void TryAgainClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new ImapSetupBackNavigationRequested());
private void CloseClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new ImapSetupDismissRequested());
}
private void TryAgainClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new ImapSetupBackNavigationRequested());
private void CloseClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new ImapSetupDismissRequested());
}

View File

@@ -15,15 +15,16 @@ using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
namespace Wino.Views.ImapSetup;
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class PreparingImapFoldersPage : Page
namespace Wino.Views.ImapSetup
{
public PreparingImapFoldersPage()
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class PreparingImapFoldersPage : Page
{
this.InitializeComponent();
public PreparingImapFoldersPage()
{
this.InitializeComponent();
}
}
}

View File

@@ -14,119 +14,120 @@ using Wino.Messaging.Client.Mails;
using Wino.Messaging.Server;
namespace Wino.Views.ImapSetup;
public sealed partial class TestingImapConnectionPage : Page
namespace Wino.Views.ImapSetup
{
private IWinoServerConnectionManager _winoServerConnectionManager = App.Current.Services.GetService<IWinoServerConnectionManager>();
private AutoDiscoverySettings autoDiscoverySettings;
private CustomServerInformation serverInformationToTest;
public TestingImapConnectionPage()
public sealed partial class TestingImapConnectionPage : Page
{
InitializeComponent();
}
private IWinoServerConnectionManager _winoServerConnectionManager = App.Current.Services.GetService<IWinoServerConnectionManager>();
private AutoDiscoverySettings autoDiscoverySettings;
private CustomServerInformation serverInformationToTest;
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// We can only go back to this page from failed connection page.
// We must go back once again in that case to actual setup dialog.
if (e.NavigationMode == NavigationMode.Back)
public TestingImapConnectionPage()
{
WeakReferenceMessenger.Default.Send(new ImapSetupBackNavigationRequested());
InitializeComponent();
}
else
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
// Test connection
base.OnNavigatedTo(e);
// Discovery settings are passed.
// Create server information out of the discovery settings.
if (e.Parameter is AutoDiscoverySettings parameterAutoDiscoverySettings)
// We can only go back to this page from failed connection page.
// We must go back once again in that case to actual setup dialog.
if (e.NavigationMode == NavigationMode.Back)
{
autoDiscoverySettings = parameterAutoDiscoverySettings;
serverInformationToTest = autoDiscoverySettings.ToServerInformation();
}
else if (e.Parameter is CustomServerInformation customServerInformation)
{
// Only server information is passed.
serverInformationToTest = customServerInformation;
}
// Make sure that certificate dialog must be present in case of SSL handshake fails.
await PerformTestAsync(allowSSLHandshake: false);
}
}
private async Task PerformTestAsync(bool allowSSLHandshake)
{
CertificateDialog.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
TestingConnectionPanel.Visibility = Windows.UI.Xaml.Visibility.Visible;
await Task.Delay(1000);
var testResultResponse = await _winoServerConnectionManager
.GetResponseAsync<ImapConnectivityTestResults, ImapConnectivityTestRequested>(new ImapConnectivityTestRequested(serverInformationToTest, allowSSLHandshake));
if (!testResultResponse.IsSuccess)
{
// Wino Server is connection is failed.
ReturnWithError(testResultResponse.Message);
}
else
{
var testResultData = testResultResponse.Data;
if (testResultData.IsSuccess)
{
// All success. Finish setup with validated server information.
ReturnWithSuccess();
WeakReferenceMessenger.Default.Send(new ImapSetupBackNavigationRequested());
}
else
{
// Check if certificate UI is required.
// Test connection
if (testResultData.IsCertificateUIRequired)
// Discovery settings are passed.
// Create server information out of the discovery settings.
if (e.Parameter is AutoDiscoverySettings parameterAutoDiscoverySettings)
{
// Certificate UI is required. Show certificate dialog.
autoDiscoverySettings = parameterAutoDiscoverySettings;
serverInformationToTest = autoDiscoverySettings.ToServerInformation();
}
else if (e.Parameter is CustomServerInformation customServerInformation)
{
// Only server information is passed.
serverInformationToTest = customServerInformation;
}
CertIssuer.Text = testResultData.CertificateIssuer;
CertValidFrom.Text = testResultData.CertificateValidFromDateString;
CertValidTo.Text = testResultData.CertificateExpirationDateString;
// Make sure that certificate dialog must be present in case of SSL handshake fails.
await PerformTestAsync(allowSSLHandshake: false);
}
}
TestingConnectionPanel.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
CertificateDialog.Visibility = Windows.UI.Xaml.Visibility.Visible;
private async Task PerformTestAsync(bool allowSSLHandshake)
{
CertificateDialog.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
TestingConnectionPanel.Visibility = Windows.UI.Xaml.Visibility.Visible;
await Task.Delay(1000);
var testResultResponse = await _winoServerConnectionManager
.GetResponseAsync<ImapConnectivityTestResults, ImapConnectivityTestRequested>(new ImapConnectivityTestRequested(serverInformationToTest, allowSSLHandshake));
if (!testResultResponse.IsSuccess)
{
// Wino Server is connection is failed.
ReturnWithError(testResultResponse.Message);
}
else
{
var testResultData = testResultResponse.Data;
if (testResultData.IsSuccess)
{
// All success. Finish setup with validated server information.
ReturnWithSuccess();
}
else
{
// Connection test failed. Show error dialog.
// Check if certificate UI is required.
var protocolLog = testResultData.FailureProtocolLog;
if (testResultData.IsCertificateUIRequired)
{
// Certificate UI is required. Show certificate dialog.
ReturnWithError(testResultData.FailedReason, protocolLog);
CertIssuer.Text = testResultData.CertificateIssuer;
CertValidFrom.Text = testResultData.CertificateValidFromDateString;
CertValidTo.Text = testResultData.CertificateExpirationDateString;
TestingConnectionPanel.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
CertificateDialog.Visibility = Windows.UI.Xaml.Visibility.Visible;
}
else
{
// Connection test failed. Show error dialog.
var protocolLog = testResultData.FailureProtocolLog;
ReturnWithError(testResultData.FailedReason, protocolLog);
}
}
}
}
}
private void ReturnWithError(string error, string protocolLog = "")
{
var failurePackage = new ImapConnectionFailedPackage(error, protocolLog, autoDiscoverySettings);
WeakReferenceMessenger.Default.Send(new ImapSetupBackNavigationRequested(typeof(ImapConnectionFailedPage), failurePackage));
}
private void ReturnWithError(string error, string protocolLog = "")
{
var failurePackage = new ImapConnectionFailedPackage(error, protocolLog, autoDiscoverySettings);
WeakReferenceMessenger.Default.Send(new ImapSetupBackNavigationRequested(typeof(ImapConnectionFailedPage), failurePackage));
}
private void ReturnWithSuccess()
=> WeakReferenceMessenger.Default.Send(new ImapSetupDismissRequested(serverInformationToTest));
private void ReturnWithSuccess()
=> WeakReferenceMessenger.Default.Send(new ImapSetupDismissRequested(serverInformationToTest));
private void DenyClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
=> ReturnWithError(Translator.IMAPSetupDialog_CertificateDenied, string.Empty);
private void DenyClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
=> ReturnWithError(Translator.IMAPSetupDialog_CertificateDenied, string.Empty);
private async void AllowClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
// Run the test again, but this time allow SSL handshake.
// Any authentication error will be shown to the user after this test.
private async void AllowClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
// Run the test again, but this time allow SSL handshake.
// Any authentication error will be shown to the user after this test.
await PerformTestAsync(allowSSLHandshake: true);
await PerformTestAsync(allowSSLHandshake: true);
}
}
}

View File

@@ -14,82 +14,83 @@ using Wino.Core.Domain.Models.AutoDiscovery;
using Wino.Messaging.Client.Mails;
namespace Wino.Views.ImapSetup;
public sealed partial class WelcomeImapSetupPage : Page
namespace Wino.Views.ImapSetup
{
private readonly IAutoDiscoveryService _autoDiscoveryService = App.Current.Services.GetService<IAutoDiscoveryService>();
public WelcomeImapSetupPage()
public sealed partial class WelcomeImapSetupPage : Page
{
InitializeComponent();
}
private readonly IAutoDiscoveryService _autoDiscoveryService = App.Current.Services.GetService<IAutoDiscoveryService>();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
AutoDiscoveryPanel.Visibility = Visibility.Collapsed;
MainSetupPanel.Visibility = Visibility.Visible;
if (e.Parameter is MailAccount accountProperties)
public WelcomeImapSetupPage()
{
DisplayNameBox.Text = accountProperties.Name;
InitializeComponent();
}
else if (e.Parameter is AccountCreationDialogResult creationDialogResult)
protected override void OnNavigatedTo(NavigationEventArgs e)
{
WeakReferenceMessenger.Default.Send(new ImapSetupNavigationRequested(typeof(TestingImapConnectionPage), creationDialogResult));
base.OnNavigatedTo(e);
AutoDiscoveryPanel.Visibility = Visibility.Collapsed;
MainSetupPanel.Visibility = Visibility.Visible;
if (e.Parameter is MailAccount accountProperties)
{
DisplayNameBox.Text = accountProperties.Name;
}
else if (e.Parameter is AccountCreationDialogResult creationDialogResult)
{
WeakReferenceMessenger.Default.Send(new ImapSetupNavigationRequested(typeof(TestingImapConnectionPage), creationDialogResult));
}
}
}
private async void SignInClicked(object sender, RoutedEventArgs e)
{
MainSetupPanel.Visibility = Visibility.Collapsed;
AutoDiscoveryPanel.Visibility = Visibility.Visible;
// Let users see the discovery message for a while...
await Task.Delay(1000);
var minimalSettings = new AutoDiscoveryMinimalSettings()
private async void SignInClicked(object sender, RoutedEventArgs e)
{
Password = PasswordBox.Password,
DisplayName = DisplayNameBox.Text,
Email = AddressBox.Text,
};
MainSetupPanel.Visibility = Visibility.Collapsed;
AutoDiscoveryPanel.Visibility = Visibility.Visible;
var discoverySettings = await _autoDiscoveryService.GetAutoDiscoverySettings(minimalSettings);
// Let users see the discovery message for a while...
if (discoverySettings == null)
{
// Couldn't find settings.
await Task.Delay(1000);
var failurePackage = new ImapConnectionFailedPackage(Translator.Exception_ImapAutoDiscoveryFailed, string.Empty, discoverySettings);
var minimalSettings = new AutoDiscoveryMinimalSettings()
{
Password = PasswordBox.Password,
DisplayName = DisplayNameBox.Text,
Email = AddressBox.Text,
};
WeakReferenceMessenger.Default.Send(new ImapSetupBackNavigationRequested(typeof(ImapConnectionFailedPage), failurePackage));
var discoverySettings = await _autoDiscoveryService.GetAutoDiscoverySettings(minimalSettings);
if (discoverySettings == null)
{
// Couldn't find settings.
var failurePackage = new ImapConnectionFailedPackage(Translator.Exception_ImapAutoDiscoveryFailed, string.Empty, discoverySettings);
WeakReferenceMessenger.Default.Send(new ImapSetupBackNavigationRequested(typeof(ImapConnectionFailedPage), failurePackage));
}
else
{
// Settings are found. Test the connection with the given password.
discoverySettings.UserMinimalSettings = minimalSettings;
WeakReferenceMessenger.Default.Send(new ImapSetupNavigationRequested(typeof(TestingImapConnectionPage), discoverySettings));
}
}
else
private void CancelClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new ImapSetupDismissRequested());
private void AdvancedConfigurationClicked(object sender, RoutedEventArgs e)
{
// Settings are found. Test the connection with the given password.
var latestMinimalSettings = new AutoDiscoveryMinimalSettings()
{
DisplayName = DisplayNameBox.Text,
Password = PasswordBox.Password,
Email = AddressBox.Text
};
discoverySettings.UserMinimalSettings = minimalSettings;
WeakReferenceMessenger.Default.Send(new ImapSetupNavigationRequested(typeof(TestingImapConnectionPage), discoverySettings));
WeakReferenceMessenger.Default.Send(new ImapSetupNavigationRequested(typeof(AdvancedImapSetupPage), latestMinimalSettings));
}
}
private void CancelClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new ImapSetupDismissRequested());
private void AdvancedConfigurationClicked(object sender, RoutedEventArgs e)
{
var latestMinimalSettings = new AutoDiscoveryMinimalSettings()
{
DisplayName = DisplayNameBox.Text,
Password = PasswordBox.Password,
Email = AddressBox.Text
};
WeakReferenceMessenger.Default.Send(new ImapSetupNavigationRequested(typeof(AdvancedImapSetupPage), latestMinimalSettings));
}
}

View File

@@ -27,467 +27,468 @@ using Wino.MenuFlyouts.Context;
using Wino.Messaging.Client.Mails;
using Wino.Views.Abstract;
namespace Wino.Views;
public sealed partial class MailListPage : MailListPageAbstract,
IRecipient<ClearMailSelectionsRequested>,
IRecipient<ActiveMailItemChangedEvent>,
IRecipient<SelectMailItemContainerEvent>,
IRecipient<DisposeRenderingFrameRequested>
namespace Wino.Views
{
private const double RENDERING_COLUMN_MIN_WIDTH = 375;
private IStatePersistanceService StatePersistenceService { get; } = App.Current.Services.GetService<IStatePersistanceService>();
private IKeyPressService KeyPressService { get; } = App.Current.Services.GetService<IKeyPressService>();
public MailListPage()
public sealed partial class MailListPage : MailListPageAbstract,
IRecipient<ClearMailSelectionsRequested>,
IRecipient<ActiveMailItemChangedEvent>,
IRecipient<SelectMailItemContainerEvent>,
IRecipient<DisposeRenderingFrameRequested>
{
InitializeComponent();
}
private const double RENDERING_COLUMN_MIN_WIDTH = 375;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
private IStatePersistanceService StatePersistenceService { get; } = App.Current.Services.GetService<IStatePersistanceService>();
private IKeyPressService KeyPressService { get; } = App.Current.Services.GetService<IKeyPressService>();
// Bindings.Update();
// Delegate to ViewModel.
if (e.Parameter is NavigateMailFolderEventArgs folderNavigationArgs)
public MailListPage()
{
WeakReferenceMessenger.Default.Send(new ActiveMailFolderChangedEvent(folderNavigationArgs.BaseFolderMenuItem, folderNavigationArgs.FolderInitLoadAwaitTask));
InitializeComponent();
}
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Dispose all WinoListView items.
MailListView.Dispose();
this.Bindings.StopTracking();
RenderingFrame.Navigate(typeof(IdlePage));
GC.Collect();
}
private void UpdateSelectAllButtonStatus()
{
// Check all checkbox if all is selected.
// Unhook events to prevent selection overriding.
SelectAllCheckbox.Checked -= SelectAllCheckboxChecked;
SelectAllCheckbox.Unchecked -= SelectAllCheckboxUnchecked;
SelectAllCheckbox.IsChecked = MailListView.Items.Count > 0 && MailListView.SelectedItems.Count == MailListView.Items.Count;
SelectAllCheckbox.Checked += SelectAllCheckboxChecked;
SelectAllCheckbox.Unchecked += SelectAllCheckboxUnchecked;
}
private void SelectionModeToggleChecked(object sender, RoutedEventArgs e)
{
ChangeSelectionMode(ListViewSelectionMode.Multiple);
}
private void FolderPivotChanged(object sender, SelectionChangedEventArgs e)
{
foreach (var addedItem in e.AddedItems)
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (addedItem is FolderPivotViewModel pivotItem)
base.OnNavigatedTo(e);
// Bindings.Update();
// Delegate to ViewModel.
if (e.Parameter is NavigateMailFolderEventArgs folderNavigationArgs)
{
pivotItem.IsSelected = true;
WeakReferenceMessenger.Default.Send(new ActiveMailFolderChangedEvent(folderNavigationArgs.BaseFolderMenuItem, folderNavigationArgs.FolderInitLoadAwaitTask));
}
}
foreach (var removedItem in e.RemovedItems)
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
if (removedItem is FolderPivotViewModel pivotItem)
base.OnNavigatedFrom(e);
// Dispose all WinoListView items.
MailListView.Dispose();
this.Bindings.StopTracking();
RenderingFrame.Navigate(typeof(IdlePage));
GC.Collect();
}
private void UpdateSelectAllButtonStatus()
{
// Check all checkbox if all is selected.
// Unhook events to prevent selection overriding.
SelectAllCheckbox.Checked -= SelectAllCheckboxChecked;
SelectAllCheckbox.Unchecked -= SelectAllCheckboxUnchecked;
SelectAllCheckbox.IsChecked = MailListView.Items.Count > 0 && MailListView.SelectedItems.Count == MailListView.Items.Count;
SelectAllCheckbox.Checked += SelectAllCheckboxChecked;
SelectAllCheckbox.Unchecked += SelectAllCheckboxUnchecked;
}
private void SelectionModeToggleChecked(object sender, RoutedEventArgs e)
{
ChangeSelectionMode(ListViewSelectionMode.Multiple);
}
private void FolderPivotChanged(object sender, SelectionChangedEventArgs e)
{
foreach (var addedItem in e.AddedItems)
{
pivotItem.IsSelected = false;
}
}
SelectAllCheckbox.IsChecked = false;
SelectionModeToggle.IsChecked = false;
MailListView.ClearSelections();
UpdateSelectAllButtonStatus();
ViewModel.SelectedPivotChangedCommand.Execute(null);
}
private void ChangeSelectionMode(ListViewSelectionMode mode)
{
MailListView.ChangeSelectionMode(mode);
if (ViewModel?.PivotFolders != null)
{
ViewModel.PivotFolders.ForEach(a => a.IsExtendedMode = mode == ListViewSelectionMode.Extended);
}
}
private void SelectionModeToggleUnchecked(object sender, RoutedEventArgs e)
{
ChangeSelectionMode(ListViewSelectionMode.Extended);
}
private void SelectAllCheckboxChecked(object sender, RoutedEventArgs e)
{
MailListView.SelectAllWino();
}
private void SelectAllCheckboxUnchecked(object sender, RoutedEventArgs e)
{
MailListView.ClearSelections();
}
private async void MailItemContextRequested(UIElement sender, ContextRequestedEventArgs args)
{
// Context is requested from a single mail point, but we might have multiple selected items.
// This menu should be calculated based on all selected items by providers.
if (sender is MailItemDisplayInformationControl control && args.TryGetPosition(sender, out Point p))
{
await FocusManager.TryFocusAsync(control, FocusState.Keyboard);
if (control.DataContext is IMailItem clickedMailItemContext)
{
var targetItems = ViewModel.GetTargetMailItemViewModels(clickedMailItemContext);
var availableActions = ViewModel.GetAvailableMailActions(targetItems);
if (!availableActions?.Any() ?? false) return;
var t = targetItems.ElementAt(0);
ViewModel.ChangeCustomFocusedState(targetItems, true);
var clickedOperation = await GetMailOperationFromFlyoutAsync(availableActions, control, p.X, p.Y);
ViewModel.ChangeCustomFocusedState(targetItems, false);
if (clickedOperation == null) return;
var prepRequest = new MailOperationPreperationRequest(clickedOperation.Operation, targetItems.Select(a => a.MailCopy));
await ViewModel.ExecuteMailOperationAsync(prepRequest);
}
}
}
private async Task<MailOperationMenuItem> GetMailOperationFromFlyoutAsync(IEnumerable<MailOperationMenuItem> availableActions,
UIElement showAtElement,
double x,
double y)
{
var source = new TaskCompletionSource<MailOperationMenuItem>();
var flyout = new MailOperationFlyout(availableActions, source);
flyout.ShowAt(showAtElement, new FlyoutShowOptions()
{
ShowMode = FlyoutShowMode.Standard,
Position = new Point(x + 30, y - 20)
});
return await source.Task;
}
void IRecipient<ClearMailSelectionsRequested>.Receive(ClearMailSelectionsRequested message)
{
MailListView.ClearSelections(null, preserveThreadExpanding: true);
}
void IRecipient<ActiveMailItemChangedEvent>.Receive(ActiveMailItemChangedEvent message)
{
// No active mail item. Go to empty page.
if (message.SelectedMailItemViewModel == null)
{
WeakReferenceMessenger.Default.Send(new CancelRenderingContentRequested());
}
else
{
// Navigate to composing page.
if (message.SelectedMailItemViewModel.IsDraft)
{
NavigationTransitionType composerPageTransition = NavigationTransitionType.None;
// Dispose active rendering if there is any and go to composer.
if (IsRenderingPageActive())
if (addedItem is FolderPivotViewModel pivotItem)
{
// Prepare WebView2 animation from Rendering to Composing page.
PrepareRenderingPageWebViewTransition();
// Dispose existing HTML content from rendering page webview.
WeakReferenceMessenger.Default.Send(new CancelRenderingContentRequested());
pivotItem.IsSelected = true;
}
else if (IsComposingPageActive())
}
foreach (var removedItem in e.RemovedItems)
{
if (removedItem is FolderPivotViewModel pivotItem)
{
// Composer is already active. Prepare composer WebView2 animation.
PrepareComposePageWebViewTransition();
pivotItem.IsSelected = false;
}
}
SelectAllCheckbox.IsChecked = false;
SelectionModeToggle.IsChecked = false;
MailListView.ClearSelections();
UpdateSelectAllButtonStatus();
ViewModel.SelectedPivotChangedCommand.Execute(null);
}
private void ChangeSelectionMode(ListViewSelectionMode mode)
{
MailListView.ChangeSelectionMode(mode);
if (ViewModel?.PivotFolders != null)
{
ViewModel.PivotFolders.ForEach(a => a.IsExtendedMode = mode == ListViewSelectionMode.Extended);
}
}
private void SelectionModeToggleUnchecked(object sender, RoutedEventArgs e)
{
ChangeSelectionMode(ListViewSelectionMode.Extended);
}
private void SelectAllCheckboxChecked(object sender, RoutedEventArgs e)
{
MailListView.SelectAllWino();
}
private void SelectAllCheckboxUnchecked(object sender, RoutedEventArgs e)
{
MailListView.ClearSelections();
}
private async void MailItemContextRequested(UIElement sender, ContextRequestedEventArgs args)
{
// Context is requested from a single mail point, but we might have multiple selected items.
// This menu should be calculated based on all selected items by providers.
if (sender is MailItemDisplayInformationControl control && args.TryGetPosition(sender, out Point p))
{
await FocusManager.TryFocusAsync(control, FocusState.Keyboard);
if (control.DataContext is IMailItem clickedMailItemContext)
{
var targetItems = ViewModel.GetTargetMailItemViewModels(clickedMailItemContext);
var availableActions = ViewModel.GetAvailableMailActions(targetItems);
if (!availableActions?.Any() ?? false) return;
var t = targetItems.ElementAt(0);
ViewModel.ChangeCustomFocusedState(targetItems, true);
var clickedOperation = await GetMailOperationFromFlyoutAsync(availableActions, control, p.X, p.Y);
ViewModel.ChangeCustomFocusedState(targetItems, false);
if (clickedOperation == null) return;
var prepRequest = new MailOperationPreperationRequest(clickedOperation.Operation, targetItems.Select(a => a.MailCopy));
await ViewModel.ExecuteMailOperationAsync(prepRequest);
}
}
}
private async Task<MailOperationMenuItem> GetMailOperationFromFlyoutAsync(IEnumerable<MailOperationMenuItem> availableActions,
UIElement showAtElement,
double x,
double y)
{
var source = new TaskCompletionSource<MailOperationMenuItem>();
var flyout = new MailOperationFlyout(availableActions, source);
flyout.ShowAt(showAtElement, new FlyoutShowOptions()
{
ShowMode = FlyoutShowMode.Standard,
Position = new Point(x + 30, y - 20)
});
return await source.Task;
}
void IRecipient<ClearMailSelectionsRequested>.Receive(ClearMailSelectionsRequested message)
{
MailListView.ClearSelections(null, preserveThreadExpanding: true);
}
void IRecipient<ActiveMailItemChangedEvent>.Receive(ActiveMailItemChangedEvent message)
{
// No active mail item. Go to empty page.
if (message.SelectedMailItemViewModel == null)
{
WeakReferenceMessenger.Default.Send(new CancelRenderingContentRequested());
}
else
{
// Navigate to composing page.
if (message.SelectedMailItemViewModel.IsDraft)
{
NavigationTransitionType composerPageTransition = NavigationTransitionType.None;
// Dispose active rendering if there is any and go to composer.
if (IsRenderingPageActive())
{
// Prepare WebView2 animation from Rendering to Composing page.
PrepareRenderingPageWebViewTransition();
// Dispose existing HTML content from rendering page webview.
WeakReferenceMessenger.Default.Send(new CancelRenderingContentRequested());
}
else if (IsComposingPageActive())
{
// Composer is already active. Prepare composer WebView2 animation.
PrepareComposePageWebViewTransition();
}
else
composerPageTransition = NavigationTransitionType.DrillIn;
ViewModel.NavigationService.Navigate(WinoPage.ComposePage, message.SelectedMailItemViewModel, NavigationReferenceFrame.RenderingFrame, composerPageTransition);
}
else
composerPageTransition = NavigationTransitionType.DrillIn;
{
// Find the MIME and go to rendering page.
ViewModel.NavigationService.Navigate(WinoPage.ComposePage, message.SelectedMailItemViewModel, NavigationReferenceFrame.RenderingFrame, composerPageTransition);
if (message.SelectedMailItemViewModel == null) return;
if (IsComposingPageActive())
{
PrepareComposePageWebViewTransition();
}
ViewModel.NavigationService.Navigate(WinoPage.MailRenderingPage, message.SelectedMailItemViewModel, NavigationReferenceFrame.RenderingFrame);
}
}
UpdateAdaptiveness();
}
private bool IsRenderingPageActive() => RenderingFrame.Content is MailRenderingPage;
private bool IsComposingPageActive() => RenderingFrame.Content is ComposePage;
private void PrepareComposePageWebViewTransition()
{
var webView = GetComposerPageWebView();
if (webView != null)
{
var animation = ConnectedAnimationService.GetForCurrentView().PrepareToAnimate("WebViewConnectedAnimation", webView);
animation.Configuration = new BasicConnectedAnimationConfiguration();
}
}
private void PrepareRenderingPageWebViewTransition()
{
var webView = GetRenderingPageWebView();
if (webView != null)
{
var animation = ConnectedAnimationService.GetForCurrentView().PrepareToAnimate("WebViewConnectedAnimation", webView);
animation.Configuration = new BasicConnectedAnimationConfiguration();
}
}
#region Connected Animation Helpers
private WebView2 GetRenderingPageWebView()
{
if (RenderingFrame.Content is MailRenderingPage renderingPage)
return renderingPage.GetWebView();
return null;
}
private WebView2 GetComposerPageWebView()
{
if (RenderingFrame.Content is ComposePage composePage)
return composePage.GetWebView();
return null;
}
#endregion
public async void Receive(SelectMailItemContainerEvent message)
{
if (message.SelectedMailViewModel == null) return;
await ViewModel.ExecuteUIThread(async () =>
{
MailListView.ClearSelections(message.SelectedMailViewModel, true);
int retriedSelectionCount = 0;
trySelection:
bool isSelected = MailListView.SelectMailItemContainer(message.SelectedMailViewModel);
if (!isSelected)
{
for (int i = retriedSelectionCount; i < 5;)
{
// Retry with delay until the container is realized. Max 1 second.
await Task.Delay(200);
retriedSelectionCount++;
goto trySelection;
}
}
// Automatically scroll to the selected item.
// This is useful when creating draft.
if (isSelected && message.ScrollToItem)
{
var collectionContainer = ViewModel.MailCollection.GetMailItemContainer(message.SelectedMailViewModel.UniqueId);
// Scroll to thread if available.
if (collectionContainer.ThreadViewModel != null)
{
MailListView.ScrollIntoView(collectionContainer.ThreadViewModel, ScrollIntoViewAlignment.Default);
}
else if (collectionContainer.ItemViewModel != null)
{
MailListView.ScrollIntoView(collectionContainer.ItemViewModel, ScrollIntoViewAlignment.Default);
}
}
});
}
private void SearchBoxFocused(object sender, RoutedEventArgs e)
{
SearchBar.PlaceholderText = string.Empty;
}
private void SearchBarUnfocused(object sender, RoutedEventArgs e)
{
SearchBar.PlaceholderText = Translator.SearchBarPlaceholder;
}
/// <summary>
/// Thread header is mail info display control and it can be dragged spearately out of ListView.
/// We need to prepare a drag package for it from the items inside.
/// </summary>
private void ThreadHeaderDragStart(UIElement sender, DragStartingEventArgs args)
{
if (sender is MailItemDisplayInformationControl control
&& control.ConnectedExpander?.Content is WinoListView contentListView)
{
var allItems = contentListView.Items.Where(a => a is IMailItem);
// Highlight all items.
allItems.Cast<MailItemViewModel>().ForEach(a => a.IsCustomFocused = true);
// Set native drag arg properties.
args.AllowedOperations = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move;
var dragPackage = new MailDragPackage(allItems.Cast<IMailItem>());
args.Data.Properties.Add(nameof(MailDragPackage), dragPackage);
args.DragUI.SetContentFromDataPackage();
control.ConnectedExpander.IsExpanded = true;
}
}
private void ThreadHeaderDragFinished(UIElement sender, DropCompletedEventArgs args)
{
if (sender is MailItemDisplayInformationControl control && control.ConnectedExpander != null && control.ConnectedExpander.Content is WinoListView contentListView)
{
contentListView.Items.Where(a => a is MailItemViewModel).Cast<MailItemViewModel>().ForEach(a => a.IsCustomFocused = false);
}
}
private async void LeftSwipeItemInvoked(Microsoft.UI.Xaml.Controls.SwipeItem sender, Microsoft.UI.Xaml.Controls.SwipeItemInvokedEventArgs args)
{
// Delete item for now.
var swipeControl = args.SwipeControl;
swipeControl.Close();
if (swipeControl.Tag is MailItemViewModel mailItemViewModel)
{
var package = new MailOperationPreperationRequest(MailOperation.SoftDelete, mailItemViewModel.MailCopy);
await ViewModel.ExecuteMailOperationAsync(package);
}
else if (swipeControl.Tag is ThreadMailItemViewModel threadMailItemViewModel)
{
var package = new MailOperationPreperationRequest(MailOperation.SoftDelete, threadMailItemViewModel.GetMailCopies());
await ViewModel.ExecuteMailOperationAsync(package);
}
}
private async void RightSwipeItemInvoked(Microsoft.UI.Xaml.Controls.SwipeItem sender, Microsoft.UI.Xaml.Controls.SwipeItemInvokedEventArgs args)
{
// Toggle status only for now.
var swipeControl = args.SwipeControl;
swipeControl.Close();
if (swipeControl.Tag is MailItemViewModel mailItemViewModel)
{
var operation = mailItemViewModel.IsRead ? MailOperation.MarkAsUnread : MailOperation.MarkAsRead;
var package = new MailOperationPreperationRequest(operation, mailItemViewModel.MailCopy);
await ViewModel.ExecuteMailOperationAsync(package);
}
else if (swipeControl.Tag is ThreadMailItemViewModel threadMailItemViewModel)
{
bool isAllRead = threadMailItemViewModel.ThreadItems.All(a => a.IsRead);
var operation = isAllRead ? MailOperation.MarkAsUnread : MailOperation.MarkAsRead;
var package = new MailOperationPreperationRequest(operation, threadMailItemViewModel.GetMailCopies());
await ViewModel.ExecuteMailOperationAsync(package);
}
}
private void PullToRefreshRequested(Microsoft.UI.Xaml.Controls.RefreshContainer sender, Microsoft.UI.Xaml.Controls.RefreshRequestedEventArgs args)
{
ViewModel.SyncFolderCommand?.Execute(null);
}
private async void SearchBar_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput && string.IsNullOrWhiteSpace(sender.Text))
{
await ViewModel.PerformSearchAsync();
}
}
public void Receive(DisposeRenderingFrameRequested message)
{
ViewModel.NavigationService.Navigate(WinoPage.IdlePage, null, NavigationReferenceFrame.RenderingFrame, NavigationTransitionType.DrillIn);
}
private void PageSizeChanged(object sender, SizeChangedEventArgs e)
{
ViewModel.MaxMailListLength = e.NewSize.Width - RENDERING_COLUMN_MIN_WIDTH;
StatePersistenceService.IsReaderNarrowed = e.NewSize.Width < StatePersistenceService.MailListPaneLength + RENDERING_COLUMN_MIN_WIDTH;
UpdateAdaptiveness();
}
private void MailListSizerManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
{
StatePersistenceService.MailListPaneLength = ViewModel.MailListLength;
}
private void UpdateAdaptiveness()
{
bool isMultiSelectionEnabled = ViewModel.IsMultiSelectionModeEnabled || KeyPressService.IsCtrlKeyPressed();
if (StatePersistenceService.IsReaderNarrowed)
{
if (ViewModel.HasSingleItemSelection && !isMultiSelectionEnabled)
{
VisualStateManager.GoToState(this, "NarrowRenderer", true);
}
else
{
VisualStateManager.GoToState(this, "NarrowMailList", true);
}
}
else
{
// Find the MIME and go to rendering page.
if (message.SelectedMailItemViewModel == null) return;
if (IsComposingPageActive())
if (ViewModel.HasSingleItemSelection && !isMultiSelectionEnabled)
{
PrepareComposePageWebViewTransition();
VisualStateManager.GoToState(this, "BothPanelsMailSelected", true);
}
ViewModel.NavigationService.Navigate(WinoPage.MailRenderingPage, message.SelectedMailItemViewModel, NavigationReferenceFrame.RenderingFrame);
}
}
UpdateAdaptiveness();
}
private bool IsRenderingPageActive() => RenderingFrame.Content is MailRenderingPage;
private bool IsComposingPageActive() => RenderingFrame.Content is ComposePage;
private void PrepareComposePageWebViewTransition()
{
var webView = GetComposerPageWebView();
if (webView != null)
{
var animation = ConnectedAnimationService.GetForCurrentView().PrepareToAnimate("WebViewConnectedAnimation", webView);
animation.Configuration = new BasicConnectedAnimationConfiguration();
}
}
private void PrepareRenderingPageWebViewTransition()
{
var webView = GetRenderingPageWebView();
if (webView != null)
{
var animation = ConnectedAnimationService.GetForCurrentView().PrepareToAnimate("WebViewConnectedAnimation", webView);
animation.Configuration = new BasicConnectedAnimationConfiguration();
}
}
#region Connected Animation Helpers
private WebView2 GetRenderingPageWebView()
{
if (RenderingFrame.Content is MailRenderingPage renderingPage)
return renderingPage.GetWebView();
return null;
}
private WebView2 GetComposerPageWebView()
{
if (RenderingFrame.Content is ComposePage composePage)
return composePage.GetWebView();
return null;
}
#endregion
public async void Receive(SelectMailItemContainerEvent message)
{
if (message.SelectedMailViewModel == null) return;
await ViewModel.ExecuteUIThread(async () =>
{
MailListView.ClearSelections(message.SelectedMailViewModel, true);
int retriedSelectionCount = 0;
trySelection:
bool isSelected = MailListView.SelectMailItemContainer(message.SelectedMailViewModel);
if (!isSelected)
{
for (int i = retriedSelectionCount; i < 5;)
else
{
// Retry with delay until the container is realized. Max 1 second.
await Task.Delay(200);
retriedSelectionCount++;
goto trySelection;
VisualStateManager.GoToState(this, "BothPanelsNoMailSelected", true);
}
}
// Automatically scroll to the selected item.
// This is useful when creating draft.
if (isSelected && message.ScrollToItem)
{
var collectionContainer = ViewModel.MailCollection.GetMailItemContainer(message.SelectedMailViewModel.UniqueId);
// Scroll to thread if available.
if (collectionContainer.ThreadViewModel != null)
{
MailListView.ScrollIntoView(collectionContainer.ThreadViewModel, ScrollIntoViewAlignment.Default);
}
else if (collectionContainer.ItemViewModel != null)
{
MailListView.ScrollIntoView(collectionContainer.ItemViewModel, ScrollIntoViewAlignment.Default);
}
}
});
}
private void SearchBoxFocused(object sender, RoutedEventArgs e)
{
SearchBar.PlaceholderText = string.Empty;
}
private void SearchBarUnfocused(object sender, RoutedEventArgs e)
{
SearchBar.PlaceholderText = Translator.SearchBarPlaceholder;
}
/// <summary>
/// Thread header is mail info display control and it can be dragged spearately out of ListView.
/// We need to prepare a drag package for it from the items inside.
/// </summary>
private void ThreadHeaderDragStart(UIElement sender, DragStartingEventArgs args)
{
if (sender is MailItemDisplayInformationControl control
&& control.ConnectedExpander?.Content is WinoListView contentListView)
{
var allItems = contentListView.Items.Where(a => a is IMailItem);
// Highlight all items.
allItems.Cast<MailItemViewModel>().ForEach(a => a.IsCustomFocused = true);
// Set native drag arg properties.
args.AllowedOperations = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move;
var dragPackage = new MailDragPackage(allItems.Cast<IMailItem>());
args.Data.Properties.Add(nameof(MailDragPackage), dragPackage);
args.DragUI.SetContentFromDataPackage();
control.ConnectedExpander.IsExpanded = true;
}
}
private void ThreadHeaderDragFinished(UIElement sender, DropCompletedEventArgs args)
{
if (sender is MailItemDisplayInformationControl control && control.ConnectedExpander != null && control.ConnectedExpander.Content is WinoListView contentListView)
{
contentListView.Items.Where(a => a is MailItemViewModel).Cast<MailItemViewModel>().ForEach(a => a.IsCustomFocused = false);
}
}
private async void LeftSwipeItemInvoked(Microsoft.UI.Xaml.Controls.SwipeItem sender, Microsoft.UI.Xaml.Controls.SwipeItemInvokedEventArgs args)
{
// Delete item for now.
var swipeControl = args.SwipeControl;
swipeControl.Close();
if (swipeControl.Tag is MailItemViewModel mailItemViewModel)
{
var package = new MailOperationPreperationRequest(MailOperation.SoftDelete, mailItemViewModel.MailCopy);
await ViewModel.ExecuteMailOperationAsync(package);
}
else if (swipeControl.Tag is ThreadMailItemViewModel threadMailItemViewModel)
{
var package = new MailOperationPreperationRequest(MailOperation.SoftDelete, threadMailItemViewModel.GetMailCopies());
await ViewModel.ExecuteMailOperationAsync(package);
}
}
private async void RightSwipeItemInvoked(Microsoft.UI.Xaml.Controls.SwipeItem sender, Microsoft.UI.Xaml.Controls.SwipeItemInvokedEventArgs args)
{
// Toggle status only for now.
var swipeControl = args.SwipeControl;
swipeControl.Close();
if (swipeControl.Tag is MailItemViewModel mailItemViewModel)
{
var operation = mailItemViewModel.IsRead ? MailOperation.MarkAsUnread : MailOperation.MarkAsRead;
var package = new MailOperationPreperationRequest(operation, mailItemViewModel.MailCopy);
await ViewModel.ExecuteMailOperationAsync(package);
}
else if (swipeControl.Tag is ThreadMailItemViewModel threadMailItemViewModel)
{
bool isAllRead = threadMailItemViewModel.ThreadItems.All(a => a.IsRead);
var operation = isAllRead ? MailOperation.MarkAsUnread : MailOperation.MarkAsRead;
var package = new MailOperationPreperationRequest(operation, threadMailItemViewModel.GetMailCopies());
await ViewModel.ExecuteMailOperationAsync(package);
}
}
private void PullToRefreshRequested(Microsoft.UI.Xaml.Controls.RefreshContainer sender, Microsoft.UI.Xaml.Controls.RefreshRequestedEventArgs args)
{
ViewModel.SyncFolderCommand?.Execute(null);
}
private async void SearchBar_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput && string.IsNullOrWhiteSpace(sender.Text))
{
await ViewModel.PerformSearchAsync();
}
}
public void Receive(DisposeRenderingFrameRequested message)
{
ViewModel.NavigationService.Navigate(WinoPage.IdlePage, null, NavigationReferenceFrame.RenderingFrame, NavigationTransitionType.DrillIn);
}
private void PageSizeChanged(object sender, SizeChangedEventArgs e)
{
ViewModel.MaxMailListLength = e.NewSize.Width - RENDERING_COLUMN_MIN_WIDTH;
StatePersistenceService.IsReaderNarrowed = e.NewSize.Width < StatePersistenceService.MailListPaneLength + RENDERING_COLUMN_MIN_WIDTH;
UpdateAdaptiveness();
}
private void MailListSizerManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
{
StatePersistenceService.MailListPaneLength = ViewModel.MailListLength;
}
private void UpdateAdaptiveness()
{
bool isMultiSelectionEnabled = ViewModel.IsMultiSelectionModeEnabled || KeyPressService.IsCtrlKeyPressed();
if (StatePersistenceService.IsReaderNarrowed)
{
if (ViewModel.HasSingleItemSelection && !isMultiSelectionEnabled)
{
VisualStateManager.GoToState(this, "NarrowRenderer", true);
}
else
{
VisualStateManager.GoToState(this, "NarrowMailList", true);
}
}
else
{
if (ViewModel.HasSingleItemSelection && !isMultiSelectionEnabled)
{
VisualStateManager.GoToState(this, "BothPanelsMailSelected", true);
}
else
{
VisualStateManager.GoToState(this, "BothPanelsNoMailSelected", true);
}
}
}
}

View File

@@ -19,257 +19,258 @@ using Wino.Messaging.Client.Mails;
using Wino.Messaging.Client.Shell;
using Wino.Views.Abstract;
namespace Wino.Views;
public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
IRecipient<HtmlRenderingRequested>,
IRecipient<CancelRenderingContentRequested>,
IRecipient<ApplicationThemeChanged>
namespace Wino.Views
{
private readonly IPreferencesService _preferencesService = App.Current.Services.GetService<IPreferencesService>();
private readonly IMailDialogService _dialogService = App.Current.Services.GetService<IMailDialogService>();
private bool isRenderingInProgress = false;
private TaskCompletionSource<bool> DOMLoadedTask = new TaskCompletionSource<bool>();
private bool isChromiumDisposed = false;
public WebView2 GetWebView() => Chromium;
public MailRenderingPage()
public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
IRecipient<HtmlRenderingRequested>,
IRecipient<CancelRenderingContentRequested>,
IRecipient<ApplicationThemeChanged>
{
InitializeComponent();
private readonly IPreferencesService _preferencesService = App.Current.Services.GetService<IPreferencesService>();
private readonly IMailDialogService _dialogService = App.Current.Services.GetService<IMailDialogService>();
Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF");
Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation,msWebView2CodeCache");
private bool isRenderingInProgress = false;
private TaskCompletionSource<bool> DOMLoadedTask = new TaskCompletionSource<bool>();
ViewModel.SaveHTMLasPDFFunc = new Func<string, Task<bool>>((path) =>
private bool isChromiumDisposed = false;
public WebView2 GetWebView() => Chromium;
public MailRenderingPage()
{
return Chromium.CoreWebView2.PrintToPdfAsync(path, null).AsTask();
});
}
InitializeComponent();
public override async void OnEditorThemeChanged()
{
base.OnEditorThemeChanged();
Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF");
Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation,msWebView2CodeCache");
await UpdateEditorThemeAsync();
}
private async Task<string> InvokeScriptSafeAsync(string function)
{
try
{
return await Chromium.ExecuteScriptAsync(function);
}
catch (Exception) { }
return string.Empty;
}
private async Task RenderInternalAsync(string htmlBody)
{
isRenderingInProgress = true;
await DOMLoadedTask.Task;
await UpdateEditorThemeAsync();
await UpdateReaderFontPropertiesAsync();
if (string.IsNullOrEmpty(htmlBody))
{
await Chromium.ExecuteScriptFunctionAsync("RenderHTML", isChromiumDisposed, JsonSerializer.Serialize(" ", BasicTypesJsonContext.Default.String));
}
else
{
var shouldLinkifyText = ViewModel.CurrentRenderModel?.MailRenderingOptions?.RenderPlaintextLinks ?? true;
await Chromium.ExecuteScriptFunctionAsync("RenderHTML", isChromiumDisposed,
JsonSerializer.Serialize(htmlBody, BasicTypesJsonContext.Default.String),
JsonSerializer.Serialize(shouldLinkifyText, BasicTypesJsonContext.Default.Boolean));
ViewModel.SaveHTMLasPDFFunc = new Func<string, Task<bool>>((path) =>
{
return Chromium.CoreWebView2.PrintToPdfAsync(path, null).AsTask();
});
}
isRenderingInProgress = false;
}
private async void WindowRequested(CoreWebView2 sender, CoreWebView2NewWindowRequestedEventArgs args)
{
args.Handled = true;
try
public override async void OnEditorThemeChanged()
{
await Launcher.LaunchUriAsync(new Uri(args.Uri));
}
catch (Exception) { }
}
base.OnEditorThemeChanged();
private void DOMContentLoaded(CoreWebView2 sender, CoreWebView2DOMContentLoadedEventArgs args) => DOMLoadedTask.TrySetResult(true);
async void IRecipient<HtmlRenderingRequested>.Receive(HtmlRenderingRequested message)
{
if (message == null || string.IsNullOrEmpty(message.HtmlBody))
{
await RenderInternalAsync(string.Empty);
return;
await UpdateEditorThemeAsync();
}
await Chromium.EnsureCoreWebView2Async();
await RenderInternalAsync(message.HtmlBody);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Disposing the page.
// Make sure the WebView2 is disposed properly.
DisposeWebView2();
}
private void DisposeWebView2()
{
if (Chromium == null) return;
Chromium.CoreWebView2Initialized -= CoreWebViewInitialized;
Chromium.NavigationStarting -= WebViewNavigationStarting;
if (Chromium.CoreWebView2 != null)
private async Task<string> InvokeScriptSafeAsync(string function)
{
try
{
return await Chromium.ExecuteScriptAsync(function);
}
catch (Exception) { }
return string.Empty;
}
private async Task RenderInternalAsync(string htmlBody)
{
isRenderingInProgress = true;
await DOMLoadedTask.Task;
await UpdateEditorThemeAsync();
await UpdateReaderFontPropertiesAsync();
if (string.IsNullOrEmpty(htmlBody))
{
await Chromium.ExecuteScriptFunctionAsync("RenderHTML", isChromiumDisposed, JsonSerializer.Serialize(" ", BasicTypesJsonContext.Default.String));
}
else
{
var shouldLinkifyText = ViewModel.CurrentRenderModel?.MailRenderingOptions?.RenderPlaintextLinks ?? true;
await Chromium.ExecuteScriptFunctionAsync("RenderHTML", isChromiumDisposed,
JsonSerializer.Serialize(htmlBody, BasicTypesJsonContext.Default.String),
JsonSerializer.Serialize(shouldLinkifyText, BasicTypesJsonContext.Default.Boolean));
}
isRenderingInProgress = false;
}
private async void WindowRequested(CoreWebView2 sender, CoreWebView2NewWindowRequestedEventArgs args)
{
args.Handled = true;
try
{
await Launcher.LaunchUriAsync(new Uri(args.Uri));
}
catch (Exception) { }
}
private void DOMContentLoaded(CoreWebView2 sender, CoreWebView2DOMContentLoadedEventArgs args) => DOMLoadedTask.TrySetResult(true);
async void IRecipient<HtmlRenderingRequested>.Receive(HtmlRenderingRequested message)
{
if (message == null || string.IsNullOrEmpty(message.HtmlBody))
{
await RenderInternalAsync(string.Empty);
return;
}
await Chromium.EnsureCoreWebView2Async();
await RenderInternalAsync(message.HtmlBody);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Disposing the page.
// Make sure the WebView2 is disposed properly.
DisposeWebView2();
}
private void DisposeWebView2()
{
if (Chromium == null) return;
Chromium.CoreWebView2Initialized -= CoreWebViewInitialized;
Chromium.NavigationStarting -= WebViewNavigationStarting;
if (Chromium.CoreWebView2 != null)
{
Chromium.CoreWebView2.DOMContentLoaded -= DOMContentLoaded;
Chromium.CoreWebView2.NewWindowRequested -= WindowRequested;
}
isChromiumDisposed = true;
Chromium.Close();
GC.Collect();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
var anim = ConnectedAnimationService.GetForCurrentView().GetAnimation("WebViewConnectedAnimation");
anim?.TryStart(Chromium);
Chromium.CoreWebView2Initialized -= CoreWebViewInitialized;
Chromium.CoreWebView2Initialized += CoreWebViewInitialized;
_ = Chromium.EnsureCoreWebView2Async();
// We don't have shell initialized here. It's only standalone EML viewing.
// Shift command bar from top to adjust the design.
if (ViewModel.StatePersistenceService.ShouldShiftMailRenderingDesign)
RendererGridFrame.Margin = new Thickness(0, 24, 0, 0);
else
RendererGridFrame.Margin = new Thickness(0, 0, 0, 0);
}
private async void CoreWebViewInitialized(WebView2 sender, CoreWebView2InitializedEventArgs args)
{
if (Chromium.CoreWebView2 == null) return;
var editorBundlePath = (await ViewModel.NativeAppService.GetEditorBundlePathAsync()).Replace("editor.html", string.Empty);
Chromium.CoreWebView2.SetVirtualHostNameToFolderMapping("app.reader", editorBundlePath, CoreWebView2HostResourceAccessKind.Allow);
Chromium.CoreWebView2.DOMContentLoaded -= DOMContentLoaded;
Chromium.CoreWebView2.DOMContentLoaded += DOMContentLoaded;
Chromium.CoreWebView2.NewWindowRequested -= WindowRequested;
Chromium.CoreWebView2.NewWindowRequested += WindowRequested;
Chromium.Source = new Uri("https://app.reader/reader.html");
}
isChromiumDisposed = true;
Chromium.Close();
GC.Collect();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
var anim = ConnectedAnimationService.GetForCurrentView().GetAnimation("WebViewConnectedAnimation");
anim?.TryStart(Chromium);
Chromium.CoreWebView2Initialized -= CoreWebViewInitialized;
Chromium.CoreWebView2Initialized += CoreWebViewInitialized;
_ = Chromium.EnsureCoreWebView2Async();
// We don't have shell initialized here. It's only standalone EML viewing.
// Shift command bar from top to adjust the design.
if (ViewModel.StatePersistenceService.ShouldShiftMailRenderingDesign)
RendererGridFrame.Margin = new Thickness(0, 24, 0, 0);
else
RendererGridFrame.Margin = new Thickness(0, 0, 0, 0);
}
private async void CoreWebViewInitialized(WebView2 sender, CoreWebView2InitializedEventArgs args)
{
if (Chromium.CoreWebView2 == null) return;
var editorBundlePath = (await ViewModel.NativeAppService.GetEditorBundlePathAsync()).Replace("editor.html", string.Empty);
Chromium.CoreWebView2.SetVirtualHostNameToFolderMapping("app.reader", editorBundlePath, CoreWebView2HostResourceAccessKind.Allow);
Chromium.CoreWebView2.DOMContentLoaded -= DOMContentLoaded;
Chromium.CoreWebView2.DOMContentLoaded += DOMContentLoaded;
Chromium.CoreWebView2.NewWindowRequested -= WindowRequested;
Chromium.CoreWebView2.NewWindowRequested += WindowRequested;
Chromium.Source = new Uri("https://app.reader/reader.html");
}
async void IRecipient<CancelRenderingContentRequested>.Receive(CancelRenderingContentRequested message)
{
await Chromium.EnsureCoreWebView2Async();
if (!isRenderingInProgress)
async void IRecipient<CancelRenderingContentRequested>.Receive(CancelRenderingContentRequested message)
{
await RenderInternalAsync(string.Empty);
await Chromium.EnsureCoreWebView2Async();
if (!isRenderingInProgress)
{
await RenderInternalAsync(string.Empty);
}
}
}
private async void WebViewNavigationStarting(WebView2 sender, CoreWebView2NavigationStartingEventArgs args)
{
// This is our reader.
if (args.Uri == "https://app.reader/reader.html")
return;
// Cancel all external navigations since it's navigating to different address inside the WebView2.
args.Cancel = !args.Uri.StartsWith("data:text/html");
// TODO: Check external link navigation setting is enabled.
// Open all external urls in launcher.
if (args.Cancel && Uri.TryCreate(args.Uri, UriKind.Absolute, out Uri newUri))
private async void WebViewNavigationStarting(WebView2 sender, CoreWebView2NavigationStartingEventArgs args)
{
await Launcher.LaunchUriAsync(newUri);
// This is our reader.
if (args.Uri == "https://app.reader/reader.html")
return;
// Cancel all external navigations since it's navigating to different address inside the WebView2.
args.Cancel = !args.Uri.StartsWith("data:text/html");
// TODO: Check external link navigation setting is enabled.
// Open all external urls in launcher.
if (args.Cancel && Uri.TryCreate(args.Uri, UriKind.Absolute, out Uri newUri))
{
await Launcher.LaunchUriAsync(newUri);
}
}
}
private void AttachmentClicked(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is MailAttachmentViewModel attachmentViewModel)
private void AttachmentClicked(object sender, ItemClickEventArgs e)
{
ViewModel.OpenAttachmentCommand.Execute(attachmentViewModel);
if (e.ClickedItem is MailAttachmentViewModel attachmentViewModel)
{
ViewModel.OpenAttachmentCommand.Execute(attachmentViewModel);
}
}
}
private void BarDynamicOverflowChanging(CommandBar sender, DynamicOverflowItemsChangingEventArgs args)
{
if (args.Action == CommandBarDynamicOverflowAction.AddingToOverflow || sender.SecondaryCommands.Any())
sender.OverflowButtonVisibility = CommandBarOverflowButtonVisibility.Visible;
else
sender.OverflowButtonVisibility = CommandBarOverflowButtonVisibility.Collapsed;
}
private async Task UpdateEditorThemeAsync()
{
await DOMLoadedTask.Task;
if (ViewModel.IsDarkWebviewRenderer)
private void BarDynamicOverflowChanging(CommandBar sender, DynamicOverflowItemsChangingEventArgs args)
{
Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark;
await InvokeScriptSafeAsync("SetDarkEditor();");
if (args.Action == CommandBarDynamicOverflowAction.AddingToOverflow || sender.SecondaryCommands.Any())
sender.OverflowButtonVisibility = CommandBarOverflowButtonVisibility.Visible;
else
sender.OverflowButtonVisibility = CommandBarOverflowButtonVisibility.Collapsed;
}
else
{
Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Light;
await InvokeScriptSafeAsync("SetLightEditor();");
private async Task UpdateEditorThemeAsync()
{
await DOMLoadedTask.Task;
if (ViewModel.IsDarkWebviewRenderer)
{
Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark;
await InvokeScriptSafeAsync("SetDarkEditor();");
}
else
{
Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Light;
await InvokeScriptSafeAsync("SetLightEditor();");
}
}
}
private async Task UpdateReaderFontPropertiesAsync()
{
await Chromium.ExecuteScriptFunctionAsync("ChangeFontSize", isChromiumDisposed, JsonSerializer.Serialize(_preferencesService.ReaderFontSize, BasicTypesJsonContext.Default.Int32));
// Prepare font family name with fallback to sans-serif by default.
var fontName = _preferencesService.ReaderFont;
// If font family name is not supported by the browser, fallback to sans-serif.
fontName += ", sans-serif";
await Chromium.ExecuteScriptFunctionAsync("ChangeFontFamily", isChromiumDisposed, JsonSerializer.Serialize(fontName, BasicTypesJsonContext.Default.String));
}
void IRecipient<ApplicationThemeChanged>.Receive(ApplicationThemeChanged message)
{
ViewModel.IsDarkWebviewRenderer = message.IsUnderlyingThemeDark;
}
private void InternetAddressClicked(object sender, RoutedEventArgs e)
{
if (sender is HyperlinkButton hyperlinkButton)
private async Task UpdateReaderFontPropertiesAsync()
{
hyperlinkButton.ContextFlyout.ShowAt(hyperlinkButton);
await Chromium.ExecuteScriptFunctionAsync("ChangeFontSize", isChromiumDisposed, JsonSerializer.Serialize(_preferencesService.ReaderFontSize, BasicTypesJsonContext.Default.Int32));
// Prepare font family name with fallback to sans-serif by default.
var fontName = _preferencesService.ReaderFont;
// If font family name is not supported by the browser, fallback to sans-serif.
fontName += ", sans-serif";
await Chromium.ExecuteScriptFunctionAsync("ChangeFontFamily", isChromiumDisposed, JsonSerializer.Serialize(fontName, BasicTypesJsonContext.Default.String));
}
void IRecipient<ApplicationThemeChanged>.Receive(ApplicationThemeChanged message)
{
ViewModel.IsDarkWebviewRenderer = message.IsUnderlyingThemeDark;
}
private void InternetAddressClicked(object sender, RoutedEventArgs e)
{
if (sender is HyperlinkButton hyperlinkButton)
{
hyperlinkButton.ContextFlyout.ShowAt(hyperlinkButton);
}
}
}
}

View File

@@ -1,11 +1,12 @@
using Wino.Views.Abstract;
namespace Wino.Views.Settings;
public sealed partial class PersonalizationPage : PersonalizationPageAbstract
namespace Wino.Views.Settings
{
public PersonalizationPage()
public sealed partial class PersonalizationPage : PersonalizationPageAbstract
{
InitializeComponent();
public PersonalizationPage()
{
InitializeComponent();
}
}
}

View File

@@ -1,11 +1,12 @@
using Wino.Views.Abstract;
namespace Wino.Views.Settings;
public sealed partial class AliasManagementPage : AliasManagementPageAbstract
namespace Wino.Views.Settings
{
public AliasManagementPage()
public sealed partial class AliasManagementPage : AliasManagementPageAbstract
{
this.InitializeComponent();
public AliasManagementPage()
{
this.InitializeComponent();
}
}
}

View File

@@ -1,11 +1,12 @@
using Wino.Views.Abstract;
namespace Wino.Views.Settings;
public sealed partial class AppPreferencesPage : AppPreferencesPageAbstract
namespace Wino.Views.Settings
{
public AppPreferencesPage()
public sealed partial class AppPreferencesPage : AppPreferencesPageAbstract
{
this.InitializeComponent();
public AppPreferencesPage()
{
this.InitializeComponent();
}
}
}

View File

@@ -1,18 +1,19 @@
using Wino.Views.Abstract;
namespace Wino.Views.Settings;
public sealed partial class LanguageTimePage : LanguageTimePageAbstract
namespace Wino.Views.Settings
{
public LanguageTimePage()
public sealed partial class LanguageTimePage : LanguageTimePageAbstract
{
this.InitializeComponent();
}
public LanguageTimePage()
{
this.InitializeComponent();
}
public override void OnLanguageChanged()
{
base.OnLanguageChanged();
public override void OnLanguageChanged()
{
base.OnLanguageChanged();
Bindings.Update();
Bindings.Update();
}
}
}

View File

@@ -1,11 +1,12 @@
using Wino.Views.Abstract;
namespace Wino.Views.Settings;
public sealed partial class MessageListPage : MessageListPageAbstract
namespace Wino.Views.Settings
{
public MessageListPage()
public sealed partial class MessageListPage : MessageListPageAbstract
{
this.InitializeComponent();
public MessageListPage()
{
this.InitializeComponent();
}
}
}

View File

@@ -1,11 +1,12 @@
using Wino.Views.Abstract;
namespace Wino.Views.Settings;
public sealed partial class ReadComposePanePage : ReadComposePanePageAbstract
namespace Wino.Views.Settings
{
public ReadComposePanePage()
public sealed partial class ReadComposePanePage : ReadComposePanePageAbstract
{
InitializeComponent();
public ReadComposePanePage()
{
InitializeComponent();
}
}
}

View File

@@ -1,11 +1,12 @@
using Wino.Views.Abstract;
namespace Wino.Views.Settings;
public sealed partial class SignatureManagementPage : SignatureManagementPageAbstract
namespace Wino.Views.Settings
{
public SignatureManagementPage()
public sealed partial class SignatureManagementPage : SignatureManagementPageAbstract
{
this.InitializeComponent();
public SignatureManagementPage()
{
this.InitializeComponent();
}
}
}