file scoped namespaces (#565)

This commit is contained in:
Aleh Khantsevich
2025-02-16 11:54:23 +01:00
committed by GitHub
parent cf9869b71e
commit 3ddc1a6229
617 changed files with 32107 additions and 32721 deletions

View File

@@ -1,37 +1,36 @@
using System.Threading.Tasks;
namespace Wino.Activation
{
public abstract class ActivationHandler
{
public abstract bool CanHandle(object args);
namespace Wino.Activation;
public abstract Task HandleAsync(object args);
public abstract class ActivationHandler
{
public abstract bool CanHandle(object args);
public abstract Task HandleAsync(object args);
}
// Extend this class to implement new ActivationHandlers
public abstract class ActivationHandler<T> : ActivationHandler
where T : class
{
// Override this method to add the activation logic in your activation handler
protected abstract Task HandleInternalAsync(T args);
public override async Task HandleAsync(object args)
{
await HandleInternalAsync(args as T);
}
// Extend this class to implement new ActivationHandlers
public abstract class ActivationHandler<T> : ActivationHandler
where T : class
public override bool CanHandle(object args)
{
// Override this method to add the activation logic in your activation handler
protected abstract Task HandleInternalAsync(T args);
// CanHandle checks the args is of type you have configured
return args is T && CanHandleInternal(args as T);
}
public override async Task HandleAsync(object args)
{
await HandleInternalAsync(args as T);
}
public override bool CanHandle(object args)
{
// CanHandle checks the args is of type you have configured
return args is T && CanHandleInternal(args as T);
}
// You can override this method to add extra validation on activation args
// to determine if your ActivationHandler should handle this activation args
protected virtual bool CanHandleInternal(T args)
{
return true;
}
// You can override this method to add extra validation on activation args
// to determine if your ActivationHandler should handle this activation args
protected virtual bool CanHandleInternal(T args)
{
return true;
}
}

View File

@@ -8,81 +8,80 @@ using Windows.UI.Xaml.Navigation;
using Wino.Core.ViewModels;
using Wino.Messaging.Client.Shell;
namespace Wino.Core.UWP
namespace Wino.Core.UWP;
public partial class BasePage : Page, IRecipient<LanguageChanged>
{
public partial class BasePage : Page, IRecipient<LanguageChanged>
public UIElement ShellContent
{
public UIElement ShellContent
{
get { return (UIElement)GetValue(ShellContentProperty); }
set { SetValue(ShellContentProperty, value); }
}
public static readonly DependencyProperty ShellContentProperty = DependencyProperty.Register(nameof(ShellContent), typeof(UIElement), typeof(BasePage), new PropertyMetadata(null));
public void Receive(LanguageChanged message)
{
OnLanguageChanged();
}
public virtual void OnLanguageChanged() { }
get { return (UIElement)GetValue(ShellContentProperty); }
set { SetValue(ShellContentProperty, value); }
}
public abstract class BasePage<T> : BasePage where T : CoreBaseViewModel
public static readonly DependencyProperty ShellContentProperty = DependencyProperty.Register(nameof(ShellContent), typeof(UIElement), typeof(BasePage), new PropertyMetadata(null));
public void Receive(LanguageChanged message)
{
public T ViewModel { get; } = WinoApplication.Current.Services.GetService<T>();
OnLanguageChanged();
}
protected BasePage()
{
ViewModel.Dispatcher = new UWPDispatcher(Dispatcher);
public virtual void OnLanguageChanged() { }
}
Loaded += PageLoaded;
Unloaded += PageUnloaded;
}
public abstract class BasePage<T> : BasePage where T : CoreBaseViewModel
{
public T ViewModel { get; } = WinoApplication.Current.Services.GetService<T>();
private void PageUnloaded(object sender, RoutedEventArgs e)
{
Loaded -= PageLoaded;
Unloaded -= PageUnloaded;
}
protected BasePage()
{
ViewModel.Dispatcher = new UWPDispatcher(Dispatcher);
private void PageLoaded(object sender, RoutedEventArgs e) => ViewModel.OnPageLoaded();
Loaded += PageLoaded;
Unloaded += PageUnloaded;
}
~BasePage()
{
Debug.WriteLine($"Disposed {GetType().Name}");
}
private void PageUnloaded(object sender, RoutedEventArgs e)
{
Loaded -= PageLoaded;
Unloaded -= PageUnloaded;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
private void PageLoaded(object sender, RoutedEventArgs e) => ViewModel.OnPageLoaded();
var mode = GetNavigationMode(e.NavigationMode);
var parameter = e.Parameter;
~BasePage()
{
Debug.WriteLine($"Disposed {GetType().Name}");
}
WeakReferenceMessenger.Default.UnregisterAll(this);
WeakReferenceMessenger.Default.RegisterAll(this);
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
ViewModel.OnNavigatedTo(mode, parameter);
}
var mode = GetNavigationMode(e.NavigationMode);
var parameter = e.Parameter;
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
base.OnNavigatingFrom(e);
WeakReferenceMessenger.Default.UnregisterAll(this);
WeakReferenceMessenger.Default.RegisterAll(this);
var mode = GetNavigationMode(e.NavigationMode);
var parameter = e.Parameter;
ViewModel.OnNavigatedTo(mode, parameter);
}
WeakReferenceMessenger.Default.UnregisterAll(this);
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
base.OnNavigatingFrom(e);
ViewModel.OnNavigatedFrom(mode, parameter);
var mode = GetNavigationMode(e.NavigationMode);
var parameter = e.Parameter;
GC.Collect();
}
WeakReferenceMessenger.Default.UnregisterAll(this);
private Domain.Models.Navigation.NavigationMode GetNavigationMode(NavigationMode mode)
{
return (Domain.Models.Navigation.NavigationMode)mode;
}
ViewModel.OnNavigatedFrom(mode, parameter);
GC.Collect();
}
private Domain.Models.Navigation.NavigationMode GetNavigationMode(NavigationMode mode)
{
return (Domain.Models.Navigation.NavigationMode)mode;
}
}

View File

@@ -9,68 +9,67 @@ using Wino.Core.Domain.Interfaces;
using Wino.Messaging.UI;
namespace Wino.Core.UWP.Controls
namespace Wino.Core.UWP.Controls;
public sealed partial class AccountCreationDialogControl : UserControl, IRecipient<CopyAuthURLRequested>
{
public sealed partial class AccountCreationDialogControl : UserControl, IRecipient<CopyAuthURLRequested>
private string copyClipboardURL;
public event EventHandler CancelClicked;
public AccountCreationDialogState State
{
private string copyClipboardURL;
public event EventHandler CancelClicked;
public AccountCreationDialogState State
{
get { return (AccountCreationDialogState)GetValue(StateProperty); }
set { SetValue(StateProperty, value); }
}
public static readonly DependencyProperty StateProperty = DependencyProperty.Register(nameof(State), typeof(AccountCreationDialogState), typeof(AccountCreationDialogControl), new PropertyMetadata(AccountCreationDialogState.Idle, new PropertyChangedCallback(OnStateChanged)));
public AccountCreationDialogControl()
{
InitializeComponent();
}
private static void OnStateChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is AccountCreationDialogControl dialog)
{
dialog.UpdateVisualStates();
}
}
private void UpdateVisualStates() => VisualStateManager.GoToState(this, State.ToString(), false);
public async void Receive(CopyAuthURLRequested message)
{
copyClipboardURL = message.AuthURL;
await Task.Delay(2000);
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
AuthHelpDialogButton.Visibility = Windows.UI.Xaml.Visibility.Visible;
});
}
private void ControlLoaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
WeakReferenceMessenger.Default.Register(this);
}
private void ControlUnloaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
WeakReferenceMessenger.Default.UnregisterAll(this);
}
private async void CopyClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
if (string.IsNullOrEmpty(copyClipboardURL)) return;
var clipboardService = WinoApplication.Current.Services.GetService<IClipboardService>();
await clipboardService.CopyClipboardAsync(copyClipboardURL);
}
private void CancelButtonClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e) => CancelClicked?.Invoke(this, null);
get { return (AccountCreationDialogState)GetValue(StateProperty); }
set { SetValue(StateProperty, value); }
}
public static readonly DependencyProperty StateProperty = DependencyProperty.Register(nameof(State), typeof(AccountCreationDialogState), typeof(AccountCreationDialogControl), new PropertyMetadata(AccountCreationDialogState.Idle, new PropertyChangedCallback(OnStateChanged)));
public AccountCreationDialogControl()
{
InitializeComponent();
}
private static void OnStateChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is AccountCreationDialogControl dialog)
{
dialog.UpdateVisualStates();
}
}
private void UpdateVisualStates() => VisualStateManager.GoToState(this, State.ToString(), false);
public async void Receive(CopyAuthURLRequested message)
{
copyClipboardURL = message.AuthURL;
await Task.Delay(2000);
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
AuthHelpDialogButton.Visibility = Windows.UI.Xaml.Visibility.Visible;
});
}
private void ControlLoaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
WeakReferenceMessenger.Default.Register(this);
}
private void ControlUnloaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
WeakReferenceMessenger.Default.UnregisterAll(this);
}
private async void CopyClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
if (string.IsNullOrEmpty(copyClipboardURL)) return;
var clipboardService = WinoApplication.Current.Services.GetService<IClipboardService>();
await clipboardService.CopyClipboardAsync(copyClipboardURL);
}
private void CancelButtonClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e) => CancelClicked?.Invoke(this, null);
}

View File

@@ -1,106 +1,105 @@
using System.Collections.Generic;
namespace Wino.Core.UWP.Controls
namespace Wino.Core.UWP.Controls;
public static class ControlConstants
{
public static class ControlConstants
public static Dictionary<WinoIconGlyph, string> WinoIconFontDictionary = new Dictionary<WinoIconGlyph, string>()
{
public static Dictionary<WinoIconGlyph, string> WinoIconFontDictionary = new Dictionary<WinoIconGlyph, string>()
{
{ WinoIconGlyph.None, "\u0020" },
{ WinoIconGlyph.Archive, "\uE066" },
{ WinoIconGlyph.UnArchive, "\uE06C" },
{ WinoIconGlyph.Reply, "\uF176" },
{ WinoIconGlyph.ReplyAll, "\uF17A" },
{ WinoIconGlyph.Sync, "\uE895" },
{ WinoIconGlyph.Send, "\uEA8E" },
{ WinoIconGlyph.LightEditor, "\uE1F6" },
{ WinoIconGlyph.Delete, "\uEEA6" },
{ WinoIconGlyph.DarkEditor, "\uEE44" },
{ WinoIconGlyph.Draft, "\uF3BE" },
{ WinoIconGlyph.Flag, "\uF40C" },
{ WinoIconGlyph.ClearFlag, "\uF40F" },
{ WinoIconGlyph.Folder, "\uE643" },
{ WinoIconGlyph.Forward, "\uE7AA" },
{ WinoIconGlyph.Inbox, "\uF516" },
{ WinoIconGlyph.MarkRead, "\uF522" },
{ WinoIconGlyph.MarkUnread, "\uF529" },
{ WinoIconGlyph.MultiSelect, "\uE84D" },
{ WinoIconGlyph.Save, "\uEA43" },
{ WinoIconGlyph.CreateFolder, "\uE645" },
{ WinoIconGlyph.Pin, "\uF5FF" },
{ WinoIconGlyph.UnPin, "\uE985" },
{ WinoIconGlyph.Star, "\uE734" },
{ WinoIconGlyph.Ignore, "\uF5D0" },
{ WinoIconGlyph.Find, "\uEA7D" },
{ WinoIconGlyph.Zoom, "\uEE8E" },
{ WinoIconGlyph.SpecialFolderInbox, "\uF516" },
{ WinoIconGlyph.SpecialFolderStarred, "\uF70D" },
{ WinoIconGlyph.SpecialFolderImportant, "\uE2F4" },
{ WinoIconGlyph.SpecialFolderSent, "\uEA8E" },
{ WinoIconGlyph.SpecialFolderDraft, "\uF3BE" },
{ WinoIconGlyph.SpecialFolderArchive, "\uE066" },
{ WinoIconGlyph.SpecialFolderDeleted, "\uEEA6" },
{ WinoIconGlyph.SpecialFolderJunk, "\uF140" },
{ WinoIconGlyph.SpecialFolderChat, "\uE8BD" },
{ WinoIconGlyph.SpecialFolderCategory, "\uF599" },
{ WinoIconGlyph.SpecialFolderUnread, "\uF529" },
{ WinoIconGlyph.SpecialFolderForums, "\uF5B8" },
{ WinoIconGlyph.SpecialFolderUpdated, "\uF565" },
{ WinoIconGlyph.SpecialFolderPersonal, "\uE25A" },
{ WinoIconGlyph.SpecialFolderPromotions, "\uF7B6" },
{ WinoIconGlyph.SpecialFolderSocial, "\uEEEB" },
{ WinoIconGlyph.SpecialFolderOther, "\uE643" },
{ WinoIconGlyph.SpecialFolderMore, "\uF0F4" },
{ WinoIconGlyph.Microsoft, "\uE904" },
{ WinoIconGlyph.Google, "\uE905" },
{ WinoIconGlyph.NewMail, "\uF107" },
{ WinoIconGlyph.TurnOfNotifications, "\uF11D" },
{ WinoIconGlyph.Rename, "\uF668" },
{ WinoIconGlyph.EmptyFolder, "\uE47E" },
{ WinoIconGlyph.DontSync, "\uF195" },
{ WinoIconGlyph.Move, "\uE7B8" },
{ WinoIconGlyph.Mail, "\uF509" },
{ WinoIconGlyph.More, "\uE824" },
{ WinoIconGlyph.CustomServer, "\uF509" },
{ WinoIconGlyph.Print, "\uE922" },
{ WinoIconGlyph.Attachment, "\uE723" },
{ WinoIconGlyph.SortTextDesc, "\U000F3606" },
{ WinoIconGlyph.SortLinesDesc, "\U000F038A" },
{ WinoIconGlyph.Certificate, "\uEB95" },
{ WinoIconGlyph.OpenInNewWindow, "\uE8A7" },
{ WinoIconGlyph.Message, "\uE8BD" },
{ WinoIconGlyph.New, "\U000F002A" },
{ WinoIconGlyph.Blocked,"\uF140" },
{ WinoIconGlyph.IMAP, "\uE715" },
{ WinoIconGlyph.Calendar, "\uE912" },
{ WinoIconGlyph.CalendarToday, "\uE911" },
{ WinoIconGlyph.CalendarDay, "\uE913" },
{ WinoIconGlyph.CalendarWeek, "\uE914" },
{ WinoIconGlyph.CalendarMonth, "\uE91c" },
{ WinoIconGlyph.CalendarYear, "\uE917" },
{ WinoIconGlyph.WeatherBlow, "\uE907" },
{ WinoIconGlyph.WeatherCloudy, "\uE920" },
{ WinoIconGlyph.WeatherSunny, "\uE90e" },
{ WinoIconGlyph.WeatherRainy, "\uE908" },
{ WinoIconGlyph.WeatherSnowy, "\uE90a" },
{ WinoIconGlyph.WeatherSnowShowerAtNight, "\uE90c" },
{ WinoIconGlyph.WeatherThunderstorm, "\uE906" },
{ WinoIconGlyph.CalendarEventRepeat, "\uE915" },
{ WinoIconGlyph.CalendarEventMuiltiDay, "\uE91b" },
{ WinoIconGlyph.Reminder, "\uE918" },
{ WinoIconGlyph.CalendarAttendee, "\uE91a" },
{ WinoIconGlyph.CalendarSync, "\uE91d" },
{ WinoIconGlyph.CalendarError, "\uE916" },
{ WinoIconGlyph.CalendarAttendees, "\uE929" },
{ WinoIconGlyph.EventEditSeries, "\uE92A" },
{ WinoIconGlyph.EventTentative, "\uE928" },
{ WinoIconGlyph.EventAccept, "\uE925" },
{ WinoIconGlyph.EventRespond, "\uE924" },
{ WinoIconGlyph.EventReminder, "\uE923" },
{ WinoIconGlyph.EventJoinOnline, "\uE926" },
{ WinoIconGlyph.ViewMessageSource, "\uE943" },
{ WinoIconGlyph.Apple, "\uE92B" },
{ WinoIconGlyph.Yahoo, "\uE92C" }
};
}
{ WinoIconGlyph.None, "\u0020" },
{ WinoIconGlyph.Archive, "\uE066" },
{ WinoIconGlyph.UnArchive, "\uE06C" },
{ WinoIconGlyph.Reply, "\uF176" },
{ WinoIconGlyph.ReplyAll, "\uF17A" },
{ WinoIconGlyph.Sync, "\uE895" },
{ WinoIconGlyph.Send, "\uEA8E" },
{ WinoIconGlyph.LightEditor, "\uE1F6" },
{ WinoIconGlyph.Delete, "\uEEA6" },
{ WinoIconGlyph.DarkEditor, "\uEE44" },
{ WinoIconGlyph.Draft, "\uF3BE" },
{ WinoIconGlyph.Flag, "\uF40C" },
{ WinoIconGlyph.ClearFlag, "\uF40F" },
{ WinoIconGlyph.Folder, "\uE643" },
{ WinoIconGlyph.Forward, "\uE7AA" },
{ WinoIconGlyph.Inbox, "\uF516" },
{ WinoIconGlyph.MarkRead, "\uF522" },
{ WinoIconGlyph.MarkUnread, "\uF529" },
{ WinoIconGlyph.MultiSelect, "\uE84D" },
{ WinoIconGlyph.Save, "\uEA43" },
{ WinoIconGlyph.CreateFolder, "\uE645" },
{ WinoIconGlyph.Pin, "\uF5FF" },
{ WinoIconGlyph.UnPin, "\uE985" },
{ WinoIconGlyph.Star, "\uE734" },
{ WinoIconGlyph.Ignore, "\uF5D0" },
{ WinoIconGlyph.Find, "\uEA7D" },
{ WinoIconGlyph.Zoom, "\uEE8E" },
{ WinoIconGlyph.SpecialFolderInbox, "\uF516" },
{ WinoIconGlyph.SpecialFolderStarred, "\uF70D" },
{ WinoIconGlyph.SpecialFolderImportant, "\uE2F4" },
{ WinoIconGlyph.SpecialFolderSent, "\uEA8E" },
{ WinoIconGlyph.SpecialFolderDraft, "\uF3BE" },
{ WinoIconGlyph.SpecialFolderArchive, "\uE066" },
{ WinoIconGlyph.SpecialFolderDeleted, "\uEEA6" },
{ WinoIconGlyph.SpecialFolderJunk, "\uF140" },
{ WinoIconGlyph.SpecialFolderChat, "\uE8BD" },
{ WinoIconGlyph.SpecialFolderCategory, "\uF599" },
{ WinoIconGlyph.SpecialFolderUnread, "\uF529" },
{ WinoIconGlyph.SpecialFolderForums, "\uF5B8" },
{ WinoIconGlyph.SpecialFolderUpdated, "\uF565" },
{ WinoIconGlyph.SpecialFolderPersonal, "\uE25A" },
{ WinoIconGlyph.SpecialFolderPromotions, "\uF7B6" },
{ WinoIconGlyph.SpecialFolderSocial, "\uEEEB" },
{ WinoIconGlyph.SpecialFolderOther, "\uE643" },
{ WinoIconGlyph.SpecialFolderMore, "\uF0F4" },
{ WinoIconGlyph.Microsoft, "\uE904" },
{ WinoIconGlyph.Google, "\uE905" },
{ WinoIconGlyph.NewMail, "\uF107" },
{ WinoIconGlyph.TurnOfNotifications, "\uF11D" },
{ WinoIconGlyph.Rename, "\uF668" },
{ WinoIconGlyph.EmptyFolder, "\uE47E" },
{ WinoIconGlyph.DontSync, "\uF195" },
{ WinoIconGlyph.Move, "\uE7B8" },
{ WinoIconGlyph.Mail, "\uF509" },
{ WinoIconGlyph.More, "\uE824" },
{ WinoIconGlyph.CustomServer, "\uF509" },
{ WinoIconGlyph.Print, "\uE922" },
{ WinoIconGlyph.Attachment, "\uE723" },
{ WinoIconGlyph.SortTextDesc, "\U000F3606" },
{ WinoIconGlyph.SortLinesDesc, "\U000F038A" },
{ WinoIconGlyph.Certificate, "\uEB95" },
{ WinoIconGlyph.OpenInNewWindow, "\uE8A7" },
{ WinoIconGlyph.Message, "\uE8BD" },
{ WinoIconGlyph.New, "\U000F002A" },
{ WinoIconGlyph.Blocked,"\uF140" },
{ WinoIconGlyph.IMAP, "\uE715" },
{ WinoIconGlyph.Calendar, "\uE912" },
{ WinoIconGlyph.CalendarToday, "\uE911" },
{ WinoIconGlyph.CalendarDay, "\uE913" },
{ WinoIconGlyph.CalendarWeek, "\uE914" },
{ WinoIconGlyph.CalendarMonth, "\uE91c" },
{ WinoIconGlyph.CalendarYear, "\uE917" },
{ WinoIconGlyph.WeatherBlow, "\uE907" },
{ WinoIconGlyph.WeatherCloudy, "\uE920" },
{ WinoIconGlyph.WeatherSunny, "\uE90e" },
{ WinoIconGlyph.WeatherRainy, "\uE908" },
{ WinoIconGlyph.WeatherSnowy, "\uE90a" },
{ WinoIconGlyph.WeatherSnowShowerAtNight, "\uE90c" },
{ WinoIconGlyph.WeatherThunderstorm, "\uE906" },
{ WinoIconGlyph.CalendarEventRepeat, "\uE915" },
{ WinoIconGlyph.CalendarEventMuiltiDay, "\uE91b" },
{ WinoIconGlyph.Reminder, "\uE918" },
{ WinoIconGlyph.CalendarAttendee, "\uE91a" },
{ WinoIconGlyph.CalendarSync, "\uE91d" },
{ WinoIconGlyph.CalendarError, "\uE916" },
{ WinoIconGlyph.CalendarAttendees, "\uE929" },
{ WinoIconGlyph.EventEditSeries, "\uE92A" },
{ WinoIconGlyph.EventTentative, "\uE928" },
{ WinoIconGlyph.EventAccept, "\uE925" },
{ WinoIconGlyph.EventRespond, "\uE924" },
{ WinoIconGlyph.EventReminder, "\uE923" },
{ WinoIconGlyph.EventJoinOnline, "\uE926" },
{ WinoIconGlyph.ViewMessageSource, "\uE943" },
{ WinoIconGlyph.Apple, "\uE92B" },
{ WinoIconGlyph.Yahoo, "\uE92C" }
};
}

View File

@@ -2,86 +2,85 @@
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace Wino.Core.UWP.Controls
namespace Wino.Core.UWP.Controls;
public partial class EqualGridPanel : Panel
{
public partial class EqualGridPanel : Panel
public int Rows
{
public int Rows
get { return (int)GetValue(RowsProperty); }
set { SetValue(RowsProperty, value); }
}
public static readonly DependencyProperty RowsProperty =
DependencyProperty.Register(
nameof(Rows),
typeof(int),
typeof(EqualGridPanel),
new PropertyMetadata(1, OnLayoutPropertyChanged));
public int Columns
{
get { return (int)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(
nameof(Columns),
typeof(int),
typeof(EqualGridPanel),
new PropertyMetadata(1, OnLayoutPropertyChanged));
private static void OnLayoutPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is EqualGridPanel panel)
{
get { return (int)GetValue(RowsProperty); }
set { SetValue(RowsProperty, value); }
}
public static readonly DependencyProperty RowsProperty =
DependencyProperty.Register(
nameof(Rows),
typeof(int),
typeof(EqualGridPanel),
new PropertyMetadata(1, OnLayoutPropertyChanged));
public int Columns
{
get { return (int)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(
nameof(Columns),
typeof(int),
typeof(EqualGridPanel),
new PropertyMetadata(1, OnLayoutPropertyChanged));
private static void OnLayoutPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is EqualGridPanel panel)
{
panel.InvalidateMeasure();
panel.InvalidateArrange();
}
}
protected override Size MeasureOverride(Size availableSize)
{
if (Rows <= 0 || Columns <= 0)
{
return new Size(0, 0);
}
double cellWidth = availableSize.Width / Columns;
double cellHeight = availableSize.Height / Rows;
foreach (UIElement child in Children)
{
child.Measure(new Size(cellWidth, cellHeight));
}
return availableSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
if (Rows <= 0 || Columns <= 0)
{
return new Size(0, 0);
}
double cellWidth = finalSize.Width / Columns;
double cellHeight = finalSize.Height / Rows;
for (int i = 0; i < Children.Count; i++)
{
int row = i / Columns;
int column = i % Columns;
double x = column * cellWidth;
double y = row * cellHeight;
Rect rect = new Rect(x, y, cellWidth, cellHeight);
Children[i].Arrange(rect);
}
return finalSize;
panel.InvalidateMeasure();
panel.InvalidateArrange();
}
}
protected override Size MeasureOverride(Size availableSize)
{
if (Rows <= 0 || Columns <= 0)
{
return new Size(0, 0);
}
double cellWidth = availableSize.Width / Columns;
double cellHeight = availableSize.Height / Rows;
foreach (UIElement child in Children)
{
child.Measure(new Size(cellWidth, cellHeight));
}
return availableSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
if (Rows <= 0 || Columns <= 0)
{
return new Size(0, 0);
}
double cellWidth = finalSize.Width / Columns;
double cellHeight = finalSize.Height / Rows;
for (int i = 0; i < Children.Count; i++)
{
int row = i / Columns;
int column = i % Columns;
double x = column * cellWidth;
double y = row * cellHeight;
Rect rect = new Rect(x, y, cellWidth, cellHeight);
Children[i].Arrange(rect);
}
return finalSize;
}
}

View File

@@ -4,193 +4,208 @@ using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Enums;
namespace Wino.Core.UWP.Controls
namespace Wino.Core.UWP.Controls;
public sealed partial class WinoAppTitleBar : UserControl
{
public sealed partial class WinoAppTitleBar : UserControl
public event TypedEventHandler<WinoAppTitleBar, RoutedEventArgs> BackButtonClicked;
public static readonly DependencyProperty IsRenderingPaneVisibleProperty = DependencyProperty.Register(nameof(IsRenderingPaneVisible), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(false, OnDrawingPropertyChanged));
public static readonly DependencyProperty IsReaderNarrowedProperty = DependencyProperty.Register(nameof(IsReaderNarrowed), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(false, OnIsReaderNarrowedChanged));
public static readonly DependencyProperty IsBackButtonVisibleProperty = DependencyProperty.Register(nameof(IsBackButtonVisible), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(false, OnDrawingPropertyChanged));
public static readonly DependencyProperty OpenPaneLengthProperty = DependencyProperty.Register(nameof(OpenPaneLength), typeof(double), typeof(WinoAppTitleBar), new PropertyMetadata(0d, OnDrawingPropertyChanged));
public static readonly DependencyProperty IsNavigationPaneOpenProperty = DependencyProperty.Register(nameof(IsNavigationPaneOpen), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(false, OnDrawingPropertyChanged));
public static readonly DependencyProperty NavigationViewDisplayModeProperty = DependencyProperty.Register(nameof(NavigationViewDisplayMode), typeof(Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode), typeof(WinoAppTitleBar), new PropertyMetadata(Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Compact, OnDrawingPropertyChanged));
public static readonly DependencyProperty ShellFrameContentProperty = DependencyProperty.Register(nameof(ShellFrameContent), typeof(UIElement), typeof(WinoAppTitleBar), new PropertyMetadata(null, OnDrawingPropertyChanged));
public static readonly DependencyProperty SystemReservedProperty = DependencyProperty.Register(nameof(SystemReserved), typeof(double), typeof(WinoAppTitleBar), new PropertyMetadata(0, OnDrawingPropertyChanged));
public static readonly DependencyProperty CoreWindowTextProperty = DependencyProperty.Register(nameof(CoreWindowText), typeof(string), typeof(WinoAppTitleBar), new PropertyMetadata(string.Empty, OnDrawingPropertyChanged));
public static readonly DependencyProperty ReadingPaneLengthProperty = DependencyProperty.Register(nameof(ReadingPaneLength), typeof(double), typeof(WinoAppTitleBar), new PropertyMetadata(420d, OnDrawingPropertyChanged));
public static readonly DependencyProperty ConnectionStatusProperty = DependencyProperty.Register(nameof(ConnectionStatus), typeof(WinoServerConnectionStatus), typeof(WinoAppTitleBar), new PropertyMetadata(WinoServerConnectionStatus.None, new PropertyChangedCallback(OnConnectionStatusChanged)));
public static readonly DependencyProperty ReconnectCommandProperty = DependencyProperty.Register(nameof(ReconnectCommand), typeof(ICommand), typeof(WinoAppTitleBar), new PropertyMetadata(null));
public static readonly DependencyProperty ShrinkShellContentOnExpansionProperty = DependencyProperty.Register(nameof(ShrinkShellContentOnExpansion), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(true));
public static readonly DependencyProperty IsDragAreaProperty = DependencyProperty.Register(nameof(IsDragArea), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(false, new PropertyChangedCallback(OnIsDragAreaChanged)));
public static readonly DependencyProperty IsShellFrameContentVisibleProperty = DependencyProperty.Register(nameof(IsShellFrameContentVisible), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(true));
public static readonly DependencyProperty IsMenuButtonVisibleProperty = DependencyProperty.Register(nameof(IsMenuButtonVisible), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(true));
public bool IsShellFrameContentVisible
{
public event TypedEventHandler<WinoAppTitleBar, RoutedEventArgs> BackButtonClicked;
get { return (bool)GetValue(IsShellFrameContentVisibleProperty); }
set { SetValue(IsShellFrameContentVisibleProperty, value); }
}
public static readonly DependencyProperty IsRenderingPaneVisibleProperty = DependencyProperty.Register(nameof(IsRenderingPaneVisible), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(false, OnDrawingPropertyChanged));
public static readonly DependencyProperty IsReaderNarrowedProperty = DependencyProperty.Register(nameof(IsReaderNarrowed), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(false, OnIsReaderNarrowedChanged));
public static readonly DependencyProperty IsBackButtonVisibleProperty = DependencyProperty.Register(nameof(IsBackButtonVisible), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(false, OnDrawingPropertyChanged));
public static readonly DependencyProperty OpenPaneLengthProperty = DependencyProperty.Register(nameof(OpenPaneLength), typeof(double), typeof(WinoAppTitleBar), new PropertyMetadata(0d, OnDrawingPropertyChanged));
public static readonly DependencyProperty IsNavigationPaneOpenProperty = DependencyProperty.Register(nameof(IsNavigationPaneOpen), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(false, OnDrawingPropertyChanged));
public static readonly DependencyProperty NavigationViewDisplayModeProperty = DependencyProperty.Register(nameof(NavigationViewDisplayMode), typeof(Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode), typeof(WinoAppTitleBar), new PropertyMetadata(Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Compact, OnDrawingPropertyChanged));
public static readonly DependencyProperty ShellFrameContentProperty = DependencyProperty.Register(nameof(ShellFrameContent), typeof(UIElement), typeof(WinoAppTitleBar), new PropertyMetadata(null, OnDrawingPropertyChanged));
public static readonly DependencyProperty SystemReservedProperty = DependencyProperty.Register(nameof(SystemReserved), typeof(double), typeof(WinoAppTitleBar), new PropertyMetadata(0, OnDrawingPropertyChanged));
public static readonly DependencyProperty CoreWindowTextProperty = DependencyProperty.Register(nameof(CoreWindowText), typeof(string), typeof(WinoAppTitleBar), new PropertyMetadata(string.Empty, OnDrawingPropertyChanged));
public static readonly DependencyProperty ReadingPaneLengthProperty = DependencyProperty.Register(nameof(ReadingPaneLength), typeof(double), typeof(WinoAppTitleBar), new PropertyMetadata(420d, OnDrawingPropertyChanged));
public static readonly DependencyProperty ConnectionStatusProperty = DependencyProperty.Register(nameof(ConnectionStatus), typeof(WinoServerConnectionStatus), typeof(WinoAppTitleBar), new PropertyMetadata(WinoServerConnectionStatus.None, new PropertyChangedCallback(OnConnectionStatusChanged)));
public static readonly DependencyProperty ReconnectCommandProperty = DependencyProperty.Register(nameof(ReconnectCommand), typeof(ICommand), typeof(WinoAppTitleBar), new PropertyMetadata(null));
public static readonly DependencyProperty ShrinkShellContentOnExpansionProperty = DependencyProperty.Register(nameof(ShrinkShellContentOnExpansion), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(true));
public static readonly DependencyProperty IsDragAreaProperty = DependencyProperty.Register(nameof(IsDragArea), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(false, new PropertyChangedCallback(OnIsDragAreaChanged)));
public static readonly DependencyProperty IsShellFrameContentVisibleProperty = DependencyProperty.Register(nameof(IsShellFrameContentVisible), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(true));
public static readonly DependencyProperty IsMenuButtonVisibleProperty = DependencyProperty.Register(nameof(IsMenuButtonVisible), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(true));
public ICommand ReconnectCommand
{
get { return (ICommand)GetValue(ReconnectCommandProperty); }
set { SetValue(ReconnectCommandProperty, value); }
}
public bool IsShellFrameContentVisible
public WinoServerConnectionStatus ConnectionStatus
{
get { return (WinoServerConnectionStatus)GetValue(ConnectionStatusProperty); }
set { SetValue(ConnectionStatusProperty, value); }
}
public string CoreWindowText
{
get { return (string)GetValue(CoreWindowTextProperty); }
set { SetValue(CoreWindowTextProperty, value); }
}
public bool IsDragArea
{
get { return (bool)GetValue(IsDragAreaProperty); }
set { SetValue(IsDragAreaProperty, value); }
}
public double SystemReserved
{
get { return (double)GetValue(SystemReservedProperty); }
set { SetValue(SystemReservedProperty, value); }
}
public UIElement ShellFrameContent
{
get { return (UIElement)GetValue(ShellFrameContentProperty); }
set { SetValue(ShellFrameContentProperty, value); }
}
public Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode NavigationViewDisplayMode
{
get { return (Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode)GetValue(NavigationViewDisplayModeProperty); }
set { SetValue(NavigationViewDisplayModeProperty, value); }
}
public bool ShrinkShellContentOnExpansion
{
get { return (bool)GetValue(ShrinkShellContentOnExpansionProperty); }
set { SetValue(ShrinkShellContentOnExpansionProperty, value); }
}
public bool IsNavigationPaneOpen
{
get { return (bool)GetValue(IsNavigationPaneOpenProperty); }
set { SetValue(IsNavigationPaneOpenProperty, value); }
}
public double OpenPaneLength
{
get { return (double)GetValue(OpenPaneLengthProperty); }
set { SetValue(OpenPaneLengthProperty, value); }
}
public bool IsMenuButtonVisible
{
get { return (bool)GetValue(IsMenuButtonVisibleProperty); }
set { SetValue(IsMenuButtonVisibleProperty, value); }
}
public bool IsBackButtonVisible
{
get { return (bool)GetValue(IsBackButtonVisibleProperty); }
set { SetValue(IsBackButtonVisibleProperty, value); }
}
public bool IsReaderNarrowed
{
get { return (bool)GetValue(IsReaderNarrowedProperty); }
set { SetValue(IsReaderNarrowedProperty, value); }
}
public bool IsRenderingPaneVisible
{
get { return (bool)GetValue(IsRenderingPaneVisibleProperty); }
set { SetValue(IsRenderingPaneVisibleProperty, value); }
}
public double ReadingPaneLength
{
get { return (double)GetValue(ReadingPaneLengthProperty); }
set { SetValue(ReadingPaneLengthProperty, value); }
}
private static void OnIsReaderNarrowedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoAppTitleBar bar)
{
get { return (bool)GetValue(IsShellFrameContentVisibleProperty); }
set { SetValue(IsShellFrameContentVisibleProperty, value); }
bar.DrawTitleBar();
}
}
public ICommand ReconnectCommand
private static void OnDrawingPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoAppTitleBar bar)
{
get { return (ICommand)GetValue(ReconnectCommandProperty); }
set { SetValue(ReconnectCommandProperty, value); }
bar.DrawTitleBar();
}
}
public WinoServerConnectionStatus ConnectionStatus
private static void OnConnectionStatusChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoAppTitleBar bar)
{
get { return (WinoServerConnectionStatus)GetValue(ConnectionStatusProperty); }
set { SetValue(ConnectionStatusProperty, value); }
bar.UpdateConnectionStatus();
}
}
public string CoreWindowText
private static void OnIsDragAreaChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoAppTitleBar bar)
{
get { return (string)GetValue(CoreWindowTextProperty); }
set { SetValue(CoreWindowTextProperty, value); }
bar.SetDragArea();
}
}
public bool IsDragArea
private void SetDragArea()
{
if (IsDragArea)
{
get { return (bool)GetValue(IsDragAreaProperty); }
set { SetValue(IsDragAreaProperty, value); }
Window.Current.SetTitleBar(dragbar);
}
}
private void UpdateConnectionStatus()
{
public double SystemReserved
}
private void DrawTitleBar()
{
UpdateLayout();
CoreWindowTitleTextBlock.Visibility = Visibility.Collapsed;
ShellContentContainer.Width = double.NaN;
ShellContentContainer.Margin = new Thickness(0, 0, 0, 0);
ShellContentContainer.HorizontalAlignment = HorizontalAlignment.Stretch;
EmptySpaceWidth.Width = new GridLength(1, GridUnitType.Star);
// Menu is not visible.
if (NavigationViewDisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Minimal)
{
get { return (double)GetValue(SystemReservedProperty); }
set { SetValue(SystemReservedProperty, value); }
}
public UIElement ShellFrameContent
else if (NavigationViewDisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Compact)
{
get { return (UIElement)GetValue(ShellFrameContentProperty); }
set { SetValue(ShellFrameContentProperty, value); }
}
// Icons are visible.
public Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode NavigationViewDisplayMode
{
get { return (Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode)GetValue(NavigationViewDisplayModeProperty); }
set { SetValue(NavigationViewDisplayModeProperty, value); }
}
public bool ShrinkShellContentOnExpansion
{
get { return (bool)GetValue(ShrinkShellContentOnExpansionProperty); }
set { SetValue(ShrinkShellContentOnExpansionProperty, value); }
}
public bool IsNavigationPaneOpen
{
get { return (bool)GetValue(IsNavigationPaneOpenProperty); }
set { SetValue(IsNavigationPaneOpenProperty, value); }
}
public double OpenPaneLength
{
get { return (double)GetValue(OpenPaneLengthProperty); }
set { SetValue(OpenPaneLengthProperty, value); }
}
public bool IsMenuButtonVisible
{
get { return (bool)GetValue(IsMenuButtonVisibleProperty); }
set { SetValue(IsMenuButtonVisibleProperty, value); }
}
public bool IsBackButtonVisible
{
get { return (bool)GetValue(IsBackButtonVisibleProperty); }
set { SetValue(IsBackButtonVisibleProperty, value); }
}
public bool IsReaderNarrowed
{
get { return (bool)GetValue(IsReaderNarrowedProperty); }
set { SetValue(IsReaderNarrowedProperty, value); }
}
public bool IsRenderingPaneVisible
{
get { return (bool)GetValue(IsRenderingPaneVisibleProperty); }
set { SetValue(IsRenderingPaneVisibleProperty, value); }
}
public double ReadingPaneLength
{
get { return (double)GetValue(ReadingPaneLengthProperty); }
set { SetValue(ReadingPaneLengthProperty, value); }
}
private static void OnIsReaderNarrowedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoAppTitleBar bar)
if (!IsReaderNarrowed && ShrinkShellContentOnExpansion)
{
bar.DrawTitleBar();
ShellContentContainer.HorizontalAlignment = HorizontalAlignment.Left;
ShellContentContainer.Width = ReadingPaneLength;
}
}
private static void OnDrawingPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
else if (NavigationViewDisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Expanded)
{
if (obj is WinoAppTitleBar bar)
if (IsNavigationPaneOpen)
{
bar.DrawTitleBar();
}
}
CoreWindowTitleTextBlock.Visibility = Visibility.Visible;
private static void OnConnectionStatusChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoAppTitleBar bar)
{
bar.UpdateConnectionStatus();
}
}
private static void OnIsDragAreaChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoAppTitleBar bar)
{
bar.SetDragArea();
}
}
private void SetDragArea()
{
if (IsDragArea)
{
Window.Current.SetTitleBar(dragbar);
}
}
private void UpdateConnectionStatus()
{
}
private void DrawTitleBar()
{
UpdateLayout();
CoreWindowTitleTextBlock.Visibility = Visibility.Collapsed;
ShellContentContainer.Width = double.NaN;
ShellContentContainer.Margin = new Thickness(0, 0, 0, 0);
ShellContentContainer.HorizontalAlignment = HorizontalAlignment.Stretch;
EmptySpaceWidth.Width = new GridLength(1, GridUnitType.Star);
// Menu is not visible.
if (NavigationViewDisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Minimal)
{
}
else if (NavigationViewDisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Compact)
{
// Icons are visible.
// LMargin = OpenPaneLength - LeftMenuStackPanel
ShellContentContainer.Margin = new Thickness(OpenPaneLength - LeftMenuStackPanel.ActualSize.X, 0, 0, 0);
if (!IsReaderNarrowed && ShrinkShellContentOnExpansion)
{
@@ -198,59 +213,43 @@ namespace Wino.Core.UWP.Controls
ShellContentContainer.Width = ReadingPaneLength;
}
}
else if (NavigationViewDisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Expanded)
else
{
if (IsNavigationPaneOpen)
if (ShrinkShellContentOnExpansion)
{
CoreWindowTitleTextBlock.Visibility = Visibility.Visible;
// LMargin = OpenPaneLength - LeftMenuStackPanel
ShellContentContainer.Margin = new Thickness(OpenPaneLength - LeftMenuStackPanel.ActualSize.X, 0, 0, 0);
if (!IsReaderNarrowed && ShrinkShellContentOnExpansion)
{
ShellContentContainer.HorizontalAlignment = HorizontalAlignment.Left;
ShellContentContainer.Width = ReadingPaneLength;
}
EmptySpaceWidth.Width = new GridLength(ReadingPaneLength, GridUnitType.Pixel);
}
else
{
if (ShrinkShellContentOnExpansion)
{
EmptySpaceWidth.Width = new GridLength(ReadingPaneLength, GridUnitType.Pixel);
}
else
{
EmptySpaceWidth.Width = new GridLength(ReadingPaneLength, GridUnitType.Star);
}
EmptySpaceWidth.Width = new GridLength(ReadingPaneLength, GridUnitType.Star);
}
}
}
}
public WinoAppTitleBar()
{
InitializeComponent();
}
public WinoAppTitleBar()
{
InitializeComponent();
}
private void BackClicked(object sender, RoutedEventArgs e)
{
BackButtonClicked?.Invoke(this, e);
}
private void BackClicked(object sender, RoutedEventArgs e)
{
BackButtonClicked?.Invoke(this, e);
}
private void PaneClicked(object sender, RoutedEventArgs e)
{
IsNavigationPaneOpen = !IsNavigationPaneOpen;
}
private void PaneClicked(object sender, RoutedEventArgs e)
{
IsNavigationPaneOpen = !IsNavigationPaneOpen;
}
private void TitlebarSizeChanged(object sender, SizeChangedEventArgs e) => DrawTitleBar();
private void TitlebarSizeChanged(object sender, SizeChangedEventArgs e) => DrawTitleBar();
private void ReconnectClicked(object sender, RoutedEventArgs e)
{
// Close the popup for reconnect button.
ReconnectFlyout.Hide();
private void ReconnectClicked(object sender, RoutedEventArgs e)
{
// Close the popup for reconnect button.
ReconnectFlyout.Hide();
// Execute the reconnect command.
ReconnectCommand?.Execute(null);
}
// Execute the reconnect command.
ReconnectCommand?.Execute(null);
}
}

View File

@@ -1,136 +1,135 @@
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace Wino.Core.UWP.Controls
namespace Wino.Core.UWP.Controls;
public enum WinoIconGlyph
{
public enum WinoIconGlyph
None,
NewMail,
Google,
Microsoft,
CustomServer,
Archive,
UnArchive,
Reply,
ReplyAll,
LightEditor,
DarkEditor,
Delete,
Move,
Mail,
Draft,
Flag,
ClearFlag,
Folder,
Forward,
Inbox,
MarkRead,
MarkUnread,
Send,
Save,
Sync,
MultiSelect,
Zoom,
Pin,
UnPin,
Ignore,
Star,
CreateFolder,
More,
Find,
SpecialFolderInbox,
SpecialFolderStarred,
SpecialFolderImportant,
SpecialFolderSent,
SpecialFolderDraft,
SpecialFolderArchive,
SpecialFolderDeleted,
SpecialFolderJunk,
SpecialFolderChat,
SpecialFolderCategory,
SpecialFolderUnread,
SpecialFolderForums,
SpecialFolderUpdated,
SpecialFolderPersonal,
SpecialFolderPromotions,
SpecialFolderSocial,
SpecialFolderOther,
SpecialFolderMore,
TurnOfNotifications,
EmptyFolder,
Rename,
DontSync,
Attachment,
SortTextDesc,
SortLinesDesc,
Certificate,
OpenInNewWindow,
Blocked,
Message,
New,
IMAP,
Print,
Calendar,
CalendarToday,
CalendarDay,
CalendarWeek,
CalendarWorkWeek,
CalendarMonth,
CalendarYear,
WeatherBlow,
WeatherCloudy,
WeatherSunny,
WeatherRainy,
WeatherSnowy,
WeatherSnowShowerAtNight,
WeatherThunderstorm,
CalendarEventRepeat,
CalendarEventMuiltiDay,
CalendarError,
Reminder,
CalendarAttendee,
CalendarAttendees,
CalendarSync,
EventRespond,
EventAccept,
EventTentative,
EventDecline,
EventReminder,
EventEditSeries,
EventJoinOnline,
ViewMessageSource,
Apple,
Yahoo
}
public partial class WinoFontIcon : FontIcon
{
public WinoIconGlyph Icon
{
None,
NewMail,
Google,
Microsoft,
CustomServer,
Archive,
UnArchive,
Reply,
ReplyAll,
LightEditor,
DarkEditor,
Delete,
Move,
Mail,
Draft,
Flag,
ClearFlag,
Folder,
Forward,
Inbox,
MarkRead,
MarkUnread,
Send,
Save,
Sync,
MultiSelect,
Zoom,
Pin,
UnPin,
Ignore,
Star,
CreateFolder,
More,
Find,
SpecialFolderInbox,
SpecialFolderStarred,
SpecialFolderImportant,
SpecialFolderSent,
SpecialFolderDraft,
SpecialFolderArchive,
SpecialFolderDeleted,
SpecialFolderJunk,
SpecialFolderChat,
SpecialFolderCategory,
SpecialFolderUnread,
SpecialFolderForums,
SpecialFolderUpdated,
SpecialFolderPersonal,
SpecialFolderPromotions,
SpecialFolderSocial,
SpecialFolderOther,
SpecialFolderMore,
TurnOfNotifications,
EmptyFolder,
Rename,
DontSync,
Attachment,
SortTextDesc,
SortLinesDesc,
Certificate,
OpenInNewWindow,
Blocked,
Message,
New,
IMAP,
Print,
Calendar,
CalendarToday,
CalendarDay,
CalendarWeek,
CalendarWorkWeek,
CalendarMonth,
CalendarYear,
WeatherBlow,
WeatherCloudy,
WeatherSunny,
WeatherRainy,
WeatherSnowy,
WeatherSnowShowerAtNight,
WeatherThunderstorm,
CalendarEventRepeat,
CalendarEventMuiltiDay,
CalendarError,
Reminder,
CalendarAttendee,
CalendarAttendees,
CalendarSync,
EventRespond,
EventAccept,
EventTentative,
EventDecline,
EventReminder,
EventEditSeries,
EventJoinOnline,
ViewMessageSource,
Apple,
Yahoo
get { return (WinoIconGlyph)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
public partial class WinoFontIcon : FontIcon
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(nameof(Icon), typeof(WinoIconGlyph), typeof(WinoFontIcon), new PropertyMetadata(WinoIconGlyph.Flag, OnIconChanged));
public WinoFontIcon()
{
public WinoIconGlyph Icon
{
get { return (WinoIconGlyph)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
FontFamily = new Windows.UI.Xaml.Media.FontFamily("ms-appx:///Wino.Core.UWP/Assets/WinoIcons.ttf#WinoIcons");
FontSize = 32;
}
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(nameof(Icon), typeof(WinoIconGlyph), typeof(WinoFontIcon), new PropertyMetadata(WinoIconGlyph.Flag, OnIconChanged));
public WinoFontIcon()
private static void OnIconChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoFontIcon fontIcon)
{
FontFamily = new Windows.UI.Xaml.Media.FontFamily("ms-appx:///Wino.Core.UWP/Assets/WinoIcons.ttf#WinoIcons");
FontSize = 32;
fontIcon.UpdateGlyph();
}
}
private static void OnIconChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoFontIcon fontIcon)
{
fontIcon.UpdateGlyph();
}
}
private void UpdateGlyph()
{
Glyph = ControlConstants.WinoIconFontDictionary[Icon];
}
private void UpdateGlyph()
{
Glyph = ControlConstants.WinoIconFontDictionary[Icon];
}
}

View File

@@ -1,35 +1,34 @@
using Windows.UI.Xaml;
using Wino.Core.UWP.Controls;
namespace Wino.Controls
namespace Wino.Controls;
public partial class WinoFontIconSource : Microsoft.UI.Xaml.Controls.FontIconSource
{
public partial class WinoFontIconSource : Microsoft.UI.Xaml.Controls.FontIconSource
public WinoIconGlyph Icon
{
public WinoIconGlyph Icon
{
get { return (WinoIconGlyph)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
get { return (WinoIconGlyph)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(nameof(Icon), typeof(WinoIconGlyph), typeof(WinoFontIconSource), new PropertyMetadata(WinoIconGlyph.Flag, OnIconChanged));
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(nameof(Icon), typeof(WinoIconGlyph), typeof(WinoFontIconSource), new PropertyMetadata(WinoIconGlyph.Flag, OnIconChanged));
public WinoFontIconSource()
{
FontFamily = new Windows.UI.Xaml.Media.FontFamily("ms-appx:///Assets/WinoIcons.ttf#WinoIcons");
FontSize = 32;
}
public WinoFontIconSource()
{
FontFamily = new Windows.UI.Xaml.Media.FontFamily("ms-appx:///Assets/WinoIcons.ttf#WinoIcons");
FontSize = 32;
}
private static void OnIconChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
private static void OnIconChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoFontIconSource fontIcon)
{
if (obj is WinoFontIconSource fontIcon)
{
fontIcon.UpdateGlyph();
}
}
private void UpdateGlyph()
{
Glyph = ControlConstants.WinoIconFontDictionary[Icon];
fontIcon.UpdateGlyph();
}
}
private void UpdateGlyph()
{
Glyph = ControlConstants.WinoIconFontDictionary[Icon];
}
}

View File

@@ -5,87 +5,86 @@ using Microsoft.UI.Xaml.Controls;
using Windows.UI.Xaml;
using Wino.Core.Domain.Enums;
namespace Wino.Core.UWP.Controls
namespace Wino.Core.UWP.Controls;
public partial class WinoInfoBar : InfoBar
{
public partial class WinoInfoBar : InfoBar
public static readonly DependencyProperty AnimationTypeProperty = DependencyProperty.Register(nameof(AnimationType), typeof(InfoBarAnimationType), typeof(WinoInfoBar), new PropertyMetadata(InfoBarAnimationType.SlideFromRightToLeft));
public static readonly DependencyProperty DismissIntervalProperty = DependencyProperty.Register(nameof(DismissInterval), typeof(int), typeof(WinoInfoBar), new PropertyMetadata(5, new PropertyChangedCallback(OnDismissIntervalChanged)));
public InfoBarAnimationType AnimationType
{
public static readonly DependencyProperty AnimationTypeProperty = DependencyProperty.Register(nameof(AnimationType), typeof(InfoBarAnimationType), typeof(WinoInfoBar), new PropertyMetadata(InfoBarAnimationType.SlideFromRightToLeft));
public static readonly DependencyProperty DismissIntervalProperty = DependencyProperty.Register(nameof(DismissInterval), typeof(int), typeof(WinoInfoBar), new PropertyMetadata(5, new PropertyChangedCallback(OnDismissIntervalChanged)));
get { return (InfoBarAnimationType)GetValue(AnimationTypeProperty); }
set { SetValue(AnimationTypeProperty, value); }
}
public InfoBarAnimationType AnimationType
public int DismissInterval
{
get { return (int)GetValue(DismissIntervalProperty); }
set { SetValue(DismissIntervalProperty, value); }
}
private readonly DispatcherTimer _dispatcherTimer = new DispatcherTimer();
public WinoInfoBar()
{
RegisterPropertyChangedCallback(IsOpenProperty, IsOpenChanged);
_dispatcherTimer.Interval = TimeSpan.FromSeconds(DismissInterval);
}
private static void OnDismissIntervalChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoInfoBar bar)
{
get { return (InfoBarAnimationType)GetValue(AnimationTypeProperty); }
set { SetValue(AnimationTypeProperty, value); }
bar.UpdateInterval(bar.DismissInterval);
}
}
public int DismissInterval
private void UpdateInterval(int seconds) => _dispatcherTimer.Interval = TimeSpan.FromSeconds(seconds);
private async void IsOpenChanged(DependencyObject sender, DependencyProperty dp)
{
if (sender is WinoInfoBar control)
{
get { return (int)GetValue(DismissIntervalProperty); }
set { SetValue(DismissIntervalProperty, value); }
}
// Message shown.
if (!control.IsOpen) return;
private readonly DispatcherTimer _dispatcherTimer = new DispatcherTimer();
Opacity = 1;
public WinoInfoBar()
{
RegisterPropertyChangedCallback(IsOpenProperty, IsOpenChanged);
_dispatcherTimer.Interval = TimeSpan.FromSeconds(DismissInterval);
}
private static void OnDismissIntervalChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoInfoBar bar)
{
bar.UpdateInterval(bar.DismissInterval);
}
}
private void UpdateInterval(int seconds) => _dispatcherTimer.Interval = TimeSpan.FromSeconds(seconds);
private async void IsOpenChanged(DependencyObject sender, DependencyProperty dp)
{
if (sender is WinoInfoBar control)
{
// Message shown.
if (!control.IsOpen) return;
Opacity = 1;
_dispatcherTimer.Stop();
_dispatcherTimer.Tick -= TimerTick;
_dispatcherTimer.Tick += TimerTick;
_dispatcherTimer.Start();
// Slide from right.
if (AnimationType == InfoBarAnimationType.SlideFromRightToLeft)
{
await AnimationBuilder.Create().Translation(new Vector3(0, 0, 0), new Vector3(150, 0, 0), null, TimeSpan.FromSeconds(0.5)).StartAsync(this);
}
else if (AnimationType == InfoBarAnimationType.SlideFromBottomToTop)
{
await AnimationBuilder.Create().Translation(new Vector3(0, 0, 0), new Vector3(0, 50, 0), null, TimeSpan.FromSeconds(0.5)).StartAsync(this);
}
}
}
private async void TimerTick(object sender, object e)
{
_dispatcherTimer.Stop();
_dispatcherTimer.Tick -= TimerTick;
_dispatcherTimer.Tick -= TimerTick;
_dispatcherTimer.Tick += TimerTick;
_dispatcherTimer.Start();
// Slide from right.
if (AnimationType == InfoBarAnimationType.SlideFromRightToLeft)
{
await AnimationBuilder.Create().Translation(new Vector3((float)ActualWidth, 0, 0), new Vector3(0, 0, 0), null, TimeSpan.FromSeconds(0.5)).StartAsync(this);
await AnimationBuilder.Create().Translation(new Vector3(0, 0, 0), new Vector3(150, 0, 0), null, TimeSpan.FromSeconds(0.5)).StartAsync(this);
}
else if (AnimationType == InfoBarAnimationType.SlideFromBottomToTop)
{
await AnimationBuilder.Create().Translation(new Vector3(0, (float)ActualHeight, 0), new Vector3(0, 0, 0), null, TimeSpan.FromSeconds(0.5)).StartAsync(this);
await AnimationBuilder.Create().Translation(new Vector3(0, 0, 0), new Vector3(0, 50, 0), null, TimeSpan.FromSeconds(0.5)).StartAsync(this);
}
IsOpen = false;
}
}
private async void TimerTick(object sender, object e)
{
_dispatcherTimer.Stop();
_dispatcherTimer.Tick -= TimerTick;
if (AnimationType == InfoBarAnimationType.SlideFromRightToLeft)
{
await AnimationBuilder.Create().Translation(new Vector3((float)ActualWidth, 0, 0), new Vector3(0, 0, 0), null, TimeSpan.FromSeconds(0.5)).StartAsync(this);
}
else if (AnimationType == InfoBarAnimationType.SlideFromBottomToTop)
{
await AnimationBuilder.Create().Translation(new Vector3(0, (float)ActualHeight, 0), new Vector3(0, 0, 0), null, TimeSpan.FromSeconds(0.5)).StartAsync(this);
}
IsOpen = false;
}
}

View File

@@ -3,45 +3,44 @@ using Microsoft.UI.Xaml.Controls;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Hosting;
namespace Wino.Core.UWP.Controls
namespace Wino.Core.UWP.Controls;
public partial class WinoNavigationViewItem : NavigationViewItem
{
public partial class WinoNavigationViewItem : NavigationViewItem
public bool IsDraggingItemOver
{
public bool IsDraggingItemOver
get { return (bool)GetValue(IsDraggingItemOverProperty); }
set { SetValue(IsDraggingItemOverProperty, value); }
}
public static readonly DependencyProperty IsDraggingItemOverProperty = DependencyProperty.Register(nameof(IsDraggingItemOver), typeof(bool), typeof(WinoNavigationViewItem), new PropertyMetadata(false, OnIsDraggingItemOverChanged));
private static void OnIsDraggingItemOverChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoNavigationViewItem control)
control.UpdateDragEnterState();
}
private void UpdateDragEnterState()
{
// TODO: Add animation. Maybe after overriding DragUI in shell?
//if (IsDraggingItemOver)
//{
// ScaleAnimation(new System.Numerics.Vector3(1.2f, 1.2f, 1.2f));
//}
//else
//{
// ScaleAnimation(new System.Numerics.Vector3(1f, 1f, 1f));
//}
}
private void ScaleAnimation(Vector3 vector)
{
if (Content is UIElement content)
{
get { return (bool)GetValue(IsDraggingItemOverProperty); }
set { SetValue(IsDraggingItemOverProperty, value); }
}
public static readonly DependencyProperty IsDraggingItemOverProperty = DependencyProperty.Register(nameof(IsDraggingItemOver), typeof(bool), typeof(WinoNavigationViewItem), new PropertyMetadata(false, OnIsDraggingItemOverChanged));
private static void OnIsDraggingItemOverChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoNavigationViewItem control)
control.UpdateDragEnterState();
}
private void UpdateDragEnterState()
{
// TODO: Add animation. Maybe after overriding DragUI in shell?
//if (IsDraggingItemOver)
//{
// ScaleAnimation(new System.Numerics.Vector3(1.2f, 1.2f, 1.2f));
//}
//else
//{
// ScaleAnimation(new System.Numerics.Vector3(1f, 1f, 1f));
//}
}
private void ScaleAnimation(Vector3 vector)
{
if (Content is UIElement content)
{
var visual = ElementCompositionPreview.GetElementVisual(content);
visual.Scale = vector;
}
var visual = ElementCompositionPreview.GetElementVisual(content);
visual.Scale = vector;
}
}
}

View File

@@ -2,26 +2,25 @@ using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
namespace Wino.Converters
{
public partial class GridLengthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is double doubleValue)
{
return new GridLength(doubleValue);
}
return new GridLength(1, GridUnitType.Auto);
}
namespace Wino.Converters;
public object ConvertBack(object value, Type targetType, object parameter, string language)
public partial class GridLengthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is double doubleValue)
{
if (value is GridLength gridLength)
{
return gridLength.Value;
}
return 0.0;
return new GridLength(doubleValue);
}
return new GridLength(1, GridUnitType.Auto);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is GridLength gridLength)
{
return gridLength.Value;
}
return 0.0;
}
}

View File

@@ -1,21 +1,20 @@
using System;
using Windows.UI.Xaml.Data;
namespace Wino.Converters
namespace Wino.Converters;
public partial class ReverseBooleanConverter : IValueConverter
{
public partial class ReverseBooleanConverter : IValueConverter
public object Convert(object value, Type targetType, object parameter, string language)
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool boolval)
return !boolval;
if (value is bool boolval)
return !boolval;
return false;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}

View File

@@ -2,18 +2,17 @@
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
namespace Wino.Converters
{
public partial class ReverseBooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return ((bool)value) ? Visibility.Collapsed : Visibility.Visible;
}
namespace Wino.Converters;
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
public partial class ReverseBooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return ((bool)value) ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}

View File

@@ -1,9 +1,8 @@
using Windows.UI.Xaml;
namespace Wino.Core.UWP
namespace Wino.Core.UWP;
public partial class CoreGeneric : ResourceDictionary
{
public partial class CoreGeneric : ResourceDictionary
{
public CoreGeneric() => InitializeComponent();
}
public CoreGeneric() => InitializeComponent();
}

View File

@@ -6,46 +6,45 @@ using Wino.Core.UWP.Services;
using Wino.Core.ViewModels;
using Wino.Services;
namespace Wino.Core.UWP
namespace Wino.Core.UWP;
public static class CoreUWPContainerSetup
{
public static class CoreUWPContainerSetup
public static void RegisterCoreUWPServices(this IServiceCollection services)
{
public static void RegisterCoreUWPServices(this IServiceCollection services)
{
var serverConnectionManager = new WinoServerConnectionManager();
var serverConnectionManager = new WinoServerConnectionManager();
services.AddSingleton<IWinoServerConnectionManager>(serverConnectionManager);
services.AddSingleton<IWinoServerConnectionManager<AppServiceConnection>>(serverConnectionManager);
services.AddSingleton<IApplicationResourceManager<ResourceDictionary>, ApplicationResourceManager>();
services.AddSingleton<IWinoServerConnectionManager>(serverConnectionManager);
services.AddSingleton<IWinoServerConnectionManager<AppServiceConnection>>(serverConnectionManager);
services.AddSingleton<IApplicationResourceManager<ResourceDictionary>, ApplicationResourceManager>();
services.AddSingleton<IUnderlyingThemeService, UnderlyingThemeService>();
services.AddSingleton<INativeAppService, NativeAppService>();
services.AddSingleton<IStoreManagementService, StoreManagementService>();
services.AddSingleton<IBackgroundTaskService, BackgroundTaskService>();
services.AddSingleton<IPreferencesService, PreferencesService>();
services.AddSingleton<IThemeService, ThemeService>();
services.AddSingleton<IStatePersistanceService, StatePersistenceService>();
services.AddSingleton<IUnderlyingThemeService, UnderlyingThemeService>();
services.AddSingleton<INativeAppService, NativeAppService>();
services.AddSingleton<IStoreManagementService, StoreManagementService>();
services.AddSingleton<IBackgroundTaskService, BackgroundTaskService>();
services.AddSingleton<IPreferencesService, PreferencesService>();
services.AddSingleton<IThemeService, ThemeService>();
services.AddSingleton<IStatePersistanceService, StatePersistenceService>();
services.AddSingleton<IDialogServiceBase, DialogServiceBase>();
services.AddTransient<IConfigurationService, ConfigurationService>();
services.AddTransient<IFileService, FileService>();
services.AddTransient<IStoreRatingService, StoreRatingService>();
services.AddTransient<IKeyPressService, KeyPressService>();
services.AddTransient<INotificationBuilder, NotificationBuilder>();
services.AddTransient<IClipboardService, ClipboardService>();
services.AddTransient<IStartupBehaviorService, StartupBehaviorService>();
services.AddSingleton<IPrintService, PrintService>();
services.AddSingleton<IDialogServiceBase, DialogServiceBase>();
services.AddTransient<IConfigurationService, ConfigurationService>();
services.AddTransient<IFileService, FileService>();
services.AddTransient<IStoreRatingService, StoreRatingService>();
services.AddTransient<IKeyPressService, KeyPressService>();
services.AddTransient<INotificationBuilder, NotificationBuilder>();
services.AddTransient<IClipboardService, ClipboardService>();
services.AddTransient<IStartupBehaviorService, StartupBehaviorService>();
services.AddSingleton<IPrintService, PrintService>();
}
}
public static void RegisterCoreViewModels(this IServiceCollection services)
{
services.AddTransient(typeof(SettingsDialogViewModel));
services.AddTransient(typeof(PersonalizationPageViewModel));
services.AddTransient(typeof(SettingOptionsPageViewModel));
services.AddTransient(typeof(AboutPageViewModel));
services.AddTransient(typeof(SettingsPageViewModel));
services.AddTransient(typeof(ManageAccountsPagePageViewModel));
}
public static void RegisterCoreViewModels(this IServiceCollection services)
{
services.AddTransient(typeof(SettingsDialogViewModel));
services.AddTransient(typeof(PersonalizationPageViewModel));
services.AddTransient(typeof(SettingOptionsPageViewModel));
services.AddTransient(typeof(AboutPageViewModel));
services.AddTransient(typeof(SettingsPageViewModel));
services.AddTransient(typeof(ManageAccountsPagePageViewModel));
}
}

View File

@@ -5,67 +5,66 @@ using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
namespace Wino.Dialogs
namespace Wino.Dialogs;
public sealed partial class AccountCreationDialog : ContentDialog, IAccountCreationDialog
{
public sealed partial class AccountCreationDialog : ContentDialog, IAccountCreationDialog
private TaskCompletionSource<bool> dialogOpened = new TaskCompletionSource<bool>();
public CancellationTokenSource CancellationTokenSource { get; private set; }
public AccountCreationDialogState State
{
private TaskCompletionSource<bool> dialogOpened = new TaskCompletionSource<bool>();
public CancellationTokenSource CancellationTokenSource { get; private set; }
get { return (AccountCreationDialogState)GetValue(StateProperty); }
set { SetValue(StateProperty, value); }
}
public AccountCreationDialogState State
public static readonly DependencyProperty StateProperty = DependencyProperty.Register(nameof(State), typeof(AccountCreationDialogState), typeof(AccountCreationDialog), new PropertyMetadata(AccountCreationDialogState.Idle));
public AccountCreationDialog()
{
InitializeComponent();
}
// Prevent users from dismissing it by ESC key.
public void DialogClosing(ContentDialog sender, ContentDialogClosingEventArgs args)
{
if (args.Result == ContentDialogResult.None)
{
get { return (AccountCreationDialogState)GetValue(StateProperty); }
set { SetValue(StateProperty, value); }
}
public static readonly DependencyProperty StateProperty = DependencyProperty.Register(nameof(State), typeof(AccountCreationDialogState), typeof(AccountCreationDialog), new PropertyMetadata(AccountCreationDialogState.Idle));
public AccountCreationDialog()
{
InitializeComponent();
}
// Prevent users from dismissing it by ESC key.
public void DialogClosing(ContentDialog sender, ContentDialogClosingEventArgs args)
{
if (args.Result == ContentDialogResult.None)
{
args.Cancel = true;
}
}
public void Complete(bool cancel)
{
State = cancel ? AccountCreationDialogState.Canceled : AccountCreationDialogState.Completed;
// Unregister from closing event.
Closing -= DialogClosing;
if (cancel && !CancellationTokenSource.IsCancellationRequested)
{
CancellationTokenSource.Cancel();
}
Hide();
}
private void CancelClicked(object sender, System.EventArgs e) => Complete(true);
public async Task ShowDialogAsync(CancellationTokenSource cancellationTokenSource)
{
CancellationTokenSource = cancellationTokenSource;
Opened += DialogOpened;
_ = ShowAsync();
await dialogOpened.Task;
}
private void DialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args)
{
Opened -= DialogOpened;
dialogOpened?.SetResult(true);
args.Cancel = true;
}
}
public void Complete(bool cancel)
{
State = cancel ? AccountCreationDialogState.Canceled : AccountCreationDialogState.Completed;
// Unregister from closing event.
Closing -= DialogClosing;
if (cancel && !CancellationTokenSource.IsCancellationRequested)
{
CancellationTokenSource.Cancel();
}
Hide();
}
private void CancelClicked(object sender, System.EventArgs e) => Complete(true);
public async Task ShowDialogAsync(CancellationTokenSource cancellationTokenSource)
{
CancellationTokenSource = cancellationTokenSource;
Opened += DialogOpened;
_ = ShowAsync();
await dialogOpened.Task;
}
private void DialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args)
{
Opened -= DialogOpened;
dialogOpened?.SetResult(true);
}
}

View File

@@ -1,22 +1,21 @@
using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Entities.Shared;
namespace Wino.Dialogs
namespace Wino.Dialogs;
public sealed partial class AccountEditDialog : ContentDialog
{
public sealed partial class AccountEditDialog : ContentDialog
public MailAccount Account { get; private set; }
public bool IsSaved { get; set; }
public AccountEditDialog(MailAccount account)
{
public MailAccount Account { get; private set; }
public bool IsSaved { get; set; }
InitializeComponent();
Account = account;
}
public AccountEditDialog(MailAccount account)
{
InitializeComponent();
Account = account;
}
private void SaveClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
IsSaved = true;
}
private void SaveClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
IsSaved = true;
}
}

View File

@@ -2,26 +2,25 @@
using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Entities.Shared;
namespace Wino.Dialogs
namespace Wino.Dialogs;
public sealed partial class AccountPickerDialog : ContentDialog
{
public sealed partial class AccountPickerDialog : ContentDialog
public MailAccount PickedAccount { get; set; }
public List<MailAccount> AvailableAccounts { get; set; }
public AccountPickerDialog(List<MailAccount> availableAccounts)
{
public MailAccount PickedAccount { get; set; }
AvailableAccounts = availableAccounts;
public List<MailAccount> AvailableAccounts { get; set; }
InitializeComponent();
}
public AccountPickerDialog(List<MailAccount> availableAccounts)
{
AvailableAccounts = availableAccounts;
private void AccountClicked(object sender, ItemClickEventArgs e)
{
PickedAccount = e.ClickedItem as MailAccount;
InitializeComponent();
}
private void AccountClicked(object sender, ItemClickEventArgs e)
{
PickedAccount = e.ClickedItem as MailAccount;
Hide();
}
Hide();
}
}

View File

@@ -1,24 +1,23 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Wino.Core.Domain.Enums;
namespace Wino.Dialogs
namespace Wino.Dialogs;
public partial class CustomMessageDialogInformationContainer : ObservableObject
{
public partial class CustomMessageDialogInformationContainer : ObservableObject
[ObservableProperty]
public partial bool IsDontAskChecked { get; set; }
public CustomMessageDialogInformationContainer(string title, string description, WinoCustomMessageDialogIcon icon, bool isDontAskAgainEnabled)
{
[ObservableProperty]
public partial bool IsDontAskChecked { get; set; }
public CustomMessageDialogInformationContainer(string title, string description, WinoCustomMessageDialogIcon icon, bool isDontAskAgainEnabled)
{
Title = title;
Description = description;
Icon = icon;
IsDontAskAgainEnabled = isDontAskAgainEnabled;
}
public string Title { get; }
public string Description { get; }
public WinoCustomMessageDialogIcon Icon { get; }
public bool IsDontAskAgainEnabled { get; }
Title = title;
Description = description;
Icon = icon;
IsDontAskAgainEnabled = isDontAskAgainEnabled;
}
public string Title { get; }
public string Description { get; }
public WinoCustomMessageDialogIcon Icon { get; }
public bool IsDontAskAgainEnabled { get; }
}

View File

@@ -6,60 +6,59 @@ using Windows.UI.Xaml.Media;
using Wino.Core.Domain.Interfaces;
using Wino.Core.UWP;
namespace Wino.Dialogs
namespace Wino.Dialogs;
public sealed partial class CustomThemeBuilderDialog : ContentDialog
{
public sealed partial class CustomThemeBuilderDialog : ContentDialog
public byte[] WallpaperData { get; private set; }
public string AccentColor { get; private set; }
private IThemeService _themeService;
public CustomThemeBuilderDialog()
{
public byte[] WallpaperData { get; private set; }
public string AccentColor { get; private set; }
InitializeComponent();
private IThemeService _themeService;
_themeService = WinoApplication.Current.Services.GetService<IThemeService>();
}
public CustomThemeBuilderDialog()
private async void ApplyClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
if (Array.Empty<byte>() == WallpaperData)
return;
var deferal = args.GetDeferral();
try
{
InitializeComponent();
_themeService = WinoApplication.Current.Services.GetService<IThemeService>();
await _themeService.CreateNewCustomThemeAsync(ThemeNameBox.Text, AccentColor, WallpaperData);
}
private async void ApplyClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
catch (Exception exception)
{
if (Array.Empty<byte>() == WallpaperData)
return;
var deferal = args.GetDeferral();
try
{
await _themeService.CreateNewCustomThemeAsync(ThemeNameBox.Text, AccentColor, WallpaperData);
}
catch (Exception exception)
{
ErrorTextBlock.Text = exception.Message;
}
finally
{
deferal.Complete();
}
ErrorTextBlock.Text = exception.Message;
}
private async void BrowseWallpaperClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
finally
{
var dialogService = WinoApplication.Current.Services.GetService<IMailDialogService>();
var pickedFileData = await dialogService.PickWindowsFileContentAsync(".jpg", ".png");
if (pickedFileData == Array.Empty<byte>()) return;
IsPrimaryButtonEnabled = true;
WallpaperData = pickedFileData;
}
private void PickerColorChanged(Microsoft.UI.Xaml.Controls.ColorPicker sender, Microsoft.UI.Xaml.Controls.ColorChangedEventArgs args)
{
PreviewAccentColorGrid.Background = new SolidColorBrush(args.NewColor);
AccentColor = args.NewColor.ToHex();
deferal.Complete();
}
}
private async void BrowseWallpaperClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
var dialogService = WinoApplication.Current.Services.GetService<IMailDialogService>();
var pickedFileData = await dialogService.PickWindowsFileContentAsync(".jpg", ".png");
if (pickedFileData == Array.Empty<byte>()) return;
IsPrimaryButtonEnabled = true;
WallpaperData = pickedFileData;
}
private void PickerColorChanged(Microsoft.UI.Xaml.Controls.ColorPicker sender, Microsoft.UI.Xaml.Controls.ColorChangedEventArgs args)
{
PreviewAccentColorGrid.Background = new SolidColorBrush(args.NewColor);
AccentColor = args.NewColor.ToHex();
}
}

View File

@@ -7,146 +7,145 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Accounts;
namespace Wino.Core.UWP.Dialogs
namespace Wino.Core.UWP.Dialogs;
public sealed partial class NewAccountDialog : ContentDialog
{
public sealed partial class NewAccountDialog : ContentDialog
private Dictionary<SpecialImapProvider, string> helpingLinks = new Dictionary<SpecialImapProvider, string>()
{
private Dictionary<SpecialImapProvider, string> helpingLinks = new Dictionary<SpecialImapProvider, string>()
{ SpecialImapProvider.iCloud, "https://support.apple.com/en-us/102654" },
{ SpecialImapProvider.Yahoo, "http://help.yahoo.com/kb/SLN15241.html" },
};
public static readonly DependencyProperty IsProviderSelectionVisibleProperty = DependencyProperty.Register(nameof(IsProviderSelectionVisible), typeof(bool), typeof(NewAccountDialog), new PropertyMetadata(true));
public static readonly DependencyProperty IsSpecialImapServerPartVisibleProperty = DependencyProperty.Register(nameof(IsSpecialImapServerPartVisible), typeof(bool), typeof(NewAccountDialog), new PropertyMetadata(false));
public static readonly DependencyProperty SelectedMailProviderProperty = DependencyProperty.Register(nameof(SelectedMailProvider), typeof(ProviderDetail), typeof(NewAccountDialog), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedProviderChanged)));
/// <summary>
/// Gets or sets current selected mail provider in the dialog.
/// </summary>
public ProviderDetail SelectedMailProvider
{
get { return (ProviderDetail)GetValue(SelectedMailProviderProperty); }
set { SetValue(SelectedMailProviderProperty, value); }
}
public bool IsProviderSelectionVisible
{
get { return (bool)GetValue(IsProviderSelectionVisibleProperty); }
set { SetValue(IsProviderSelectionVisibleProperty, value); }
}
public bool IsSpecialImapServerPartVisible
{
get { return (bool)GetValue(IsSpecialImapServerPartVisibleProperty); }
set { SetValue(IsSpecialImapServerPartVisibleProperty, value); }
}
// List of available mail providers for now.
public List<IProviderDetail> Providers { get; set; }
public AccountCreationDialogResult Result = null;
public NewAccountDialog()
{
InitializeComponent();
// AccountColorPicker.Color = Colors.Blue;
}
private static void OnSelectedProviderChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is NewAccountDialog dialog)
dialog.Validate();
}
private void CancelClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
Hide();
}
private void CreateClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
if (IsSpecialImapServerPartVisible)
{
{ SpecialImapProvider.iCloud, "https://support.apple.com/en-us/102654" },
{ SpecialImapProvider.Yahoo, "http://help.yahoo.com/kb/SLN15241.html" },
};
// Special imap detail input.
public static readonly DependencyProperty IsProviderSelectionVisibleProperty = DependencyProperty.Register(nameof(IsProviderSelectionVisible), typeof(bool), typeof(NewAccountDialog), new PropertyMetadata(true));
public static readonly DependencyProperty IsSpecialImapServerPartVisibleProperty = DependencyProperty.Register(nameof(IsSpecialImapServerPartVisible), typeof(bool), typeof(NewAccountDialog), new PropertyMetadata(false));
public static readonly DependencyProperty SelectedMailProviderProperty = DependencyProperty.Register(nameof(SelectedMailProvider), typeof(ProviderDetail), typeof(NewAccountDialog), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedProviderChanged)));
/// <summary>
/// Gets or sets current selected mail provider in the dialog.
/// </summary>
public ProviderDetail SelectedMailProvider
{
get { return (ProviderDetail)GetValue(SelectedMailProviderProperty); }
set { SetValue(SelectedMailProviderProperty, value); }
}
public bool IsProviderSelectionVisible
{
get { return (bool)GetValue(IsProviderSelectionVisibleProperty); }
set { SetValue(IsProviderSelectionVisibleProperty, value); }
}
public bool IsSpecialImapServerPartVisible
{
get { return (bool)GetValue(IsSpecialImapServerPartVisibleProperty); }
set { SetValue(IsSpecialImapServerPartVisibleProperty, value); }
}
// List of available mail providers for now.
public List<IProviderDetail> Providers { get; set; }
public AccountCreationDialogResult Result = null;
public NewAccountDialog()
{
InitializeComponent();
// AccountColorPicker.Color = Colors.Blue;
}
private static void OnSelectedProviderChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is NewAccountDialog dialog)
dialog.Validate();
}
private void CancelClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
var details = new SpecialImapProviderDetails(SpecialImapAddress.Text.Trim(), AppSpecificPassword.Password.Trim(), DisplayNameTextBox.Text.Trim(), SelectedMailProvider.SpecialImapProvider);
Result = new AccountCreationDialogResult(SelectedMailProvider.Type, AccountNameTextbox.Text.Trim(), details);
Hide();
return;
}
private void CreateClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
if (IsSpecialImapServerPartVisible)
{
// Special imap detail input.
Validate();
var details = new SpecialImapProviderDetails(SpecialImapAddress.Text.Trim(), AppSpecificPassword.Password.Trim(), DisplayNameTextBox.Text.Trim(), SelectedMailProvider.SpecialImapProvider);
Result = new AccountCreationDialogResult(SelectedMailProvider.Type, AccountNameTextbox.Text.Trim(), details);
if (IsSecondaryButtonEnabled)
{
if (SelectedMailProvider.SpecialImapProvider != SpecialImapProvider.None)
{
// This step requires app-sepcific password login for some providers.
args.Cancel = true;
IsProviderSelectionVisible = false;
IsSpecialImapServerPartVisible = true;
Validate();
}
else
{
Result = new AccountCreationDialogResult(SelectedMailProvider.Type, AccountNameTextbox.Text.Trim(), null);
Hide();
return;
}
Validate();
if (IsSecondaryButtonEnabled)
{
if (SelectedMailProvider.SpecialImapProvider != SpecialImapProvider.None)
{
// This step requires app-sepcific password login for some providers.
args.Cancel = true;
IsProviderSelectionVisible = false;
IsSpecialImapServerPartVisible = true;
Validate();
}
else
{
Result = new AccountCreationDialogResult(SelectedMailProvider.Type, AccountNameTextbox.Text.Trim(), null);
Hide();
}
}
}
private void InputChanged(object sender, TextChangedEventArgs e) => Validate();
private void SenderNameChanged(object sender, TextChangedEventArgs e) => Validate();
private void Validate()
{
ValidateCreateButton();
ValidateNames();
}
// Returns whether we can create account or not.
private void ValidateCreateButton()
{
bool shouldEnable = SelectedMailProvider != null
&& SelectedMailProvider.IsSupported
&& !string.IsNullOrEmpty(AccountNameTextbox.Text)
&& (IsSpecialImapServerPartVisible ? (!string.IsNullOrEmpty(AppSpecificPassword.Password)
&& !string.IsNullOrEmpty(DisplayNameTextBox.Text)
&& EmailValidation.EmailValidator.Validate(SpecialImapAddress.Text)) : true);
IsPrimaryButtonEnabled = shouldEnable;
}
private void ValidateNames()
{
AccountNameTextbox.IsEnabled = SelectedMailProvider != null;
}
private void DialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args) => Validate();
private void BackClicked(object sender, RoutedEventArgs e)
{
IsSpecialImapServerPartVisible = false;
IsProviderSelectionVisible = true;
Validate();
}
private void ImapPasswordChanged(object sender, RoutedEventArgs e) => Validate();
private async void AppSpecificHelpButtonClicked(object sender, RoutedEventArgs e)
{
var helpUrl = helpingLinks[SelectedMailProvider.SpecialImapProvider];
await Launcher.LaunchUriAsync(new Uri(helpUrl));
}
}
private void InputChanged(object sender, TextChangedEventArgs e) => Validate();
private void SenderNameChanged(object sender, TextChangedEventArgs e) => Validate();
private void Validate()
{
ValidateCreateButton();
ValidateNames();
}
// Returns whether we can create account or not.
private void ValidateCreateButton()
{
bool shouldEnable = SelectedMailProvider != null
&& SelectedMailProvider.IsSupported
&& !string.IsNullOrEmpty(AccountNameTextbox.Text)
&& (IsSpecialImapServerPartVisible ? (!string.IsNullOrEmpty(AppSpecificPassword.Password)
&& !string.IsNullOrEmpty(DisplayNameTextBox.Text)
&& EmailValidation.EmailValidator.Validate(SpecialImapAddress.Text)) : true);
IsPrimaryButtonEnabled = shouldEnable;
}
private void ValidateNames()
{
AccountNameTextbox.IsEnabled = SelectedMailProvider != null;
}
private void DialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args) => Validate();
private void BackClicked(object sender, RoutedEventArgs e)
{
IsSpecialImapServerPartVisible = false;
IsProviderSelectionVisible = true;
Validate();
}
private void ImapPasswordChanged(object sender, RoutedEventArgs e) => Validate();
private async void AppSpecificHelpButtonClicked(object sender, RoutedEventArgs e)
{
var helpUrl = helpingLinks[SelectedMailProvider.SpecialImapProvider];
await Launcher.LaunchUriAsync(new Uri(helpUrl));
}
}

View File

@@ -1,45 +1,44 @@
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace Wino.Dialogs
namespace Wino.Dialogs;
public sealed partial class TextInputDialog : ContentDialog
{
public sealed partial class TextInputDialog : ContentDialog
public bool? HasInput { get; set; }
public string CurrentInput
{
public bool? HasInput { get; set; }
get { return (string)GetValue(CurrentInputProperty); }
set { SetValue(CurrentInputProperty, value); }
}
public string CurrentInput
{
get { return (string)GetValue(CurrentInputProperty); }
set { SetValue(CurrentInputProperty, value); }
}
public static readonly DependencyProperty CurrentInputProperty = DependencyProperty.Register(nameof(CurrentInput), typeof(string), typeof(TextInputDialog), new PropertyMetadata(string.Empty));
public static readonly DependencyProperty CurrentInputProperty = DependencyProperty.Register(nameof(CurrentInput), typeof(string), typeof(TextInputDialog), new PropertyMetadata(string.Empty));
public TextInputDialog()
{
InitializeComponent();
}
public TextInputDialog()
{
InitializeComponent();
}
public void SetDescription(string description)
{
DialogDescription.Text = description;
}
public void SetDescription(string description)
{
DialogDescription.Text = description;
}
public void SetPrimaryButtonText(string text)
{
PrimaryButtonText = text;
}
public void SetPrimaryButtonText(string text)
{
PrimaryButtonText = text;
}
private void CancelClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
Hide();
}
private void CancelClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
Hide();
}
private void UpdateOrCreateClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
HasInput = true;
private void UpdateOrCreateClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
HasInput = true;
Hide();
}
Hide();
}
}

View File

@@ -3,18 +3,17 @@ using System.Threading.Tasks;
using Windows.UI.Core;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.UWP
namespace Wino.Core.UWP;
public class UWPDispatcher : IDispatcher
{
public class UWPDispatcher : IDispatcher
private readonly CoreDispatcher _coreDispatcher;
public UWPDispatcher(CoreDispatcher coreDispatcher)
{
private readonly CoreDispatcher _coreDispatcher;
public UWPDispatcher(CoreDispatcher coreDispatcher)
{
_coreDispatcher = coreDispatcher;
}
public Task ExecuteOnUIThread(Action action)
=> _coreDispatcher.RunAsync(CoreDispatcherPriority.Normal, () => action()).AsTask();
_coreDispatcher = coreDispatcher;
}
public Task ExecuteOnUIThread(Action action)
=> _coreDispatcher.RunAsync(CoreDispatcherPriority.Normal, () => action()).AsTask();
}

View File

@@ -8,109 +8,108 @@ using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media.Animation;
namespace Wino.Extensions
namespace Wino.Extensions;
public static class AnimationExtensions
{
public static class AnimationExtensions
#region Composition
public static ScalarKeyFrameAnimation CreateScalarKeyFrameAnimation(this Compositor compositor, float? from, float to,
double duration, double delay, CompositionEasingFunction easing, AnimationIterationBehavior iterationBehavior)
{
#region Composition
var animation = compositor.CreateScalarKeyFrameAnimation();
public static ScalarKeyFrameAnimation CreateScalarKeyFrameAnimation(this Compositor compositor, float? from, float to,
double duration, double delay, CompositionEasingFunction easing, AnimationIterationBehavior iterationBehavior)
{
var animation = compositor.CreateScalarKeyFrameAnimation();
animation.Duration = TimeSpan.FromMilliseconds(duration);
if (!delay.Equals(0)) animation.DelayTime = TimeSpan.FromMilliseconds(delay);
if (from.HasValue) animation.InsertKeyFrame(0.0f, from.Value, easing);
animation.InsertKeyFrame(1.0f, to, easing);
animation.IterationBehavior = iterationBehavior;
animation.Duration = TimeSpan.FromMilliseconds(duration);
if (!delay.Equals(0)) animation.DelayTime = TimeSpan.FromMilliseconds(delay);
if (from.HasValue) animation.InsertKeyFrame(0.0f, from.Value, easing);
animation.InsertKeyFrame(1.0f, to, easing);
animation.IterationBehavior = iterationBehavior;
return animation;
}
public static Vector2KeyFrameAnimation CreateVector2KeyFrameAnimation(this Compositor compositor, Vector2? from, Vector2 to,
double duration, double delay, CompositionEasingFunction easing, AnimationIterationBehavior iterationBehavior)
{
var animation = compositor.CreateVector2KeyFrameAnimation();
animation.Duration = TimeSpan.FromMilliseconds(duration);
animation.DelayTime = TimeSpan.FromMilliseconds(delay);
if (from.HasValue) animation.InsertKeyFrame(0.0f, from.Value, easing);
animation.InsertKeyFrame(1.0f, to, easing);
animation.IterationBehavior = iterationBehavior;
return animation;
}
public static Vector3KeyFrameAnimation CreateVector3KeyFrameAnimation(this Compositor compositor, Vector2? from, Vector2 to,
double duration, double delay, CompositionEasingFunction easing, AnimationIterationBehavior iterationBehavior)
{
var animation = compositor.CreateVector3KeyFrameAnimation();
animation.Duration = TimeSpan.FromMilliseconds(duration);
animation.DelayTime = TimeSpan.FromMilliseconds(delay);
if (from.HasValue) animation.InsertKeyFrame(0.0f, new Vector3(from.Value, 1.0f), easing);
animation.InsertKeyFrame(1.0f, new Vector3(to, 1.0f), easing);
animation.IterationBehavior = iterationBehavior;
return animation;
}
public static Vector3KeyFrameAnimation CreateVector3KeyFrameAnimation(this Compositor compositor, Vector3? from, Vector3 to,
double duration, double delay, CompositionEasingFunction easing, AnimationIterationBehavior iterationBehavior)
{
var animation = compositor.CreateVector3KeyFrameAnimation();
animation.Duration = TimeSpan.FromMilliseconds(duration);
animation.DelayTime = TimeSpan.FromMilliseconds(delay);
if (from.HasValue) animation.InsertKeyFrame(0.0f, from.Value, easing);
animation.InsertKeyFrame(1.0f, to, easing);
animation.IterationBehavior = iterationBehavior;
return animation;
}
#endregion
#region Xaml Storyboard
public static void Animate(this DependencyObject target, double? from, double to,
string propertyPath, int duration = 400, int startTime = 0,
EasingFunctionBase easing = null, Action completed = null, bool enableDependentAnimation = false)
{
if (easing == null)
{
easing = new ExponentialEase();
}
var db = new DoubleAnimation
{
EnableDependentAnimation = enableDependentAnimation,
To = to,
From = from,
EasingFunction = easing,
Duration = TimeSpan.FromMilliseconds(duration)
};
Storyboard.SetTarget(db, target);
Storyboard.SetTargetProperty(db, propertyPath);
var sb = new Storyboard
{
BeginTime = TimeSpan.FromMilliseconds(startTime)
};
if (completed != null)
{
sb.Completed += (s, e) =>
{
completed();
};
}
sb.Children.Add(db);
sb.Begin();
}
#endregion
return animation;
}
public static Vector2KeyFrameAnimation CreateVector2KeyFrameAnimation(this Compositor compositor, Vector2? from, Vector2 to,
double duration, double delay, CompositionEasingFunction easing, AnimationIterationBehavior iterationBehavior)
{
var animation = compositor.CreateVector2KeyFrameAnimation();
animation.Duration = TimeSpan.FromMilliseconds(duration);
animation.DelayTime = TimeSpan.FromMilliseconds(delay);
if (from.HasValue) animation.InsertKeyFrame(0.0f, from.Value, easing);
animation.InsertKeyFrame(1.0f, to, easing);
animation.IterationBehavior = iterationBehavior;
return animation;
}
public static Vector3KeyFrameAnimation CreateVector3KeyFrameAnimation(this Compositor compositor, Vector2? from, Vector2 to,
double duration, double delay, CompositionEasingFunction easing, AnimationIterationBehavior iterationBehavior)
{
var animation = compositor.CreateVector3KeyFrameAnimation();
animation.Duration = TimeSpan.FromMilliseconds(duration);
animation.DelayTime = TimeSpan.FromMilliseconds(delay);
if (from.HasValue) animation.InsertKeyFrame(0.0f, new Vector3(from.Value, 1.0f), easing);
animation.InsertKeyFrame(1.0f, new Vector3(to, 1.0f), easing);
animation.IterationBehavior = iterationBehavior;
return animation;
}
public static Vector3KeyFrameAnimation CreateVector3KeyFrameAnimation(this Compositor compositor, Vector3? from, Vector3 to,
double duration, double delay, CompositionEasingFunction easing, AnimationIterationBehavior iterationBehavior)
{
var animation = compositor.CreateVector3KeyFrameAnimation();
animation.Duration = TimeSpan.FromMilliseconds(duration);
animation.DelayTime = TimeSpan.FromMilliseconds(delay);
if (from.HasValue) animation.InsertKeyFrame(0.0f, from.Value, easing);
animation.InsertKeyFrame(1.0f, to, easing);
animation.IterationBehavior = iterationBehavior;
return animation;
}
#endregion
#region Xaml Storyboard
public static void Animate(this DependencyObject target, double? from, double to,
string propertyPath, int duration = 400, int startTime = 0,
EasingFunctionBase easing = null, Action completed = null, bool enableDependentAnimation = false)
{
if (easing == null)
{
easing = new ExponentialEase();
}
var db = new DoubleAnimation
{
EnableDependentAnimation = enableDependentAnimation,
To = to,
From = from,
EasingFunction = easing,
Duration = TimeSpan.FromMilliseconds(duration)
};
Storyboard.SetTarget(db, target);
Storyboard.SetTargetProperty(db, propertyPath);
var sb = new Storyboard
{
BeginTime = TimeSpan.FromMilliseconds(startTime)
};
if (completed != null)
{
sb.Completed += (s, e) =>
{
completed();
};
}
sb.Children.Add(db);
sb.Begin();
}
#endregion
}

View File

@@ -4,70 +4,69 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wino.Extensions
namespace Wino.Extensions;
public enum TransitionDirection
{
public enum TransitionDirection
{
TopToBottom,
BottomToTop,
LeftToRight,
RightToLeft
}
public enum ClipAnimationDirection
{
Top,
Bottom,
Left,
Right
}
public enum AnimationAxis
{
X,
Y,
Z
}
public enum AnimationType
{
KeyFrame,
Expression
}
public enum FlickDirection
{
None,
Up,
Down,
Left,
Right
}
public enum ViewState
{
Empty,
Small,
Big,
Full
}
public enum Gesture
{
Initial,
Tap,
Swipe
}
[Flags]
public enum VisualPropertyType
{
None = 0,
Opacity = 1 << 0,
Offset = 1 << 1,
Scale = 1 << 2,
Size = 1 << 3,
RotationAngleInDegrees = 1 << 4,
All = ~0
}
TopToBottom,
BottomToTop,
LeftToRight,
RightToLeft
}
public enum ClipAnimationDirection
{
Top,
Bottom,
Left,
Right
}
public enum AnimationAxis
{
X,
Y,
Z
}
public enum AnimationType
{
KeyFrame,
Expression
}
public enum FlickDirection
{
None,
Up,
Down,
Left,
Right
}
public enum ViewState
{
Empty,
Small,
Big,
Full
}
public enum Gesture
{
Initial,
Tap,
Swipe
}
[Flags]
public enum VisualPropertyType
{
None = 0,
Opacity = 1 << 0,
Offset = 1 << 1,
Scale = 1 << 2,
Size = 1 << 3,
RotationAngleInDegrees = 1 << 4,
All = ~0
}

View File

@@ -8,193 +8,192 @@ using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Hosting;
namespace Wino.Extensions
namespace Wino.Extensions;
public static partial class CompositionExtensions
{
public static partial class CompositionExtensions
public static void EnableFluidVisibilityAnimation(this UIElement element, AnimationAxis? axis = null,
float showFromOffset = 0.0f, float hideToOffset = 0.0f, Vector3? centerPoint = null,
float showFromScale = 1.0f, float hideToScale = 1.0f, float showDuration = 800.0f, float hideDuration = 800.0f,
int showDelay = 0, int hideDelay = 0, bool animateOpacity = true)
{
public static void EnableFluidVisibilityAnimation(this UIElement element, AnimationAxis? axis = null,
float showFromOffset = 0.0f, float hideToOffset = 0.0f, Vector3? centerPoint = null,
float showFromScale = 1.0f, float hideToScale = 1.0f, float showDuration = 800.0f, float hideDuration = 800.0f,
int showDelay = 0, int hideDelay = 0, bool animateOpacity = true)
var elementVisual = element.Visual();
var compositor = elementVisual.Compositor;
ElementCompositionPreview.SetIsTranslationEnabled(element, true);
ScalarKeyFrameAnimation hideOpacityAnimation = null;
ScalarKeyFrameAnimation showOpacityAnimation = null;
ScalarKeyFrameAnimation hideOffsetAnimation = null;
ScalarKeyFrameAnimation showOffsetAnimation = null;
Vector2KeyFrameAnimation hideScaleAnimation = null;
Vector2KeyFrameAnimation showeScaleAnimation = null;
if (animateOpacity)
{
var elementVisual = element.Visual();
var compositor = elementVisual.Compositor;
ElementCompositionPreview.SetIsTranslationEnabled(element, true);
ScalarKeyFrameAnimation hideOpacityAnimation = null;
ScalarKeyFrameAnimation showOpacityAnimation = null;
ScalarKeyFrameAnimation hideOffsetAnimation = null;
ScalarKeyFrameAnimation showOffsetAnimation = null;
Vector2KeyFrameAnimation hideScaleAnimation = null;
Vector2KeyFrameAnimation showeScaleAnimation = null;
if (animateOpacity)
{
hideOpacityAnimation = compositor.CreateScalarKeyFrameAnimation();
hideOpacityAnimation.InsertKeyFrame(1.0f, 0.0f);
hideOpacityAnimation.Duration = TimeSpan.FromMilliseconds(hideDuration);
hideOpacityAnimation.DelayTime = TimeSpan.FromMilliseconds(hideDelay);
hideOpacityAnimation.Target = "Opacity";
}
if (!hideToOffset.Equals(0.0f))
{
hideOffsetAnimation = compositor.CreateScalarKeyFrameAnimation();
hideOffsetAnimation.InsertKeyFrame(1.0f, hideToOffset);
hideOffsetAnimation.Duration = TimeSpan.FromMilliseconds(hideDuration);
hideOffsetAnimation.DelayTime = TimeSpan.FromMilliseconds(hideDelay);
hideOffsetAnimation.Target = $"Translation.{axis}";
}
if (centerPoint.HasValue)
{
elementVisual.CenterPoint = centerPoint.Value;
}
if (!hideToScale.Equals(1.0f))
{
hideScaleAnimation = compositor.CreateVector2KeyFrameAnimation();
hideScaleAnimation.InsertKeyFrame(1.0f, new Vector2(hideToScale));
hideScaleAnimation.Duration = TimeSpan.FromMilliseconds(hideDuration);
hideScaleAnimation.DelayTime = TimeSpan.FromMilliseconds(hideDelay);
hideScaleAnimation.Target = "Scale.XY";
}
var hideAnimationGroup = compositor.CreateAnimationGroup();
if (hideOpacityAnimation != null)
{
hideAnimationGroup.Add(hideOpacityAnimation);
}
if (hideOffsetAnimation != null)
{
hideAnimationGroup.Add(hideOffsetAnimation);
}
if (hideScaleAnimation != null)
{
hideAnimationGroup.Add(hideScaleAnimation);
}
ElementCompositionPreview.SetImplicitHideAnimation(element, hideAnimationGroup);
if (animateOpacity)
{
showOpacityAnimation = compositor.CreateScalarKeyFrameAnimation();
showOpacityAnimation.InsertKeyFrame(1.0f, 1.0f);
showOpacityAnimation.Duration = TimeSpan.FromMilliseconds(showDuration);
showOpacityAnimation.DelayTime = TimeSpan.FromMilliseconds(showDelay);
showOpacityAnimation.Target = "Opacity";
}
if (!showFromOffset.Equals(0.0f))
{
showOffsetAnimation = compositor.CreateScalarKeyFrameAnimation();
showOffsetAnimation.InsertKeyFrame(0.0f, showFromOffset);
showOffsetAnimation.InsertKeyFrame(1.0f, 0.0f);
showOffsetAnimation.Duration = TimeSpan.FromMilliseconds(showDuration);
showOffsetAnimation.DelayTime = TimeSpan.FromMilliseconds(showDelay);
showOffsetAnimation.Target = $"Translation.{axis}";
}
if (!showFromScale.Equals(1.0f))
{
showeScaleAnimation = compositor.CreateVector2KeyFrameAnimation();
showeScaleAnimation.InsertKeyFrame(0.0f, new Vector2(showFromScale));
showeScaleAnimation.InsertKeyFrame(1.0f, Vector2.One);
showeScaleAnimation.Duration = TimeSpan.FromMilliseconds(showDuration);
showeScaleAnimation.DelayTime = TimeSpan.FromMilliseconds(showDelay);
showeScaleAnimation.Target = "Scale.XY";
}
var showAnimationGroup = compositor.CreateAnimationGroup();
if (showOpacityAnimation != null)
{
showAnimationGroup.Add(showOpacityAnimation);
}
if (showOffsetAnimation != null)
{
showAnimationGroup.Add(showOffsetAnimation);
}
if (showeScaleAnimation != null)
{
showAnimationGroup.Add(showeScaleAnimation);
}
ElementCompositionPreview.SetImplicitShowAnimation(element, showAnimationGroup);
hideOpacityAnimation = compositor.CreateScalarKeyFrameAnimation();
hideOpacityAnimation.InsertKeyFrame(1.0f, 0.0f);
hideOpacityAnimation.Duration = TimeSpan.FromMilliseconds(hideDuration);
hideOpacityAnimation.DelayTime = TimeSpan.FromMilliseconds(hideDelay);
hideOpacityAnimation.Target = "Opacity";
}
public static void EnableImplicitAnimation(this UIElement element, VisualPropertyType typeToAnimate,
double duration = 800, double delay = 0, CompositionEasingFunction easing = null)
if (!hideToOffset.Equals(0.0f))
{
var visual = element.Visual();
var compositor = visual.Compositor;
var animationCollection = compositor.CreateImplicitAnimationCollection();
foreach (var type in UtilExtensions.GetValues<VisualPropertyType>())
{
if (!typeToAnimate.HasFlag(type)) continue;
var animation = CreateAnimationByType(compositor, type, duration, delay, easing);
if (animation != null)
{
animationCollection[type.ToString()] = animation;
}
}
visual.ImplicitAnimations = animationCollection;
hideOffsetAnimation = compositor.CreateScalarKeyFrameAnimation();
hideOffsetAnimation.InsertKeyFrame(1.0f, hideToOffset);
hideOffsetAnimation.Duration = TimeSpan.FromMilliseconds(hideDuration);
hideOffsetAnimation.DelayTime = TimeSpan.FromMilliseconds(hideDelay);
hideOffsetAnimation.Target = $"Translation.{axis}";
}
public static void EnableImplicitAnimation(this Visual visual, VisualPropertyType typeToAnimate,
double duration = 800, double delay = 0, CompositionEasingFunction easing = null)
if (centerPoint.HasValue)
{
var compositor = visual.Compositor;
var animationCollection = compositor.CreateImplicitAnimationCollection();
foreach (var type in UtilExtensions.GetValues<VisualPropertyType>())
{
if (!typeToAnimate.HasFlag(type)) continue;
var animation = CreateAnimationByType(compositor, type, duration, delay, easing);
if (animation != null)
{
animationCollection[type.ToString()] = animation;
}
}
visual.ImplicitAnimations = animationCollection;
elementVisual.CenterPoint = centerPoint.Value;
}
private static KeyFrameAnimation CreateAnimationByType(Compositor compositor, VisualPropertyType type,
double duration = 800, double delay = 0, CompositionEasingFunction easing = null)
if (!hideToScale.Equals(1.0f))
{
KeyFrameAnimation animation;
switch (type)
{
case VisualPropertyType.Offset:
case VisualPropertyType.Scale:
animation = compositor.CreateVector3KeyFrameAnimation();
break;
case VisualPropertyType.Size:
animation = compositor.CreateVector2KeyFrameAnimation();
break;
case VisualPropertyType.Opacity:
case VisualPropertyType.RotationAngleInDegrees:
animation = compositor.CreateScalarKeyFrameAnimation();
break;
default:
return null;
}
animation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", easing);
animation.Duration = TimeSpan.FromMilliseconds(duration);
animation.DelayTime = TimeSpan.FromMilliseconds(delay);
animation.Target = type.ToString();
return animation;
hideScaleAnimation = compositor.CreateVector2KeyFrameAnimation();
hideScaleAnimation.InsertKeyFrame(1.0f, new Vector2(hideToScale));
hideScaleAnimation.Duration = TimeSpan.FromMilliseconds(hideDuration);
hideScaleAnimation.DelayTime = TimeSpan.FromMilliseconds(hideDelay);
hideScaleAnimation.Target = "Scale.XY";
}
var hideAnimationGroup = compositor.CreateAnimationGroup();
if (hideOpacityAnimation != null)
{
hideAnimationGroup.Add(hideOpacityAnimation);
}
if (hideOffsetAnimation != null)
{
hideAnimationGroup.Add(hideOffsetAnimation);
}
if (hideScaleAnimation != null)
{
hideAnimationGroup.Add(hideScaleAnimation);
}
ElementCompositionPreview.SetImplicitHideAnimation(element, hideAnimationGroup);
if (animateOpacity)
{
showOpacityAnimation = compositor.CreateScalarKeyFrameAnimation();
showOpacityAnimation.InsertKeyFrame(1.0f, 1.0f);
showOpacityAnimation.Duration = TimeSpan.FromMilliseconds(showDuration);
showOpacityAnimation.DelayTime = TimeSpan.FromMilliseconds(showDelay);
showOpacityAnimation.Target = "Opacity";
}
if (!showFromOffset.Equals(0.0f))
{
showOffsetAnimation = compositor.CreateScalarKeyFrameAnimation();
showOffsetAnimation.InsertKeyFrame(0.0f, showFromOffset);
showOffsetAnimation.InsertKeyFrame(1.0f, 0.0f);
showOffsetAnimation.Duration = TimeSpan.FromMilliseconds(showDuration);
showOffsetAnimation.DelayTime = TimeSpan.FromMilliseconds(showDelay);
showOffsetAnimation.Target = $"Translation.{axis}";
}
if (!showFromScale.Equals(1.0f))
{
showeScaleAnimation = compositor.CreateVector2KeyFrameAnimation();
showeScaleAnimation.InsertKeyFrame(0.0f, new Vector2(showFromScale));
showeScaleAnimation.InsertKeyFrame(1.0f, Vector2.One);
showeScaleAnimation.Duration = TimeSpan.FromMilliseconds(showDuration);
showeScaleAnimation.DelayTime = TimeSpan.FromMilliseconds(showDelay);
showeScaleAnimation.Target = "Scale.XY";
}
var showAnimationGroup = compositor.CreateAnimationGroup();
if (showOpacityAnimation != null)
{
showAnimationGroup.Add(showOpacityAnimation);
}
if (showOffsetAnimation != null)
{
showAnimationGroup.Add(showOffsetAnimation);
}
if (showeScaleAnimation != null)
{
showAnimationGroup.Add(showeScaleAnimation);
}
ElementCompositionPreview.SetImplicitShowAnimation(element, showAnimationGroup);
}
public static void EnableImplicitAnimation(this UIElement element, VisualPropertyType typeToAnimate,
double duration = 800, double delay = 0, CompositionEasingFunction easing = null)
{
var visual = element.Visual();
var compositor = visual.Compositor;
var animationCollection = compositor.CreateImplicitAnimationCollection();
foreach (var type in UtilExtensions.GetValues<VisualPropertyType>())
{
if (!typeToAnimate.HasFlag(type)) continue;
var animation = CreateAnimationByType(compositor, type, duration, delay, easing);
if (animation != null)
{
animationCollection[type.ToString()] = animation;
}
}
visual.ImplicitAnimations = animationCollection;
}
public static void EnableImplicitAnimation(this Visual visual, VisualPropertyType typeToAnimate,
double duration = 800, double delay = 0, CompositionEasingFunction easing = null)
{
var compositor = visual.Compositor;
var animationCollection = compositor.CreateImplicitAnimationCollection();
foreach (var type in UtilExtensions.GetValues<VisualPropertyType>())
{
if (!typeToAnimate.HasFlag(type)) continue;
var animation = CreateAnimationByType(compositor, type, duration, delay, easing);
if (animation != null)
{
animationCollection[type.ToString()] = animation;
}
}
visual.ImplicitAnimations = animationCollection;
}
private static KeyFrameAnimation CreateAnimationByType(Compositor compositor, VisualPropertyType type,
double duration = 800, double delay = 0, CompositionEasingFunction easing = null)
{
KeyFrameAnimation animation;
switch (type)
{
case VisualPropertyType.Offset:
case VisualPropertyType.Scale:
animation = compositor.CreateVector3KeyFrameAnimation();
break;
case VisualPropertyType.Size:
animation = compositor.CreateVector2KeyFrameAnimation();
break;
case VisualPropertyType.Opacity:
case VisualPropertyType.RotationAngleInDegrees:
animation = compositor.CreateScalarKeyFrameAnimation();
break;
default:
return null;
}
animation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", easing);
animation.Duration = TimeSpan.FromMilliseconds(duration);
animation.DelayTime = TimeSpan.FromMilliseconds(delay);
animation.Target = type.ToString();
return animation;
}
}

View File

@@ -4,124 +4,123 @@ using System.Threading.Tasks;
using Windows.UI.Composition;
using Windows.UI.Xaml;
namespace Wino.Extensions
namespace Wino.Extensions;
public static partial class CompositionExtensions
{
public static partial class CompositionExtensions
public static void StartSizeAnimation(this UIElement element, Vector2? from = null, Vector2? to = null,
double duration = 800, int delay = 0, CompositionEasingFunction easing = null, Action completed = null,
AnimationIterationBehavior iterationBehavior = AnimationIterationBehavior.Count)
{
public static void StartSizeAnimation(this UIElement element, Vector2? from = null, Vector2? to = null,
double duration = 800, int delay = 0, CompositionEasingFunction easing = null, Action completed = null,
AnimationIterationBehavior iterationBehavior = AnimationIterationBehavior.Count)
CompositionScopedBatch batch = null;
var visual = element.Visual();
var compositor = visual.Compositor;
if (completed != null)
{
CompositionScopedBatch batch = null;
var visual = element.Visual();
var compositor = visual.Compositor;
if (completed != null)
{
batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
batch.Completed += (s, e) => completed();
}
if (to == null)
{
to = Vector2.One;
}
visual.StartAnimation("Size",
compositor.CreateVector2KeyFrameAnimation(from, to.Value, duration, delay, easing, iterationBehavior));
batch?.End();
}
public static void StartSizeAnimation(this Visual visual, Vector2? from = null, Vector2? to = null,
double duration = 800, int delay = 0, CompositionEasingFunction easing = null, Action completed = null,
AnimationIterationBehavior iterationBehavior = AnimationIterationBehavior.Count)
{
CompositionScopedBatch batch = null;
var compositor = visual.Compositor;
if (completed != null)
{
batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
batch.Completed += (s, e) => completed();
}
if (to == null)
{
to = Vector2.One;
}
visual.StartAnimation("Size",
compositor.CreateVector2KeyFrameAnimation(from, to.Value, duration, delay, easing, iterationBehavior));
batch?.End();
}
public static Task StartSizeAnimationAsync(this UIElement element, Vector2? from = null, Vector2? to = null,
double duration = 800, int delay = 0, CompositionEasingFunction easing = null,
AnimationIterationBehavior iterationBehavior = AnimationIterationBehavior.Count)
{
CompositionScopedBatch batch;
var visual = element.Visual();
var compositor = visual.Compositor;
var taskSource = new TaskCompletionSource<bool>();
void Completed(object o, CompositionBatchCompletedEventArgs e)
{
batch.Completed -= Completed;
taskSource.SetResult(true);
}
batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
batch.Completed += Completed;
if (to == null)
{
to = Vector2.One;
}
visual.StartAnimation("Size",
compositor.CreateVector2KeyFrameAnimation(from, to.Value, duration, delay, easing, iterationBehavior));
batch.End();
return taskSource.Task;
batch.Completed += (s, e) => completed();
}
public static Task StartSizeAnimationAsync(this Visual visual, Vector2? from = null, Vector2? to = null,
double duration = 800, int delay = 0, CompositionEasingFunction easing = null,
AnimationIterationBehavior iterationBehavior = AnimationIterationBehavior.Count)
if (to == null)
{
CompositionScopedBatch batch;
var compositor = visual.Compositor;
var taskSource = new TaskCompletionSource<bool>();
void Completed(object o, CompositionBatchCompletedEventArgs e)
{
batch.Completed -= Completed;
taskSource.SetResult(true);
}
batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
batch.Completed += Completed;
if (to == null)
{
to = Vector2.One;
}
visual.StartAnimation("Size",
compositor.CreateVector2KeyFrameAnimation(from, to.Value, duration, delay, easing, iterationBehavior));
batch.End();
return taskSource.Task;
to = Vector2.One;
}
visual.StartAnimation("Size",
compositor.CreateVector2KeyFrameAnimation(from, to.Value, duration, delay, easing, iterationBehavior));
batch?.End();
}
public static void StartSizeAnimation(this Visual visual, Vector2? from = null, Vector2? to = null,
double duration = 800, int delay = 0, CompositionEasingFunction easing = null, Action completed = null,
AnimationIterationBehavior iterationBehavior = AnimationIterationBehavior.Count)
{
CompositionScopedBatch batch = null;
var compositor = visual.Compositor;
if (completed != null)
{
batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
batch.Completed += (s, e) => completed();
}
if (to == null)
{
to = Vector2.One;
}
visual.StartAnimation("Size",
compositor.CreateVector2KeyFrameAnimation(from, to.Value, duration, delay, easing, iterationBehavior));
batch?.End();
}
public static Task StartSizeAnimationAsync(this UIElement element, Vector2? from = null, Vector2? to = null,
double duration = 800, int delay = 0, CompositionEasingFunction easing = null,
AnimationIterationBehavior iterationBehavior = AnimationIterationBehavior.Count)
{
CompositionScopedBatch batch;
var visual = element.Visual();
var compositor = visual.Compositor;
var taskSource = new TaskCompletionSource<bool>();
void Completed(object o, CompositionBatchCompletedEventArgs e)
{
batch.Completed -= Completed;
taskSource.SetResult(true);
}
batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
batch.Completed += Completed;
if (to == null)
{
to = Vector2.One;
}
visual.StartAnimation("Size",
compositor.CreateVector2KeyFrameAnimation(from, to.Value, duration, delay, easing, iterationBehavior));
batch.End();
return taskSource.Task;
}
public static Task StartSizeAnimationAsync(this Visual visual, Vector2? from = null, Vector2? to = null,
double duration = 800, int delay = 0, CompositionEasingFunction easing = null,
AnimationIterationBehavior iterationBehavior = AnimationIterationBehavior.Count)
{
CompositionScopedBatch batch;
var compositor = visual.Compositor;
var taskSource = new TaskCompletionSource<bool>();
void Completed(object o, CompositionBatchCompletedEventArgs e)
{
batch.Completed -= Completed;
taskSource.SetResult(true);
}
batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
batch.Completed += Completed;
if (to == null)
{
to = Vector2.One;
}
visual.StartAnimation("Size",
compositor.CreateVector2KeyFrameAnimation(from, to.Value, duration, delay, easing, iterationBehavior));
batch.End();
return taskSource.Task;
}
}

View File

@@ -1,34 +1,33 @@
using Windows.UI.Xaml;
using Wino.Core.Domain.Enums;
namespace Wino.Core.UWP.Extensions
namespace Wino.Core.UWP.Extensions;
public static class ElementThemeExtensions
{
public static class ElementThemeExtensions
public static ApplicationElementTheme ToWinoElementTheme(this ElementTheme elementTheme)
{
public static ApplicationElementTheme ToWinoElementTheme(this ElementTheme elementTheme)
switch (elementTheme)
{
switch (elementTheme)
{
case ElementTheme.Light:
return ApplicationElementTheme.Light;
case ElementTheme.Dark:
return ApplicationElementTheme.Dark;
}
return ApplicationElementTheme.Default;
case ElementTheme.Light:
return ApplicationElementTheme.Light;
case ElementTheme.Dark:
return ApplicationElementTheme.Dark;
}
public static ElementTheme ToWindowsElementTheme(this ApplicationElementTheme elementTheme)
{
switch (elementTheme)
{
case ApplicationElementTheme.Light:
return ElementTheme.Light;
case ApplicationElementTheme.Dark:
return ElementTheme.Dark;
}
return ApplicationElementTheme.Default;
}
return ElementTheme.Default;
public static ElementTheme ToWindowsElementTheme(this ApplicationElementTheme elementTheme)
{
switch (elementTheme)
{
case ApplicationElementTheme.Light:
return ElementTheme.Light;
case ApplicationElementTheme.Dark:
return ElementTheme.Dark;
}
return ElementTheme.Default;
}
}

View File

@@ -1,25 +1,24 @@
using Windows.ApplicationModel;
using Wino.Core.Domain.Enums;
namespace Wino.Core.UWP.Extensions
namespace Wino.Core.UWP.Extensions;
public static class StartupTaskStateExtensions
{
public static class StartupTaskStateExtensions
public static StartupBehaviorResult AsStartupBehaviorResult(this StartupTaskState state)
{
public static StartupBehaviorResult AsStartupBehaviorResult(this StartupTaskState state)
switch (state)
{
switch (state)
{
case StartupTaskState.Disabled:
case StartupTaskState.DisabledByPolicy:
return StartupBehaviorResult.Disabled;
case StartupTaskState.DisabledByUser:
return StartupBehaviorResult.DisabledByUser;
case StartupTaskState.Enabled:
case StartupTaskState.EnabledByPolicy:
return StartupBehaviorResult.Enabled;
default:
return StartupBehaviorResult.Fatal;
}
case StartupTaskState.Disabled:
case StartupTaskState.DisabledByPolicy:
return StartupBehaviorResult.Disabled;
case StartupTaskState.DisabledByUser:
return StartupBehaviorResult.DisabledByUser;
case StartupTaskState.Enabled:
case StartupTaskState.EnabledByPolicy:
return StartupBehaviorResult.Enabled;
default:
return StartupBehaviorResult.Fatal;
}
}
}

View File

@@ -4,28 +4,27 @@ using System.Threading.Tasks;
using Windows.Storage;
using Wino.Core.Domain.Models.Common;
namespace Wino.Core.UWP.Extensions
namespace Wino.Core.UWP.Extensions;
public static class StorageFileExtensions
{
public static class StorageFileExtensions
public static async Task<SharedFile> ToSharedFileAsync(this Windows.Storage.StorageFile storageFile)
{
public static async Task<SharedFile> ToSharedFileAsync(this Windows.Storage.StorageFile storageFile)
var content = await storageFile.ToByteArrayAsync();
return new SharedFile(storageFile.Path, content);
}
public static async Task<byte[]> ToByteArrayAsync(this StorageFile file)
{
if (file == null)
throw new ArgumentNullException(nameof(file));
using (var stream = await file.OpenReadAsync())
using (var memoryStream = new MemoryStream())
{
var content = await storageFile.ToByteArrayAsync();
return new SharedFile(storageFile.Path, content);
}
public static async Task<byte[]> ToByteArrayAsync(this StorageFile file)
{
if (file == null)
throw new ArgumentNullException(nameof(file));
using (var stream = await file.OpenReadAsync())
using (var memoryStream = new MemoryStream())
{
await stream.AsStreamForRead().CopyToAsync(memoryStream);
return memoryStream.ToArray();
}
await stream.AsStreamForRead().CopyToAsync(memoryStream);
return memoryStream.ToArray();
}
}
}

View File

@@ -1,20 +1,19 @@
using Microsoft.UI.Xaml.Controls;
using Wino.Core.Domain.Enums;
namespace Wino.Extensions
namespace Wino.Extensions;
public static class UIExtensions
{
public static class UIExtensions
public static InfoBarSeverity AsMUXCInfoBarSeverity(this InfoBarMessageType messageType)
{
public static InfoBarSeverity AsMUXCInfoBarSeverity(this InfoBarMessageType messageType)
return messageType switch
{
return messageType switch
{
InfoBarMessageType.Error => InfoBarSeverity.Error,
InfoBarMessageType.Warning => InfoBarSeverity.Warning,
InfoBarMessageType.Information => InfoBarSeverity.Informational,
InfoBarMessageType.Success => InfoBarSeverity.Success,
_ => InfoBarSeverity.Informational
};
}
InfoBarMessageType.Error => InfoBarSeverity.Error,
InfoBarMessageType.Warning => InfoBarSeverity.Warning,
InfoBarMessageType.Information => InfoBarSeverity.Informational,
InfoBarMessageType.Success => InfoBarSeverity.Success,
_ => InfoBarSeverity.Informational
};
}
}

View File

@@ -8,101 +8,100 @@ using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Hosting;
using Windows.UI.Xaml.Media;
namespace Wino.Extensions
namespace Wino.Extensions;
public static class UtilExtensions
{
public static class UtilExtensions
public static float ToFloat(this double value) => (float)value;
public static List<FrameworkElement> Children(this DependencyObject parent)
{
public static float ToFloat(this double value) => (float)value;
var list = new List<FrameworkElement>();
public static List<FrameworkElement> Children(this DependencyObject parent)
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var list = new List<FrameworkElement>();
var child = VisualTreeHelper.GetChild(parent, i);
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
if (child is FrameworkElement)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is FrameworkElement)
{
list.Add(child as FrameworkElement);
}
list.AddRange(Children(child));
list.Add(child as FrameworkElement);
}
return list;
list.AddRange(Children(child));
}
public static T GetChildByName<T>(this DependencyObject parent, string name)
{
var childControls = Children(parent);
var controls = childControls.OfType<FrameworkElement>();
if (controls == null)
{
return default(T);
}
var control = controls
.Where(x => x.Name.Equals(name))
.Cast<T>()
.First();
return control;
}
public static Visual Visual(this UIElement element) =>
ElementCompositionPreview.GetElementVisual(element);
public static void SetChildVisual(this UIElement element, Visual childVisual) =>
ElementCompositionPreview.SetElementChildVisual(element, childVisual);
public static Point RelativePosition(this UIElement element, UIElement other) =>
element.TransformToVisual(other).TransformPoint(new Point(0, 0));
public static bool IsFullyVisibile(this FrameworkElement element, FrameworkElement parent)
{
if (element == null || parent == null)
return false;
if (element.Visibility != Visibility.Visible)
return false;
var elementBounds = element.TransformToVisual(parent).TransformBounds(new Windows.Foundation.Rect(0, 0, element.ActualWidth, element.ActualHeight));
var containerBounds = new Windows.Foundation.Rect(0, 0, parent.ActualWidth, parent.ActualHeight);
var originalElementWidth = elementBounds.Width;
var originalElementHeight = elementBounds.Height;
elementBounds.Intersect(containerBounds);
var newElementWidth = elementBounds.Width;
var newElementHeight = elementBounds.Height;
return originalElementWidth.Equals(newElementWidth) && originalElementHeight.Equals(newElementHeight);
}
public static void ScrollToElement(this ScrollViewer scrollViewer, FrameworkElement element,
bool isVerticalScrolling = true, bool smoothScrolling = true, float? zoomFactor = null, bool bringToTopOrLeft = true, bool addMargin = true)
{
if (!bringToTopOrLeft && element.IsFullyVisibile(scrollViewer))
return;
var contentArea = (FrameworkElement)scrollViewer.Content;
var position = element.RelativePosition(contentArea);
if (isVerticalScrolling)
{
// Accomodate for additional header.
scrollViewer.ChangeView(null, Math.Max(0, position.Y - (addMargin ? 48 : 0)), zoomFactor, !smoothScrolling);
}
else
{
scrollViewer.ChangeView(position.X, null, zoomFactor, !smoothScrolling);
}
}
public static T[] GetValues<T>() where T : struct, Enum => Enum.GetValues<T>();
return list;
}
public static T GetChildByName<T>(this DependencyObject parent, string name)
{
var childControls = Children(parent);
var controls = childControls.OfType<FrameworkElement>();
if (controls == null)
{
return default(T);
}
var control = controls
.Where(x => x.Name.Equals(name))
.Cast<T>()
.First();
return control;
}
public static Visual Visual(this UIElement element) =>
ElementCompositionPreview.GetElementVisual(element);
public static void SetChildVisual(this UIElement element, Visual childVisual) =>
ElementCompositionPreview.SetElementChildVisual(element, childVisual);
public static Point RelativePosition(this UIElement element, UIElement other) =>
element.TransformToVisual(other).TransformPoint(new Point(0, 0));
public static bool IsFullyVisibile(this FrameworkElement element, FrameworkElement parent)
{
if (element == null || parent == null)
return false;
if (element.Visibility != Visibility.Visible)
return false;
var elementBounds = element.TransformToVisual(parent).TransformBounds(new Windows.Foundation.Rect(0, 0, element.ActualWidth, element.ActualHeight));
var containerBounds = new Windows.Foundation.Rect(0, 0, parent.ActualWidth, parent.ActualHeight);
var originalElementWidth = elementBounds.Width;
var originalElementHeight = elementBounds.Height;
elementBounds.Intersect(containerBounds);
var newElementWidth = elementBounds.Width;
var newElementHeight = elementBounds.Height;
return originalElementWidth.Equals(newElementWidth) && originalElementHeight.Equals(newElementHeight);
}
public static void ScrollToElement(this ScrollViewer scrollViewer, FrameworkElement element,
bool isVerticalScrolling = true, bool smoothScrolling = true, float? zoomFactor = null, bool bringToTopOrLeft = true, bool addMargin = true)
{
if (!bringToTopOrLeft && element.IsFullyVisibile(scrollViewer))
return;
var contentArea = (FrameworkElement)scrollViewer.Content;
var position = element.RelativePosition(contentArea);
if (isVerticalScrolling)
{
// Accomodate for additional header.
scrollViewer.ChangeView(null, Math.Max(0, position.Y - (addMargin ? 48 : 0)), zoomFactor, !smoothScrolling);
}
else
{
scrollViewer.ChangeView(position.X, null, zoomFactor, !smoothScrolling);
}
}
public static T[] GetValues<T>() where T : struct, Enum => Enum.GetValues<T>();
}

View File

@@ -2,51 +2,50 @@
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
namespace Wino.Helpers
namespace Wino.Helpers;
public static class WinoVisualTreeHelper
{
public static class WinoVisualTreeHelper
public static T GetChildObject<T>(DependencyObject obj, string name) where T : FrameworkElement
{
public static T GetChildObject<T>(DependencyObject obj, string name) where T : FrameworkElement
DependencyObject child = null;
T grandChild = null;
for (int i = 0; i <= VisualTreeHelper.GetChildrenCount(obj) - 1; i++)
{
DependencyObject child = null;
T grandChild = null;
child = VisualTreeHelper.GetChild(obj, i);
for (int i = 0; i <= VisualTreeHelper.GetChildrenCount(obj) - 1; i++)
if (child is T && (((T)child).Name == name | string.IsNullOrEmpty(name)))
{
child = VisualTreeHelper.GetChild(obj, i);
if (child is T && (((T)child).Name == name | string.IsNullOrEmpty(name)))
{
return (T)child;
}
else
{
grandChild = GetChildObject<T>(child, name);
}
if (grandChild != null)
{
return grandChild;
}
return (T)child;
}
return null;
}
public static IEnumerable<T> FindDescendants<T>(this DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
else
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
grandChild = GetChildObject<T>(child, name);
}
if (grandChild != null)
{
return grandChild;
}
}
return null;
}
foreach (T childOfChild in FindDescendants<T>(child))
{
yield return childOfChild;
}
public static IEnumerable<T> FindDescendants<T>(this DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindDescendants<T>(child))
{
yield return childOfChild;
}
}
}

View File

@@ -16,351 +16,350 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.UWP.Controls;
namespace Wino.Helpers
namespace Wino.Helpers;
public static class XamlHelpers
{
public static class XamlHelpers
private const string TwentyFourHourTimeFormat = "HH:mm";
private const string TwelveHourTimeFormat = "hh:mm tt";
#region Converters
public static bool IsMultiple(int count) => count > 1;
public static bool ReverseIsMultiple(int count) => count < 1;
public static PopupPlacementMode GetPlaccementModeForCalendarType(CalendarDisplayType type)
{
private const string TwentyFourHourTimeFormat = "HH:mm";
private const string TwelveHourTimeFormat = "hh:mm tt";
#region Converters
public static bool IsMultiple(int count) => count > 1;
public static bool ReverseIsMultiple(int count) => count < 1;
public static PopupPlacementMode GetPlaccementModeForCalendarType(CalendarDisplayType type)
return type switch
{
return type switch
{
CalendarDisplayType.Week => PopupPlacementMode.Right,
_ => PopupPlacementMode.Bottom,
};
}
public static Visibility ReverseBoolToVisibilityConverter(bool value) => value ? Visibility.Collapsed : Visibility.Visible;
public static Visibility ReverseVisibilityConverter(Visibility visibility) => visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
public static bool ReverseBoolConverter(bool value) => !value;
public static bool ShouldDisplayPreview(string text) => text.Any(x => char.IsLetter(x));
public static bool CountToBooleanConverter(int value) => value > 0;
public static bool ObjectEquals(object obj1, object obj2) => object.Equals(obj1, obj2);
public static Visibility CountToVisibilityConverter(int value) => value > 0 ? Visibility.Visible : Visibility.Collapsed;
public static Visibility CountToVisibilityConverterWithThreshold(int value, int threshold) => value > threshold ? Visibility.Visible : Visibility.Collapsed;
public static InfoBarSeverity InfoBarSeverityConverter(InfoBarMessageType messageType)
{
return messageType switch
{
InfoBarMessageType.Information => InfoBarSeverity.Informational,
InfoBarMessageType.Success => InfoBarSeverity.Success,
InfoBarMessageType.Warning => InfoBarSeverity.Warning,
InfoBarMessageType.Error => InfoBarSeverity.Error,
_ => InfoBarSeverity.Informational,
};
}
public static SolidColorBrush GetReadableTextColor(string backgroundColor)
{
if (!backgroundColor.StartsWith("#")) throw new ArgumentException("Hex color must start with #.");
backgroundColor = backgroundColor.TrimStart('#');
if (backgroundColor.Length == 6)
{
var r = int.Parse(backgroundColor.Substring(0, 2), NumberStyles.HexNumber);
var g = int.Parse(backgroundColor.Substring(2, 2), NumberStyles.HexNumber);
var b = int.Parse(backgroundColor.Substring(4, 2), NumberStyles.HexNumber);
// Calculate relative luminance
double luminance = (0.2126 * GetLinearValue(r)) +
(0.7152 * GetLinearValue(g)) +
(0.0722 * GetLinearValue(b));
return luminance > 0.5 ? new SolidColorBrush(Colors.Black) : new SolidColorBrush(Colors.White);
}
else
{
throw new ArgumentException("Hex color must be 6 characters long (e.g., #RRGGBB).");
}
}
private static double GetLinearValue(int colorComponent)
{
double sRGB = colorComponent / 255.0;
return sRGB <= 0.03928 ? sRGB / 12.92 : Math.Pow((sRGB + 0.055) / 1.055, 2.4);
}
public static Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode NavigationViewDisplayModeConverter(SplitViewDisplayMode splitViewDisplayMode)
{
return splitViewDisplayMode switch
{
SplitViewDisplayMode.CompactOverlay => Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Compact,
SplitViewDisplayMode.CompactInline => Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Minimal,
SplitViewDisplayMode.Overlay => Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Expanded,
SplitViewDisplayMode.Inline => Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Expanded,
_ => Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Minimal,
};
}
public static string GetColorFromHex(Color color) => color.ToHex();
public static Color GetWindowsColorFromHex(string hex) => hex.ToColor();
public static SolidColorBrush GetSolidColorBrushFromHex(string colorHex) => string.IsNullOrEmpty(colorHex) ? new SolidColorBrush(Colors.Transparent) : new SolidColorBrush(colorHex.ToColor());
public static Visibility IsSelectionModeMultiple(ListViewSelectionMode mode) => mode == ListViewSelectionMode.Multiple ? Visibility.Visible : Visibility.Collapsed;
public static FontWeight GetFontWeightBySyncState(bool isSyncing) => isSyncing ? FontWeights.SemiBold : FontWeights.Normal;
public static FontWeight GetFontWeightByChildSelectedState(bool isChildSelected) => isChildSelected ? FontWeights.SemiBold : FontWeights.Normal;
public static Visibility StringToVisibilityConverter(string value) => string.IsNullOrWhiteSpace(value) ? Visibility.Collapsed : Visibility.Visible;
public static Visibility StringToVisibilityReversedConverter(string value) => string.IsNullOrWhiteSpace(value) ? Visibility.Visible : Visibility.Collapsed;
public static string GetMailItemDisplaySummaryForListing(bool isDraft, DateTime receivedDate, bool prefer24HourTime)
{
if (isDraft)
return Translator.Draft;
else
{
var localTime = receivedDate.ToLocalTime();
return prefer24HourTime ? localTime.ToString(TwentyFourHourTimeFormat) : localTime.ToString(TwelveHourTimeFormat);
}
}
public static string GetCreationDateString(DateTime date, bool prefer24HourTime)
{
var localTime = date.ToLocalTime();
return $"{localTime.ToLongDateString()} {(prefer24HourTime ? localTime.ToString(TwentyFourHourTimeFormat) : localTime.ToString(TwelveHourTimeFormat))}";
}
public static string GetMailGroupDateString(object groupObject)
{
if (groupObject is string stringObject)
return stringObject;
object dateObject = null;
// From regular mail header template
if (groupObject is DateTime groupedDate)
dateObject = groupedDate;
else if (groupObject is IGrouping<object, IMailItem> groupKey)
{
// From semantic group header.
dateObject = groupKey.Key;
}
if (dateObject != null)
{
if (dateObject is DateTime dateTimeValue)
{
if (dateTimeValue == DateTime.Today)
return Translator.Today;
else if (dateTimeValue == DateTime.Today.AddDays(-1))
return Translator.Yesterday;
else
return dateTimeValue.ToLongDateString();
}
else
return dateObject.ToString();
}
return Translator.UnknownDateHeader;
}
public static bool ConnectionStatusEquals(WinoServerConnectionStatus winoServerConnectionStatus, WinoServerConnectionStatus connectionStatus) => winoServerConnectionStatus == connectionStatus;
#endregion
#region Wino Font Icon Transformation
public static WinoIconGlyph GetWinoIconGlyph(FilterOptionType type) => type switch
{
FilterOptionType.All => WinoIconGlyph.Mail,
FilterOptionType.Unread => WinoIconGlyph.MarkUnread,
FilterOptionType.Flagged => WinoIconGlyph.Flag,
FilterOptionType.Mentions => WinoIconGlyph.NewMail,
FilterOptionType.Files => WinoIconGlyph.Attachment,
_ => WinoIconGlyph.None,
CalendarDisplayType.Week => PopupPlacementMode.Right,
_ => PopupPlacementMode.Bottom,
};
public static WinoIconGlyph GetWinoIconGlyph(SortingOptionType type) => type switch
{
SortingOptionType.Sender => WinoIconGlyph.SortTextDesc,
SortingOptionType.ReceiveDate => WinoIconGlyph.SortLinesDesc,
_ => WinoIconGlyph.None,
};
public static WinoIconGlyph GetWinoIconGlyph(MailOperation operation)
{
return operation switch
{
MailOperation.None => WinoIconGlyph.None,
MailOperation.Archive => WinoIconGlyph.Archive,
MailOperation.UnArchive => WinoIconGlyph.UnArchive,
MailOperation.SoftDelete => WinoIconGlyph.Delete,
MailOperation.HardDelete => WinoIconGlyph.Delete,
MailOperation.Move => WinoIconGlyph.Forward,
MailOperation.MoveToJunk => WinoIconGlyph.Blocked,
MailOperation.MoveToFocused => WinoIconGlyph.None,
MailOperation.MoveToOther => WinoIconGlyph.None,
MailOperation.AlwaysMoveToOther => WinoIconGlyph.None,
MailOperation.AlwaysMoveToFocused => WinoIconGlyph.None,
MailOperation.SetFlag => WinoIconGlyph.Flag,
MailOperation.ClearFlag => WinoIconGlyph.ClearFlag,
MailOperation.MarkAsRead => WinoIconGlyph.MarkRead,
MailOperation.MarkAsUnread => WinoIconGlyph.MarkUnread,
MailOperation.MarkAsNotJunk => WinoIconGlyph.Blocked,
MailOperation.Ignore => WinoIconGlyph.Ignore,
MailOperation.Reply => WinoIconGlyph.Reply,
MailOperation.ReplyAll => WinoIconGlyph.ReplyAll,
MailOperation.Zoom => WinoIconGlyph.Zoom,
MailOperation.SaveAs => WinoIconGlyph.Save,
MailOperation.Print => WinoIconGlyph.Print,
MailOperation.Find => WinoIconGlyph.Find,
MailOperation.Forward => WinoIconGlyph.Forward,
MailOperation.DarkEditor => WinoIconGlyph.DarkEditor,
MailOperation.LightEditor => WinoIconGlyph.LightEditor,
MailOperation.ViewMessageSource => WinoIconGlyph.ViewMessageSource,
_ => WinoIconGlyph.None,
};
}
public static WinoIconGlyph GetPathGeometry(FolderOperation operation)
{
return operation switch
{
FolderOperation.None => WinoIconGlyph.None,
FolderOperation.Pin => WinoIconGlyph.Pin,
FolderOperation.Unpin => WinoIconGlyph.UnPin,
FolderOperation.MarkAllAsRead => WinoIconGlyph.MarkRead,
FolderOperation.DontSync => WinoIconGlyph.DontSync,
FolderOperation.Empty => WinoIconGlyph.EmptyFolder,
FolderOperation.Rename => WinoIconGlyph.Rename,
FolderOperation.Delete => WinoIconGlyph.Delete,
FolderOperation.Move => WinoIconGlyph.Forward,
FolderOperation.TurnOffNotifications => WinoIconGlyph.TurnOfNotifications,
FolderOperation.CreateSubFolder => WinoIconGlyph.CreateFolder,
_ => WinoIconGlyph.None,
};
}
public static WinoIconGlyph GetSpecialFolderPathIconGeometry(SpecialFolderType specialFolderType)
{
return specialFolderType switch
{
SpecialFolderType.Inbox => WinoIconGlyph.SpecialFolderInbox,
SpecialFolderType.Starred => WinoIconGlyph.SpecialFolderStarred,
SpecialFolderType.Important => WinoIconGlyph.SpecialFolderImportant,
SpecialFolderType.Sent => WinoIconGlyph.SpecialFolderSent,
SpecialFolderType.Draft => WinoIconGlyph.SpecialFolderDraft,
SpecialFolderType.Archive => WinoIconGlyph.SpecialFolderArchive,
SpecialFolderType.Deleted => WinoIconGlyph.SpecialFolderDeleted,
SpecialFolderType.Junk => WinoIconGlyph.SpecialFolderJunk,
SpecialFolderType.Chat => WinoIconGlyph.SpecialFolderChat,
SpecialFolderType.Category => WinoIconGlyph.SpecialFolderCategory,
SpecialFolderType.Unread => WinoIconGlyph.SpecialFolderUnread,
SpecialFolderType.Forums => WinoIconGlyph.SpecialFolderForums,
SpecialFolderType.Updates => WinoIconGlyph.SpecialFolderUpdated,
SpecialFolderType.Personal => WinoIconGlyph.SpecialFolderPersonal,
SpecialFolderType.Promotions => WinoIconGlyph.SpecialFolderPromotions,
SpecialFolderType.Social => WinoIconGlyph.SpecialFolderSocial,
SpecialFolderType.Other => WinoIconGlyph.SpecialFolderOther,
SpecialFolderType.More => WinoIconGlyph.SpecialFolderMore,
_ => WinoIconGlyph.None,
};
}
public static WinoIconGlyph GetProviderIcon(MailProviderType providerType, SpecialImapProvider specialImapProvider)
{
if (specialImapProvider == SpecialImapProvider.None)
{
return providerType switch
{
MailProviderType.Outlook => WinoIconGlyph.Microsoft,
MailProviderType.Gmail => WinoIconGlyph.Google,
MailProviderType.IMAP4 => WinoIconGlyph.IMAP,
_ => WinoIconGlyph.None,
};
}
else
{
return specialImapProvider switch
{
SpecialImapProvider.iCloud => WinoIconGlyph.Apple,
SpecialImapProvider.Yahoo => WinoIconGlyph.Yahoo,
_ => WinoIconGlyph.None,
};
}
}
public static WinoIconGlyph GetProviderIcon(MailAccount account)
=> GetProviderIcon(account.ProviderType, account.SpecialImapProvider);
public static Geometry GetPathGeometry(string pathMarkup)
{
string xaml =
"<Path " +
"xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>" +
"<Path.Data>" + pathMarkup + "</Path.Data></Path>";
var path = XamlReader.Load(xaml) as Windows.UI.Xaml.Shapes.Path;
Geometry geometry = path.Data;
path.Data = null;
return geometry;
}
#endregion
#region Internationalization
public static string GetOperationString(MailOperation operation)
{
return operation switch
{
MailOperation.None => "unknown",
MailOperation.Archive => Translator.MailOperation_Archive,
MailOperation.UnArchive => Translator.MailOperation_Unarchive,
MailOperation.SoftDelete => Translator.MailOperation_Delete,
MailOperation.HardDelete => Translator.MailOperation_Delete,
MailOperation.Move => Translator.MailOperation_Move,
MailOperation.MoveToJunk => Translator.MailOperation_MoveJunk,
MailOperation.MoveToFocused => Translator.MailOperation_MoveFocused,
MailOperation.MoveToOther => Translator.MailOperation_MoveOther,
MailOperation.AlwaysMoveToOther => Translator.MailOperation_AlwaysMoveOther,
MailOperation.AlwaysMoveToFocused => Translator.MailOperation_AlwaysMoveFocused,
MailOperation.SetFlag => Translator.MailOperation_SetFlag,
MailOperation.ClearFlag => Translator.MailOperation_ClearFlag,
MailOperation.MarkAsRead => Translator.MailOperation_MarkAsRead,
MailOperation.MarkAsUnread => Translator.MailOperation_MarkAsUnread,
MailOperation.MarkAsNotJunk => Translator.MailOperation_MarkNotJunk,
MailOperation.Seperator => string.Empty,
MailOperation.Ignore => Translator.MailOperation_Ignore,
MailOperation.Reply => Translator.MailOperation_Reply,
MailOperation.ReplyAll => Translator.MailOperation_ReplyAll,
MailOperation.Zoom => Translator.MailOperation_Zoom,
MailOperation.SaveAs => Translator.MailOperation_SaveAs,
MailOperation.Find => Translator.MailOperation_Find,
MailOperation.Forward => Translator.MailOperation_Forward,
MailOperation.DarkEditor => string.Empty,
MailOperation.LightEditor => string.Empty,
MailOperation.Print => Translator.MailOperation_Print,
MailOperation.ViewMessageSource => Translator.MailOperation_ViewMessageSource,
MailOperation.Navigate => Translator.MailOperation_Navigate,
_ => "unknown",
};
}
public static string GetOperationString(FolderOperation operation)
{
return operation switch
{
FolderOperation.None => string.Empty,
FolderOperation.Pin => Translator.FolderOperation_Pin,
FolderOperation.Unpin => Translator.FolderOperation_Unpin,
FolderOperation.MarkAllAsRead => Translator.FolderOperation_MarkAllAsRead,
FolderOperation.DontSync => Translator.FolderOperation_DontSync,
FolderOperation.Empty => Translator.FolderOperation_Empty,
FolderOperation.Rename => Translator.FolderOperation_Rename,
FolderOperation.Delete => Translator.FolderOperation_Delete,
FolderOperation.Move => Translator.FolderOperation_Move,
FolderOperation.CreateSubFolder => Translator.FolderOperation_CreateSubFolder,
_ => string.Empty,
};
}
#endregion
}
public static Visibility ReverseBoolToVisibilityConverter(bool value) => value ? Visibility.Collapsed : Visibility.Visible;
public static Visibility ReverseVisibilityConverter(Visibility visibility) => visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
public static bool ReverseBoolConverter(bool value) => !value;
public static bool ShouldDisplayPreview(string text) => text.Any(x => char.IsLetter(x));
public static bool CountToBooleanConverter(int value) => value > 0;
public static bool ObjectEquals(object obj1, object obj2) => object.Equals(obj1, obj2);
public static Visibility CountToVisibilityConverter(int value) => value > 0 ? Visibility.Visible : Visibility.Collapsed;
public static Visibility CountToVisibilityConverterWithThreshold(int value, int threshold) => value > threshold ? Visibility.Visible : Visibility.Collapsed;
public static InfoBarSeverity InfoBarSeverityConverter(InfoBarMessageType messageType)
{
return messageType switch
{
InfoBarMessageType.Information => InfoBarSeverity.Informational,
InfoBarMessageType.Success => InfoBarSeverity.Success,
InfoBarMessageType.Warning => InfoBarSeverity.Warning,
InfoBarMessageType.Error => InfoBarSeverity.Error,
_ => InfoBarSeverity.Informational,
};
}
public static SolidColorBrush GetReadableTextColor(string backgroundColor)
{
if (!backgroundColor.StartsWith("#")) throw new ArgumentException("Hex color must start with #.");
backgroundColor = backgroundColor.TrimStart('#');
if (backgroundColor.Length == 6)
{
var r = int.Parse(backgroundColor.Substring(0, 2), NumberStyles.HexNumber);
var g = int.Parse(backgroundColor.Substring(2, 2), NumberStyles.HexNumber);
var b = int.Parse(backgroundColor.Substring(4, 2), NumberStyles.HexNumber);
// Calculate relative luminance
double luminance = (0.2126 * GetLinearValue(r)) +
(0.7152 * GetLinearValue(g)) +
(0.0722 * GetLinearValue(b));
return luminance > 0.5 ? new SolidColorBrush(Colors.Black) : new SolidColorBrush(Colors.White);
}
else
{
throw new ArgumentException("Hex color must be 6 characters long (e.g., #RRGGBB).");
}
}
private static double GetLinearValue(int colorComponent)
{
double sRGB = colorComponent / 255.0;
return sRGB <= 0.03928 ? sRGB / 12.92 : Math.Pow((sRGB + 0.055) / 1.055, 2.4);
}
public static Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode NavigationViewDisplayModeConverter(SplitViewDisplayMode splitViewDisplayMode)
{
return splitViewDisplayMode switch
{
SplitViewDisplayMode.CompactOverlay => Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Compact,
SplitViewDisplayMode.CompactInline => Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Minimal,
SplitViewDisplayMode.Overlay => Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Expanded,
SplitViewDisplayMode.Inline => Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Expanded,
_ => Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Minimal,
};
}
public static string GetColorFromHex(Color color) => color.ToHex();
public static Color GetWindowsColorFromHex(string hex) => hex.ToColor();
public static SolidColorBrush GetSolidColorBrushFromHex(string colorHex) => string.IsNullOrEmpty(colorHex) ? new SolidColorBrush(Colors.Transparent) : new SolidColorBrush(colorHex.ToColor());
public static Visibility IsSelectionModeMultiple(ListViewSelectionMode mode) => mode == ListViewSelectionMode.Multiple ? Visibility.Visible : Visibility.Collapsed;
public static FontWeight GetFontWeightBySyncState(bool isSyncing) => isSyncing ? FontWeights.SemiBold : FontWeights.Normal;
public static FontWeight GetFontWeightByChildSelectedState(bool isChildSelected) => isChildSelected ? FontWeights.SemiBold : FontWeights.Normal;
public static Visibility StringToVisibilityConverter(string value) => string.IsNullOrWhiteSpace(value) ? Visibility.Collapsed : Visibility.Visible;
public static Visibility StringToVisibilityReversedConverter(string value) => string.IsNullOrWhiteSpace(value) ? Visibility.Visible : Visibility.Collapsed;
public static string GetMailItemDisplaySummaryForListing(bool isDraft, DateTime receivedDate, bool prefer24HourTime)
{
if (isDraft)
return Translator.Draft;
else
{
var localTime = receivedDate.ToLocalTime();
return prefer24HourTime ? localTime.ToString(TwentyFourHourTimeFormat) : localTime.ToString(TwelveHourTimeFormat);
}
}
public static string GetCreationDateString(DateTime date, bool prefer24HourTime)
{
var localTime = date.ToLocalTime();
return $"{localTime.ToLongDateString()} {(prefer24HourTime ? localTime.ToString(TwentyFourHourTimeFormat) : localTime.ToString(TwelveHourTimeFormat))}";
}
public static string GetMailGroupDateString(object groupObject)
{
if (groupObject is string stringObject)
return stringObject;
object dateObject = null;
// From regular mail header template
if (groupObject is DateTime groupedDate)
dateObject = groupedDate;
else if (groupObject is IGrouping<object, IMailItem> groupKey)
{
// From semantic group header.
dateObject = groupKey.Key;
}
if (dateObject != null)
{
if (dateObject is DateTime dateTimeValue)
{
if (dateTimeValue == DateTime.Today)
return Translator.Today;
else if (dateTimeValue == DateTime.Today.AddDays(-1))
return Translator.Yesterday;
else
return dateTimeValue.ToLongDateString();
}
else
return dateObject.ToString();
}
return Translator.UnknownDateHeader;
}
public static bool ConnectionStatusEquals(WinoServerConnectionStatus winoServerConnectionStatus, WinoServerConnectionStatus connectionStatus) => winoServerConnectionStatus == connectionStatus;
#endregion
#region Wino Font Icon Transformation
public static WinoIconGlyph GetWinoIconGlyph(FilterOptionType type) => type switch
{
FilterOptionType.All => WinoIconGlyph.Mail,
FilterOptionType.Unread => WinoIconGlyph.MarkUnread,
FilterOptionType.Flagged => WinoIconGlyph.Flag,
FilterOptionType.Mentions => WinoIconGlyph.NewMail,
FilterOptionType.Files => WinoIconGlyph.Attachment,
_ => WinoIconGlyph.None,
};
public static WinoIconGlyph GetWinoIconGlyph(SortingOptionType type) => type switch
{
SortingOptionType.Sender => WinoIconGlyph.SortTextDesc,
SortingOptionType.ReceiveDate => WinoIconGlyph.SortLinesDesc,
_ => WinoIconGlyph.None,
};
public static WinoIconGlyph GetWinoIconGlyph(MailOperation operation)
{
return operation switch
{
MailOperation.None => WinoIconGlyph.None,
MailOperation.Archive => WinoIconGlyph.Archive,
MailOperation.UnArchive => WinoIconGlyph.UnArchive,
MailOperation.SoftDelete => WinoIconGlyph.Delete,
MailOperation.HardDelete => WinoIconGlyph.Delete,
MailOperation.Move => WinoIconGlyph.Forward,
MailOperation.MoveToJunk => WinoIconGlyph.Blocked,
MailOperation.MoveToFocused => WinoIconGlyph.None,
MailOperation.MoveToOther => WinoIconGlyph.None,
MailOperation.AlwaysMoveToOther => WinoIconGlyph.None,
MailOperation.AlwaysMoveToFocused => WinoIconGlyph.None,
MailOperation.SetFlag => WinoIconGlyph.Flag,
MailOperation.ClearFlag => WinoIconGlyph.ClearFlag,
MailOperation.MarkAsRead => WinoIconGlyph.MarkRead,
MailOperation.MarkAsUnread => WinoIconGlyph.MarkUnread,
MailOperation.MarkAsNotJunk => WinoIconGlyph.Blocked,
MailOperation.Ignore => WinoIconGlyph.Ignore,
MailOperation.Reply => WinoIconGlyph.Reply,
MailOperation.ReplyAll => WinoIconGlyph.ReplyAll,
MailOperation.Zoom => WinoIconGlyph.Zoom,
MailOperation.SaveAs => WinoIconGlyph.Save,
MailOperation.Print => WinoIconGlyph.Print,
MailOperation.Find => WinoIconGlyph.Find,
MailOperation.Forward => WinoIconGlyph.Forward,
MailOperation.DarkEditor => WinoIconGlyph.DarkEditor,
MailOperation.LightEditor => WinoIconGlyph.LightEditor,
MailOperation.ViewMessageSource => WinoIconGlyph.ViewMessageSource,
_ => WinoIconGlyph.None,
};
}
public static WinoIconGlyph GetPathGeometry(FolderOperation operation)
{
return operation switch
{
FolderOperation.None => WinoIconGlyph.None,
FolderOperation.Pin => WinoIconGlyph.Pin,
FolderOperation.Unpin => WinoIconGlyph.UnPin,
FolderOperation.MarkAllAsRead => WinoIconGlyph.MarkRead,
FolderOperation.DontSync => WinoIconGlyph.DontSync,
FolderOperation.Empty => WinoIconGlyph.EmptyFolder,
FolderOperation.Rename => WinoIconGlyph.Rename,
FolderOperation.Delete => WinoIconGlyph.Delete,
FolderOperation.Move => WinoIconGlyph.Forward,
FolderOperation.TurnOffNotifications => WinoIconGlyph.TurnOfNotifications,
FolderOperation.CreateSubFolder => WinoIconGlyph.CreateFolder,
_ => WinoIconGlyph.None,
};
}
public static WinoIconGlyph GetSpecialFolderPathIconGeometry(SpecialFolderType specialFolderType)
{
return specialFolderType switch
{
SpecialFolderType.Inbox => WinoIconGlyph.SpecialFolderInbox,
SpecialFolderType.Starred => WinoIconGlyph.SpecialFolderStarred,
SpecialFolderType.Important => WinoIconGlyph.SpecialFolderImportant,
SpecialFolderType.Sent => WinoIconGlyph.SpecialFolderSent,
SpecialFolderType.Draft => WinoIconGlyph.SpecialFolderDraft,
SpecialFolderType.Archive => WinoIconGlyph.SpecialFolderArchive,
SpecialFolderType.Deleted => WinoIconGlyph.SpecialFolderDeleted,
SpecialFolderType.Junk => WinoIconGlyph.SpecialFolderJunk,
SpecialFolderType.Chat => WinoIconGlyph.SpecialFolderChat,
SpecialFolderType.Category => WinoIconGlyph.SpecialFolderCategory,
SpecialFolderType.Unread => WinoIconGlyph.SpecialFolderUnread,
SpecialFolderType.Forums => WinoIconGlyph.SpecialFolderForums,
SpecialFolderType.Updates => WinoIconGlyph.SpecialFolderUpdated,
SpecialFolderType.Personal => WinoIconGlyph.SpecialFolderPersonal,
SpecialFolderType.Promotions => WinoIconGlyph.SpecialFolderPromotions,
SpecialFolderType.Social => WinoIconGlyph.SpecialFolderSocial,
SpecialFolderType.Other => WinoIconGlyph.SpecialFolderOther,
SpecialFolderType.More => WinoIconGlyph.SpecialFolderMore,
_ => WinoIconGlyph.None,
};
}
public static WinoIconGlyph GetProviderIcon(MailProviderType providerType, SpecialImapProvider specialImapProvider)
{
if (specialImapProvider == SpecialImapProvider.None)
{
return providerType switch
{
MailProviderType.Outlook => WinoIconGlyph.Microsoft,
MailProviderType.Gmail => WinoIconGlyph.Google,
MailProviderType.IMAP4 => WinoIconGlyph.IMAP,
_ => WinoIconGlyph.None,
};
}
else
{
return specialImapProvider switch
{
SpecialImapProvider.iCloud => WinoIconGlyph.Apple,
SpecialImapProvider.Yahoo => WinoIconGlyph.Yahoo,
_ => WinoIconGlyph.None,
};
}
}
public static WinoIconGlyph GetProviderIcon(MailAccount account)
=> GetProviderIcon(account.ProviderType, account.SpecialImapProvider);
public static Geometry GetPathGeometry(string pathMarkup)
{
string xaml =
"<Path " +
"xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>" +
"<Path.Data>" + pathMarkup + "</Path.Data></Path>";
var path = XamlReader.Load(xaml) as Windows.UI.Xaml.Shapes.Path;
Geometry geometry = path.Data;
path.Data = null;
return geometry;
}
#endregion
#region Internationalization
public static string GetOperationString(MailOperation operation)
{
return operation switch
{
MailOperation.None => "unknown",
MailOperation.Archive => Translator.MailOperation_Archive,
MailOperation.UnArchive => Translator.MailOperation_Unarchive,
MailOperation.SoftDelete => Translator.MailOperation_Delete,
MailOperation.HardDelete => Translator.MailOperation_Delete,
MailOperation.Move => Translator.MailOperation_Move,
MailOperation.MoveToJunk => Translator.MailOperation_MoveJunk,
MailOperation.MoveToFocused => Translator.MailOperation_MoveFocused,
MailOperation.MoveToOther => Translator.MailOperation_MoveOther,
MailOperation.AlwaysMoveToOther => Translator.MailOperation_AlwaysMoveOther,
MailOperation.AlwaysMoveToFocused => Translator.MailOperation_AlwaysMoveFocused,
MailOperation.SetFlag => Translator.MailOperation_SetFlag,
MailOperation.ClearFlag => Translator.MailOperation_ClearFlag,
MailOperation.MarkAsRead => Translator.MailOperation_MarkAsRead,
MailOperation.MarkAsUnread => Translator.MailOperation_MarkAsUnread,
MailOperation.MarkAsNotJunk => Translator.MailOperation_MarkNotJunk,
MailOperation.Seperator => string.Empty,
MailOperation.Ignore => Translator.MailOperation_Ignore,
MailOperation.Reply => Translator.MailOperation_Reply,
MailOperation.ReplyAll => Translator.MailOperation_ReplyAll,
MailOperation.Zoom => Translator.MailOperation_Zoom,
MailOperation.SaveAs => Translator.MailOperation_SaveAs,
MailOperation.Find => Translator.MailOperation_Find,
MailOperation.Forward => Translator.MailOperation_Forward,
MailOperation.DarkEditor => string.Empty,
MailOperation.LightEditor => string.Empty,
MailOperation.Print => Translator.MailOperation_Print,
MailOperation.ViewMessageSource => Translator.MailOperation_ViewMessageSource,
MailOperation.Navigate => Translator.MailOperation_Navigate,
_ => "unknown",
};
}
public static string GetOperationString(FolderOperation operation)
{
return operation switch
{
FolderOperation.None => string.Empty,
FolderOperation.Pin => Translator.FolderOperation_Pin,
FolderOperation.Unpin => Translator.FolderOperation_Unpin,
FolderOperation.MarkAllAsRead => Translator.FolderOperation_MarkAllAsRead,
FolderOperation.DontSync => Translator.FolderOperation_DontSync,
FolderOperation.Empty => Translator.FolderOperation_Empty,
FolderOperation.Rename => Translator.FolderOperation_Rename,
FolderOperation.Delete => Translator.FolderOperation_Delete,
FolderOperation.Move => Translator.FolderOperation_Move,
FolderOperation.CreateSubFolder => Translator.FolderOperation_CreateSubFolder,
_ => string.Empty,
};
}
#endregion
}

View File

@@ -5,27 +5,26 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Personalization;
using Wino.Services;
namespace Wino.Core.UWP.Models.Personalization
namespace Wino.Core.UWP.Models.Personalization;
/// <summary>
/// Custom themes that are generated by users.
/// </summary>
public class CustomAppTheme : AppThemeBase
{
/// <summary>
/// Custom themes that are generated by users.
/// </summary>
public class CustomAppTheme : AppThemeBase
public CustomAppTheme(CustomThemeMetadata metadata) : base(metadata.Name, metadata.Id)
{
public CustomAppTheme(CustomThemeMetadata metadata) : base(metadata.Name, metadata.Id)
{
AccentColor = metadata.AccentColorHex;
}
AccentColor = metadata.AccentColorHex;
}
public override AppThemeType AppThemeType => AppThemeType.Custom;
public override AppThemeType AppThemeType => AppThemeType.Custom;
public override string GetBackgroundPreviewImagePath()
=> $"ms-appdata:///local/{ThemeService.CustomThemeFolderName}/{Id}_preview.jpg";
public override string GetBackgroundPreviewImagePath()
=> $"ms-appdata:///local/{ThemeService.CustomThemeFolderName}/{Id}_preview.jpg";
public override async Task<string> GetThemeResourceDictionaryContentAsync()
{
var customAppThemeFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Wino.Core.UWP/AppThemes/Custom.xaml"));
return await FileIO.ReadTextAsync(customAppThemeFile);
}
public override async Task<string> GetThemeResourceDictionaryContentAsync()
{
var customAppThemeFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Wino.Core.UWP/AppThemes/Custom.xaml"));
return await FileIO.ReadTextAsync(customAppThemeFile);
}
}

View File

@@ -4,31 +4,30 @@ using Windows.Storage;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Personalization;
namespace Wino.Core.UWP.Models.Personalization
namespace Wino.Core.UWP.Models.Personalization;
/// <summary>
/// Forest, Nighty, Clouds etc. applies to pre-defined themes in Wino.
/// </summary>
public class PreDefinedAppTheme : AppThemeBase
{
/// <summary>
/// Forest, Nighty, Clouds etc. applies to pre-defined themes in Wino.
/// </summary>
public class PreDefinedAppTheme : AppThemeBase
public PreDefinedAppTheme(string themeName,
Guid id,
string accentColor = "",
ApplicationElementTheme forcedElementTheme = ApplicationElementTheme.Default) : base(themeName, id)
{
public PreDefinedAppTheme(string themeName,
Guid id,
string accentColor = "",
ApplicationElementTheme forcedElementTheme = ApplicationElementTheme.Default) : base(themeName, id)
{
AccentColor = accentColor;
ForceElementTheme = forcedElementTheme;
}
AccentColor = accentColor;
ForceElementTheme = forcedElementTheme;
}
public override AppThemeType AppThemeType => AppThemeType.PreDefined;
public override AppThemeType AppThemeType => AppThemeType.PreDefined;
public override string GetBackgroundPreviewImagePath()
=> $"ms-appx:///Wino.Core.UWP/BackgroundImages/{ThemeName}.jpg";
public override string GetBackgroundPreviewImagePath()
=> $"ms-appx:///Wino.Core.UWP/BackgroundImages/{ThemeName}.jpg";
public override async Task<string> GetThemeResourceDictionaryContentAsync()
{
var xamlDictionaryFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri($"ms-appx:///Wino.Core.UWP/AppThemes/{ThemeName}.xaml"));
return await FileIO.ReadTextAsync(xamlDictionaryFile);
}
public override async Task<string> GetThemeResourceDictionaryContentAsync()
{
var xamlDictionaryFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri($"ms-appx:///Wino.Core.UWP/AppThemes/{ThemeName}.xaml"));
return await FileIO.ReadTextAsync(xamlDictionaryFile);
}
}

View File

@@ -1,13 +1,12 @@
using System;
using Wino.Core.Domain.Enums;
namespace Wino.Core.UWP.Models.Personalization
{
// Mica - Acrylic.
public class SystemAppTheme : PreDefinedAppTheme
{
public SystemAppTheme(string themeName, Guid id) : base(themeName, id, "") { }
namespace Wino.Core.UWP.Models.Personalization;
public override AppThemeType AppThemeType => AppThemeType.System;
}
// Mica - Acrylic.
public class SystemAppTheme : PreDefinedAppTheme
{
public SystemAppTheme(string themeName, Guid id) : base(themeName, id, "") { }
public override AppThemeType AppThemeType => AppThemeType.System;
}

View File

@@ -2,24 +2,23 @@
using Windows.UI.Xaml.Controls;
using Wino.Core.UWP.Models.Personalization;
namespace Wino.Core.UWP.Selectors
namespace Wino.Core.UWP.Selectors;
public partial class AppThemePreviewTemplateSelector : DataTemplateSelector
{
public partial class AppThemePreviewTemplateSelector : DataTemplateSelector
public DataTemplate SystemThemeTemplate { get; set; }
public DataTemplate PreDefinedThemeTemplate { get; set; }
public DataTemplate CustomAppTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
public DataTemplate SystemThemeTemplate { get; set; }
public DataTemplate PreDefinedThemeTemplate { get; set; }
public DataTemplate CustomAppTemplate { get; set; }
if (item is SystemAppTheme)
return SystemThemeTemplate;
else if (item is PreDefinedAppTheme)
return PreDefinedThemeTemplate;
else if (item is CustomAppTheme)
return CustomAppTemplate;
protected override DataTemplate SelectTemplateCore(object item)
{
if (item is SystemAppTheme)
return SystemThemeTemplate;
else if (item is PreDefinedAppTheme)
return PreDefinedThemeTemplate;
else if (item is CustomAppTheme)
return CustomAppTemplate;
return base.SelectTemplateCore(item);
}
return base.SelectTemplateCore(item);
}
}

View File

@@ -3,36 +3,35 @@ using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Enums;
namespace Wino.Core.UWP.Selectors
namespace Wino.Core.UWP.Selectors;
public partial class CustomWinoMessageDialogIconSelector : DataTemplateSelector
{
public partial class CustomWinoMessageDialogIconSelector : DataTemplateSelector
public DataTemplate InfoIconTemplate { get; set; }
public DataTemplate WarningIconTemplate { get; set; }
public DataTemplate QuestionIconTemplate { get; set; }
public DataTemplate ErrorIconTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
public DataTemplate InfoIconTemplate { get; set; }
public DataTemplate WarningIconTemplate { get; set; }
public DataTemplate QuestionIconTemplate { get; set; }
public DataTemplate ErrorIconTemplate { get; set; }
if (item == null) return null;
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
if (item is WinoCustomMessageDialogIcon icon)
{
if (item == null) return null;
if (item is WinoCustomMessageDialogIcon icon)
switch (icon)
{
switch (icon)
{
case WinoCustomMessageDialogIcon.Information:
return InfoIconTemplate;
case WinoCustomMessageDialogIcon.Warning:
return WarningIconTemplate;
case WinoCustomMessageDialogIcon.Error:
return ErrorIconTemplate;
case WinoCustomMessageDialogIcon.Question:
return QuestionIconTemplate;
default:
throw new Exception("Unknown custom message dialog icon.");
}
case WinoCustomMessageDialogIcon.Information:
return InfoIconTemplate;
case WinoCustomMessageDialogIcon.Warning:
return WarningIconTemplate;
case WinoCustomMessageDialogIcon.Error:
return ErrorIconTemplate;
case WinoCustomMessageDialogIcon.Question:
return QuestionIconTemplate;
default:
throw new Exception("Unknown custom message dialog icon.");
}
return base.SelectTemplateCore(item, container);
}
return base.SelectTemplateCore(item, container);
}
}

View File

@@ -2,51 +2,50 @@
using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Enums;
namespace Wino.Core.UWP.Selectors
namespace Wino.Core.UWP.Selectors;
public partial class FileAttachmentTypeSelector : DataTemplateSelector
{
public partial class FileAttachmentTypeSelector : DataTemplateSelector
public DataTemplate None { get; set; }
public DataTemplate Executable { get; set; }
public DataTemplate Image { get; set; }
public DataTemplate Audio { get; set; }
public DataTemplate Video { get; set; }
public DataTemplate PDF { get; set; }
public DataTemplate HTML { get; set; }
public DataTemplate RarArchive { get; set; }
public DataTemplate Archive { get; set; }
public DataTemplate Other { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
public DataTemplate None { get; set; }
public DataTemplate Executable { get; set; }
public DataTemplate Image { get; set; }
public DataTemplate Audio { get; set; }
public DataTemplate Video { get; set; }
public DataTemplate PDF { get; set; }
public DataTemplate HTML { get; set; }
public DataTemplate RarArchive { get; set; }
public DataTemplate Archive { get; set; }
public DataTemplate Other { get; set; }
if (item == null)
return None;
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
var type = (MailAttachmentType)item;
switch (type)
{
if (item == null)
case MailAttachmentType.None:
return None;
var type = (MailAttachmentType)item;
switch (type)
{
case MailAttachmentType.None:
return None;
case MailAttachmentType.Executable:
return Executable;
case MailAttachmentType.Image:
return Image;
case MailAttachmentType.Audio:
return Audio;
case MailAttachmentType.Video:
return Video;
case MailAttachmentType.PDF:
return PDF;
case MailAttachmentType.HTML:
return HTML;
case MailAttachmentType.RarArchive:
return RarArchive;
case MailAttachmentType.Archive:
return Archive;
default:
return Other;
}
case MailAttachmentType.Executable:
return Executable;
case MailAttachmentType.Image:
return Image;
case MailAttachmentType.Audio:
return Audio;
case MailAttachmentType.Video:
return Video;
case MailAttachmentType.PDF:
return PDF;
case MailAttachmentType.HTML:
return HTML;
case MailAttachmentType.RarArchive:
return RarArchive;
case MailAttachmentType.Archive:
return Archive;
default:
return Other;
}
}
}

View File

@@ -2,58 +2,57 @@
using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.MenuItems;
namespace Wino.Core.UWP.Selectors
{
public partial class NavigationMenuTemplateSelector : DataTemplateSelector
{
public DataTemplate MenuItemTemplate { get; set; }
public DataTemplate AccountManagementTemplate { get; set; }
public DataTemplate ClickableAccountMenuTemplate { get; set; }
public DataTemplate MergedAccountTemplate { get; set; }
public DataTemplate MergedAccountFolderTemplate { get; set; }
public DataTemplate MergedAccountMoreExpansionItemTemplate { get; set; }
public DataTemplate FolderMenuTemplate { get; set; }
public DataTemplate SettingsItemTemplate { get; set; }
public DataTemplate MoreItemsFolderTemplate { get; set; }
public DataTemplate RatingItemTemplate { get; set; }
public DataTemplate CreateNewFolderTemplate { get; set; }
public DataTemplate SeperatorTemplate { get; set; }
public DataTemplate NewMailTemplate { get; set; }
public DataTemplate CategoryItemsTemplate { get; set; }
public DataTemplate FixAuthenticationIssueTemplate { get; set; }
public DataTemplate FixMissingFolderConfigTemplate { get; set; }
namespace Wino.Core.UWP.Selectors;
protected override DataTemplate SelectTemplateCore(object item)
public partial class NavigationMenuTemplateSelector : DataTemplateSelector
{
public DataTemplate MenuItemTemplate { get; set; }
public DataTemplate AccountManagementTemplate { get; set; }
public DataTemplate ClickableAccountMenuTemplate { get; set; }
public DataTemplate MergedAccountTemplate { get; set; }
public DataTemplate MergedAccountFolderTemplate { get; set; }
public DataTemplate MergedAccountMoreExpansionItemTemplate { get; set; }
public DataTemplate FolderMenuTemplate { get; set; }
public DataTemplate SettingsItemTemplate { get; set; }
public DataTemplate MoreItemsFolderTemplate { get; set; }
public DataTemplate RatingItemTemplate { get; set; }
public DataTemplate CreateNewFolderTemplate { get; set; }
public DataTemplate SeperatorTemplate { get; set; }
public DataTemplate NewMailTemplate { get; set; }
public DataTemplate CategoryItemsTemplate { get; set; }
public DataTemplate FixAuthenticationIssueTemplate { get; set; }
public DataTemplate FixMissingFolderConfigTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
if (item is NewMailMenuItem)
return NewMailTemplate;
else if (item is SettingsItem)
return SettingsItemTemplate;
else if (item is SeperatorItem)
return SeperatorTemplate;
else if (item is AccountMenuItem accountMenuItem)
// Merged inbox account menu items must be nested.
return ClickableAccountMenuTemplate;
else if (item is ManageAccountsMenuItem)
return AccountManagementTemplate;
else if (item is RateMenuItem)
return RatingItemTemplate;
else if (item is MergedAccountMenuItem)
return MergedAccountTemplate;
else if (item is MergedAccountMoreFolderMenuItem)
return MergedAccountMoreExpansionItemTemplate;
else if (item is MergedAccountFolderMenuItem)
return MergedAccountFolderTemplate;
else if (item is FolderMenuItem)
return FolderMenuTemplate;
else if (item is FixAccountIssuesMenuItem fixAccountIssuesMenuItem)
return fixAccountIssuesMenuItem.Account.AttentionReason == Domain.Enums.AccountAttentionReason.MissingSystemFolderConfiguration
? FixMissingFolderConfigTemplate : FixAuthenticationIssueTemplate;
else
{
if (item is NewMailMenuItem)
return NewMailTemplate;
else if (item is SettingsItem)
return SettingsItemTemplate;
else if (item is SeperatorItem)
return SeperatorTemplate;
else if (item is AccountMenuItem accountMenuItem)
// Merged inbox account menu items must be nested.
return ClickableAccountMenuTemplate;
else if (item is ManageAccountsMenuItem)
return AccountManagementTemplate;
else if (item is RateMenuItem)
return RatingItemTemplate;
else if (item is MergedAccountMenuItem)
return MergedAccountTemplate;
else if (item is MergedAccountMoreFolderMenuItem)
return MergedAccountMoreExpansionItemTemplate;
else if (item is MergedAccountFolderMenuItem)
return MergedAccountFolderTemplate;
else if (item is FolderMenuItem)
return FolderMenuTemplate;
else if (item is FixAccountIssuesMenuItem fixAccountIssuesMenuItem)
return fixAccountIssuesMenuItem.Account.AttentionReason == Domain.Enums.AccountAttentionReason.MissingSystemFolderConfiguration
? FixMissingFolderConfigTemplate : FixAuthenticationIssueTemplate;
else
{
var type = item.GetType();
return null;
}
var type = item.GetType();
return null;
}
}
}

View File

@@ -3,25 +3,24 @@ using Windows.UI.Xaml;
using Wino.Core.Domain.Interfaces;
using Wino.Core.UWP;
namespace Wino.Services
namespace Wino.Services;
public class ApplicationResourceManager : IApplicationResourceManager<ResourceDictionary>
{
public class ApplicationResourceManager : IApplicationResourceManager<ResourceDictionary>
{
public void AddResource(ResourceDictionary resource)
=> WinoApplication.Current.Resources.MergedDictionaries.Add(resource);
public void RemoveResource(ResourceDictionary resource)
=> WinoApplication.Current.Resources.MergedDictionaries.Remove(resource);
public void AddResource(ResourceDictionary resource)
=> WinoApplication.Current.Resources.MergedDictionaries.Add(resource);
public void RemoveResource(ResourceDictionary resource)
=> WinoApplication.Current.Resources.MergedDictionaries.Remove(resource);
public bool ContainsResourceKey(string resourceKey)
=> WinoApplication.Current.Resources.ContainsKey(resourceKey);
public bool ContainsResourceKey(string resourceKey)
=> WinoApplication.Current.Resources.ContainsKey(resourceKey);
public ResourceDictionary GetLastResource()
=> WinoApplication.Current.Resources.MergedDictionaries.LastOrDefault();
public ResourceDictionary GetLastResource()
=> WinoApplication.Current.Resources.MergedDictionaries.LastOrDefault();
public void ReplaceResource(string resourceKey, object resource)
=> WinoApplication.Current.Resources[resourceKey] = resource;
public void ReplaceResource(string resourceKey, object resource)
=> WinoApplication.Current.Resources[resourceKey] = resource;
public TReturnType GetResource<TReturnType>(string resourceKey)
=> (TReturnType)WinoApplication.Current.Resources[resourceKey];
}
public TReturnType GetResource<TReturnType>(string resourceKey)
=> (TReturnType)WinoApplication.Current.Resources[resourceKey];
}

View File

@@ -5,59 +5,58 @@ using Serilog;
using Windows.ApplicationModel.Background;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.UWP.Services
namespace Wino.Core.UWP.Services;
public class BackgroundTaskService : IBackgroundTaskService
{
public class BackgroundTaskService : IBackgroundTaskService
private const string IsBackgroundTasksUnregisteredKey = nameof(IsBackgroundTasksUnregisteredKey);
public const string ToastNotificationActivationHandlerTaskName = "ToastNotificationActivationHandlerTask";
private readonly IConfigurationService _configurationService;
public BackgroundTaskService(IConfigurationService configurationService)
{
private const string IsBackgroundTasksUnregisteredKey = nameof(IsBackgroundTasksUnregisteredKey);
public const string ToastNotificationActivationHandlerTaskName = "ToastNotificationActivationHandlerTask";
_configurationService = configurationService;
}
private readonly IConfigurationService _configurationService;
public BackgroundTaskService(IConfigurationService configurationService)
public void UnregisterAllBackgroundTask()
{
if (_configurationService.Get(IsBackgroundTasksUnregisteredKey, false))
{
_configurationService = configurationService;
}
public void UnregisterAllBackgroundTask()
{
if (_configurationService.Get(IsBackgroundTasksUnregisteredKey, false))
foreach (var task in BackgroundTaskRegistration.AllTasks)
{
foreach (var task in BackgroundTaskRegistration.AllTasks)
{
task.Value.Unregister(true);
}
Log.Information("Unregistered all background tasks.");
_configurationService.Set(IsBackgroundTasksUnregisteredKey, true);
task.Value.Unregister(true);
}
}
public Task RegisterBackgroundTasksAsync()
{
return RegisterToastNotificationHandlerBackgroundTaskAsync();
}
public async Task RegisterToastNotificationHandlerBackgroundTaskAsync()
{
// If background task is already registered, do nothing.
if (BackgroundTaskRegistration.AllTasks.Any(i => i.Value.Name.Equals(ToastNotificationActivationHandlerTaskName)))
return;
// Otherwise request access
BackgroundAccessStatus status = await BackgroundExecutionManager.RequestAccessAsync();
// Create the background task
BackgroundTaskBuilder builder = new BackgroundTaskBuilder()
{
Name = ToastNotificationActivationHandlerTaskName
};
// Assign the toast action trigger
builder.SetTrigger(new ToastNotificationActionTrigger());
// And register the task
BackgroundTaskRegistration registration = builder.Register();
Log.Information("Unregistered all background tasks.");
_configurationService.Set(IsBackgroundTasksUnregisteredKey, true);
}
}
public Task RegisterBackgroundTasksAsync()
{
return RegisterToastNotificationHandlerBackgroundTaskAsync();
}
public async Task RegisterToastNotificationHandlerBackgroundTaskAsync()
{
// If background task is already registered, do nothing.
if (BackgroundTaskRegistration.AllTasks.Any(i => i.Value.Name.Equals(ToastNotificationActivationHandlerTaskName)))
return;
// Otherwise request access
BackgroundAccessStatus status = await BackgroundExecutionManager.RequestAccessAsync();
// Create the background task
BackgroundTaskBuilder builder = new BackgroundTaskBuilder()
{
Name = ToastNotificationActivationHandlerTaskName
};
// Assign the toast action trigger
builder.SetTrigger(new ToastNotificationActionTrigger());
// And register the task
BackgroundTaskRegistration registration = builder.Register();
}
}

View File

@@ -2,18 +2,17 @@
using Windows.ApplicationModel.DataTransfer;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.UWP.Services
namespace Wino.Core.UWP.Services;
public class ClipboardService : IClipboardService
{
public class ClipboardService : IClipboardService
public Task CopyClipboardAsync(string text)
{
public Task CopyClipboardAsync(string text)
{
var package = new DataPackage();
package.SetText(text);
var package = new DataPackage();
package.SetText(text);
Clipboard.SetContent(package);
Clipboard.SetContent(package);
return Task.CompletedTask;
}
return Task.CompletedTask;
}
}

View File

@@ -5,48 +5,47 @@ using Windows.Foundation.Collections;
using Windows.Storage;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.UWP.Services
namespace Wino.Core.UWP.Services;
public class ConfigurationService : IConfigurationService
{
public class ConfigurationService : IConfigurationService
public T Get<T>(string key, T defaultValue = default)
=> GetInternal(key, ApplicationData.Current.LocalSettings.Values, defaultValue);
public T GetRoaming<T>(string key, T defaultValue = default)
=> GetInternal(key, ApplicationData.Current.RoamingSettings.Values, defaultValue);
public void Set(string key, object value)
=> SetInternal(key, value, ApplicationData.Current.LocalSettings.Values);
public void SetRoaming(string key, object value)
=> SetInternal(key, value, ApplicationData.Current.RoamingSettings.Values);
private static T GetInternal<T>(string key, IPropertySet collection, T defaultValue = default)
{
public T Get<T>(string key, T defaultValue = default)
=> GetInternal(key, ApplicationData.Current.LocalSettings.Values, defaultValue);
public T GetRoaming<T>(string key, T defaultValue = default)
=> GetInternal(key, ApplicationData.Current.RoamingSettings.Values, defaultValue);
public void Set(string key, object value)
=> SetInternal(key, value, ApplicationData.Current.LocalSettings.Values);
public void SetRoaming(string key, object value)
=> SetInternal(key, value, ApplicationData.Current.RoamingSettings.Values);
private static T GetInternal<T>(string key, IPropertySet collection, T defaultValue = default)
if (collection.TryGetValue(key, out object value))
{
if (collection.TryGetValue(key, out object value))
var stringValue = value?.ToString();
if (typeof(T).IsEnum)
return (T)Enum.Parse(typeof(T), stringValue);
if ((typeof(T) == typeof(Guid?) || typeof(T) == typeof(Guid)) && Guid.TryParse(stringValue, out Guid guidResult))
{
var stringValue = value?.ToString();
if (typeof(T).IsEnum)
return (T)Enum.Parse(typeof(T), stringValue);
if ((typeof(T) == typeof(Guid?) || typeof(T) == typeof(Guid)) && Guid.TryParse(stringValue, out Guid guidResult))
{
return (T)(object)guidResult;
}
if (typeof(T) == typeof(TimeSpan))
{
return (T)(object)TimeSpan.Parse(stringValue);
}
return (T)Convert.ChangeType(stringValue, typeof(T));
return (T)(object)guidResult;
}
return defaultValue;
if (typeof(T) == typeof(TimeSpan))
{
return (T)(object)TimeSpan.Parse(stringValue);
}
return (T)Convert.ChangeType(stringValue, typeof(T));
}
private static void SetInternal(string key, object value, IPropertySet collection)
=> collection[key] = value?.ToString();
return defaultValue;
}
private static void SetInternal(string key, object value, IPropertySet collection)
=> collection[key] = value?.ToString();
}

View File

@@ -20,284 +20,283 @@ using Wino.Core.UWP.Extensions;
using Wino.Dialogs;
using Wino.Messaging.Client.Shell;
namespace Wino.Core.UWP.Services
namespace Wino.Core.UWP.Services;
public class DialogServiceBase : IDialogServiceBase
{
public class DialogServiceBase : IDialogServiceBase
private SemaphoreSlim _presentationSemaphore = new SemaphoreSlim(1);
protected IThemeService ThemeService { get; }
protected IConfigurationService ConfigurationService { get; }
protected IApplicationResourceManager<ResourceDictionary> ApplicationResourceManager { get; }
public DialogServiceBase(IThemeService themeService, IConfigurationService configurationService, IApplicationResourceManager<ResourceDictionary> applicationResourceManager)
{
private SemaphoreSlim _presentationSemaphore = new SemaphoreSlim(1);
ThemeService = themeService;
ConfigurationService = configurationService;
ApplicationResourceManager = applicationResourceManager;
}
protected IThemeService ThemeService { get; }
protected IConfigurationService ConfigurationService { get; }
protected IApplicationResourceManager<ResourceDictionary> ApplicationResourceManager { get; }
public DialogServiceBase(IThemeService themeService, IConfigurationService configurationService, IApplicationResourceManager<ResourceDictionary> applicationResourceManager)
public async Task<MailAccount> ShowEditAccountDialogAsync(MailAccount account)
{
var editAccountDialog = new AccountEditDialog(account)
{
ThemeService = themeService;
ConfigurationService = configurationService;
ApplicationResourceManager = applicationResourceManager;
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(editAccountDialog);
return editAccountDialog.IsSaved ? editAccountDialog.Account : null;
}
public async Task<string> PickFilePathAsync(string saveFileName)
{
var picker = new FolderPicker()
{
SuggestedStartLocation = PickerLocationId.Desktop
};
picker.FileTypeFilter.Add("*");
var folder = await picker.PickSingleFolderAsync();
if (folder == null) return string.Empty;
StorageApplicationPermissions.FutureAccessList.Add(folder);
return folder.Path;
//var picker = new FileSavePicker
//{
// SuggestedStartLocation = PickerLocationId.Desktop,
// SuggestedFileName = saveFileName
//};
//picker.FileTypeChoices.Add(Translator.FilteringOption_All, [".*"]);
//var file = await picker.PickSaveFileAsync();
//if (file == null) return string.Empty;
//StorageApplicationPermissions.FutureAccessList.Add(file);
//return file.Path;
}
public async Task<List<SharedFile>> PickFilesAsync(params object[] typeFilters)
{
var returnList = new List<SharedFile>();
var picker = new FileOpenPicker
{
ViewMode = PickerViewMode.Thumbnail,
SuggestedStartLocation = PickerLocationId.Desktop
};
foreach (var filter in typeFilters)
{
picker.FileTypeFilter.Add(filter.ToString());
}
public async Task<MailAccount> ShowEditAccountDialogAsync(MailAccount account)
var files = await picker.PickMultipleFilesAsync();
if (files == null) return returnList;
foreach (var file in files)
{
var editAccountDialog = new AccountEditDialog(account)
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(editAccountDialog);
return editAccountDialog.IsSaved ? editAccountDialog.Account : null;
}
public async Task<string> PickFilePathAsync(string saveFileName)
{
var picker = new FolderPicker()
{
SuggestedStartLocation = PickerLocationId.Desktop
};
picker.FileTypeFilter.Add("*");
var folder = await picker.PickSingleFolderAsync();
if (folder == null) return string.Empty;
StorageApplicationPermissions.FutureAccessList.Add(folder);
return folder.Path;
//var picker = new FileSavePicker
//{
// SuggestedStartLocation = PickerLocationId.Desktop,
// SuggestedFileName = saveFileName
//};
//picker.FileTypeChoices.Add(Translator.FilteringOption_All, [".*"]);
//var file = await picker.PickSaveFileAsync();
//if (file == null) return string.Empty;
//StorageApplicationPermissions.FutureAccessList.Add(file);
//return file.Path;
}
public async Task<List<SharedFile>> PickFilesAsync(params object[] typeFilters)
{
var returnList = new List<SharedFile>();
var picker = new FileOpenPicker
{
ViewMode = PickerViewMode.Thumbnail,
SuggestedStartLocation = PickerLocationId.Desktop
};
foreach (var filter in typeFilters)
{
picker.FileTypeFilter.Add(filter.ToString());
}
var files = await picker.PickMultipleFilesAsync();
if (files == null) return returnList;
foreach (var file in files)
{
StorageApplicationPermissions.FutureAccessList.Add(file);
var sharedFile = await file.ToSharedFileAsync();
returnList.Add(sharedFile);
}
return returnList;
}
private async Task<StorageFile> PickFileAsync(params object[] typeFilters)
{
var picker = new FileOpenPicker
{
ViewMode = PickerViewMode.Thumbnail
};
foreach (var filter in typeFilters)
{
picker.FileTypeFilter.Add(filter.ToString());
}
var file = await picker.PickSingleFileAsync();
if (file == null) return null;
StorageApplicationPermissions.FutureAccessList.Add(file);
return file;
var sharedFile = await file.ToSharedFileAsync();
returnList.Add(sharedFile);
}
public virtual IAccountCreationDialog GetAccountCreationDialog(AccountCreationDialogResult accountCreationDialogResult)
return returnList;
}
private async Task<StorageFile> PickFileAsync(params object[] typeFilters)
{
var picker = new FileOpenPicker
{
return new AccountCreationDialog
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
}
ViewMode = PickerViewMode.Thumbnail
};
public async Task<byte[]> PickWindowsFileContentAsync(params object[] typeFilters)
foreach (var filter in typeFilters)
{
var file = await PickFileAsync(typeFilters);
if (file == null) return [];
return await file.ToByteArrayAsync();
picker.FileTypeFilter.Add(filter.ToString());
}
public Task ShowMessageAsync(string message, string title, WinoCustomMessageDialogIcon icon = WinoCustomMessageDialogIcon.Information)
=> ShowWinoCustomMessageDialogAsync(title, message, Translator.Buttons_Close, icon);
var file = await picker.PickSingleFileAsync();
public Task<bool> ShowConfirmationDialogAsync(string question, string title, string confirmationButtonTitle)
=> ShowWinoCustomMessageDialogAsync(title, question, confirmationButtonTitle, WinoCustomMessageDialogIcon.Question, Translator.Buttons_Cancel, string.Empty);
if (file == null) return null;
public async Task<bool> ShowWinoCustomMessageDialogAsync(string title,
string description,
string approveButtonText,
WinoCustomMessageDialogIcon? icon,
string cancelButtonText = "",
string dontAskAgainConfigurationKey = "")
StorageApplicationPermissions.FutureAccessList.Add(file);
return file;
}
public virtual IAccountCreationDialog GetAccountCreationDialog(AccountCreationDialogResult accountCreationDialogResult)
{
return new AccountCreationDialog
{
// This config key has been marked as don't ask again already.
// Return immidiate result without presenting the dialog.
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
}
bool isDontAskEnabled = !string.IsNullOrEmpty(dontAskAgainConfigurationKey);
public async Task<byte[]> PickWindowsFileContentAsync(params object[] typeFilters)
{
var file = await PickFileAsync(typeFilters);
if (isDontAskEnabled && ConfigurationService.Get(dontAskAgainConfigurationKey, false)) return false;
if (file == null) return [];
var informationContainer = new CustomMessageDialogInformationContainer(title, description, icon.Value, isDontAskEnabled);
return await file.ToByteArrayAsync();
}
var dialog = new ContentDialog
{
Style = ApplicationResourceManager.GetResource<Style>("WinoDialogStyle"),
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme(),
DefaultButton = ContentDialogButton.Primary,
PrimaryButtonText = approveButtonText,
ContentTemplate = ApplicationResourceManager.GetResource<DataTemplate>("CustomWinoContentDialogContentTemplate"),
Content = informationContainer
};
public Task ShowMessageAsync(string message, string title, WinoCustomMessageDialogIcon icon = WinoCustomMessageDialogIcon.Information)
=> ShowWinoCustomMessageDialogAsync(title, message, Translator.Buttons_Close, icon);
if (!string.IsNullOrEmpty(cancelButtonText))
{
dialog.SecondaryButtonText = cancelButtonText;
}
public Task<bool> ShowConfirmationDialogAsync(string question, string title, string confirmationButtonTitle)
=> ShowWinoCustomMessageDialogAsync(title, question, confirmationButtonTitle, WinoCustomMessageDialogIcon.Question, Translator.Buttons_Cancel, string.Empty);
var dialogResult = await HandleDialogPresentationAsync(dialog);
public async Task<bool> ShowWinoCustomMessageDialogAsync(string title,
string description,
string approveButtonText,
WinoCustomMessageDialogIcon? icon,
string cancelButtonText = "",
string dontAskAgainConfigurationKey = "")
// Mark this key to not ask again if user checked the checkbox.
if (informationContainer.IsDontAskChecked)
{
ConfigurationService.Set(dontAskAgainConfigurationKey, true);
}
{
// This config key has been marked as don't ask again already.
// Return immidiate result without presenting the dialog.
return dialogResult == ContentDialogResult.Primary;
}
bool isDontAskEnabled = !string.IsNullOrEmpty(dontAskAgainConfigurationKey);
/// <summary>
/// Waits for PopupRoot to be available before presenting the dialog and returns the result after presentation.
/// </summary>
/// <param name="dialog">Dialog to present and wait for closing.</param>
/// <returns>Dialog result from WinRT.</returns>
public async Task<ContentDialogResult> HandleDialogPresentationAsync(ContentDialog dialog)
if (isDontAskEnabled && ConfigurationService.Get(dontAskAgainConfigurationKey, false)) return false;
var informationContainer = new CustomMessageDialogInformationContainer(title, description, icon.Value, isDontAskEnabled);
var dialog = new ContentDialog
{
await _presentationSemaphore.WaitAsync();
Style = ApplicationResourceManager.GetResource<Style>("WinoDialogStyle"),
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme(),
DefaultButton = ContentDialogButton.Primary,
PrimaryButtonText = approveButtonText,
ContentTemplate = ApplicationResourceManager.GetResource<DataTemplate>("CustomWinoContentDialogContentTemplate"),
Content = informationContainer
};
try
{
return await dialog.ShowAsync();
}
catch (Exception ex)
{
Log.Error(ex, $"Handling dialog service failed. Dialog was {dialog.GetType().Name}");
}
finally
{
_presentationSemaphore.Release();
}
return ContentDialogResult.None;
}
public void InfoBarMessage(string title, string message, InfoBarMessageType messageType)
=> WeakReferenceMessenger.Default.Send(new InfoBarMessageRequested(messageType, title, message));
public void InfoBarMessage(string title, string message, InfoBarMessageType messageType, string actionButtonText, Action action)
=> WeakReferenceMessenger.Default.Send(new InfoBarMessageRequested(messageType, title, message, actionButtonText, action));
public void ShowNotSupportedMessage()
=> InfoBarMessage(Translator.Info_UnsupportedFunctionalityTitle,
Translator.Info_UnsupportedFunctionalityDescription,
InfoBarMessageType.Error);
public async Task<string> ShowTextInputDialogAsync(string currentInput, string dialogTitle, string dialogDescription, string primaryButtonText)
if (!string.IsNullOrEmpty(cancelButtonText))
{
var inputDialog = new TextInputDialog()
{
CurrentInput = currentInput,
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme(),
Title = dialogTitle
};
inputDialog.SetDescription(dialogDescription);
inputDialog.SetPrimaryButtonText(primaryButtonText);
await HandleDialogPresentationAsync(inputDialog);
if (inputDialog.HasInput.GetValueOrDefault() && !currentInput.Equals(inputDialog.CurrentInput))
return inputDialog.CurrentInput;
return string.Empty;
dialog.SecondaryButtonText = cancelButtonText;
}
public async Task<string> PickWindowsFolderAsync()
var dialogResult = await HandleDialogPresentationAsync(dialog);
// Mark this key to not ask again if user checked the checkbox.
if (informationContainer.IsDontAskChecked)
{
var picker = new FolderPicker
{
SuggestedStartLocation = PickerLocationId.DocumentsLibrary
};
picker.FileTypeFilter.Add("*");
var pickedFolder = await picker.PickSingleFolderAsync();
if (pickedFolder != null)
{
Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList.AddOrReplace("FolderPickerToken", pickedFolder);
return pickedFolder.Path;
}
return string.Empty;
ConfigurationService.Set(dontAskAgainConfigurationKey, true);
}
public async Task<bool> ShowCustomThemeBuilderDialogAsync()
return dialogResult == ContentDialogResult.Primary;
}
/// <summary>
/// Waits for PopupRoot to be available before presenting the dialog and returns the result after presentation.
/// </summary>
/// <param name="dialog">Dialog to present and wait for closing.</param>
/// <returns>Dialog result from WinRT.</returns>
public async Task<ContentDialogResult> HandleDialogPresentationAsync(ContentDialog dialog)
{
await _presentationSemaphore.WaitAsync();
try
{
var themeBuilderDialog = new CustomThemeBuilderDialog()
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
var dialogResult = await HandleDialogPresentationAsync(themeBuilderDialog);
return dialogResult == ContentDialogResult.Primary;
return await dialog.ShowAsync();
}
public async Task<AccountCreationDialogResult> ShowAccountProviderSelectionDialogAsync(List<IProviderDetail> availableProviders)
catch (Exception ex)
{
var dialog = new NewAccountDialog
{
Providers = availableProviders,
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(dialog);
return dialog.Result;
Log.Error(ex, $"Handling dialog service failed. Dialog was {dialog.GetType().Name}");
}
finally
{
_presentationSemaphore.Release();
}
return ContentDialogResult.None;
}
public void InfoBarMessage(string title, string message, InfoBarMessageType messageType)
=> WeakReferenceMessenger.Default.Send(new InfoBarMessageRequested(messageType, title, message));
public void InfoBarMessage(string title, string message, InfoBarMessageType messageType, string actionButtonText, Action action)
=> WeakReferenceMessenger.Default.Send(new InfoBarMessageRequested(messageType, title, message, actionButtonText, action));
public void ShowNotSupportedMessage()
=> InfoBarMessage(Translator.Info_UnsupportedFunctionalityTitle,
Translator.Info_UnsupportedFunctionalityDescription,
InfoBarMessageType.Error);
public async Task<string> ShowTextInputDialogAsync(string currentInput, string dialogTitle, string dialogDescription, string primaryButtonText)
{
var inputDialog = new TextInputDialog()
{
CurrentInput = currentInput,
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme(),
Title = dialogTitle
};
inputDialog.SetDescription(dialogDescription);
inputDialog.SetPrimaryButtonText(primaryButtonText);
await HandleDialogPresentationAsync(inputDialog);
if (inputDialog.HasInput.GetValueOrDefault() && !currentInput.Equals(inputDialog.CurrentInput))
return inputDialog.CurrentInput;
return string.Empty;
}
public async Task<string> PickWindowsFolderAsync()
{
var picker = new FolderPicker
{
SuggestedStartLocation = PickerLocationId.DocumentsLibrary
};
picker.FileTypeFilter.Add("*");
var pickedFolder = await picker.PickSingleFolderAsync();
if (pickedFolder != null)
{
Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList.AddOrReplace("FolderPickerToken", pickedFolder);
return pickedFolder.Path;
}
return string.Empty;
}
public async Task<bool> ShowCustomThemeBuilderDialogAsync()
{
var themeBuilderDialog = new CustomThemeBuilderDialog()
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
var dialogResult = await HandleDialogPresentationAsync(themeBuilderDialog);
return dialogResult == ContentDialogResult.Primary;
}
public async Task<AccountCreationDialogResult> ShowAccountProviderSelectionDialogAsync(List<IProviderDetail> availableProviders)
{
var dialog = new NewAccountDialog
{
Providers = availableProviders,
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(dialog);
return dialog.Result;
}
}

View File

@@ -6,57 +6,56 @@ using Windows.Storage;
using Wino.Core.Domain;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.UWP.Services
namespace Wino.Core.UWP.Services;
public class FileService : IFileService
{
public class FileService : IFileService
public async Task<string> CopyFileAsync(string sourceFilePath, string destinationFolderPath)
{
public async Task<string> CopyFileAsync(string sourceFilePath, string destinationFolderPath)
var fileName = Path.GetFileName(sourceFilePath);
var sourceFileHandle = await StorageFile.GetFileFromPathAsync(sourceFilePath);
var destinationFolder = await StorageFolder.GetFolderFromPathAsync(destinationFolderPath);
var copiedFile = await sourceFileHandle.CopyAsync(destinationFolder, fileName, NameCollisionOption.GenerateUniqueName);
return copiedFile.Path;
}
public async Task<string> GetFileContentByApplicationUriAsync(string resourcePath)
{
var releaseNoteFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri(resourcePath));
return await FileIO.ReadTextAsync(releaseNoteFile);
}
public async Task<Stream> GetFileStreamAsync(string folderPath, string fileName)
{
var folder = await StorageFolder.GetFolderFromPathAsync(folderPath);
var createdFile = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);
return await createdFile.OpenStreamForWriteAsync();
}
public async Task<bool> SaveLogsToFolderAsync(string logsFolder, string destinationFolder)
{
var logFiles = Directory.GetFiles(logsFolder, "*.log");
if (logFiles.Length == 0) return false;
using var fileStream = await GetFileStreamAsync(destinationFolder, Constants.LogArchiveFileName);
using var archive = new ZipArchive(fileStream, ZipArchiveMode.Create, true);
foreach (var logFile in logFiles)
{
var fileName = Path.GetFileName(sourceFilePath);
using FileStream logFileStream = File.Open(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var sourceFileHandle = await StorageFile.GetFileFromPathAsync(sourceFilePath);
var destinationFolder = await StorageFolder.GetFolderFromPathAsync(destinationFolderPath);
var zipArchiveEntry = archive.CreateEntry(Path.GetFileName(logFile), CompressionLevel.Fastest);
using var zipStream = zipArchiveEntry.Open();
var copiedFile = await sourceFileHandle.CopyAsync(destinationFolder, fileName, NameCollisionOption.GenerateUniqueName);
return copiedFile.Path;
await logFileStream.CopyToAsync(zipStream);
}
public async Task<string> GetFileContentByApplicationUriAsync(string resourcePath)
{
var releaseNoteFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri(resourcePath));
return await FileIO.ReadTextAsync(releaseNoteFile);
}
public async Task<Stream> GetFileStreamAsync(string folderPath, string fileName)
{
var folder = await StorageFolder.GetFolderFromPathAsync(folderPath);
var createdFile = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);
return await createdFile.OpenStreamForWriteAsync();
}
public async Task<bool> SaveLogsToFolderAsync(string logsFolder, string destinationFolder)
{
var logFiles = Directory.GetFiles(logsFolder, "*.log");
if (logFiles.Length == 0) return false;
using var fileStream = await GetFileStreamAsync(destinationFolder, Constants.LogArchiveFileName);
using var archive = new ZipArchive(fileStream, ZipArchiveMode.Create, true);
foreach (var logFile in logFiles)
{
using FileStream logFileStream = File.Open(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var zipArchiveEntry = archive.CreateEntry(Path.GetFileName(logFile), CompressionLevel.Fastest);
using var zipStream = zipArchiveEntry.Open();
await logFileStream.CopyToAsync(zipStream);
}
return true;
}
return true;
}
}

View File

@@ -3,14 +3,13 @@ using Windows.UI.Core;
using Windows.UI.Xaml;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.UWP.Services
{
public class KeyPressService : IKeyPressService
{
public bool IsCtrlKeyPressed()
=> Window.Current?.CoreWindow?.GetKeyState(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down) ?? false;
namespace Wino.Core.UWP.Services;
public bool IsShiftKeyPressed()
=> Window.Current?.CoreWindow?.GetKeyState(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down) ?? false;
}
public class KeyPressService : IKeyPressService
{
public bool IsCtrlKeyPressed()
=> Window.Current?.CoreWindow?.GetKeyState(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down) ?? false;
public bool IsShiftKeyPressed()
=> Window.Current?.CoreWindow?.GetKeyState(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down) ?? false;
}

View File

@@ -14,96 +14,95 @@ using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#endif
namespace Wino.Services
namespace Wino.Services;
public class NativeAppService : INativeAppService
{
public class NativeAppService : INativeAppService
private string _mimeMessagesFolder;
private string _editorBundlePath;
public Func<IntPtr> GetCoreWindowHwnd { get; set; }
public string GetWebAuthenticationBrokerUri()
{
private string _mimeMessagesFolder;
private string _editorBundlePath;
public Func<IntPtr> GetCoreWindowHwnd { get; set; }
public string GetWebAuthenticationBrokerUri()
{
#if WINDOWS_UWP
return WebAuthenticationBroker.GetCurrentApplicationCallbackUri().AbsoluteUri;
return WebAuthenticationBroker.GetCurrentApplicationCallbackUri().AbsoluteUri;
#endif
return string.Empty;
}
public async Task<string> GetMimeMessageStoragePath()
{
if (!string.IsNullOrEmpty(_mimeMessagesFolder))
return _mimeMessagesFolder;
var localFolder = ApplicationData.Current.LocalFolder;
var mimeFolder = await localFolder.CreateFolderAsync("Mime", CreationCollisionOption.OpenIfExists);
_mimeMessagesFolder = mimeFolder.Path;
return string.Empty;
}
public async Task<string> GetMimeMessageStoragePath()
{
if (!string.IsNullOrEmpty(_mimeMessagesFolder))
return _mimeMessagesFolder;
var localFolder = ApplicationData.Current.LocalFolder;
var mimeFolder = await localFolder.CreateFolderAsync("Mime", CreationCollisionOption.OpenIfExists);
_mimeMessagesFolder = mimeFolder.Path;
return _mimeMessagesFolder;
}
public async Task<string> GetEditorBundlePathAsync()
{
if (string.IsNullOrEmpty(_editorBundlePath))
{
var editorFileFromBundle = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///JS/editor.html"))
.AsTask()
.ConfigureAwait(false);
_editorBundlePath = editorFileFromBundle.Path;
}
return _editorBundlePath;
}
public async Task<string> GetEditorBundlePathAsync()
{
if (string.IsNullOrEmpty(_editorBundlePath))
{
var editorFileFromBundle = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///JS/editor.html"))
.AsTask()
.ConfigureAwait(false);
_editorBundlePath = editorFileFromBundle.Path;
}
return _editorBundlePath;
}
[Obsolete("This should be removed. There should be no functionality.")]
public bool IsAppRunning()
{
[Obsolete("This should be removed. There should be no functionality.")]
public bool IsAppRunning()
{
#if WINDOWS_UWP
return (Window.Current?.Content as Frame)?.Content != null;
return (Window.Current?.Content as Frame)?.Content != null;
#endif
return true;
}
return true;
}
public async Task LaunchFileAsync(string filePath)
{
var file = await StorageFile.GetFileFromPathAsync(filePath);
public async Task LaunchFileAsync(string filePath)
{
var file = await StorageFile.GetFileFromPathAsync(filePath);
await Launcher.LaunchFileAsync(file);
}
await Launcher.LaunchFileAsync(file);
}
public Task<bool> LaunchUriAsync(Uri uri) => Launcher.LaunchUriAsync(uri).AsTask();
public Task<bool> LaunchUriAsync(Uri uri) => Launcher.LaunchUriAsync(uri).AsTask();
public string GetFullAppVersion()
{
Package package = Package.Current;
PackageId packageId = package.Id;
PackageVersion version = packageId.Version;
public string GetFullAppVersion()
{
Package package = Package.Current;
PackageId packageId = package.Id;
PackageVersion version = packageId.Version;
return string.Format("{0}.{1}.{2}.{3}", version.Major, version.Minor, version.Build, version.Revision);
}
return string.Format("{0}.{1}.{2}.{3}", version.Major, version.Minor, version.Build, version.Revision);
}
public async Task PinAppToTaskbarAsync()
{
// If Start screen manager API's aren't present
if (!ApiInformation.IsTypePresent("Windows.UI.Shell.TaskbarManager")) return;
public async Task PinAppToTaskbarAsync()
{
// If Start screen manager API's aren't present
if (!ApiInformation.IsTypePresent("Windows.UI.Shell.TaskbarManager")) return;
// Get the taskbar manager
var taskbarManager = TaskbarManager.GetDefault();
// Get the taskbar manager
var taskbarManager = TaskbarManager.GetDefault();
// If Taskbar doesn't allow pinning, don't show the tip
if (!taskbarManager.IsPinningAllowed) return;
// If Taskbar doesn't allow pinning, don't show the tip
if (!taskbarManager.IsPinningAllowed) return;
// If already pinned, don't show the tip
if (await taskbarManager.IsCurrentAppPinnedAsync()) return;
// If already pinned, don't show the tip
if (await taskbarManager.IsCurrentAppPinnedAsync()) return;
await taskbarManager.RequestPinCurrentAppAsync();
}
await taskbarManager.RequestPinCurrentAppAsync();
}
}

View File

@@ -3,26 +3,25 @@ using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Animation;
using Wino.Core.Domain.Models.Navigation;
namespace Wino.Core.UWP.Services
namespace Wino.Core.UWP.Services;
public class NavigationServiceBase
{
public class NavigationServiceBase
public NavigationTransitionInfo GetNavigationTransitionInfo(NavigationTransitionType transition)
{
public NavigationTransitionInfo GetNavigationTransitionInfo(NavigationTransitionType transition)
return transition switch
{
return transition switch
{
NavigationTransitionType.DrillIn => new DrillInNavigationTransitionInfo(),
NavigationTransitionType.Entrance => new EntranceNavigationTransitionInfo(),
_ => new SuppressNavigationTransitionInfo(),
};
}
NavigationTransitionType.DrillIn => new DrillInNavigationTransitionInfo(),
NavigationTransitionType.Entrance => new EntranceNavigationTransitionInfo(),
_ => new SuppressNavigationTransitionInfo(),
};
}
public Type GetCurrentFrameType(ref Frame _frame)
{
if (_frame != null && _frame.Content != null)
return _frame.Content.GetType();
public Type GetCurrentFrameType(ref Frame _frame)
{
if (_frame != null && _frame.Content != null)
return _frame.Content.GetType();
return null;
}
return null;
}
}

View File

@@ -13,43 +13,109 @@ using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Services;
namespace Wino.Core.UWP.Services
namespace Wino.Core.UWP.Services;
// TODO: Refactor this thing. It's garbage.
public class NotificationBuilder : INotificationBuilder
{
// TODO: Refactor this thing. It's garbage.
private readonly IUnderlyingThemeService _underlyingThemeService;
private readonly IAccountService _accountService;
private readonly IFolderService _folderService;
private readonly IMailService _mailService;
public class NotificationBuilder : INotificationBuilder
public NotificationBuilder(IUnderlyingThemeService underlyingThemeService,
IAccountService accountService,
IFolderService folderService,
IMailService mailService)
{
private readonly IUnderlyingThemeService _underlyingThemeService;
private readonly IAccountService _accountService;
private readonly IFolderService _folderService;
private readonly IMailService _mailService;
_underlyingThemeService = underlyingThemeService;
_accountService = accountService;
_folderService = folderService;
_mailService = mailService;
}
public NotificationBuilder(IUnderlyingThemeService underlyingThemeService,
IAccountService accountService,
IFolderService folderService,
IMailService mailService)
public async Task CreateNotificationsAsync(Guid inboxFolderId, IEnumerable<IMailItem> downloadedMailItems)
{
var mailCount = downloadedMailItems.Count();
try
{
_underlyingThemeService = underlyingThemeService;
_accountService = accountService;
_folderService = folderService;
_mailService = mailService;
}
public async Task CreateNotificationsAsync(Guid inboxFolderId, IEnumerable<IMailItem> downloadedMailItems)
{
var mailCount = downloadedMailItems.Count();
try
// If there are more than 3 mails, just display 1 general toast.
if (mailCount > 3)
{
// If there are more than 3 mails, just display 1 general toast.
if (mailCount > 3)
var builder = new ToastContentBuilder();
builder.SetToastScenario(ToastScenario.Default);
builder.AddText(Translator.Notifications_MultipleNotificationsTitle);
builder.AddText(string.Format(Translator.Notifications_MultipleNotificationsMessage, mailCount));
builder.AddButton(GetDismissButton());
builder.AddAudio(new ToastAudio()
{
Src = new Uri("ms-winsoundevent:Notification.Mail")
});
builder.Show();
}
else
{
var validItems = new List<IMailItem>();
// Fetch mails again to fill up assigned folder data and latest statuses.
// They've been marked as read by executing synchronizer tasks until inital sync finishes.
foreach (var item in downloadedMailItems)
{
var mailItem = await _mailService.GetSingleMailItemAsync(item.UniqueId);
if (mailItem != null && mailItem.AssignedFolder != null)
{
validItems.Add(mailItem);
}
}
foreach (var mailItem in validItems)
{
if (mailItem.IsRead)
continue;
var builder = new ToastContentBuilder();
builder.SetToastScenario(ToastScenario.Default);
builder.AddText(Translator.Notifications_MultipleNotificationsTitle);
builder.AddText(string.Format(Translator.Notifications_MultipleNotificationsMessage, mailCount));
var host = ThumbnailService.GetHost(mailItem.FromAddress);
var knownTuple = ThumbnailService.CheckIsKnown(host);
bool isKnown = knownTuple.Item1;
host = knownTuple.Item2;
if (isKnown)
builder.AddAppLogoOverride(new System.Uri(ThumbnailService.GetKnownHostImage(host)), hintCrop: ToastGenericAppLogoCrop.Default);
else
{
// TODO: https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/adaptive-interactive-toasts?tabs=toolkit
// Follow official guides for icons/theme.
bool isOSDarkTheme = _underlyingThemeService.IsUnderlyingThemeDark();
string profileLogoName = isOSDarkTheme ? "profile-dark.png" : "profile-light.png";
builder.AddAppLogoOverride(new System.Uri($"ms-appx:///Assets/NotificationIcons/{profileLogoName}"), hintCrop: ToastGenericAppLogoCrop.Circle);
}
// Override system notification timetamp with received date of the mail.
// It may create confusion for some users, but still it's the truth...
builder.AddCustomTimeStamp(mailItem.CreationDate.ToLocalTime());
builder.AddText(mailItem.FromName);
builder.AddText(mailItem.Subject);
builder.AddText(mailItem.PreviewText);
builder.AddArgument(Constants.ToastMailUniqueIdKey, mailItem.UniqueId.ToString());
builder.AddArgument(Constants.ToastActionKey, MailOperation.Navigate);
builder.AddButton(GetMarkedAsRead(mailItem.UniqueId));
builder.AddButton(GetDeleteButton(mailItem.UniqueId));
builder.AddButton(GetDismissButton());
builder.AddAudio(new ToastAudio()
{
@@ -58,174 +124,107 @@ namespace Wino.Core.UWP.Services
builder.Show();
}
else
{
var validItems = new List<IMailItem>();
// Fetch mails again to fill up assigned folder data and latest statuses.
// They've been marked as read by executing synchronizer tasks until inital sync finishes.
foreach (var item in downloadedMailItems)
{
var mailItem = await _mailService.GetSingleMailItemAsync(item.UniqueId);
if (mailItem != null && mailItem.AssignedFolder != null)
{
validItems.Add(mailItem);
}
}
foreach (var mailItem in validItems)
{
if (mailItem.IsRead)
continue;
var builder = new ToastContentBuilder();
builder.SetToastScenario(ToastScenario.Default);
var host = ThumbnailService.GetHost(mailItem.FromAddress);
var knownTuple = ThumbnailService.CheckIsKnown(host);
bool isKnown = knownTuple.Item1;
host = knownTuple.Item2;
if (isKnown)
builder.AddAppLogoOverride(new System.Uri(ThumbnailService.GetKnownHostImage(host)), hintCrop: ToastGenericAppLogoCrop.Default);
else
{
// TODO: https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/adaptive-interactive-toasts?tabs=toolkit
// Follow official guides for icons/theme.
bool isOSDarkTheme = _underlyingThemeService.IsUnderlyingThemeDark();
string profileLogoName = isOSDarkTheme ? "profile-dark.png" : "profile-light.png";
builder.AddAppLogoOverride(new System.Uri($"ms-appx:///Assets/NotificationIcons/{profileLogoName}"), hintCrop: ToastGenericAppLogoCrop.Circle);
}
// Override system notification timetamp with received date of the mail.
// It may create confusion for some users, but still it's the truth...
builder.AddCustomTimeStamp(mailItem.CreationDate.ToLocalTime());
builder.AddText(mailItem.FromName);
builder.AddText(mailItem.Subject);
builder.AddText(mailItem.PreviewText);
builder.AddArgument(Constants.ToastMailUniqueIdKey, mailItem.UniqueId.ToString());
builder.AddArgument(Constants.ToastActionKey, MailOperation.Navigate);
builder.AddButton(GetMarkedAsRead(mailItem.UniqueId));
builder.AddButton(GetDeleteButton(mailItem.UniqueId));
builder.AddButton(GetDismissButton());
builder.AddAudio(new ToastAudio()
{
Src = new Uri("ms-winsoundevent:Notification.Mail")
});
builder.Show();
}
await UpdateTaskbarIconBadgeAsync();
}
}
catch (Exception ex)
{
Log.Error(ex, "Failed to create notifications.");
await UpdateTaskbarIconBadgeAsync();
}
}
private ToastButton GetDismissButton()
=> new ToastButton()
.SetDismissActivation()
.SetImageUri(new Uri("ms-appx:///Assets/NotificationIcons/dismiss.png"));
private ToastButton GetDeleteButton(Guid mailUniqueId)
=> new ToastButton()
.SetContent(Translator.MailOperation_Delete)
.SetImageUri(new Uri("ms-appx:///Assets/NotificationIcons/delete.png"))
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
.AddArgument(Constants.ToastActionKey, MailOperation.SoftDelete)
.SetBackgroundActivation();
private ToastButton GetMarkedAsRead(Guid mailUniqueId)
=> new ToastButton()
.SetContent(Translator.MailOperation_MarkAsRead)
.SetImageUri(new System.Uri("ms-appx:///Assets/NotificationIcons/markread.png"))
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
.AddArgument(Constants.ToastActionKey, MailOperation.MarkAsRead)
.SetBackgroundActivation();
public async Task UpdateTaskbarIconBadgeAsync()
catch (Exception ex)
{
int totalUnreadCount = 0;
try
{
var badgeUpdater = BadgeUpdateManager.CreateBadgeUpdaterForApplication();
var accounts = await _accountService.GetAccountsAsync();
foreach (var account in accounts)
{
if (!account.Preferences.IsTaskbarBadgeEnabled) continue;
var accountInbox = await _folderService.GetSpecialFolderByAccountIdAsync(account.Id, SpecialFolderType.Inbox);
if (accountInbox == null)
continue;
var inboxUnreadCount = await _folderService.GetFolderNotificationBadgeAsync(accountInbox.Id);
totalUnreadCount += inboxUnreadCount;
}
if (totalUnreadCount > 0)
{
// Get the blank badge XML payload for a badge number
XmlDocument badgeXml = BadgeUpdateManager.GetTemplateContent(BadgeTemplateType.BadgeNumber);
// Set the value of the badge in the XML to our number
XmlElement badgeElement = badgeXml.SelectSingleNode("/badge") as XmlElement;
badgeElement.SetAttribute("value", totalUnreadCount.ToString());
// Create the badge notification
BadgeNotification badge = new BadgeNotification(badgeXml);
// And update the badge
badgeUpdater.Update(badge);
}
else
badgeUpdater.Clear();
}
catch (Exception ex)
{
Log.Error(ex, "Error while updating taskbar badge.");
}
}
public async Task CreateTestNotificationAsync(string title, string message)
{
// with args test.
await CreateNotificationsAsync(Guid.Parse("28c3c39b-7147-4de3-b209-949bd19eede6"), new List<IMailItem>()
{
new MailCopy()
{
Subject = "test subject",
PreviewText = "preview html",
CreationDate = DateTime.UtcNow,
FromAddress = "bkaankose@outlook.com",
Id = "AAkALgAAAAAAHYQDEapmEc2byACqAC-EWg0AnMdP0zg8wkS_Ib2Eeh80LAAGq91I3QAA",
}
});
//var builder = new ToastContentBuilder();
//builder.SetToastScenario(ToastScenario.Default);
//builder.AddText(title);
//builder.AddText(message);
//builder.Show();
//await Task.CompletedTask;
Log.Error(ex, "Failed to create notifications.");
}
}
private ToastButton GetDismissButton()
=> new ToastButton()
.SetDismissActivation()
.SetImageUri(new Uri("ms-appx:///Assets/NotificationIcons/dismiss.png"));
private ToastButton GetDeleteButton(Guid mailUniqueId)
=> new ToastButton()
.SetContent(Translator.MailOperation_Delete)
.SetImageUri(new Uri("ms-appx:///Assets/NotificationIcons/delete.png"))
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
.AddArgument(Constants.ToastActionKey, MailOperation.SoftDelete)
.SetBackgroundActivation();
private ToastButton GetMarkedAsRead(Guid mailUniqueId)
=> new ToastButton()
.SetContent(Translator.MailOperation_MarkAsRead)
.SetImageUri(new System.Uri("ms-appx:///Assets/NotificationIcons/markread.png"))
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
.AddArgument(Constants.ToastActionKey, MailOperation.MarkAsRead)
.SetBackgroundActivation();
public async Task UpdateTaskbarIconBadgeAsync()
{
int totalUnreadCount = 0;
try
{
var badgeUpdater = BadgeUpdateManager.CreateBadgeUpdaterForApplication();
var accounts = await _accountService.GetAccountsAsync();
foreach (var account in accounts)
{
if (!account.Preferences.IsTaskbarBadgeEnabled) continue;
var accountInbox = await _folderService.GetSpecialFolderByAccountIdAsync(account.Id, SpecialFolderType.Inbox);
if (accountInbox == null)
continue;
var inboxUnreadCount = await _folderService.GetFolderNotificationBadgeAsync(accountInbox.Id);
totalUnreadCount += inboxUnreadCount;
}
if (totalUnreadCount > 0)
{
// Get the blank badge XML payload for a badge number
XmlDocument badgeXml = BadgeUpdateManager.GetTemplateContent(BadgeTemplateType.BadgeNumber);
// Set the value of the badge in the XML to our number
XmlElement badgeElement = badgeXml.SelectSingleNode("/badge") as XmlElement;
badgeElement.SetAttribute("value", totalUnreadCount.ToString());
// Create the badge notification
BadgeNotification badge = new BadgeNotification(badgeXml);
// And update the badge
badgeUpdater.Update(badge);
}
else
badgeUpdater.Clear();
}
catch (Exception ex)
{
Log.Error(ex, "Error while updating taskbar badge.");
}
}
public async Task CreateTestNotificationAsync(string title, string message)
{
// with args test.
await CreateNotificationsAsync(Guid.Parse("28c3c39b-7147-4de3-b209-949bd19eede6"), new List<IMailItem>()
{
new MailCopy()
{
Subject = "test subject",
PreviewText = "preview html",
CreationDate = DateTime.UtcNow,
FromAddress = "bkaankose@outlook.com",
Id = "AAkALgAAAAAAHYQDEapmEc2byACqAC-EWg0AnMdP0zg8wkS_Ib2Eeh80LAAGq91I3QAA",
}
});
//var builder = new ToastContentBuilder();
//builder.SetToastScenario(ToastScenario.Default);
//builder.AddText(title);
//builder.AddText(message);
//builder.Show();
//await Task.CompletedTask;
}
}

View File

@@ -11,300 +11,299 @@ using Wino.Core.Domain.Models.Reader;
using Wino.Core.Domain.Translations;
using Wino.Services;
namespace Wino.Core.UWP.Services
namespace Wino.Core.UWP.Services;
public class PreferencesService : ObservableObject, IPreferencesService
{
public class PreferencesService : ObservableObject, IPreferencesService
private readonly IConfigurationService _configurationService;
public event EventHandler<string> PreferenceChanged;
public PreferencesService(IConfigurationService configurationService)
{
private readonly IConfigurationService _configurationService;
_configurationService = configurationService;
}
public event EventHandler<string> PreferenceChanged;
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
public PreferencesService(IConfigurationService configurationService)
PreferenceChanged?.Invoke(this, e.PropertyName);
}
private void SaveProperty(string propertyName, object value) => _configurationService.Set(propertyName, value);
private void SetPropertyAndSave(string propertyName, object value)
{
_configurationService.Set(propertyName, value);
OnPropertyChanged(propertyName);
Debug.WriteLine($"PreferencesService -> {propertyName}:{value?.ToString()}");
}
public MailRenderingOptions GetRenderingOptions()
=> new MailRenderingOptions()
{
_configurationService = configurationService;
LoadImages = RenderImages,
LoadStyles = RenderStyles,
RenderPlaintextLinks = RenderPlaintextLinks
};
public MailListDisplayMode MailItemDisplayMode
{
get => _configurationService.Get(nameof(MailItemDisplayMode), MailListDisplayMode.Spacious);
set => SetPropertyAndSave(nameof(MailItemDisplayMode), value);
}
public bool IsSemanticZoomEnabled
{
get => _configurationService.Get(nameof(IsSemanticZoomEnabled), true);
set => SetPropertyAndSave(nameof(IsSemanticZoomEnabled), value);
}
public bool IsHardDeleteProtectionEnabled
{
get => _configurationService.Get(nameof(IsHardDeleteProtectionEnabled), true);
set => SetPropertyAndSave(nameof(IsHardDeleteProtectionEnabled), value);
}
public bool IsThreadingEnabled
{
get => _configurationService.Get(nameof(IsThreadingEnabled), true);
set => SetPropertyAndSave(nameof(IsThreadingEnabled), value);
}
public bool IsMailListActionBarEnabled
{
get => _configurationService.Get(nameof(IsMailListActionBarEnabled), false);
set => SetPropertyAndSave(nameof(IsMailListActionBarEnabled), value);
}
public bool IsShowSenderPicturesEnabled
{
get => _configurationService.Get(nameof(IsShowSenderPicturesEnabled), true);
set => SetPropertyAndSave(nameof(IsShowSenderPicturesEnabled), value);
}
public bool IsShowPreviewEnabled
{
get => _configurationService.Get(nameof(IsShowPreviewEnabled), true);
set => SetPropertyAndSave(nameof(IsShowPreviewEnabled), value);
}
public bool RenderStyles
{
get => _configurationService.Get(nameof(RenderStyles), true);
set => SetPropertyAndSave(nameof(RenderStyles), value);
}
public bool RenderPlaintextLinks
{
get => _configurationService.Get(nameof(RenderPlaintextLinks), true);
set => SetPropertyAndSave(nameof(RenderPlaintextLinks), value);
}
public bool RenderImages
{
get => _configurationService.Get(nameof(RenderImages), true);
set => SetPropertyAndSave(nameof(RenderImages), value);
}
public bool Prefer24HourTimeFormat
{
get => _configurationService.Get(nameof(Prefer24HourTimeFormat), false);
set => SetPropertyAndSave(nameof(Prefer24HourTimeFormat), value);
}
public MailMarkAsOption MarkAsPreference
{
get => _configurationService.Get(nameof(MarkAsPreference), MailMarkAsOption.WhenSelected);
set => SetPropertyAndSave(nameof(MarkAsPreference), value);
}
public int MarkAsDelay
{
get => _configurationService.Get(nameof(MarkAsDelay), 5);
set => SetPropertyAndSave(nameof(MarkAsDelay), value);
}
public MailOperation RightSwipeOperation
{
get => _configurationService.Get(nameof(RightSwipeOperation), MailOperation.MarkAsRead);
set => SetPropertyAndSave(nameof(RightSwipeOperation), value);
}
public MailOperation LeftSwipeOperation
{
get => _configurationService.Get(nameof(LeftSwipeOperation), MailOperation.SoftDelete);
set => SetPropertyAndSave(nameof(LeftSwipeOperation), value);
}
public bool IsHoverActionsEnabled
{
get => _configurationService.Get(nameof(IsHoverActionsEnabled), true);
set => SetPropertyAndSave(nameof(IsHoverActionsEnabled), value);
}
public MailOperation LeftHoverAction
{
get => _configurationService.Get(nameof(LeftHoverAction), MailOperation.Archive);
set => SetPropertyAndSave(nameof(LeftHoverAction), value);
}
public MailOperation CenterHoverAction
{
get => _configurationService.Get(nameof(CenterHoverAction), MailOperation.SoftDelete);
set => SetPropertyAndSave(nameof(CenterHoverAction), value);
}
public MailOperation RightHoverAction
{
get => _configurationService.Get(nameof(RightHoverAction), MailOperation.SetFlag);
set => SetPropertyAndSave(nameof(RightHoverAction), value);
}
public bool IsLoggingEnabled
{
get => _configurationService.Get(nameof(IsLoggingEnabled), true);
set => SetPropertyAndSave(nameof(IsLoggingEnabled), value);
}
public bool IsMailkitProtocolLoggerEnabled
{
get => _configurationService.Get(nameof(IsMailkitProtocolLoggerEnabled), false);
set => SetPropertyAndSave(nameof(IsMailkitProtocolLoggerEnabled), value);
}
public Guid? StartupEntityId
{
get => _configurationService.Get<Guid?>(nameof(StartupEntityId), null);
set => SaveProperty(propertyName: nameof(StartupEntityId), value);
}
public AppLanguage CurrentLanguage
{
get => _configurationService.Get(nameof(CurrentLanguage), TranslationService.DefaultAppLanguage);
set => SaveProperty(propertyName: nameof(CurrentLanguage), value);
}
public string ReaderFont
{
get => _configurationService.Get(nameof(ReaderFont), "Calibri");
set => SaveProperty(propertyName: nameof(ReaderFont), value);
}
public int ReaderFontSize
{
get => _configurationService.Get(nameof(ReaderFontSize), 14);
set => SaveProperty(propertyName: nameof(ReaderFontSize), value);
}
public string ComposerFont
{
get => _configurationService.Get(nameof(ComposerFont), "Calibri");
set => SaveProperty(propertyName: nameof(ComposerFont), value);
}
public int ComposerFontSize
{
get => _configurationService.Get(nameof(ComposerFontSize), 14);
set => SaveProperty(propertyName: nameof(ComposerFontSize), value);
}
public bool IsNavigationPaneOpened
{
get => _configurationService.Get(nameof(IsNavigationPaneOpened), true);
set => SaveProperty(propertyName: nameof(IsNavigationPaneOpened), value);
}
public bool AutoSelectNextItem
{
get => _configurationService.Get(nameof(AutoSelectNextItem), true);
set => SaveProperty(propertyName: nameof(AutoSelectNextItem), value);
}
public ServerBackgroundMode ServerTerminationBehavior
{
get => _configurationService.Get(nameof(ServerTerminationBehavior), ServerBackgroundMode.MinimizedTray);
set => SaveProperty(propertyName: nameof(ServerTerminationBehavior), value);
}
public string DiagnosticId
{
get => _configurationService.Get(nameof(DiagnosticId), Guid.NewGuid().ToString());
set => SaveProperty(propertyName: nameof(DiagnosticId), value);
}
public DayOfWeek FirstDayOfWeek
{
get => _configurationService.Get(nameof(FirstDayOfWeek), DayOfWeek.Monday);
set => SaveProperty(propertyName: nameof(FirstDayOfWeek), value);
}
public double HourHeight
{
get => _configurationService.Get(nameof(HourHeight), 60.0);
set => SaveProperty(propertyName: nameof(HourHeight), value);
}
public TimeSpan WorkingHourStart
{
get => _configurationService.Get(nameof(WorkingHourStart), new TimeSpan(8, 0, 0));
set => SaveProperty(propertyName: nameof(WorkingHourStart), value);
}
public TimeSpan WorkingHourEnd
{
get => _configurationService.Get(nameof(WorkingHourEnd), new TimeSpan(17, 0, 0));
set => SaveProperty(propertyName: nameof(WorkingHourEnd), value);
}
public DayOfWeek WorkingDayStart
{
get => _configurationService.Get(nameof(WorkingDayStart), DayOfWeek.Monday);
set => SaveProperty(propertyName: nameof(WorkingDayStart), value);
}
public DayOfWeek WorkingDayEnd
{
get => _configurationService.Get(nameof(WorkingDayEnd), DayOfWeek.Friday);
set => SaveProperty(propertyName: nameof(WorkingDayEnd), value);
}
public CalendarSettings GetCurrentCalendarSettings()
{
var workingDays = GetDaysBetween(WorkingDayStart, WorkingDayEnd);
return new CalendarSettings(FirstDayOfWeek,
workingDays,
WorkingHourStart,
WorkingHourEnd,
HourHeight,
Prefer24HourTimeFormat ? DayHeaderDisplayType.TwentyFourHour : DayHeaderDisplayType.TwelveHour,
new CultureInfo(WinoTranslationDictionary.GetLanguageFileNameRelativePath(CurrentLanguage)));
}
private List<DayOfWeek> GetDaysBetween(DayOfWeek startDay, DayOfWeek endDay)
{
var daysOfWeek = new List<DayOfWeek>();
int currentDay = (int)startDay;
int endDayInt = (int)endDay;
// If endDay is before startDay in the week, wrap around
if (endDayInt < currentDay)
{
endDayInt += 7;
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
// Collect days from startDay to endDay
while (currentDay <= endDayInt)
{
base.OnPropertyChanged(e);
PreferenceChanged?.Invoke(this, e.PropertyName);
daysOfWeek.Add((DayOfWeek)(currentDay % 7));
currentDay++;
}
private void SaveProperty(string propertyName, object value) => _configurationService.Set(propertyName, value);
private void SetPropertyAndSave(string propertyName, object value)
{
_configurationService.Set(propertyName, value);
OnPropertyChanged(propertyName);
Debug.WriteLine($"PreferencesService -> {propertyName}:{value?.ToString()}");
}
public MailRenderingOptions GetRenderingOptions()
=> new MailRenderingOptions()
{
LoadImages = RenderImages,
LoadStyles = RenderStyles,
RenderPlaintextLinks = RenderPlaintextLinks
};
public MailListDisplayMode MailItemDisplayMode
{
get => _configurationService.Get(nameof(MailItemDisplayMode), MailListDisplayMode.Spacious);
set => SetPropertyAndSave(nameof(MailItemDisplayMode), value);
}
public bool IsSemanticZoomEnabled
{
get => _configurationService.Get(nameof(IsSemanticZoomEnabled), true);
set => SetPropertyAndSave(nameof(IsSemanticZoomEnabled), value);
}
public bool IsHardDeleteProtectionEnabled
{
get => _configurationService.Get(nameof(IsHardDeleteProtectionEnabled), true);
set => SetPropertyAndSave(nameof(IsHardDeleteProtectionEnabled), value);
}
public bool IsThreadingEnabled
{
get => _configurationService.Get(nameof(IsThreadingEnabled), true);
set => SetPropertyAndSave(nameof(IsThreadingEnabled), value);
}
public bool IsMailListActionBarEnabled
{
get => _configurationService.Get(nameof(IsMailListActionBarEnabled), false);
set => SetPropertyAndSave(nameof(IsMailListActionBarEnabled), value);
}
public bool IsShowSenderPicturesEnabled
{
get => _configurationService.Get(nameof(IsShowSenderPicturesEnabled), true);
set => SetPropertyAndSave(nameof(IsShowSenderPicturesEnabled), value);
}
public bool IsShowPreviewEnabled
{
get => _configurationService.Get(nameof(IsShowPreviewEnabled), true);
set => SetPropertyAndSave(nameof(IsShowPreviewEnabled), value);
}
public bool RenderStyles
{
get => _configurationService.Get(nameof(RenderStyles), true);
set => SetPropertyAndSave(nameof(RenderStyles), value);
}
public bool RenderPlaintextLinks
{
get => _configurationService.Get(nameof(RenderPlaintextLinks), true);
set => SetPropertyAndSave(nameof(RenderPlaintextLinks), value);
}
public bool RenderImages
{
get => _configurationService.Get(nameof(RenderImages), true);
set => SetPropertyAndSave(nameof(RenderImages), value);
}
public bool Prefer24HourTimeFormat
{
get => _configurationService.Get(nameof(Prefer24HourTimeFormat), false);
set => SetPropertyAndSave(nameof(Prefer24HourTimeFormat), value);
}
public MailMarkAsOption MarkAsPreference
{
get => _configurationService.Get(nameof(MarkAsPreference), MailMarkAsOption.WhenSelected);
set => SetPropertyAndSave(nameof(MarkAsPreference), value);
}
public int MarkAsDelay
{
get => _configurationService.Get(nameof(MarkAsDelay), 5);
set => SetPropertyAndSave(nameof(MarkAsDelay), value);
}
public MailOperation RightSwipeOperation
{
get => _configurationService.Get(nameof(RightSwipeOperation), MailOperation.MarkAsRead);
set => SetPropertyAndSave(nameof(RightSwipeOperation), value);
}
public MailOperation LeftSwipeOperation
{
get => _configurationService.Get(nameof(LeftSwipeOperation), MailOperation.SoftDelete);
set => SetPropertyAndSave(nameof(LeftSwipeOperation), value);
}
public bool IsHoverActionsEnabled
{
get => _configurationService.Get(nameof(IsHoverActionsEnabled), true);
set => SetPropertyAndSave(nameof(IsHoverActionsEnabled), value);
}
public MailOperation LeftHoverAction
{
get => _configurationService.Get(nameof(LeftHoverAction), MailOperation.Archive);
set => SetPropertyAndSave(nameof(LeftHoverAction), value);
}
public MailOperation CenterHoverAction
{
get => _configurationService.Get(nameof(CenterHoverAction), MailOperation.SoftDelete);
set => SetPropertyAndSave(nameof(CenterHoverAction), value);
}
public MailOperation RightHoverAction
{
get => _configurationService.Get(nameof(RightHoverAction), MailOperation.SetFlag);
set => SetPropertyAndSave(nameof(RightHoverAction), value);
}
public bool IsLoggingEnabled
{
get => _configurationService.Get(nameof(IsLoggingEnabled), true);
set => SetPropertyAndSave(nameof(IsLoggingEnabled), value);
}
public bool IsMailkitProtocolLoggerEnabled
{
get => _configurationService.Get(nameof(IsMailkitProtocolLoggerEnabled), false);
set => SetPropertyAndSave(nameof(IsMailkitProtocolLoggerEnabled), value);
}
public Guid? StartupEntityId
{
get => _configurationService.Get<Guid?>(nameof(StartupEntityId), null);
set => SaveProperty(propertyName: nameof(StartupEntityId), value);
}
public AppLanguage CurrentLanguage
{
get => _configurationService.Get(nameof(CurrentLanguage), TranslationService.DefaultAppLanguage);
set => SaveProperty(propertyName: nameof(CurrentLanguage), value);
}
public string ReaderFont
{
get => _configurationService.Get(nameof(ReaderFont), "Calibri");
set => SaveProperty(propertyName: nameof(ReaderFont), value);
}
public int ReaderFontSize
{
get => _configurationService.Get(nameof(ReaderFontSize), 14);
set => SaveProperty(propertyName: nameof(ReaderFontSize), value);
}
public string ComposerFont
{
get => _configurationService.Get(nameof(ComposerFont), "Calibri");
set => SaveProperty(propertyName: nameof(ComposerFont), value);
}
public int ComposerFontSize
{
get => _configurationService.Get(nameof(ComposerFontSize), 14);
set => SaveProperty(propertyName: nameof(ComposerFontSize), value);
}
public bool IsNavigationPaneOpened
{
get => _configurationService.Get(nameof(IsNavigationPaneOpened), true);
set => SaveProperty(propertyName: nameof(IsNavigationPaneOpened), value);
}
public bool AutoSelectNextItem
{
get => _configurationService.Get(nameof(AutoSelectNextItem), true);
set => SaveProperty(propertyName: nameof(AutoSelectNextItem), value);
}
public ServerBackgroundMode ServerTerminationBehavior
{
get => _configurationService.Get(nameof(ServerTerminationBehavior), ServerBackgroundMode.MinimizedTray);
set => SaveProperty(propertyName: nameof(ServerTerminationBehavior), value);
}
public string DiagnosticId
{
get => _configurationService.Get(nameof(DiagnosticId), Guid.NewGuid().ToString());
set => SaveProperty(propertyName: nameof(DiagnosticId), value);
}
public DayOfWeek FirstDayOfWeek
{
get => _configurationService.Get(nameof(FirstDayOfWeek), DayOfWeek.Monday);
set => SaveProperty(propertyName: nameof(FirstDayOfWeek), value);
}
public double HourHeight
{
get => _configurationService.Get(nameof(HourHeight), 60.0);
set => SaveProperty(propertyName: nameof(HourHeight), value);
}
public TimeSpan WorkingHourStart
{
get => _configurationService.Get(nameof(WorkingHourStart), new TimeSpan(8, 0, 0));
set => SaveProperty(propertyName: nameof(WorkingHourStart), value);
}
public TimeSpan WorkingHourEnd
{
get => _configurationService.Get(nameof(WorkingHourEnd), new TimeSpan(17, 0, 0));
set => SaveProperty(propertyName: nameof(WorkingHourEnd), value);
}
public DayOfWeek WorkingDayStart
{
get => _configurationService.Get(nameof(WorkingDayStart), DayOfWeek.Monday);
set => SaveProperty(propertyName: nameof(WorkingDayStart), value);
}
public DayOfWeek WorkingDayEnd
{
get => _configurationService.Get(nameof(WorkingDayEnd), DayOfWeek.Friday);
set => SaveProperty(propertyName: nameof(WorkingDayEnd), value);
}
public CalendarSettings GetCurrentCalendarSettings()
{
var workingDays = GetDaysBetween(WorkingDayStart, WorkingDayEnd);
return new CalendarSettings(FirstDayOfWeek,
workingDays,
WorkingHourStart,
WorkingHourEnd,
HourHeight,
Prefer24HourTimeFormat ? DayHeaderDisplayType.TwentyFourHour : DayHeaderDisplayType.TwelveHour,
new CultureInfo(WinoTranslationDictionary.GetLanguageFileNameRelativePath(CurrentLanguage)));
}
private List<DayOfWeek> GetDaysBetween(DayOfWeek startDay, DayOfWeek endDay)
{
var daysOfWeek = new List<DayOfWeek>();
int currentDay = (int)startDay;
int endDayInt = (int)endDay;
// If endDay is before startDay in the week, wrap around
if (endDayInt < currentDay)
{
endDayInt += 7;
}
// Collect days from startDay to endDay
while (currentDay <= endDayInt)
{
daysOfWeek.Add((DayOfWeek)(currentDay % 7));
currentDay++;
}
return daysOfWeek;
}
return daysOfWeek;
}
}

View File

@@ -12,254 +12,253 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Printing;
namespace Wino.Core.UWP.Services
namespace Wino.Core.UWP.Services;
/// <summary>
/// Printer service that uses WinRT APIs to print PDF files.
/// Used modified version of the code here:
/// https://github.com/microsoft/Win2D-Samples/blob/reunion_master/ExampleGallery/PrintingExample.xaml.cs
/// HTML file is saved as PDF to temporary location.
/// Then PDF is loaded as PdfDocument and printed using CanvasBitmap for each page.
/// </summary>
public class PrintService : IPrintService
{
/// <summary>
/// Printer service that uses WinRT APIs to print PDF files.
/// Used modified version of the code here:
/// https://github.com/microsoft/Win2D-Samples/blob/reunion_master/ExampleGallery/PrintingExample.xaml.cs
/// HTML file is saved as PDF to temporary location.
/// Then PDF is loaded as PdfDocument and printed using CanvasBitmap for each page.
/// </summary>
public class PrintService : IPrintService
private TaskCompletionSource<PrintingResult> _taskCompletionSource;
private CanvasPrintDocument printDocument;
private PrintTask printTask;
private PdfDocument pdfDocument;
private List<CanvasBitmap> bitmaps = new();
private Vector2 largestBitmap;
private Vector2 pageSize;
private Vector2 imagePadding = new Vector2(64, 64);
private Vector2 cellSize;
private int bitmapCount;
private int columns;
private int rows;
private int bitmapsPerPage;
private int pageCount = -1;
private PrintInformation _currentPrintInformation;
public async Task<PrintingResult> PrintPdfFileAsync(string pdfFilePath, string printTitle)
{
private TaskCompletionSource<PrintingResult> _taskCompletionSource;
private CanvasPrintDocument printDocument;
private PrintTask printTask;
private PdfDocument pdfDocument;
private List<CanvasBitmap> bitmaps = new();
private Vector2 largestBitmap;
private Vector2 pageSize;
private Vector2 imagePadding = new Vector2(64, 64);
private Vector2 cellSize;
private int bitmapCount;
private int columns;
private int rows;
private int bitmapsPerPage;
private int pageCount = -1;
private PrintInformation _currentPrintInformation;
public async Task<PrintingResult> PrintPdfFileAsync(string pdfFilePath, string printTitle)
if (_taskCompletionSource != null)
{
if (_taskCompletionSource != null)
{
_taskCompletionSource.TrySetResult(PrintingResult.Abandoned);
_taskCompletionSource = new TaskCompletionSource<PrintingResult>();
}
// Load the PDF file
var file = await Windows.Storage.StorageFile.GetFileFromPathAsync(pdfFilePath);
pdfDocument = await PdfDocument.LoadFromFileAsync(file);
_taskCompletionSource ??= new TaskCompletionSource<PrintingResult>();
_currentPrintInformation = new PrintInformation(pdfFilePath, printTitle);
printDocument = new CanvasPrintDocument();
printDocument.PrintTaskOptionsChanged += OnDocumentTaskOptionsChanged;
printDocument.Preview += OnDocumentPreview;
printDocument.Print += OnDocumentPrint;
var printManager = PrintManager.GetForCurrentView();
printManager.PrintTaskRequested += PrintingExample_PrintTaskRequested;
try
{
await PrintManager.ShowPrintUIAsync();
var result = await _taskCompletionSource.Task;
return result;
}
finally
{
// Dispose everything.
UnregisterPrintManager(printManager);
ClearBitmaps();
UnregisterTask();
DisposePDFDocument();
_taskCompletionSource = null;
}
_taskCompletionSource.TrySetResult(PrintingResult.Abandoned);
_taskCompletionSource = new TaskCompletionSource<PrintingResult>();
}
private void DisposePDFDocument()
// Load the PDF file
var file = await Windows.Storage.StorageFile.GetFileFromPathAsync(pdfFilePath);
pdfDocument = await PdfDocument.LoadFromFileAsync(file);
_taskCompletionSource ??= new TaskCompletionSource<PrintingResult>();
_currentPrintInformation = new PrintInformation(pdfFilePath, printTitle);
printDocument = new CanvasPrintDocument();
printDocument.PrintTaskOptionsChanged += OnDocumentTaskOptionsChanged;
printDocument.Preview += OnDocumentPreview;
printDocument.Print += OnDocumentPrint;
var printManager = PrintManager.GetForCurrentView();
printManager.PrintTaskRequested += PrintingExample_PrintTaskRequested;
try
{
if (pdfDocument != null)
{
pdfDocument = null;
}
await PrintManager.ShowPrintUIAsync();
var result = await _taskCompletionSource.Task;
return result;
}
private void UnregisterTask()
{
if (printTask != null)
{
printTask.Completed -= TaskCompleted;
printTask = null;
}
}
private void UnregisterPrintManager(PrintManager manager)
{
manager.PrintTaskRequested -= PrintingExample_PrintTaskRequested;
}
private void ClearBitmaps()
{
foreach (var bitmap in bitmaps)
{
bitmap.Dispose();
}
bitmaps.Clear();
}
private void PrintingExample_PrintTaskRequested(PrintManager sender, PrintTaskRequestedEventArgs args)
{
if (_currentPrintInformation == null) return;
printTask = args.Request.CreatePrintTask(_currentPrintInformation.PDFTitle, (createPrintTaskArgs) =>
{
createPrintTaskArgs.SetSource(printDocument);
});
printTask.Completed += TaskCompleted;
}
private void TaskCompleted(PrintTask sender, PrintTaskCompletedEventArgs args)
=> _taskCompletionSource?.TrySetResult((PrintingResult)args.Completion);
private async void OnDocumentTaskOptionsChanged(CanvasPrintDocument sender, CanvasPrintTaskOptionsChangedEventArgs args)
{
var deferral = args.GetDeferral();
try
{
await LoadPDFPageBitmapsAsync(sender);
var pageDesc = args.PrintTaskOptions.GetPageDescription(1);
var newPageSize = pageDesc.PageSize.ToVector2();
if (pageSize == newPageSize && pageCount != -1)
{
// We've already figured out the pages and the page size hasn't changed, so there's nothing left for us to do here.
return;
}
pageSize = newPageSize;
sender.InvalidatePreview();
// Figure out the bitmap index at the top of the current preview page. We'll request that the preview defaults to showing
// the page that still has this bitmap on it in the new layout.
int indexOnCurrentPage = 0;
if (pageCount != -1)
{
indexOnCurrentPage = (int)(args.CurrentPreviewPageNumber - 1) * bitmapsPerPage;
}
// Calculate the new layout
var printablePageSize = pageSize * 0.9f;
cellSize = largestBitmap + imagePadding;
var cellsPerPage = printablePageSize / cellSize;
columns = Math.Max(1, (int)Math.Floor(cellsPerPage.X));
rows = Math.Max(1, (int)Math.Floor(cellsPerPage.Y));
bitmapsPerPage = columns * rows;
// Calculate the page count
bitmapCount = bitmaps.Count;
pageCount = (int)Math.Ceiling(bitmapCount / (double)bitmapsPerPage);
sender.SetPageCount((uint)pageCount);
// Set the preview page to the one that has the item that was currently displayed in the last preview
args.NewPreviewPageNumber = (uint)(indexOnCurrentPage / bitmapsPerPage) + 1;
}
finally
{
deferral.Complete();
}
}
private async Task LoadPDFPageBitmapsAsync(CanvasPrintDocument sender)
finally
{
// Dispose everything.
UnregisterPrintManager(printManager);
ClearBitmaps();
UnregisterTask();
DisposePDFDocument();
bitmaps ??= new List<CanvasBitmap>();
_taskCompletionSource = null;
}
}
for (int i = 0; i < pdfDocument.PageCount; i++)
{
var page = pdfDocument.GetPage((uint)i);
var stream = new InMemoryRandomAccessStream();
await page.RenderToStreamAsync(stream);
var bitmap = await CanvasBitmap.LoadAsync(sender, stream);
bitmaps.Add(bitmap);
}
private void DisposePDFDocument()
{
if (pdfDocument != null)
{
pdfDocument = null;
}
}
largestBitmap = Vector2.Zero;
private void UnregisterTask()
{
if (printTask != null)
{
printTask.Completed -= TaskCompleted;
printTask = null;
}
}
foreach (var bitmap in bitmaps)
{
largestBitmap.X = Math.Max(largestBitmap.X, (float)bitmap.Size.Width);
largestBitmap.Y = Math.Max(largestBitmap.Y, (float)bitmap.Size.Height);
}
private void UnregisterPrintManager(PrintManager manager)
{
manager.PrintTaskRequested -= PrintingExample_PrintTaskRequested;
}
private void ClearBitmaps()
{
foreach (var bitmap in bitmaps)
{
bitmap.Dispose();
}
bitmaps.Clear();
}
private void OnDocumentPreview(CanvasPrintDocument sender, CanvasPreviewEventArgs args)
private void PrintingExample_PrintTaskRequested(PrintManager sender, PrintTaskRequestedEventArgs args)
{
if (_currentPrintInformation == null) return;
printTask = args.Request.CreatePrintTask(_currentPrintInformation.PDFTitle, (createPrintTaskArgs) =>
{
var ds = args.DrawingSession;
var pageNumber = args.PageNumber;
createPrintTaskArgs.SetSource(printDocument);
});
DrawPdfPage(sender, ds, pageNumber);
}
printTask.Completed += TaskCompleted;
}
private void OnDocumentPrint(CanvasPrintDocument sender, CanvasPrintEventArgs args)
private void TaskCompleted(PrintTask sender, PrintTaskCompletedEventArgs args)
=> _taskCompletionSource?.TrySetResult((PrintingResult)args.Completion);
private async void OnDocumentTaskOptionsChanged(CanvasPrintDocument sender, CanvasPrintTaskOptionsChangedEventArgs args)
{
var deferral = args.GetDeferral();
try
{
var detailedOptions = PrintTaskOptionDetails.GetFromPrintTaskOptions(args.PrintTaskOptions);
await LoadPDFPageBitmapsAsync(sender);
int pageCountToPrint = (int)pdfDocument.PageCount;
var pageDesc = args.PrintTaskOptions.GetPageDescription(1);
var newPageSize = pageDesc.PageSize.ToVector2();
for (uint i = 1; i <= pageCountToPrint; ++i)
if (pageSize == newPageSize && pageCount != -1)
{
using var ds = args.CreateDrawingSession();
var imageableRect = args.PrintTaskOptions.GetPageDescription(i).ImageableRect;
DrawPdfPage(sender, ds, i);
// We've already figured out the pages and the page size hasn't changed, so there's nothing left for us to do here.
return;
}
pageSize = newPageSize;
sender.InvalidatePreview();
// Figure out the bitmap index at the top of the current preview page. We'll request that the preview defaults to showing
// the page that still has this bitmap on it in the new layout.
int indexOnCurrentPage = 0;
if (pageCount != -1)
{
indexOnCurrentPage = (int)(args.CurrentPreviewPageNumber - 1) * bitmapsPerPage;
}
// Calculate the new layout
var printablePageSize = pageSize * 0.9f;
cellSize = largestBitmap + imagePadding;
var cellsPerPage = printablePageSize / cellSize;
columns = Math.Max(1, (int)Math.Floor(cellsPerPage.X));
rows = Math.Max(1, (int)Math.Floor(cellsPerPage.Y));
bitmapsPerPage = columns * rows;
// Calculate the page count
bitmapCount = bitmaps.Count;
pageCount = (int)Math.Ceiling(bitmapCount / (double)bitmapsPerPage);
sender.SetPageCount((uint)pageCount);
// Set the preview page to the one that has the item that was currently displayed in the last preview
args.NewPreviewPageNumber = (uint)(indexOnCurrentPage / bitmapsPerPage) + 1;
}
finally
{
deferral.Complete();
}
}
private async Task LoadPDFPageBitmapsAsync(CanvasPrintDocument sender)
{
ClearBitmaps();
bitmaps ??= new List<CanvasBitmap>();
for (int i = 0; i < pdfDocument.PageCount; i++)
{
var page = pdfDocument.GetPage((uint)i);
var stream = new InMemoryRandomAccessStream();
await page.RenderToStreamAsync(stream);
var bitmap = await CanvasBitmap.LoadAsync(sender, stream);
bitmaps.Add(bitmap);
}
private void DrawPdfPage(CanvasPrintDocument sender, CanvasDrawingSession ds, uint pageNumber)
largestBitmap = Vector2.Zero;
foreach (var bitmap in bitmaps)
{
if (bitmaps?.Count == 0) return;
largestBitmap.X = Math.Max(largestBitmap.X, (float)bitmap.Size.Width);
largestBitmap.Y = Math.Max(largestBitmap.Y, (float)bitmap.Size.Height);
}
}
var cellAcross = new Vector2(cellSize.X, 0);
var cellDown = new Vector2(0, cellSize.Y);
var totalSize = cellAcross * columns + cellDown * rows;
Vector2 topLeft = (pageSize - totalSize) / 2;
private void OnDocumentPreview(CanvasPrintDocument sender, CanvasPreviewEventArgs args)
{
var ds = args.DrawingSession;
var pageNumber = args.PageNumber;
int bitmapIndex = ((int)pageNumber - 1) * bitmapsPerPage;
DrawPdfPage(sender, ds, pageNumber);
}
for (int row = 0; row < rows; ++row)
private void OnDocumentPrint(CanvasPrintDocument sender, CanvasPrintEventArgs args)
{
var detailedOptions = PrintTaskOptionDetails.GetFromPrintTaskOptions(args.PrintTaskOptions);
int pageCountToPrint = (int)pdfDocument.PageCount;
for (uint i = 1; i <= pageCountToPrint; ++i)
{
using var ds = args.CreateDrawingSession();
var imageableRect = args.PrintTaskOptions.GetPageDescription(i).ImageableRect;
DrawPdfPage(sender, ds, i);
}
}
private void DrawPdfPage(CanvasPrintDocument sender, CanvasDrawingSession ds, uint pageNumber)
{
if (bitmaps?.Count == 0) return;
var cellAcross = new Vector2(cellSize.X, 0);
var cellDown = new Vector2(0, cellSize.Y);
var totalSize = cellAcross * columns + cellDown * rows;
Vector2 topLeft = (pageSize - totalSize) / 2;
int bitmapIndex = ((int)pageNumber - 1) * bitmapsPerPage;
for (int row = 0; row < rows; ++row)
{
for (int column = 0; column < columns; ++column)
{
for (int column = 0; column < columns; ++column)
{
var cellTopLeft = topLeft + cellAcross * column + cellDown * row;
var bitmapInfo = bitmaps[bitmapIndex % bitmaps.Count];
var bitmapPos = cellTopLeft + (cellSize - bitmapInfo.Size.ToVector2()) / 2;
var cellTopLeft = topLeft + cellAcross * column + cellDown * row;
var bitmapInfo = bitmaps[bitmapIndex % bitmaps.Count];
var bitmapPos = cellTopLeft + (cellSize - bitmapInfo.Size.ToVector2()) / 2;
ds.DrawImage(bitmapInfo, bitmapPos);
ds.DrawImage(bitmapInfo, bitmapPos);
bitmapIndex++;
}
bitmapIndex++;
}
}
}

View File

@@ -6,51 +6,50 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.UWP.Extensions;
namespace Wino.Core.UWP.Services
namespace Wino.Core.UWP.Services;
public class StartupBehaviorService : IStartupBehaviorService
{
public class StartupBehaviorService : IStartupBehaviorService
private const string WinoServerTaskId = "WinoServer";
public async Task<StartupBehaviorResult> ToggleStartupBehavior(bool isEnabled)
{
private const string WinoServerTaskId = "WinoServer";
public async Task<StartupBehaviorResult> ToggleStartupBehavior(bool isEnabled)
try
{
try
var task = await StartupTask.GetAsync(WinoServerTaskId);
if (isEnabled)
{
var task = await StartupTask.GetAsync(WinoServerTaskId);
if (isEnabled)
{
await task.RequestEnableAsync();
}
else
{
task.Disable();
}
await task.RequestEnableAsync();
}
catch (Exception)
else
{
Log.Error("Error toggling startup behavior");
task.Disable();
}
}
catch (Exception)
{
Log.Error("Error toggling startup behavior");
return await GetCurrentStartupBehaviorAsync();
}
public async Task<StartupBehaviorResult> GetCurrentStartupBehaviorAsync()
return await GetCurrentStartupBehaviorAsync();
}
public async Task<StartupBehaviorResult> GetCurrentStartupBehaviorAsync()
{
try
{
try
{
var task = await StartupTask.GetAsync(WinoServerTaskId);
var task = await StartupTask.GetAsync(WinoServerTaskId);
return task.State.AsStartupBehaviorResult();
return task.State.AsStartupBehaviorResult();
}
catch (Exception ex)
{
Log.Error(ex, "Error getting startup behavior");
}
catch (Exception ex)
{
Log.Error(ex, "Error getting startup behavior");
return StartupBehaviorResult.Fatal;
}
return StartupBehaviorResult.Fatal;
}
}
}

View File

@@ -4,141 +4,140 @@ using CommunityToolkit.Mvvm.ComponentModel;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
namespace Wino.Services
namespace Wino.Services;
public class StatePersistenceService : ObservableObject, IStatePersistanceService
{
public class StatePersistenceService : ObservableObject, IStatePersistanceService
public event EventHandler<string> StatePropertyChanged;
private const string OpenPaneLengthKey = nameof(OpenPaneLengthKey);
private const string MailListPaneLengthKey = nameof(MailListPaneLengthKey);
private readonly IConfigurationService _configurationService;
public StatePersistenceService(IConfigurationService configurationService)
{
public event EventHandler<string> StatePropertyChanged;
_configurationService = configurationService;
private const string OpenPaneLengthKey = nameof(OpenPaneLengthKey);
private const string MailListPaneLengthKey = nameof(MailListPaneLengthKey);
_openPaneLength = _configurationService.Get(OpenPaneLengthKey, 320d);
_mailListPaneLength = _configurationService.Get(MailListPaneLengthKey, 420d);
_calendarDisplayType = _configurationService.Get(nameof(CalendarDisplayType), CalendarDisplayType.Week);
_dayDisplayCount = _configurationService.Get(nameof(DayDisplayCount), 1);
private readonly IConfigurationService _configurationService;
PropertyChanged += ServicePropertyChanged;
}
public StatePersistenceService(IConfigurationService configurationService)
private void ServicePropertyChanged(object sender, PropertyChangedEventArgs e) => StatePropertyChanged?.Invoke(this, e.PropertyName);
public bool IsBackButtonVisible => IsReadingMail && IsReaderNarrowed;
private bool isReadingMail;
public bool IsReadingMail
{
get => isReadingMail;
set
{
_configurationService = configurationService;
_openPaneLength = _configurationService.Get(OpenPaneLengthKey, 320d);
_mailListPaneLength = _configurationService.Get(MailListPaneLengthKey, 420d);
_calendarDisplayType = _configurationService.Get(nameof(CalendarDisplayType), CalendarDisplayType.Week);
_dayDisplayCount = _configurationService.Get(nameof(DayDisplayCount), 1);
PropertyChanged += ServicePropertyChanged;
}
private void ServicePropertyChanged(object sender, PropertyChangedEventArgs e) => StatePropertyChanged?.Invoke(this, e.PropertyName);
public bool IsBackButtonVisible => IsReadingMail && IsReaderNarrowed;
private bool isReadingMail;
public bool IsReadingMail
{
get => isReadingMail;
set
if (SetProperty(ref isReadingMail, value))
{
if (SetProperty(ref isReadingMail, value))
{
OnPropertyChanged(nameof(IsBackButtonVisible));
}
OnPropertyChanged(nameof(IsBackButtonVisible));
}
}
private bool shouldShiftMailRenderingDesign;
public bool ShouldShiftMailRenderingDesign
{
get { return shouldShiftMailRenderingDesign; }
set { shouldShiftMailRenderingDesign = value; }
}
private bool isReaderNarrowed;
public bool IsReaderNarrowed
{
get => isReaderNarrowed;
set
{
if (SetProperty(ref isReaderNarrowed, value))
{
OnPropertyChanged(nameof(IsBackButtonVisible));
}
}
}
private string coreWindowTitle;
public string CoreWindowTitle
{
get => coreWindowTitle;
set
{
if (SetProperty(ref coreWindowTitle, value))
{
UpdateAppCoreWindowTitle();
}
}
}
private double _openPaneLength;
public double OpenPaneLength
{
get => _openPaneLength;
set
{
if (SetProperty(ref _openPaneLength, value))
{
_configurationService.Set(OpenPaneLengthKey, value);
}
}
}
private double _mailListPaneLength;
public double MailListPaneLength
{
get => _mailListPaneLength;
set
{
if (SetProperty(ref _mailListPaneLength, value))
{
_configurationService.Set(MailListPaneLengthKey, value);
}
}
}
private CalendarDisplayType _calendarDisplayType;
public CalendarDisplayType CalendarDisplayType
{
get => _calendarDisplayType;
set
{
if (SetProperty(ref _calendarDisplayType, value))
{
_configurationService.Set(nameof(CalendarDisplayType), value);
}
}
}
private int _dayDisplayCount;
public int DayDisplayCount
{
get => _dayDisplayCount;
set
{
if (SetProperty(ref _dayDisplayCount, value))
{
_configurationService.Set(nameof(DayDisplayCount), value);
}
}
}
private void UpdateAppCoreWindowTitle()
{
var appView = Windows.UI.ViewManagement.ApplicationView.GetForCurrentView();
if (appView != null)
appView.Title = CoreWindowTitle;
}
}
private bool shouldShiftMailRenderingDesign;
public bool ShouldShiftMailRenderingDesign
{
get { return shouldShiftMailRenderingDesign; }
set { shouldShiftMailRenderingDesign = value; }
}
private bool isReaderNarrowed;
public bool IsReaderNarrowed
{
get => isReaderNarrowed;
set
{
if (SetProperty(ref isReaderNarrowed, value))
{
OnPropertyChanged(nameof(IsBackButtonVisible));
}
}
}
private string coreWindowTitle;
public string CoreWindowTitle
{
get => coreWindowTitle;
set
{
if (SetProperty(ref coreWindowTitle, value))
{
UpdateAppCoreWindowTitle();
}
}
}
private double _openPaneLength;
public double OpenPaneLength
{
get => _openPaneLength;
set
{
if (SetProperty(ref _openPaneLength, value))
{
_configurationService.Set(OpenPaneLengthKey, value);
}
}
}
private double _mailListPaneLength;
public double MailListPaneLength
{
get => _mailListPaneLength;
set
{
if (SetProperty(ref _mailListPaneLength, value))
{
_configurationService.Set(MailListPaneLengthKey, value);
}
}
}
private CalendarDisplayType _calendarDisplayType;
public CalendarDisplayType CalendarDisplayType
{
get => _calendarDisplayType;
set
{
if (SetProperty(ref _calendarDisplayType, value))
{
_configurationService.Set(nameof(CalendarDisplayType), value);
}
}
}
private int _dayDisplayCount;
public int DayDisplayCount
{
get => _dayDisplayCount;
set
{
if (SetProperty(ref _dayDisplayCount, value))
{
_configurationService.Set(nameof(DayDisplayCount), value);
}
}
}
private void UpdateAppCoreWindowTitle()
{
var appView = Windows.UI.ViewManagement.ApplicationView.GetForCurrentView();
if (appView != null)
appView.Title = CoreWindowTitle;
}
}

View File

@@ -5,68 +5,67 @@ using Windows.Services.Store;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Store;
namespace Wino.Core.UWP.Services
namespace Wino.Core.UWP.Services;
public class StoreManagementService : IStoreManagementService
{
public class StoreManagementService : IStoreManagementService
private StoreContext CurrentContext { get; }
private readonly Dictionary<StoreProductType, string> productIds = new Dictionary<StoreProductType, string>()
{
private StoreContext CurrentContext { get; }
{ StoreProductType.UnlimitedAccounts, "UnlimitedAccounts" }
};
private readonly Dictionary<StoreProductType, string> productIds = new Dictionary<StoreProductType, string>()
{
{ StoreProductType.UnlimitedAccounts, "UnlimitedAccounts" }
};
private readonly Dictionary<StoreProductType, string> skuIds = new Dictionary<StoreProductType, string>()
{
{ StoreProductType.UnlimitedAccounts, "9P02MXZ42GSM" }
};
private readonly Dictionary<StoreProductType, string> skuIds = new Dictionary<StoreProductType, string>()
{
{ StoreProductType.UnlimitedAccounts, "9P02MXZ42GSM" }
};
public StoreManagementService()
{
CurrentContext = StoreContext.GetDefault();
}
public StoreManagementService()
{
CurrentContext = StoreContext.GetDefault();
}
public async Task<bool> HasProductAsync(StoreProductType productType)
{
var productKey = productIds[productType];
var appLicense = await CurrentContext.GetAppLicenseAsync();
if (appLicense == null)
return false;
// Access the valid licenses for durable add-ons for this app.
foreach (KeyValuePair<string, StoreLicense> item in appLicense.AddOnLicenses)
{
StoreLicense addOnLicense = item.Value;
if (addOnLicense.InAppOfferToken == productKey)
{
return addOnLicense.IsActive;
}
}
public async Task<bool> HasProductAsync(StoreProductType productType)
{
var productKey = productIds[productType];
var appLicense = await CurrentContext.GetAppLicenseAsync();
if (appLicense == null)
return false;
// Access the valid licenses for durable add-ons for this app.
foreach (KeyValuePair<string, StoreLicense> item in appLicense.AddOnLicenses)
{
StoreLicense addOnLicense = item.Value;
if (addOnLicense.InAppOfferToken == productKey)
{
return addOnLicense.IsActive;
}
}
public async Task<Domain.Enums.StorePurchaseResult> PurchaseAsync(StoreProductType productType)
return false;
}
public async Task<Domain.Enums.StorePurchaseResult> PurchaseAsync(StoreProductType productType)
{
if (await HasProductAsync(productType))
return Domain.Enums.StorePurchaseResult.AlreadyPurchased;
else
{
if (await HasProductAsync(productType))
return Domain.Enums.StorePurchaseResult.AlreadyPurchased;
else
var productKey = skuIds[productType];
var result = await CurrentContext.RequestPurchaseAsync(productKey);
switch (result.Status)
{
var productKey = skuIds[productType];
var result = await CurrentContext.RequestPurchaseAsync(productKey);
switch (result.Status)
{
case StorePurchaseStatus.Succeeded:
return Domain.Enums.StorePurchaseResult.Succeeded;
case StorePurchaseStatus.AlreadyPurchased:
return Domain.Enums.StorePurchaseResult.AlreadyPurchased;
default:
return Domain.Enums.StorePurchaseResult.NotPurchased;
}
case StorePurchaseStatus.Succeeded:
return Domain.Enums.StorePurchaseResult.Succeeded;
case StorePurchaseStatus.AlreadyPurchased:
return Domain.Enums.StorePurchaseResult.AlreadyPurchased;
default:
return Domain.Enums.StorePurchaseResult.NotPurchased;
}
}
}

View File

@@ -7,127 +7,126 @@ using Windows.System;
using Wino.Core.Domain;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.UWP.Services
namespace Wino.Core.UWP.Services;
public class StoreRatingService : IStoreRatingService
{
public class StoreRatingService : IStoreRatingService
private const string RatedStorageKey = nameof(RatedStorageKey);
private const string LatestAskedKey = nameof(LatestAskedKey);
private readonly IConfigurationService _configurationService;
private readonly IMailDialogService _dialogService;
public StoreRatingService(IConfigurationService configurationService, IMailDialogService dialogService)
{
private const string RatedStorageKey = nameof(RatedStorageKey);
private const string LatestAskedKey = nameof(LatestAskedKey);
_configurationService = configurationService;
_dialogService = dialogService;
}
private readonly IConfigurationService _configurationService;
private readonly IMailDialogService _dialogService;
private bool IsAskingThresholdExceeded()
{
var latestAskedDate = _configurationService.Get(LatestAskedKey, DateTime.MinValue);
public StoreRatingService(IConfigurationService configurationService, IMailDialogService dialogService)
// Never asked before.
// Set the threshold and wait for the next trigger.
if (latestAskedDate == DateTime.MinValue)
{
_configurationService = configurationService;
_dialogService = dialogService;
_configurationService.Set(LatestAskedKey, DateTime.UtcNow);
}
else if (DateTime.UtcNow >= latestAskedDate.AddMinutes(30))
{
return true;
}
private bool IsAskingThresholdExceeded()
return false;
}
public async Task PromptRatingDialogAsync()
{
// Annoying.
if (Debugger.IsAttached) return;
// Swallow all exceptions. App should not crash in any errors.
try
{
var latestAskedDate = _configurationService.Get(LatestAskedKey, DateTime.MinValue);
bool isRated = _configurationService.GetRoaming(RatedStorageKey, false);
// Never asked before.
// Set the threshold and wait for the next trigger.
if (isRated) return;
if (latestAskedDate == DateTime.MinValue)
if (!isRated)
{
_configurationService.Set(LatestAskedKey, DateTime.UtcNow);
}
else if (DateTime.UtcNow >= latestAskedDate.AddMinutes(30))
{
return true;
}
if (!IsAskingThresholdExceeded()) return;
return false;
}
var isRateWinoApproved = await _dialogService.ShowWinoCustomMessageDialogAsync(Translator.StoreRatingDialog_Title,
Translator.StoreRatingDialog_MessageFirstLine,
Translator.Buttons_RateWino,
Domain.Enums.WinoCustomMessageDialogIcon.Question,
Translator.Buttons_No,
RatedStorageKey);
public async Task PromptRatingDialogAsync()
{
// Annoying.
if (Debugger.IsAttached) return;
// Swallow all exceptions. App should not crash in any errors.
try
{
bool isRated = _configurationService.GetRoaming(RatedStorageKey, false);
if (isRated) return;
if (!isRated)
if (isRateWinoApproved)
{
if (!IsAskingThresholdExceeded()) return;
// In case of failure of this call, we will navigate users to Store page directly.
var isRateWinoApproved = await _dialogService.ShowWinoCustomMessageDialogAsync(Translator.StoreRatingDialog_Title,
Translator.StoreRatingDialog_MessageFirstLine,
Translator.Buttons_RateWino,
Domain.Enums.WinoCustomMessageDialogIcon.Question,
Translator.Buttons_No,
RatedStorageKey);
if (isRateWinoApproved)
try
{
// In case of failure of this call, we will navigate users to Store page directly.
try
{
await ShowPortableRatingDialogAsync();
}
catch (Exception)
{
await Launcher.LaunchUriAsync(new Uri($"ms-windows-store://review/?ProductId=9NCRCVJC50WL"));
}
await ShowPortableRatingDialogAsync();
}
catch (Exception)
{
await Launcher.LaunchUriAsync(new Uri($"ms-windows-store://review/?ProductId=9NCRCVJC50WL"));
}
}
}
catch (Exception) { }
finally
{
_configurationService.Set(LatestAskedKey, DateTime.UtcNow);
}
}
private async Task ShowPortableRatingDialogAsync()
catch (Exception) { }
finally
{
var _storeContext = StoreContext.GetDefault();
StoreRateAndReviewResult result = await _storeContext.RequestRateAndReviewAppAsync();
// Check status
switch (result.Status)
{
case StoreRateAndReviewStatus.Succeeded:
if (result.WasUpdated)
_dialogService.InfoBarMessage(Translator.Info_ReviewSuccessTitle, Translator.Info_ReviewUpdatedMessage, Domain.Enums.InfoBarMessageType.Success);
else
_dialogService.InfoBarMessage(Translator.Info_ReviewSuccessTitle, Translator.Info_ReviewNewMessage, Domain.Enums.InfoBarMessageType.Success);
_configurationService.Set(RatedStorageKey, true);
break;
case StoreRateAndReviewStatus.CanceledByUser:
break;
case StoreRateAndReviewStatus.NetworkError:
_dialogService.InfoBarMessage(Translator.Info_ReviewNetworkErrorTitle, Translator.Info_ReviewNetworkErrorMessage, Domain.Enums.InfoBarMessageType.Warning);
break;
default:
_dialogService.InfoBarMessage(Translator.Info_ReviewUnknownErrorTitle, string.Format(Translator.Info_ReviewUnknownErrorMessage, result.ExtendedError.Message), Domain.Enums.InfoBarMessageType.Warning);
break;
}
}
public async Task LaunchStorePageForReviewAsync()
{
try
{
await CoreApplication.GetCurrentView()?.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
{
// TODO: Get it from package info.
await Launcher.LaunchUriAsync(new Uri($"ms-windows-store://review/?ProductId=9NCRCVJC50WL"));
});
}
catch (Exception) { }
_configurationService.Set(LatestAskedKey, DateTime.UtcNow);
}
}
private async Task ShowPortableRatingDialogAsync()
{
var _storeContext = StoreContext.GetDefault();
StoreRateAndReviewResult result = await _storeContext.RequestRateAndReviewAppAsync();
// Check status
switch (result.Status)
{
case StoreRateAndReviewStatus.Succeeded:
if (result.WasUpdated)
_dialogService.InfoBarMessage(Translator.Info_ReviewSuccessTitle, Translator.Info_ReviewUpdatedMessage, Domain.Enums.InfoBarMessageType.Success);
else
_dialogService.InfoBarMessage(Translator.Info_ReviewSuccessTitle, Translator.Info_ReviewNewMessage, Domain.Enums.InfoBarMessageType.Success);
_configurationService.Set(RatedStorageKey, true);
break;
case StoreRateAndReviewStatus.CanceledByUser:
break;
case StoreRateAndReviewStatus.NetworkError:
_dialogService.InfoBarMessage(Translator.Info_ReviewNetworkErrorTitle, Translator.Info_ReviewNetworkErrorMessage, Domain.Enums.InfoBarMessageType.Warning);
break;
default:
_dialogService.InfoBarMessage(Translator.Info_ReviewUnknownErrorTitle, string.Format(Translator.Info_ReviewUnknownErrorMessage, result.ExtendedError.Message), Domain.Enums.InfoBarMessageType.Warning);
break;
}
}
public async Task LaunchStorePageForReviewAsync()
{
try
{
await CoreApplication.GetCurrentView()?.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
{
// TODO: Get it from package info.
await Launcher.LaunchUriAsync(new Uri($"ms-windows-store://review/?ProductId=9NCRCVJC50WL"));
});
}
catch (Exception) { }
}
}

View File

@@ -26,437 +26,436 @@ using Wino.Core.UWP.Models.Personalization;
using Wino.Core.UWP.Services;
using Wino.Messaging.Client.Shell;
namespace Wino.Services
namespace Wino.Services;
/// <summary>
/// Class providing functionality around switching and restoring theme settings
/// </summary>
public class ThemeService : IThemeService
{
/// <summary>
/// Class providing functionality around switching and restoring theme settings
/// </summary>
public class ThemeService : IThemeService
public const string CustomThemeFolderName = "CustomThemes";
private static string _micaThemeId = "a160b1b0-2ab8-4e97-a803-f4050f036e25";
private static string _acrylicThemeId = "fc08e58c-36fd-46e2-a562-26cf277f1467";
private static string _cloudsThemeId = "3b621cc2-e270-4a76-8477-737917cccda0";
private static string _forestThemeId = "8bc89b37-a7c5-4049-86e2-de1ae8858dbd";
private static string _nightyThemeId = "5b65e04e-fd7e-4c2d-8221-068d3e02d23a";
private static string _snowflakeThemeId = "e143ddde-2e28-4846-9d98-dad63d6505f1";
private static string _gardenThemeId = "698e4466-f88c-4799-9c61-f0ea1308ed49";
private Frame mainApplicationFrame = null;
public event EventHandler<ApplicationElementTheme> ElementThemeChanged;
public event EventHandler<string> AccentColorChanged;
private const string AccentColorKey = nameof(AccentColorKey);
private const string CurrentApplicationThemeKey = nameof(CurrentApplicationThemeKey);
// Custom theme
public const string CustomThemeAccentColorKey = nameof(CustomThemeAccentColorKey);
// Keep reference so it does not get optimized/garbage collected
private readonly UISettings uiSettings = new UISettings();
private readonly IConfigurationService _configurationService;
private readonly IUnderlyingThemeService _underlyingThemeService;
private readonly IApplicationResourceManager<ResourceDictionary> _applicationResourceManager;
private List<AppThemeBase> preDefinedThemes { get; set; } = new List<AppThemeBase>()
{
public const string CustomThemeFolderName = "CustomThemes";
new SystemAppTheme("Mica", Guid.Parse(_micaThemeId)),
new SystemAppTheme("Acrylic", Guid.Parse(_acrylicThemeId)),
new PreDefinedAppTheme("Nighty", Guid.Parse(_nightyThemeId), "#e1b12c", ApplicationElementTheme.Dark),
new PreDefinedAppTheme("Forest", Guid.Parse(_forestThemeId), "#16a085", ApplicationElementTheme.Dark),
new PreDefinedAppTheme("Clouds", Guid.Parse(_cloudsThemeId), "#0984e3", ApplicationElementTheme.Light),
new PreDefinedAppTheme("Snowflake", Guid.Parse(_snowflakeThemeId), "#4a69bd", ApplicationElementTheme.Light),
new PreDefinedAppTheme("Garden", Guid.Parse(_gardenThemeId), "#05c46b", ApplicationElementTheme.Light),
};
private static string _micaThemeId = "a160b1b0-2ab8-4e97-a803-f4050f036e25";
private static string _acrylicThemeId = "fc08e58c-36fd-46e2-a562-26cf277f1467";
private static string _cloudsThemeId = "3b621cc2-e270-4a76-8477-737917cccda0";
private static string _forestThemeId = "8bc89b37-a7c5-4049-86e2-de1ae8858dbd";
private static string _nightyThemeId = "5b65e04e-fd7e-4c2d-8221-068d3e02d23a";
private static string _snowflakeThemeId = "e143ddde-2e28-4846-9d98-dad63d6505f1";
private static string _gardenThemeId = "698e4466-f88c-4799-9c61-f0ea1308ed49";
public ThemeService(IConfigurationService configurationService,
IUnderlyingThemeService underlyingThemeService,
IApplicationResourceManager<ResourceDictionary> applicationResourceManager)
{
_configurationService = configurationService;
_underlyingThemeService = underlyingThemeService;
_applicationResourceManager = applicationResourceManager;
}
private Frame mainApplicationFrame = null;
public event EventHandler<ApplicationElementTheme> ElementThemeChanged;
public event EventHandler<string> AccentColorChanged;
private const string AccentColorKey = nameof(AccentColorKey);
private const string CurrentApplicationThemeKey = nameof(CurrentApplicationThemeKey);
// Custom theme
public const string CustomThemeAccentColorKey = nameof(CustomThemeAccentColorKey);
// Keep reference so it does not get optimized/garbage collected
private readonly UISettings uiSettings = new UISettings();
private readonly IConfigurationService _configurationService;
private readonly IUnderlyingThemeService _underlyingThemeService;
private readonly IApplicationResourceManager<ResourceDictionary> _applicationResourceManager;
private List<AppThemeBase> preDefinedThemes { get; set; } = new List<AppThemeBase>()
/// <summary>
/// Gets or sets (with LocalSettings persistence) the RequestedTheme of the root element.
/// </summary>
public ApplicationElementTheme RootTheme
{
get
{
new SystemAppTheme("Mica", Guid.Parse(_micaThemeId)),
new SystemAppTheme("Acrylic", Guid.Parse(_acrylicThemeId)),
new PreDefinedAppTheme("Nighty", Guid.Parse(_nightyThemeId), "#e1b12c", ApplicationElementTheme.Dark),
new PreDefinedAppTheme("Forest", Guid.Parse(_forestThemeId), "#16a085", ApplicationElementTheme.Dark),
new PreDefinedAppTheme("Clouds", Guid.Parse(_cloudsThemeId), "#0984e3", ApplicationElementTheme.Light),
new PreDefinedAppTheme("Snowflake", Guid.Parse(_snowflakeThemeId), "#4a69bd", ApplicationElementTheme.Light),
new PreDefinedAppTheme("Garden", Guid.Parse(_gardenThemeId), "#05c46b", ApplicationElementTheme.Light),
};
if (mainApplicationFrame == null) return ApplicationElementTheme.Default;
public ThemeService(IConfigurationService configurationService,
IUnderlyingThemeService underlyingThemeService,
IApplicationResourceManager<ResourceDictionary> applicationResourceManager)
{
_configurationService = configurationService;
_underlyingThemeService = underlyingThemeService;
_applicationResourceManager = applicationResourceManager;
return mainApplicationFrame.RequestedTheme.ToWinoElementTheme();
}
/// <summary>
/// Gets or sets (with LocalSettings persistence) the RequestedTheme of the root element.
/// </summary>
public ApplicationElementTheme RootTheme
set
{
get
{
if (mainApplicationFrame == null) return ApplicationElementTheme.Default;
return mainApplicationFrame.RequestedTheme.ToWinoElementTheme();
}
set
{
if (mainApplicationFrame == null)
return;
mainApplicationFrame.RequestedTheme = value.ToWindowsElementTheme();
_configurationService.Set(UnderlyingThemeService.SelectedAppThemeKey, value);
UpdateSystemCaptionButtonColors();
// PopupRoot usually needs to react to changes.
NotifyThemeUpdate();
}
}
private Guid currentApplicationThemeId;
public Guid CurrentApplicationThemeId
{
get { return currentApplicationThemeId; }
set
{
currentApplicationThemeId = value;
_configurationService.Set(CurrentApplicationThemeKey, value);
_ = mainApplicationFrame.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, async () =>
{
await ApplyCustomThemeAsync(false);
});
}
}
private string accentColor;
public string AccentColor
{
get { return accentColor; }
set
{
accentColor = value;
UpdateAccentColor(value);
_configurationService.Set(AccentColorKey, value);
AccentColorChanged?.Invoke(this, value);
}
}
public bool IsCustomTheme
{
get
{
return currentApplicationThemeId != Guid.Parse(_micaThemeId) &&
currentApplicationThemeId != Guid.Parse(_acrylicThemeId);
}
}
public async Task InitializeAsync()
{
// Already initialized. There is no need.
if (mainApplicationFrame != null)
if (mainApplicationFrame == null)
return;
// Save reference as this might be null when the user is in another app
mainApplicationFrame.RequestedTheme = value.ToWindowsElementTheme();
mainApplicationFrame = Window.Current.Content as Frame;
_configurationService.Set(UnderlyingThemeService.SelectedAppThemeKey, value);
if (mainApplicationFrame == null) return;
UpdateSystemCaptionButtonColors();
RootTheme = _configurationService.Get(UnderlyingThemeService.SelectedAppThemeKey, ApplicationElementTheme.Default);
AccentColor = _configurationService.Get(AccentColorKey, string.Empty);
// Set the current theme id. Default to Mica.
currentApplicationThemeId = _configurationService.Get(CurrentApplicationThemeKey, Guid.Parse(_micaThemeId));
await ApplyCustomThemeAsync(true);
// Registering to color changes, thus we notice when user changes theme system wide
uiSettings.ColorValuesChanged -= UISettingsColorChanged;
uiSettings.ColorValuesChanged += UISettingsColorChanged;
// PopupRoot usually needs to react to changes.
NotifyThemeUpdate();
}
}
private void NotifyThemeUpdate()
private Guid currentApplicationThemeId;
public Guid CurrentApplicationThemeId
{
get { return currentApplicationThemeId; }
set
{
if (mainApplicationFrame == null || mainApplicationFrame.Dispatcher == null) return;
currentApplicationThemeId = value;
_configurationService.Set(CurrentApplicationThemeKey, value);
_ = mainApplicationFrame.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, async () =>
{
await ApplyCustomThemeAsync(false);
});
}
}
private string accentColor;
public string AccentColor
{
get { return accentColor; }
set
{
accentColor = value;
UpdateAccentColor(value);
_configurationService.Set(AccentColorKey, value);
AccentColorChanged?.Invoke(this, value);
}
}
public bool IsCustomTheme
{
get
{
return currentApplicationThemeId != Guid.Parse(_micaThemeId) &&
currentApplicationThemeId != Guid.Parse(_acrylicThemeId);
}
}
public async Task InitializeAsync()
{
// Already initialized. There is no need.
if (mainApplicationFrame != null)
return;
// Save reference as this might be null when the user is in another app
mainApplicationFrame = Window.Current.Content as Frame;
if (mainApplicationFrame == null) return;
RootTheme = _configurationService.Get(UnderlyingThemeService.SelectedAppThemeKey, ApplicationElementTheme.Default);
AccentColor = _configurationService.Get(AccentColorKey, string.Empty);
// Set the current theme id. Default to Mica.
currentApplicationThemeId = _configurationService.Get(CurrentApplicationThemeKey, Guid.Parse(_micaThemeId));
await ApplyCustomThemeAsync(true);
// Registering to color changes, thus we notice when user changes theme system wide
uiSettings.ColorValuesChanged -= UISettingsColorChanged;
uiSettings.ColorValuesChanged += UISettingsColorChanged;
}
private void NotifyThemeUpdate()
{
if (mainApplicationFrame == null || mainApplicationFrame.Dispatcher == null) return;
_ = mainApplicationFrame.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
{
ElementThemeChanged?.Invoke(this, RootTheme);
WeakReferenceMessenger.Default.Send(new ApplicationThemeChanged(_underlyingThemeService.IsUnderlyingThemeDark()));
});
}
private void UISettingsColorChanged(UISettings sender, object args)
{
// Make sure we have a reference to our window so we dispatch a UI change
if (mainApplicationFrame != null)
{
// Dispatch on UI thread so that we have a current appbar to access and change
_ = mainApplicationFrame.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
{
ElementThemeChanged?.Invoke(this, RootTheme);
WeakReferenceMessenger.Default.Send(new ApplicationThemeChanged(_underlyingThemeService.IsUnderlyingThemeDark()));
UpdateSystemCaptionButtonColors();
var accentColor = sender.GetColorValue(UIColorType.Accent);
//AccentColorChangedBySystem?.Invoke(this, accentColor.ToHex());
});
}
private void UISettingsColorChanged(UISettings sender, object args)
NotifyThemeUpdate();
}
public void UpdateSystemCaptionButtonColors()
{
if (mainApplicationFrame == null) return;
_ = mainApplicationFrame.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
// Make sure we have a reference to our window so we dispatch a UI change
if (mainApplicationFrame != null)
ApplicationViewTitleBar titleBar = ApplicationView.GetForCurrentView().TitleBar;
if (titleBar == null) return;
if (_underlyingThemeService.IsUnderlyingThemeDark())
{
// Dispatch on UI thread so that we have a current appbar to access and change
_ = mainApplicationFrame.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
{
UpdateSystemCaptionButtonColors();
var accentColor = sender.GetColorValue(UIColorType.Accent);
//AccentColorChangedBySystem?.Invoke(this, accentColor.ToHex());
});
}
NotifyThemeUpdate();
}
public void UpdateSystemCaptionButtonColors()
{
if (mainApplicationFrame == null) return;
_ = mainApplicationFrame.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
ApplicationViewTitleBar titleBar = ApplicationView.GetForCurrentView().TitleBar;
if (titleBar == null) return;
if (_underlyingThemeService.IsUnderlyingThemeDark())
{
titleBar.ButtonForegroundColor = Colors.White;
}
else
{
titleBar.ButtonForegroundColor = Colors.Black;
}
});
}
public void UpdateAccentColor(string hex)
{
// Change accent color if specified.
if (!string.IsNullOrEmpty(hex))
{
var color = CommunityToolkit.WinUI.Helpers.ColorHelper.ToColor(hex);
var brush = new SolidColorBrush(color);
if (_applicationResourceManager.ContainsResourceKey("SystemAccentColor"))
_applicationResourceManager.ReplaceResource("SystemAccentColor", color);
if (_applicationResourceManager.ContainsResourceKey("NavigationViewSelectionIndicatorForeground"))
_applicationResourceManager.ReplaceResource("NavigationViewSelectionIndicatorForeground", brush);
if (_applicationResourceManager.ContainsResourceKey("SystemControlBackgroundAccentBrush"))
_applicationResourceManager.ReplaceResource("SystemControlBackgroundAccentBrush", brush);
if (_applicationResourceManager.ContainsResourceKey("SystemColorControlAccentBrush"))
_applicationResourceManager.ReplaceResource("SystemColorControlAccentBrush", brush);
RefreshThemeResource();
}
}
private void RefreshThemeResource()
{
if (mainApplicationFrame == null) return;
if (mainApplicationFrame.RequestedTheme == ElementTheme.Dark)
{
mainApplicationFrame.RequestedTheme = ElementTheme.Light;
mainApplicationFrame.RequestedTheme = ElementTheme.Dark;
}
else if (mainApplicationFrame.RequestedTheme == ElementTheme.Light)
{
mainApplicationFrame.RequestedTheme = ElementTheme.Dark;
mainApplicationFrame.RequestedTheme = ElementTheme.Light;
titleBar.ButtonForegroundColor = Colors.White;
}
else
{
var isUnderlyingDark = _underlyingThemeService.IsUnderlyingThemeDark();
mainApplicationFrame.RequestedTheme = isUnderlyingDark ? ElementTheme.Light : ElementTheme.Dark;
mainApplicationFrame.RequestedTheme = ElementTheme.Default;
titleBar.ButtonForegroundColor = Colors.Black;
}
}
});
}
public async Task ApplyCustomThemeAsync(bool isInitializing)
public void UpdateAccentColor(string hex)
{
// Change accent color if specified.
if (!string.IsNullOrEmpty(hex))
{
AppThemeBase applyingTheme = null;
var color = CommunityToolkit.WinUI.Helpers.ColorHelper.ToColor(hex);
var brush = new SolidColorBrush(color);
var controlThemeList = new List<AppThemeBase>(preDefinedThemes);
if (_applicationResourceManager.ContainsResourceKey("SystemAccentColor"))
_applicationResourceManager.ReplaceResource("SystemAccentColor", color);
// Don't search for custom themes if applying theme is already in pre-defined templates.
// This is important for startup performance because we won't be loading the custom themes on launch.
if (_applicationResourceManager.ContainsResourceKey("NavigationViewSelectionIndicatorForeground"))
_applicationResourceManager.ReplaceResource("NavigationViewSelectionIndicatorForeground", brush);
bool isApplyingPreDefinedTheme = preDefinedThemes.Exists(a => a.Id == currentApplicationThemeId);
if (_applicationResourceManager.ContainsResourceKey("SystemControlBackgroundAccentBrush"))
_applicationResourceManager.ReplaceResource("SystemControlBackgroundAccentBrush", brush);
if (isApplyingPreDefinedTheme)
{
applyingTheme = preDefinedThemes.Find(a => a.Id == currentApplicationThemeId);
}
else
{
// User applied custom theme. Load custom themes and find it there.
// Fallback to Mica if nothing found.
if (_applicationResourceManager.ContainsResourceKey("SystemColorControlAccentBrush"))
_applicationResourceManager.ReplaceResource("SystemColorControlAccentBrush", brush);
var customThemes = await GetCurrentCustomThemesAsync();
controlThemeList.AddRange(customThemes.Select(a => new CustomAppTheme(a)));
applyingTheme = controlThemeList.Find(a => a.Id == currentApplicationThemeId) ?? preDefinedThemes.First(a => a.Id == Guid.Parse(_micaThemeId));
}
try
{
var existingThemeDictionary = _applicationResourceManager.GetLastResource();
if (existingThemeDictionary != null && existingThemeDictionary.TryGetValue("ThemeName", out object themeNameString))
{
var themeName = themeNameString.ToString();
// Applying different theme.
if (themeName != applyingTheme.ThemeName)
{
var resourceDictionaryContent = await applyingTheme.GetThemeResourceDictionaryContentAsync();
var resourceDictionary = XamlReader.Load(resourceDictionaryContent) as ResourceDictionary;
// Custom themes require special attention for background image because
// they share the same base theme resource dictionary.
if (applyingTheme is CustomAppTheme)
{
resourceDictionary["ThemeBackgroundImage"] = $"ms-appdata:///local/{CustomThemeFolderName}/{applyingTheme.Id}.jpg";
}
_applicationResourceManager.RemoveResource(existingThemeDictionary);
_applicationResourceManager.AddResource(resourceDictionary);
bool isSystemTheme = applyingTheme is SystemAppTheme || applyingTheme is CustomAppTheme;
if (isSystemTheme)
{
// For system themes, set the RootElement theme from saved values.
// Potential bug: When we set it to system default, theme is not applied when system and
// app element theme is different :)
var savedElement = _configurationService.Get(UnderlyingThemeService.SelectedAppThemeKey, ApplicationElementTheme.Default);
RootTheme = savedElement;
// Quickly switch theme to apply theme resource changes.
RefreshThemeResource();
}
else
RootTheme = applyingTheme.ForceElementTheme;
// Theme has accent color. Override.
if (!isInitializing)
{
AccentColor = applyingTheme.AccentColor;
}
}
else
UpdateSystemCaptionButtonColors();
}
}
catch (Exception ex)
{
Debug.WriteLine($"Apply theme failed -> {ex.Message}");
}
RefreshThemeResource();
}
}
public async Task<List<AppThemeBase>> GetAvailableThemesAsync()
private void RefreshThemeResource()
{
if (mainApplicationFrame == null) return;
if (mainApplicationFrame.RequestedTheme == ElementTheme.Dark)
{
var availableThemes = new List<AppThemeBase>(preDefinedThemes);
mainApplicationFrame.RequestedTheme = ElementTheme.Light;
mainApplicationFrame.RequestedTheme = ElementTheme.Dark;
}
else if (mainApplicationFrame.RequestedTheme == ElementTheme.Light)
{
mainApplicationFrame.RequestedTheme = ElementTheme.Dark;
mainApplicationFrame.RequestedTheme = ElementTheme.Light;
}
else
{
var isUnderlyingDark = _underlyingThemeService.IsUnderlyingThemeDark();
mainApplicationFrame.RequestedTheme = isUnderlyingDark ? ElementTheme.Light : ElementTheme.Dark;
mainApplicationFrame.RequestedTheme = ElementTheme.Default;
}
}
public async Task ApplyCustomThemeAsync(bool isInitializing)
{
AppThemeBase applyingTheme = null;
var controlThemeList = new List<AppThemeBase>(preDefinedThemes);
// Don't search for custom themes if applying theme is already in pre-defined templates.
// This is important for startup performance because we won't be loading the custom themes on launch.
bool isApplyingPreDefinedTheme = preDefinedThemes.Exists(a => a.Id == currentApplicationThemeId);
if (isApplyingPreDefinedTheme)
{
applyingTheme = preDefinedThemes.Find(a => a.Id == currentApplicationThemeId);
}
else
{
// User applied custom theme. Load custom themes and find it there.
// Fallback to Mica if nothing found.
var customThemes = await GetCurrentCustomThemesAsync();
availableThemes.AddRange(customThemes.Select(a => new CustomAppTheme(a)));
controlThemeList.AddRange(customThemes.Select(a => new CustomAppTheme(a)));
return availableThemes;
applyingTheme = controlThemeList.Find(a => a.Id == currentApplicationThemeId) ?? preDefinedThemes.First(a => a.Id == Guid.Parse(_micaThemeId));
}
public async Task<CustomThemeMetadata> CreateNewCustomThemeAsync(string themeName, string accentColor, byte[] wallpaperData)
try
{
if (wallpaperData == null || wallpaperData.Length == 0)
throw new CustomThemeCreationFailedException(Translator.Exception_CustomThemeMissingWallpaper);
var existingThemeDictionary = _applicationResourceManager.GetLastResource();
if (string.IsNullOrEmpty(themeName))
throw new CustomThemeCreationFailedException(Translator.Exception_CustomThemeMissingName);
var themes = await GetCurrentCustomThemesAsync();
if (themes.Exists(a => a.Name == themeName))
throw new CustomThemeCreationFailedException(Translator.Exception_CustomThemeExists);
var newTheme = new CustomThemeMetadata()
if (existingThemeDictionary != null && existingThemeDictionary.TryGetValue("ThemeName", out object themeNameString))
{
Id = Guid.NewGuid(),
Name = themeName,
AccentColorHex = accentColor
};
var themeName = themeNameString.ToString();
// Save wallpaper.
// Filename would be the same as metadata id, in jpg format.
// Applying different theme.
if (themeName != applyingTheme.ThemeName)
{
var resourceDictionaryContent = await applyingTheme.GetThemeResourceDictionaryContentAsync();
var themeFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(CustomThemeFolderName, CreationCollisionOption.OpenIfExists);
var resourceDictionary = XamlReader.Load(resourceDictionaryContent) as ResourceDictionary;
var wallpaperFile = await themeFolder.CreateFileAsync($"{newTheme.Id}.jpg", CreationCollisionOption.ReplaceExisting);
await FileIO.WriteBytesAsync(wallpaperFile, wallpaperData);
// Custom themes require special attention for background image because
// they share the same base theme resource dictionary.
// Generate thumbnail for settings page.
if (applyingTheme is CustomAppTheme)
{
resourceDictionary["ThemeBackgroundImage"] = $"ms-appdata:///local/{CustomThemeFolderName}/{applyingTheme.Id}.jpg";
}
var thumbnail = await wallpaperFile.GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode.PicturesView);
var thumbnailFile = await themeFolder.CreateFileAsync($"{newTheme.Id}_preview.jpg", CreationCollisionOption.ReplaceExisting);
_applicationResourceManager.RemoveResource(existingThemeDictionary);
_applicationResourceManager.AddResource(resourceDictionary);
using (var readerStream = thumbnail.AsStreamForRead())
{
byte[] bytes = new byte[readerStream.Length];
bool isSystemTheme = applyingTheme is SystemAppTheme || applyingTheme is CustomAppTheme;
await readerStream.ReadExactlyAsync(bytes);
if (isSystemTheme)
{
// For system themes, set the RootElement theme from saved values.
// Potential bug: When we set it to system default, theme is not applied when system and
// app element theme is different :)
var buffer = bytes.AsBuffer();
var savedElement = _configurationService.Get(UnderlyingThemeService.SelectedAppThemeKey, ApplicationElementTheme.Default);
RootTheme = savedElement;
await FileIO.WriteBufferAsync(thumbnailFile, buffer);
// Quickly switch theme to apply theme resource changes.
RefreshThemeResource();
}
else
RootTheme = applyingTheme.ForceElementTheme;
// Theme has accent color. Override.
if (!isInitializing)
{
AccentColor = applyingTheme.AccentColor;
}
}
else
UpdateSystemCaptionButtonColors();
}
// Save metadata.
var metadataFile = await themeFolder.CreateFileAsync($"{newTheme.Id}.json", CreationCollisionOption.ReplaceExisting);
var serialized = JsonSerializer.Serialize(newTheme, DomainModelsJsonContext.Default.CustomThemeMetadata);
await FileIO.WriteTextAsync(metadataFile, serialized);
return newTheme;
}
public async Task<List<CustomThemeMetadata>> GetCurrentCustomThemesAsync()
catch (Exception ex)
{
var results = new List<CustomThemeMetadata>();
var themeFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(CustomThemeFolderName, CreationCollisionOption.OpenIfExists);
var allFiles = await themeFolder.GetFilesAsync();
var themeMetadatas = allFiles.Where(a => a.FileType == ".json");
foreach (var theme in themeMetadatas)
{
var metadata = await GetCustomMetadataAsync(theme).ConfigureAwait(false);
if (metadata == null) continue;
results.Add(metadata);
}
return results;
Debug.WriteLine($"Apply theme failed -> {ex.Message}");
}
private async Task<CustomThemeMetadata> GetCustomMetadataAsync(IStorageFile file)
{
var fileContent = await FileIO.ReadTextAsync(file);
return JsonSerializer.Deserialize(fileContent, DomainModelsJsonContext.Default.CustomThemeMetadata);
}
public string GetSystemAccentColorHex()
=> uiSettings.GetColorValue(UIColorType.Accent).ToHex();
}
public async Task<List<AppThemeBase>> GetAvailableThemesAsync()
{
var availableThemes = new List<AppThemeBase>(preDefinedThemes);
var customThemes = await GetCurrentCustomThemesAsync();
availableThemes.AddRange(customThemes.Select(a => new CustomAppTheme(a)));
return availableThemes;
}
public async Task<CustomThemeMetadata> CreateNewCustomThemeAsync(string themeName, string accentColor, byte[] wallpaperData)
{
if (wallpaperData == null || wallpaperData.Length == 0)
throw new CustomThemeCreationFailedException(Translator.Exception_CustomThemeMissingWallpaper);
if (string.IsNullOrEmpty(themeName))
throw new CustomThemeCreationFailedException(Translator.Exception_CustomThemeMissingName);
var themes = await GetCurrentCustomThemesAsync();
if (themes.Exists(a => a.Name == themeName))
throw new CustomThemeCreationFailedException(Translator.Exception_CustomThemeExists);
var newTheme = new CustomThemeMetadata()
{
Id = Guid.NewGuid(),
Name = themeName,
AccentColorHex = accentColor
};
// Save wallpaper.
// Filename would be the same as metadata id, in jpg format.
var themeFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(CustomThemeFolderName, CreationCollisionOption.OpenIfExists);
var wallpaperFile = await themeFolder.CreateFileAsync($"{newTheme.Id}.jpg", CreationCollisionOption.ReplaceExisting);
await FileIO.WriteBytesAsync(wallpaperFile, wallpaperData);
// Generate thumbnail for settings page.
var thumbnail = await wallpaperFile.GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode.PicturesView);
var thumbnailFile = await themeFolder.CreateFileAsync($"{newTheme.Id}_preview.jpg", CreationCollisionOption.ReplaceExisting);
using (var readerStream = thumbnail.AsStreamForRead())
{
byte[] bytes = new byte[readerStream.Length];
await readerStream.ReadExactlyAsync(bytes);
var buffer = bytes.AsBuffer();
await FileIO.WriteBufferAsync(thumbnailFile, buffer);
}
// Save metadata.
var metadataFile = await themeFolder.CreateFileAsync($"{newTheme.Id}.json", CreationCollisionOption.ReplaceExisting);
var serialized = JsonSerializer.Serialize(newTheme, DomainModelsJsonContext.Default.CustomThemeMetadata);
await FileIO.WriteTextAsync(metadataFile, serialized);
return newTheme;
}
public async Task<List<CustomThemeMetadata>> GetCurrentCustomThemesAsync()
{
var results = new List<CustomThemeMetadata>();
var themeFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(CustomThemeFolderName, CreationCollisionOption.OpenIfExists);
var allFiles = await themeFolder.GetFilesAsync();
var themeMetadatas = allFiles.Where(a => a.FileType == ".json");
foreach (var theme in themeMetadatas)
{
var metadata = await GetCustomMetadataAsync(theme).ConfigureAwait(false);
if (metadata == null) continue;
results.Add(metadata);
}
return results;
}
private async Task<CustomThemeMetadata> GetCustomMetadataAsync(IStorageFile file)
{
var fileContent = await FileIO.ReadTextAsync(file);
return JsonSerializer.Deserialize(fileContent, DomainModelsJsonContext.Default.CustomThemeMetadata);
}
public string GetSystemAccentColorHex()
=> uiSettings.GetColorValue(UIColorType.Accent).ToHex();
}

View File

@@ -2,63 +2,62 @@
using System.Linq;
using System.Net.Mail;
namespace Wino.Core.UWP.Services
namespace Wino.Core.UWP.Services;
public static class ThumbnailService
{
public static class ThumbnailService
private static string[] knownCompanies = new string[]
{
private static string[] knownCompanies = new string[]
"microsoft.com", "apple.com", "google.com", "steampowered.com", "airbnb.com", "youtube.com", "uber.com"
};
public static bool IsKnown(string mailHost) => !string.IsNullOrEmpty(mailHost) && knownCompanies.Contains(mailHost);
public static string GetHost(string address)
{
if (string.IsNullOrEmpty(address))
return string.Empty;
if (address.Contains('@'))
{
"microsoft.com", "apple.com", "google.com", "steampowered.com", "airbnb.com", "youtube.com", "uber.com"
};
var splitted = address.Split('@');
public static bool IsKnown(string mailHost) => !string.IsNullOrEmpty(mailHost) && knownCompanies.Contains(mailHost);
public static string GetHost(string address)
{
if (string.IsNullOrEmpty(address))
return string.Empty;
if (address.Contains('@'))
if (splitted.Length >= 2 && !string.IsNullOrEmpty(splitted[1]))
{
var splitted = address.Split('@');
if (splitted.Length >= 2 && !string.IsNullOrEmpty(splitted[1]))
try
{
try
{
return new MailAddress(address).Host;
}
catch (Exception)
{
// TODO: Exceptions are ignored for now.
}
return new MailAddress(address).Host;
}
catch (Exception)
{
// TODO: Exceptions are ignored for now.
}
}
return string.Empty;
}
public static Tuple<bool, string> CheckIsKnown(string host)
{
// Check known hosts.
// Apply company logo if available.
try
{
var last = host.Split('.');
if (last.Length > 2)
host = $"{last[last.Length - 2]}.{last[last.Length - 1]}";
}
catch (Exception)
{
return new Tuple<bool, string>(false, host);
}
return new Tuple<bool, string>(IsKnown(host), host);
}
public static string GetKnownHostImage(string host)
=> $"ms-appx:///Assets/Thumbnails/{host}.png";
return string.Empty;
}
public static Tuple<bool, string> CheckIsKnown(string host)
{
// Check known hosts.
// Apply company logo if available.
try
{
var last = host.Split('.');
if (last.Length > 2)
host = $"{last[last.Length - 2]}.{last[last.Length - 1]}";
}
catch (Exception)
{
return new Tuple<bool, string>(false, host);
}
return new Tuple<bool, string>(IsKnown(host), host);
}
public static string GetKnownHostImage(string host)
=> $"ms-appx:///Assets/Thumbnails/{host}.png";
}

View File

@@ -2,31 +2,30 @@
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.UWP.Services
namespace Wino.Core.UWP.Services;
public class UnderlyingThemeService : IUnderlyingThemeService
{
public class UnderlyingThemeService : IUnderlyingThemeService
public const string SelectedAppThemeKey = nameof(SelectedAppThemeKey);
private readonly UISettings uiSettings = new UISettings();
private readonly IConfigurationService _configurationService;
public UnderlyingThemeService(IConfigurationService configurationService)
{
public const string SelectedAppThemeKey = nameof(SelectedAppThemeKey);
_configurationService = configurationService;
}
private readonly UISettings uiSettings = new UISettings();
private readonly IConfigurationService _configurationService;
// This should not rely on application window to be present.
// Check theme from the settings, rely on UISettings background color if Default.
public UnderlyingThemeService(IConfigurationService configurationService)
{
_configurationService = configurationService;
}
public bool IsUnderlyingThemeDark()
{
var currentTheme = _configurationService.Get(SelectedAppThemeKey, ApplicationElementTheme.Default);
// This should not rely on application window to be present.
// Check theme from the settings, rely on UISettings background color if Default.
public bool IsUnderlyingThemeDark()
{
var currentTheme = _configurationService.Get(SelectedAppThemeKey, ApplicationElementTheme.Default);
if (currentTheme == ApplicationElementTheme.Default)
return uiSettings.GetColorValue(UIColorType.Background).ToString() == "#FF000000";
else
return currentTheme == ApplicationElementTheme.Dark;
}
if (currentTheme == ApplicationElementTheme.Default)
return uiSettings.GetColorValue(UIColorType.Background).ToString() == "#FF000000";
else
return currentTheme == ApplicationElementTheme.Dark;
}
}

View File

@@ -21,344 +21,343 @@ using Wino.Messaging.Enums;
using Wino.Messaging.Server;
using Wino.Messaging.UI;
namespace Wino.Core.UWP.Services
namespace Wino.Core.UWP.Services;
public class WinoServerConnectionManager :
IWinoServerConnectionManager<AppServiceConnection>,
IRecipient<WinoServerConnectionEstablished>
{
public class WinoServerConnectionManager :
IWinoServerConnectionManager<AppServiceConnection>,
IRecipient<WinoServerConnectionEstablished>
private const int ServerConnectionTimeoutMs = 10000;
public event EventHandler<WinoServerConnectionStatus> StatusChanged;
public TaskCompletionSource<bool> ConnectingHandle { get; private set; }
private ILogger Logger => Logger.ForContext<WinoServerConnectionManager>();
private WinoServerConnectionStatus status;
public WinoServerConnectionStatus Status
{
private const int ServerConnectionTimeoutMs = 10000;
public event EventHandler<WinoServerConnectionStatus> StatusChanged;
public TaskCompletionSource<bool> ConnectingHandle { get; private set; }
private ILogger Logger => Logger.ForContext<WinoServerConnectionManager>();
private WinoServerConnectionStatus status;
public WinoServerConnectionStatus Status
get { return status; }
private set
{
get { return status; }
private set
{
Log.Information("Server connection status changed to {Status}.", value);
status = value;
StatusChanged?.Invoke(this, value);
}
Log.Information("Server connection status changed to {Status}.", value);
status = value;
StatusChanged?.Invoke(this, value);
}
private AppServiceConnection _connection;
public AppServiceConnection Connection
{
get { return _connection; }
set
{
if (_connection != null)
{
_connection.RequestReceived -= ServerMessageReceived;
_connection.ServiceClosed -= ServerDisconnected;
}
_connection = value;
if (value == null)
{
Status = WinoServerConnectionStatus.Disconnected;
}
else
{
value.RequestReceived += ServerMessageReceived;
value.ServiceClosed += ServerDisconnected;
Status = WinoServerConnectionStatus.Connected;
}
}
}
private readonly JsonSerializerOptions _jsonSerializerOptions = new()
{
TypeInfoResolver = new ServerRequestTypeInfoResolver()
};
public WinoServerConnectionManager()
{
WeakReferenceMessenger.Default.Register(this);
}
public async Task<bool> ConnectAsync()
{
if (Status == WinoServerConnectionStatus.Connected)
{
Log.Information("Server is already connected.");
return true;
}
if (Status == WinoServerConnectionStatus.Connecting)
{
// A connection is already being established at the moment.
// No need to run another connection establishment process.
// Await the connecting handler if possible.
if (ConnectingHandle != null)
{
return await ConnectingHandle.Task;
}
}
if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
{
try
{
ConnectingHandle = new TaskCompletionSource<bool>();
Status = WinoServerConnectionStatus.Connecting;
var connectionCancellationToken = new CancellationTokenSource(TimeSpan.FromMilliseconds(ServerConnectionTimeoutMs));
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("WinoServer");
// Connection establishment handler is in App.xaml.cs OnBackgroundActivated.
// Once the connection is established, the handler will set the Connection property
// and WinoServerConnectionEstablished will be fired by the messenger.
await ConnectingHandle.Task.WaitAsync(connectionCancellationToken.Token);
Log.Information("Server connection established successfully.");
}
catch (OperationCanceledException canceledException)
{
Log.Error(canceledException, $"Server process did not start in {ServerConnectionTimeoutMs} ms. Operation is canceled.");
ConnectingHandle?.TrySetException(canceledException);
Status = WinoServerConnectionStatus.Failed;
return false;
}
catch (Exception ex)
{
Log.Error(ex, "Failed to connect to the server.");
ConnectingHandle?.TrySetException(ex);
Status = WinoServerConnectionStatus.Failed;
return false;
}
return true;
}
else
{
Log.Information("FullTrustAppContract is not present in the system. Server connection is not possible.");
}
return false;
}
public async Task InitializeAsync()
{
var isConnectionSuccessfull = await ConnectAsync();
if (isConnectionSuccessfull)
{
Log.Information("ServerConnectionManager initialized successfully.");
}
else
{
Log.Error("ServerConnectionManager initialization failed.");
}
}
private void ServerMessageReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
if (args.Request.Message.TryGetValue(MessageConstants.MessageTypeKey, out object messageTypeObject) && messageTypeObject is int messageTypeInt)
{
var messageType = (MessageType)messageTypeInt;
if (args.Request.Message.TryGetValue(MessageConstants.MessageDataKey, out object messageDataObject) && messageDataObject is string messageJson)
{
switch (messageType)
{
case MessageType.UIMessage:
if (!args.Request.Message.TryGetValue(MessageConstants.MessageDataTypeKey, out object dataTypeObject) || dataTypeObject is not string dataTypeName)
throw new ArgumentException("Message data type is missing.");
HandleUIMessage(messageJson, dataTypeName);
break;
default:
break;
}
}
}
}
/// <summary>
/// Unpacks IServerMessage objects and delegate it to Messenger for UI to process.
/// </summary>
/// <param name="messageJson">Message data in json format.</param>
private void HandleUIMessage(string messageJson, string typeName)
{
switch (typeName)
{
case nameof(MailAddedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.MailAddedMessage));
break;
case nameof(MailDownloadedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.MailDownloadedMessage));
break;
case nameof(MailRemovedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.MailRemovedMessage));
break;
case nameof(MailUpdatedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.MailUpdatedMessage));
break;
case nameof(AccountCreatedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.AccountCreatedMessage));
break;
case nameof(AccountRemovedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.AccountRemovedMessage));
break;
case nameof(AccountUpdatedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.AccountUpdatedMessage));
break;
case nameof(DraftCreated):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.DraftCreated));
break;
case nameof(DraftFailed):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.DraftFailed));
break;
case nameof(DraftMapped):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.DraftMapped));
break;
case nameof(FolderRenamed):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.FolderRenamed));
break;
case nameof(FolderSynchronizationEnabled):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.FolderSynchronizationEnabled));
break;
case nameof(MergedInboxRenamed):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.MergedInboxRenamed));
break;
case nameof(AccountSynchronizationCompleted):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.AccountSynchronizationCompleted));
break;
case nameof(RefreshUnreadCountsMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.RefreshUnreadCountsMessage));
break;
case nameof(AccountSynchronizerStateChanged):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.AccountSynchronizerStateChanged));
break;
case nameof(AccountSynchronizationProgressUpdatedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.AccountSynchronizationProgressUpdatedMessage));
break;
case nameof(AccountFolderConfigurationUpdated):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.AccountFolderConfigurationUpdated));
break;
case nameof(CopyAuthURLRequested):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.CopyAuthURLRequested));
break;
case nameof(NewMailSynchronizationRequested):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<NewMailSynchronizationRequested>(messageJson));
break;
default:
throw new Exception("Invalid data type name passed to client.");
}
}
private void ServerDisconnected(AppServiceConnection sender, AppServiceClosedEventArgs args)
{
Log.Information("Server disconnected.");
}
public async Task QueueRequestAsync(IRequestBase request, Guid accountId)
{
var queuePackage = new ServerRequestPackage(accountId, request);
var queueResponse = await GetResponseInternalAsync<bool, ServerRequestPackage>(queuePackage, new Dictionary<string, object>()
{
{ MessageConstants.MessageDataRequestAccountIdKey, accountId }
});
queueResponse.ThrowIfFailed();
}
public Task<WinoServerResponse<TResponse>> GetResponseAsync<TResponse, TRequestType>(TRequestType message, CancellationToken cancellationToken = default) where TRequestType : IClientMessage
=> GetResponseInternalAsync<TResponse, TRequestType>(message, cancellationToken: cancellationToken);
private async Task<WinoServerResponse<TResponse>> GetResponseInternalAsync<TResponse, TRequestType>(TRequestType message,
Dictionary<string, object> parameters = null,
CancellationToken cancellationToken = default)
{
if (Status != WinoServerConnectionStatus.Connected)
await ConnectAsync();
if (Connection == null) return WinoServerResponse<TResponse>.CreateErrorResponse("Server connection is not established.");
string serializedMessage = string.Empty;
try
{
serializedMessage = JsonSerializer.Serialize(message, _jsonSerializerOptions);
}
catch (Exception serializationException)
{
Logger.Error(serializationException, $"Failed to serialize client message for sending.");
return WinoServerResponse<TResponse>.CreateErrorResponse($"Failed to serialize message.\n{serializationException.Message}");
}
AppServiceResponse response = null;
try
{
var valueSet = new ValueSet
{
{ MessageConstants.MessageTypeKey, (int)MessageType.ServerMessage },
{ MessageConstants.MessageDataKey, serializedMessage },
{ MessageConstants.MessageDataTypeKey, message.GetType().Name }
};
// Add additional parameters into ValueSet
if (parameters != null)
{
foreach (var item in parameters)
{
valueSet.Add(item.Key, item.Value);
}
}
response = await Connection.SendMessageAsync(valueSet).AsTask(cancellationToken);
}
catch (OperationCanceledException)
{
return WinoServerResponse<TResponse>.CreateErrorResponse($"Request is canceled by client.");
}
catch (Exception serverSendException)
{
Logger.Error(serverSendException, $"Failed to send message to server.");
return WinoServerResponse<TResponse>.CreateErrorResponse($"Failed to send message to server.\n{serverSendException.Message}");
}
// It should be always Success.
if (response.Status != AppServiceResponseStatus.Success)
return WinoServerResponse<TResponse>.CreateErrorResponse($"Wino Server responded with '{response.Status}' status to message delivery.");
// All responses must contain a message data.
if (!(response.Message.TryGetValue(MessageConstants.MessageDataKey, out object messageDataObject) && messageDataObject is string messageJson))
return WinoServerResponse<TResponse>.CreateErrorResponse("Server response did not contain message data.");
// Try deserialize the message data.
try
{
return JsonSerializer.Deserialize<WinoServerResponse<TResponse>>(messageJson);
}
catch (Exception jsonDeserializationError)
{
Logger.Error(jsonDeserializationError, $"Failed to deserialize server response message data.");
return WinoServerResponse<TResponse>.CreateErrorResponse($"Failed to deserialize Wino server response message data.\n{jsonDeserializationError.Message}");
}
}
public void Receive(WinoServerConnectionEstablished message)
=> ConnectingHandle?.TrySetResult(true);
}
private AppServiceConnection _connection;
public AppServiceConnection Connection
{
get { return _connection; }
set
{
if (_connection != null)
{
_connection.RequestReceived -= ServerMessageReceived;
_connection.ServiceClosed -= ServerDisconnected;
}
_connection = value;
if (value == null)
{
Status = WinoServerConnectionStatus.Disconnected;
}
else
{
value.RequestReceived += ServerMessageReceived;
value.ServiceClosed += ServerDisconnected;
Status = WinoServerConnectionStatus.Connected;
}
}
}
private readonly JsonSerializerOptions _jsonSerializerOptions = new()
{
TypeInfoResolver = new ServerRequestTypeInfoResolver()
};
public WinoServerConnectionManager()
{
WeakReferenceMessenger.Default.Register(this);
}
public async Task<bool> ConnectAsync()
{
if (Status == WinoServerConnectionStatus.Connected)
{
Log.Information("Server is already connected.");
return true;
}
if (Status == WinoServerConnectionStatus.Connecting)
{
// A connection is already being established at the moment.
// No need to run another connection establishment process.
// Await the connecting handler if possible.
if (ConnectingHandle != null)
{
return await ConnectingHandle.Task;
}
}
if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
{
try
{
ConnectingHandle = new TaskCompletionSource<bool>();
Status = WinoServerConnectionStatus.Connecting;
var connectionCancellationToken = new CancellationTokenSource(TimeSpan.FromMilliseconds(ServerConnectionTimeoutMs));
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("WinoServer");
// Connection establishment handler is in App.xaml.cs OnBackgroundActivated.
// Once the connection is established, the handler will set the Connection property
// and WinoServerConnectionEstablished will be fired by the messenger.
await ConnectingHandle.Task.WaitAsync(connectionCancellationToken.Token);
Log.Information("Server connection established successfully.");
}
catch (OperationCanceledException canceledException)
{
Log.Error(canceledException, $"Server process did not start in {ServerConnectionTimeoutMs} ms. Operation is canceled.");
ConnectingHandle?.TrySetException(canceledException);
Status = WinoServerConnectionStatus.Failed;
return false;
}
catch (Exception ex)
{
Log.Error(ex, "Failed to connect to the server.");
ConnectingHandle?.TrySetException(ex);
Status = WinoServerConnectionStatus.Failed;
return false;
}
return true;
}
else
{
Log.Information("FullTrustAppContract is not present in the system. Server connection is not possible.");
}
return false;
}
public async Task InitializeAsync()
{
var isConnectionSuccessfull = await ConnectAsync();
if (isConnectionSuccessfull)
{
Log.Information("ServerConnectionManager initialized successfully.");
}
else
{
Log.Error("ServerConnectionManager initialization failed.");
}
}
private void ServerMessageReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
if (args.Request.Message.TryGetValue(MessageConstants.MessageTypeKey, out object messageTypeObject) && messageTypeObject is int messageTypeInt)
{
var messageType = (MessageType)messageTypeInt;
if (args.Request.Message.TryGetValue(MessageConstants.MessageDataKey, out object messageDataObject) && messageDataObject is string messageJson)
{
switch (messageType)
{
case MessageType.UIMessage:
if (!args.Request.Message.TryGetValue(MessageConstants.MessageDataTypeKey, out object dataTypeObject) || dataTypeObject is not string dataTypeName)
throw new ArgumentException("Message data type is missing.");
HandleUIMessage(messageJson, dataTypeName);
break;
default:
break;
}
}
}
}
/// <summary>
/// Unpacks IServerMessage objects and delegate it to Messenger for UI to process.
/// </summary>
/// <param name="messageJson">Message data in json format.</param>
private void HandleUIMessage(string messageJson, string typeName)
{
switch (typeName)
{
case nameof(MailAddedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.MailAddedMessage));
break;
case nameof(MailDownloadedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.MailDownloadedMessage));
break;
case nameof(MailRemovedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.MailRemovedMessage));
break;
case nameof(MailUpdatedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.MailUpdatedMessage));
break;
case nameof(AccountCreatedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.AccountCreatedMessage));
break;
case nameof(AccountRemovedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.AccountRemovedMessage));
break;
case nameof(AccountUpdatedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.AccountUpdatedMessage));
break;
case nameof(DraftCreated):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.DraftCreated));
break;
case nameof(DraftFailed):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.DraftFailed));
break;
case nameof(DraftMapped):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.DraftMapped));
break;
case nameof(FolderRenamed):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.FolderRenamed));
break;
case nameof(FolderSynchronizationEnabled):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.FolderSynchronizationEnabled));
break;
case nameof(MergedInboxRenamed):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.MergedInboxRenamed));
break;
case nameof(AccountSynchronizationCompleted):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.AccountSynchronizationCompleted));
break;
case nameof(RefreshUnreadCountsMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.RefreshUnreadCountsMessage));
break;
case nameof(AccountSynchronizerStateChanged):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.AccountSynchronizerStateChanged));
break;
case nameof(AccountSynchronizationProgressUpdatedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.AccountSynchronizationProgressUpdatedMessage));
break;
case nameof(AccountFolderConfigurationUpdated):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.AccountFolderConfigurationUpdated));
break;
case nameof(CopyAuthURLRequested):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson, CommunicationMessagesContext.Default.CopyAuthURLRequested));
break;
case nameof(NewMailSynchronizationRequested):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<NewMailSynchronizationRequested>(messageJson));
break;
default:
throw new Exception("Invalid data type name passed to client.");
}
}
private void ServerDisconnected(AppServiceConnection sender, AppServiceClosedEventArgs args)
{
Log.Information("Server disconnected.");
}
public async Task QueueRequestAsync(IRequestBase request, Guid accountId)
{
var queuePackage = new ServerRequestPackage(accountId, request);
var queueResponse = await GetResponseInternalAsync<bool, ServerRequestPackage>(queuePackage, new Dictionary<string, object>()
{
{ MessageConstants.MessageDataRequestAccountIdKey, accountId }
});
queueResponse.ThrowIfFailed();
}
public Task<WinoServerResponse<TResponse>> GetResponseAsync<TResponse, TRequestType>(TRequestType message, CancellationToken cancellationToken = default) where TRequestType : IClientMessage
=> GetResponseInternalAsync<TResponse, TRequestType>(message, cancellationToken: cancellationToken);
private async Task<WinoServerResponse<TResponse>> GetResponseInternalAsync<TResponse, TRequestType>(TRequestType message,
Dictionary<string, object> parameters = null,
CancellationToken cancellationToken = default)
{
if (Status != WinoServerConnectionStatus.Connected)
await ConnectAsync();
if (Connection == null) return WinoServerResponse<TResponse>.CreateErrorResponse("Server connection is not established.");
string serializedMessage = string.Empty;
try
{
serializedMessage = JsonSerializer.Serialize(message, _jsonSerializerOptions);
}
catch (Exception serializationException)
{
Logger.Error(serializationException, $"Failed to serialize client message for sending.");
return WinoServerResponse<TResponse>.CreateErrorResponse($"Failed to serialize message.\n{serializationException.Message}");
}
AppServiceResponse response = null;
try
{
var valueSet = new ValueSet
{
{ MessageConstants.MessageTypeKey, (int)MessageType.ServerMessage },
{ MessageConstants.MessageDataKey, serializedMessage },
{ MessageConstants.MessageDataTypeKey, message.GetType().Name }
};
// Add additional parameters into ValueSet
if (parameters != null)
{
foreach (var item in parameters)
{
valueSet.Add(item.Key, item.Value);
}
}
response = await Connection.SendMessageAsync(valueSet).AsTask(cancellationToken);
}
catch (OperationCanceledException)
{
return WinoServerResponse<TResponse>.CreateErrorResponse($"Request is canceled by client.");
}
catch (Exception serverSendException)
{
Logger.Error(serverSendException, $"Failed to send message to server.");
return WinoServerResponse<TResponse>.CreateErrorResponse($"Failed to send message to server.\n{serverSendException.Message}");
}
// It should be always Success.
if (response.Status != AppServiceResponseStatus.Success)
return WinoServerResponse<TResponse>.CreateErrorResponse($"Wino Server responded with '{response.Status}' status to message delivery.");
// All responses must contain a message data.
if (!(response.Message.TryGetValue(MessageConstants.MessageDataKey, out object messageDataObject) && messageDataObject is string messageJson))
return WinoServerResponse<TResponse>.CreateErrorResponse("Server response did not contain message data.");
// Try deserialize the message data.
try
{
return JsonSerializer.Deserialize<WinoServerResponse<TResponse>>(messageJson);
}
catch (Exception jsonDeserializationError)
{
Logger.Error(jsonDeserializationError, $"Failed to deserialize server response message data.");
return WinoServerResponse<TResponse>.CreateErrorResponse($"Failed to deserialize Wino server response message data.\n{jsonDeserializationError.Message}");
}
}
public void Receive(WinoServerConnectionEstablished message)
=> ConnectingHandle?.TrySetResult(true);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,16 +2,15 @@
using Wino.Core.UWP;
using Wino.Core.ViewModels;
namespace Wino.Views.Abstract
{
public partial class SettingsPageBase<T> : BasePage<T> where T : CoreBaseViewModel
{
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
namespace Wino.Views.Abstract;
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(SettingsPageBase<T>), new PropertyMetadata(string.Empty));
public partial class SettingsPageBase<T> : BasePage<T> where T : CoreBaseViewModel
{
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(SettingsPageBase<T>), new PropertyMetadata(string.Empty));
}

View File

@@ -11,92 +11,91 @@ using Wino.Mail.ViewModels.Data;
using Wino.Messaging.Client.Navigation;
using Wino.Messaging.UI;
namespace Wino.Views
namespace Wino.Views;
public sealed partial class ManageAccountsPage : ManageAccountsPageAbstract,
IRecipient<BackBreadcrumNavigationRequested>,
IRecipient<BreadcrumbNavigationRequested>,
IRecipient<MergedInboxRenamed>
{
public sealed partial class ManageAccountsPage : ManageAccountsPageAbstract,
IRecipient<BackBreadcrumNavigationRequested>,
IRecipient<BreadcrumbNavigationRequested>,
IRecipient<MergedInboxRenamed>
public ObservableCollection<BreadcrumbNavigationItemViewModel> PageHistory { get; set; } = new ObservableCollection<BreadcrumbNavigationItemViewModel>();
public ManageAccountsPage()
{
public ObservableCollection<BreadcrumbNavigationItemViewModel> PageHistory { get; set; } = new ObservableCollection<BreadcrumbNavigationItemViewModel>();
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
var initialRequest = new BreadcrumbNavigationRequested(Translator.MenuManageAccounts, WinoPage.AccountManagementPage);
PageHistory.Add(new BreadcrumbNavigationItemViewModel(initialRequest, true));
var accountManagementPageType = ViewModel.NavigationService.GetPageType(WinoPage.AccountManagementPage);
AccountPagesFrame.Navigate(accountManagementPageType, null, new SuppressNavigationTransitionInfo());
}
public ManageAccountsPage()
void IRecipient<BreadcrumbNavigationRequested>.Receive(BreadcrumbNavigationRequested message)
{
var pageType = ViewModel.NavigationService.GetPageType(message.PageType);
if (pageType == null) return;
AccountPagesFrame.Navigate(pageType, message.Parameter, new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromRight });
PageHistory.ForEach(a => a.IsActive = false);
PageHistory.Add(new BreadcrumbNavigationItemViewModel(message, true));
}
private void GoBackFrame()
{
if (AccountPagesFrame.CanGoBack)
{
InitializeComponent();
}
PageHistory.RemoveAt(PageHistory.Count - 1);
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
var initialRequest = new BreadcrumbNavigationRequested(Translator.MenuManageAccounts, WinoPage.AccountManagementPage);
PageHistory.Add(new BreadcrumbNavigationItemViewModel(initialRequest, true));
var accountManagementPageType = ViewModel.NavigationService.GetPageType(WinoPage.AccountManagementPage);
AccountPagesFrame.Navigate(accountManagementPageType, null, new SuppressNavigationTransitionInfo());
}
void IRecipient<BreadcrumbNavigationRequested>.Receive(BreadcrumbNavigationRequested message)
{
var pageType = ViewModel.NavigationService.GetPageType(message.PageType);
if (pageType == null) return;
AccountPagesFrame.Navigate(pageType, message.Parameter, new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromRight });
PageHistory.ForEach(a => a.IsActive = false);
PageHistory.Add(new BreadcrumbNavigationItemViewModel(message, true));
}
private void GoBackFrame()
{
if (AccountPagesFrame.CanGoBack)
{
PageHistory.RemoveAt(PageHistory.Count - 1);
AccountPagesFrame.GoBack(new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromRight });
}
}
private void BreadItemClicked(Microsoft.UI.Xaml.Controls.BreadcrumbBar sender, Microsoft.UI.Xaml.Controls.BreadcrumbBarItemClickedEventArgs args)
{
var clickedPageHistory = PageHistory[args.Index];
while (PageHistory.FirstOrDefault(a => a.IsActive) != clickedPageHistory)
{
AccountPagesFrame.GoBack(new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromRight });
PageHistory.RemoveAt(PageHistory.Count - 1);
PageHistory[PageHistory.Count - 1].IsActive = true;
}
}
public void Receive(BackBreadcrumNavigationRequested message)
{
GoBackFrame();
}
public void Receive(AccountUpdatedMessage message)
{
// TODO: Find better way to retrieve page history from the stack for the account.
var activePage = PageHistory.LastOrDefault();
if (activePage == null) return;
activePage.Title = message.Account.Name;
}
public void Receive(MergedInboxRenamed message)
{
// TODO: Find better way to retrieve page history from the stack for the merged account.
var activePage = PageHistory.LastOrDefault();
if (activePage == null) return;
activePage.Title = message.NewName;
AccountPagesFrame.GoBack(new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromRight });
}
}
private void BreadItemClicked(Microsoft.UI.Xaml.Controls.BreadcrumbBar sender, Microsoft.UI.Xaml.Controls.BreadcrumbBarItemClickedEventArgs args)
{
var clickedPageHistory = PageHistory[args.Index];
while (PageHistory.FirstOrDefault(a => a.IsActive) != clickedPageHistory)
{
AccountPagesFrame.GoBack(new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromRight });
PageHistory.RemoveAt(PageHistory.Count - 1);
PageHistory[PageHistory.Count - 1].IsActive = true;
}
}
public void Receive(BackBreadcrumNavigationRequested message)
{
GoBackFrame();
}
public void Receive(AccountUpdatedMessage message)
{
// TODO: Find better way to retrieve page history from the stack for the account.
var activePage = PageHistory.LastOrDefault();
if (activePage == null) return;
activePage.Title = message.Account.Name;
}
public void Receive(MergedInboxRenamed message)
{
// TODO: Find better way to retrieve page history from the stack for the merged account.
var activePage = PageHistory.LastOrDefault();
if (activePage == null) return;
activePage.Title = message.NewName;
}
}

View File

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

View File

@@ -11,77 +11,76 @@ using Wino.Messaging.Client.Navigation;
using Wino.Views.Abstract;
using Wino.Views.Settings;
namespace Wino.Views
namespace Wino.Views;
public sealed partial class SettingsPage : SettingsPageAbstract, IRecipient<BreadcrumbNavigationRequested>
{
public sealed partial class SettingsPage : SettingsPageAbstract, IRecipient<BreadcrumbNavigationRequested>
public ObservableCollection<BreadcrumbNavigationItemViewModel> PageHistory { get; set; } = [];
public SettingsPage()
{
public ObservableCollection<BreadcrumbNavigationItemViewModel> PageHistory { get; set; } = [];
InitializeComponent();
}
public SettingsPage()
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
SettingsFrame.Navigate(typeof(SettingOptionsPage), null, new SuppressNavigationTransitionInfo());
var initialRequest = new BreadcrumbNavigationRequested(Translator.MenuSettings, WinoPage.SettingOptionsPage);
PageHistory.Add(new BreadcrumbNavigationItemViewModel(initialRequest, true));
if (e.Parameter is WinoPage parameterPage)
{
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
SettingsFrame.Navigate(typeof(SettingOptionsPage), null, new SuppressNavigationTransitionInfo());
var initialRequest = new BreadcrumbNavigationRequested(Translator.MenuSettings, WinoPage.SettingOptionsPage);
PageHistory.Add(new BreadcrumbNavigationItemViewModel(initialRequest, true));
if (e.Parameter is WinoPage parameterPage)
switch (parameterPage)
{
switch (parameterPage)
{
case WinoPage.AppPreferencesPage:
WeakReferenceMessenger.Default.Send(new BreadcrumbNavigationRequested(Translator.SettingsAppPreferences_Title, WinoPage.AppPreferencesPage));
break;
case WinoPage.PersonalizationPage:
WeakReferenceMessenger.Default.Send(new BreadcrumbNavigationRequested(Translator.SettingsPersonalization_Title, WinoPage.PersonalizationPage));
break;
}
}
}
public override void OnLanguageChanged()
{
base.OnLanguageChanged();
// Update Settings header in breadcrumb.
var settingsHeader = PageHistory.FirstOrDefault();
if (settingsHeader == null) return;
settingsHeader.Title = Translator.MenuSettings;
}
void IRecipient<BreadcrumbNavigationRequested>.Receive(BreadcrumbNavigationRequested message)
{
var pageType = ViewModel.NavigationService.GetPageType(message.PageType);
if (pageType == null) return;
SettingsFrame.Navigate(pageType, message.Parameter, new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromRight });
PageHistory.ForEach(a => a.IsActive = false);
PageHistory.Add(new BreadcrumbNavigationItemViewModel(message, true));
}
private void BreadItemClicked(Microsoft.UI.Xaml.Controls.BreadcrumbBar sender, Microsoft.UI.Xaml.Controls.BreadcrumbBarItemClickedEventArgs args)
{
var clickedPageHistory = PageHistory[args.Index];
var activeIndex = PageHistory.IndexOf(PageHistory.FirstOrDefault(a => a.IsActive));
while (PageHistory.FirstOrDefault(a => a.IsActive) != clickedPageHistory)
{
SettingsFrame.GoBack(new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromRight });
PageHistory.RemoveAt(PageHistory.Count - 1);
PageHistory[PageHistory.Count - 1].IsActive = true;
case WinoPage.AppPreferencesPage:
WeakReferenceMessenger.Default.Send(new BreadcrumbNavigationRequested(Translator.SettingsAppPreferences_Title, WinoPage.AppPreferencesPage));
break;
case WinoPage.PersonalizationPage:
WeakReferenceMessenger.Default.Send(new BreadcrumbNavigationRequested(Translator.SettingsPersonalization_Title, WinoPage.PersonalizationPage));
break;
}
}
}
public override void OnLanguageChanged()
{
base.OnLanguageChanged();
// Update Settings header in breadcrumb.
var settingsHeader = PageHistory.FirstOrDefault();
if (settingsHeader == null) return;
settingsHeader.Title = Translator.MenuSettings;
}
void IRecipient<BreadcrumbNavigationRequested>.Receive(BreadcrumbNavigationRequested message)
{
var pageType = ViewModel.NavigationService.GetPageType(message.PageType);
if (pageType == null) return;
SettingsFrame.Navigate(pageType, message.Parameter, new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromRight });
PageHistory.ForEach(a => a.IsActive = false);
PageHistory.Add(new BreadcrumbNavigationItemViewModel(message, true));
}
private void BreadItemClicked(Microsoft.UI.Xaml.Controls.BreadcrumbBar sender, Microsoft.UI.Xaml.Controls.BreadcrumbBarItemClickedEventArgs args)
{
var clickedPageHistory = PageHistory[args.Index];
var activeIndex = PageHistory.IndexOf(PageHistory.FirstOrDefault(a => a.IsActive));
while (PageHistory.FirstOrDefault(a => a.IsActive) != clickedPageHistory)
{
SettingsFrame.GoBack(new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromRight });
PageHistory.RemoveAt(PageHistory.Count - 1);
PageHistory[PageHistory.Count - 1].IsActive = true;
}
}
}

View File

@@ -22,228 +22,227 @@ using Wino.Core.Domain;
using Wino.Core.Domain.Interfaces;
using Wino.Services;
namespace Wino.Core.UWP
namespace Wino.Core.UWP;
public abstract class WinoApplication : Application
{
public abstract class WinoApplication : Application
public new static WinoApplication Current => (WinoApplication)Application.Current;
public const string WinoLaunchLogPrefix = "[Wino Launch] ";
public IServiceProvider Services { get; }
protected ILogInitializer LogInitializer { get; }
protected IApplicationConfiguration AppConfiguration { get; }
protected IWinoServerConnectionManager<AppServiceConnection> AppServiceConnectionManager { get; }
protected IThemeService ThemeService { get; }
protected IDatabaseService DatabaseService { get; }
protected ITranslationService TranslationService { get; }
protected WinoApplication()
{
public new static WinoApplication Current => (WinoApplication)Application.Current;
public const string WinoLaunchLogPrefix = "[Wino Launch] ";
ConfigurePrelaunch();
public IServiceProvider Services { get; }
protected ILogInitializer LogInitializer { get; }
protected IApplicationConfiguration AppConfiguration { get; }
protected IWinoServerConnectionManager<AppServiceConnection> AppServiceConnectionManager { get; }
protected IThemeService ThemeService { get; }
protected IDatabaseService DatabaseService { get; }
protected ITranslationService TranslationService { get; }
Services = ConfigureServices();
protected WinoApplication()
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
UnhandledException += OnAppUnhandledException;
Resuming += OnResuming;
Suspending += OnSuspending;
LogInitializer = Services.GetService<ILogInitializer>();
AppConfiguration = Services.GetService<IApplicationConfiguration>();
AppServiceConnectionManager = Services.GetService<IWinoServerConnectionManager<AppServiceConnection>>();
ThemeService = Services.GetService<IThemeService>();
DatabaseService = Services.GetService<IDatabaseService>();
TranslationService = Services.GetService<ITranslationService>();
// Make sure the paths are setup on app start.
AppConfiguration.ApplicationDataFolderPath = ApplicationData.Current.LocalFolder.Path;
AppConfiguration.PublisherSharedFolderPath = ApplicationData.Current.GetPublisherCacheFolder(ApplicationConfiguration.SharedFolderName).Path;
AppConfiguration.ApplicationTempFolderPath = ApplicationData.Current.TemporaryFolder.Path;
ConfigureLogging();
}
private void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e)
=> Log.Fatal(e.ExceptionObject as Exception, "AppDomain Unhandled Exception");
private void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
=> Log.Error(e.Exception, "Unobserved Task Exception");
private void OnAppUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e)
{
Log.Fatal(e.Exception, "Unhandled Exception");
e.Handled = true;
}
protected abstract void OnApplicationCloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e);
protected abstract IEnumerable<ActivationHandler> GetActivationHandlers();
protected abstract ActivationHandler<IActivatedEventArgs> GetDefaultActivationHandler();
protected override void OnWindowCreated(WindowCreatedEventArgs args)
{
base.OnWindowCreated(args);
ConfigureTitleBar();
LogActivation($"OnWindowCreated -> IsWindowNull: {args.Window == null}");
TryRegisterAppCloseChange();
}
public IEnumerable<IInitializeAsync> GetActivationServices()
{
yield return DatabaseService;
yield return TranslationService;
yield return ThemeService;
}
public Task InitializeServicesAsync() => GetActivationServices().Select(a => a.InitializeAsync()).WhenAll();
public bool IsInteractiveLaunchArgs(object args) => args is IActivatedEventArgs;
public void LogActivation(string log) => Log.Information($"{WinoLaunchLogPrefix}{log}");
private void ConfigureTitleBar()
{
var coreTitleBar = CoreApplication.GetCurrentView().TitleBar;
var applicationViewTitleBar = ApplicationView.GetForCurrentView().TitleBar;
// Extend shell content into core window to meet design requirements.
coreTitleBar.ExtendViewIntoTitleBar = true;
// Change system buttons and background colors to meet design requirements.
applicationViewTitleBar.ButtonBackgroundColor = Colors.Transparent;
applicationViewTitleBar.BackgroundColor = Colors.Transparent;
applicationViewTitleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
applicationViewTitleBar.ButtonForegroundColor = Colors.White;
}
public async Task ActivateWinoAsync(object args)
{
await InitializeServicesAsync();
if (IsInteractiveLaunchArgs(args))
{
ConfigurePrelaunch();
Services = ConfigureServices();
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
UnhandledException += OnAppUnhandledException;
Resuming += OnResuming;
Suspending += OnSuspending;
LogInitializer = Services.GetService<ILogInitializer>();
AppConfiguration = Services.GetService<IApplicationConfiguration>();
AppServiceConnectionManager = Services.GetService<IWinoServerConnectionManager<AppServiceConnection>>();
ThemeService = Services.GetService<IThemeService>();
DatabaseService = Services.GetService<IDatabaseService>();
TranslationService = Services.GetService<ITranslationService>();
// Make sure the paths are setup on app start.
AppConfiguration.ApplicationDataFolderPath = ApplicationData.Current.LocalFolder.Path;
AppConfiguration.PublisherSharedFolderPath = ApplicationData.Current.GetPublisherCacheFolder(ApplicationConfiguration.SharedFolderName).Path;
AppConfiguration.ApplicationTempFolderPath = ApplicationData.Current.TemporaryFolder.Path;
ConfigureLogging();
}
private void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e)
=> Log.Fatal(e.ExceptionObject as Exception, "AppDomain Unhandled Exception");
private void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
=> Log.Error(e.Exception, "Unobserved Task Exception");
private void OnAppUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e)
{
Log.Fatal(e.Exception, "Unhandled Exception");
e.Handled = true;
}
protected abstract void OnApplicationCloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e);
protected abstract IEnumerable<ActivationHandler> GetActivationHandlers();
protected abstract ActivationHandler<IActivatedEventArgs> GetDefaultActivationHandler();
protected override void OnWindowCreated(WindowCreatedEventArgs args)
{
base.OnWindowCreated(args);
ConfigureTitleBar();
LogActivation($"OnWindowCreated -> IsWindowNull: {args.Window == null}");
TryRegisterAppCloseChange();
}
public IEnumerable<IInitializeAsync> GetActivationServices()
{
yield return DatabaseService;
yield return TranslationService;
yield return ThemeService;
}
public Task InitializeServicesAsync() => GetActivationServices().Select(a => a.InitializeAsync()).WhenAll();
public bool IsInteractiveLaunchArgs(object args) => args is IActivatedEventArgs;
public void LogActivation(string log) => Log.Information($"{WinoLaunchLogPrefix}{log}");
private void ConfigureTitleBar()
{
var coreTitleBar = CoreApplication.GetCurrentView().TitleBar;
var applicationViewTitleBar = ApplicationView.GetForCurrentView().TitleBar;
// Extend shell content into core window to meet design requirements.
coreTitleBar.ExtendViewIntoTitleBar = true;
// Change system buttons and background colors to meet design requirements.
applicationViewTitleBar.ButtonBackgroundColor = Colors.Transparent;
applicationViewTitleBar.BackgroundColor = Colors.Transparent;
applicationViewTitleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
applicationViewTitleBar.ButtonForegroundColor = Colors.White;
}
public async Task ActivateWinoAsync(object args)
{
await InitializeServicesAsync();
if (IsInteractiveLaunchArgs(args))
if (Window.Current.Content == null)
{
if (Window.Current.Content == null)
{
var mainFrame = new Frame();
var mainFrame = new Frame();
Window.Current.Content = mainFrame;
Window.Current.Content = mainFrame;
await ThemeService.InitializeAsync();
}
}
await HandleActivationAsync(args);
if (IsInteractiveLaunchArgs(args))
{
Window.Current.Activate();
LogActivation("Window activated");
await ThemeService.InitializeAsync();
}
}
public async Task HandleActivationAsync(object activationArgs)
await HandleActivationAsync(args);
if (IsInteractiveLaunchArgs(args))
{
if (GetActivationHandlers() != null)
{
var activationHandler = GetActivationHandlers().FirstOrDefault(h => h.CanHandle(activationArgs)) ?? null;
Window.Current.Activate();
if (activationHandler != null)
{
await activationHandler.HandleAsync(activationArgs);
}
}
if (IsInteractiveLaunchArgs(activationArgs))
{
var defaultHandler = GetDefaultActivationHandler();
if (defaultHandler.CanHandle(activationArgs))
{
await defaultHandler.HandleAsync(activationArgs);
}
}
}
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
LogActivation($"OnLaunched -> {args.GetType().Name}, Kind -> {args.Kind}, PreviousExecutionState -> {args.PreviousExecutionState}, IsPrelaunch -> {args.PrelaunchActivated}");
if (!args.PrelaunchActivated)
{
await ActivateWinoAsync(args);
}
}
protected override async void OnFileActivated(FileActivatedEventArgs args)
{
base.OnFileActivated(args);
LogActivation($"OnFileActivated -> ItemCount: {args.Files.Count}, Kind: {args.Kind}, PreviousExecutionState: {args.PreviousExecutionState}");
await ActivateWinoAsync(args);
}
protected override async void OnActivated(IActivatedEventArgs args)
{
base.OnActivated(args);
Log.Information($"OnActivated -> {args.GetType().Name}, Kind -> {args.Kind}, Prev Execution State -> {args.PreviousExecutionState}");
await ActivateWinoAsync(args);
}
private void TryRegisterAppCloseChange()
{
try
{
var systemNavigationManagerPreview = SystemNavigationManagerPreview.GetForCurrentView();
systemNavigationManagerPreview.CloseRequested -= OnApplicationCloseRequested;
systemNavigationManagerPreview.CloseRequested += OnApplicationCloseRequested;
}
catch { }
}
private void ConfigurePrelaunch()
{
if (ApiInformation.IsMethodPresent("Windows.ApplicationModel.Core.CoreApplication", "EnablePrelaunch"))
CoreApplication.EnablePrelaunch(true);
}
public virtual async void OnResuming(object sender, object e)
{
// App Service connection was lost on suspension.
// We must restore it.
// Server might be running already, but re-launching it will trigger a new connection attempt.
try
{
await AppServiceConnectionManager.ConnectAsync();
}
catch (OperationCanceledException)
{
// Ignore
}
catch (Exception ex)
{
Log.Error(ex, "Failed to connect to server after resuming the app.");
}
}
public virtual void OnSuspending(object sender, SuspendingEventArgs e) { }
public abstract IServiceProvider ConfigureServices();
public void ConfigureLogging()
{
string logFilePath = Path.Combine(ApplicationData.Current.LocalFolder.Path, Constants.ClientLogFile);
LogInitializer.SetupLogger(logFilePath);
LogActivation("Window activated");
}
}
public async Task HandleActivationAsync(object activationArgs)
{
if (GetActivationHandlers() != null)
{
var activationHandler = GetActivationHandlers().FirstOrDefault(h => h.CanHandle(activationArgs)) ?? null;
if (activationHandler != null)
{
await activationHandler.HandleAsync(activationArgs);
}
}
if (IsInteractiveLaunchArgs(activationArgs))
{
var defaultHandler = GetDefaultActivationHandler();
if (defaultHandler.CanHandle(activationArgs))
{
await defaultHandler.HandleAsync(activationArgs);
}
}
}
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
LogActivation($"OnLaunched -> {args.GetType().Name}, Kind -> {args.Kind}, PreviousExecutionState -> {args.PreviousExecutionState}, IsPrelaunch -> {args.PrelaunchActivated}");
if (!args.PrelaunchActivated)
{
await ActivateWinoAsync(args);
}
}
protected override async void OnFileActivated(FileActivatedEventArgs args)
{
base.OnFileActivated(args);
LogActivation($"OnFileActivated -> ItemCount: {args.Files.Count}, Kind: {args.Kind}, PreviousExecutionState: {args.PreviousExecutionState}");
await ActivateWinoAsync(args);
}
protected override async void OnActivated(IActivatedEventArgs args)
{
base.OnActivated(args);
Log.Information($"OnActivated -> {args.GetType().Name}, Kind -> {args.Kind}, Prev Execution State -> {args.PreviousExecutionState}");
await ActivateWinoAsync(args);
}
private void TryRegisterAppCloseChange()
{
try
{
var systemNavigationManagerPreview = SystemNavigationManagerPreview.GetForCurrentView();
systemNavigationManagerPreview.CloseRequested -= OnApplicationCloseRequested;
systemNavigationManagerPreview.CloseRequested += OnApplicationCloseRequested;
}
catch { }
}
private void ConfigurePrelaunch()
{
if (ApiInformation.IsMethodPresent("Windows.ApplicationModel.Core.CoreApplication", "EnablePrelaunch"))
CoreApplication.EnablePrelaunch(true);
}
public virtual async void OnResuming(object sender, object e)
{
// App Service connection was lost on suspension.
// We must restore it.
// Server might be running already, but re-launching it will trigger a new connection attempt.
try
{
await AppServiceConnectionManager.ConnectAsync();
}
catch (OperationCanceledException)
{
// Ignore
}
catch (Exception ex)
{
Log.Error(ex, "Failed to connect to server after resuming the app.");
}
}
public virtual void OnSuspending(object sender, SuspendingEventArgs e) { }
public abstract IServiceProvider ConfigureServices();
public void ConfigureLogging()
{
string logFilePath = Path.Combine(ApplicationData.Current.LocalFolder.Path, Constants.ClientLogFile);
LogInitializer.SetupLogger(logFilePath);
}
}