File scoped namespaces
This commit is contained in:
@@ -5,19 +5,18 @@ using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Media.Animation;
|
||||
using Wino.Views;
|
||||
|
||||
namespace Wino.Activation
|
||||
namespace Wino.Activation;
|
||||
|
||||
internal class DefaultActivationHandler : ActivationHandler<IActivatedEventArgs>
|
||||
{
|
||||
internal class DefaultActivationHandler : ActivationHandler<IActivatedEventArgs>
|
||||
protected override Task HandleInternalAsync(IActivatedEventArgs args)
|
||||
{
|
||||
protected override Task HandleInternalAsync(IActivatedEventArgs args)
|
||||
{
|
||||
(Window.Current.Content as Frame).Navigate(typeof(AppShell), null, new DrillInNavigationTransitionInfo());
|
||||
(Window.Current.Content as Frame).Navigate(typeof(AppShell), null, new DrillInNavigationTransitionInfo());
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Only navigate if Frame content doesn't exist.
|
||||
protected override bool CanHandleInternal(IActivatedEventArgs args)
|
||||
=> (Window.Current?.Content as Frame)?.Content == null;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Only navigate if Frame content doesn't exist.
|
||||
protected override bool CanHandleInternal(IActivatedEventArgs args)
|
||||
=> (Window.Current?.Content as Frame)?.Content == null;
|
||||
}
|
||||
|
||||
@@ -12,57 +12,56 @@ using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.UWP.Extensions;
|
||||
using Wino.Views;
|
||||
|
||||
namespace Wino.Activation
|
||||
namespace Wino.Activation;
|
||||
|
||||
internal class FileActivationHandler : ActivationHandler<FileActivatedEventArgs>
|
||||
{
|
||||
internal class FileActivationHandler : ActivationHandler<FileActivatedEventArgs>
|
||||
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)
|
||||
{
|
||||
private readonly INativeAppService _nativeAppService;
|
||||
private readonly IMimeFileService _mimeFileService;
|
||||
private readonly IStatePersistanceService _statePersistanceService;
|
||||
private readonly INavigationService _winoNavigationService;
|
||||
_nativeAppService = nativeAppService;
|
||||
_mimeFileService = mimeFileService;
|
||||
_statePersistanceService = statePersistanceService;
|
||||
_winoNavigationService = winoNavigationService;
|
||||
}
|
||||
|
||||
public FileActivationHandler(INativeAppService nativeAppService,
|
||||
IMimeFileService mimeFileService,
|
||||
IStatePersistanceService statePersistanceService,
|
||||
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))
|
||||
{
|
||||
_nativeAppService = nativeAppService;
|
||||
_mimeFileService = mimeFileService;
|
||||
_statePersistanceService = statePersistanceService;
|
||||
_winoNavigationService = winoNavigationService;
|
||||
}
|
||||
var fileBytes = await file.ToByteArrayAsync();
|
||||
var directoryName = Path.GetDirectoryName(file.Path);
|
||||
|
||||
protected override async Task HandleInternalAsync(FileActivatedEventArgs args)
|
||||
{
|
||||
// Always handle the last item passed.
|
||||
// Multiple files are not supported.
|
||||
var messageInformation = await _mimeFileService.GetMimeMessageInformationAsync(fileBytes, directoryName).ConfigureAwait(false);
|
||||
|
||||
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))
|
||||
if (_nativeAppService.IsAppRunning())
|
||||
{
|
||||
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());
|
||||
}
|
||||
// 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();
|
||||
|
||||
}
|
||||
|
||||
@@ -6,55 +6,54 @@ using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Launch;
|
||||
using Wino.Messaging.Client.Shell;
|
||||
|
||||
namespace Wino.Activation
|
||||
namespace Wino.Activation;
|
||||
|
||||
internal class ProtocolActivationHandler : ActivationHandler<ProtocolActivatedEventArgs>
|
||||
{
|
||||
internal class ProtocolActivationHandler : ActivationHandler<ProtocolActivatedEventArgs>
|
||||
private const string MailtoProtocolTag = "mailto:";
|
||||
|
||||
private readonly INativeAppService _nativeAppService;
|
||||
private readonly ILaunchProtocolService _launchProtocolService;
|
||||
|
||||
public ProtocolActivationHandler(INativeAppService nativeAppService, ILaunchProtocolService launchProtocolService)
|
||||
{
|
||||
private const string MailtoProtocolTag = "mailto:";
|
||||
_nativeAppService = nativeAppService;
|
||||
_launchProtocolService = launchProtocolService;
|
||||
}
|
||||
|
||||
private readonly INativeAppService _nativeAppService;
|
||||
private readonly ILaunchProtocolService _launchProtocolService;
|
||||
protected override Task HandleInternalAsync(ProtocolActivatedEventArgs args)
|
||||
{
|
||||
// Check URI prefix.
|
||||
var protocolString = args.Uri.AbsoluteUri;
|
||||
|
||||
public ProtocolActivationHandler(INativeAppService nativeAppService, ILaunchProtocolService launchProtocolService)
|
||||
if (protocolString.StartsWith(MailtoProtocolTag))
|
||||
{
|
||||
_nativeAppService = nativeAppService;
|
||||
_launchProtocolService = launchProtocolService;
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
||||
protected override Task HandleInternalAsync(ProtocolActivatedEventArgs args)
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected override bool CanHandleInternal(ProtocolActivatedEventArgs args)
|
||||
{
|
||||
// Validate the URI scheme.
|
||||
|
||||
try
|
||||
{
|
||||
// Check URI prefix.
|
||||
var protocolString = args.Uri.AbsoluteUri;
|
||||
|
||||
if (protocolString.StartsWith(MailtoProtocolTag))
|
||||
{
|
||||
// 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;
|
||||
var uriGet = args.Uri;
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool CanHandleInternal(ProtocolActivatedEventArgs args)
|
||||
{
|
||||
// Validate the URI scheme.
|
||||
|
||||
try
|
||||
{
|
||||
var uriGet = args.Uri;
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.CanHandleInternal(args);
|
||||
}
|
||||
return base.CanHandleInternal(args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,68 +9,67 @@ using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Messaging.Client.Accounts;
|
||||
|
||||
namespace Wino.Activation
|
||||
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>
|
||||
{
|
||||
/// <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>
|
||||
private readonly IMailService _mailService;
|
||||
private readonly IFolderService _folderService;
|
||||
|
||||
private ToastArguments _toastArguments;
|
||||
|
||||
public ToastNotificationActivationHandler(IMailService mailService,
|
||||
IFolderService folderService)
|
||||
{
|
||||
private readonly IMailService _mailService;
|
||||
private readonly IFolderService _folderService;
|
||||
_mailService = mailService;
|
||||
_folderService = folderService;
|
||||
}
|
||||
|
||||
private ToastArguments _toastArguments;
|
||||
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.
|
||||
|
||||
public ToastNotificationActivationHandler(IMailService mailService,
|
||||
IFolderService folderService)
|
||||
// Parse the mail unique id and perform above actions.
|
||||
if (Guid.TryParse(_toastArguments[Constants.ToastMailUniqueIdKey], out Guid mailItemUniqueId))
|
||||
{
|
||||
_mailService = mailService;
|
||||
_folderService = folderService;
|
||||
}
|
||||
var account = await _mailService.GetMailAccountByUniqueIdAsync(mailItemUniqueId).ConfigureAwait(false);
|
||||
if (account == null) return;
|
||||
|
||||
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.
|
||||
var mailItem = await _mailService.GetSingleMailItemAsync(mailItemUniqueId).ConfigureAwait(false);
|
||||
if (mailItem == null) return;
|
||||
|
||||
// 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 message = new AccountMenuItemExtended(mailItem.AssignedFolder.Id, mailItem);
|
||||
|
||||
var mailItem = await _mailService.GetSingleMailItemAsync(mailItemUniqueId).ConfigureAwait(false);
|
||||
if (mailItem == null) return;
|
||||
// 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;
|
||||
|
||||
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;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,287 +27,288 @@ using Wino.Messaging.Client.Navigation;
|
||||
using Wino.Messaging.Server;
|
||||
using Wino.Services;
|
||||
|
||||
namespace Wino
|
||||
namespace Wino;
|
||||
|
||||
public sealed partial class App : WinoApplication, IRecipient<NewMailSynchronizationRequested>
|
||||
{
|
||||
public sealed partial class App : WinoApplication, IRecipient<NewMailSynchronizationRequested>
|
||||
public override string AppCenterKey { get; } = "90deb1d0-a77f-47d0-8a6b-7eaf111c6b72";
|
||||
|
||||
private BackgroundTaskDeferral connectionBackgroundTaskDeferral;
|
||||
private BackgroundTaskDeferral toastActionBackgroundTaskDeferral;
|
||||
|
||||
public App()
|
||||
{
|
||||
private BackgroundTaskDeferral connectionBackgroundTaskDeferral;
|
||||
private BackgroundTaskDeferral toastActionBackgroundTaskDeferral;
|
||||
InitializeComponent();
|
||||
|
||||
public App()
|
||||
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
|
||||
{
|
||||
InitializeComponent();
|
||||
await AppServiceConnectionManager.ConnectAsync();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to connect to server after resuming the app.");
|
||||
}
|
||||
}
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
public override IServiceProvider ConfigureServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
WeakReferenceMessenger.Default.Register(this);
|
||||
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");
|
||||
}
|
||||
|
||||
public override async void OnResuming(object sender, object e)
|
||||
if (isGoToAppPreferencesRequested == null && currentStartupBehavior != StartupBehaviorResult.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.
|
||||
// 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
|
||||
{
|
||||
await AppServiceConnectionManager.ConnectAsync();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Ignore
|
||||
var isServerKilled = await AppServiceConnectionManager.GetResponseAsync<bool, TerminateServerRequested>(new TerminateServerRequested());
|
||||
|
||||
isServerKilled.ThrowIfFailed();
|
||||
|
||||
Log.Information("Server is killed.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to connect to server after resuming the app.");
|
||||
Log.Error(ex, "Failed to kill server.");
|
||||
}
|
||||
}
|
||||
|
||||
public override IServiceProvider ConfigureServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
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();
|
||||
deferral.Complete();
|
||||
}
|
||||
|
||||
protected override ActivationHandler<IActivatedEventArgs> GetDefaultActivationHandler()
|
||||
=> new DefaultActivationHandler();
|
||||
}
|
||||
|
||||
@@ -28,309 +28,308 @@ using Wino.Messaging.Client.Mails;
|
||||
using Wino.Messaging.Client.Shell;
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views
|
||||
namespace Wino.Views;
|
||||
|
||||
public sealed partial class AppShell : AppShellAbstract,
|
||||
IRecipient<AccountMenuItemExtended>,
|
||||
IRecipient<NavigateMailFolderEvent>,
|
||||
IRecipient<CreateNewMailWithMultipleAccountsRequested>,
|
||||
IRecipient<InfoBarMessageRequested>
|
||||
{
|
||||
public sealed partial class AppShell : AppShellAbstract,
|
||||
IRecipient<AccountMenuItemExtended>,
|
||||
IRecipient<NavigateMailFolderEvent>,
|
||||
IRecipient<CreateNewMailWithMultipleAccountsRequested>,
|
||||
IRecipient<InfoBarMessageRequested>
|
||||
public AppShell() : base()
|
||||
{
|
||||
public AppShell() : base()
|
||||
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)
|
||||
{
|
||||
InitializeComponent();
|
||||
droppedContainer.IsDraggingItemOver = false;
|
||||
|
||||
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 (CanContinueDragDrop(droppedContainer, e))
|
||||
{
|
||||
droppedContainer.IsDraggingItemOver = false;
|
||||
|
||||
if (CanContinueDragDrop(droppedContainer, e))
|
||||
if (droppedContainer.DataContext is IBaseFolderMenuItem draggingFolder)
|
||||
{
|
||||
if (droppedContainer.DataContext is IBaseFolderMenuItem draggingFolder)
|
||||
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)
|
||||
{
|
||||
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 (item is MailItemViewModel singleMailItemViewModel)
|
||||
{
|
||||
if (item is MailItemViewModel singleMailItemViewModel)
|
||||
{
|
||||
mailCopies.Add(singleMailItemViewModel.MailCopy);
|
||||
}
|
||||
else if (item is ThreadMailItemViewModel threadViewModel)
|
||||
{
|
||||
mailCopies.AddRange(threadViewModel.GetMailCopies());
|
||||
}
|
||||
mailCopies.Add(singleMailItemViewModel.MailCopy);
|
||||
}
|
||||
else if (item is ThreadMailItemViewModel threadViewModel)
|
||||
{
|
||||
mailCopies.AddRange(threadViewModel.GetMailCopies());
|
||||
}
|
||||
|
||||
await ViewModel.PerformMoveOperationAsync(mailCopies, draggingFolder);
|
||||
}
|
||||
|
||||
await ViewModel.PerformMoveOperationAsync(mailCopies, draggingFolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ItemDragLeaveFromFolder(object sender, DragEventArgs e)
|
||||
private void ItemDragLeaveFromFolder(object sender, DragEventArgs e)
|
||||
{
|
||||
if (sender is WinoNavigationViewItem leavingContainer)
|
||||
{
|
||||
if (sender is WinoNavigationViewItem leavingContainer)
|
||||
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))
|
||||
{
|
||||
leavingContainer.IsDraggingItemOver = false;
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
else if (ViewModel.MenuItems.TryGetAccountMenuItem(message.NavigateMailItem.AssignedAccount.Id, out IAccountMenuItem accountMenuItem))
|
||||
{
|
||||
droppedContainer.IsDraggingItemOver = true;
|
||||
// Loaded account is different. First change the folder items and navigate.
|
||||
|
||||
var draggingFolder = droppedContainer.DataContext as IBaseFolderMenuItem;
|
||||
await ViewModel.ChangeLoadedAccountAsync(accountMenuItem, navigateInbox: false);
|
||||
|
||||
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move;
|
||||
e.DragUIOverride.Caption = string.Format(Translator.DragMoveToFolderCaption, draggingFolder.FolderName);
|
||||
}
|
||||
}
|
||||
// Find the folder.
|
||||
|
||||
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 (ViewModel.MenuItems.TryGetFolderMenuItem(message.FolderId, out IBaseFolderMenuItem accountFolderMenuItem))
|
||||
{
|
||||
foundMenuItem.Expand();
|
||||
accountFolderMenuItem.Expand();
|
||||
|
||||
await ViewModel.NavigateFolderAsync(foundMenuItem);
|
||||
await ViewModel.NavigateFolderAsync(accountFolderMenuItem);
|
||||
|
||||
navigationView.SelectedItem = foundMenuItem;
|
||||
|
||||
if (message.NavigateMailItem == null) return;
|
||||
navigationView.SelectedItem = accountFolderMenuItem;
|
||||
|
||||
// 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.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await ViewModel.ChangeLoadedAccountAsync(accountMenuItem, navigateInbox: false);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Find the folder.
|
||||
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;
|
||||
|
||||
if (ViewModel.MenuItems.TryGetFolderMenuItem(message.FolderId, out IBaseFolderMenuItem accountFolderMenuItem))
|
||||
{
|
||||
accountFolderMenuItem.Expand();
|
||||
await ViewModel.MenuItemInvokedOrSelectedAsync(winoNavigationViewItem.DataContext as IMenuItem);
|
||||
}
|
||||
}
|
||||
|
||||
await ViewModel.NavigateFolderAsync(accountFolderMenuItem);
|
||||
public void Receive(NavigateMailFolderEvent message)
|
||||
{
|
||||
if (message.BaseFolderMenuItem == null) return;
|
||||
|
||||
navigationView.SelectedItem = accountFolderMenuItem;
|
||||
if (navigationView.SelectedItem != message.BaseFolderMenuItem)
|
||||
{
|
||||
var navigateFolderArgs = new NavigateMailFolderEventArgs(message.BaseFolderMenuItem, message.FolderInitLoadAwaitTask);
|
||||
|
||||
// At this point folder is navigated and items are loaded.
|
||||
WeakReferenceMessenger.Default.Send(new MailItemNavigationRequested(message.NavigateMailItem.UniqueId, ScrollToItem: true));
|
||||
}
|
||||
}
|
||||
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)
|
||||
});
|
||||
|
||||
var operation = await source.Task;
|
||||
|
||||
flyout.Dispose();
|
||||
|
||||
// No action selected.
|
||||
if (operation == null) return;
|
||||
|
||||
await ViewModel.PerformFolderOperationAsync(operation.Operation, baseFolderMenuItem);
|
||||
}
|
||||
}
|
||||
|
||||
private async void MenuSelectionChanged(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewSelectionChangedEventArgs args)
|
||||
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()
|
||||
{
|
||||
if (args.SelectedItem is IMenuItem invokedMenuItem)
|
||||
{
|
||||
await ViewModel.MenuItemInvokedOrSelectedAsync(invokedMenuItem);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
private async void NavigationViewItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args)
|
||||
/// <summary>
|
||||
/// InfoBar message is requested.
|
||||
/// </summary>
|
||||
public async void Receive(InfoBarMessageRequested message)
|
||||
{
|
||||
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
// SelectsOnInvoked is handled in MenuSelectionChanged.
|
||||
// This part is only for the items that are not selectable.
|
||||
if (args.InvokedItemContainer is WinoNavigationViewItem winoNavigationViewItem)
|
||||
if (string.IsNullOrEmpty(message.ActionButtonTitle) || message.Action == null)
|
||||
{
|
||||
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;
|
||||
ShellInfoBar.ActionButton = null;
|
||||
}
|
||||
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()
|
||||
ShellInfoBar.ActionButton = new Button()
|
||||
{
|
||||
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);
|
||||
Content = message.ActionButtonTitle,
|
||||
Command = new RelayCommand(message.Action)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(CreateNewMailWithMultipleAccountsRequested message)
|
||||
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)
|
||||
{
|
||||
// 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
|
||||
});
|
||||
ShellFrame.Margin = new Thickness(7, 0, 0, 0);
|
||||
}
|
||||
|
||||
private void NavigationPaneOpening(Microsoft.UI.Xaml.Controls.NavigationView sender, object args)
|
||||
else
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
ShellFrame.Margin = new Thickness(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,185 +10,184 @@ using Wino.Core.Domain.Models.Menus;
|
||||
using Wino.Core.UWP.Controls;
|
||||
using Wino.Helpers;
|
||||
|
||||
namespace Wino.Behaviors
|
||||
namespace Wino.Behaviors;
|
||||
|
||||
public class BindableCommandBarBehavior : Behavior<CommandBar>
|
||||
{
|
||||
public class BindableCommandBarBehavior : Behavior<CommandBar>
|
||||
public static readonly DependencyProperty PrimaryCommandsProperty = DependencyProperty.Register(
|
||||
"PrimaryCommands", typeof(object), typeof(BindableCommandBarBehavior),
|
||||
new PropertyMetadata(null, UpdateCommands));
|
||||
|
||||
public ICommand ItemClickedCommand
|
||||
{
|
||||
public static readonly DependencyProperty PrimaryCommandsProperty = DependencyProperty.Register(
|
||||
"PrimaryCommands", typeof(object), typeof(BindableCommandBarBehavior),
|
||||
new PropertyMetadata(null, UpdateCommands));
|
||||
get { return (ICommand)GetValue(ItemClickedCommandProperty); }
|
||||
set { SetValue(ItemClickedCommandProperty, value); }
|
||||
}
|
||||
|
||||
public ICommand ItemClickedCommand
|
||||
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)
|
||||
{
|
||||
get { return (ICommand)GetValue(ItemClickedCommandProperty); }
|
||||
set { SetValue(ItemClickedCommandProperty, value); }
|
||||
}
|
||||
|
||||
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)
|
||||
foreach (var item in enumerable)
|
||||
{
|
||||
foreach (var item in enumerable)
|
||||
if (item is ButtonBase button)
|
||||
{
|
||||
if (item is ButtonBase button)
|
||||
{
|
||||
button.Click -= Button_Click;
|
||||
}
|
||||
button.Click -= Button_Click;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePrimaryCommands()
|
||||
{
|
||||
if (AssociatedObject == null)
|
||||
return;
|
||||
|
||||
if (PrimaryCommands == null)
|
||||
return;
|
||||
|
||||
if (AssociatedObject.PrimaryCommands is IEnumerable enumerableObjects)
|
||||
{
|
||||
foreach (var item in enumerableObjects)
|
||||
{
|
||||
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)
|
||||
// {
|
||||
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePrimaryCommands()
|
||||
{
|
||||
if (AssociatedObject == null)
|
||||
return;
|
||||
|
||||
if (PrimaryCommands == null)
|
||||
return;
|
||||
|
||||
if (AssociatedObject.PrimaryCommands is IEnumerable enumerableObjects)
|
||||
{
|
||||
foreach (var item in enumerableObjects)
|
||||
{
|
||||
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)
|
||||
// {
|
||||
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,83 +4,82 @@ using Windows.UI.Xaml;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.UWP.Controls;
|
||||
|
||||
namespace Wino.Behaviors
|
||||
namespace Wino.Behaviors;
|
||||
|
||||
public class CreateMailNavigationItemBehavior : Behavior<WinoNavigationViewItem>
|
||||
{
|
||||
public class CreateMailNavigationItemBehavior : Behavior<WinoNavigationViewItem>
|
||||
public IMenuItem SelectedMenuItem
|
||||
{
|
||||
public IMenuItem SelectedMenuItem
|
||||
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)
|
||||
{
|
||||
get { return (IMenuItem)GetValue(SelectedMenuItemProperty); }
|
||||
set { SetValue(SelectedMenuItemProperty, value); }
|
||||
if (dependencyPropertyChangedEventArgs.NewValue != null)
|
||||
behavior.RegisterMenuItemChanges();
|
||||
|
||||
behavior.ManageAccounts();
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<IMenuItem> MenuItems
|
||||
private void RegisterMenuItemChanges()
|
||||
{
|
||||
if (MenuItems != null)
|
||||
{
|
||||
get { return (ObservableCollection<IMenuItem>)GetValue(MenuItemsProperty); }
|
||||
set { SetValue(MenuItemsProperty, value); }
|
||||
MenuItems.CollectionChanged -= MenuCollectionUpdated;
|
||||
MenuItems.CollectionChanged += MenuCollectionUpdated;
|
||||
}
|
||||
}
|
||||
|
||||
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 void MenuCollectionUpdated(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
ManageAccounts();
|
||||
}
|
||||
|
||||
public CreateMailNavigationItemBehavior()
|
||||
private static void OnSelectedMenuItemChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
|
||||
{
|
||||
if (dependencyObject is CreateMailNavigationItemBehavior behavior)
|
||||
{
|
||||
|
||||
behavior.ManageAccounts();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAttached()
|
||||
private void ManageAccounts()
|
||||
{
|
||||
if (MenuItems == null) return;
|
||||
|
||||
AssociatedObject.MenuItems.Clear();
|
||||
|
||||
if (SelectedMenuItem == null)
|
||||
{
|
||||
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)
|
||||
{
|
||||
// ??
|
||||
}
|
||||
// ??
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,70 +4,69 @@ using Windows.UI.Xaml;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.UWP.Controls;
|
||||
|
||||
namespace Wino.Controls
|
||||
namespace Wino.Controls;
|
||||
|
||||
public partial class AccountNavigationItem : WinoNavigationViewItem
|
||||
{
|
||||
public partial class AccountNavigationItem : WinoNavigationViewItem
|
||||
|
||||
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
|
||||
{
|
||||
get { return (bool)GetValue(IsActiveAccountProperty); }
|
||||
set { SetValue(IsActiveAccountProperty, 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));
|
||||
public IAccountMenuItem BindingData
|
||||
{
|
||||
get { return (IAccountMenuItem)GetValue(BindingDataProperty); }
|
||||
set { SetValue(BindingDataProperty, value); }
|
||||
}
|
||||
|
||||
private const string PART_NavigationViewItemMenuItemsHost = "NavigationViewItemMenuItemsHost";
|
||||
private const string PART_SelectionIndicator = "CustomSelectionIndicator";
|
||||
|
||||
public bool IsActiveAccount
|
||||
{
|
||||
get { return (bool)GetValue(IsActiveAccountProperty); }
|
||||
set { SetValue(IsActiveAccountProperty, value); }
|
||||
}
|
||||
private ItemsRepeater _itemsRepeater;
|
||||
private Windows.UI.Xaml.Shapes.Rectangle _selectionIndicator;
|
||||
|
||||
public IAccountMenuItem BindingData
|
||||
{
|
||||
get { return (IAccountMenuItem)GetValue(BindingDataProperty); }
|
||||
set { SetValue(BindingDataProperty, value); }
|
||||
}
|
||||
public AccountNavigationItem()
|
||||
{
|
||||
DefaultStyleKey = typeof(AccountNavigationItem);
|
||||
}
|
||||
|
||||
private const string PART_NavigationViewItemMenuItemsHost = "NavigationViewItemMenuItemsHost";
|
||||
private const string PART_SelectionIndicator = "CustomSelectionIndicator";
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
private ItemsRepeater _itemsRepeater;
|
||||
private Windows.UI.Xaml.Shapes.Rectangle _selectionIndicator;
|
||||
_itemsRepeater = GetTemplateChild(PART_NavigationViewItemMenuItemsHost) as ItemsRepeater;
|
||||
_selectionIndicator = GetTemplateChild(PART_SelectionIndicator) as Windows.UI.Xaml.Shapes.Rectangle;
|
||||
|
||||
public AccountNavigationItem()
|
||||
{
|
||||
DefaultStyleKey = typeof(AccountNavigationItem);
|
||||
}
|
||||
if (_itemsRepeater == null) return;
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
(_itemsRepeater.Layout as StackLayout).Spacing = 0;
|
||||
|
||||
_itemsRepeater = GetTemplateChild(PART_NavigationViewItemMenuItemsHost) as ItemsRepeater;
|
||||
_selectionIndicator = GetTemplateChild(PART_SelectionIndicator) as Windows.UI.Xaml.Shapes.Rectangle;
|
||||
UpdateSelectionBorder();
|
||||
}
|
||||
|
||||
if (_itemsRepeater == null) return;
|
||||
private static void OnIsActiveAccountChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
if (obj is AccountNavigationItem control)
|
||||
control.UpdateSelectionBorder();
|
||||
}
|
||||
|
||||
(_itemsRepeater.Layout as StackLayout).Spacing = 0;
|
||||
private void UpdateSelectionBorder()
|
||||
{
|
||||
if (_selectionIndicator == null) return;
|
||||
|
||||
UpdateSelectionBorder();
|
||||
}
|
||||
// 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.
|
||||
|
||||
private static void OnIsActiveAccountChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
if (obj is AccountNavigationItem control)
|
||||
control.UpdateSelectionBorder();
|
||||
}
|
||||
bool hasParentMenuItem = BindingData is IAccountMenuItem accountMenuItem && accountMenuItem.ParentMenuItem != null;
|
||||
|
||||
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;
|
||||
}
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,254 +13,244 @@ using Wino.Extensions;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
using Wino.Mail.ViewModels.Messages;
|
||||
|
||||
namespace Wino.Controls.Advanced
|
||||
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
|
||||
{
|
||||
private ILogger logger = Log.ForContext<WinoListView>();
|
||||
|
||||
private const string PART_ScrollViewer = "ScrollViewer";
|
||||
private ScrollViewer internalScrollviewer;
|
||||
|
||||
/// <summary>
|
||||
/// Custom ListView control that handles multiple selection with Extended/Multiple selection mode
|
||||
/// and supports threads.
|
||||
/// Gets or sets whether this ListView belongs to thread items.
|
||||
/// This is important for detecting selected items etc.
|
||||
/// </summary>
|
||||
public partial class WinoListView : ListView, IDisposable
|
||||
public bool IsThreadListView
|
||||
{
|
||||
private ILogger logger = Log.ForContext<WinoListView>();
|
||||
get { return (bool)GetValue(IsThreadListViewProperty); }
|
||||
set { SetValue(IsThreadListViewProperty, value); }
|
||||
}
|
||||
|
||||
private const string PART_ScrollViewer = "ScrollViewer";
|
||||
private ScrollViewer internalScrollviewer;
|
||||
public ICommand ItemDeletedCommand
|
||||
{
|
||||
get { return (ICommand)GetValue(ItemDeletedCommandProperty); }
|
||||
set { SetValue(ItemDeletedCommandProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this ListView belongs to thread items.
|
||||
/// This is important for detecting selected items etc.
|
||||
/// </summary>
|
||||
public bool IsThreadListView
|
||||
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)
|
||||
{
|
||||
get { return (bool)GetValue(IsThreadListViewProperty); }
|
||||
set { SetValue(IsThreadListViewProperty, value); }
|
||||
logger.Warning("WinoListView does not have an internal ScrollViewer. Infinite scrolling behavior might be effected.");
|
||||
return;
|
||||
}
|
||||
|
||||
public ICommand ItemDeletedCommand
|
||||
internalScrollviewer.ViewChanged -= InternalScrollVeiwerViewChanged;
|
||||
internalScrollviewer.ViewChanged += InternalScrollVeiwerViewChanged;
|
||||
}
|
||||
|
||||
private static void OnIsThreadViewChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
if (obj is WinoListView winoListView)
|
||||
{
|
||||
get { return (ICommand)GetValue(ItemDeletedCommandProperty); }
|
||||
set { SetValue(ItemDeletedCommandProperty, value); }
|
||||
winoListView.AdjustThreadViewContainerVisuals();
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand LoadMoreCommand
|
||||
private void AdjustThreadViewContainerVisuals()
|
||||
{
|
||||
if (IsThreadListView)
|
||||
{
|
||||
get { return (ICommand)GetValue(LoadMoreCommandProperty); }
|
||||
set { SetValue(LoadMoreCommandProperty, value); }
|
||||
ItemContainerTransitions.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsThreadScrollingEnabled
|
||||
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)
|
||||
{
|
||||
get { return (bool)GetValue(IsThreadScrollingEnabledProperty); }
|
||||
set { SetValue(IsThreadScrollingEnabledProperty, value); }
|
||||
}
|
||||
var currentOffset = internalScrollviewer.VerticalOffset;
|
||||
var maxOffset = internalScrollviewer.ScrollableHeight;
|
||||
|
||||
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)
|
||||
if (currentOffset + 10 >= maxOffset && lastestRaisedOffset != maxOffset && Items.Count != lastItemSize)
|
||||
{
|
||||
logger.Warning("WinoListView does not have an internal ScrollViewer. Infinite scrolling behavior might be effected.");
|
||||
return;
|
||||
}
|
||||
// We must load more.
|
||||
lastestRaisedOffset = maxOffset;
|
||||
lastItemSize = Items.Count;
|
||||
|
||||
internalScrollviewer.ViewChanged -= InternalScrollVeiwerViewChanged;
|
||||
internalScrollviewer.ViewChanged += InternalScrollVeiwerViewChanged;
|
||||
}
|
||||
|
||||
private static void OnIsThreadViewChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
if (obj is WinoListView winoListView)
|
||||
{
|
||||
winoListView.AdjustThreadViewContainerVisuals();
|
||||
LoadMoreCommand?.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AdjustThreadViewContainerVisuals()
|
||||
private void ProcessDelKey(UIElement sender, Windows.UI.Xaml.Input.ProcessKeyboardAcceleratorEventArgs args)
|
||||
{
|
||||
if (args.Key == Windows.System.VirtualKey.Delete)
|
||||
{
|
||||
if (IsThreadListView)
|
||||
{
|
||||
ItemContainerTransitions.Clear();
|
||||
}
|
||||
args.Handled = true;
|
||||
|
||||
ItemDeletedCommand?.Execute(MailOperation.SoftDelete);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
private void ItemDragCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
|
||||
{
|
||||
if (args.Items.Any(a => a is MailItemViewModel))
|
||||
{
|
||||
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)
|
||||
{
|
||||
// We must load more.
|
||||
lastestRaisedOffset = maxOffset;
|
||||
lastItemSize = Items.Count;
|
||||
|
||||
LoadMoreCommand?.Execute(null);
|
||||
}
|
||||
}
|
||||
args.Items.Cast<MailItemViewModel>().ForEach(a => a.IsCustomFocused = false);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessDelKey(UIElement sender, Windows.UI.Xaml.Input.ProcessKeyboardAcceleratorEventArgs args)
|
||||
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)
|
||||
{
|
||||
if (args.Key == Windows.System.VirtualKey.Delete)
|
||||
{
|
||||
args.Handled = true;
|
||||
var allItems = args.Items.Cast<MailItemViewModel>();
|
||||
|
||||
ItemDeletedCommand?.Execute(MailOperation.SoftDelete);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
private void ItemDragCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
|
||||
else
|
||||
{
|
||||
if (args.Items.Any(a => a is MailItemViewModel))
|
||||
{
|
||||
args.Items.Cast<MailItemViewModel>().ForEach(a => a.IsCustomFocused = false);
|
||||
}
|
||||
var dragPackage = new MailDragPackage(args.Items.Cast<IMailItem>());
|
||||
|
||||
args.Data.Properties.Add(nameof(MailDragPackage), dragPackage);
|
||||
}
|
||||
}
|
||||
|
||||
private void ItemDragStarting(object sender, DragItemsStartingEventArgs args)
|
||||
public void ChangeSelectionMode(ListViewSelectionMode selectionMode)
|
||||
{
|
||||
SelectionMode = selectionMode;
|
||||
|
||||
if (!IsThreadListView)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
threadListView.SelectionMode = selectionMode;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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 != null)
|
||||
{
|
||||
if (!threadListView.SelectedItems.Contains(exceptViewModel))
|
||||
{
|
||||
if (!preserveThreadExpanding)
|
||||
{
|
||||
c.IsThreadExpanded = false;
|
||||
}
|
||||
|
||||
threadListView.SelectedItems.Clear();
|
||||
}
|
||||
threadListView.SelectionMode = selectionMode;
|
||||
}
|
||||
else
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -269,150 +259,159 @@ namespace Wino.Controls.Advanced
|
||||
|
||||
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 =>
|
||||
}
|
||||
else
|
||||
{
|
||||
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 (!preserveThreadExpanding)
|
||||
{
|
||||
if (removedItem is MailItemViewModel removedMailItemViewModel)
|
||||
{
|
||||
// Mail item un-selected.
|
||||
c.IsThreadExpanded = false;
|
||||
}
|
||||
|
||||
removedMailItemViewModel.IsSelected = false;
|
||||
WeakReferenceMessenger.Default.Send(new MailItemSelectionRemovedEvent(removedMailItemViewModel));
|
||||
}
|
||||
else if (removedItem is ThreadMailItemViewModel removedThreadItemViewModel)
|
||||
{
|
||||
removedThreadItemViewModel.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)
|
||||
if (e.AddedItems != null)
|
||||
{
|
||||
foreach (var addedItem in e.AddedItems)
|
||||
{
|
||||
foreach (var addedItem in e.AddedItems)
|
||||
if (addedItem is MailItemViewModel addedMailItemViewModel)
|
||||
{
|
||||
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)
|
||||
{
|
||||
// Mail item selected.
|
||||
|
||||
addedMailItemViewModel.IsSelected = true;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new MailItemSelectedEvent(addedMailItemViewModel));
|
||||
}
|
||||
else if (addedItem is ThreadMailItemViewModel threadMailItemViewModel)
|
||||
{
|
||||
if (IsThreadScrollingEnabled)
|
||||
if (internalScrollviewer != null && ContainerFromItem(threadMailItemViewModel) is FrameworkElement threadFrameworkElement)
|
||||
{
|
||||
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();
|
||||
internalScrollviewer.ScrollToElement(threadFrameworkElement, true, true, bringToTopOrLeft: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 =>
|
||||
// Try to select first item.
|
||||
if (GetThreadInternalListView(threadMailItemViewModel) is WinoListView internalListView)
|
||||
{
|
||||
// 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;
|
||||
internalListView.SelectFirstItem();
|
||||
}
|
||||
|
||||
firstMailItemViewModel.IsSelected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private WinoListView GetThreadInternalListView(ThreadMailItemViewModel threadMailItemViewModel)
|
||||
if (!IsThreadListView)
|
||||
{
|
||||
var itemContainer = ContainerFromItem(threadMailItemViewModel);
|
||||
|
||||
if (itemContainer is ListViewItem listItem)
|
||||
if (SelectionMode == ListViewSelectionMode.Extended && SelectedItems.Count == 1)
|
||||
{
|
||||
var expander = listItem.GetChildByName<WinoExpander>("ThreadExpander");
|
||||
// Only 1 single item is selected in extended mode for main list view.
|
||||
// We should un-select all thread items.
|
||||
|
||||
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)
|
||||
Items.Where(a => a is ThreadMailItemViewModel).Cast<ThreadMailItemViewModel>().ForEach(c =>
|
||||
{
|
||||
var threadListView = GetThreadInternalListView(threadMailItemViewModel);
|
||||
threadListView?.Dispose();
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,186 +13,185 @@ using Windows.UI.Xaml.Media.Imaging;
|
||||
using Windows.UI.Xaml.Shapes;
|
||||
using Wino.Core.UWP.Services;
|
||||
|
||||
namespace Wino.Controls
|
||||
namespace Wino.Controls;
|
||||
|
||||
public partial class ImagePreviewControl : Control
|
||||
{
|
||||
public partial class ImagePreviewControl : Control
|
||||
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
|
||||
{
|
||||
private const string PART_EllipseInitialsGrid = "EllipseInitialsGrid";
|
||||
private const string PART_InitialsTextBlock = "InitialsTextBlock";
|
||||
private const string PART_KnownHostImage = "KnownHostImage";
|
||||
private const string PART_Ellipse = "Ellipse";
|
||||
get { return (string)GetValue(SenderContactPictureProperty); }
|
||||
set { SetValue(SenderContactPictureProperty, value); }
|
||||
}
|
||||
|
||||
#region Dependency Properties
|
||||
public string FromName
|
||||
{
|
||||
get { return (string)GetValue(FromNameProperty); }
|
||||
set { SetValue(FromNameProperty, 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)));
|
||||
public string FromAddress
|
||||
{
|
||||
get { return (string)GetValue(FromAddressProperty); }
|
||||
set { SetValue(FromAddressProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets base64 string of the sender contact picture.
|
||||
/// </summary>
|
||||
public string 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)
|
||||
{
|
||||
get { return (string)GetValue(SenderContactPictureProperty); }
|
||||
set { SetValue(SenderContactPictureProperty, value); }
|
||||
contactPictureLoadingCancellationTokenSource.Cancel();
|
||||
}
|
||||
|
||||
public string FromName
|
||||
var host = ThumbnailService.GetHost(FromAddress);
|
||||
|
||||
bool isKnownHost = false;
|
||||
|
||||
if (!string.IsNullOrEmpty(host))
|
||||
{
|
||||
get { return (string)GetValue(FromNameProperty); }
|
||||
set { SetValue(FromNameProperty, value); }
|
||||
var tuple = ThumbnailService.CheckIsKnown(host);
|
||||
|
||||
isKnownHost = tuple.Item1;
|
||||
host = tuple.Item2;
|
||||
}
|
||||
|
||||
public string FromAddress
|
||||
if (isKnownHost)
|
||||
{
|
||||
get { return (string)GetValue(FromAddressProperty); }
|
||||
set { SetValue(FromAddressProperty, value); }
|
||||
// Unrealize others.
|
||||
|
||||
KnownHostImage.Visibility = Visibility.Visible;
|
||||
InitialsGrid.Visibility = Visibility.Collapsed;
|
||||
|
||||
// Apply company logo.
|
||||
KnownHostImage.Source = new BitmapImage(new Uri(ThumbnailService.GetKnownHostImage(host)));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Ellipse Ellipse;
|
||||
private Grid InitialsGrid;
|
||||
private TextBlock InitialsTextblock;
|
||||
private Image KnownHostImage;
|
||||
private CancellationTokenSource contactPictureLoadingCancellationTokenSource;
|
||||
|
||||
public ImagePreviewControl()
|
||||
else
|
||||
{
|
||||
DefaultStyleKey = nameof(ImagePreviewControl);
|
||||
}
|
||||
KnownHostImage.Visibility = Visibility.Collapsed;
|
||||
InitialsGrid.Visibility = Visibility.Visible;
|
||||
|
||||
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)
|
||||
if (!string.IsNullOrEmpty(SenderContactPicture))
|
||||
{
|
||||
contactPictureLoadingCancellationTokenSource.Cancel();
|
||||
}
|
||||
contactPictureLoadingCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
var host = ThumbnailService.GetHost(FromAddress);
|
||||
try
|
||||
{
|
||||
var brush = await GetContactImageBrushAsync();
|
||||
|
||||
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)));
|
||||
if (!contactPictureLoadingCancellationTokenSource?.Token.IsCancellationRequested ?? false)
|
||||
{
|
||||
Ellipse.Fill = brush;
|
||||
InitialsTextblock.Text = string.Empty;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Log exception.
|
||||
Debugger.Break();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
KnownHostImage.Visibility = Visibility.Collapsed;
|
||||
InitialsGrid.Visibility = Visibility.Visible;
|
||||
var colorHash = new ColorHash();
|
||||
var rgb = colorHash.Rgb(FromAddress);
|
||||
|
||||
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);
|
||||
}
|
||||
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))
|
||||
{
|
||||
name = FromAddress;
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
name = FromAddress;
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,200 +10,199 @@ using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Extensions;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
|
||||
namespace Wino.Controls
|
||||
namespace Wino.Controls;
|
||||
|
||||
public sealed partial class MailItemDisplayInformationControl : UserControl
|
||||
{
|
||||
public sealed partial class MailItemDisplayInformationControl : UserControl
|
||||
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 ImagePreviewControl GetImagePreviewControl() => ContactImage;
|
||||
get { return (bool)GetValue(IsThreadExpandedProperty); }
|
||||
set { SetValue(IsThreadExpandedProperty, value); }
|
||||
}
|
||||
|
||||
public bool IsRunningHoverAction { get; set; }
|
||||
public bool IsThreadExpanderVisible
|
||||
{
|
||||
get { return (bool)GetValue(IsThreadExpanderVisibleProperty); }
|
||||
set { SetValue(IsThreadExpanderVisibleProperty, 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 Prefer24HourTimeFormat
|
||||
{
|
||||
get { return (bool)GetValue(Prefer24HourTimeFormatProperty); }
|
||||
set { SetValue(Prefer24HourTimeFormatProperty, value); }
|
||||
}
|
||||
|
||||
public bool IsThreadExpanded
|
||||
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)
|
||||
{
|
||||
get { return (bool)GetValue(IsThreadExpandedProperty); }
|
||||
set { SetValue(IsThreadExpandedProperty, value); }
|
||||
}
|
||||
|
||||
public bool IsThreadExpanderVisible
|
||||
{
|
||||
get { return (bool)GetValue(IsThreadExpanderVisibleProperty); }
|
||||
set { SetValue(IsThreadExpanderVisibleProperty, value); }
|
||||
}
|
||||
|
||||
public bool Prefer24HourTimeFormat
|
||||
{
|
||||
get { return (bool)GetValue(Prefer24HourTimeFormatProperty); }
|
||||
set { SetValue(Prefer24HourTimeFormatProperty, value); }
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,71 +3,70 @@ using System.Windows.Input;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace Wino.Controls
|
||||
namespace Wino.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Templated button for each setting in Settings Dialog.
|
||||
/// </summary>
|
||||
public partial class SettingsMenuItemControl : Control
|
||||
{
|
||||
/// <summary>
|
||||
/// Templated button for each setting in Settings Dialog.
|
||||
/// </summary>
|
||||
public partial class SettingsMenuItemControl : Control
|
||||
public string Title
|
||||
{
|
||||
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));
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -4,126 +4,125 @@ using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Hosting;
|
||||
using Windows.UI.Xaml.Markup;
|
||||
|
||||
namespace Wino.Controls
|
||||
namespace Wino.Controls;
|
||||
|
||||
[ContentProperty(Name = nameof(Content))]
|
||||
public partial class WinoExpander : Control
|
||||
{
|
||||
[ContentProperty(Name = nameof(Content))]
|
||||
public partial class WinoExpander : Control
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
get { return (UIElement)GetValue(ContentProperty); }
|
||||
set { SetValue(ContentProperty, value); }
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
get { return (UIElement)GetValue(ContentProperty); }
|
||||
set { SetValue(ContentProperty, value); }
|
||||
}
|
||||
|
||||
#region Settings
|
||||
|
||||
public class WinoExpanderTemplateSettings : DependencyObject
|
||||
public WinoExpanderTemplateSettings TemplateSettings
|
||||
{
|
||||
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); }
|
||||
}
|
||||
get { return (WinoExpanderTemplateSettings)GetValue(TemplateSettingsProperty); }
|
||||
set { SetValue(TemplateSettingsProperty, value); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
#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
|
||||
|
||||
@@ -6,77 +6,76 @@ using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Helpers;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
|
||||
namespace Wino.Controls
|
||||
namespace Wino.Controls;
|
||||
|
||||
public partial class WinoSwipeControlItems : SwipeItems
|
||||
{
|
||||
public partial class WinoSwipeControlItems : SwipeItems
|
||||
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 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));
|
||||
get { return (IMailItem)GetValue(MailItemProperty); }
|
||||
set { SetValue(MailItemProperty, value); }
|
||||
}
|
||||
|
||||
public IMailItem MailItem
|
||||
|
||||
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)
|
||||
{
|
||||
get { return (IMailItem)GetValue(MailItemProperty); }
|
||||
set { SetValue(MailItemProperty, value); }
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,49 +4,48 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Dialogs
|
||||
namespace Wino.Dialogs;
|
||||
|
||||
public sealed partial class AccountReorderDialog : ContentDialog
|
||||
{
|
||||
public sealed partial class AccountReorderDialog : ContentDialog
|
||||
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 ObservableCollection<IAccountProviderDetailViewModel> Accounts { get; }
|
||||
Accounts = accounts;
|
||||
|
||||
private int count;
|
||||
private bool isOrdering = false;
|
||||
count = accounts.Count;
|
||||
|
||||
private readonly IAccountService _accountService = App.Current.Services.GetService<IAccountService>();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public AccountReorderDialog(ObservableCollection<IAccountProviderDetailViewModel> accounts)
|
||||
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)
|
||||
{
|
||||
Accounts = accounts;
|
||||
// Order is completed. Apply changes.
|
||||
|
||||
count = accounts.Count;
|
||||
var dict = Accounts.ToDictionary(a => a.StartupEntityId, a => Accounts.IndexOf(a));
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
await _accountService.UpdateAccountOrdersAsync(dict);
|
||||
|
||||
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;
|
||||
}
|
||||
isOrdering = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,28 +3,27 @@ using Windows.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Dialogs
|
||||
namespace Wino.Dialogs;
|
||||
|
||||
public sealed partial class CreateAccountAliasDialog : ContentDialog, ICreateAccountAliasDialog
|
||||
{
|
||||
public sealed partial class CreateAccountAliasDialog : ContentDialog, ICreateAccountAliasDialog
|
||||
public MailAccountAlias CreatedAccountAlias { get; set; }
|
||||
public CreateAccountAliasDialog()
|
||||
{
|
||||
public MailAccountAlias CreatedAccountAlias { get; set; }
|
||||
public CreateAccountAliasDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void CreateClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
private void CreateClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
CreatedAccountAlias = new MailAccountAlias
|
||||
{
|
||||
CreatedAccountAlias = new MailAccountAlias
|
||||
{
|
||||
AliasAddress = AliasTextBox.Text.Trim(),
|
||||
ReplyToAddress = ReplyToTextBox.Text.Trim(),
|
||||
Id = Guid.NewGuid(),
|
||||
IsPrimary = false,
|
||||
IsVerified = false
|
||||
};
|
||||
AliasAddress = AliasTextBox.Text.Trim(),
|
||||
ReplyToAddress = ReplyToTextBox.Text.Trim(),
|
||||
Id = Guid.NewGuid(),
|
||||
IsPrimary = false,
|
||||
IsVerified = false
|
||||
};
|
||||
|
||||
Hide();
|
||||
}
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,22 +3,21 @@ using Windows.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
|
||||
namespace Wino.Mail.Dialogs
|
||||
{
|
||||
public sealed partial class MessageSourceDialog : ContentDialog
|
||||
{
|
||||
private readonly IClipboardService _clipboardService = App.Current.Services.GetService<IClipboardService>();
|
||||
public string MessageSource { get; set; }
|
||||
public bool Copied { get; set; }
|
||||
public MessageSourceDialog()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
namespace Wino.Mail.Dialogs;
|
||||
|
||||
private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
_clipboardService.CopyClipboardAsync(MessageSource);
|
||||
Copied = true;
|
||||
}
|
||||
public sealed partial class MessageSourceDialog : ContentDialog
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,72 +4,71 @@ using Windows.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
|
||||
namespace Wino.Dialogs
|
||||
namespace Wino.Dialogs;
|
||||
|
||||
public sealed partial class MoveMailDialog : ContentDialog
|
||||
{
|
||||
public sealed partial class MoveMailDialog : ContentDialog
|
||||
public IMailItemFolder SelectedFolder
|
||||
{
|
||||
public IMailItemFolder SelectedFolder
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
dialog.VerifySelection();
|
||||
}
|
||||
}
|
||||
|
||||
private void VerifySelection()
|
||||
{
|
||||
if (SelectedFolder != null)
|
||||
{
|
||||
// Don't select non-move capable folders like Categories or More.
|
||||
|
||||
if (!SelectedFolder.IsMoveTarget)
|
||||
{
|
||||
// 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.SelectedItem != null)
|
||||
{
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CancelClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
Hide();
|
||||
dialog.VerifySelection();
|
||||
}
|
||||
}
|
||||
|
||||
private void VerifySelection()
|
||||
{
|
||||
if (SelectedFolder != null)
|
||||
{
|
||||
// Don't select non-move capable folders like Categories or More.
|
||||
|
||||
if (!SelectedFolder.IsMoveTarget)
|
||||
{
|
||||
// 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.SelectedItem != null)
|
||||
{
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CancelClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,102 +11,101 @@ using Wino.Core.Domain.Models.Accounts;
|
||||
using Wino.Messaging.Client.Mails;
|
||||
using Wino.Views.ImapSetup;
|
||||
|
||||
namespace Wino.Dialogs
|
||||
namespace Wino.Dialogs;
|
||||
|
||||
public enum ImapSetupState
|
||||
{
|
||||
public enum ImapSetupState
|
||||
{
|
||||
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()
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (ImapFrame.CanGoBack)
|
||||
{
|
||||
// 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 });
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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()
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (ImapFrame.CanGoBack)
|
||||
{
|
||||
// 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 });
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -15,363 +15,362 @@ using Wino.Core.Domain.Models.Reader;
|
||||
using Wino.Core.UWP.Extensions;
|
||||
using Wino.Views.Settings;
|
||||
|
||||
namespace Wino.Dialogs
|
||||
namespace Wino.Dialogs;
|
||||
|
||||
public sealed partial class SignatureEditorDialog : ContentDialog
|
||||
{
|
||||
public sealed partial class SignatureEditorDialog : ContentDialog
|
||||
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
|
||||
{
|
||||
private Func<Task<string>> _getHTMLBodyFunction;
|
||||
private readonly TaskCompletionSource<bool> _domLoadedTask = new TaskCompletionSource<bool>();
|
||||
get { return (bool)GetValue(IsComposerDarkModeProperty); }
|
||||
set { SetValue(IsComposerDarkModeProperty, value); }
|
||||
}
|
||||
|
||||
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 static readonly DependencyProperty IsComposerDarkModeProperty = DependencyProperty.Register(nameof(IsComposerDarkMode), typeof(bool), typeof(SignatureManagementPage), new PropertyMetadata(false, OnIsComposerDarkModeChanged));
|
||||
|
||||
public AccountSignature Result;
|
||||
public SignatureEditorDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
public bool IsComposerDarkMode
|
||||
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
|
||||
{
|
||||
get { return (bool)GetValue(IsComposerDarkModeProperty); }
|
||||
set { SetValue(IsComposerDarkModeProperty, value); }
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsComposerDarkModeProperty = DependencyProperty.Register(nameof(IsComposerDarkMode), typeof(bool), typeof(SignatureManagementPage), new PropertyMetadata(false, OnIsComposerDarkModeChanged));
|
||||
private async void SaveClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
var newSignature = Regex.Unescape(await _getHTMLBodyFunction());
|
||||
|
||||
public SignatureEditorDialog()
|
||||
if (Result == null)
|
||||
{
|
||||
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 = signatureModel.Id,
|
||||
Name = signatureModel.Name,
|
||||
MailAccountId = signatureModel.MailAccountId,
|
||||
HtmlBody = signatureModel.HtmlBody
|
||||
Id = Guid.NewGuid(),
|
||||
Name = SignatureNameTextBox.Text.Trim(),
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
Result.Name = SignatureNameTextBox.Text.Trim();
|
||||
Result.HtmlBody = newSignature;
|
||||
}
|
||||
|
||||
private async void SignatureDialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args)
|
||||
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)
|
||||
{
|
||||
Chromium.CoreWebView2Initialized -= ChromiumInitialized;
|
||||
Chromium.CoreWebView2Initialized += ChromiumInitialized;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
await Chromium.EnsureCoreWebView2Async();
|
||||
private async Task<string> InvokeScriptSafeAsync(string function)
|
||||
{
|
||||
if (Chromium == null) return string.Empty;
|
||||
|
||||
_getHTMLBodyFunction = new Func<Task<string>>(async () =>
|
||||
{
|
||||
var editorContent = await Chromium.ExecuteScriptFunctionSafeAsync("GetHTMLContent");
|
||||
try
|
||||
{
|
||||
|
||||
return JsonSerializer.Deserialize(editorContent, BasicTypesJsonContext.Default.String);
|
||||
});
|
||||
|
||||
var underlyingThemeService = App.Current.Services.GetService<IUnderlyingThemeService>();
|
||||
|
||||
IsComposerDarkMode = underlyingThemeService.IsUnderlyingThemeDark();
|
||||
|
||||
await RenderInternalAsync(Result?.HtmlBody ?? string.Empty);
|
||||
return await Chromium.ExecuteScriptAsync(function);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
|
||||
private void DialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args)
|
||||
{
|
||||
Chromium.CoreWebView2Initialized -= ChromiumInitialized;
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (Chromium.CoreWebView2 != null)
|
||||
{
|
||||
Chromium.CoreWebView2.DOMContentLoaded -= DOMLoaded;
|
||||
Chromium.CoreWebView2.WebMessageReceived -= ScriptMessageReceived;
|
||||
}
|
||||
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
|
||||
{
|
||||
return await Chromium.ExecuteScriptAsync("getSelectedText();");
|
||||
}
|
||||
catch { }
|
||||
|
||||
private async void SaveClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
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)
|
||||
{
|
||||
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();
|
||||
Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark;
|
||||
await InvokeScriptSafeAsync("SetDarkEditor();");
|
||||
}
|
||||
|
||||
private void CancelClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
else
|
||||
{
|
||||
Hide();
|
||||
Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Light;
|
||||
await InvokeScriptSafeAsync("SetLightEditor();");
|
||||
}
|
||||
}
|
||||
|
||||
private async void BoldButtonClicked(object sender, RoutedEventArgs e)
|
||||
private async Task RenderInternalAsync(string htmlBody)
|
||||
{
|
||||
await _domLoadedTask.Task;
|
||||
|
||||
await UpdateEditorThemeAsync();
|
||||
await InitializeEditorAsync();
|
||||
|
||||
if (string.IsNullOrEmpty(htmlBody))
|
||||
{
|
||||
await InvokeScriptSafeAsync("editor.execCommand('bold')");
|
||||
await Chromium.ExecuteScriptFunctionAsync("RenderHTML", parameters: JsonSerializer.Serialize(" ", BasicTypesJsonContext.Default.String));
|
||||
}
|
||||
|
||||
private async void ItalicButtonClicked(object sender, RoutedEventArgs e)
|
||||
else
|
||||
{
|
||||
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 Chromium.ExecuteScriptFunctionAsync("RenderHTML", parameters: JsonSerializer.Serialize(htmlBody, BasicTypesJsonContext.Default.String));
|
||||
|
||||
await FocusEditorAsync();
|
||||
}
|
||||
|
||||
private async Task<string> TryGetSelectedTextAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
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 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;
|
||||
}
|
||||
|
||||
@@ -8,65 +8,64 @@ using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Exceptions;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
|
||||
namespace Wino.Dialogs
|
||||
namespace Wino.Dialogs;
|
||||
|
||||
public sealed partial class SystemFolderConfigurationDialog : ContentDialog
|
||||
{
|
||||
public sealed partial class SystemFolderConfigurationDialog : ContentDialog
|
||||
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)
|
||||
{
|
||||
private bool canDismissDialog = false;
|
||||
InitializeComponent();
|
||||
|
||||
public SystemFolderConfiguration Configuration { get; set; }
|
||||
public List<MailItemFolder> AvailableFolders { get; }
|
||||
AvailableFolders = availableFolders;
|
||||
|
||||
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; }
|
||||
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 SystemFolderConfigurationDialog(List<MailItemFolder> availableFolders)
|
||||
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>()
|
||||
{
|
||||
InitializeComponent();
|
||||
Sent, Draft, Archive, Trash, Junk
|
||||
};
|
||||
|
||||
AvailableFolders = availableFolders;
|
||||
if (allSpecialFolders.Any(a => a != null && a.SpecialFolderType == SpecialFolderType.Inbox))
|
||||
ValidationErrorTextBlock.Text = Translator.SystemFolderConfigDialogValidation_InboxSelected;
|
||||
|
||||
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);
|
||||
}
|
||||
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;
|
||||
|
||||
private void DialogClosing(ContentDialog sender, ContentDialogClosingEventArgs args)
|
||||
// Check if we can save.
|
||||
if (string.IsNullOrEmpty(ValidationErrorTextBlock.Text))
|
||||
{
|
||||
args.Cancel = !canDismissDialog;
|
||||
}
|
||||
var configuration = new SystemFolderConfiguration(Sent, Draft, Archive, Trash, Junk);
|
||||
|
||||
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;
|
||||
}
|
||||
canDismissDialog = true;
|
||||
Configuration = configuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,53 +7,52 @@ using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.UWP.Controls;
|
||||
using Wino.Helpers;
|
||||
|
||||
namespace Wino.MenuFlyouts
|
||||
namespace Wino.MenuFlyouts;
|
||||
|
||||
public partial class AccountSelectorFlyout : MenuFlyout, IDisposable
|
||||
{
|
||||
public partial class AccountSelectorFlyout : MenuFlyout, IDisposable
|
||||
private readonly IEnumerable<MailAccount> _accounts;
|
||||
private readonly Func<MailAccount, Task> _onItemSelection;
|
||||
|
||||
public AccountSelectorFlyout(IEnumerable<MailAccount> accounts, Func<MailAccount, Task> onItemSelection)
|
||||
{
|
||||
private readonly IEnumerable<MailAccount> _accounts;
|
||||
private readonly Func<MailAccount, Task> _onItemSelection;
|
||||
_accounts = accounts;
|
||||
_onItemSelection = onItemSelection;
|
||||
|
||||
public AccountSelectorFlyout(IEnumerable<MailAccount> accounts, Func<MailAccount, Task> onItemSelection)
|
||||
foreach (var account in _accounts)
|
||||
{
|
||||
_accounts = accounts;
|
||||
_onItemSelection = 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 };
|
||||
|
||||
foreach (var account in _accounts)
|
||||
{
|
||||
var pathData = new WinoFontIcon() { Icon = XamlHelpers.GetProviderIcon(account) };
|
||||
var menuItem = new MenuFlyoutItem() { Tag = account.Address, Icon = pathData, Text = $"{account.Name} ({account.Address})", MinHeight = 55 };
|
||||
|
||||
menuItem.Click += AccountClicked;
|
||||
Items.Add(menuItem);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
menuItem.Click += AccountClicked;
|
||||
Items.Add(menuItem);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,213 +7,212 @@ using Wino.Core.Domain.Models.Reader;
|
||||
using Wino.Core.UWP.Controls;
|
||||
using Wino.Helpers;
|
||||
|
||||
namespace Wino.MenuFlyouts
|
||||
namespace Wino.MenuFlyouts;
|
||||
|
||||
public partial class FilterMenuFlyout : MenuFlyout
|
||||
{
|
||||
public partial class FilterMenuFlyout : MenuFlyout
|
||||
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 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));
|
||||
get { return (IRelayCommand<FilterOption>)GetValue(SelectedFilterChangedCommandProperty); }
|
||||
set { SetValue(SelectedFilterChangedCommandProperty, value); }
|
||||
}
|
||||
|
||||
public IRelayCommand<FilterOption> SelectedFilterChangedCommand
|
||||
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)
|
||||
{
|
||||
get { return (IRelayCommand<FilterOption>)GetValue(SelectedFilterChangedCommandProperty); }
|
||||
set { SetValue(SelectedFilterChangedCommandProperty, value); }
|
||||
bar.SelectFilterOption(bar.SelectedFilterOption);
|
||||
bar.SelectedFilterChangedCommand?.Execute(bar.SelectedFilterOption);
|
||||
}
|
||||
}
|
||||
|
||||
public IRelayCommand<SortingOption> SelectedSortingOptionChangedCommand
|
||||
private static void OnSelectedSortingOptionChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
if (obj is FilterMenuFlyout bar)
|
||||
{
|
||||
get { return (IRelayCommand<SortingOption>)GetValue(SelectedSortingOptionChangedCommandProperty); }
|
||||
set { SetValue(SelectedSortingOptionChangedCommandProperty, value); }
|
||||
bar.SelectSortingOption(bar.SelectedSortingOption);
|
||||
bar.SelectedSortingOptionChangedCommand?.Execute(bar.SelectedSortingOption);
|
||||
}
|
||||
}
|
||||
|
||||
public List<FilterOption> FilterOptions
|
||||
private ToggleMenuFlyoutItem CreateFilterToggleButton(FilterOption option)
|
||||
{
|
||||
var button = new ToggleMenuFlyoutItem()
|
||||
{
|
||||
get { return (List<FilterOption>)GetValue(FilterOptionsProperty); }
|
||||
set { SetValue(FilterOptionsProperty, value); }
|
||||
}
|
||||
Text = option.Title,
|
||||
Tag = option,
|
||||
Icon = new WinoFontIcon() { Icon = XamlHelpers.GetWinoIconGlyph(option.Type) },
|
||||
IsChecked = option == SelectedFilterOption
|
||||
};
|
||||
|
||||
public List<SortingOption> SortingOptions
|
||||
button.Click += FilterToggleChecked;
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
private ToggleMenuFlyoutItem CreateSortingToggleButton(SortingOption option)
|
||||
{
|
||||
var button = new ToggleMenuFlyoutItem()
|
||||
{
|
||||
get { return (List<SortingOption>)GetValue(SortingOptionsProperty); }
|
||||
set { SetValue(SortingOptionsProperty, value); }
|
||||
}
|
||||
Text = option.Title,
|
||||
Tag = option,
|
||||
Icon = new WinoFontIcon() { Icon = XamlHelpers.GetWinoIconGlyph(option.Type)},
|
||||
IsChecked = option == SelectedSortingOption
|
||||
};
|
||||
|
||||
public FilterOption SelectedFilterOption
|
||||
button.Click += SortingOptionChecked;
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
private void SortingOptionChecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is ToggleMenuFlyoutItem button)
|
||||
{
|
||||
get { return (FilterOption)GetValue(SelectedFilterOptionProperty); }
|
||||
set { SetValue(SelectedFilterOptionProperty, value); }
|
||||
}
|
||||
button.IsHitTestVisible = false;
|
||||
|
||||
public SortingOption SelectedSortingOption
|
||||
var optionModel = button.Tag as SortingOption;
|
||||
|
||||
SelectSortingOption(optionModel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void FilterToggleChecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is ToggleMenuFlyoutItem button)
|
||||
{
|
||||
get { return (SortingOption)GetValue(SelectedSortingOptionProperty); }
|
||||
set { SetValue(SelectedSortingOptionProperty, value); }
|
||||
button.IsHitTestVisible = false;
|
||||
|
||||
var optionModel = button.Tag as FilterOption;
|
||||
|
||||
SelectFilterOption(optionModel);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnSelectedFilterOptionChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
if (obj is FilterMenuFlyout bar)
|
||||
{
|
||||
bar.SelectFilterOption(bar.SelectedFilterOption);
|
||||
bar.SelectedFilterChangedCommand?.Execute(bar.SelectedFilterOption);
|
||||
}
|
||||
}
|
||||
private void SelectFilterOption(FilterOption option)
|
||||
{
|
||||
SelectedFilterOption = option;
|
||||
|
||||
private static void OnSelectedSortingOptionChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
if (obj is FilterMenuFlyout bar)
|
||||
{
|
||||
bar.SelectSortingOption(bar.SelectedSortingOption);
|
||||
bar.SelectedSortingOptionChangedCommand?.Execute(bar.SelectedSortingOption);
|
||||
}
|
||||
}
|
||||
UncheckOtherFilterOptions();
|
||||
}
|
||||
|
||||
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 SelectSortingOption(SortingOption option)
|
||||
{
|
||||
SelectedSortingOption = option;
|
||||
|
||||
button.Click += FilterToggleChecked;
|
||||
UncheckOtherSortingOptions();
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
private void UnregisterCheckedHandler(ToggleMenuFlyoutItem button)
|
||||
{
|
||||
button.Click -= FilterToggleChecked;
|
||||
}
|
||||
|
||||
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()
|
||||
private void UncheckOtherFilterOptions()
|
||||
{
|
||||
if (Items.Any())
|
||||
{
|
||||
foreach (var item in Items)
|
||||
{
|
||||
if (item is ToggleMenuFlyoutItem toggleButton)
|
||||
if (item is ToggleMenuFlyoutItem toggleButton && toggleButton.Tag is FilterOption option && option != SelectedFilterOption)
|
||||
{
|
||||
UnregisterCheckedHandler(toggleButton);
|
||||
toggleButton.IsChecked = false;
|
||||
toggleButton.IsHitTestVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnOptionsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
private void UncheckOtherSortingOptions()
|
||||
{
|
||||
if (Items.Any())
|
||||
{
|
||||
if (obj is FilterMenuFlyout bar && bar.SortingOptions != null && bar.FilterOptions != null)
|
||||
foreach (var item in Items)
|
||||
{
|
||||
bar.Dispose();
|
||||
|
||||
bar.Items.Clear();
|
||||
|
||||
if (bar.FilterOptions != null)
|
||||
if (item is ToggleMenuFlyoutItem toggleButton && toggleButton.Tag is SortingOption option && option != SelectedSortingOption)
|
||||
{
|
||||
foreach (var item in bar.FilterOptions)
|
||||
{
|
||||
bar.Items.Add(bar.CreateFilterToggleButton(item));
|
||||
}
|
||||
toggleButton.IsChecked = false;
|
||||
toggleButton.IsHitTestVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bar.Items.Add(new MenuFlyoutSeparator());
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var item in Items)
|
||||
{
|
||||
if (item is ToggleMenuFlyoutItem toggleButton)
|
||||
{
|
||||
UnregisterCheckedHandler(toggleButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sorting options.
|
||||
private static void OnOptionsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
if (obj is FilterMenuFlyout bar && bar.SortingOptions != null && bar.FilterOptions != null)
|
||||
{
|
||||
bar.Dispose();
|
||||
|
||||
if (bar.SortingOptions != null)
|
||||
bar.Items.Clear();
|
||||
|
||||
if (bar.FilterOptions != null)
|
||||
{
|
||||
foreach (var item in bar.FilterOptions)
|
||||
{
|
||||
foreach (var item in bar.SortingOptions)
|
||||
{
|
||||
bar.Items.Add(bar.CreateSortingToggleButton(item));
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,24 +4,23 @@ using Windows.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
|
||||
namespace Wino.MenuFlyouts.Context
|
||||
namespace Wino.MenuFlyouts.Context;
|
||||
|
||||
public partial class FolderOperationFlyout : WinoOperationFlyout<FolderOperationMenuItem>
|
||||
{
|
||||
public partial class FolderOperationFlyout : WinoOperationFlyout<FolderOperationMenuItem>
|
||||
public FolderOperationFlyout(IEnumerable<FolderOperationMenuItem> availableActions, TaskCompletionSource<FolderOperationMenuItem> completionSource) : base(availableActions, completionSource)
|
||||
{
|
||||
public FolderOperationFlyout(IEnumerable<FolderOperationMenuItem> availableActions, TaskCompletionSource<FolderOperationMenuItem> completionSource) : base(availableActions, completionSource)
|
||||
if (AvailableActions == null) return;
|
||||
|
||||
foreach (var action in AvailableActions)
|
||||
{
|
||||
if (AvailableActions == null) return;
|
||||
|
||||
foreach (var action in AvailableActions)
|
||||
if (action.Operation == FolderOperation.Seperator)
|
||||
Items.Add(new MenuFlyoutSeparator());
|
||||
else
|
||||
{
|
||||
if (action.Operation == FolderOperation.Seperator)
|
||||
Items.Add(new MenuFlyoutSeparator());
|
||||
else
|
||||
{
|
||||
var menuFlyoutItem = new FolderOperationMenuFlyoutItem(action, (c) => MenuItemClicked(c));
|
||||
var menuFlyoutItem = new FolderOperationMenuFlyoutItem(action, (c) => MenuItemClicked(c));
|
||||
|
||||
Items.Add(menuFlyoutItem);
|
||||
}
|
||||
Items.Add(menuFlyoutItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using System;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
|
||||
namespace Wino.MenuFlyouts
|
||||
namespace Wino.MenuFlyouts;
|
||||
|
||||
public partial class FolderOperationMenuFlyoutItem : WinoOperationFlyoutItem<FolderOperationMenuItem>
|
||||
{
|
||||
public partial class FolderOperationMenuFlyoutItem : WinoOperationFlyoutItem<FolderOperationMenuItem>
|
||||
public FolderOperationMenuFlyoutItem(FolderOperationMenuItem operationMenuItem, Action<FolderOperationMenuItem> clicked) : base(operationMenuItem, clicked)
|
||||
{
|
||||
public FolderOperationMenuFlyoutItem(FolderOperationMenuItem operationMenuItem, Action<FolderOperationMenuItem> clicked) : base(operationMenuItem, clicked)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,24 +4,23 @@ using Windows.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Menus;
|
||||
|
||||
namespace Wino.MenuFlyouts.Context
|
||||
namespace Wino.MenuFlyouts.Context;
|
||||
|
||||
public partial class MailOperationFlyout : WinoOperationFlyout<MailOperationMenuItem>
|
||||
{
|
||||
public partial class MailOperationFlyout : WinoOperationFlyout<MailOperationMenuItem>
|
||||
public MailOperationFlyout(IEnumerable<MailOperationMenuItem> availableActions, TaskCompletionSource<MailOperationMenuItem> completionSource) : base(availableActions, completionSource)
|
||||
{
|
||||
public MailOperationFlyout(IEnumerable<MailOperationMenuItem> availableActions, TaskCompletionSource<MailOperationMenuItem> completionSource) : base(availableActions, completionSource)
|
||||
if (AvailableActions == null) return;
|
||||
|
||||
foreach (var action in AvailableActions)
|
||||
{
|
||||
if (AvailableActions == null) return;
|
||||
|
||||
foreach (var action in AvailableActions)
|
||||
if (action.Operation == MailOperation.Seperator)
|
||||
Items.Add(new MenuFlyoutSeparator());
|
||||
else
|
||||
{
|
||||
if (action.Operation == MailOperation.Seperator)
|
||||
Items.Add(new MenuFlyoutSeparator());
|
||||
else
|
||||
{
|
||||
var menuFlyoutItem = new MailOperationMenuFlyoutItem(action, (c) => MenuItemClicked(c));
|
||||
var menuFlyoutItem = new MailOperationMenuFlyoutItem(action, (c) => MenuItemClicked(c));
|
||||
|
||||
Items.Add(menuFlyoutItem);
|
||||
}
|
||||
Items.Add(menuFlyoutItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using System;
|
||||
using Wino.Core.Domain.Models.Menus;
|
||||
|
||||
namespace Wino.MenuFlyouts.Context
|
||||
namespace Wino.MenuFlyouts.Context;
|
||||
|
||||
public partial class MailOperationMenuFlyoutItem : WinoOperationFlyoutItem<MailOperationMenuItem>
|
||||
{
|
||||
public partial class MailOperationMenuFlyoutItem : WinoOperationFlyoutItem<MailOperationMenuItem>
|
||||
public MailOperationMenuFlyoutItem(MailOperationMenuItem operationMenuItem, Action<MailOperationMenuItem> clicked) : base(operationMenuItem, clicked)
|
||||
{
|
||||
public MailOperationMenuFlyoutItem(MailOperationMenuItem operationMenuItem, Action<MailOperationMenuItem> clicked) : base(operationMenuItem, clicked)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,82 +6,81 @@ using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
|
||||
namespace Wino.MenuFlyouts
|
||||
namespace Wino.MenuFlyouts;
|
||||
|
||||
public class MoveButtonMenuItemClickedEventArgs
|
||||
{
|
||||
public class MoveButtonMenuItemClickedEventArgs
|
||||
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 Guid ClickedFolderId { get; set; }
|
||||
get { return (List<MailItemFolder>)GetValue(FoldersProperty); }
|
||||
set { SetValue(FoldersProperty, value); }
|
||||
}
|
||||
|
||||
public partial class MoveButtonFlyout : MenuFlyout
|
||||
private static void OnFoldersChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
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
|
||||
if (obj is MoveButtonFlyout menu)
|
||||
{
|
||||
get { return (List<MailItemFolder>)GetValue(FoldersProperty); }
|
||||
set { SetValue(FoldersProperty, value); }
|
||||
menu.InitializeMenu();
|
||||
}
|
||||
|
||||
private static void OnFoldersChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
|
||||
}
|
||||
|
||||
private void InitializeMenu()
|
||||
{
|
||||
Dispose();
|
||||
|
||||
Items.Clear();
|
||||
|
||||
if (Folders == null || !Folders.Any())
|
||||
return;
|
||||
|
||||
// TODO: Child folders.
|
||||
|
||||
foreach (var item in Folders)
|
||||
{
|
||||
if (obj is MoveButtonFlyout menu)
|
||||
// We don't expect this, but it crashes startup.
|
||||
// Just to be on the safe side.
|
||||
if (item.FolderName != null)
|
||||
{
|
||||
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)
|
||||
var folderMenuItem = new MenuFlyoutItem()
|
||||
{
|
||||
var folderMenuItem = new MenuFlyoutItem()
|
||||
{
|
||||
Tag = item,
|
||||
Text = item.FolderName
|
||||
};
|
||||
Tag = item,
|
||||
Text = item.FolderName
|
||||
};
|
||||
|
||||
folderMenuItem.Click += MenuItemClicked;
|
||||
folderMenuItem.Click += MenuItemClicked;
|
||||
|
||||
Items.Add(folderMenuItem);
|
||||
}
|
||||
Items.Add(folderMenuItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MenuItemClicked(object sender, RoutedEventArgs e)
|
||||
private void MenuItemClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var clickedFolder = (sender as MenuFlyoutItem).Tag as MailItemFolder;
|
||||
|
||||
MenuItemClick?.Invoke(this, new MoveButtonMenuItemClickedEventArgs()
|
||||
{
|
||||
var clickedFolder = (sender as MenuFlyoutItem).Tag as MailItemFolder;
|
||||
ClickedFolderId = clickedFolder.Id
|
||||
});
|
||||
}
|
||||
|
||||
MenuItemClick?.Invoke(this, new MoveButtonMenuItemClickedEventArgs()
|
||||
{
|
||||
ClickedFolderId = clickedFolder.Id
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var item in Items)
|
||||
{
|
||||
foreach (var item in Items)
|
||||
if (item is MenuFlyoutItem menuItem)
|
||||
{
|
||||
if (item is MenuFlyoutItem menuItem)
|
||||
{
|
||||
menuItem.Click -= MenuItemClicked;
|
||||
}
|
||||
menuItem.Click -= MenuItemClicked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,47 +3,46 @@ using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace Wino.MenuFlyouts
|
||||
namespace Wino.MenuFlyouts;
|
||||
|
||||
public partial class WinoOperationFlyout<TActionType> : MenuFlyout, IDisposable where TActionType : class
|
||||
{
|
||||
public partial class WinoOperationFlyout<TActionType> : MenuFlyout, IDisposable where TActionType : class
|
||||
public TActionType ClickedOperation { get; set; }
|
||||
|
||||
protected readonly IEnumerable<TActionType> AvailableActions;
|
||||
|
||||
private readonly TaskCompletionSource<TActionType> _completionSource;
|
||||
|
||||
public WinoOperationFlyout(IEnumerable<TActionType> availableActions, TaskCompletionSource<TActionType> completionSource)
|
||||
{
|
||||
public TActionType ClickedOperation { get; set; }
|
||||
_completionSource = completionSource;
|
||||
|
||||
protected readonly IEnumerable<TActionType> AvailableActions;
|
||||
AvailableActions = availableActions;
|
||||
|
||||
private readonly TaskCompletionSource<TActionType> _completionSource;
|
||||
Closing += FlyoutClosing;
|
||||
}
|
||||
|
||||
public WinoOperationFlyout(IEnumerable<TActionType> availableActions, 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)
|
||||
{
|
||||
_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)
|
||||
if (item is IDisposable disposableItem)
|
||||
{
|
||||
if (item is IDisposable disposableItem)
|
||||
{
|
||||
disposableItem.Dispose();
|
||||
}
|
||||
disposableItem.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,52 +7,51 @@ using Wino.Core.Domain.Models.Menus;
|
||||
using Wino.Core.UWP.Controls;
|
||||
using Wino.Helpers;
|
||||
|
||||
namespace Wino.MenuFlyouts
|
||||
namespace Wino.MenuFlyouts;
|
||||
|
||||
public partial class WinoOperationFlyoutItem<TOperationMenuItem> : MenuFlyoutItem, IDisposable where TOperationMenuItem : IMenuOperation
|
||||
{
|
||||
public partial class WinoOperationFlyoutItem<TOperationMenuItem> : MenuFlyoutItem, IDisposable where TOperationMenuItem : IMenuOperation
|
||||
private const double CustomHeight = 35;
|
||||
|
||||
public TOperationMenuItem Operation { get; set; }
|
||||
Action<TOperationMenuItem> Clicked { get; set; }
|
||||
|
||||
public WinoOperationFlyoutItem(TOperationMenuItem operationMenuItem, Action<TOperationMenuItem> clicked)
|
||||
{
|
||||
private const double CustomHeight = 35;
|
||||
Margin = new Thickness(4, 2, 4, 2);
|
||||
CornerRadius = new CornerRadius(6, 6, 6, 6);
|
||||
|
||||
public TOperationMenuItem Operation { get; set; }
|
||||
Action<TOperationMenuItem> Clicked { get; set; }
|
||||
MinHeight = CustomHeight;
|
||||
|
||||
public WinoOperationFlyoutItem(TOperationMenuItem operationMenuItem, Action<TOperationMenuItem> clicked)
|
||||
Operation = operationMenuItem;
|
||||
IsEnabled = operationMenuItem.IsEnabled;
|
||||
|
||||
if (Operation is FolderOperationMenuItem folderOperationMenuItem)
|
||||
{
|
||||
Margin = new Thickness(4, 2, 4, 2);
|
||||
CornerRadius = new CornerRadius(6, 6, 6, 6);
|
||||
var internalOperation = folderOperationMenuItem.Operation;
|
||||
|
||||
MinHeight = CustomHeight;
|
||||
Icon = new WinoFontIcon() { Icon = XamlHelpers.GetPathGeometry(internalOperation) };
|
||||
Text = XamlHelpers.GetOperationString(internalOperation);
|
||||
}
|
||||
else if (Operation is MailOperationMenuItem mailOperationMenuItem)
|
||||
{
|
||||
var internalOperation = mailOperationMenuItem.Operation;
|
||||
|
||||
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;
|
||||
Icon = new WinoFontIcon() { Icon = XamlHelpers.GetWinoIconGlyph(internalOperation) };
|
||||
Text = XamlHelpers.GetOperationString(internalOperation);
|
||||
}
|
||||
|
||||
private void MenuClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
Clicked(Operation);
|
||||
}
|
||||
Clicked = clicked;
|
||||
Click += MenuClicked;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Click -= MenuClicked;
|
||||
}
|
||||
private void MenuClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
Clicked(Operation);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Click -= MenuClicked;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,18 @@
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Wino.Core.ViewModels.Data;
|
||||
|
||||
namespace Wino.Selectors
|
||||
{
|
||||
public partial class AccountProviderViewModelTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public DataTemplate RootAccountTemplate { get; set; }
|
||||
public DataTemplate MergedAccountTemplate { get; set; }
|
||||
namespace Wino.Selectors;
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||
{
|
||||
if (item is MergedAccountProviderDetailViewModel)
|
||||
return MergedAccountTemplate;
|
||||
else
|
||||
return RootAccountTemplate;
|
||||
}
|
||||
public partial class AccountProviderViewModelTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,20 @@
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Wino.Core.ViewModels.Data;
|
||||
|
||||
namespace Wino.Selectors
|
||||
namespace Wino.Selectors;
|
||||
|
||||
public partial class AccountReorderTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public partial class AccountReorderTemplateSelector : DataTemplateSelector
|
||||
public DataTemplate MergedAccountReorderTemplate { get; set; }
|
||||
public DataTemplate RootAccountReorderTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||
{
|
||||
public DataTemplate MergedAccountReorderTemplate { get; set; }
|
||||
public DataTemplate RootAccountReorderTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||
if (item is MergedAccountProviderDetailViewModel)
|
||||
{
|
||||
if (item is MergedAccountProviderDetailViewModel)
|
||||
{
|
||||
return MergedAccountReorderTemplate;
|
||||
}
|
||||
|
||||
return RootAccountReorderTemplate;
|
||||
return MergedAccountReorderTemplate;
|
||||
}
|
||||
|
||||
return RootAccountReorderTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,17 @@
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
|
||||
namespace Wino.Selectors
|
||||
{
|
||||
public partial class MailItemContainerStyleSelector : StyleSelector
|
||||
{
|
||||
public Style Thread { get; set; }
|
||||
namespace Wino.Selectors;
|
||||
|
||||
protected override Style SelectStyleCore(object item, DependencyObject container)
|
||||
{
|
||||
if (item is ThreadMailItemViewModel)
|
||||
return Thread;
|
||||
else
|
||||
return base.SelectStyleCore(item, container);
|
||||
}
|
||||
public partial class MailItemContainerStyleSelector : StyleSelector
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,33 +2,32 @@
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Selectors
|
||||
namespace Wino.Selectors;
|
||||
|
||||
/// <summary>
|
||||
/// Template selector for previewing mail item display modes in Settings->Personalization page.
|
||||
/// </summary>
|
||||
public partial class MailItemDisplayModePreviewTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
/// <summary>
|
||||
/// Template selector for previewing mail item display modes in Settings->Personalization page.
|
||||
/// </summary>
|
||||
public partial class MailItemDisplayModePreviewTemplateSelector : DataTemplateSelector
|
||||
public DataTemplate CompactTemplate { get; set; }
|
||||
public DataTemplate MediumTemplate { get; set; }
|
||||
public DataTemplate SpaciousTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||
{
|
||||
public DataTemplate CompactTemplate { get; set; }
|
||||
public DataTemplate MediumTemplate { get; set; }
|
||||
public DataTemplate SpaciousTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||
if (item is MailListDisplayMode mode)
|
||||
{
|
||||
if (item is MailListDisplayMode mode)
|
||||
switch (mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case MailListDisplayMode.Spacious:
|
||||
return SpaciousTemplate;
|
||||
case MailListDisplayMode.Medium:
|
||||
return MediumTemplate;
|
||||
case MailListDisplayMode.Compact:
|
||||
return CompactTemplate;
|
||||
}
|
||||
case MailListDisplayMode.Spacious:
|
||||
return SpaciousTemplate;
|
||||
case MailListDisplayMode.Medium:
|
||||
return MediumTemplate;
|
||||
case MailListDisplayMode.Compact:
|
||||
return CompactTemplate;
|
||||
}
|
||||
|
||||
return base.SelectTemplateCore(item, container);
|
||||
}
|
||||
|
||||
return base.SelectTemplateCore(item, container);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,20 @@
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
|
||||
namespace Wino.Selectors
|
||||
namespace Wino.Selectors;
|
||||
|
||||
public partial class MailItemDisplaySelector : DataTemplateSelector
|
||||
{
|
||||
public partial class MailItemDisplaySelector : DataTemplateSelector
|
||||
public DataTemplate SingleMailItemTemplate { get; set; }
|
||||
public DataTemplate ThreadMailItemTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||
{
|
||||
public DataTemplate SingleMailItemTemplate { get; set; }
|
||||
public DataTemplate ThreadMailItemTemplate { get; set; }
|
||||
if (item is MailItemViewModel)
|
||||
return SingleMailItemTemplate;
|
||||
else if (item is ThreadMailItemViewModel)
|
||||
return ThreadMailItemTemplate;
|
||||
|
||||
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);
|
||||
}
|
||||
return base.SelectTemplateCore(item, container);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,188 +20,187 @@ using Wino.Mail.Dialogs;
|
||||
using Wino.Messaging.Server;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Services
|
||||
namespace Wino.Services;
|
||||
|
||||
public class DialogService : DialogServiceBase, IMailDialogService
|
||||
{
|
||||
public class DialogService : DialogServiceBase, IMailDialogService
|
||||
public DialogService(IThemeService themeService,
|
||||
IConfigurationService configurationService,
|
||||
IApplicationResourceManager<ResourceDictionary> applicationResourceManager) : base(themeService, configurationService, applicationResourceManager)
|
||||
{
|
||||
public DialogService(IThemeService themeService,
|
||||
IConfigurationService configurationService,
|
||||
IApplicationResourceManager<ResourceDictionary> applicationResourceManager) : base(themeService, configurationService, applicationResourceManager)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public override IAccountCreationDialog GetAccountCreationDialog(AccountCreationDialogResult accountCreationDialogResult)
|
||||
public override IAccountCreationDialog GetAccountCreationDialog(AccountCreationDialogResult accountCreationDialogResult)
|
||||
{
|
||||
if (accountCreationDialogResult.SpecialImapProviderDetails == null)
|
||||
{
|
||||
if (accountCreationDialogResult.SpecialImapProviderDetails == null)
|
||||
if (accountCreationDialogResult.ProviderType == MailProviderType.IMAP4)
|
||||
{
|
||||
if (accountCreationDialogResult.ProviderType == MailProviderType.IMAP4)
|
||||
return new NewImapSetupDialog
|
||||
{
|
||||
return new NewImapSetupDialog
|
||||
{
|
||||
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.GetAccountCreationDialog(accountCreationDialogResult);
|
||||
}
|
||||
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// Special IMAP provider like iCloud or Yahoo.
|
||||
|
||||
return base.GetAccountCreationDialog(accountCreationDialogResult);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MailAccount> ShowEditAccountDialogAsync(MailAccount account)
|
||||
else
|
||||
{
|
||||
var editAccountDialog = new AccountEditDialog(account)
|
||||
{
|
||||
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
|
||||
};
|
||||
// Special IMAP provider like iCloud or Yahoo.
|
||||
|
||||
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
|
||||
{
|
||||
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);
|
||||
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
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,31 @@
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Services
|
||||
namespace Wino.Services;
|
||||
|
||||
public class MailAuthenticatorConfiguration : IAuthenticatorConfig
|
||||
{
|
||||
public class MailAuthenticatorConfiguration : IAuthenticatorConfig
|
||||
public string OutlookAuthenticatorClientId => "b19c2035-d740-49ff-b297-de6ec561b208";
|
||||
|
||||
public string[] OutlookScope => new string[]
|
||||
{
|
||||
public string OutlookAuthenticatorClientId => "b19c2035-d740-49ff-b297-de6ec561b208";
|
||||
"email",
|
||||
"mail.readwrite",
|
||||
"offline_access",
|
||||
"mail.send",
|
||||
"Mail.Send.Shared",
|
||||
"Mail.ReadWrite.Shared",
|
||||
"User.Read"
|
||||
};
|
||||
|
||||
public string[] OutlookScope => new string[]
|
||||
{
|
||||
"email",
|
||||
"mail.readwrite",
|
||||
"offline_access",
|
||||
"mail.send",
|
||||
"Mail.Send.Shared",
|
||||
"Mail.ReadWrite.Shared",
|
||||
"User.Read"
|
||||
};
|
||||
public string GmailAuthenticatorClientId => "973025879644-s7b4ur9p3rlgop6a22u7iuptdc0brnrn.apps.googleusercontent.com";
|
||||
|
||||
public string GmailAuthenticatorClientId => "973025879644-s7b4ur9p3rlgop6a22u7iuptdc0brnrn.apps.googleusercontent.com";
|
||||
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[] 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";
|
||||
}
|
||||
public string GmailTokenStoreIdentifier => "WinoMailGmailTokenStore";
|
||||
}
|
||||
|
||||
@@ -15,148 +15,147 @@ using Wino.Views;
|
||||
using Wino.Views.Account;
|
||||
using Wino.Views.Settings;
|
||||
|
||||
namespace Wino.Services
|
||||
namespace Wino.Services;
|
||||
|
||||
public class NavigationService : NavigationServiceBase, INavigationService
|
||||
{
|
||||
public class NavigationService : NavigationServiceBase, INavigationService
|
||||
private readonly IStatePersistanceService _statePersistanceService;
|
||||
|
||||
private WinoPage[] _renderingPageTypes = new WinoPage[]
|
||||
{
|
||||
private readonly IStatePersistanceService _statePersistanceService;
|
||||
WinoPage.MailRenderingPage,
|
||||
WinoPage.ComposePage
|
||||
};
|
||||
|
||||
private WinoPage[] _renderingPageTypes = new WinoPage[]
|
||||
public NavigationService(IStatePersistanceService statePersistanceService)
|
||||
{
|
||||
_statePersistanceService = statePersistanceService;
|
||||
}
|
||||
|
||||
public Type GetPageType(WinoPage winoPage)
|
||||
{
|
||||
return winoPage switch
|
||||
{
|
||||
WinoPage.MailRenderingPage,
|
||||
WinoPage.ComposePage
|
||||
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 NavigationService(IStatePersistanceService statePersistanceService)
|
||||
{
|
||||
_statePersistanceService = statePersistanceService;
|
||||
}
|
||||
public Frame GetCoreFrame(NavigationReferenceFrame frameType)
|
||||
{
|
||||
if (Window.Current.Content is Frame appFrame)
|
||||
return WinoVisualTreeHelper.GetChildObject<Frame>(appFrame.Content as UIElement, frameType.ToString());
|
||||
|
||||
public Type GetPageType(WinoPage winoPage)
|
||||
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)
|
||||
{
|
||||
return winoPage switch
|
||||
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)
|
||||
{
|
||||
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,
|
||||
};
|
||||
}
|
||||
// No need for new navigation, just refresh the folder.
|
||||
WeakReferenceMessenger.Default.Send(new ActiveMailFolderChangedEvent(folderNavigationArgs.BaseFolderMenuItem, folderNavigationArgs.FolderInitLoadAwaitTask));
|
||||
WeakReferenceMessenger.Default.Send(new DisposeRenderingFrameRequested());
|
||||
|
||||
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 true;
|
||||
}
|
||||
|
||||
return false;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -4,32 +4,31 @@ using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
|
||||
namespace Wino.Mail.Services
|
||||
namespace Wino.Mail.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service that is returning available provider details.
|
||||
/// </summary>
|
||||
public class ProviderService : IProviderService
|
||||
{
|
||||
/// <summary>
|
||||
/// Service that is returning available provider details.
|
||||
/// </summary>
|
||||
public class ProviderService : IProviderService
|
||||
public IProviderDetail GetProviderDetail(MailProviderType type)
|
||||
{
|
||||
public IProviderDetail GetProviderDetail(MailProviderType type)
|
||||
var details = GetAvailableProviders();
|
||||
|
||||
return details.FirstOrDefault(a => a.Type == type);
|
||||
}
|
||||
|
||||
public List<IProviderDetail> GetAvailableProviders()
|
||||
{
|
||||
var providerList = new List<IProviderDetail>
|
||||
{
|
||||
var details = GetAvailableProviders();
|
||||
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 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;
|
||||
}
|
||||
return providerList;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,12 +1,11 @@
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
namespace Wino.Styles
|
||||
namespace Wino.Styles;
|
||||
|
||||
partial class WinoExpanderStyle : ResourceDictionary
|
||||
{
|
||||
partial class WinoExpanderStyle : ResourceDictionary
|
||||
public WinoExpanderStyle()
|
||||
{
|
||||
public WinoExpanderStyle()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views.Settings
|
||||
namespace Wino.Views.Settings;
|
||||
|
||||
public sealed partial class AboutPage : AboutPageAbstract
|
||||
{
|
||||
public sealed partial class AboutPage : AboutPageAbstract
|
||||
public AboutPage()
|
||||
{
|
||||
public AboutPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Wino.Core.UWP;
|
||||
using Wino.Core.ViewModels;
|
||||
|
||||
namespace Wino.Views.Abstract
|
||||
namespace Wino.Views.Abstract;
|
||||
|
||||
public abstract class AboutPageAbstract : BasePage<AboutPageViewModel>
|
||||
{
|
||||
public abstract class AboutPageAbstract : BasePage<AboutPageViewModel>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Wino.Core.UWP;
|
||||
using Wino.Mail.ViewModels;
|
||||
|
||||
namespace Wino.Views.Abstract
|
||||
namespace Wino.Views.Abstract;
|
||||
|
||||
public abstract class AccountDetailsPageAbstract : BasePage<AccountDetailsPageViewModel>
|
||||
{
|
||||
public abstract class AccountDetailsPageAbstract : BasePage<AccountDetailsPageViewModel>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
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>
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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> { }
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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> { }
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Wino.Core.UWP;
|
||||
using Wino.Mail.ViewModels;
|
||||
|
||||
namespace Wino.Views.Abstract
|
||||
namespace Wino.Views.Abstract;
|
||||
|
||||
public abstract class AppShellAbstract : BasePage<AppShellViewModel>
|
||||
{
|
||||
public abstract class AppShellAbstract : BasePage<AppShellViewModel>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Wino.Core.UWP;
|
||||
using Wino.Mail.ViewModels;
|
||||
|
||||
namespace Wino.Views.Abstract
|
||||
namespace Wino.Views.Abstract;
|
||||
|
||||
public abstract class ComposePageAbstract : BasePage<ComposePageViewModel>
|
||||
{
|
||||
public abstract class ComposePageAbstract : BasePage<ComposePageViewModel>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Wino.Core.UWP;
|
||||
using Wino.Mail.ViewModels;
|
||||
|
||||
namespace Wino.Views.Abstract
|
||||
namespace Wino.Views.Abstract;
|
||||
|
||||
public abstract class IdlePageAbstract : BasePage<IdlePageViewModel>
|
||||
{
|
||||
public abstract class IdlePageAbstract : BasePage<IdlePageViewModel>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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> { }
|
||||
|
||||
@@ -2,26 +2,25 @@
|
||||
using Wino.Core.UWP;
|
||||
using Wino.Mail.ViewModels;
|
||||
|
||||
namespace Wino.Views.Abstract
|
||||
namespace Wino.Views.Abstract;
|
||||
|
||||
public abstract class MailRenderingPageAbstract : BasePage<MailRenderingPageViewModel>
|
||||
{
|
||||
public abstract class MailRenderingPageAbstract : BasePage<MailRenderingPageViewModel>
|
||||
public bool IsDarkEditor
|
||||
{
|
||||
public bool IsDarkEditor
|
||||
{
|
||||
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)
|
||||
{
|
||||
page.OnEditorThemeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnEditorThemeChanged() { }
|
||||
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)
|
||||
{
|
||||
page.OnEditorThemeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnEditorThemeChanged() { }
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Wino.Core.UWP;
|
||||
using Wino.Mail.ViewModels;
|
||||
|
||||
namespace Wino.Views.Abstract
|
||||
namespace Wino.Views.Abstract;
|
||||
|
||||
public abstract class MergedAccountDetailsPageAbstract : BasePage<MergedAccountDetailsPageViewModel>
|
||||
{
|
||||
public abstract class MergedAccountDetailsPageAbstract : BasePage<MergedAccountDetailsPageViewModel>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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> { }
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Wino.Core.ViewModels;
|
||||
|
||||
namespace Wino.Views.Abstract
|
||||
{
|
||||
public abstract class PersonalizationPageAbstract : SettingsPageBase<PersonalizationPageViewModel>
|
||||
{
|
||||
namespace Wino.Views.Abstract;
|
||||
|
||||
public abstract class PersonalizationPageAbstract : SettingsPageBase<PersonalizationPageViewModel>
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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> { }
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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> { }
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
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>
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,29 +2,28 @@
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views
|
||||
namespace Wino.Views;
|
||||
|
||||
public sealed partial class AccountDetailsPage : AccountDetailsPageAbstract
|
||||
{
|
||||
public sealed partial class AccountDetailsPage : AccountDetailsPageAbstract
|
||||
public AccountDetailsPage()
|
||||
{
|
||||
public AccountDetailsPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void SyncFolderToggled(object sender, Windows.UI.Xaml.RoutedEventArgs e)
|
||||
private async void SyncFolderToggled(object sender, Windows.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
if (sender is CheckBox checkBox && checkBox.Tag is IMailItemFolder folder)
|
||||
{
|
||||
if (sender is CheckBox checkBox && checkBox.Tag is IMailItemFolder folder)
|
||||
{
|
||||
await ViewModel.FolderSyncToggledAsync(folder, checkBox.IsChecked.GetValueOrDefault());
|
||||
}
|
||||
await ViewModel.FolderSyncToggledAsync(folder, checkBox.IsChecked.GetValueOrDefault());
|
||||
}
|
||||
}
|
||||
|
||||
private async void UnreadBadgeCheckboxToggled(object sender, Windows.UI.Xaml.RoutedEventArgs e)
|
||||
private async void UnreadBadgeCheckboxToggled(object sender, Windows.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
if (sender is CheckBox checkBox && checkBox.Tag is IMailItemFolder folder)
|
||||
{
|
||||
if (sender is CheckBox checkBox && checkBox.Tag is IMailItemFolder folder)
|
||||
{
|
||||
await ViewModel.FolderShowUnreadToggled(folder, checkBox.IsChecked.GetValueOrDefault());
|
||||
}
|
||||
await ViewModel.FolderShowUnreadToggled(folder, checkBox.IsChecked.GetValueOrDefault());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views
|
||||
{
|
||||
public sealed partial class AccountManagementPage : AccountManagementPageAbstract
|
||||
{
|
||||
public AccountManagementPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
namespace Wino.Views;
|
||||
|
||||
NavigationCacheMode = NavigationCacheMode.Enabled;
|
||||
}
|
||||
public sealed partial class AccountManagementPage : AccountManagementPageAbstract
|
||||
{
|
||||
public AccountManagementPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
NavigationCacheMode = NavigationCacheMode.Enabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
|
||||
namespace Wino.Views.Account
|
||||
namespace Wino.Views.Account;
|
||||
|
||||
public sealed partial class MergedAccountDetailsPage : MergedAccountDetailsPageAbstract
|
||||
{
|
||||
public sealed partial class MergedAccountDetailsPage : MergedAccountDetailsPageAbstract
|
||||
public MergedAccountDetailsPage()
|
||||
{
|
||||
public MergedAccountDetailsPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,11 @@
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views
|
||||
namespace Wino.Views;
|
||||
|
||||
public sealed partial class IdlePage : IdlePageAbstract
|
||||
{
|
||||
public sealed partial class IdlePage : IdlePageAbstract
|
||||
public IdlePage()
|
||||
{
|
||||
public IdlePage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,205 +11,204 @@ using Wino.Core.Domain.Models.AutoDiscovery;
|
||||
using Wino.Messaging.Client.Mails;
|
||||
|
||||
|
||||
namespace Wino.Views.ImapSetup
|
||||
namespace Wino.Views.ImapSetup;
|
||||
|
||||
public sealed partial class AdvancedImapSetupPage : Page
|
||||
{
|
||||
public sealed partial class AdvancedImapSetupPage : Page
|
||||
public List<ImapAuthenticationMethodModel> AvailableAuthenticationMethods { get; } = new List<ImapAuthenticationMethodModel>()
|
||||
{
|
||||
public List<ImapAuthenticationMethodModel> AvailableAuthenticationMethods { get; } = new List<ImapAuthenticationMethodModel>()
|
||||
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)
|
||||
{
|
||||
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)
|
||||
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
|
||||
};
|
||||
|
||||
public List<ImapConnectionSecurityModel> AvailableConnectionSecurities { get; set; } = new List<ImapConnectionSecurityModel>()
|
||||
if (UseSameCredentialsForSending)
|
||||
{
|
||||
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 = info.IncomingServerUsername;
|
||||
info.OutgoingServerPassword = info.IncomingServerPassword;
|
||||
}
|
||||
else
|
||||
{
|
||||
get { return (bool)GetValue(UseSameCredentialsForSendingProperty); }
|
||||
set { SetValue(UseSameCredentialsForSendingProperty, value); }
|
||||
info.OutgoingServerUsername = OutgoingUsernameBox.Text;
|
||||
info.OutgoingServerPassword = OutgoingPasswordBox.Password;
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty UseSameCredentialsForSendingProperty = DependencyProperty.Register(nameof(UseSameCredentialsForSending), typeof(bool), typeof(AdvancedImapSetupPage), new PropertyMetadata(true, OnUseSameCredentialsForSendingChanged));
|
||||
WeakReferenceMessenger.Default.Send(new ImapSetupNavigationRequested(typeof(TestingImapConnectionPage), info));
|
||||
}
|
||||
|
||||
public AdvancedImapSetupPage()
|
||||
private void IncomingServerChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (sender is TextBox senderTextBox)
|
||||
{
|
||||
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(':');
|
||||
var splitted = senderTextBox.Text.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
|
||||
};
|
||||
|
||||
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 OutgoingServerChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (sender is TextBox senderTextBox)
|
||||
{
|
||||
var splitted = senderTextBox.Text.Split(':');
|
||||
|
||||
if (splitted.Length > 1)
|
||||
{
|
||||
OutgoingServerPort.Text = splitted[splitted.Length - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void IncomingUsernameChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (UseSameCredentialsForSending)
|
||||
{
|
||||
OutgoingUsernameBox.Text = UsernameBox.Text;
|
||||
}
|
||||
}
|
||||
|
||||
private void IncomingPasswordChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (UseSameCredentialsForSending)
|
||||
{
|
||||
OutgoingPasswordBox.Password = PasswordBox.Password;
|
||||
IncomingServerPortBox.Text = splitted[splitted.Length - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OutgoingServerChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (sender is TextBox senderTextBox)
|
||||
{
|
||||
var splitted = senderTextBox.Text.Split(':');
|
||||
|
||||
if (splitted.Length > 1)
|
||||
{
|
||||
OutgoingServerPort.Text = splitted[splitted.Length - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void IncomingUsernameChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (UseSameCredentialsForSending)
|
||||
{
|
||||
OutgoingUsernameBox.Text = UsernameBox.Text;
|
||||
}
|
||||
}
|
||||
|
||||
private void IncomingPasswordChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (UseSameCredentialsForSending)
|
||||
{
|
||||
OutgoingPasswordBox.Password = PasswordBox.Password;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,42 +8,41 @@ using Wino.Core.Domain.Exceptions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Messaging.Client.Mails;
|
||||
|
||||
namespace Wino.Views.ImapSetup
|
||||
namespace Wino.Views.ImapSetup;
|
||||
|
||||
public sealed partial class ImapConnectionFailedPage : Page
|
||||
{
|
||||
public sealed partial class ImapConnectionFailedPage : Page
|
||||
private string _protocolLog;
|
||||
|
||||
private readonly IClipboardService _clipboardService = App.Current.Services.GetService<IClipboardService>();
|
||||
private readonly IMailDialogService _dialogService = App.Current.Services.GetService<IMailDialogService>();
|
||||
|
||||
public ImapConnectionFailedPage()
|
||||
{
|
||||
private string _protocolLog;
|
||||
|
||||
private readonly IClipboardService _clipboardService = App.Current.Services.GetService<IClipboardService>();
|
||||
private readonly IMailDialogService _dialogService = App.Current.Services.GetService<IMailDialogService>();
|
||||
|
||||
public ImapConnectionFailedPage()
|
||||
{
|
||||
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());
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -15,16 +15,15 @@ using Windows.UI.Xaml.Navigation;
|
||||
|
||||
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
|
||||
|
||||
namespace Wino.Views.ImapSetup
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class PreparingImapFoldersPage : Page
|
||||
public PreparingImapFoldersPage()
|
||||
{
|
||||
public PreparingImapFoldersPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,120 +14,119 @@ using Wino.Messaging.Client.Mails;
|
||||
using Wino.Messaging.Server;
|
||||
|
||||
|
||||
namespace Wino.Views.ImapSetup
|
||||
namespace Wino.Views.ImapSetup;
|
||||
|
||||
public sealed partial class TestingImapConnectionPage : Page
|
||||
{
|
||||
public sealed partial class TestingImapConnectionPage : Page
|
||||
private IWinoServerConnectionManager _winoServerConnectionManager = App.Current.Services.GetService<IWinoServerConnectionManager>();
|
||||
private AutoDiscoverySettings autoDiscoverySettings;
|
||||
private CustomServerInformation serverInformationToTest;
|
||||
|
||||
public TestingImapConnectionPage()
|
||||
{
|
||||
private IWinoServerConnectionManager _winoServerConnectionManager = App.Current.Services.GetService<IWinoServerConnectionManager>();
|
||||
private AutoDiscoverySettings autoDiscoverySettings;
|
||||
private CustomServerInformation serverInformationToTest;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public TestingImapConnectionPage()
|
||||
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)
|
||||
{
|
||||
InitializeComponent();
|
||||
WeakReferenceMessenger.Default.Send(new ImapSetupBackNavigationRequested());
|
||||
}
|
||||
|
||||
protected override async void OnNavigatedTo(NavigationEventArgs e)
|
||||
else
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
// Test connection
|
||||
|
||||
// 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)
|
||||
// Discovery settings are passed.
|
||||
// Create server information out of the discovery settings.
|
||||
if (e.Parameter is AutoDiscoverySettings parameterAutoDiscoverySettings)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new ImapSetupBackNavigationRequested());
|
||||
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();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Test connection
|
||||
// Check if certificate UI is required.
|
||||
|
||||
// Discovery settings are passed.
|
||||
// Create server information out of the discovery settings.
|
||||
if (e.Parameter is AutoDiscoverySettings parameterAutoDiscoverySettings)
|
||||
if (testResultData.IsCertificateUIRequired)
|
||||
{
|
||||
autoDiscoverySettings = parameterAutoDiscoverySettings;
|
||||
serverInformationToTest = autoDiscoverySettings.ToServerInformation();
|
||||
}
|
||||
else if (e.Parameter is CustomServerInformation customServerInformation)
|
||||
{
|
||||
// Only server information is passed.
|
||||
serverInformationToTest = customServerInformation;
|
||||
}
|
||||
// Certificate UI is required. Show certificate dialog.
|
||||
|
||||
// Make sure that certificate dialog must be present in case of SSL handshake fails.
|
||||
await PerformTestAsync(allowSSLHandshake: false);
|
||||
}
|
||||
}
|
||||
CertIssuer.Text = testResultData.CertificateIssuer;
|
||||
CertValidFrom.Text = testResultData.CertificateValidFromDateString;
|
||||
CertValidTo.Text = testResultData.CertificateExpirationDateString;
|
||||
|
||||
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();
|
||||
TestingConnectionPanel.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
|
||||
CertificateDialog.Visibility = Windows.UI.Xaml.Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check if certificate UI is required.
|
||||
// Connection test failed. Show error dialog.
|
||||
|
||||
if (testResultData.IsCertificateUIRequired)
|
||||
{
|
||||
// Certificate UI is required. Show certificate dialog.
|
||||
var protocolLog = testResultData.FailureProtocolLog;
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,83 +14,82 @@ using Wino.Core.Domain.Models.AutoDiscovery;
|
||||
using Wino.Messaging.Client.Mails;
|
||||
|
||||
|
||||
namespace Wino.Views.ImapSetup
|
||||
namespace Wino.Views.ImapSetup;
|
||||
|
||||
public sealed partial class WelcomeImapSetupPage : Page
|
||||
{
|
||||
public sealed partial class WelcomeImapSetupPage : Page
|
||||
private readonly IAutoDiscoveryService _autoDiscoveryService = App.Current.Services.GetService<IAutoDiscoveryService>();
|
||||
|
||||
public WelcomeImapSetupPage()
|
||||
{
|
||||
private readonly IAutoDiscoveryService _autoDiscoveryService = App.Current.Services.GetService<IAutoDiscoveryService>();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public WelcomeImapSetupPage()
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
AutoDiscoveryPanel.Visibility = Visibility.Collapsed;
|
||||
MainSetupPanel.Visibility = Visibility.Visible;
|
||||
|
||||
if (e.Parameter is MailAccount accountProperties)
|
||||
{
|
||||
InitializeComponent();
|
||||
DisplayNameBox.Text = accountProperties.Name;
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
else if (e.Parameter is AccountCreationDialogResult 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()
|
||||
{
|
||||
Password = PasswordBox.Password,
|
||||
DisplayName = DisplayNameBox.Text,
|
||||
Email = AddressBox.Text,
|
||||
};
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
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()
|
||||
{
|
||||
Password = PasswordBox.Password,
|
||||
DisplayName = DisplayNameBox.Text,
|
||||
Email = AddressBox.Text,
|
||||
};
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,467 +27,466 @@ using Wino.MenuFlyouts.Context;
|
||||
using Wino.Messaging.Client.Mails;
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views
|
||||
namespace Wino.Views;
|
||||
|
||||
public sealed partial class MailListPage : MailListPageAbstract,
|
||||
IRecipient<ClearMailSelectionsRequested>,
|
||||
IRecipient<ActiveMailItemChangedEvent>,
|
||||
IRecipient<SelectMailItemContainerEvent>,
|
||||
IRecipient<DisposeRenderingFrameRequested>
|
||||
{
|
||||
public sealed partial class MailListPage : MailListPageAbstract,
|
||||
IRecipient<ClearMailSelectionsRequested>,
|
||||
IRecipient<ActiveMailItemChangedEvent>,
|
||||
IRecipient<SelectMailItemContainerEvent>,
|
||||
IRecipient<DisposeRenderingFrameRequested>
|
||||
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()
|
||||
{
|
||||
private const double RENDERING_COLUMN_MIN_WIDTH = 375;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private IStatePersistanceService StatePersistenceService { get; } = App.Current.Services.GetService<IStatePersistanceService>();
|
||||
private IKeyPressService KeyPressService { get; } = App.Current.Services.GetService<IKeyPressService>();
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
public MailListPage()
|
||||
// Bindings.Update();
|
||||
|
||||
// Delegate to ViewModel.
|
||||
if (e.Parameter is NavigateMailFolderEventArgs folderNavigationArgs)
|
||||
{
|
||||
InitializeComponent();
|
||||
WeakReferenceMessenger.Default.Send(new ActiveMailFolderChangedEvent(folderNavigationArgs.BaseFolderMenuItem, folderNavigationArgs.FolderInitLoadAwaitTask));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
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)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
// Bindings.Update();
|
||||
|
||||
// Delegate to ViewModel.
|
||||
if (e.Parameter is NavigateMailFolderEventArgs folderNavigationArgs)
|
||||
if (addedItem is FolderPivotViewModel pivotItem)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new ActiveMailFolderChangedEvent(folderNavigationArgs.BaseFolderMenuItem, folderNavigationArgs.FolderInitLoadAwaitTask));
|
||||
pivotItem.IsSelected = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
||||
foreach (var removedItem in e.RemovedItems)
|
||||
{
|
||||
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)
|
||||
if (removedItem is FolderPivotViewModel pivotItem)
|
||||
{
|
||||
if (addedItem is FolderPivotViewModel pivotItem)
|
||||
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())
|
||||
{
|
||||
pivotItem.IsSelected = true;
|
||||
}
|
||||
}
|
||||
// Prepare WebView2 animation from Rendering to Composing page.
|
||||
PrepareRenderingPageWebViewTransition();
|
||||
|
||||
foreach (var removedItem in e.RemovedItems)
|
||||
{
|
||||
if (removedItem is FolderPivotViewModel pivotItem)
|
||||
// Dispose existing HTML content from rendering page webview.
|
||||
WeakReferenceMessenger.Default.Send(new CancelRenderingContentRequested());
|
||||
}
|
||||
else if (IsComposingPageActive())
|
||||
{
|
||||
pivotItem.IsSelected = false;
|
||||
// Composer is already active. Prepare composer WebView2 animation.
|
||||
PrepareComposePageWebViewTransition();
|
||||
}
|
||||
}
|
||||
else
|
||||
composerPageTransition = NavigationTransitionType.DrillIn;
|
||||
|
||||
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());
|
||||
ViewModel.NavigationService.Navigate(WinoPage.ComposePage, message.SelectedMailItemViewModel, NavigationReferenceFrame.RenderingFrame, composerPageTransition);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Navigate to composing page.
|
||||
if (message.SelectedMailItemViewModel.IsDraft)
|
||||
// Find the MIME and go to rendering page.
|
||||
|
||||
if (message.SelectedMailItemViewModel == null) return;
|
||||
|
||||
if (IsComposingPageActive())
|
||||
{
|
||||
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);
|
||||
PrepareComposePageWebViewTransition();
|
||||
}
|
||||
else
|
||||
|
||||
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;)
|
||||
{
|
||||
// Find the MIME and go to rendering page.
|
||||
// Retry with delay until the container is realized. Max 1 second.
|
||||
await Task.Delay(200);
|
||||
|
||||
if (message.SelectedMailItemViewModel == null) return;
|
||||
retriedSelectionCount++;
|
||||
|
||||
if (IsComposingPageActive())
|
||||
{
|
||||
PrepareComposePageWebViewTransition();
|
||||
}
|
||||
|
||||
ViewModel.NavigationService.Navigate(WinoPage.MailRenderingPage, message.SelectedMailItemViewModel, NavigationReferenceFrame.RenderingFrame);
|
||||
goto trySelection;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateAdaptiveness();
|
||||
}
|
||||
|
||||
private bool IsRenderingPageActive() => RenderingFrame.Content is MailRenderingPage;
|
||||
private bool IsComposingPageActive() => RenderingFrame.Content is ComposePage;
|
||||
|
||||
private void PrepareComposePageWebViewTransition()
|
||||
{
|
||||
var webView = GetComposerPageWebView();
|
||||
|
||||
if (webView != null)
|
||||
// Automatically scroll to the selected item.
|
||||
// This is useful when creating draft.
|
||||
if (isSelected && message.ScrollToItem)
|
||||
{
|
||||
var animation = ConnectedAnimationService.GetForCurrentView().PrepareToAnimate("WebViewConnectedAnimation", webView);
|
||||
animation.Configuration = new BasicConnectedAnimationConfiguration();
|
||||
}
|
||||
}
|
||||
var collectionContainer = ViewModel.MailCollection.GetMailItemContainer(message.SelectedMailViewModel.UniqueId);
|
||||
|
||||
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)
|
||||
// Scroll to thread if available.
|
||||
if (collectionContainer.ThreadViewModel != null)
|
||||
{
|
||||
for (int i = retriedSelectionCount; i < 5;)
|
||||
{
|
||||
// Retry with delay until the container is realized. Max 1 second.
|
||||
await Task.Delay(200);
|
||||
|
||||
retriedSelectionCount++;
|
||||
|
||||
goto trySelection;
|
||||
}
|
||||
MailListView.ScrollIntoView(collectionContainer.ThreadViewModel, ScrollIntoViewAlignment.Default);
|
||||
}
|
||||
else if (collectionContainer.ItemViewModel != null)
|
||||
{
|
||||
MailListView.ScrollIntoView(collectionContainer.ItemViewModel, ScrollIntoViewAlignment.Default);
|
||||
}
|
||||
|
||||
// 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)
|
||||
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)
|
||||
{
|
||||
if (sender is MailItemDisplayInformationControl control && control.ConnectedExpander != null && 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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
VisualStateManager.GoToState(this, "NarrowRenderer", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ViewModel.HasSingleItemSelection && !isMultiSelectionEnabled)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "BothPanelsMailSelected", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
VisualStateManager.GoToState(this, "BothPanelsNoMailSelected", true);
|
||||
}
|
||||
VisualStateManager.GoToState(this, "NarrowMailList", true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ViewModel.HasSingleItemSelection && !isMultiSelectionEnabled)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "BothPanelsMailSelected", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
VisualStateManager.GoToState(this, "BothPanelsNoMailSelected", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,258 +19,257 @@ using Wino.Messaging.Client.Mails;
|
||||
using Wino.Messaging.Client.Shell;
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views
|
||||
namespace Wino.Views;
|
||||
|
||||
public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
||||
IRecipient<HtmlRenderingRequested>,
|
||||
IRecipient<CancelRenderingContentRequested>,
|
||||
IRecipient<ApplicationThemeChanged>
|
||||
{
|
||||
public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
||||
IRecipient<HtmlRenderingRequested>,
|
||||
IRecipient<CancelRenderingContentRequested>,
|
||||
IRecipient<ApplicationThemeChanged>
|
||||
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()
|
||||
{
|
||||
private readonly IPreferencesService _preferencesService = App.Current.Services.GetService<IPreferencesService>();
|
||||
private readonly IMailDialogService _dialogService = App.Current.Services.GetService<IMailDialogService>();
|
||||
InitializeComponent();
|
||||
|
||||
private bool isRenderingInProgress = false;
|
||||
private TaskCompletionSource<bool> DOMLoadedTask = new TaskCompletionSource<bool>();
|
||||
Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF");
|
||||
Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation,msWebView2CodeCache");
|
||||
|
||||
private bool isChromiumDisposed = false;
|
||||
|
||||
public WebView2 GetWebView() => Chromium;
|
||||
|
||||
public MailRenderingPage()
|
||||
ViewModel.SaveHTMLasPDFFunc = new Func<string, Task<bool>>((path) =>
|
||||
{
|
||||
InitializeComponent();
|
||||
return Chromium.CoreWebView2.PrintToPdfAsync(path, null).AsTask();
|
||||
});
|
||||
}
|
||||
|
||||
Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF");
|
||||
Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation,msWebView2CodeCache");
|
||||
public override async void OnEditorThemeChanged()
|
||||
{
|
||||
base.OnEditorThemeChanged();
|
||||
|
||||
ViewModel.SaveHTMLasPDFFunc = new Func<string, Task<bool>>((path) =>
|
||||
{
|
||||
return Chromium.CoreWebView2.PrintToPdfAsync(path, null).AsTask();
|
||||
});
|
||||
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));
|
||||
}
|
||||
|
||||
public override async void OnEditorThemeChanged()
|
||||
{
|
||||
base.OnEditorThemeChanged();
|
||||
isRenderingInProgress = false;
|
||||
}
|
||||
|
||||
await UpdateEditorThemeAsync();
|
||||
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;
|
||||
}
|
||||
|
||||
private async Task<string> InvokeScriptSafeAsync(string function)
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
||||
async void IRecipient<CancelRenderingContentRequested>.Receive(CancelRenderingContentRequested message)
|
||||
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)
|
||||
{
|
||||
await Chromium.EnsureCoreWebView2Async();
|
||||
|
||||
if (!isRenderingInProgress)
|
||||
{
|
||||
await RenderInternalAsync(string.Empty);
|
||||
}
|
||||
await RenderInternalAsync(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private async void WebViewNavigationStarting(WebView2 sender, CoreWebView2NavigationStartingEventArgs args)
|
||||
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))
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
await Launcher.LaunchUriAsync(newUri);
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachmentClicked(object sender, ItemClickEventArgs e)
|
||||
private void AttachmentClicked(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
if (e.ClickedItem is MailAttachmentViewModel attachmentViewModel)
|
||||
{
|
||||
if (e.ClickedItem is MailAttachmentViewModel attachmentViewModel)
|
||||
{
|
||||
ViewModel.OpenAttachmentCommand.Execute(attachmentViewModel);
|
||||
}
|
||||
ViewModel.OpenAttachmentCommand.Execute(attachmentViewModel);
|
||||
}
|
||||
}
|
||||
|
||||
private void BarDynamicOverflowChanging(CommandBar sender, DynamicOverflowItemsChangingEventArgs args)
|
||||
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)
|
||||
{
|
||||
if (args.Action == CommandBarDynamicOverflowAction.AddingToOverflow || sender.SecondaryCommands.Any())
|
||||
sender.OverflowButtonVisibility = CommandBarOverflowButtonVisibility.Visible;
|
||||
else
|
||||
sender.OverflowButtonVisibility = CommandBarOverflowButtonVisibility.Collapsed;
|
||||
Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark;
|
||||
|
||||
await InvokeScriptSafeAsync("SetDarkEditor();");
|
||||
}
|
||||
|
||||
private async Task UpdateEditorThemeAsync()
|
||||
else
|
||||
{
|
||||
await DOMLoadedTask.Task;
|
||||
Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Light;
|
||||
|
||||
if (ViewModel.IsDarkWebviewRenderer)
|
||||
{
|
||||
Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark;
|
||||
|
||||
await InvokeScriptSafeAsync("SetDarkEditor();");
|
||||
}
|
||||
else
|
||||
{
|
||||
Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Light;
|
||||
|
||||
await InvokeScriptSafeAsync("SetLightEditor();");
|
||||
}
|
||||
await InvokeScriptSafeAsync("SetLightEditor();");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateReaderFontPropertiesAsync()
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
hyperlinkButton.ContextFlyout.ShowAt(hyperlinkButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views.Settings
|
||||
namespace Wino.Views.Settings;
|
||||
|
||||
public sealed partial class PersonalizationPage : PersonalizationPageAbstract
|
||||
{
|
||||
public sealed partial class PersonalizationPage : PersonalizationPageAbstract
|
||||
public PersonalizationPage()
|
||||
{
|
||||
public PersonalizationPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views.Settings
|
||||
namespace Wino.Views.Settings;
|
||||
|
||||
public sealed partial class AliasManagementPage : AliasManagementPageAbstract
|
||||
{
|
||||
public sealed partial class AliasManagementPage : AliasManagementPageAbstract
|
||||
public AliasManagementPage()
|
||||
{
|
||||
public AliasManagementPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views.Settings
|
||||
namespace Wino.Views.Settings;
|
||||
|
||||
public sealed partial class AppPreferencesPage : AppPreferencesPageAbstract
|
||||
{
|
||||
public sealed partial class AppPreferencesPage : AppPreferencesPageAbstract
|
||||
public AppPreferencesPage()
|
||||
{
|
||||
public AppPreferencesPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views.Settings
|
||||
namespace Wino.Views.Settings;
|
||||
|
||||
public sealed partial class LanguageTimePage : LanguageTimePageAbstract
|
||||
{
|
||||
public sealed partial class LanguageTimePage : LanguageTimePageAbstract
|
||||
public LanguageTimePage()
|
||||
{
|
||||
public LanguageTimePage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
public override void OnLanguageChanged()
|
||||
{
|
||||
base.OnLanguageChanged();
|
||||
public override void OnLanguageChanged()
|
||||
{
|
||||
base.OnLanguageChanged();
|
||||
|
||||
Bindings.Update();
|
||||
}
|
||||
Bindings.Update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views.Settings
|
||||
namespace Wino.Views.Settings;
|
||||
|
||||
public sealed partial class MessageListPage : MessageListPageAbstract
|
||||
{
|
||||
public sealed partial class MessageListPage : MessageListPageAbstract
|
||||
public MessageListPage()
|
||||
{
|
||||
public MessageListPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views.Settings
|
||||
namespace Wino.Views.Settings;
|
||||
|
||||
public sealed partial class ReadComposePanePage : ReadComposePanePageAbstract
|
||||
{
|
||||
public sealed partial class ReadComposePanePage : ReadComposePanePageAbstract
|
||||
public ReadComposePanePage()
|
||||
{
|
||||
public ReadComposePanePage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views.Settings
|
||||
namespace Wino.Views.Settings;
|
||||
|
||||
public sealed partial class SignatureManagementPage : SignatureManagementPageAbstract
|
||||
{
|
||||
public sealed partial class SignatureManagementPage : SignatureManagementPageAbstract
|
||||
public SignatureManagementPage()
|
||||
{
|
||||
public SignatureManagementPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user