Initial WinUI switch.
@@ -0,0 +1,22 @@
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Wino.Views;
|
||||
|
||||
namespace Wino.Activation;
|
||||
|
||||
internal class DefaultActivationHandler : ActivationHandler<IActivatedEventArgs>
|
||||
{
|
||||
protected override Task HandleInternalAsync(IActivatedEventArgs args)
|
||||
{
|
||||
(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;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
using Windows.Storage;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.UWP.Extensions;
|
||||
using Wino.Views;
|
||||
|
||||
namespace Wino.Activation;
|
||||
|
||||
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)
|
||||
{
|
||||
_nativeAppService = nativeAppService;
|
||||
_mimeFileService = mimeFileService;
|
||||
_statePersistanceService = statePersistanceService;
|
||||
_winoNavigationService = 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))
|
||||
{
|
||||
var fileBytes = await file.ToByteArrayAsync();
|
||||
var directoryName = Path.GetDirectoryName(file.Path);
|
||||
|
||||
var messageInformation = await _mimeFileService.GetMimeMessageInformationAsync(fileBytes, directoryName).ConfigureAwait(false);
|
||||
|
||||
if (_nativeAppService.IsAppRunning())
|
||||
{
|
||||
// TODO: Activate another Window and go to mail rendering page.
|
||||
_winoNavigationService.Navigate(WinoPage.MailRenderingPage, messageInformation, NavigationReferenceFrame.RenderingFrame);
|
||||
}
|
||||
else
|
||||
{
|
||||
_statePersistanceService.ShouldShiftMailRenderingDesign = true;
|
||||
(Window.Current.Content as Frame).Navigate(typeof(MailRenderingPage), messageInformation, new DrillInNavigationTransitionInfo());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool CanHandleInternal(FileActivatedEventArgs args) => args.Files.Any();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Launch;
|
||||
using Wino.Messaging.Client.Shell;
|
||||
|
||||
namespace Wino.Activation;
|
||||
|
||||
internal class ProtocolActivationHandler : ActivationHandler<ProtocolActivatedEventArgs>
|
||||
{
|
||||
private const string MailtoProtocolTag = "mailto:";
|
||||
|
||||
private readonly INativeAppService _nativeAppService;
|
||||
private readonly ILaunchProtocolService _launchProtocolService;
|
||||
|
||||
public ProtocolActivationHandler(INativeAppService nativeAppService, ILaunchProtocolService launchProtocolService)
|
||||
{
|
||||
_nativeAppService = nativeAppService;
|
||||
_launchProtocolService = launchProtocolService;
|
||||
}
|
||||
|
||||
protected override Task HandleInternalAsync(ProtocolActivatedEventArgs args)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
protected override bool CanHandleInternal(ProtocolActivatedEventArgs args)
|
||||
{
|
||||
// Validate the URI scheme.
|
||||
|
||||
try
|
||||
{
|
||||
var uriGet = args.Uri;
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.CanHandleInternal(args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Toolkit.Uwp.Notifications;
|
||||
using Serilog;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Mail.WinUI;
|
||||
using Wino.Messaging.Client.Accounts;
|
||||
|
||||
namespace Wino.Activation;
|
||||
|
||||
/// <summary>
|
||||
/// This handler will only handle the toasts that runs on foreground.
|
||||
/// Background executions are not handled here like mark as read or delete.
|
||||
/// </summary>
|
||||
internal class ToastNotificationActivationHandler : ActivationHandler<ToastNotificationActivatedEventArgs>
|
||||
{
|
||||
private readonly IMailService _mailService;
|
||||
private readonly IFolderService _folderService;
|
||||
|
||||
private ToastArguments _toastArguments;
|
||||
|
||||
public ToastNotificationActivationHandler(IMailService mailService,
|
||||
IFolderService folderService)
|
||||
{
|
||||
_mailService = mailService;
|
||||
_folderService = folderService;
|
||||
}
|
||||
|
||||
protected override async Task HandleInternalAsync(ToastNotificationActivatedEventArgs args)
|
||||
{
|
||||
// Create the mail item navigation event.
|
||||
// If the app is running, it'll be picked up by the Messenger.
|
||||
// Otherwise we'll save it and handle it when the shell loads all accounts.
|
||||
|
||||
// Parse the mail unique id and perform above actions.
|
||||
if (Guid.TryParse(_toastArguments[Constants.ToastMailUniqueIdKey], out Guid mailItemUniqueId))
|
||||
{
|
||||
var account = await _mailService.GetMailAccountByUniqueIdAsync(mailItemUniqueId).ConfigureAwait(false);
|
||||
if (account == null) return;
|
||||
|
||||
var mailItem = await _mailService.GetSingleMailItemAsync(mailItemUniqueId).ConfigureAwait(false);
|
||||
if (mailItem == null) return;
|
||||
|
||||
var message = new AccountMenuItemExtended(mailItem.AssignedFolder.Id, mailItem);
|
||||
|
||||
// Delegate this event to LaunchProtocolService so app shell can pick it up on launch if app doesn't work.
|
||||
var launchProtocolService = App.Current.Services.GetService<ILaunchProtocolService>();
|
||||
launchProtocolService.LaunchParameter = message;
|
||||
|
||||
// Send the messsage anyways. Launch protocol service will be ignored if the message is picked up by subscriber shell.
|
||||
WeakReferenceMessenger.Default.Send(message);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool CanHandleInternal(ToastNotificationActivatedEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
_toastArguments = ToastArguments.Parse(args.Argument);
|
||||
|
||||
return
|
||||
_toastArguments.Contains(Constants.ToastMailUniqueIdKey) &&
|
||||
_toastArguments.Contains(Constants.ToastActionKey);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Couldn't handle parsing toast notification arguments for foreground navigate.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<uwp:WinoApplication
|
||||
x:Class="Wino.Mail.WinUI.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Wino.Mail.WinUI"
|
||||
xmlns:styles="using:Wino.Styles"
|
||||
xmlns:uwp="using:Wino.Core.UWP">
|
||||
<uwp:WinoApplication.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<uwp:CoreGeneric />
|
||||
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.Segmented/Segmented/Segmented.xaml" />
|
||||
<ResourceDictionary Source="Styles/ItemContainerStyles.xaml" />
|
||||
<ResourceDictionary Source="Styles/ImagePreviewControl.xaml" />
|
||||
<ResourceDictionary Source="Styles/WebViewEditorControl.xaml" />
|
||||
|
||||
<styles:WinoExpanderStyle />
|
||||
<ResourceDictionary Source="/Wino.Core.WinUI/AppThemes/Mica.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</uwp:WinoApplication.Resources>
|
||||
</uwp:WinoApplication>
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.UWP;
|
||||
using Wino.Core.WinUI.Interfaces;
|
||||
using Wino.Mail.Services;
|
||||
using Wino.Mail.ViewModels;
|
||||
using Wino.Messaging.Server;
|
||||
using Wino.Services;
|
||||
namespace Wino.Mail.WinUI;
|
||||
|
||||
public partial class App : WinoApplication, IRecipient<NewMailSynchronizationRequested>
|
||||
{
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
WeakReferenceMessenger.Default.Register<NewMailSynchronizationRequested>(this);
|
||||
}
|
||||
|
||||
#region Dependency Injection
|
||||
|
||||
|
||||
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(EditAccountDetailsPageViewModel));
|
||||
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
|
||||
|
||||
public override IServiceProvider ConfigureServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
services.RegisterViewModelService();
|
||||
services.RegisterSharedServices();
|
||||
services.RegisterCoreUWPServices();
|
||||
services.RegisterCoreViewModels();
|
||||
|
||||
RegisterUWPServices(services);
|
||||
RegisterViewModels(services);
|
||||
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
|
||||
protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
||||
{
|
||||
// TODO: Check app relaunch mutex before loading anything.
|
||||
|
||||
MainWindow = new ShellWindow();
|
||||
|
||||
await InitializeServicesAsync();
|
||||
|
||||
if (MainWindow is not IWinoShellWindow shellWindow) throw new ArgumentException("MainWindow must implement IWinoShellWindow");
|
||||
|
||||
shellWindow.HandleAppActivation(args);
|
||||
|
||||
MainWindow.Activate();
|
||||
}
|
||||
|
||||
public async void Receive(NewMailSynchronizationRequested message)
|
||||
{
|
||||
// TODO: Trigger new sync.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
<abstract:AppShellAbstract
|
||||
x:Class="Wino.Views.AppShell"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:abstract="using:Wino.Views.Abstract"
|
||||
xmlns:advanced="using:Wino.Controls.Advanced"
|
||||
xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals"
|
||||
xmlns:animations="using:CommunityToolkit.WinUI.Animations"
|
||||
xmlns:controls="using:Wino.Controls"
|
||||
xmlns:controls1="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:coreControls="using:Wino.Core.UWP.Controls"
|
||||
xmlns:coreConverters="using:Wino.Core.UWP.Converters"
|
||||
xmlns:coreSelectors="using:Wino.Core.UWP.Selectors"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
xmlns:enums="using:Wino.Core.Domain.Enums"
|
||||
xmlns:helpers="using:Wino.Helpers"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:menu="using:Wino.Core.Domain.MenuItems"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||
x:Name="Root"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
|
||||
<coreConverters:HexToColorBrushConverter x:Key="HexToColorBrushConverter" />
|
||||
|
||||
<!-- Clickable New Style Account Template -->
|
||||
<DataTemplate x:Key="ClickableAccountMenuTemplate" x:DataType="menu:AccountMenuItem">
|
||||
<controls:AccountNavigationItem
|
||||
x:Name="AccountItem"
|
||||
Height="50"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
BindingData="{x:Bind}"
|
||||
DataContext="{x:Bind}"
|
||||
IsActiveAccount="{x:Bind IsSelected, Mode=OneWay}"
|
||||
IsExpanded="{x:Bind IsExpanded, Mode=TwoWay}"
|
||||
SelectsOnInvoked="False"
|
||||
Style="{StaticResource SingleAccountNavigationViewItemTemplate}">
|
||||
<coreControls:WinoNavigationViewItem.ContentTransitions>
|
||||
<TransitionCollection>
|
||||
<EdgeUIThemeTransition Edge="Top" />
|
||||
</TransitionCollection>
|
||||
</coreControls:WinoNavigationViewItem.ContentTransitions>
|
||||
<muxc:NavigationViewItem.Icon>
|
||||
<coreControls:WinoFontIcon
|
||||
FontSize="12"
|
||||
Foreground="{x:Bind AccountColorHex, Converter={StaticResource HexToColorBrushConverter}, Mode=OneWay}"
|
||||
Icon="{x:Bind helpers:XamlHelpers.GetProviderIcon(Parameter)}" />
|
||||
</muxc:NavigationViewItem.Icon>
|
||||
<muxc:NavigationViewItem.InfoBadge>
|
||||
<muxc:InfoBadge
|
||||
Background="{ThemeResource SystemAccentColor}"
|
||||
Foreground="White"
|
||||
Visibility="{x:Bind helpers:XamlHelpers.CountToVisibilityConverter(UnreadItemCount), Mode=OneWay}"
|
||||
Value="{x:Bind UnreadItemCount, Mode=OneWay}" />
|
||||
</muxc:NavigationViewItem.InfoBadge>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock
|
||||
x:Name="AccountNameTextblock"
|
||||
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightByChildSelectedState(IsSelected), Mode=OneWay}"
|
||||
MaxLines="1"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{x:Bind AccountName, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
MaxLines="1"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind Parameter.Address, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
|
||||
<PathIcon
|
||||
x:Name="AttentionIcon"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
x:Load="{x:Bind IsAttentionRequired, Mode=OneWay}"
|
||||
Data="F1 M 2.021484 18.769531 C 1.767578 18.769531 1.52832 18.720703 1.303711 18.623047 C 1.079102 18.525391 0.880534 18.391928 0.708008 18.222656 C 0.535482 18.053385 0.398763 17.856445 0.297852 17.631836 C 0.19694 17.407227 0.146484 17.167969 0.146484 16.914062 C 0.146484 16.614584 0.211589 16.328125 0.341797 16.054688 L 7.695312 1.347656 C 7.851562 1.035156 8.082682 0.784506 8.388672 0.595703 C 8.694661 0.406902 9.023438 0.3125 9.375 0.3125 C 9.726562 0.3125 10.055338 0.406902 10.361328 0.595703 C 10.667317 0.784506 10.898438 1.035156 11.054688 1.347656 L 18.408203 16.054688 C 18.53841 16.328125 18.603516 16.614584 18.603516 16.914062 C 18.603516 17.167969 18.553059 17.407227 18.452148 17.631836 C 18.351236 17.856445 18.216145 18.053385 18.046875 18.222656 C 17.877604 18.391928 17.679035 18.525391 17.451172 18.623047 C 17.223307 18.720703 16.982422 18.769531 16.728516 18.769531 Z M 16.728516 17.519531 C 16.884766 17.519531 17.027994 17.460938 17.158203 17.34375 C 17.28841 17.226562 17.353516 17.086588 17.353516 16.923828 C 17.353516 16.806641 17.330729 16.702475 17.285156 16.611328 L 9.931641 1.904297 C 9.879557 1.793621 9.80306 1.708984 9.702148 1.650391 C 9.601236 1.591797 9.492188 1.5625 9.375 1.5625 C 9.257812 1.5625 9.148763 1.593426 9.047852 1.655273 C 8.946939 1.717123 8.870442 1.800131 8.818359 1.904297 L 1.464844 16.611328 C 1.419271 16.702475 1.396484 16.803387 1.396484 16.914062 C 1.396484 17.083334 1.459961 17.226562 1.586914 17.34375 C 1.713867 17.460938 1.858724 17.519531 2.021484 17.519531 Z M 8.75 11.875 L 8.75 6.875 C 8.75 6.705729 8.811849 6.559245 8.935547 6.435547 C 9.059244 6.31185 9.205729 6.25 9.375 6.25 C 9.544271 6.25 9.690755 6.31185 9.814453 6.435547 C 9.93815 6.559245 10 6.705729 10 6.875 L 10 11.875 C 10 12.044271 9.93815 12.190756 9.814453 12.314453 C 9.690755 12.438151 9.544271 12.5 9.375 12.5 C 9.205729 12.5 9.059244 12.438151 8.935547 12.314453 C 8.811849 12.190756 8.75 12.044271 8.75 11.875 Z M 8.4375 14.375 C 8.4375 14.114584 8.528646 13.893229 8.710938 13.710938 C 8.893229 13.528646 9.114583 13.4375 9.375 13.4375 C 9.635416 13.4375 9.856771 13.528646 10.039062 13.710938 C 10.221354 13.893229 10.3125 14.114584 10.3125 14.375 C 10.3125 14.635417 10.221354 14.856771 10.039062 15.039062 C 9.856771 15.221354 9.635416 15.3125 9.375 15.3125 C 9.114583 15.3125 8.893229 15.221354 8.710938 15.039062 C 8.528646 14.856771 8.4375 14.635417 8.4375 14.375 Z "
|
||||
Foreground="{ThemeResource InfoBarWarningSeverityIconBackground}" />
|
||||
|
||||
<muxc:ProgressRing
|
||||
x:Name="SynchronizationProgressBar"
|
||||
Grid.ColumnSpan="3"
|
||||
Width="10"
|
||||
Height="10"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Background="{ThemeResource AppBarItemBackgroundThemeBrush}"
|
||||
Foreground="{ThemeResource AppBarItemForegroundThemeBrush}"
|
||||
IsActive="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}"
|
||||
IsIndeterminate="{x:Bind IsProgressIndeterminate}"
|
||||
Maximum="100"
|
||||
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}"
|
||||
Value="{x:Bind SynchronizationProgress, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</controls:AccountNavigationItem>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- Fix account issues. -->
|
||||
<!-- Authentication -->
|
||||
<DataTemplate x:Key="FixAuthenticationIssueTemplate" x:DataType="menu:FixAccountIssuesMenuItem">
|
||||
<coreControls:WinoNavigationViewItem SelectsOnInvoked="False">
|
||||
<coreControls:WinoNavigationViewItem.Content>
|
||||
<TextBlock
|
||||
Margin="0,10"
|
||||
Foreground="{ThemeResource InfoBarWarningSeverityIconBackground}"
|
||||
HorizontalTextAlignment="Center"
|
||||
TextWrapping="WrapWholeWords">
|
||||
<Run Text="Account credentials can not be verified." /><LineBreak /><Run Text="Click here to fix it." />
|
||||
</TextBlock>
|
||||
</coreControls:WinoNavigationViewItem.Content>
|
||||
</coreControls:WinoNavigationViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- Missing system folder config. -->
|
||||
<DataTemplate x:Key="FixMissingFolderConfig" x:DataType="menu:FixAccountIssuesMenuItem">
|
||||
<coreControls:WinoNavigationViewItem SelectsOnInvoked="False">
|
||||
<coreControls:WinoNavigationViewItem.Content>
|
||||
<TextBlock
|
||||
Margin="0,10"
|
||||
Foreground="{ThemeResource InfoBarWarningSeverityIconBackground}"
|
||||
HorizontalTextAlignment="Center"
|
||||
TextWrapping="WrapWholeWords">
|
||||
<Run Text="Account is missing system folder configuration." /><LineBreak /><LineBreak /><Run Text="Click here to fix it." />
|
||||
</TextBlock>
|
||||
</coreControls:WinoNavigationViewItem.Content>
|
||||
</coreControls:WinoNavigationViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- New Mail -->
|
||||
<DataTemplate x:Key="CreateNewMailTemplate" x:DataType="menu:NewMailMenuItem">
|
||||
<coreControls:WinoNavigationViewItem
|
||||
Height="50"
|
||||
DataContext="{x:Bind}"
|
||||
SelectsOnInvoked="False">
|
||||
<muxc:NavigationViewItem.Icon>
|
||||
<coreControls:WinoFontIcon Icon="NewMail" />
|
||||
</muxc:NavigationViewItem.Icon>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Style="{StaticResource FlyoutPickerTitleTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.MenuNewMail}" />
|
||||
</coreControls:WinoNavigationViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- Inbox or any other folders. -->
|
||||
<DataTemplate x:Key="FolderMenuTemplate" x:DataType="menu:FolderMenuItem">
|
||||
<coreControls:WinoNavigationViewItem
|
||||
MinHeight="40"
|
||||
AllowDrop="True"
|
||||
ContextRequested="MenuItemContextRequested"
|
||||
DataContext="{x:Bind}"
|
||||
DragEnter="ItemDragEnterOnFolder"
|
||||
DragLeave="ItemDragLeaveFromFolder"
|
||||
Drop="ItemDroppedOnFolder"
|
||||
FontSize="50"
|
||||
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightByChildSelectedState(IsSelected), Mode=OneWay}"
|
||||
IsExpanded="{x:Bind IsExpanded, Mode=TwoWay}"
|
||||
IsSelected="{x:Bind IsSelected, Mode=TwoWay}"
|
||||
MenuItemsSource="{x:Bind SubMenuItems, Mode=OneWay}"
|
||||
SelectsOnInvoked="{x:Bind IsMoveTarget, Mode=OneWay}"
|
||||
ToolTipService.ToolTip="{x:Bind FolderName, Mode=OneWay}">
|
||||
<animations:Implicit.Animations>
|
||||
<animations:ScaleAnimation Duration="0:0:0.5" />
|
||||
</animations:Implicit.Animations>
|
||||
<coreControls:WinoNavigationViewItem.Icon>
|
||||
<coreControls:WinoFontIcon FontSize="64" Icon="{x:Bind helpers:XamlHelpers.GetSpecialFolderPathIconGeometry(Parameter.SpecialFolderType)}" />
|
||||
</coreControls:WinoNavigationViewItem.Icon>
|
||||
<muxc:NavigationViewItem.InfoBadge>
|
||||
<muxc:InfoBadge
|
||||
x:Name="FolderInfoBadge"
|
||||
Background="{StaticResource SystemAccentColor}"
|
||||
Foreground="White"
|
||||
Visibility="{x:Bind helpers:XamlHelpers.CountToVisibilityConverter(UnreadItemCount), Mode=OneWay}"
|
||||
Value="{x:Bind UnreadItemCount, Mode=OneWay}" />
|
||||
</muxc:NavigationViewItem.InfoBadge>
|
||||
<muxc:NavigationViewItem.Content>
|
||||
<Grid
|
||||
x:Name="FolderBackgroundGrid"
|
||||
MaxHeight="36"
|
||||
Padding="2"
|
||||
VerticalAlignment="Center">
|
||||
<Grid
|
||||
x:Name="BackgroundColorGrid"
|
||||
x:Load="{x:Bind HasTextColor, Mode=OneWay}"
|
||||
Background="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(Parameter.BackgroundColorHex), Mode=OneWay}"
|
||||
CornerRadius="3">
|
||||
<TextBlock
|
||||
x:Name="CustomColorTitle"
|
||||
Margin="4,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightBySyncState(IsSelected), Mode=OneWay}"
|
||||
Foreground="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(Parameter.TextColorHex), Mode=OneWay}"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{x:Bind FolderName, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock
|
||||
x:Name="NormalTitle"
|
||||
VerticalAlignment="Center"
|
||||
x:Load="{x:Bind HasTextColor, Converter={StaticResource ReverseBooleanConverter}}"
|
||||
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightBySyncState(IsSelected), Mode=OneWay}"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{x:Bind FolderName, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</Grid>
|
||||
</muxc:NavigationViewItem.Content>
|
||||
</coreControls:WinoNavigationViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- Merged Inbox -->
|
||||
<DataTemplate x:Key="MergedAccountTemplate" x:DataType="menu:MergedAccountMenuItem">
|
||||
<controls:AccountNavigationItem
|
||||
x:Name="AccountItem"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
BindingData="{x:Bind}"
|
||||
DataContext="{x:Bind}"
|
||||
IsActiveAccount="{x:Bind IsSelected, Mode=TwoWay}"
|
||||
IsExpanded="{x:Bind IsExpanded, Mode=TwoWay}"
|
||||
MenuItemsSource="{x:Bind SubMenuItems}"
|
||||
SelectsOnInvoked="False"
|
||||
Style="{StaticResource SingleAccountNavigationViewItemTemplate}">
|
||||
<muxc:NavigationViewItem.InfoBadge>
|
||||
<muxc:InfoBadge
|
||||
x:Name="FolderInfoBadge"
|
||||
Background="{StaticResource SystemAccentColor}"
|
||||
Foreground="White"
|
||||
Visibility="{x:Bind helpers:XamlHelpers.CountToVisibilityConverter(UnreadItemCount), Mode=OneWay}"
|
||||
Value="{x:Bind UnreadItemCount, Mode=OneWay}" />
|
||||
</muxc:NavigationViewItem.InfoBadge>
|
||||
<coreControls:WinoNavigationViewItem.ContentTransitions>
|
||||
<TransitionCollection>
|
||||
<EdgeUIThemeTransition Edge="Top" />
|
||||
</TransitionCollection>
|
||||
</coreControls:WinoNavigationViewItem.ContentTransitions>
|
||||
<coreControls:WinoNavigationViewItem.Icon>
|
||||
<PathIcon Data="F1 M 8.613281 17.5 C 8.75 17.942709 8.945312 18.359375 9.199219 18.75 L 4.921875 18.75 C 4.433594 18.75 3.966471 18.650717 3.520508 18.452148 C 3.074544 18.25358 2.683919 17.986654 2.348633 17.651367 C 2.013346 17.31608 1.746419 16.925455 1.547852 16.479492 C 1.349284 16.033529 1.25 15.566406 1.25 15.078125 L 1.25 4.921875 C 1.25 4.433594 1.349284 3.966473 1.547852 3.520508 C 1.746419 3.074545 2.013346 2.68392 2.348633 2.348633 C 2.683919 2.013348 3.074544 1.74642 3.520508 1.547852 C 3.966471 1.349285 4.433594 1.25 4.921875 1.25 L 15.078125 1.25 C 15.566406 1.25 16.033527 1.349285 16.479492 1.547852 C 16.925455 1.74642 17.31608 2.013348 17.651367 2.348633 C 17.986652 2.68392 18.25358 3.074545 18.452148 3.520508 C 18.650715 3.966473 18.75 4.433594 18.75 4.921875 L 18.75 6.572266 C 18.580729 6.344402 18.390299 6.132813 18.178711 5.9375 C 17.967121 5.742188 17.740885 5.566407 17.5 5.410156 L 17.5 4.951172 C 17.5 4.625651 17.433268 4.314779 17.299805 4.018555 C 17.16634 3.722332 16.987305 3.461914 16.762695 3.237305 C 16.538086 3.012695 16.277668 2.83366 15.981445 2.700195 C 15.685221 2.566732 15.374349 2.5 15.048828 2.5 L 4.951172 2.5 C 4.619141 2.5 4.303385 2.568359 4.003906 2.705078 C 3.704427 2.841797 3.44401 3.02409 3.222656 3.251953 C 3.001302 3.479818 2.825521 3.745117 2.695312 4.047852 C 2.565104 4.350587 2.5 4.66797 2.5 5 L 13.310547 5 C 12.60091 5.266928 11.998697 5.683594 11.503906 6.25 L 2.5 6.25 L 2.5 15.048828 C 2.5 15.38737 2.568359 15.704753 2.705078 16.000977 C 2.841797 16.297201 3.024088 16.55599 3.251953 16.777344 C 3.479818 16.998697 3.745117 17.174479 4.047852 17.304688 C 4.350586 17.434896 4.667969 17.5 5 17.5 Z" />
|
||||
</coreControls:WinoNavigationViewItem.Icon>
|
||||
|
||||
<Grid Height="50">
|
||||
<StackPanel VerticalAlignment="Center" Spacing="0">
|
||||
<TextBlock
|
||||
x:Name="AccountNameTextblock"
|
||||
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightByChildSelectedState(IsChildSelected), Mode=OneWay}"
|
||||
MaxLines="1"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{x:Bind MergedAccountName, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
|
||||
<TextBlock
|
||||
FontSize="12"
|
||||
MaxLines="1"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
TextTrimming="CharacterEllipsis">
|
||||
<Run Text="{x:Bind MergedAccountCount}" /><Run Text="{x:Bind domain:Translator.MenuMergedAccountItemAccountsSuffix}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</controls:AccountNavigationItem>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- Merged Account Common Folder Item -->
|
||||
<DataTemplate x:Key="MergedAccountFolderMenuItemTemplate" x:DataType="menu:MergedAccountFolderMenuItem">
|
||||
<coreControls:WinoNavigationViewItem
|
||||
MinHeight="30"
|
||||
AllowDrop="True"
|
||||
ContextRequested="MenuItemContextRequested"
|
||||
DataContext="{x:Bind}"
|
||||
DragEnter="ItemDragEnterOnFolder"
|
||||
DragLeave="ItemDragLeaveFromFolder"
|
||||
Drop="ItemDroppedOnFolder"
|
||||
FontSize="50"
|
||||
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightByChildSelectedState(IsSelected), Mode=OneWay}"
|
||||
IsExpanded="{x:Bind IsExpanded, Mode=TwoWay}"
|
||||
IsSelected="{x:Bind IsSelected, Mode=TwoWay}"
|
||||
SelectsOnInvoked="True"
|
||||
ToolTipService.ToolTip="{x:Bind FolderName, Mode=OneWay}">
|
||||
<animations:Implicit.Animations>
|
||||
<animations:ScaleAnimation Duration="0:0:0.5" />
|
||||
</animations:Implicit.Animations>
|
||||
<coreControls:WinoNavigationViewItem.Icon>
|
||||
<coreControls:WinoFontIcon FontSize="64" Icon="{x:Bind helpers:XamlHelpers.GetSpecialFolderPathIconGeometry(FolderType)}" />
|
||||
</coreControls:WinoNavigationViewItem.Icon>
|
||||
<muxc:NavigationViewItem.InfoBadge>
|
||||
<muxc:InfoBadge
|
||||
x:Name="FolderInfoBadge"
|
||||
Background="{StaticResource SystemAccentColor}"
|
||||
Foreground="White"
|
||||
Visibility="{x:Bind helpers:XamlHelpers.CountToVisibilityConverter(UnreadItemCount), Mode=OneWay}"
|
||||
Value="{x:Bind UnreadItemCount, Mode=OneWay}" />
|
||||
</muxc:NavigationViewItem.InfoBadge>
|
||||
<Grid
|
||||
x:Name="FolderBackgroundGrid"
|
||||
Padding="2"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock
|
||||
x:Name="NormalTitle"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightBySyncState(IsSelected), Mode=OneWay}"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{x:Bind FolderName, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</Grid>
|
||||
</coreControls:WinoNavigationViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- Merged Account More Expansion Item -->
|
||||
<DataTemplate x:Key="MergedAccountMoreFolderItemTemplate" x:DataType="menu:MergedAccountMoreFolderMenuItem">
|
||||
<coreControls:WinoNavigationViewItem
|
||||
MinHeight="30"
|
||||
Content="{x:Bind domain:Translator.More}"
|
||||
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightByChildSelectedState(IsSelected), Mode=OneWay}"
|
||||
IsExpanded="{x:Bind IsExpanded, Mode=TwoWay}"
|
||||
IsSelected="{x:Bind IsSelected, Mode=TwoWay}"
|
||||
MenuItemsSource="{x:Bind SubMenuItems, Mode=OneWay}"
|
||||
SelectsOnInvoked="True">
|
||||
<animations:Implicit.Animations>
|
||||
<animations:ScaleAnimation Duration="0:0:0.5" />
|
||||
</animations:Implicit.Animations>
|
||||
<coreControls:WinoNavigationViewItem.Icon>
|
||||
<coreControls:WinoFontIcon FontSize="64" Icon="{x:Bind helpers:XamlHelpers.GetSpecialFolderPathIconGeometry(enums:SpecialFolderType.More)}" />
|
||||
</coreControls:WinoNavigationViewItem.Icon>
|
||||
</coreControls:WinoNavigationViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<coreSelectors:NavigationMenuTemplateSelector
|
||||
x:Key="NavigationMenuTemplateSelector"
|
||||
AccountManagementTemplate="{StaticResource ManageAccountsTemplate}"
|
||||
ClickableAccountMenuTemplate="{StaticResource ClickableAccountMenuTemplate}"
|
||||
FixAuthenticationIssueTemplate="{StaticResource FixAuthenticationIssueTemplate}"
|
||||
FixMissingFolderConfigTemplate="{StaticResource FixMissingFolderConfig}"
|
||||
FolderMenuTemplate="{StaticResource FolderMenuTemplate}"
|
||||
MergedAccountFolderTemplate="{StaticResource MergedAccountFolderMenuItemTemplate}"
|
||||
MergedAccountMoreExpansionItemTemplate="{StaticResource MergedAccountMoreFolderItemTemplate}"
|
||||
MergedAccountTemplate="{StaticResource MergedAccountTemplate}"
|
||||
NewMailTemplate="{StaticResource CreateNewMailTemplate}"
|
||||
RatingItemTemplate="{StaticResource RatingItemTemplate}"
|
||||
SeperatorTemplate="{StaticResource SeperatorTemplate}"
|
||||
SettingsItemTemplate="{StaticResource SettingsItemTemplate}" />
|
||||
</Page.Resources>
|
||||
|
||||
<Grid
|
||||
x:Name="RootGrid"
|
||||
Padding="0"
|
||||
ColumnSpacing="0"
|
||||
RowSpacing="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="48" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="48" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid
|
||||
Grid.RowSpan="2"
|
||||
Grid.ColumnSpan="2"
|
||||
Background="{ThemeResource WinoApplicationBackgroundColor}"
|
||||
IsHitTestVisible="False" />
|
||||
|
||||
<muxc:NavigationView
|
||||
x:Name="navigationView"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="3"
|
||||
Margin="-1,-1,0,0"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
AlwaysShowHeader="True"
|
||||
DisplayModeChanged="NavigationViewDisplayModeChanged"
|
||||
FooterMenuItemsSource="{x:Bind ViewModel.FooterItems, Mode=OneWay}"
|
||||
IsBackButtonVisible="Collapsed"
|
||||
IsPaneOpen="{x:Bind ViewModel.PreferencesService.IsNavigationPaneOpened, Mode=TwoWay}"
|
||||
IsPaneToggleButtonVisible="False"
|
||||
IsSettingsVisible="False"
|
||||
IsTabStop="True"
|
||||
IsTitleBarAutoPaddingEnabled="False"
|
||||
ItemInvoked="NavigationViewItemInvoked"
|
||||
MenuItemTemplateSelector="{StaticResource NavigationMenuTemplateSelector}"
|
||||
MenuItemsSource="{x:Bind ViewModel.MenuItems, Mode=OneWay}"
|
||||
OpenPaneLength="{x:Bind ViewModel.StatePersistenceService.OpenPaneLength, Mode=TwoWay}"
|
||||
PaneDisplayMode="Auto"
|
||||
PaneOpening="NavigationPaneOpening"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Hidden"
|
||||
SelectedItem="{x:Bind ViewModel.SelectedMenuItem, Mode=TwoWay}"
|
||||
SelectionChanged="MenuSelectionChanged">
|
||||
<muxc:NavigationView.ContentTransitions>
|
||||
<TransitionCollection>
|
||||
<AddDeleteThemeTransition />
|
||||
</TransitionCollection>
|
||||
</muxc:NavigationView.ContentTransitions>
|
||||
<Grid ColumnSpacing="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<controls1:PropertySizer
|
||||
Width="1"
|
||||
HorizontalAlignment="Left"
|
||||
Background="Transparent"
|
||||
Binding="{x:Bind ViewModel.StatePersistenceService.OpenPaneLength, Mode=TwoWay}"
|
||||
Canvas.ZIndex="20"
|
||||
Foreground="Transparent"
|
||||
IsHitTestVisible="{x:Bind navigationView.IsPaneOpen, Mode=OneWay}"
|
||||
IsTabStop="False"
|
||||
Maximum="1000"
|
||||
Minimum="116" />
|
||||
|
||||
<!-- Main Content -->
|
||||
<Frame
|
||||
x:Name="ShellFrame"
|
||||
Padding="0,0,7,7"
|
||||
IsNavigationStackEnabled="False"
|
||||
Navigated="ShellFrameContentNavigated">
|
||||
<Frame.ContentTransitions>
|
||||
<TransitionCollection>
|
||||
<PopupThemeTransition />
|
||||
</TransitionCollection>
|
||||
</Frame.ContentTransitions>
|
||||
</Frame>
|
||||
|
||||
<!-- InfoBar -->
|
||||
<coreControls:WinoInfoBar
|
||||
x:Name="ShellInfoBar"
|
||||
MaxWidth="700"
|
||||
Margin="0,60,25,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
IsClosable="False"
|
||||
IsOpen="False" />
|
||||
|
||||
<!-- Teaching Tip -->
|
||||
<muxc:TeachingTip
|
||||
x:Name="ShellTip"
|
||||
IsOpen="False"
|
||||
PreferredPlacement="Bottom"
|
||||
Target="{x:Bind ShellInfoBar}" />
|
||||
</Grid>
|
||||
</muxc:NavigationView>
|
||||
|
||||
<!--<coreControls:WinoAppTitleBar
|
||||
x:Name="RealAppBar"
|
||||
Grid.ColumnSpan="2"
|
||||
BackButtonClicked="BackButtonClicked"
|
||||
Canvas.ZIndex="150"
|
||||
ConnectionStatus="{x:Bind ViewModel.ActiveConnectionStatus, Mode=OneWay}"
|
||||
CoreWindowText="{x:Bind ViewModel.StatePersistenceService.CoreWindowTitle, Mode=OneWay}"
|
||||
IsBackButtonVisible="{x:Bind ViewModel.StatePersistenceService.IsBackButtonVisible, Mode=OneWay}"
|
||||
IsDragArea="True"
|
||||
IsNavigationPaneOpen="{x:Bind navigationView.IsPaneOpen, Mode=TwoWay}"
|
||||
IsReaderNarrowed="{x:Bind ViewModel.StatePersistenceService.IsReaderNarrowed, Mode=OneWay}"
|
||||
NavigationViewDisplayMode="{x:Bind navigationView.DisplayMode, Mode=OneWay}"
|
||||
OpenPaneLength="{x:Bind ViewModel.StatePersistenceService.OpenPaneLength, Mode=OneWay}"
|
||||
ReadingPaneLength="{x:Bind ViewModel.StatePersistenceService.MailListPaneLength, Mode=OneWay}"
|
||||
ReconnectCommand="{x:Bind ViewModel.ReconnectServerCommand}"
|
||||
SystemReserved="180" />-->
|
||||
</Grid>
|
||||
</abstract:AppShellAbstract>
|
||||
@@ -0,0 +1,327 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Windows.Foundation;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.UWP.Controls;
|
||||
using Wino.Extensions;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
using Wino.MenuFlyouts;
|
||||
using Wino.MenuFlyouts.Context;
|
||||
using Wino.Messaging.Client.Accounts;
|
||||
using Wino.Messaging.Client.Mails;
|
||||
using Wino.Messaging.Client.Shell;
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views;
|
||||
|
||||
public sealed partial class AppShell : AppShellAbstract,
|
||||
IRecipient<AccountMenuItemExtended>,
|
||||
IRecipient<NavigateMailFolderEvent>,
|
||||
IRecipient<CreateNewMailWithMultipleAccountsRequested>,
|
||||
IRecipient<InfoBarMessageRequested>
|
||||
{
|
||||
public AppShell() : base()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void ItemDroppedOnFolder(object sender, DragEventArgs e)
|
||||
{
|
||||
// Validate package content.
|
||||
if (sender is WinoNavigationViewItem droppedContainer)
|
||||
{
|
||||
droppedContainer.IsDraggingItemOver = false;
|
||||
|
||||
if (CanContinueDragDrop(droppedContainer, e))
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (item is MailItemViewModel singleMailItemViewModel)
|
||||
{
|
||||
mailCopies.Add(singleMailItemViewModel.MailCopy);
|
||||
}
|
||||
else if (item is ThreadMailItemViewModel threadViewModel)
|
||||
{
|
||||
mailCopies.AddRange(threadViewModel.GetMailCopies());
|
||||
}
|
||||
}
|
||||
|
||||
await ViewModel.PerformMoveOperationAsync(mailCopies, draggingFolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ItemDragLeaveFromFolder(object sender, DragEventArgs e)
|
||||
{
|
||||
if (sender is WinoNavigationViewItem leavingContainer)
|
||||
{
|
||||
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))
|
||||
{
|
||||
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));
|
||||
}
|
||||
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);
|
||||
|
||||
// Find the folder.
|
||||
|
||||
if (ViewModel.MenuItems.TryGetFolderMenuItem(message.FolderId, out IBaseFolderMenuItem accountFolderMenuItem))
|
||||
{
|
||||
accountFolderMenuItem.Expand();
|
||||
|
||||
await ViewModel.NavigateFolderAsync(accountFolderMenuItem);
|
||||
|
||||
navigationView.SelectedItem = accountFolderMenuItem;
|
||||
|
||||
// At this point folder is navigated and items are loaded.
|
||||
WeakReferenceMessenger.Default.Send(new MailItemNavigationRequested(message.NavigateMailItem.UniqueId, ScrollToItem: true));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private async void NavigationViewItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args)
|
||||
{
|
||||
// SelectsOnInvoked is handled in MenuSelectionChanged.
|
||||
// This part is only for the items that are not selectable.
|
||||
if (args.InvokedItemContainer is WinoNavigationViewItem winoNavigationViewItem)
|
||||
{
|
||||
if (winoNavigationViewItem.SelectsOnInvoked) return;
|
||||
|
||||
await ViewModel.MenuItemInvokedOrSelectedAsync(winoNavigationViewItem.DataContext as IMenuItem);
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(NavigateMailFolderEvent message)
|
||||
{
|
||||
if (message.BaseFolderMenuItem == null) return;
|
||||
|
||||
if (navigationView.SelectedItem != message.BaseFolderMenuItem)
|
||||
{
|
||||
var navigateFolderArgs = new NavigateMailFolderEventArgs(message.BaseFolderMenuItem, message.FolderInitLoadAwaitTask);
|
||||
|
||||
ViewModel.NavigationService.Navigate(WinoPage.MailListPage, navigateFolderArgs, NavigationReferenceFrame.ShellFrame);
|
||||
|
||||
// Prevent double navigation.
|
||||
navigationView.SelectionChanged -= MenuSelectionChanged;
|
||||
navigationView.SelectedItem = message.BaseFolderMenuItem;
|
||||
navigationView.SelectionChanged += MenuSelectionChanged;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Complete the init task since we are already on the right page.
|
||||
message.FolderInitLoadAwaitTask?.TrySetResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShellFrameContentNavigated(object sender, Microsoft.UI.Xaml.Navigation.NavigationEventArgs e)
|
||||
{
|
||||
// => RealAppBar.ShellFrameContent = (e.Content as BasePage).ShellContent;
|
||||
|
||||
// TODO: WinUI3: Update shell content. Just remove it.
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(CreateNewMailWithMultipleAccountsRequested message)
|
||||
{
|
||||
// Find the NewMail menu item container.
|
||||
|
||||
var container = navigationView.ContainerFromMenuItem(ViewModel.CreateMailMenuItem);
|
||||
|
||||
var flyout = new AccountSelectorFlyout(message.AllAccounts, ViewModel.CreateNewMailForAsync);
|
||||
|
||||
flyout.ShowAt(container, new FlyoutShowOptions()
|
||||
{
|
||||
ShowMode = FlyoutShowMode.Auto,
|
||||
Placement = FlyoutPlacementMode.Right
|
||||
});
|
||||
}
|
||||
|
||||
private void NavigationPaneOpening(Microsoft.UI.Xaml.Controls.NavigationView sender, object args)
|
||||
{
|
||||
// It's annoying that NavigationView doesn't respect expansion state of the items in Minimal display mode.
|
||||
// Expanded items are collaped, and users need to expand them again.
|
||||
// Regardless of the reason, we will expand the selected item if it's a folder with parent account for visibility.
|
||||
|
||||
if (sender.DisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Minimal && sender.SelectedItem is IFolderMenuItem selectedFolderMenuItem)
|
||||
{
|
||||
selectedFolderMenuItem.Expand();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// InfoBar message is requested.
|
||||
/// </summary>
|
||||
public async void Receive(InfoBarMessageRequested message)
|
||||
{
|
||||
await DispatcherQueue.EnqueueAsync(async () =>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 209 B |
|
After Width: | Height: | Size: 238 B |
|
After Width: | Height: | Size: 286 B |
|
After Width: | Height: | Size: 360 B |
|
After Width: | Height: | Size: 673 B |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 432 B |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,71 @@
|
||||
# 🚀 Welcome to Wino Mail v1.10.2
|
||||
|
||||
It is time for another pack of improvements for Wino Mail! Checkout the latest updates we packaged for this version:
|
||||
|
||||
## .NET 9 Upgrade
|
||||
|
||||
For most of you, this may not bring significant improvements, but our underlying (and outdated) technology stack moves a step further with this change. We got rid of the outdated technologies for the codebase to bring significant updates easier later on.
|
||||
|
||||
If you are a developer and want to learn about how .NET9 affects the UWP applications, checkout [this blog post](https://devblogs.microsoft.com/ifdef-windows/preview-uwp-support-for-dotnet-9-native-aot/) to learn more about our upgrade journey. Native AOT is still in progress and will be available in later updates.
|
||||
|
||||
## Account Colors and Edit Account Details
|
||||
|
||||
In older versions, you were only able to rename your account name in account details. Now there is a new page where you can edit account name, sender name and assign a new account color to highlight account icon on the side bar menu.
|
||||
|
||||

|
||||
|
||||
For IMAP accounts, you can change your configuration as well.
|
||||
|
||||

|
||||
|
||||
## Online Search
|
||||
|
||||
One of the common issues users report as a feedback is that they can't find the mail they are looking for. Main problem for this issue is that Wino Mail does not synchronize all your mails (at least for some mail providers like Outlook) for performance reasons. Search was performed for the downloaded mails locally in offline mode.
|
||||
|
||||
Just like how old Windows Mail used to work, Wino Mail can now perform provider specific queries to perform an online search and download the missing mails. If you can't find what you are looking for you can always do an online search using this button under the search results.
|
||||
|
||||

|
||||
|
||||
Gmail has it's own query language to perform the filter such as "label:UNREAD". This is also supported. You can use the search just like in Gmail Web UI and get the mails you need.
|
||||
|
||||
Default search mode is still Local, meaning that all searches will be performed in offline mode and the button will be visible. If you want to switch the default search mode to Online; go to Settings -> App Preferences -> Default Search Mode to change it
|
||||
|
||||

|
||||
|
||||
## Live changes for IMAP and stability improvements.
|
||||
|
||||
For IMAP servers that support [IDLE command](https://datatracker.ietf.org/doc/html/rfc2177.html) live changes to Inbox folder will be listened with minimum effort. This means that whenever you recieve a mail or some flag (read/unread etc.) changes in your Inbox folder, changes are immidiately reflected to Wino without requiring a synchronization.
|
||||
|
||||
On top of that; this update brings significant stability and performance improvements to all IMAP servers. We have reworked our IMAP synchronizers to be more resource efficient and performant.
|
||||
|
||||
## iCloud and Yahoo on setup dialog
|
||||
|
||||
Account setup dialog is more streamlined in this version. Now it supports iCloud and Yahoo; with an additional helping links to create app-specific password to login with Wino. This is the first effort to make setting up accounts easier for users and there will be more in the future.
|
||||
|
||||

|
||||
|
||||
## Gmail Archive Functionality
|
||||
|
||||
In reality, Archive folder is a virtual folder in Gmail that doesn't exist. For Gmail, archiving means 'it doesn't belong to Inbox' or putting in words as Google "something that does not have Inbox label". Due to limitations in Wino Mail's architecture and lack of support in Gmail API; Archive functionality used to work by moving the mails you marked as 'archived' to your special archive folder you have configured in account settings. This behavior is no longer exists and archiving/unarchiving will work as it is in Gmail web UI.
|
||||
|
||||
Starting from this version you will be able to see "Archive" folder for your Gmail accounts and all your archived mails will be synchronized on your next sync. (If you don't see the folder, relaunch the app after doing a synchronization.)
|
||||
|
||||

|
||||
|
||||
## Additional Bugfixes and Improvements
|
||||
|
||||
As always, this major release has a lot of overall bugfixes for the application.
|
||||
|
||||
* Fixed 410 GONE error for Outlook.
|
||||
* Fixed 404 NOT FOUND error for Gmail. If your local cache is expired and Wino can't store the state of your mails, it will re-synchronize everything to keep state healthy.
|
||||
* AppCenter SDK is removed. Logging and analytics are migrated to Azure App Insights.
|
||||
* Diagnostic ID is implemented to track user errors better. If you had an error in the app and share the Diagnostic Id under Settings -> About -> Diagnostics, we can easily track down the issue you had to provide better help.
|
||||
* Fixed the issue with dates and times are not translated properly to selected application language.
|
||||
* Prevented moving mails in linked accounts when users try to move multiple mails that belong to different accounts.
|
||||
* Implemented IMAP setup dialog validations to notify users during IMAP account creation for missing fields.
|
||||
* Displaying "You" for received mails instead of your full mail address by @Tiktack in https://github.com/bkaankose/Wino-Mail/pull/566
|
||||
* Automatically saving drafts on app close to prevent data loss by @Tiktack in https://github.com/bkaankose/Wino-Mail/pull/546
|
||||
* Displaying full message (MIME) source by @Tiktack in https://github.com/bkaankose/Wino-Mail/pull/541
|
||||
* Clearing rendered text selection when changing mails by @Tiktack in https://github.com/bkaankose/Wino-Mail/pull/543
|
||||
* Creating a shared web editor component by @Tiktack in https://github.com/bkaankose/Wino-Mail/pull/578
|
||||
* Clickable plaintext links and fixes to dark mode by @KamilDev in https://github.com/bkaankose/Wino-Mail/pull/488
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 755 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 755 B |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 592 B |
|
After Width: | Height: | Size: 962 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 456 B |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 28 KiB |
@@ -0,0 +1,199 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.Xaml.Interactivity;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Menus;
|
||||
using Wino.Core.UWP.Controls;
|
||||
using Wino.Helpers;
|
||||
using Wino.Mail.WinUI;
|
||||
|
||||
namespace Wino.Behaviors;
|
||||
|
||||
public partial class BindableCommandBarBehavior : Behavior<CommandBar>
|
||||
{
|
||||
private readonly IPreferencesService _preferencesService = App.Current.Services.GetService<IPreferencesService>();
|
||||
public static readonly DependencyProperty PrimaryCommandsProperty = DependencyProperty.Register(
|
||||
"PrimaryCommands", typeof(object), typeof(BindableCommandBarBehavior),
|
||||
new PropertyMetadata(null, UpdateCommands));
|
||||
|
||||
[GeneratedDependencyProperty]
|
||||
public partial ICommand ItemClickedCommand { get; set; }
|
||||
|
||||
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)
|
||||
{
|
||||
if (item is ButtonBase button)
|
||||
{
|
||||
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 not 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);
|
||||
var labelPosition = string.IsNullOrWhiteSpace(label) || !_preferencesService.IsShowActionLabelsEnabled ?
|
||||
CommandBarLabelPosition.Collapsed : CommandBarLabelPosition.Default;
|
||||
menuItem = new AppBarButton
|
||||
{
|
||||
Width = double.NaN,
|
||||
MinWidth = 40,
|
||||
Icon = new WinoFontIcon() { Glyph = ControlConstants.WinoIconFontDictionary[XamlHelpers.GetWinoIconGlyph(mailOperationMenuItem.Operation)] },
|
||||
Label = label,
|
||||
LabelPosition = labelPosition,
|
||||
DataContext = mailOperationMenuItem,
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(label))
|
||||
{
|
||||
var 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 not 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.Xaml.Interactivity;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.UWP.Controls;
|
||||
|
||||
namespace Wino.Behaviors;
|
||||
|
||||
public class CreateMailNavigationItemBehavior : Behavior<WinoNavigationViewItem>
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
// ??
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.Numerics;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.UWP.Controls;
|
||||
|
||||
namespace Wino.Controls;
|
||||
|
||||
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 IAccountMenuItem BindingData
|
||||
{
|
||||
get { return (IAccountMenuItem)GetValue(BindingDataProperty); }
|
||||
set { SetValue(BindingDataProperty, value); }
|
||||
}
|
||||
|
||||
private const string PART_NavigationViewItemMenuItemsHost = "NavigationViewItemMenuItemsHost";
|
||||
private const string PART_SelectionIndicator = "CustomSelectionIndicator";
|
||||
|
||||
private ItemsRepeater _itemsRepeater;
|
||||
private Microsoft.UI.Xaml.Shapes.Rectangle _selectionIndicator;
|
||||
|
||||
public AccountNavigationItem()
|
||||
{
|
||||
DefaultStyleKey = typeof(AccountNavigationItem);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_itemsRepeater = GetTemplateChild(PART_NavigationViewItemMenuItemsHost) as ItemsRepeater;
|
||||
_selectionIndicator = GetTemplateChild(PART_SelectionIndicator) as Microsoft.UI.Xaml.Shapes.Rectangle;
|
||||
|
||||
UpdateSelectionBorder();
|
||||
}
|
||||
|
||||
private static void OnIsActiveAccountChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
if (obj is AccountNavigationItem control)
|
||||
control.UpdateSelectionBorder();
|
||||
}
|
||||
|
||||
private void UpdateSelectionBorder()
|
||||
{
|
||||
if (_selectionIndicator == null) return;
|
||||
|
||||
_selectionIndicator.Scale = IsActiveAccount ? new Vector3(1, 1, 1) : new Vector3(0, 0, 0);
|
||||
_selectionIndicator.Visibility = IsActiveAccount ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using MoreLinq;
|
||||
using Serilog;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Extensions;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
using Wino.Mail.ViewModels.Messages;
|
||||
|
||||
namespace Wino.Controls.Advanced;
|
||||
|
||||
/// <summary>
|
||||
/// Custom ListView control that handles multiple selection with Extended/Multiple selection mode
|
||||
/// and supports threads.
|
||||
/// </summary>
|
||||
public partial class WinoListView : ListView, IDisposable
|
||||
{
|
||||
private ILogger logger = Log.ForContext<WinoListView>();
|
||||
|
||||
private const string PART_ScrollViewer = "ScrollViewer";
|
||||
private ScrollViewer internalScrollviewer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this ListView belongs to thread items.
|
||||
/// This is important for detecting selected items etc.
|
||||
/// </summary>
|
||||
public bool IsThreadListView
|
||||
{
|
||||
get { return (bool)GetValue(IsThreadListViewProperty); }
|
||||
set { SetValue(IsThreadListViewProperty, value); }
|
||||
}
|
||||
|
||||
public ICommand ItemDeletedCommand
|
||||
{
|
||||
get { return (ICommand)GetValue(ItemDeletedCommandProperty); }
|
||||
set { SetValue(ItemDeletedCommandProperty, value); }
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
logger.Warning("WinoListView does not have an internal ScrollViewer. Infinite scrolling behavior might be effected.");
|
||||
return;
|
||||
}
|
||||
|
||||
internalScrollviewer.ViewChanged -= InternalScrollVeiwerViewChanged;
|
||||
internalScrollviewer.ViewChanged += InternalScrollVeiwerViewChanged;
|
||||
}
|
||||
|
||||
private static void OnIsThreadViewChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
if (obj is WinoListView winoListView)
|
||||
{
|
||||
winoListView.AdjustThreadViewContainerVisuals();
|
||||
}
|
||||
}
|
||||
|
||||
private void AdjustThreadViewContainerVisuals()
|
||||
{
|
||||
if (IsThreadListView)
|
||||
{
|
||||
ItemContainerTransitions.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private double lastestRaisedOffset = 0;
|
||||
private int lastItemSize = 0;
|
||||
|
||||
// TODO: This is buggy. Does not work all the time. Debug.
|
||||
|
||||
private void InternalScrollVeiwerViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
|
||||
{
|
||||
if (internalScrollviewer == null) return;
|
||||
|
||||
// No need to raise init request if there are no items in the list.
|
||||
if (Items.Count == 0) return;
|
||||
|
||||
// If the scrolling is finished, check the current viewport height.
|
||||
if (e.IsIntermediate)
|
||||
{
|
||||
var currentOffset = internalScrollviewer.VerticalOffset;
|
||||
var maxOffset = internalScrollviewer.ScrollableHeight;
|
||||
|
||||
if (currentOffset + 10 >= maxOffset && lastestRaisedOffset != maxOffset && Items.Count != lastItemSize)
|
||||
{
|
||||
// We must load more.
|
||||
lastestRaisedOffset = maxOffset;
|
||||
lastItemSize = Items.Count;
|
||||
|
||||
LoadMoreCommand?.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessDelKey(UIElement sender, Microsoft.UI.Xaml.Input.ProcessKeyboardAcceleratorEventArgs args)
|
||||
{
|
||||
if (args.Key == Windows.System.VirtualKey.Delete)
|
||||
{
|
||||
args.Handled = true;
|
||||
|
||||
ItemDeletedCommand?.Execute(MailOperation.SoftDelete);
|
||||
}
|
||||
}
|
||||
|
||||
private void ItemDragCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
|
||||
{
|
||||
if (args.Items.Any(a => a is MailItemViewModel))
|
||||
{
|
||||
args.Items.Cast<MailItemViewModel>().ForEach(a => a.IsCustomFocused = false);
|
||||
}
|
||||
}
|
||||
|
||||
private void ItemDragStarting(object sender, DragItemsStartingEventArgs args)
|
||||
{
|
||||
// Dragging multiple mails from different accounts/folders are supported with the condition below:
|
||||
// All mails belongs to the drag will be matched on the dropped folder's account.
|
||||
// Meaning that if users drag 1 mail from Account A/Inbox and 1 mail from Account B/Inbox,
|
||||
// and drop to Account A/Inbox, the mail from Account B/Inbox will NOT be moved.
|
||||
|
||||
if (IsThreadListView)
|
||||
{
|
||||
var allItems = args.Items.Cast<MailItemViewModel>();
|
||||
|
||||
// Highlight all items
|
||||
allItems.ForEach(a => a.IsCustomFocused = true);
|
||||
|
||||
// Set native drag arg properties.
|
||||
|
||||
var dragPackage = new MailDragPackage(allItems.Cast<IMailItem>());
|
||||
|
||||
args.Data.Properties.Add(nameof(MailDragPackage), dragPackage);
|
||||
}
|
||||
else
|
||||
{
|
||||
var dragPackage = new MailDragPackage(args.Items.Cast<IMailItem>());
|
||||
|
||||
args.Data.Properties.Add(nameof(MailDragPackage), dragPackage);
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeSelectionMode(ListViewSelectionMode selectionMode)
|
||||
{
|
||||
SelectionMode = selectionMode;
|
||||
|
||||
if (!IsThreadListView)
|
||||
{
|
||||
Items.Where(a => a is ThreadMailItemViewModel).Cast<ThreadMailItemViewModel>().ForEach(c =>
|
||||
{
|
||||
var threadListView = GetThreadInternalListView(c);
|
||||
|
||||
if (threadListView != null)
|
||||
{
|
||||
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.SelectedItems.Contains(exceptViewModel))
|
||||
{
|
||||
if (!preserveThreadExpanding)
|
||||
{
|
||||
c.IsThreadExpanded = false;
|
||||
}
|
||||
|
||||
threadListView.SelectedItems.Clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!preserveThreadExpanding)
|
||||
{
|
||||
c.IsThreadExpanded = false;
|
||||
}
|
||||
|
||||
threadListView.SelectedItems.Clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively selects all mails, including thread items.
|
||||
/// </summary>
|
||||
public void SelectAllWino()
|
||||
{
|
||||
SelectAll();
|
||||
|
||||
Items.Where(a => a is ThreadMailItemViewModel).Cast<ThreadMailItemViewModel>().ForEach(c =>
|
||||
{
|
||||
c.IsThreadExpanded = true;
|
||||
|
||||
var threadListView = GetThreadInternalListView(c);
|
||||
|
||||
threadListView?.SelectAll();
|
||||
});
|
||||
}
|
||||
|
||||
// SelectedItems changed.
|
||||
private void SelectedItemsChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (e.RemovedItems != null)
|
||||
{
|
||||
foreach (var removedItem in e.RemovedItems)
|
||||
{
|
||||
if (removedItem is MailItemViewModel removedMailItemViewModel)
|
||||
{
|
||||
// Mail item un-selected.
|
||||
|
||||
removedMailItemViewModel.IsSelected = false;
|
||||
WeakReferenceMessenger.Default.Send(new MailItemSelectionRemovedEvent(removedMailItemViewModel));
|
||||
}
|
||||
else if (removedItem is ThreadMailItemViewModel removedThreadItemViewModel)
|
||||
{
|
||||
removedThreadItemViewModel.IsThreadExpanded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (e.AddedItems != null)
|
||||
{
|
||||
foreach (var addedItem in e.AddedItems)
|
||||
{
|
||||
if (addedItem is MailItemViewModel addedMailItemViewModel)
|
||||
{
|
||||
// Mail item selected.
|
||||
|
||||
addedMailItemViewModel.IsSelected = true;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new MailItemSelectedEvent(addedMailItemViewModel));
|
||||
}
|
||||
else if (addedItem is ThreadMailItemViewModel threadMailItemViewModel)
|
||||
{
|
||||
if (IsThreadScrollingEnabled)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 =>
|
||||
{
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Fernandezja.ColorHashSharp;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Microsoft.UI.Xaml.Shapes;
|
||||
using Windows.UI;
|
||||
using Wino.Mail.WinUI;
|
||||
|
||||
namespace Wino.Controls;
|
||||
|
||||
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";
|
||||
private const string PART_FaviconSquircle = "FaviconSquircle";
|
||||
private const string PART_FaviconImage = "FaviconImage";
|
||||
|
||||
#region Dependency Properties
|
||||
|
||||
public static readonly DependencyProperty FromNameProperty = DependencyProperty.Register(nameof(FromName), typeof(string), typeof(ImagePreviewControl), new PropertyMetadata(string.Empty, OnInformationChanged));
|
||||
public static readonly DependencyProperty FromAddressProperty = DependencyProperty.Register(nameof(FromAddress), typeof(string), typeof(ImagePreviewControl), new PropertyMetadata(string.Empty, OnInformationChanged));
|
||||
public static readonly DependencyProperty SenderContactPictureProperty = DependencyProperty.Register(nameof(SenderContactPicture), typeof(string), typeof(ImagePreviewControl), new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnInformationChanged)));
|
||||
public static readonly DependencyProperty ThumbnailUpdatedEventProperty = DependencyProperty.Register(nameof(ThumbnailUpdatedEvent), typeof(bool), typeof(ImagePreviewControl), new PropertyMetadata(false, new PropertyChangedCallback(OnInformationChanged)));
|
||||
|
||||
public bool ThumbnailUpdatedEvent
|
||||
{
|
||||
get { return (bool)GetValue(ThumbnailUpdatedEventProperty); }
|
||||
set { SetValue(ThumbnailUpdatedEventProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets base64 string of the sender contact picture.
|
||||
/// </summary>
|
||||
public string SenderContactPicture
|
||||
{
|
||||
get { return (string)GetValue(SenderContactPictureProperty); }
|
||||
set { SetValue(SenderContactPictureProperty, value); }
|
||||
}
|
||||
|
||||
public string FromName
|
||||
{
|
||||
get { return (string)GetValue(FromNameProperty); }
|
||||
set { SetValue(FromNameProperty, value); }
|
||||
}
|
||||
|
||||
public string FromAddress
|
||||
{
|
||||
get { return (string)GetValue(FromAddressProperty); }
|
||||
set { SetValue(FromAddressProperty, value); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Ellipse Ellipse;
|
||||
private Grid InitialsGrid;
|
||||
private TextBlock InitialsTextblock;
|
||||
private Image KnownHostImage;
|
||||
private Border FaviconSquircle;
|
||||
private Image FaviconImage;
|
||||
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;
|
||||
FaviconSquircle = GetTemplateChild(PART_FaviconSquircle) as Border;
|
||||
FaviconImage = GetTemplateChild(PART_FaviconImage) as Image;
|
||||
|
||||
UpdateInformation();
|
||||
}
|
||||
|
||||
private static void OnInformationChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
if (obj is ImagePreviewControl control)
|
||||
control.UpdateInformation();
|
||||
}
|
||||
|
||||
private async void UpdateInformation()
|
||||
{
|
||||
if ((KnownHostImage == null && FaviconSquircle == null) || InitialsGrid == null || InitialsTextblock == null || (string.IsNullOrEmpty(FromName) && string.IsNullOrEmpty(FromAddress)))
|
||||
return;
|
||||
|
||||
// Cancel active image loading if exists.
|
||||
if (!contactPictureLoadingCancellationTokenSource?.IsCancellationRequested ?? false)
|
||||
{
|
||||
contactPictureLoadingCancellationTokenSource.Cancel();
|
||||
}
|
||||
|
||||
string contactPicture = SenderContactPicture;
|
||||
|
||||
var isAvatarThumbnail = false;
|
||||
|
||||
if (string.IsNullOrEmpty(contactPicture) && !string.IsNullOrEmpty(FromAddress))
|
||||
{
|
||||
contactPicture = await App.Current.ThumbnailService.GetThumbnailAsync(FromAddress);
|
||||
isAvatarThumbnail = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(contactPicture))
|
||||
{
|
||||
if (isAvatarThumbnail && FaviconSquircle != null && FaviconImage != null)
|
||||
{
|
||||
// Show favicon in squircle
|
||||
FaviconSquircle.Visibility = Visibility.Visible;
|
||||
InitialsGrid.Visibility = Visibility.Collapsed;
|
||||
KnownHostImage.Visibility = Visibility.Collapsed;
|
||||
|
||||
var bitmapImage = await GetBitmapImageAsync(contactPicture);
|
||||
|
||||
if (bitmapImage != null)
|
||||
{
|
||||
FaviconImage.Source = bitmapImage;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Show normal avatar (tondo)
|
||||
FaviconSquircle.Visibility = Visibility.Collapsed;
|
||||
KnownHostImage.Visibility = Visibility.Collapsed;
|
||||
InitialsGrid.Visibility = Visibility.Visible;
|
||||
contactPictureLoadingCancellationTokenSource = new CancellationTokenSource();
|
||||
try
|
||||
{
|
||||
var brush = await GetContactImageBrushAsync(contactPicture);
|
||||
|
||||
if (brush != null)
|
||||
{
|
||||
if (!contactPictureLoadingCancellationTokenSource?.Token.IsCancellationRequested ?? false)
|
||||
{
|
||||
Ellipse.Fill = brush;
|
||||
InitialsTextblock.Text = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Debugger.Break();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FaviconSquircle.Visibility = Visibility.Collapsed;
|
||||
KnownHostImage.Visibility = Visibility.Collapsed;
|
||||
InitialsGrid.Visibility = Visibility.Visible;
|
||||
|
||||
var colorHash = new ColorHash();
|
||||
var rgb = colorHash.Rgb(FromAddress);
|
||||
|
||||
Ellipse.Fill = new SolidColorBrush(Color.FromArgb(rgb.A, rgb.R, rgb.G, rgb.B));
|
||||
InitialsTextblock.Text = ExtractInitialsFromName(FromName);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<ImageBrush> GetContactImageBrushAsync(string base64)
|
||||
{
|
||||
// Load the image from base64 string.
|
||||
|
||||
var bitmapImage = await GetBitmapImageAsync(base64);
|
||||
|
||||
if (bitmapImage == null) return null;
|
||||
|
||||
return new ImageBrush() { ImageSource = bitmapImage };
|
||||
}
|
||||
|
||||
private static async Task<BitmapImage> GetBitmapImageAsync(string base64)
|
||||
{
|
||||
try
|
||||
{
|
||||
var bitmapImage = new BitmapImage();
|
||||
var imageArray = Convert.FromBase64String(base64);
|
||||
var imageStream = new MemoryStream(imageArray);
|
||||
var randomAccessImageStream = imageStream.AsRandomAccessStream();
|
||||
randomAccessImageStream.Seek(0);
|
||||
await bitmapImage.SetSourceAsync(randomAccessImageStream);
|
||||
return bitmapImage;
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
<UserControl
|
||||
x:Class="Wino.Controls.MailItemDisplayInformationControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals"
|
||||
xmlns:controls="using:Wino.Controls"
|
||||
xmlns:coreControls="using:Wino.Core.UWP.Controls"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
xmlns:enums="using:Wino.Core.Domain.Enums"
|
||||
xmlns:helpers="using:Wino.Helpers"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
FocusVisualMargin="8"
|
||||
FocusVisualPrimaryBrush="{StaticResource SystemControlRevealFocusVisualBrush}"
|
||||
FocusVisualPrimaryThickness="2"
|
||||
FocusVisualSecondaryBrush="{StaticResource SystemControlFocusVisualSecondaryBrush}"
|
||||
FocusVisualSecondaryThickness="1"
|
||||
PointerEntered="ControlPointerEntered"
|
||||
PointerExited="ControlPointerExited">
|
||||
|
||||
<UserControl.Resources>
|
||||
<Style
|
||||
x:Key="HoverActionButtonStyle"
|
||||
BasedOn="{StaticResource DefaultButtonStyle}"
|
||||
TargetType="Button">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid
|
||||
x:Name="RootContainer"
|
||||
Padding="0,1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
x:DefaultBindMode="OneWay">
|
||||
|
||||
<Grid x:Name="UnreadContainer">
|
||||
<Ellipse
|
||||
Width="8"
|
||||
Height="8"
|
||||
Margin="0,12,8,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Canvas.ZIndex="0"
|
||||
Fill="{ThemeResource SystemAccentColor}"
|
||||
Visibility="{x:Bind helpers:XamlHelpers.ReverseBoolToVisibilityConverter(MailItem.IsRead), Mode=OneWay}" />
|
||||
</Grid>
|
||||
|
||||
|
||||
<Border
|
||||
x:Name="RootContainerVisualWrapper"
|
||||
Margin="0,4"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0.5"
|
||||
CornerRadius="4" />
|
||||
|
||||
<Grid x:Name="MainContentContainer">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<controls:ImagePreviewControl
|
||||
x:Name="ContactImage"
|
||||
Width="35"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
FromAddress="{x:Bind MailItem.FromAddress, Mode=OneWay}"
|
||||
FromName="{x:Bind MailItem.FromName, Mode=OneWay}"
|
||||
SenderContactPicture="{x:Bind MailItem.SenderContact.Base64ContactPicture}"
|
||||
ThumbnailUpdatedEvent="{x:Bind IsThumbnailUpdated, Mode=OneWay}"
|
||||
Visibility="{x:Bind IsAvatarVisible, Mode=OneWay}" />
|
||||
|
||||
<Grid
|
||||
x:Name="ContentGrid"
|
||||
Grid.Column="1"
|
||||
Canvas.ZIndex="2">
|
||||
|
||||
<!-- Sender + Title -->
|
||||
<Grid x:Name="ContentStackpanel" VerticalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Sender + IsDraft + Hover Buttons -->
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- IsDraft Tag -->
|
||||
<TextBlock
|
||||
x:Name="DraftTitle"
|
||||
Margin="0,0,4,0"
|
||||
x:Load="{x:Bind MailItem.IsDraft, Mode=OneWay}"
|
||||
Foreground="{StaticResource DeleteBrush}">
|
||||
|
||||
<Run Text="[" /><Run Text="{x:Bind domain:Translator.Draft}" /><Run Text="]" /> <Run Text=" " />
|
||||
</TextBlock>
|
||||
|
||||
<!-- Sender -->
|
||||
<TextBlock
|
||||
x:Name="SenderTextFromName"
|
||||
Grid.Column="1"
|
||||
Text="{x:Bind MailItem.FromName}"
|
||||
TextTrimming="WordEllipsis"
|
||||
Visibility="{x:Bind helpers:XamlHelpers.StringToVisibilityConverter(MailItem.FromName)}" />
|
||||
|
||||
<!-- Sender -->
|
||||
<TextBlock
|
||||
x:Name="SenderTextFromAddress"
|
||||
Grid.Column="1"
|
||||
Text="{x:Bind MailItem.FromAddress}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
Visibility="{x:Bind helpers:XamlHelpers.StringToVisibilityReversedConverter(MailItem.FromName)}" />
|
||||
|
||||
<!-- Hover button -->
|
||||
<StackPanel
|
||||
x:Name="HoverActionButtons"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
Background="Transparent"
|
||||
Canvas.ZIndex="999"
|
||||
ChildrenTransitions="{x:Null}"
|
||||
Orientation="Horizontal"
|
||||
Spacing="12"
|
||||
Visibility="Collapsed">
|
||||
<Button Click="FirstActionClicked" Style="{StaticResource HoverActionButtonStyle}">
|
||||
<Button.Content>
|
||||
<coreControls:WinoFontIcon FontSize="16" Icon="{x:Bind helpers:XamlHelpers.GetWinoIconGlyph(LeftHoverAction), Mode=OneWay}" />
|
||||
</Button.Content>
|
||||
</Button>
|
||||
|
||||
<Button Click="SecondActionClicked" Style="{StaticResource HoverActionButtonStyle}">
|
||||
<Button.Content>
|
||||
<coreControls:WinoFontIcon FontSize="16" Icon="{x:Bind helpers:XamlHelpers.GetWinoIconGlyph(CenterHoverAction), Mode=OneWay}" />
|
||||
</Button.Content>
|
||||
</Button>
|
||||
|
||||
<Button Click="ThirdActionClicked" Style="{StaticResource HoverActionButtonStyle}">
|
||||
<Button.Content>
|
||||
<coreControls:WinoFontIcon FontSize="16" Icon="{x:Bind helpers:XamlHelpers.GetWinoIconGlyph(RightHoverAction), Mode=OneWay}" />
|
||||
</Button.Content>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Subject + IsDraft -->
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<local:AnimatedIcon
|
||||
xmlns:local="using:Microsoft.UI.Xaml.Controls"
|
||||
x:Name="ExpandCollapseChevron"
|
||||
Width="14"
|
||||
Height="14"
|
||||
Margin="-4,0,2,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
local:AnimatedIcon.State="NormalOff"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Foreground="{ThemeResource ApplicationForegroundThemeBrush}"
|
||||
RenderTransformOrigin="0.5, 0.5"
|
||||
Visibility="{x:Bind IsThreadExpanderVisible, Mode=OneWay}">
|
||||
<animatedvisuals:AnimatedChevronRightDownSmallVisualSource />
|
||||
<local:AnimatedIcon.FallbackIconSource>
|
||||
<local:FontIconSource
|
||||
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
||||
FontSize="12"
|
||||
Glyph=""
|
||||
IsTextScaleFactorEnabled="False" />
|
||||
</local:AnimatedIcon.FallbackIconSource>
|
||||
<local:AnimatedIcon.RenderTransform />
|
||||
</local:AnimatedIcon>
|
||||
|
||||
<!-- Subject is bound in the code behind. -->
|
||||
<TextBlock
|
||||
x:Name="TitleText"
|
||||
Grid.Column="1"
|
||||
MaxLines="1"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Margin="4,0,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="11"
|
||||
Opacity="0.7"
|
||||
Text="{x:Bind helpers:XamlHelpers.GetMailItemDisplaySummaryForListing(MailItem.IsDraft, MailItem.CreationDate, Prefer24HourTimeFormat)}" />
|
||||
</Grid>
|
||||
|
||||
<!-- Message -->
|
||||
<Grid
|
||||
x:Name="PreviewTextContainerRoot"
|
||||
Grid.Row="2"
|
||||
VerticalAlignment="Top">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid x:Name="PreviewTextContainer">
|
||||
<TextBlock
|
||||
x:Name="PreviewTextblock"
|
||||
x:Load="{x:Bind helpers:XamlHelpers.ShouldDisplayPreview(MailItem.PreviewText), Mode=OneWay}"
|
||||
MaxLines="1"
|
||||
Opacity="0.7"
|
||||
Text="{x:Bind MailItem.PreviewText}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
</Grid>
|
||||
|
||||
<!-- Right Icons Container -->
|
||||
<StackPanel
|
||||
x:Name="IconsContainer"
|
||||
Grid.Column="1"
|
||||
Margin="4,4,1,4"
|
||||
Orientation="Horizontal"
|
||||
Spacing="2">
|
||||
|
||||
<ContentPresenter
|
||||
x:Name="HasAttachmentContent"
|
||||
x:Load="{x:Bind MailItem.HasAttachments, Mode=OneWay}"
|
||||
ContentTemplate="{StaticResource AttachmentSymbolControlTemplate}" />
|
||||
|
||||
<ContentPresenter
|
||||
x:Name="IsFlaggedContent"
|
||||
x:Load="{x:Bind MailItem.IsFlagged, Mode=OneWay}"
|
||||
ContentTemplate="{StaticResource FlaggedSymbolControlTemplate}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<!-- Read States -->
|
||||
<VisualStateGroup x:Name="ReadStates">
|
||||
<VisualState x:Name="Unread">
|
||||
<VisualState.StateTriggers>
|
||||
<StateTrigger IsActive="{x:Bind MailItem.IsRead, Converter={StaticResource ReverseBooleanConverter}, Mode=OneWay}" />
|
||||
</VisualState.StateTriggers>
|
||||
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TitleText.Foreground" Value="{ThemeResource SystemAccentColor}" />
|
||||
<Setter Target="TitleText.FontWeight" Value="Semibold" />
|
||||
<Setter Target="SenderTextFromName.FontWeight" Value="Bold" />
|
||||
<Setter Target="SenderTextFromAddress.FontWeight" Value="Bold" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Read" />
|
||||
</VisualStateGroup>
|
||||
|
||||
<!-- Sizing States -->
|
||||
<VisualStateGroup x:Name="SizingStates">
|
||||
<VisualState x:Name="Compact">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="RootContainer.Height" Value="60" />
|
||||
<Setter Target="ContentGrid.Padding" Value="8,0" />
|
||||
<Setter Target="PreviewTextContainer.Visibility" Value="Collapsed" />
|
||||
</VisualState.Setters>
|
||||
<VisualState.StateTriggers>
|
||||
<StateTrigger IsActive="{x:Bind helpers:XamlHelpers.ObjectEquals(DisplayMode, enums:MailListDisplayMode.Compact), Mode=OneWay}" />
|
||||
</VisualState.StateTriggers>
|
||||
</VisualState>
|
||||
|
||||
<!-- Medium -->
|
||||
<VisualState x:Name="Medium">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="RootContainer.Height" Value="80" />
|
||||
<Setter Target="ContentGrid.Padding" Value="6,0" />
|
||||
<Setter Target="PreviewTextContainer.Visibility" Value="Visible" />
|
||||
</VisualState.Setters>
|
||||
<VisualState.StateTriggers>
|
||||
<StateTrigger IsActive="{x:Bind helpers:XamlHelpers.ObjectEquals(DisplayMode, enums:MailListDisplayMode.Medium), Mode=OneWay}" />
|
||||
</VisualState.StateTriggers>
|
||||
</VisualState>
|
||||
|
||||
<!-- Spacious -->
|
||||
<VisualState x:Name="Spacious">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="RootContainer.Height" Value="Auto" />
|
||||
<Setter Target="ContentGrid.Padding" Value="12,12,6,12" />
|
||||
<Setter Target="PreviewTextContainer.Visibility" Value="Visible" />
|
||||
</VisualState.Setters>
|
||||
<VisualState.StateTriggers>
|
||||
<StateTrigger IsActive="{x:Bind helpers:XamlHelpers.ObjectEquals(DisplayMode, enums:MailListDisplayMode.Spacious), Mode=OneWay}" />
|
||||
</VisualState.StateTriggers>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
|
||||
<!-- Preview Text States -->
|
||||
<VisualStateGroup x:Name="PreviewTextStates">
|
||||
<VisualState x:Name="ShowText" />
|
||||
<VisualState x:Name="HideText">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PreviewTextContainerRoot.Visibility" Value="Collapsed" />
|
||||
</VisualState.Setters>
|
||||
<VisualState.StateTriggers>
|
||||
<StateTrigger IsActive="{x:Bind ShowPreviewText, Mode=OneWay, Converter={StaticResource ReverseBooleanConverter}}" />
|
||||
</VisualState.StateTriggers>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
|
||||
<!-- Thread Expanding States -->
|
||||
<VisualStateGroup x:Name="ExpanderStates">
|
||||
<VisualState x:Name="NotExpanded" />
|
||||
<VisualState x:Name="ExpandedState">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ExpandCollapseChevron.(controls:AnimatedIcon.State)" Value="NormalOn" />
|
||||
</VisualState.Setters>
|
||||
<VisualState.StateTriggers>
|
||||
<StateTrigger IsActive="{x:Bind IsThreadExpanded, Mode=OneWay}" />
|
||||
</VisualState.StateTriggers>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,218 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Extensions;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
|
||||
namespace Wino.Controls;
|
||||
|
||||
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 static readonly DependencyProperty IsThumbnailUpdatedProperty = DependencyProperty.Register(nameof(IsThumbnailUpdated), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
|
||||
|
||||
public bool IsThumbnailUpdated
|
||||
{
|
||||
get { return (bool)GetValue(IsThumbnailUpdatedProperty); }
|
||||
set { SetValue(IsThumbnailUpdatedProperty, value); }
|
||||
}
|
||||
|
||||
public bool IsThreadExpanded
|
||||
{
|
||||
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, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
if (IsHoverActionsEnabled)
|
||||
{
|
||||
HoverActionButtons.Visibility = Visibility.Visible;
|
||||
UnreadContainer.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void ControlPointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
if (IsHoverActionsEnabled)
|
||||
{
|
||||
HoverActionButtons.Visibility = Visibility.Collapsed;
|
||||
UnreadContainer.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Wino.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Templated button for each setting in Settings Dialog.
|
||||
/// </summary>
|
||||
public partial class SettingsMenuItemControl : Control
|
||||
{
|
||||
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));
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Windows.UI.ViewManagement.Core;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models;
|
||||
using Wino.Core.Domain.Models.Reader;
|
||||
using Wino.Core.UWP;
|
||||
using Wino.Core.UWP.Extensions;
|
||||
using Wino.Mail.WinUI;
|
||||
|
||||
namespace Wino.Mail.Controls;
|
||||
|
||||
public sealed partial class WebViewEditorControl : Control, IDisposable
|
||||
{
|
||||
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>();
|
||||
|
||||
[GeneratedDependencyProperty]
|
||||
public partial bool IsEditorDarkMode { get; set; }
|
||||
async partial void OnIsEditorDarkModeChanged(bool newValue)
|
||||
{
|
||||
await UpdateEditorThemeAsync();
|
||||
}
|
||||
|
||||
[GeneratedDependencyProperty]
|
||||
public partial bool IsEditorBold { get; set; }
|
||||
private bool _isEditorBoldInternal;
|
||||
async partial void OnIsEditorBoldChanged(bool newValue)
|
||||
{
|
||||
if (newValue != _isEditorBoldInternal)
|
||||
{
|
||||
await _chromium.ExecuteScriptFunctionSafeAsync("editor.execCommand", JsonSerializer.Serialize("bold", BasicTypesJsonContext.Default.String));
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedDependencyProperty]
|
||||
public partial bool IsEditorItalic { get; set; }
|
||||
private bool _isEditorItalicInternal;
|
||||
async partial void OnIsEditorItalicChanged(bool newValue)
|
||||
{
|
||||
if (newValue != _isEditorItalicInternal)
|
||||
{
|
||||
await _chromium.ExecuteScriptFunctionSafeAsync("editor.execCommand", JsonSerializer.Serialize("italic", BasicTypesJsonContext.Default.String));
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedDependencyProperty]
|
||||
public partial bool IsEditorUnderline { get; set; }
|
||||
private bool _isEditorUnderlineInternal;
|
||||
async partial void OnIsEditorUnderlineChanged(bool newValue)
|
||||
{
|
||||
if (newValue != _isEditorUnderlineInternal)
|
||||
{
|
||||
await _chromium.ExecuteScriptFunctionSafeAsync("editor.execCommand", JsonSerializer.Serialize("underline", BasicTypesJsonContext.Default.String));
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedDependencyProperty]
|
||||
public partial bool IsEditorStrikethrough { get; set; }
|
||||
private bool _isEditorStrikethroughInternal;
|
||||
async partial void OnIsEditorStrikethroughChanged(bool newValue)
|
||||
{
|
||||
if (newValue != _isEditorStrikethroughInternal)
|
||||
{
|
||||
await _chromium.ExecuteScriptFunctionSafeAsync("editor.execCommand", JsonSerializer.Serialize("strikethrough", BasicTypesJsonContext.Default.String));
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedDependencyProperty]
|
||||
public partial bool IsEditorOl { get; set; }
|
||||
private bool _isEditorOlInternal;
|
||||
async partial void OnIsEditorOlChanged(bool newValue)
|
||||
{
|
||||
if (newValue != _isEditorOlInternal)
|
||||
{
|
||||
await _chromium.ExecuteScriptFunctionSafeAsync("editor.execCommand", JsonSerializer.Serialize("insertorderedlist", BasicTypesJsonContext.Default.String));
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedDependencyProperty]
|
||||
public partial bool IsEditorUl { get; set; }
|
||||
private bool _isEditorUlInternal;
|
||||
async partial void OnIsEditorUlChanged(bool newValue)
|
||||
{
|
||||
if (newValue != _isEditorUlInternal)
|
||||
{
|
||||
await _chromium.ExecuteScriptFunctionSafeAsync("editor.execCommand", JsonSerializer.Serialize("insertunorderedlist", BasicTypesJsonContext.Default.String));
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedDependencyProperty(DefaultValue = true)]
|
||||
public partial bool IsEditorIndentEnabled { get; private set; }
|
||||
|
||||
[GeneratedDependencyProperty]
|
||||
public partial bool IsEditorOutdentEnabled { get; private set; }
|
||||
|
||||
[GeneratedDependencyProperty]
|
||||
public partial int EditorAlignmentSelectedIndex { get; set; }
|
||||
private int _editorAlignmentSelectedIndexInternal;
|
||||
async partial void OnEditorAlignmentSelectedIndexChanged(int newValue)
|
||||
{
|
||||
if (newValue != _editorAlignmentSelectedIndexInternal)
|
||||
{
|
||||
var alignmentAction = newValue switch
|
||||
{
|
||||
0 => "justifyleft",
|
||||
1 => "justifycenter",
|
||||
2 => "justifyright",
|
||||
3 => "justifyfull",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(newValue))
|
||||
};
|
||||
|
||||
await _chromium.ExecuteScriptFunctionSafeAsync("editor.execCommand", JsonSerializer.Serialize(alignmentAction, BasicTypesJsonContext.Default.String));
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedDependencyProperty]
|
||||
public partial bool IsEditorWebViewEditor { get; set; }
|
||||
|
||||
async partial void OnIsEditorWebViewEditorChanged(bool newValue)
|
||||
{
|
||||
await _chromium.ExecuteScriptFunctionSafeAsync("toggleToolbar", JsonSerializer.Serialize(newValue, BasicTypesJsonContext.Default.Boolean));
|
||||
}
|
||||
|
||||
private const string PART_WebView = "WebView";
|
||||
private WebView2 _chromium;
|
||||
private bool _disposedValue;
|
||||
private readonly TaskCompletionSource<bool> _domLoadedTask = new();
|
||||
|
||||
public WebViewEditorControl()
|
||||
{
|
||||
this.DefaultStyleKey = typeof(WebViewEditorControl);
|
||||
|
||||
IsEditorDarkMode = WinoApplication.Current.UnderlyingThemeService.IsUnderlyingThemeDark();
|
||||
}
|
||||
|
||||
protected override async void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_chromium = GetTemplateChild(PART_WebView) as WebView2;
|
||||
|
||||
await InitializeComponent();
|
||||
}
|
||||
|
||||
private async Task InitializeComponent()
|
||||
{
|
||||
Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF");
|
||||
Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation");
|
||||
_chromium.CoreWebView2Initialized += ChromiumInitialized;
|
||||
|
||||
await _chromium.EnsureCoreWebView2Async();
|
||||
}
|
||||
|
||||
public async void EditorIndentAsync()
|
||||
{
|
||||
await _chromium.ExecuteScriptFunctionSafeAsync("editor.execCommand", JsonSerializer.Serialize("indent", BasicTypesJsonContext.Default.String));
|
||||
}
|
||||
|
||||
public async void EditorOutdentAsync()
|
||||
{
|
||||
await _chromium.ExecuteScriptFunctionSafeAsync("editor.execCommand", JsonSerializer.Serialize("outdent", BasicTypesJsonContext.Default.String));
|
||||
}
|
||||
|
||||
public void ToggleEditorTheme()
|
||||
{
|
||||
IsEditorDarkMode = !IsEditorDarkMode;
|
||||
}
|
||||
|
||||
public async Task<string> GetHtmlBodyAsync()
|
||||
{
|
||||
var editorContent = await _chromium.ExecuteScriptFunctionSafeAsync("GetHTMLContent");
|
||||
|
||||
return JsonSerializer.Deserialize(editorContent, BasicTypesJsonContext.Default.String);
|
||||
}
|
||||
|
||||
public async void ShowImagePicker()
|
||||
{
|
||||
await _chromium.ExecuteScriptFunctionSafeAsync("imageInput.click");
|
||||
}
|
||||
|
||||
public async Task InsertImagesAsync(List<ImageInfo> images)
|
||||
{
|
||||
await _chromium.ExecuteScriptFunctionSafeAsync("insertImages", JsonSerializer.Serialize(images, DomainModelsJsonContext.Default.ListImageInfo));
|
||||
}
|
||||
|
||||
public async void ShowEmojiPicker()
|
||||
{
|
||||
CoreInputView.GetForCurrentView().TryShow(CoreInputViewKind.Emoji);
|
||||
|
||||
await FocusEditorAsync(focusControlAsWell: true);
|
||||
}
|
||||
|
||||
public WebView2 GetUnderlyingWebView() => _chromium;
|
||||
|
||||
public async Task RenderHtmlAsync(string htmlBody)
|
||||
{
|
||||
await _domLoadedTask.Task;
|
||||
|
||||
await UpdateEditorThemeAsync();
|
||||
await InitializeEditorAsync();
|
||||
|
||||
await _chromium.ExecuteScriptFunctionAsync("RenderHTML", parameters: JsonSerializer.Serialize(string.IsNullOrEmpty(htmlBody) ? " " : htmlBody, BasicTypesJsonContext.Default.String));
|
||||
}
|
||||
|
||||
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(WebView2 sender, 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.WebMessageReceived += ScriptMessageReceived;
|
||||
}
|
||||
|
||||
public async Task UpdateEditorThemeAsync()
|
||||
{
|
||||
await _domLoadedTask.Task;
|
||||
|
||||
if (IsEditorDarkMode)
|
||||
{
|
||||
_chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark;
|
||||
await _chromium.ExecuteScriptFunctionSafeAsync("SetDarkEditor");
|
||||
}
|
||||
else
|
||||
{
|
||||
_chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Light;
|
||||
await _chromium.ExecuteScriptFunctionSafeAsync("SetLightEditor");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Places the cursor in the composer.
|
||||
/// </summary>
|
||||
/// <param name="focusControlAsWell">Whether control itself should be focused as well or not.</param>
|
||||
public async Task FocusEditorAsync(bool focusControlAsWell)
|
||||
{
|
||||
await _chromium.ExecuteScriptSafeAsync("editor.selection.setCursorIn(editor.editor.firstChild, true)");
|
||||
|
||||
if (focusControlAsWell)
|
||||
{
|
||||
_chromium.Focus(FocusState.Keyboard);
|
||||
_chromium.Focus(FocusState.Programmatic);
|
||||
}
|
||||
}
|
||||
|
||||
private void ScriptMessageReceived(CoreWebView2 sender, CoreWebView2WebMessageReceivedEventArgs args)
|
||||
{
|
||||
var change = JsonSerializer.Deserialize(args.WebMessageAsJson, DomainModelsJsonContext.Default.WebViewMessage);
|
||||
|
||||
if (change.Type == "bold")
|
||||
{
|
||||
_isEditorBoldInternal = change.Value == "true";
|
||||
IsEditorBold = _isEditorBoldInternal;
|
||||
}
|
||||
else if (change.Type == "italic")
|
||||
{
|
||||
_isEditorItalicInternal = change.Value == "true";
|
||||
IsEditorItalic = _isEditorItalicInternal;
|
||||
}
|
||||
else if (change.Type == "underline")
|
||||
{
|
||||
_isEditorUnderlineInternal = change.Value == "true";
|
||||
IsEditorUnderline = _isEditorUnderlineInternal;
|
||||
}
|
||||
else if (change.Type == "strikethrough")
|
||||
{
|
||||
_isEditorStrikethroughInternal = change.Value == "true";
|
||||
IsEditorStrikethrough = _isEditorStrikethroughInternal;
|
||||
}
|
||||
else if (change.Type == "ol")
|
||||
{
|
||||
_isEditorOlInternal = change.Value == "true";
|
||||
IsEditorOl = _isEditorOlInternal;
|
||||
}
|
||||
else if (change.Type == "ul")
|
||||
{
|
||||
_isEditorUlInternal = change.Value == "true";
|
||||
IsEditorUl = _isEditorUlInternal;
|
||||
}
|
||||
else if (change.Type == "indent")
|
||||
{
|
||||
IsEditorIndentEnabled = change.Value != "disabled";
|
||||
}
|
||||
else if (change.Type == "outdent")
|
||||
{
|
||||
IsEditorOutdentEnabled = change.Value != "disabled";
|
||||
}
|
||||
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
|
||||
};
|
||||
_editorAlignmentSelectedIndexInternal = parsedValue;
|
||||
EditorAlignmentSelectedIndex = _editorAlignmentSelectedIndexInternal;
|
||||
}
|
||||
}
|
||||
|
||||
private void DomLoaded(CoreWebView2 sender, CoreWebView2DOMContentLoadedEventArgs args) => _domLoadedTask.TrySetResult(true);
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing && _chromium != null)
|
||||
{
|
||||
_chromium.CoreWebView2Initialized -= ChromiumInitialized;
|
||||
|
||||
if (_chromium.CoreWebView2 != null)
|
||||
{
|
||||
_chromium.CoreWebView2.DOMContentLoaded -= DomLoaded;
|
||||
_chromium.CoreWebView2.WebMessageReceived -= ScriptMessageReceived;
|
||||
}
|
||||
|
||||
_chromium.Close();
|
||||
}
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
using CommunityToolkit.Diagnostics;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Hosting;
|
||||
using Microsoft.UI.Xaml.Markup;
|
||||
|
||||
namespace Wino.Controls;
|
||||
|
||||
[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
|
||||
{
|
||||
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, Microsoft.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
|
||||
@@ -0,0 +1,81 @@
|
||||
using System.Linq;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Helpers;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
|
||||
namespace Wino.Controls;
|
||||
|
||||
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
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<ContentDialog
|
||||
x:Class="Wino.Dialogs.AccountReorderDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Wino.Controls"
|
||||
xmlns:coreControls="using:Wino.Core.UWP.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
xmlns:helpers="using:Wino.Helpers"
|
||||
xmlns:interfaces="using:Wino.Core.Domain.Interfaces"
|
||||
xmlns:local="using:Wino.Dialogs"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:selectors="using:Wino.Selectors"
|
||||
Title="Reorder Accounnts"
|
||||
Closed="DialogClosed"
|
||||
DefaultButton="Secondary"
|
||||
Opened="DialogOpened"
|
||||
SecondaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}"
|
||||
Style="{StaticResource WinoDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<ContentDialog.Resources>
|
||||
<DataTemplate x:Key="RootAccountReorderTemplate" x:DataType="interfaces:IAccountProviderDetailViewModel">
|
||||
<Grid Padding="12" ColumnSpacing="24">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<coreControls:WinoFontIcon
|
||||
Grid.RowSpan="2"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="24"
|
||||
Icon="{x:Bind helpers:XamlHelpers.GetProviderIcon(ProviderDetail.Type, ProviderDetail.SpecialImapProvider)}" />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
FontWeight="SemiBold"
|
||||
Text="{x:Bind StartupEntityTitle}" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Text="{x:Bind StartupEntityAddresses}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="MergedAccountReorderTemplate" x:DataType="interfaces:IAccountProviderDetailViewModel">
|
||||
<Grid Padding="12" ColumnSpacing="24">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<PathIcon
|
||||
Grid.RowSpan="2"
|
||||
VerticalAlignment="Center"
|
||||
Data="F1 M 8.613281 17.5 C 8.75 17.942709 8.945312 18.359375 9.199219 18.75 L 4.921875 18.75 C 4.433594 18.75 3.966471 18.650717 3.520508 18.452148 C 3.074544 18.25358 2.683919 17.986654 2.348633 17.651367 C 2.013346 17.31608 1.746419 16.925455 1.547852 16.479492 C 1.349284 16.033529 1.25 15.566406 1.25 15.078125 L 1.25 4.921875 C 1.25 4.433594 1.349284 3.966473 1.547852 3.520508 C 1.746419 3.074545 2.013346 2.68392 2.348633 2.348633 C 2.683919 2.013348 3.074544 1.74642 3.520508 1.547852 C 3.966471 1.349285 4.433594 1.25 4.921875 1.25 L 15.078125 1.25 C 15.566406 1.25 16.033527 1.349285 16.479492 1.547852 C 16.925455 1.74642 17.31608 2.013348 17.651367 2.348633 C 17.986652 2.68392 18.25358 3.074545 18.452148 3.520508 C 18.650715 3.966473 18.75 4.433594 18.75 4.921875 L 18.75 6.572266 C 18.580729 6.344402 18.390299 6.132813 18.178711 5.9375 C 17.967121 5.742188 17.740885 5.566407 17.5 5.410156 L 17.5 4.951172 C 17.5 4.625651 17.433268 4.314779 17.299805 4.018555 C 17.16634 3.722332 16.987305 3.461914 16.762695 3.237305 C 16.538086 3.012695 16.277668 2.83366 15.981445 2.700195 C 15.685221 2.566732 15.374349 2.5 15.048828 2.5 L 4.951172 2.5 C 4.619141 2.5 4.303385 2.568359 4.003906 2.705078 C 3.704427 2.841797 3.44401 3.02409 3.222656 3.251953 C 3.001302 3.479818 2.825521 3.745117 2.695312 4.047852 C 2.565104 4.350587 2.5 4.66797 2.5 5 L 13.310547 5 C 12.60091 5.266928 11.998697 5.683594 11.503906 6.25 L 2.5 6.25 L 2.5 15.048828 C 2.5 15.38737 2.568359 15.704753 2.705078 16.000977 C 2.841797 16.297201 3.024088 16.55599 3.251953 16.777344 C 3.479818 16.998697 3.745117 17.174479 4.047852 17.304688 C 4.350586 17.434896 4.667969 17.5 5 17.5 Z M 18.125 9.443359 C 18.125 9.866537 18.040363 10.263672 17.871094 10.634766 C 17.701822 11.005859 17.473957 11.329753 17.1875 11.606445 C 16.901041 11.883139 16.56901 12.101237 16.191406 12.260742 C 15.813802 12.420248 15.416666 12.5 15 12.5 C 14.563802 12.5 14.1569 12.41862 13.779297 12.255859 C 13.401691 12.0931 13.071288 11.870117 12.788086 11.586914 C 12.504882 11.303711 12.2819 10.973308 12.119141 10.595703 C 11.95638 10.2181 11.875 9.811198 11.875 9.375 C 11.875 8.938803 11.95638 8.531901 12.119141 8.154297 C 12.2819 7.776693 12.504882 7.446289 12.788086 7.163086 C 13.071288 6.879883 13.401691 6.656901 13.779297 6.494141 C 14.1569 6.331381 14.563802 6.25 15 6.25 C 15.449218 6.25 15.864257 6.333008 16.245117 6.499023 C 16.625977 6.665039 16.956379 6.892904 17.236328 7.182617 C 17.516275 7.472331 17.734375 7.810873 17.890625 8.198242 C 18.046875 8.585612 18.125 9.000651 18.125 9.443359 Z M 20 16.25 C 20 16.666666 19.926758 17.049154 19.780273 17.397461 C 19.633789 17.745768 19.435221 18.058268 19.18457 18.334961 C 18.933918 18.611654 18.642578 18.854166 18.310547 19.0625 C 17.978516 19.270834 17.626953 19.444986 17.255859 19.584961 C 16.884766 19.724936 16.505533 19.829102 16.118164 19.897461 C 15.730794 19.96582 15.358072 20 15 20 C 14.654947 20 14.291992 19.96582 13.911133 19.897461 C 13.530273 19.829102 13.154297 19.726562 12.783203 19.589844 C 12.412109 19.453125 12.058919 19.282227 11.723633 19.077148 C 11.388346 18.87207 11.092122 18.632812 10.834961 18.359375 C 10.577799 18.085938 10.374349 17.779947 10.224609 17.441406 C 10.074869 17.102865 10 16.731771 10 16.328125 L 10 15.78125 C 10 15.501303 10.052083 15.237631 10.15625 14.990234 C 10.260416 14.742839 10.405273 14.526367 10.59082 14.34082 C 10.776367 14.155273 10.991211 14.010417 11.235352 13.90625 C 11.479492 13.802084 11.744791 13.75 12.03125 13.75 L 17.96875 13.75 C 18.248697 13.75 18.512369 13.803711 18.759766 13.911133 C 19.00716 14.018555 19.222004 14.163412 19.404297 14.345703 C 19.586588 14.527995 19.731445 14.742839 19.838867 14.990234 C 19.946289 15.237631 20 15.501303 20 15.78125 Z " />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
FontWeight="SemiBold"
|
||||
Text="{x:Bind StartupEntityTitle}" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Text="{x:Bind StartupEntityAddresses}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<selectors:AccountReorderTemplateSelector
|
||||
x:Key="AccountReorderTemplateSelector"
|
||||
MergedAccountReorderTemplate="{StaticResource MergedAccountReorderTemplate}"
|
||||
RootAccountReorderTemplate="{StaticResource RootAccountReorderTemplate}" />
|
||||
</ContentDialog.Resources>
|
||||
|
||||
<ListView
|
||||
AllowDrop="True"
|
||||
CanReorderItems="True"
|
||||
ItemTemplateSelector="{StaticResource AccountReorderTemplateSelector}"
|
||||
ItemsSource="{x:Bind Accounts}"
|
||||
SelectionMode="None" />
|
||||
</ContentDialog>
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Mail.WinUI;
|
||||
|
||||
namespace Wino.Dialogs;
|
||||
|
||||
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)
|
||||
{
|
||||
Accounts = accounts;
|
||||
|
||||
count = accounts.Count;
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<ContentDialog
|
||||
x:Class="Wino.Dialogs.CreateAccountAliasDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
xmlns:local="using:Wino.Dialogs"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="{x:Bind domain:Translator.CreateAccountAliasDialog_Title}"
|
||||
DefaultButton="Primary"
|
||||
PrimaryButtonClick="CreateClicked"
|
||||
PrimaryButtonText="{x:Bind domain:Translator.Buttons_Create}"
|
||||
SecondaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}"
|
||||
Style="{StaticResource WinoDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="{x:Bind domain:Translator.CreateAccountAliasDialog_Description}" />
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Margin="0,20"
|
||||
Spacing="8">
|
||||
<TextBox
|
||||
x:Name="AliasTextBox"
|
||||
Header="{x:Bind domain:Translator.CreateAccountAliasDialog_AliasAddress}"
|
||||
PlaceholderText="{x:Bind domain:Translator.CreateAccountAliasDialog_AliasAddressPlaceholder}" />
|
||||
|
||||
<TextBox
|
||||
x:Name="ReplyToTextBox"
|
||||
Header="{x:Bind domain:Translator.CreateAccountAliasDialog_ReplyToAddress}"
|
||||
PlaceholderText="{x:Bind domain:Translator.CreateAccountAliasDialog_ReplyToAddressPlaceholder}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Dialogs;
|
||||
|
||||
public sealed partial class CreateAccountAliasDialog : ContentDialog, ICreateAccountAliasDialog
|
||||
{
|
||||
public MailAccountAlias CreatedAccountAlias { get; set; }
|
||||
public CreateAccountAliasDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void CreateClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
CreatedAccountAlias = new MailAccountAlias
|
||||
{
|
||||
AliasAddress = AliasTextBox.Text.Trim(),
|
||||
ReplyToAddress = ReplyToTextBox.Text.Trim(),
|
||||
Id = Guid.NewGuid(),
|
||||
IsPrimary = false,
|
||||
IsVerified = false
|
||||
};
|
||||
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<ContentDialog
|
||||
x:Class="Wino.Mail.Dialogs.MessageSourceDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
xmlns:local="using:Wino.Mail.Dialogs"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="{x:Bind domain:Translator.MessageSourceDialog_Title}"
|
||||
DefaultButton="Primary"
|
||||
PrimaryButtonClick="ContentDialog_PrimaryButtonClick"
|
||||
PrimaryButtonText="{x:Bind domain:Translator.Buttons_Copy}"
|
||||
SecondaryButtonText="{x:Bind domain:Translator.Buttons_Close}"
|
||||
Style="{StaticResource WinoDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<ContentDialog.Resources>
|
||||
<x:Double x:Key="ContentDialogMaxWidth">1200</x:Double>
|
||||
</ContentDialog.Resources>
|
||||
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" HorizontalScrollMode="Disabled">
|
||||
<TextBlock
|
||||
MaxWidth="1000"
|
||||
IsTextSelectionEnabled="True"
|
||||
Text="{x:Bind MessageSource, Mode=OneWay}"
|
||||
TextWrapping="Wrap" />
|
||||
</ScrollViewer>
|
||||
</ContentDialog>
|
||||
@@ -0,0 +1,24 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Mail.WinUI;
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
_clipboardService.CopyClipboardAsync(MessageSource);
|
||||
Copied = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<ContentDialog
|
||||
x:Class="Wino.Dialogs.MoveMailDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Wino.Controls"
|
||||
xmlns:coreControls="using:Wino.Core.UWP.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
xmlns:folders="using:Wino.Core.Domain.Models.Folders"
|
||||
xmlns:helpers="using:Wino.Helpers"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||
Title="{x:Bind domain:Translator.MoveMailDialog_Title}"
|
||||
DefaultButton="Primary"
|
||||
PrimaryButtonClick="CancelClicked"
|
||||
PrimaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}"
|
||||
Style="{StaticResource WinoDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<ContentDialog.Resources>
|
||||
<x:Double x:Key="ContentDialogMinWidth">600</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxWidth">600</x:Double>
|
||||
<x:Double x:Key="ContentDialogMinHeight">756</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxHeight">756</x:Double>
|
||||
|
||||
<DataTemplate x:Key="FolderStructureMenuFlyoutItemTemplate" x:DataType="folders:IMailItemFolder">
|
||||
<TreeViewItem IsExpanded="True" ItemsSource="{x:Bind ChildFolders}">
|
||||
<StackPanel
|
||||
Height="32"
|
||||
Orientation="Horizontal"
|
||||
Spacing="12">
|
||||
<coreControls:WinoFontIcon FontSize="20" Icon="{x:Bind helpers:XamlHelpers.GetSpecialFolderPathIconGeometry(SpecialFolderType)}" />
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind FolderName}" />
|
||||
</StackPanel>
|
||||
</TreeViewItem>
|
||||
</DataTemplate>
|
||||
</ContentDialog.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock
|
||||
x:Name="InvalidFolderText"
|
||||
Margin="0,0,0,6"
|
||||
Foreground="{ThemeResource InfoBarWarningSeverityIconBackground}"
|
||||
Visibility="Collapsed" />
|
||||
|
||||
<TreeView
|
||||
x:Name="FolderTreeView"
|
||||
Grid.Row="1"
|
||||
CanDragItems="False"
|
||||
CanReorderItems="False"
|
||||
ItemTemplate="{StaticResource FolderStructureMenuFlyoutItemTemplate}"
|
||||
ItemsSource="{x:Bind FolderList, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind SelectedFolder, Mode=TwoWay}"
|
||||
SelectionMode="Single" />
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
|
||||
namespace Wino.Dialogs;
|
||||
|
||||
public sealed partial class MoveMailDialog : ContentDialog
|
||||
{
|
||||
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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<ContentDialog
|
||||
x:Class="Wino.Dialogs.NewImapSetupDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Wino.Dialogs"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Closed="ImapSetupDialogClosed"
|
||||
Closing="OnDialogClosing"
|
||||
DefaultButton="Secondary"
|
||||
FullSizeDesired="False"
|
||||
Opened="ImapSetupDialogOpened"
|
||||
Style="{StaticResource WinoDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<ContentDialog.Resources>
|
||||
<Thickness x:Key="ContentDialogPadding">0,0,0,0</Thickness>
|
||||
<!--<x:Double x:Key="ContentDialogMinWidth">768</x:Double>-->
|
||||
<x:Double x:Key="ContentDialogMaxWidth">1920</x:Double>
|
||||
<!--<x:Double x:Key="ContentDialogMinHeight">768</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxHeight">2000</x:Double>-->
|
||||
</ContentDialog.Resources>
|
||||
|
||||
<Frame x:Name="ImapFrame" />
|
||||
</ContentDialog>
|
||||
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
using Wino.Messaging.Client.Mails;
|
||||
using Wino.Views.ImapSetup;
|
||||
|
||||
namespace Wino.Dialogs;
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
<ContentDialog
|
||||
x:Class="Wino.Dialogs.SignatureEditorDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:accounts="using:Wino.Core.Domain.Models.Accounts"
|
||||
xmlns:controls="using:Wino.Controls"
|
||||
xmlns:controls1="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:controls2="using:Wino.Mail.Controls"
|
||||
xmlns:coreControls="using:Wino.Core.UWP.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||
Title="{x:Bind domain:Translator.SignatureEditorDialog_Title}"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Closed="DialogClosed"
|
||||
DefaultButton="Primary"
|
||||
IsPrimaryButtonEnabled="False"
|
||||
Opened="SignatureDialogOpened"
|
||||
PrimaryButtonClick="SaveClicked"
|
||||
PrimaryButtonText="{x:Bind domain:Translator.Buttons_Save}"
|
||||
SecondaryButtonClick="CancelClicked"
|
||||
SecondaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}"
|
||||
Style="{StaticResource WinoDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<ContentDialog.Resources>
|
||||
<x:Double x:Key="ContentDialogMaxWidth">1200</x:Double>
|
||||
</ContentDialog.Resources>
|
||||
|
||||
<Grid Margin="0,20,0,0" RowSpacing="30">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" MaxHeight="400" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBox
|
||||
x:Name="SignatureNameTextBox"
|
||||
MinWidth="300"
|
||||
MaxWidth="500"
|
||||
HorizontalAlignment="Left"
|
||||
PlaceholderText="{x:Bind domain:Translator.SignatureEditorDialog_SignatureName_Placeholder}"
|
||||
TextChanged="SignatureNameTextBoxTextChanged" />
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="300" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<CommandBar
|
||||
Grid.Row="0"
|
||||
HorizontalAlignment="Left"
|
||||
Background="Transparent"
|
||||
DefaultLabelPosition="Collapsed"
|
||||
IsOpen="False">
|
||||
<AppBarButton
|
||||
Width="Auto"
|
||||
MinWidth="40"
|
||||
Click="{x:Bind WebViewEditor.ToggleEditorTheme}"
|
||||
LabelPosition="Collapsed"
|
||||
ToolTipService.ToolTip="Light Theme"
|
||||
Visibility="{x:Bind WebViewEditor.IsEditorDarkMode, Mode=OneWay}">
|
||||
<AppBarButton.Icon>
|
||||
<coreControls:WinoFontIcon Icon="LightEditor" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarButton
|
||||
Width="Auto"
|
||||
MinWidth="40"
|
||||
Click="{x:Bind WebViewEditor.ToggleEditorTheme}"
|
||||
LabelPosition="Collapsed"
|
||||
ToolTipService.ToolTip="Dark Theme"
|
||||
Visibility="{x:Bind WebViewEditor.IsEditorDarkMode, Mode=OneWay, Converter={StaticResource ReverseBooleanToVisibilityConverter}}">
|
||||
<AppBarButton.Icon>
|
||||
<coreControls:WinoFontIcon Icon="DarkEditor" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarSeparator />
|
||||
|
||||
<AppBarToggleButton
|
||||
Width="Auto"
|
||||
MinWidth="40"
|
||||
IsChecked="{x:Bind WebViewEditor.IsEditorBold, Mode=TwoWay}"
|
||||
Label="Bold"
|
||||
ToolTipService.ToolTip="Bold">
|
||||
<AppBarToggleButton.Icon>
|
||||
<PathIcon Data="{StaticResource BoldPathIcon}" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
<AppBarToggleButton
|
||||
Width="Auto"
|
||||
MinWidth="40"
|
||||
IsChecked="{x:Bind WebViewEditor.IsEditorItalic, Mode=TwoWay}"
|
||||
Label="Italic"
|
||||
ToolTipService.ToolTip="Italic">
|
||||
<AppBarToggleButton.Icon>
|
||||
<PathIcon Data="{StaticResource ItalicPathIcon}" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
<AppBarToggleButton
|
||||
Width="Auto"
|
||||
MinWidth="40"
|
||||
IsChecked="{x:Bind WebViewEditor.IsEditorUnderline, Mode=TwoWay}"
|
||||
Label="Underline"
|
||||
ToolTipService.ToolTip="Underline">
|
||||
<AppBarToggleButton.Icon>
|
||||
<PathIcon Data="{StaticResource UnderlinePathIcon}" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
<AppBarToggleButton
|
||||
Width="Auto"
|
||||
MinWidth="40"
|
||||
IsChecked="{x:Bind WebViewEditor.IsEditorStrikethrough, Mode=TwoWay}"
|
||||
Label="Stroke"
|
||||
ToolTipService.ToolTip="Stroke">
|
||||
<AppBarToggleButton.Icon>
|
||||
<PathIcon Data="{StaticResource StrikePathIcon}" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
<AppBarSeparator />
|
||||
<AppBarToggleButton
|
||||
Width="Auto"
|
||||
MinWidth="40"
|
||||
IsChecked="{x:Bind WebViewEditor.IsEditorUl, Mode=TwoWay}"
|
||||
Label="Bullet List"
|
||||
ToolTipService.ToolTip="Bullet List">
|
||||
<AppBarToggleButton.Icon>
|
||||
<PathIcon Data="{StaticResource BulletedListPathIcon}" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
<AppBarToggleButton
|
||||
Width="Auto"
|
||||
MinWidth="40"
|
||||
IsChecked="{x:Bind WebViewEditor.IsEditorOl, Mode=TwoWay}"
|
||||
Label="Ordered List"
|
||||
ToolTipService.ToolTip="Ordered List">
|
||||
<AppBarToggleButton.Icon>
|
||||
<PathIcon Data="{StaticResource OrderedListPathIcon}" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
|
||||
<AppBarSeparator />
|
||||
|
||||
<AppBarButton
|
||||
Width="Auto"
|
||||
MinWidth="40"
|
||||
Click="{x:Bind WebViewEditor.EditorOutdentAsync}"
|
||||
IsEnabled="{x:Bind WebViewEditor.IsEditorOutdentEnabled, Mode=OneWay}"
|
||||
Label="Decrease Indent"
|
||||
ToolTipService.ToolTip="Decrease Indent">
|
||||
<AppBarButton.Icon>
|
||||
<PathIcon Data="{StaticResource DecreaseIndentPathIcon}" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
<AppBarButton
|
||||
Width="Auto"
|
||||
MinWidth="40"
|
||||
Click="{x:Bind WebViewEditor.EditorIndentAsync}"
|
||||
IsEnabled="{x:Bind WebViewEditor.IsEditorIndentEnabled, Mode=OneWay}"
|
||||
Label="Increase Indent"
|
||||
ToolTipService.ToolTip="Increase Indent">
|
||||
<AppBarButton.Icon>
|
||||
<PathIcon Data="{StaticResource IncreaseIndentPathIcon}" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarElementContainer
|
||||
Width="Auto"
|
||||
MinWidth="40"
|
||||
VerticalAlignment="Center">
|
||||
<ComboBox
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
SelectedIndex="{x:Bind WebViewEditor.EditorAlignmentSelectedIndex, Mode=TwoWay}">
|
||||
<ComboBoxItem IsSelected="True" Tag="left">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Viewbox Width="16">
|
||||
<PathIcon Data="{StaticResource AlignLeftPathIcon}" />
|
||||
</Viewbox>
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.Left}" />
|
||||
</StackPanel>
|
||||
</ComboBoxItem>
|
||||
|
||||
<ComboBoxItem Tag="center">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Viewbox Width="16">
|
||||
<PathIcon Data="{StaticResource AlignCenterPathIcon}" />
|
||||
</Viewbox>
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.Center}" />
|
||||
</StackPanel>
|
||||
</ComboBoxItem>
|
||||
|
||||
<ComboBoxItem Tag="right">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Viewbox Width="16">
|
||||
<PathIcon Data="{StaticResource AlignRightPathIcon}" />
|
||||
</Viewbox>
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.Right}" />
|
||||
</StackPanel>
|
||||
</ComboBoxItem>
|
||||
|
||||
<ComboBoxItem Tag="justify">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Viewbox Width="16">
|
||||
<PathIcon Data="{StaticResource AlignJustifyPathIcon}" />
|
||||
</Viewbox>
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.Justify}" />
|
||||
</StackPanel>
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</AppBarElementContainer>
|
||||
<AppBarSeparator />
|
||||
<AppBarButton
|
||||
Width="Auto"
|
||||
MinWidth="40"
|
||||
Click="{x:Bind WebViewEditor.ShowImagePicker}"
|
||||
Label="Add Image"
|
||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Photos}">
|
||||
<AppBarButton.Icon>
|
||||
<PathIcon Data="{StaticResource AddPhotoPathIcon}" />
|
||||
</AppBarButton.Icon>
|
||||
<AppBarButton.Content>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Viewbox Width="16" VerticalAlignment="Center">
|
||||
<PathIcon Data="{StaticResource AddPhotoPathIcon}" />
|
||||
</Viewbox>
|
||||
<TextBlock Text="{x:Bind domain:Translator.Photos}" />
|
||||
</StackPanel>
|
||||
</AppBarButton.Content>
|
||||
</AppBarButton>
|
||||
<AppBarButton
|
||||
Width="Auto"
|
||||
MinWidth="40"
|
||||
Click="{x:Bind WebViewEditor.ShowEmojiPicker}"
|
||||
Label="Add Emoji"
|
||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Emoji}">
|
||||
<AppBarButton.Icon>
|
||||
<PathIcon Data="{StaticResource EmojiPathIcon}" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarToggleButton
|
||||
Width="Auto"
|
||||
MinWidth="40"
|
||||
IsChecked="{x:Bind WebViewEditor.IsEditorWebViewEditor, Mode=TwoWay}"
|
||||
Label="Webview ToolBar"
|
||||
ToolTipService.ToolTip="Webview ToolBar">
|
||||
<AppBarToggleButton.Icon>
|
||||
<PathIcon Data="{StaticResource WebviewToolBarPathIcon}" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
</CommandBar>
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Margin="0,6,0,0"
|
||||
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="3">
|
||||
<controls2:WebViewEditorControl x:Name="WebViewEditor" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
|
||||
namespace Wino.Dialogs;
|
||||
|
||||
public sealed partial class SignatureEditorDialog : ContentDialog
|
||||
{
|
||||
public AccountSignature Result;
|
||||
|
||||
public SignatureEditorDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
SignatureNameTextBox.Header = Translator.SignatureEditorDialog_SignatureName_TitleNew;
|
||||
|
||||
// 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
|
||||
};
|
||||
|
||||
// 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)
|
||||
{
|
||||
await WebViewEditor.RenderHtmlAsync(Result?.HtmlBody ?? string.Empty);
|
||||
}
|
||||
|
||||
private void DialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args)
|
||||
{
|
||||
WebViewEditor.Dispose();
|
||||
}
|
||||
|
||||
private async void SaveClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
var newSignature = Regex.Unescape(await WebViewEditor.GetHtmlBodyAsync());
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
private void CancelClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
|
||||
private void SignatureNameTextBoxTextChanged(object sender, TextChangedEventArgs e) => IsPrimaryButtonEnabled = !string.IsNullOrWhiteSpace(SignatureNameTextBox.Text);
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<ContentDialog
|
||||
x:Class="Wino.Dialogs.SystemFolderConfigurationDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Wino.Controls"
|
||||
xmlns:controls1="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:coreControls="using:Wino.Core.UWP.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="{x:Bind domain:Translator.SettingsConfigureSpecialFolders_Title}"
|
||||
Closing="DialogClosing"
|
||||
DefaultButton="Primary"
|
||||
IsPrimaryButtonEnabled="True"
|
||||
PrimaryButtonClick="SaveClicked"
|
||||
PrimaryButtonText="{x:Bind domain:Translator.Buttons_SaveConfiguration}"
|
||||
SecondaryButtonClick="CancelClicked"
|
||||
SecondaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}"
|
||||
Style="{StaticResource WinoDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<ContentDialog.Resources>
|
||||
<Style TargetType="ComboBox">
|
||||
<Setter Property="Width" Value="100" />
|
||||
</Style>
|
||||
</ContentDialog.Resources>
|
||||
<ScrollViewer>
|
||||
<Grid RowSpacing="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Style="{StaticResource BodyTextBlockStyle}">
|
||||
<Run Text="{x:Bind domain:Translator.SystemFolderConfigDialog_MessageFirstLine}" />
|
||||
<LineBreak />
|
||||
<LineBreak />
|
||||
<Run
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{StaticResource SystemErrorTextColor}"
|
||||
Text="{x:Bind domain:Translator.SystemFolderConfigDialog_MessageSecondLine}" />
|
||||
</TextBlock>
|
||||
|
||||
<StackPanel Grid.Row="1" Spacing="6">
|
||||
<controls1:SettingsCard
|
||||
x:Name="SentCard"
|
||||
Description="{x:Bind domain:Translator.SystemFolderConfigDialog_SentFolderDescription}"
|
||||
Header="{x:Bind domain:Translator.SystemFolderConfigDialog_SentFolderHeader}">
|
||||
<controls1:SettingsCard.HeaderIcon>
|
||||
<coreControls:WinoFontIcon FontSize="16" Icon="SpecialFolderSent" />
|
||||
</controls1:SettingsCard.HeaderIcon>
|
||||
<ComboBox ItemsSource="{x:Bind AvailableFolders}" SelectedItem="{x:Bind Sent, Mode=TwoWay}" />
|
||||
</controls1:SettingsCard>
|
||||
|
||||
<controls1:SettingsCard
|
||||
x:Name="DraftCard"
|
||||
Description="{x:Bind domain:Translator.SystemFolderConfigDialog_DraftFolderDescription}"
|
||||
Header="{x:Bind domain:Translator.SystemFolderConfigDialog_DraftFolderHeader}">
|
||||
<controls1:SettingsCard.HeaderIcon>
|
||||
<coreControls:WinoFontIcon FontSize="16" Icon="SpecialFolderDraft" />
|
||||
</controls1:SettingsCard.HeaderIcon>
|
||||
<ComboBox ItemsSource="{x:Bind AvailableFolders}" SelectedItem="{x:Bind Draft, Mode=TwoWay}" />
|
||||
</controls1:SettingsCard>
|
||||
|
||||
<controls1:SettingsCard
|
||||
x:Name="ArchiveCard"
|
||||
Description="{x:Bind domain:Translator.SystemFolderConfigDialog_ArchiveFolderDescription}"
|
||||
Header="{x:Bind domain:Translator.SystemFolderConfigDialog_ArchiveFolderHeader}">
|
||||
<controls1:SettingsCard.HeaderIcon>
|
||||
<coreControls:WinoFontIcon FontSize="16" Icon="SpecialFolderArchive" />
|
||||
</controls1:SettingsCard.HeaderIcon>
|
||||
<ComboBox ItemsSource="{x:Bind AvailableFolders}" SelectedItem="{x:Bind Archive, Mode=TwoWay}" />
|
||||
</controls1:SettingsCard>
|
||||
|
||||
<controls1:SettingsCard
|
||||
x:Name="DeletedCard"
|
||||
Description="{x:Bind domain:Translator.SystemFolderConfigDialog_DeletedFolderDescription}"
|
||||
Header="{x:Bind domain:Translator.SystemFolderConfigDialog_DeletedFolderHeader}">
|
||||
<controls1:SettingsCard.HeaderIcon>
|
||||
<coreControls:WinoFontIcon FontSize="16" Icon="SpecialFolderDeleted" />
|
||||
</controls1:SettingsCard.HeaderIcon>
|
||||
<ComboBox ItemsSource="{x:Bind AvailableFolders}" SelectedItem="{x:Bind Trash, Mode=TwoWay}" />
|
||||
</controls1:SettingsCard>
|
||||
|
||||
<controls1:SettingsCard
|
||||
x:Name="JunkCard"
|
||||
Description="{x:Bind domain:Translator.SystemFolderConfigDialog_JunkFolderDescription}"
|
||||
Header="{x:Bind domain:Translator.SystemFolderConfigDialog_JunkFolderHeader}">
|
||||
<controls1:SettingsCard.HeaderIcon>
|
||||
<coreControls:WinoFontIcon FontSize="16" Icon="SpecialFolderJunk" />
|
||||
</controls1:SettingsCard.HeaderIcon>
|
||||
<ComboBox ItemsSource="{x:Bind AvailableFolders}" SelectedItem="{x:Bind Junk, Mode=TwoWay}" />
|
||||
</controls1:SettingsCard>
|
||||
|
||||
<TextBlock x:Name="ValidationErrorTextBlock" Foreground="{StaticResource SystemErrorTextColor}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</ContentDialog>
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Exceptions;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
|
||||
namespace Wino.Dialogs;
|
||||
|
||||
public sealed partial class SystemFolderConfigurationDialog : ContentDialog
|
||||
{
|
||||
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)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
AvailableFolders = availableFolders;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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>()
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="language" content="english">
|
||||
<script src="./libs/jodit.min.js"></script>
|
||||
<script src="./libs/darkreader.js"></script>
|
||||
<link rel="stylesheet" href="./libs/jodit.min.css" />
|
||||
<link rel="stylesheet" href="./global.css" />
|
||||
|
||||
<style>
|
||||
.jodit-toolbar-button__trigger svg > path {
|
||||
fill: black;
|
||||
}
|
||||
|
||||
.jodit-container:not(.jodit_inline) {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-radius: initial;
|
||||
}
|
||||
|
||||
/* Hide taskbar in css. Should not be hidden from configuration, because it's used to sync state with native buttons. */
|
||||
.jodit-toolbar__box {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html, body, .jodit-container, .jodit-workplace {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<meta name="color-scheme" content="dark light">
|
||||
<textarea id="editor" name="editor"></textarea>
|
||||
|
||||
<!-- hidden input to handle image uploads -->
|
||||
<input type="file" id="imageInput" style="display:none;">
|
||||
<script src="/editor.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||