Revert "File scoped namespaces"

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

View File

@@ -1,36 +1,37 @@
using System.Threading.Tasks;
namespace Wino.Activation;
public abstract class ActivationHandler
namespace Wino.Activation
{
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)
public abstract class ActivationHandler
{
await HandleInternalAsync(args as T);
public abstract bool CanHandle(object args);
public abstract Task HandleAsync(object args);
}
public override bool CanHandle(object args)
// Extend this class to implement new ActivationHandlers
public abstract class ActivationHandler<T> : ActivationHandler
where T : class
{
// CanHandle checks the args is of type you have configured
return args is T && CanHandleInternal(args as T);
}
// Override this method to add the activation logic in your activation handler
protected abstract Task HandleInternalAsync(T args);
// 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;
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;
}
}
}

View File

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

View File

@@ -9,67 +9,68 @@ using Wino.Core.Domain.Interfaces;
using Wino.Messaging.UI;
namespace Wino.Core.UWP.Controls;
public sealed partial class AccountCreationDialogControl : UserControl, IRecipient<CopyAuthURLRequested>
namespace Wino.Core.UWP.Controls
{
private string copyClipboardURL;
public event EventHandler CancelClicked;
public AccountCreationDialogState State
public sealed partial class AccountCreationDialogControl : UserControl, IRecipient<CopyAuthURLRequested>
{
get { return (AccountCreationDialogState)GetValue(StateProperty); }
set { SetValue(StateProperty, value); }
}
private string copyClipboardURL;
public static readonly DependencyProperty StateProperty = DependencyProperty.Register(nameof(State), typeof(AccountCreationDialogState), typeof(AccountCreationDialogControl), new PropertyMetadata(AccountCreationDialogState.Idle, new PropertyChangedCallback(OnStateChanged)));
public event EventHandler CancelClicked;
public AccountCreationDialogControl()
{
InitializeComponent();
}
private static void OnStateChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is AccountCreationDialogControl dialog)
public AccountCreationDialogState State
{
dialog.UpdateVisualStates();
get { return (AccountCreationDialogState)GetValue(StateProperty); }
set { SetValue(StateProperty, value); }
}
}
private void UpdateVisualStates() => VisualStateManager.GoToState(this, State.ToString(), false);
public static readonly DependencyProperty StateProperty = DependencyProperty.Register(nameof(State), typeof(AccountCreationDialogState), typeof(AccountCreationDialogControl), new PropertyMetadata(AccountCreationDialogState.Idle, new PropertyChangedCallback(OnStateChanged)));
public async void Receive(CopyAuthURLRequested message)
{
copyClipboardURL = message.AuthURL;
await Task.Delay(2000);
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
public AccountCreationDialogControl()
{
AuthHelpDialogButton.Visibility = Windows.UI.Xaml.Visibility.Visible;
});
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);
}
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,105 +1,106 @@
using System.Collections.Generic;
namespace Wino.Core.UWP.Controls;
public static class ControlConstants
namespace Wino.Core.UWP.Controls
{
public static Dictionary<WinoIconGlyph, string> WinoIconFontDictionary = new Dictionary<WinoIconGlyph, string>()
public static class ControlConstants
{
{ 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" }
};
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" }
};
}
}

View File

@@ -2,85 +2,86 @@
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace Wino.Core.UWP.Controls;
public partial class EqualGridPanel : Panel
namespace Wino.Core.UWP.Controls
{
public int Rows
public partial class 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)
public int Rows
{
panel.InvalidateMeasure();
panel.InvalidateArrange();
}
}
protected override Size MeasureOverride(Size availableSize)
{
if (Rows <= 0 || Columns <= 0)
{
return new Size(0, 0);
get { return (int)GetValue(RowsProperty); }
set { SetValue(RowsProperty, value); }
}
double cellWidth = availableSize.Width / Columns;
double cellHeight = availableSize.Height / Rows;
public static readonly DependencyProperty RowsProperty =
DependencyProperty.Register(
nameof(Rows),
typeof(int),
typeof(EqualGridPanel),
new PropertyMetadata(1, OnLayoutPropertyChanged));
foreach (UIElement child in Children)
public int Columns
{
child.Measure(new Size(cellWidth, cellHeight));
get { return (int)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
return availableSize;
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(
nameof(Columns),
typeof(int),
typeof(EqualGridPanel),
new PropertyMetadata(1, OnLayoutPropertyChanged));
protected override Size ArrangeOverride(Size finalSize)
{
if (Rows <= 0 || Columns <= 0)
private static void OnLayoutPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
return new Size(0, 0);
if (d is EqualGridPanel panel)
{
panel.InvalidateMeasure();
panel.InvalidateArrange();
}
}
double cellWidth = finalSize.Width / Columns;
double cellHeight = finalSize.Height / Rows;
for (int i = 0; i < Children.Count; i++)
protected override Size MeasureOverride(Size availableSize)
{
int row = i / Columns;
int column = i % Columns;
if (Rows <= 0 || Columns <= 0)
{
return new Size(0, 0);
}
double x = column * cellWidth;
double y = row * cellHeight;
double cellWidth = availableSize.Width / Columns;
double cellHeight = availableSize.Height / Rows;
Rect rect = new Rect(x, y, cellWidth, cellHeight);
Children[i].Arrange(rect);
foreach (UIElement child in Children)
{
child.Measure(new Size(cellWidth, cellHeight));
}
return availableSize;
}
return finalSize;
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,208 +4,193 @@ using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Enums;
namespace Wino.Core.UWP.Controls;
public sealed partial class WinoAppTitleBar : UserControl
namespace Wino.Core.UWP.Controls
{
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 sealed partial class WinoAppTitleBar : UserControl
{
get { return (bool)GetValue(IsShellFrameContentVisibleProperty); }
set { SetValue(IsShellFrameContentVisibleProperty, value); }
}
public event TypedEventHandler<WinoAppTitleBar, RoutedEventArgs> BackButtonClicked;
public ICommand ReconnectCommand
{
get { return (ICommand)GetValue(ReconnectCommandProperty); }
set { SetValue(ReconnectCommandProperty, 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 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)
public bool IsShellFrameContentVisible
{
bar.DrawTitleBar();
get { return (bool)GetValue(IsShellFrameContentVisibleProperty); }
set { SetValue(IsShellFrameContentVisibleProperty, value); }
}
}
private static void OnDrawingPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoAppTitleBar bar)
public ICommand ReconnectCommand
{
bar.DrawTitleBar();
get { return (ICommand)GetValue(ReconnectCommandProperty); }
set { SetValue(ReconnectCommandProperty, value); }
}
}
private static void OnConnectionStatusChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoAppTitleBar bar)
public WinoServerConnectionStatus ConnectionStatus
{
bar.UpdateConnectionStatus();
get { return (WinoServerConnectionStatus)GetValue(ConnectionStatusProperty); }
set { SetValue(ConnectionStatusProperty, value); }
}
}
private static void OnIsDragAreaChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoAppTitleBar bar)
public string CoreWindowText
{
bar.SetDragArea();
get { return (string)GetValue(CoreWindowTextProperty); }
set { SetValue(CoreWindowTextProperty, value); }
}
}
private void SetDragArea()
{
if (IsDragArea)
public bool IsDragArea
{
Window.Current.SetTitleBar(dragbar);
get { return (bool)GetValue(IsDragAreaProperty); }
set { SetValue(IsDragAreaProperty, value); }
}
}
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)
public double SystemReserved
{
get { return (double)GetValue(SystemReservedProperty); }
set { SetValue(SystemReservedProperty, value); }
}
else if (NavigationViewDisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Compact)
{
// Icons are visible.
if (!IsReaderNarrowed && ShrinkShellContentOnExpansion)
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)
{
ShellContentContainer.HorizontalAlignment = HorizontalAlignment.Left;
ShellContentContainer.Width = ReadingPaneLength;
bar.DrawTitleBar();
}
}
else if (NavigationViewDisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Expanded)
{
if (IsNavigationPaneOpen)
{
CoreWindowTitleTextBlock.Visibility = Visibility.Visible;
// LMargin = OpenPaneLength - LeftMenuStackPanel
ShellContentContainer.Margin = new Thickness(OpenPaneLength - LeftMenuStackPanel.ActualSize.X, 0, 0, 0);
private static void OnDrawingPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoAppTitleBar bar)
{
bar.DrawTitleBar();
}
}
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.
if (!IsReaderNarrowed && ShrinkShellContentOnExpansion)
{
@@ -213,43 +198,59 @@ public sealed partial class WinoAppTitleBar : UserControl
ShellContentContainer.Width = ReadingPaneLength;
}
}
else
else if (NavigationViewDisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Expanded)
{
if (ShrinkShellContentOnExpansion)
if (IsNavigationPaneOpen)
{
EmptySpaceWidth.Width = new GridLength(ReadingPaneLength, GridUnitType.Pixel);
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;
}
}
else
{
EmptySpaceWidth.Width = new GridLength(ReadingPaneLength, GridUnitType.Star);
if (ShrinkShellContentOnExpansion)
{
EmptySpaceWidth.Width = new GridLength(ReadingPaneLength, GridUnitType.Pixel);
}
else
{
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,135 +1,136 @@
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace Wino.Core.UWP.Controls;
public enum WinoIconGlyph
namespace Wino.Core.UWP.Controls
{
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
public enum WinoIconGlyph
{
get { return (WinoIconGlyph)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
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 static readonly DependencyProperty IconProperty = DependencyProperty.Register(nameof(Icon), typeof(WinoIconGlyph), typeof(WinoFontIcon), new PropertyMetadata(WinoIconGlyph.Flag, OnIconChanged));
public WinoFontIcon()
public partial class WinoFontIcon : FontIcon
{
FontFamily = new Windows.UI.Xaml.Media.FontFamily("ms-appx:///Wino.Core.UWP/Assets/WinoIcons.ttf#WinoIcons");
FontSize = 32;
}
private static void OnIconChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoFontIcon fontIcon)
public WinoIconGlyph Icon
{
fontIcon.UpdateGlyph();
get { return (WinoIconGlyph)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(nameof(Icon), typeof(WinoIconGlyph), typeof(WinoFontIcon), new PropertyMetadata(WinoIconGlyph.Flag, OnIconChanged));
public WinoFontIcon()
{
FontFamily = new Windows.UI.Xaml.Media.FontFamily("ms-appx:///Wino.Core.UWP/Assets/WinoIcons.ttf#WinoIcons");
FontSize = 32;
}
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,34 +1,35 @@
using Windows.UI.Xaml;
using Wino.Core.UWP.Controls;
namespace Wino.Controls;
public partial class WinoFontIconSource : Microsoft.UI.Xaml.Controls.FontIconSource
namespace Wino.Controls
{
public WinoIconGlyph Icon
public partial class WinoFontIconSource : Microsoft.UI.Xaml.Controls.FontIconSource
{
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 WinoFontIconSource()
{
FontFamily = new Windows.UI.Xaml.Media.FontFamily("ms-appx:///Assets/WinoIcons.ttf#WinoIcons");
FontSize = 32;
}
private static void OnIconChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoFontIconSource fontIcon)
public WinoIconGlyph Icon
{
fontIcon.UpdateGlyph();
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 WinoFontIconSource()
{
FontFamily = new Windows.UI.Xaml.Media.FontFamily("ms-appx:///Assets/WinoIcons.ttf#WinoIcons");
FontSize = 32;
}
private static void OnIconChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoFontIconSource fontIcon)
{
fontIcon.UpdateGlyph();
}
}
private void UpdateGlyph()
{
Glyph = ControlConstants.WinoIconFontDictionary[Icon];
}
}
private void UpdateGlyph()
{
Glyph = ControlConstants.WinoIconFontDictionary[Icon];
}
}

View File

@@ -5,86 +5,87 @@ using Microsoft.UI.Xaml.Controls;
using Windows.UI.Xaml;
using Wino.Core.Domain.Enums;
namespace Wino.Core.UWP.Controls;
public partial class WinoInfoBar : InfoBar
namespace Wino.Core.UWP.Controls
{
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 partial class WinoInfoBar : InfoBar
{
get { return (InfoBarAnimationType)GetValue(AnimationTypeProperty); }
set { SetValue(AnimationTypeProperty, value); }
}
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 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)
public InfoBarAnimationType AnimationType
{
bar.UpdateInterval(bar.DismissInterval);
get { return (InfoBarAnimationType)GetValue(AnimationTypeProperty); }
set { SetValue(AnimationTypeProperty, value); }
}
}
private void UpdateInterval(int seconds) => _dispatcherTimer.Interval = TimeSpan.FromSeconds(seconds);
private async void IsOpenChanged(DependencyObject sender, DependencyProperty dp)
{
if (sender is WinoInfoBar control)
public int DismissInterval
{
// Message shown.
if (!control.IsOpen) return;
get { return (int)GetValue(DismissIntervalProperty); }
set { SetValue(DismissIntervalProperty, value); }
}
Opacity = 1;
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)
{
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.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);
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, 0, 0), new Vector3(0, 50, 0), null, TimeSpan.FromSeconds(0.5)).StartAsync(this);
await AnimationBuilder.Create().Translation(new Vector3(0, (float)ActualHeight, 0), new Vector3(0, 0, 0), null, TimeSpan.FromSeconds(0.5)).StartAsync(this);
}
}
}
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);
IsOpen = false;
}
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,44 +3,45 @@ using Microsoft.UI.Xaml.Controls;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Hosting;
namespace Wino.Core.UWP.Controls;
public partial class WinoNavigationViewItem : NavigationViewItem
namespace Wino.Core.UWP.Controls
{
public bool IsDraggingItemOver
public partial class WinoNavigationViewItem : NavigationViewItem
{
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)
public bool IsDraggingItemOver
{
var visual = ElementCompositionPreview.GetElementVisual(content);
visual.Scale = vector;
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;
}
}
}
}

View File

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

View File

@@ -1,20 +1,21 @@
using System;
using Windows.UI.Xaml.Data;
namespace Wino.Converters;
public partial class ReverseBooleanConverter : IValueConverter
namespace Wino.Converters
{
public object Convert(object value, Type targetType, object parameter, string language)
public partial class ReverseBooleanConverter : IValueConverter
{
if (value is bool boolval)
return !boolval;
public object Convert(object value, Type targetType, object parameter, string language)
{
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,17 +2,18 @@
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
namespace Wino.Converters;
public partial class ReverseBooleanToVisibilityConverter : IValueConverter
namespace Wino.Converters
{
public object Convert(object value, Type targetType, object parameter, string language)
public partial class ReverseBooleanToVisibilityConverter : IValueConverter
{
return ((bool)value) ? Visibility.Collapsed : Visibility.Visible;
}
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();
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

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

View File

@@ -6,45 +6,46 @@ using Wino.Core.UWP.Services;
using Wino.Core.ViewModels;
using Wino.Services;
namespace Wino.Core.UWP;
public static class CoreUWPContainerSetup
namespace Wino.Core.UWP
{
public static void RegisterCoreUWPServices(this IServiceCollection services)
public static class CoreUWPContainerSetup
{
var serverConnectionManager = new WinoServerConnectionManager();
public static void RegisterCoreUWPServices(this IServiceCollection services)
{
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,66 +5,67 @@ using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
namespace Wino.Dialogs;
public sealed partial class AccountCreationDialog : ContentDialog, IAccountCreationDialog
namespace Wino.Dialogs
{
private TaskCompletionSource<bool> dialogOpened = new TaskCompletionSource<bool>();
public CancellationTokenSource CancellationTokenSource { get; private set; }
public AccountCreationDialogState State
public sealed partial class AccountCreationDialog : ContentDialog, IAccountCreationDialog
{
get { return (AccountCreationDialogState)GetValue(StateProperty); }
set { SetValue(StateProperty, value); }
}
private TaskCompletionSource<bool> dialogOpened = new TaskCompletionSource<bool>();
public CancellationTokenSource CancellationTokenSource { get; private set; }
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)
public AccountCreationDialogState State
{
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();
get { return (AccountCreationDialogState)GetValue(StateProperty); }
set { SetValue(StateProperty, value); }
}
Hide();
}
public static readonly DependencyProperty StateProperty = DependencyProperty.Register(nameof(State), typeof(AccountCreationDialogState), typeof(AccountCreationDialog), new PropertyMetadata(AccountCreationDialogState.Idle));
private void CancelClicked(object sender, System.EventArgs e) => Complete(true);
public AccountCreationDialog()
{
InitializeComponent();
}
public async Task ShowDialogAsync(CancellationTokenSource cancellationTokenSource)
{
CancellationTokenSource = cancellationTokenSource;
// Prevent users from dismissing it by ESC key.
public void DialogClosing(ContentDialog sender, ContentDialogClosingEventArgs args)
{
if (args.Result == ContentDialogResult.None)
{
args.Cancel = true;
}
}
Opened += DialogOpened;
_ = ShowAsync();
public void Complete(bool cancel)
{
State = cancel ? AccountCreationDialogState.Canceled : AccountCreationDialogState.Completed;
await dialogOpened.Task;
}
// Unregister from closing event.
Closing -= DialogClosing;
private void DialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args)
{
Opened -= DialogOpened;
if (cancel && !CancellationTokenSource.IsCancellationRequested)
{
CancellationTokenSource.Cancel();
}
dialogOpened?.SetResult(true);
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,21 +1,22 @@
using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Entities.Shared;
namespace Wino.Dialogs;
public sealed partial class AccountEditDialog : ContentDialog
namespace Wino.Dialogs
{
public MailAccount Account { get; private set; }
public bool IsSaved { get; set; }
public AccountEditDialog(MailAccount account)
public sealed partial class AccountEditDialog : ContentDialog
{
InitializeComponent();
Account = account;
}
public MailAccount Account { get; private set; }
public bool IsSaved { get; set; }
private void SaveClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
IsSaved = true;
public AccountEditDialog(MailAccount account)
{
InitializeComponent();
Account = account;
}
private void SaveClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
IsSaved = true;
}
}
}

View File

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

View File

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

View File

@@ -6,59 +6,60 @@ using Windows.UI.Xaml.Media;
using Wino.Core.Domain.Interfaces;
using Wino.Core.UWP;
namespace Wino.Dialogs;
public sealed partial class CustomThemeBuilderDialog : ContentDialog
namespace Wino.Dialogs
{
public byte[] WallpaperData { get; private set; }
public string AccentColor { get; private set; }
private IThemeService _themeService;
public CustomThemeBuilderDialog()
public sealed partial class CustomThemeBuilderDialog : ContentDialog
{
InitializeComponent();
public byte[] WallpaperData { get; private set; }
public string AccentColor { get; private set; }
_themeService = WinoApplication.Current.Services.GetService<IThemeService>();
}
private IThemeService _themeService;
private async void ApplyClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
if (Array.Empty<byte>() == WallpaperData)
return;
var deferal = args.GetDeferral();
try
public CustomThemeBuilderDialog()
{
await _themeService.CreateNewCustomThemeAsync(ThemeNameBox.Text, AccentColor, WallpaperData);
InitializeComponent();
_themeService = WinoApplication.Current.Services.GetService<IThemeService>();
}
catch (Exception exception)
private async void ApplyClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
ErrorTextBlock.Text = exception.Message;
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();
}
}
finally
private async void BrowseWallpaperClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
deferal.Complete();
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 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();
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,145 +7,146 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Accounts;
namespace Wino.Core.UWP.Dialogs;
public sealed partial class NewAccountDialog : ContentDialog
namespace Wino.Core.UWP.Dialogs
{
private Dictionary<SpecialImapProvider, string> helpingLinks = new Dictionary<SpecialImapProvider, string>()
public sealed partial class NewAccountDialog : ContentDialog
{
{ 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)
private Dictionary<SpecialImapProvider, string> helpingLinks = new Dictionary<SpecialImapProvider, string>()
{
// Special imap detail input.
{ SpecialImapProvider.iCloud, "https://support.apple.com/en-us/102654" },
{ SpecialImapProvider.Yahoo, "http://help.yahoo.com/kb/SLN15241.html" },
};
var details = new SpecialImapProviderDetails(SpecialImapAddress.Text.Trim(), AppSpecificPassword.Password.Trim(), DisplayNameTextBox.Text.Trim(), SelectedMailProvider.SpecialImapProvider);
Result = new AccountCreationDialogResult(SelectedMailProvider.Type, AccountNameTextbox.Text.Trim(), details);
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();
return;
}
Validate();
if (IsSecondaryButtonEnabled)
private void CreateClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
if (SelectedMailProvider.SpecialImapProvider != SpecialImapProvider.None)
if (IsSpecialImapServerPartVisible)
{
// This step requires app-sepcific password login for some providers.
args.Cancel = true;
// Special imap detail input.
IsProviderSelectionVisible = false;
IsSpecialImapServerPartVisible = true;
Validate();
}
else
{
Result = new AccountCreationDialogResult(SelectedMailProvider.Type, AccountNameTextbox.Text.Trim(), null);
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;
}
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 InputChanged(object sender, TextChangedEventArgs e) => Validate();
private void SenderNameChanged(object sender, TextChangedEventArgs e) => Validate();
private void Validate()
{
ValidateCreateButton();
ValidateNames();
}
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);
// 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;
}
IsPrimaryButtonEnabled = shouldEnable;
}
private void ValidateNames()
{
AccountNameTextbox.IsEnabled = SelectedMailProvider != null;
}
private void ValidateNames()
{
AccountNameTextbox.IsEnabled = SelectedMailProvider != null;
}
private void DialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args) => Validate();
private void DialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args) => Validate();
private void BackClicked(object sender, RoutedEventArgs e)
{
IsSpecialImapServerPartVisible = false;
IsProviderSelectionVisible = true;
private void BackClicked(object sender, RoutedEventArgs e)
{
IsSpecialImapServerPartVisible = false;
IsProviderSelectionVisible = true;
Validate();
}
Validate();
}
private void ImapPasswordChanged(object sender, RoutedEventArgs e) => Validate();
private void ImapPasswordChanged(object sender, RoutedEventArgs e) => Validate();
private async void AppSpecificHelpButtonClicked(object sender, RoutedEventArgs e)
{
var helpUrl = helpingLinks[SelectedMailProvider.SpecialImapProvider];
private async void AppSpecificHelpButtonClicked(object sender, RoutedEventArgs e)
{
var helpUrl = helpingLinks[SelectedMailProvider.SpecialImapProvider];
await Launcher.LaunchUriAsync(new Uri(helpUrl));
await Launcher.LaunchUriAsync(new Uri(helpUrl));
}
}
}

View File

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

View File

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

View File

@@ -8,108 +8,109 @@ using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media.Animation;
namespace Wino.Extensions;
public static class AnimationExtensions
namespace Wino.Extensions
{
#region Composition
public static ScalarKeyFrameAnimation CreateScalarKeyFrameAnimation(this Compositor compositor, float? from, float to,
double duration, double delay, CompositionEasingFunction easing, AnimationIterationBehavior iterationBehavior)
public static class AnimationExtensions
{
var animation = compositor.CreateScalarKeyFrameAnimation();
#region Composition
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)
public static ScalarKeyFrameAnimation CreateScalarKeyFrameAnimation(this Compositor compositor, float? from, float to,
double duration, double delay, CompositionEasingFunction easing, AnimationIterationBehavior iterationBehavior)
{
easing = new ExponentialEase();
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;
return animation;
}
var db = new DoubleAnimation
public static Vector2KeyFrameAnimation CreateVector2KeyFrameAnimation(this Compositor compositor, Vector2? from, Vector2 to,
double duration, double delay, CompositionEasingFunction easing, AnimationIterationBehavior iterationBehavior)
{
EnableDependentAnimation = enableDependentAnimation,
To = to,
From = from,
EasingFunction = easing,
Duration = TimeSpan.FromMilliseconds(duration)
};
Storyboard.SetTarget(db, target);
Storyboard.SetTargetProperty(db, propertyPath);
var animation = compositor.CreateVector2KeyFrameAnimation();
var sb = new Storyboard
{
BeginTime = TimeSpan.FromMilliseconds(startTime)
};
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;
if (completed != null)
return animation;
}
public static Vector3KeyFrameAnimation CreateVector3KeyFrameAnimation(this Compositor compositor, Vector2? from, Vector2 to,
double duration, double delay, CompositionEasingFunction easing, AnimationIterationBehavior iterationBehavior)
{
sb.Completed += (s, e) =>
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)
{
completed();
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();
}
sb.Children.Add(db);
sb.Begin();
#endregion
}
#endregion
}

View File

@@ -4,69 +4,70 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wino.Extensions;
public enum TransitionDirection
namespace Wino.Extensions
{
TopToBottom,
BottomToTop,
LeftToRight,
RightToLeft
}
public enum TransitionDirection
{
TopToBottom,
BottomToTop,
LeftToRight,
RightToLeft
}
public enum ClipAnimationDirection
{
Top,
Bottom,
Left,
Right
}
public enum ClipAnimationDirection
{
Top,
Bottom,
Left,
Right
}
public enum AnimationAxis
{
X,
Y,
Z
}
public enum AnimationAxis
{
X,
Y,
Z
}
public enum AnimationType
{
KeyFrame,
Expression
}
public enum AnimationType
{
KeyFrame,
Expression
}
public enum FlickDirection
{
None,
Up,
Down,
Left,
Right
}
public enum FlickDirection
{
None,
Up,
Down,
Left,
Right
}
public enum ViewState
{
Empty,
Small,
Big,
Full
}
public enum ViewState
{
Empty,
Small,
Big,
Full
}
public enum Gesture
{
Initial,
Tap,
Swipe
}
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
[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,192 +8,193 @@ using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Hosting;
namespace Wino.Extensions;
public static partial class CompositionExtensions
namespace Wino.Extensions
{
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 partial class CompositionExtensions
{
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)
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)
{
hideOpacityAnimation = compositor.CreateScalarKeyFrameAnimation();
hideOpacityAnimation.InsertKeyFrame(1.0f, 0.0f);
hideOpacityAnimation.Duration = TimeSpan.FromMilliseconds(hideDuration);
hideOpacityAnimation.DelayTime = TimeSpan.FromMilliseconds(hideDelay);
hideOpacityAnimation.Target = "Opacity";
}
var elementVisual = element.Visual();
var compositor = elementVisual.Compositor;
ElementCompositionPreview.SetIsTranslationEnabled(element, true);
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}";
}
ScalarKeyFrameAnimation hideOpacityAnimation = null;
ScalarKeyFrameAnimation showOpacityAnimation = null;
ScalarKeyFrameAnimation hideOffsetAnimation = null;
ScalarKeyFrameAnimation showOffsetAnimation = null;
Vector2KeyFrameAnimation hideScaleAnimation = null;
Vector2KeyFrameAnimation showeScaleAnimation = null;
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);
}
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)
if (animateOpacity)
{
animationCollection[type.ToString()] = animation;
hideOpacityAnimation = compositor.CreateScalarKeyFrameAnimation();
hideOpacityAnimation.InsertKeyFrame(1.0f, 0.0f);
hideOpacityAnimation.Duration = TimeSpan.FromMilliseconds(hideDuration);
hideOpacityAnimation.DelayTime = TimeSpan.FromMilliseconds(hideDelay);
hideOpacityAnimation.Target = "Opacity";
}
}
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)
if (!hideToOffset.Equals(0.0f))
{
animationCollection[type.ToString()] = animation;
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);
}
visual.ImplicitAnimations = animationCollection;
}
private static KeyFrameAnimation CreateAnimationByType(Compositor compositor, VisualPropertyType type,
double duration = 800, double delay = 0, CompositionEasingFunction easing = null)
{
KeyFrameAnimation animation;
switch (type)
public static void EnableImplicitAnimation(this UIElement element, VisualPropertyType typeToAnimate,
double duration = 800, double delay = 0, CompositionEasingFunction easing = null)
{
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;
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;
}
animation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", easing);
animation.Duration = TimeSpan.FromMilliseconds(duration);
animation.DelayTime = TimeSpan.FromMilliseconds(delay);
animation.Target = type.ToString();
public static void EnableImplicitAnimation(this Visual visual, VisualPropertyType typeToAnimate,
double duration = 800, double delay = 0, CompositionEasingFunction easing = null)
{
var compositor = visual.Compositor;
return animation;
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,123 +4,124 @@ using System.Threading.Tasks;
using Windows.UI.Composition;
using Windows.UI.Xaml;
namespace Wino.Extensions;
public static partial class CompositionExtensions
namespace Wino.Extensions
{
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 partial class CompositionExtensions
{
CompositionScopedBatch batch = null;
var visual = element.Visual();
var compositor = visual.Compositor;
if (completed != null)
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)
{
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 += (s, e) => completed();
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;
}
if (to == null)
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)
{
to = Vector2.One;
}
CompositionScopedBatch batch;
visual.StartAnimation("Size",
compositor.CreateVector2KeyFrameAnimation(from, to.Value, duration, delay, easing, iterationBehavior));
var compositor = visual.Compositor;
batch?.End();
}
var taskSource = new TaskCompletionSource<bool>();
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;
void Completed(object o, CompositionBatchCompletedEventArgs e)
{
batch.Completed -= Completed;
taskSource.SetResult(true);
}
if (completed != null)
{
batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
batch.Completed += (s, e) => completed();
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;
}
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,33 +1,34 @@
using Windows.UI.Xaml;
using Wino.Core.Domain.Enums;
namespace Wino.Core.UWP.Extensions;
public static class ElementThemeExtensions
namespace Wino.Core.UWP.Extensions
{
public static ApplicationElementTheme ToWinoElementTheme(this ElementTheme elementTheme)
public static class ElementThemeExtensions
{
switch (elementTheme)
public static ApplicationElementTheme ToWinoElementTheme(this ElementTheme elementTheme)
{
case ElementTheme.Light:
return ApplicationElementTheme.Light;
case ElementTheme.Dark:
return ApplicationElementTheme.Dark;
switch (elementTheme)
{
case ElementTheme.Light:
return ApplicationElementTheme.Light;
case ElementTheme.Dark:
return ApplicationElementTheme.Dark;
}
return ApplicationElementTheme.Default;
}
return ApplicationElementTheme.Default;
}
public static ElementTheme ToWindowsElementTheme(this ApplicationElementTheme elementTheme)
{
switch (elementTheme)
public static ElementTheme ToWindowsElementTheme(this ApplicationElementTheme elementTheme)
{
case ApplicationElementTheme.Light:
return ElementTheme.Light;
case ApplicationElementTheme.Dark:
return ElementTheme.Dark;
}
switch (elementTheme)
{
case ApplicationElementTheme.Light:
return ElementTheme.Light;
case ApplicationElementTheme.Dark:
return ElementTheme.Dark;
}
return ElementTheme.Default;
return ElementTheme.Default;
}
}
}

View File

@@ -1,24 +1,25 @@
using Windows.ApplicationModel;
using Wino.Core.Domain.Enums;
namespace Wino.Core.UWP.Extensions;
public static class StartupTaskStateExtensions
namespace Wino.Core.UWP.Extensions
{
public static StartupBehaviorResult AsStartupBehaviorResult(this StartupTaskState state)
public static class StartupTaskStateExtensions
{
switch (state)
public static StartupBehaviorResult AsStartupBehaviorResult(this StartupTaskState 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;
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;
}
}
}
}

View File

@@ -4,27 +4,28 @@ using System.Threading.Tasks;
using Windows.Storage;
using Wino.Core.Domain.Models.Common;
namespace Wino.Core.UWP.Extensions;
public static class StorageFileExtensions
namespace Wino.Core.UWP.Extensions
{
public static async Task<SharedFile> ToSharedFileAsync(this Windows.Storage.StorageFile storageFile)
public static class StorageFileExtensions
{
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())
public static async Task<SharedFile> ToSharedFileAsync(this Windows.Storage.StorageFile storageFile)
{
await stream.AsStreamForRead().CopyToAsync(memoryStream);
return memoryStream.ToArray();
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();
}
}
}
}

View File

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

View File

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

View File

@@ -2,50 +2,51 @@
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
namespace Wino.Helpers;
public static class WinoVisualTreeHelper
namespace Wino.Helpers
{
public static T GetChildObject<T>(DependencyObject obj, string name) where T : FrameworkElement
public static class WinoVisualTreeHelper
{
DependencyObject child = null;
T grandChild = null;
for (int i = 0; i <= VisualTreeHelper.GetChildrenCount(obj) - 1; i++)
public static T GetChildObject<T>(DependencyObject obj, string name) where T : FrameworkElement
{
child = VisualTreeHelper.GetChild(obj, i);
DependencyObject child = null;
T grandChild = null;
if (child is T && (((T)child).Name == name | string.IsNullOrEmpty(name)))
for (int i = 0; i <= VisualTreeHelper.GetChildrenCount(obj) - 1; i++)
{
return (T)child;
}
else
{
grandChild = GetChildObject<T>(child, name);
}
if (grandChild != null)
{
return grandChild;
}
}
return null;
}
child = VisualTreeHelper.GetChild(obj, i);
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)
if (child is T && (((T)child).Name == name | string.IsNullOrEmpty(name)))
{
yield return (T)child;
return (T)child;
}
foreach (T childOfChild in FindDescendants<T>(child))
else
{
yield return childOfChild;
grandChild = GetChildObject<T>(child, name);
}
if (grandChild != null)
{
return grandChild;
}
}
return null;
}
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,350 +16,351 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.UWP.Controls;
namespace Wino.Helpers;
public static class XamlHelpers
namespace Wino.Helpers
{
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)
public static class XamlHelpers
{
return type switch
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)
{
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)
return type switch
{
if (dateTimeValue == DateTime.Today)
return Translator.Today;
else if (dateTimeValue == DateTime.Today.AddDays(-1))
return Translator.Yesterday;
else
return dateTimeValue.ToLongDateString();
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
return dateObject.ToString();
{
throw new ArgumentException("Hex color must be 6 characters long (e.g., #RRGGBB).");
}
}
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
private static double GetLinearValue(int colorComponent)
{
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,
};
}
double sRGB = colorComponent / 255.0;
return sRGB <= 0.03928 ? sRGB / 12.92 : Math.Pow((sRGB + 0.055) / 1.055, 2.4);
}
public static WinoIconGlyph GetPathGeometry(FolderOperation operation)
{
return operation switch
public static Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode NavigationViewDisplayModeConverter(SplitViewDisplayMode splitViewDisplayMode)
{
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
return splitViewDisplayMode switch
{
MailProviderType.Outlook => WinoIconGlyph.Microsoft,
MailProviderType.Gmail => WinoIconGlyph.Google,
MailProviderType.IMAP4 => WinoIconGlyph.IMAP,
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,
};
}
else
public static WinoIconGlyph GetPathGeometry(FolderOperation operation)
{
return specialImapProvider switch
return operation switch
{
SpecialImapProvider.iCloud => WinoIconGlyph.Apple,
SpecialImapProvider.Yahoo => WinoIconGlyph.Yahoo,
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 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
public static WinoIconGlyph GetSpecialFolderPathIconGeometry(SpecialFolderType specialFolderType)
{
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",
};
}
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 string GetOperationString(FolderOperation operation)
{
return operation switch
public static WinoIconGlyph GetProviderIcon(MailProviderType providerType, SpecialImapProvider specialImapProvider)
{
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,
};
}
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);
#endregion
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,26 +5,27 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Personalization;
using Wino.Services;
namespace Wino.Core.UWP.Models.Personalization;
/// <summary>
/// Custom themes that are generated by users.
/// </summary>
public class CustomAppTheme : AppThemeBase
namespace Wino.Core.UWP.Models.Personalization
{
public CustomAppTheme(CustomThemeMetadata metadata) : base(metadata.Name, metadata.Id)
/// <summary>
/// Custom themes that are generated by users.
/// </summary>
public class CustomAppTheme : AppThemeBase
{
AccentColor = metadata.AccentColorHex;
}
public CustomAppTheme(CustomThemeMetadata metadata) : base(metadata.Name, metadata.Id)
{
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,30 +4,31 @@ using Windows.Storage;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.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
namespace Wino.Core.UWP.Models.Personalization
{
public PreDefinedAppTheme(string themeName,
Guid id,
string accentColor = "",
ApplicationElementTheme forcedElementTheme = ApplicationElementTheme.Default) : base(themeName, id)
/// <summary>
/// Forest, Nighty, Clouds etc. applies to pre-defined themes in Wino.
/// </summary>
public class PreDefinedAppTheme : AppThemeBase
{
AccentColor = accentColor;
ForceElementTheme = forcedElementTheme;
}
public PreDefinedAppTheme(string themeName,
Guid id,
string accentColor = "",
ApplicationElementTheme forcedElementTheme = ApplicationElementTheme.Default) : base(themeName, id)
{
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,12 +1,13 @@
using System;
using Wino.Core.Domain.Enums;
namespace Wino.Core.UWP.Models.Personalization;
// Mica - Acrylic.
public class SystemAppTheme : PreDefinedAppTheme
namespace Wino.Core.UWP.Models.Personalization
{
public SystemAppTheme(string themeName, Guid id) : base(themeName, id, "") { }
// Mica - Acrylic.
public class SystemAppTheme : PreDefinedAppTheme
{
public SystemAppTheme(string themeName, Guid id) : base(themeName, id, "") { }
public override AppThemeType AppThemeType => AppThemeType.System;
public override AppThemeType AppThemeType => AppThemeType.System;
}
}

View File

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

View File

@@ -3,35 +3,36 @@ using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Enums;
namespace Wino.Core.UWP.Selectors;
public partial class CustomWinoMessageDialogIconSelector : DataTemplateSelector
namespace Wino.Core.UWP.Selectors
{
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 partial class CustomWinoMessageDialogIconSelector : DataTemplateSelector
{
if (item == null) return null;
public DataTemplate InfoIconTemplate { get; set; }
public DataTemplate WarningIconTemplate { get; set; }
public DataTemplate QuestionIconTemplate { get; set; }
public DataTemplate ErrorIconTemplate { get; set; }
if (item is WinoCustomMessageDialogIcon icon)
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
switch (icon)
if (item == null) return null;
if (item is WinoCustomMessageDialogIcon 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.");
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.");
}
}
return base.SelectTemplateCore(item, container);
}
return base.SelectTemplateCore(item, container);
}
}

View File

@@ -2,50 +2,51 @@
using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Enums;
namespace Wino.Core.UWP.Selectors;
public partial class FileAttachmentTypeSelector : DataTemplateSelector
namespace Wino.Core.UWP.Selectors
{
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 partial class FileAttachmentTypeSelector : DataTemplateSelector
{
if (item == null)
return None;
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; }
var type = (MailAttachmentType)item;
switch (type)
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
case MailAttachmentType.None:
if (item == null)
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;
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;
}
}
}
}

View File

@@ -2,57 +2,58 @@
using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.MenuItems;
namespace Wino.Core.UWP.Selectors;
public partial class NavigationMenuTemplateSelector : DataTemplateSelector
namespace Wino.Core.UWP.Selectors
{
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)
public partial class NavigationMenuTemplateSelector : DataTemplateSelector
{
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
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)
{
var type = item.GetType();
return null;
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;
}
}
}
}

View File

@@ -3,24 +3,25 @@ using Windows.UI.Xaml;
using Wino.Core.Domain.Interfaces;
using Wino.Core.UWP;
namespace Wino.Services;
public class ApplicationResourceManager : IApplicationResourceManager<ResourceDictionary>
namespace Wino.Services
{
public void AddResource(ResourceDictionary resource)
=> WinoApplication.Current.Resources.MergedDictionaries.Add(resource);
public void RemoveResource(ResourceDictionary resource)
=> WinoApplication.Current.Resources.MergedDictionaries.Remove(resource);
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 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,58 +5,59 @@ using Serilog;
using Windows.ApplicationModel.Background;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.UWP.Services;
public class BackgroundTaskService : IBackgroundTaskService
namespace Wino.Core.UWP.Services
{
private const string IsBackgroundTasksUnregisteredKey = nameof(IsBackgroundTasksUnregisteredKey);
public const string ToastNotificationActivationHandlerTaskName = "ToastNotificationActivationHandlerTask";
private readonly IConfigurationService _configurationService;
public BackgroundTaskService(IConfigurationService configurationService)
public class BackgroundTaskService : IBackgroundTaskService
{
_configurationService = configurationService;
}
private const string IsBackgroundTasksUnregisteredKey = nameof(IsBackgroundTasksUnregisteredKey);
public const string ToastNotificationActivationHandlerTaskName = "ToastNotificationActivationHandlerTask";
public void UnregisterAllBackgroundTask()
{
if (_configurationService.Get(IsBackgroundTasksUnregisteredKey, false))
private readonly IConfigurationService _configurationService;
public BackgroundTaskService(IConfigurationService configurationService)
{
foreach (var task in BackgroundTaskRegistration.AllTasks)
{
task.Value.Unregister(true);
}
_configurationService = configurationService;
}
Log.Information("Unregistered all background tasks.");
_configurationService.Set(IsBackgroundTasksUnregisteredKey, true);
public void UnregisterAllBackgroundTask()
{
if (_configurationService.Get(IsBackgroundTasksUnregisteredKey, false))
{
foreach (var task in BackgroundTaskRegistration.AllTasks)
{
task.Value.Unregister(true);
}
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();
}
}
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,17 +2,18 @@
using Windows.ApplicationModel.DataTransfer;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.UWP.Services;
public class ClipboardService : IClipboardService
namespace Wino.Core.UWP.Services
{
public Task CopyClipboardAsync(string text)
public class ClipboardService : IClipboardService
{
var package = new DataPackage();
package.SetText(text);
public Task CopyClipboardAsync(string text)
{
var package = new DataPackage();
package.SetText(text);
Clipboard.SetContent(package);
Clipboard.SetContent(package);
return Task.CompletedTask;
return Task.CompletedTask;
}
}
}

View File

@@ -5,47 +5,48 @@ using Windows.Foundation.Collections;
using Windows.Storage;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.UWP.Services;
public class ConfigurationService : IConfigurationService
namespace Wino.Core.UWP.Services
{
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 class ConfigurationService : IConfigurationService
{
if (collection.TryGetValue(key, out object value))
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)
{
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))
if (collection.TryGetValue(key, out object value))
{
return (T)(object)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));
}
if (typeof(T) == typeof(TimeSpan))
{
return (T)(object)TimeSpan.Parse(stringValue);
}
return (T)Convert.ChangeType(stringValue, typeof(T));
return defaultValue;
}
return defaultValue;
private static void SetInternal(string key, object value, IPropertySet collection)
=> collection[key] = value?.ToString();
}
private static void SetInternal(string key, object value, IPropertySet collection)
=> collection[key] = value?.ToString();
}

View File

@@ -20,283 +20,284 @@ using Wino.Core.UWP.Extensions;
using Wino.Dialogs;
using Wino.Messaging.Client.Shell;
namespace Wino.Core.UWP.Services;
public class DialogServiceBase : IDialogServiceBase
namespace Wino.Core.UWP.Services
{
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)
public class DialogServiceBase : IDialogServiceBase
{
ThemeService = themeService;
ConfigurationService = configurationService;
ApplicationResourceManager = applicationResourceManager;
}
private SemaphoreSlim _presentationSemaphore = new SemaphoreSlim(1);
public async Task<MailAccount> ShowEditAccountDialogAsync(MailAccount account)
{
var editAccountDialog = new AccountEditDialog(account)
protected IThemeService ThemeService { get; }
protected IConfigurationService ConfigurationService { get; }
protected IApplicationResourceManager<ResourceDictionary> ApplicationResourceManager { get; }
public DialogServiceBase(IThemeService themeService, IConfigurationService configurationService, IApplicationResourceManager<ResourceDictionary> 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());
ThemeService = themeService;
ConfigurationService = configurationService;
ApplicationResourceManager = applicationResourceManager;
}
var files = await picker.PickMultipleFilesAsync();
if (files == null) return returnList;
foreach (var file in files)
public async Task<MailAccount> ShowEditAccountDialogAsync(MailAccount account)
{
var editAccountDialog = new AccountEditDialog(account)
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(editAccountDialog);
return editAccountDialog.IsSaved ? editAccountDialog.Account : null;
}
public async Task<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);
var sharedFile = await file.ToSharedFileAsync();
returnList.Add(sharedFile);
return file;
}
return returnList;
}
private async Task<StorageFile> PickFileAsync(params object[] typeFilters)
{
var picker = new FileOpenPicker
public virtual IAccountCreationDialog GetAccountCreationDialog(AccountCreationDialogResult accountCreationDialogResult)
{
ViewMode = PickerViewMode.Thumbnail
};
foreach (var filter in typeFilters)
{
picker.FileTypeFilter.Add(filter.ToString());
return new AccountCreationDialog
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
}
var file = await picker.PickSingleFileAsync();
if (file == null) return null;
StorageApplicationPermissions.FutureAccessList.Add(file);
return file;
}
public virtual IAccountCreationDialog GetAccountCreationDialog(AccountCreationDialogResult accountCreationDialogResult)
{
return new AccountCreationDialog
public async Task<byte[]> PickWindowsFileContentAsync(params object[] typeFilters)
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
}
var file = await PickFileAsync(typeFilters);
public async Task<byte[]> PickWindowsFileContentAsync(params object[] typeFilters)
{
var file = await PickFileAsync(typeFilters);
if (file == null) return [];
if (file == null) return [];
return await file.ToByteArrayAsync();
}
public Task ShowMessageAsync(string message, string title, WinoCustomMessageDialogIcon icon = WinoCustomMessageDialogIcon.Information)
=> ShowWinoCustomMessageDialogAsync(title, message, Translator.Buttons_Close, icon);
public Task<bool> ShowConfirmationDialogAsync(string question, string title, string confirmationButtonTitle)
=> ShowWinoCustomMessageDialogAsync(title, question, confirmationButtonTitle, WinoCustomMessageDialogIcon.Question, Translator.Buttons_Cancel, string.Empty);
public async Task<bool> ShowWinoCustomMessageDialogAsync(string title,
string description,
string approveButtonText,
WinoCustomMessageDialogIcon? icon,
string cancelButtonText = "",
string dontAskAgainConfigurationKey = "")
{
// This config key has been marked as don't ask again already.
// Return immidiate result without presenting the dialog.
bool isDontAskEnabled = !string.IsNullOrEmpty(dontAskAgainConfigurationKey);
if (isDontAskEnabled && ConfigurationService.Get(dontAskAgainConfigurationKey, false)) return false;
var informationContainer = new CustomMessageDialogInformationContainer(title, description, icon.Value, isDontAskEnabled);
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
};
if (!string.IsNullOrEmpty(cancelButtonText))
{
dialog.SecondaryButtonText = cancelButtonText;
return await file.ToByteArrayAsync();
}
var dialogResult = await HandleDialogPresentationAsync(dialog);
public Task ShowMessageAsync(string message, string title, WinoCustomMessageDialogIcon icon = WinoCustomMessageDialogIcon.Information)
=> ShowWinoCustomMessageDialogAsync(title, message, Translator.Buttons_Close, icon);
public Task<bool> ShowConfirmationDialogAsync(string question, string title, string confirmationButtonTitle)
=> ShowWinoCustomMessageDialogAsync(title, question, confirmationButtonTitle, WinoCustomMessageDialogIcon.Question, Translator.Buttons_Cancel, string.Empty);
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.
bool isDontAskEnabled = !string.IsNullOrEmpty(dontAskAgainConfigurationKey);
if (isDontAskEnabled && ConfigurationService.Get(dontAskAgainConfigurationKey, false)) return false;
var informationContainer = new CustomMessageDialogInformationContainer(title, description, icon.Value, isDontAskEnabled);
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
};
if (!string.IsNullOrEmpty(cancelButtonText))
{
dialog.SecondaryButtonText = cancelButtonText;
}
var dialogResult = await HandleDialogPresentationAsync(dialog);
// Mark this key to not ask again if user checked the checkbox.
if (informationContainer.IsDontAskChecked)
{
ConfigurationService.Set(dontAskAgainConfigurationKey, true);
}
return dialogResult == ContentDialogResult.Primary;
}
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();
/// <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
{
return await dialog.ShowAsync();
}
catch (Exception ex)
{
Log.Error(ex, $"Handling dialog service failed. Dialog was {dialog.GetType().Name}");
}
finally
{
_presentationSemaphore.Release();
}
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;
}
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)
=> 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 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 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()
public async Task<string> ShowTextInputDialogAsync(string currentInput, string dialogTitle, string dialogDescription, string primaryButtonText)
{
CurrentInput = currentInput,
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme(),
Title = dialogTitle
};
var inputDialog = new TextInputDialog()
{
CurrentInput = currentInput,
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme(),
Title = dialogTitle
};
inputDialog.SetDescription(dialogDescription);
inputDialog.SetPrimaryButtonText(primaryButtonText);
inputDialog.SetDescription(dialogDescription);
inputDialog.SetPrimaryButtonText(primaryButtonText);
await HandleDialogPresentationAsync(inputDialog);
await HandleDialogPresentationAsync(inputDialog);
if (inputDialog.HasInput.GetValueOrDefault() && !currentInput.Equals(inputDialog.CurrentInput))
return inputDialog.CurrentInput;
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;
}
return string.Empty;
}
public async Task<bool> ShowCustomThemeBuilderDialogAsync()
{
var themeBuilderDialog = new CustomThemeBuilderDialog()
public async Task<string> PickWindowsFolderAsync()
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
var picker = new FolderPicker
{
SuggestedStartLocation = PickerLocationId.DocumentsLibrary
};
var dialogResult = await HandleDialogPresentationAsync(themeBuilderDialog);
picker.FileTypeFilter.Add("*");
return dialogResult == ContentDialogResult.Primary;
}
var pickedFolder = await picker.PickSingleFolderAsync();
public async Task<AccountCreationDialogResult> ShowAccountProviderSelectionDialogAsync(List<IProviderDetail> availableProviders)
{
var dialog = new NewAccountDialog
if (pickedFolder != null)
{
Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList.AddOrReplace("FolderPickerToken", pickedFolder);
return pickedFolder.Path;
}
return string.Empty;
}
public async Task<bool> ShowCustomThemeBuilderDialogAsync()
{
Providers = availableProviders,
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
var themeBuilderDialog = new CustomThemeBuilderDialog()
{
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(dialog);
var dialogResult = await HandleDialogPresentationAsync(themeBuilderDialog);
return dialog.Result;
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,56 +6,57 @@ using Windows.Storage;
using Wino.Core.Domain;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.UWP.Services;
public class FileService : IFileService
namespace Wino.Core.UWP.Services
{
public async Task<string> CopyFileAsync(string sourceFilePath, string destinationFolderPath)
public class FileService : IFileService
{
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)
public async Task<string> CopyFileAsync(string sourceFilePath, string destinationFolderPath)
{
using FileStream logFileStream = File.Open(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var fileName = Path.GetFileName(sourceFilePath);
var zipArchiveEntry = archive.CreateEntry(Path.GetFileName(logFile), CompressionLevel.Fastest);
using var zipStream = zipArchiveEntry.Open();
var sourceFileHandle = await StorageFile.GetFileFromPathAsync(sourceFilePath);
var destinationFolder = await StorageFolder.GetFolderFromPathAsync(destinationFolderPath);
await logFileStream.CopyToAsync(zipStream);
var copiedFile = await sourceFileHandle.CopyAsync(destinationFolder, fileName, NameCollisionOption.GenerateUniqueName);
return copiedFile.Path;
}
return true;
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;
}
}
}

View File

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

View File

@@ -14,95 +14,96 @@ using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#endif
namespace Wino.Services;
public class NativeAppService : INativeAppService
namespace Wino.Services
{
private string _mimeMessagesFolder;
private string _editorBundlePath;
public Func<IntPtr> GetCoreWindowHwnd { get; set; }
public string GetWebAuthenticationBrokerUri()
public class NativeAppService : INativeAppService
{
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 _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 string.Empty;
}
return _editorBundlePath;
}
public async Task<string> GetMimeMessageStoragePath()
{
if (!string.IsNullOrEmpty(_mimeMessagesFolder))
return _mimeMessagesFolder;
[Obsolete("This should be removed. There should be no functionality.")]
public bool IsAppRunning()
{
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;
}
[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,25 +3,26 @@ using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Animation;
using Wino.Core.Domain.Models.Navigation;
namespace Wino.Core.UWP.Services;
public class NavigationServiceBase
namespace Wino.Core.UWP.Services
{
public NavigationTransitionInfo GetNavigationTransitionInfo(NavigationTransitionType transition)
public class NavigationServiceBase
{
return transition switch
public NavigationTransitionInfo GetNavigationTransitionInfo(NavigationTransitionType transition)
{
NavigationTransitionType.DrillIn => new DrillInNavigationTransitionInfo(),
NavigationTransitionType.Entrance => new EntranceNavigationTransitionInfo(),
_ => new SuppressNavigationTransitionInfo(),
};
}
return transition switch
{
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,109 +13,43 @@ using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Services;
namespace Wino.Core.UWP.Services;
// TODO: Refactor this thing. It's garbage.
public class NotificationBuilder : INotificationBuilder
namespace Wino.Core.UWP.Services
{
private readonly IUnderlyingThemeService _underlyingThemeService;
private readonly IAccountService _accountService;
private readonly IFolderService _folderService;
private readonly IMailService _mailService;
// TODO: Refactor this thing. It's garbage.
public NotificationBuilder(IUnderlyingThemeService underlyingThemeService,
IAccountService accountService,
IFolderService folderService,
IMailService mailService)
public class NotificationBuilder : INotificationBuilder
{
_underlyingThemeService = underlyingThemeService;
_accountService = accountService;
_folderService = folderService;
_mailService = mailService;
}
private readonly IUnderlyingThemeService _underlyingThemeService;
private readonly IAccountService _accountService;
private readonly IFolderService _folderService;
private readonly IMailService _mailService;
public async Task CreateNotificationsAsync(Guid inboxFolderId, IEnumerable<IMailItem> downloadedMailItems)
{
var mailCount = downloadedMailItems.Count();
try
public NotificationBuilder(IUnderlyingThemeService underlyingThemeService,
IAccountService accountService,
IFolderService folderService,
IMailService mailService)
{
// If there are more than 3 mails, just display 1 general toast.
if (mailCount > 3)
_underlyingThemeService = underlyingThemeService;
_accountService = accountService;
_folderService = folderService;
_mailService = mailService;
}
public async Task CreateNotificationsAsync(Guid inboxFolderId, IEnumerable<IMailItem> downloadedMailItems)
{
var mailCount = downloadedMailItems.Count();
try
{
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()
// If there are more than 3 mails, just display 1 general toast.
if (mailCount > 3)
{
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);
var host = ThumbnailService.GetHost(mailItem.FromAddress);
builder.AddText(Translator.Notifications_MultipleNotificationsTitle);
builder.AddText(string.Format(Translator.Notifications_MultipleNotificationsMessage, mailCount));
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()
{
@@ -124,107 +58,174 @@ public class NotificationBuilder : INotificationBuilder
builder.Show();
}
else
{
var validItems = new List<IMailItem>();
await UpdateTaskbarIconBadgeAsync();
// 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.");
}
}
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)
catch (Exception ex)
{
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;
Log.Error(ex, "Failed to create notifications.");
}
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>()
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()
{
new MailCopy()
int totalUnreadCount = 0;
try
{
Subject = "test subject",
PreviewText = "preview html",
CreationDate = DateTime.UtcNow,
FromAddress = "bkaankose@outlook.com",
Id = "AAkALgAAAAAAHYQDEapmEc2byACqAC-EWg0AnMdP0zg8wkS_Ib2Eeh80LAAGq91I3QAA",
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.");
}
}
//var builder = new ToastContentBuilder();
//builder.SetToastScenario(ToastScenario.Default);
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",
}
});
//builder.AddText(title);
//builder.AddText(message);
//var builder = new ToastContentBuilder();
//builder.SetToastScenario(ToastScenario.Default);
//builder.Show();
//builder.AddText(title);
//builder.AddText(message);
//await Task.CompletedTask;
//builder.Show();
//await Task.CompletedTask;
}
}
}

View File

@@ -11,299 +11,300 @@ using Wino.Core.Domain.Models.Reader;
using Wino.Core.Domain.Translations;
using Wino.Services;
namespace Wino.Core.UWP.Services;
public class PreferencesService : ObservableObject, IPreferencesService
namespace Wino.Core.UWP.Services
{
private readonly IConfigurationService _configurationService;
public event EventHandler<string> PreferenceChanged;
public PreferencesService(IConfigurationService configurationService)
public class PreferencesService : ObservableObject, IPreferencesService
{
_configurationService = configurationService;
}
private readonly IConfigurationService _configurationService;
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
public event EventHandler<string> PreferenceChanged;
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()
public PreferencesService(IConfigurationService 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;
_configurationService = configurationService;
}
// Collect days from startDay to endDay
while (currentDay <= endDayInt)
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
daysOfWeek.Add((DayOfWeek)(currentDay % 7));
currentDay++;
base.OnPropertyChanged(e);
PreferenceChanged?.Invoke(this, e.PropertyName);
}
return daysOfWeek;
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;
}
}
}

View File

@@ -12,253 +12,254 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Printing;
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
namespace Wino.Core.UWP.Services
{
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)
/// <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
{
if (_taskCompletionSource != null)
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)
{
_taskCompletionSource.TrySetResult(PrintingResult.Abandoned);
_taskCompletionSource = new TaskCompletionSource<PrintingResult>();
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;
}
}
// 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
private void DisposePDFDocument()
{
await PrintManager.ShowPrintUIAsync();
var result = await _taskCompletionSource.Task;
return result;
if (pdfDocument != null)
{
pdfDocument = null;
}
}
finally
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)
{
// Dispose everything.
UnregisterPrintManager(printManager);
ClearBitmaps();
UnregisterTask();
DisposePDFDocument();
_taskCompletionSource = null;
}
}
bitmaps ??= new List<CanvasBitmap>();
private void DisposePDFDocument()
{
if (pdfDocument != null)
{
pdfDocument = null;
}
}
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)
for (int i = 0; i < pdfDocument.PageCount; 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;
var page = pdfDocument.GetPage((uint)i);
var stream = new InMemoryRandomAccessStream();
await page.RenderToStreamAsync(stream);
var bitmap = await CanvasBitmap.LoadAsync(sender, stream);
bitmaps.Add(bitmap);
}
pageSize = newPageSize;
sender.InvalidatePreview();
largestBitmap = Vector2.Zero;
// 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)
foreach (var bitmap in bitmaps)
{
indexOnCurrentPage = (int)(args.CurrentPreviewPageNumber - 1) * bitmapsPerPage;
largestBitmap.X = Math.Max(largestBitmap.X, (float)bitmap.Size.Width);
largestBitmap.Y = Math.Max(largestBitmap.Y, (float)bitmap.Size.Height);
}
// 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);
}
largestBitmap = Vector2.Zero;
foreach (var bitmap in bitmaps)
private void OnDocumentPreview(CanvasPrintDocument sender, CanvasPreviewEventArgs args)
{
largestBitmap.X = Math.Max(largestBitmap.X, (float)bitmap.Size.Width);
largestBitmap.Y = Math.Max(largestBitmap.Y, (float)bitmap.Size.Height);
var ds = args.DrawingSession;
var pageNumber = args.PageNumber;
DrawPdfPage(sender, ds, pageNumber);
}
}
private void OnDocumentPreview(CanvasPrintDocument sender, CanvasPreviewEventArgs args)
{
var ds = args.DrawingSession;
var pageNumber = args.PageNumber;
DrawPdfPage(sender, ds, pageNumber);
}
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)
private void OnDocumentPrint(CanvasPrintDocument sender, CanvasPrintEventArgs args)
{
using var ds = args.CreateDrawingSession();
var imageableRect = args.PrintTaskOptions.GetPageDescription(i).ImageableRect;
var detailedOptions = PrintTaskOptionDetails.GetFromPrintTaskOptions(args.PrintTaskOptions);
DrawPdfPage(sender, ds, i);
}
}
int pageCountToPrint = (int)pdfDocument.PageCount;
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 (uint i = 1; i <= pageCountToPrint; ++i)
{
var cellTopLeft = topLeft + cellAcross * column + cellDown * row;
var bitmapInfo = bitmaps[bitmapIndex % bitmaps.Count];
var bitmapPos = cellTopLeft + (cellSize - bitmapInfo.Size.ToVector2()) / 2;
using var ds = args.CreateDrawingSession();
var imageableRect = args.PrintTaskOptions.GetPageDescription(i).ImageableRect;
ds.DrawImage(bitmapInfo, bitmapPos);
DrawPdfPage(sender, ds, i);
}
}
bitmapIndex++;
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)
{
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);
bitmapIndex++;
}
}
}
}

View File

@@ -6,50 +6,51 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.UWP.Extensions;
namespace Wino.Core.UWP.Services;
public class StartupBehaviorService : IStartupBehaviorService
namespace Wino.Core.UWP.Services
{
private const string WinoServerTaskId = "WinoServer";
public async Task<StartupBehaviorResult> ToggleStartupBehavior(bool isEnabled)
public class StartupBehaviorService : IStartupBehaviorService
{
try
{
var task = await StartupTask.GetAsync(WinoServerTaskId);
private const string WinoServerTaskId = "WinoServer";
if (isEnabled)
public async Task<StartupBehaviorResult> ToggleStartupBehavior(bool isEnabled)
{
try
{
await task.RequestEnableAsync();
var task = await StartupTask.GetAsync(WinoServerTaskId);
if (isEnabled)
{
await task.RequestEnableAsync();
}
else
{
task.Disable();
}
}
else
catch (Exception)
{
task.Disable();
Log.Error("Error toggling startup behavior");
}
}
catch (Exception)
{
Log.Error("Error toggling startup behavior");
return await GetCurrentStartupBehaviorAsync();
}
return await GetCurrentStartupBehaviorAsync();
}
public async Task<StartupBehaviorResult> GetCurrentStartupBehaviorAsync()
{
try
public async Task<StartupBehaviorResult> GetCurrentStartupBehaviorAsync()
{
var task = await StartupTask.GetAsync(WinoServerTaskId);
try
{
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,140 +4,141 @@ using CommunityToolkit.Mvvm.ComponentModel;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
namespace Wino.Services;
public class StatePersistenceService : ObservableObject, IStatePersistanceService
namespace Wino.Services
{
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 class StatePersistenceService : ObservableObject, IStatePersistanceService
{
_configurationService = configurationService;
public event EventHandler<string> StatePropertyChanged;
_openPaneLength = _configurationService.Get(OpenPaneLengthKey, 320d);
_mailListPaneLength = _configurationService.Get(MailListPaneLengthKey, 420d);
_calendarDisplayType = _configurationService.Get(nameof(CalendarDisplayType), CalendarDisplayType.Week);
_dayDisplayCount = _configurationService.Get(nameof(DayDisplayCount), 1);
private const string OpenPaneLengthKey = nameof(OpenPaneLengthKey);
private const string MailListPaneLengthKey = nameof(MailListPaneLengthKey);
PropertyChanged += ServicePropertyChanged;
}
private readonly 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
public StatePersistenceService(IConfigurationService configurationService)
{
if (SetProperty(ref isReadingMail, value))
_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
{
OnPropertyChanged(nameof(IsBackButtonVisible));
if (SetProperty(ref isReadingMail, value))
{
OnPropertyChanged(nameof(IsBackButtonVisible));
}
}
}
}
private bool shouldShiftMailRenderingDesign;
private bool shouldShiftMailRenderingDesign;
public bool ShouldShiftMailRenderingDesign
{
get { return shouldShiftMailRenderingDesign; }
set { shouldShiftMailRenderingDesign = value; }
}
private bool isReaderNarrowed;
public bool IsReaderNarrowed
{
get => isReaderNarrowed;
set
public bool ShouldShiftMailRenderingDesign
{
if (SetProperty(ref isReaderNarrowed, value))
get { return shouldShiftMailRenderingDesign; }
set { shouldShiftMailRenderingDesign = value; }
}
private bool isReaderNarrowed;
public bool IsReaderNarrowed
{
get => isReaderNarrowed;
set
{
OnPropertyChanged(nameof(IsBackButtonVisible));
if (SetProperty(ref isReaderNarrowed, value))
{
OnPropertyChanged(nameof(IsBackButtonVisible));
}
}
}
}
private string coreWindowTitle;
private string coreWindowTitle;
public string CoreWindowTitle
{
get => coreWindowTitle;
set
public string CoreWindowTitle
{
if (SetProperty(ref coreWindowTitle, value))
get => coreWindowTitle;
set
{
UpdateAppCoreWindowTitle();
if (SetProperty(ref coreWindowTitle, value))
{
UpdateAppCoreWindowTitle();
}
}
}
}
private double _openPaneLength;
public double OpenPaneLength
{
get => _openPaneLength;
set
private double _openPaneLength;
public double OpenPaneLength
{
if (SetProperty(ref _openPaneLength, value))
get => _openPaneLength;
set
{
_configurationService.Set(OpenPaneLengthKey, value);
if (SetProperty(ref _openPaneLength, value))
{
_configurationService.Set(OpenPaneLengthKey, value);
}
}
}
}
private double _mailListPaneLength;
public double MailListPaneLength
{
get => _mailListPaneLength;
set
private double _mailListPaneLength;
public double MailListPaneLength
{
if (SetProperty(ref _mailListPaneLength, value))
get => _mailListPaneLength;
set
{
_configurationService.Set(MailListPaneLengthKey, value);
if (SetProperty(ref _mailListPaneLength, value))
{
_configurationService.Set(MailListPaneLengthKey, value);
}
}
}
}
private CalendarDisplayType _calendarDisplayType;
public CalendarDisplayType CalendarDisplayType
{
get => _calendarDisplayType;
set
private CalendarDisplayType _calendarDisplayType;
public CalendarDisplayType CalendarDisplayType
{
if (SetProperty(ref _calendarDisplayType, value))
get => _calendarDisplayType;
set
{
_configurationService.Set(nameof(CalendarDisplayType), value);
if (SetProperty(ref _calendarDisplayType, value))
{
_configurationService.Set(nameof(CalendarDisplayType), value);
}
}
}
}
private int _dayDisplayCount;
public int DayDisplayCount
{
get => _dayDisplayCount;
set
private int _dayDisplayCount;
public int DayDisplayCount
{
if (SetProperty(ref _dayDisplayCount, value))
get => _dayDisplayCount;
set
{
_configurationService.Set(nameof(DayDisplayCount), value);
if (SetProperty(ref _dayDisplayCount, value))
{
_configurationService.Set(nameof(DayDisplayCount), value);
}
}
}
}
private void UpdateAppCoreWindowTitle()
{
var appView = Windows.UI.ViewManagement.ApplicationView.GetForCurrentView();
private void UpdateAppCoreWindowTitle()
{
var appView = Windows.UI.ViewManagement.ApplicationView.GetForCurrentView();
if (appView != null)
appView.Title = CoreWindowTitle;
if (appView != null)
appView.Title = CoreWindowTitle;
}
}
}

View File

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

View File

@@ -7,126 +7,127 @@ using Windows.System;
using Wino.Core.Domain;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.UWP.Services;
public class StoreRatingService : IStoreRatingService
namespace Wino.Core.UWP.Services
{
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)
public class StoreRatingService : IStoreRatingService
{
_configurationService = configurationService;
_dialogService = dialogService;
}
private const string RatedStorageKey = nameof(RatedStorageKey);
private const string LatestAskedKey = nameof(LatestAskedKey);
private bool IsAskingThresholdExceeded()
{
var latestAskedDate = _configurationService.Get(LatestAskedKey, DateTime.MinValue);
private readonly IConfigurationService _configurationService;
private readonly IMailDialogService _dialogService;
// Never asked before.
// Set the threshold and wait for the next trigger.
if (latestAskedDate == DateTime.MinValue)
public StoreRatingService(IConfigurationService configurationService, IMailDialogService dialogService)
{
_configurationService.Set(LatestAskedKey, DateTime.UtcNow);
}
else if (DateTime.UtcNow >= latestAskedDate.AddMinutes(30))
{
return true;
_configurationService = configurationService;
_dialogService = dialogService;
}
return false;
}
public async Task PromptRatingDialogAsync()
{
// Annoying.
if (Debugger.IsAttached) return;
// Swallow all exceptions. App should not crash in any errors.
try
private bool IsAskingThresholdExceeded()
{
bool isRated = _configurationService.GetRoaming(RatedStorageKey, false);
var latestAskedDate = _configurationService.Get(LatestAskedKey, DateTime.MinValue);
if (isRated) return;
// Never asked before.
// Set the threshold and wait for the next trigger.
if (!isRated)
if (latestAskedDate == DateTime.MinValue)
{
if (!IsAskingThresholdExceeded()) return;
_configurationService.Set(LatestAskedKey, DateTime.UtcNow);
}
else if (DateTime.UtcNow >= latestAskedDate.AddMinutes(30))
{
return true;
}
var isRateWinoApproved = await _dialogService.ShowWinoCustomMessageDialogAsync(Translator.StoreRatingDialog_Title,
Translator.StoreRatingDialog_MessageFirstLine,
Translator.Buttons_RateWino,
Domain.Enums.WinoCustomMessageDialogIcon.Question,
Translator.Buttons_No,
RatedStorageKey);
return false;
}
if (isRateWinoApproved)
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)
{
// In case of failure of this call, we will navigate users to Store page directly.
if (!IsAskingThresholdExceeded()) return;
try
var isRateWinoApproved = await _dialogService.ShowWinoCustomMessageDialogAsync(Translator.StoreRatingDialog_Title,
Translator.StoreRatingDialog_MessageFirstLine,
Translator.Buttons_RateWino,
Domain.Enums.WinoCustomMessageDialogIcon.Question,
Translator.Buttons_No,
RatedStorageKey);
if (isRateWinoApproved)
{
await ShowPortableRatingDialogAsync();
}
catch (Exception)
{
await Launcher.LaunchUriAsync(new Uri($"ms-windows-store://review/?ProductId=9NCRCVJC50WL"));
// 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"));
}
}
}
}
}
catch (Exception) { }
finally
{
_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 () =>
catch (Exception) { }
finally
{
// TODO: Get it from package info.
await Launcher.LaunchUriAsync(new Uri($"ms-windows-store://review/?ProductId=9NCRCVJC50WL"));
});
_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) { }
}
catch (Exception) { }
}
}

View File

@@ -26,436 +26,437 @@ using Wino.Core.UWP.Models.Personalization;
using Wino.Core.UWP.Services;
using Wino.Messaging.Client.Shell;
namespace Wino.Services;
/// <summary>
/// Class providing functionality around switching and restoring theme settings
/// </summary>
public class ThemeService : IThemeService
namespace Wino.Services
{
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>()
{
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),
};
public ThemeService(IConfigurationService configurationService,
IUnderlyingThemeService underlyingThemeService,
IApplicationResourceManager<ResourceDictionary> applicationResourceManager)
{
_configurationService = configurationService;
_underlyingThemeService = underlyingThemeService;
_applicationResourceManager = applicationResourceManager;
}
/// <summary>
/// Gets or sets (with LocalSettings persistence) the RequestedTheme of the root element.
/// Class providing functionality around switching and restoring theme settings
/// </summary>
public ApplicationElementTheme RootTheme
public class ThemeService : IThemeService
{
get
{
if (mainApplicationFrame == null) return ApplicationElementTheme.Default;
public const string CustomThemeFolderName = "CustomThemes";
return mainApplicationFrame.RequestedTheme.ToWinoElementTheme();
}
set
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>()
{
if (mainApplicationFrame == null)
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),
};
public ThemeService(IConfigurationService configurationService,
IUnderlyingThemeService underlyingThemeService,
IApplicationResourceManager<ResourceDictionary> applicationResourceManager)
{
_configurationService = configurationService;
_underlyingThemeService = underlyingThemeService;
_applicationResourceManager = applicationResourceManager;
}
/// <summary>
/// Gets or sets (with LocalSettings persistence) the RequestedTheme of the root element.
/// </summary>
public ApplicationElementTheme RootTheme
{
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)
return;
mainApplicationFrame.RequestedTheme = value.ToWindowsElementTheme();
// Save reference as this might be null when the user is in another app
_configurationService.Set(UnderlyingThemeService.SelectedAppThemeKey, value);
mainApplicationFrame = Window.Current.Content as Frame;
UpdateSystemCaptionButtonColors();
if (mainApplicationFrame == null) return;
// PopupRoot usually needs to react to changes.
NotifyThemeUpdate();
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 Guid currentApplicationThemeId;
public Guid CurrentApplicationThemeId
{
get { return currentApplicationThemeId; }
set
private void NotifyThemeUpdate()
{
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
if (mainApplicationFrame == null || mainApplicationFrame.Dispatcher == null) return;
_ = mainApplicationFrame.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
{
UpdateSystemCaptionButtonColors();
var accentColor = sender.GetColorValue(UIColorType.Accent);
//AccentColorChangedBySystem?.Invoke(this, accentColor.ToHex());
ElementThemeChanged?.Invoke(this, RootTheme);
WeakReferenceMessenger.Default.Send(new ApplicationThemeChanged(_underlyingThemeService.IsUnderlyingThemeDark()));
});
}
NotifyThemeUpdate();
}
public void UpdateSystemCaptionButtonColors()
{
if (mainApplicationFrame == null) return;
_ = mainApplicationFrame.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
private void UISettingsColorChanged(UISettings sender, object args)
{
ApplicationViewTitleBar titleBar = ApplicationView.GetForCurrentView().TitleBar;
if (titleBar == null) return;
if (_underlyingThemeService.IsUnderlyingThemeDark())
// Make sure we have a reference to our window so we dispatch a UI change
if (mainApplicationFrame != null)
{
titleBar.ButtonForegroundColor = Colors.White;
// 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;
}
else
{
titleBar.ButtonForegroundColor = Colors.Black;
var isUnderlyingDark = _underlyingThemeService.IsUnderlyingThemeDark();
mainApplicationFrame.RequestedTheme = isUnderlyingDark ? ElementTheme.Light : ElementTheme.Dark;
mainApplicationFrame.RequestedTheme = ElementTheme.Default;
}
});
}
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)
public async Task ApplyCustomThemeAsync(bool isInitializing)
{
mainApplicationFrame.RequestedTheme = ElementTheme.Light;
mainApplicationFrame.RequestedTheme = ElementTheme.Dark;
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();
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}");
}
}
else if (mainApplicationFrame.RequestedTheme == ElementTheme.Light)
public async Task<List<AppThemeBase>> GetAvailableThemesAsync()
{
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 availableThemes = new List<AppThemeBase>(preDefinedThemes);
var customThemes = await GetCurrentCustomThemesAsync();
controlThemeList.AddRange(customThemes.Select(a => new CustomAppTheme(a)));
availableThemes.AddRange(customThemes.Select(a => new CustomAppTheme(a)));
applyingTheme = controlThemeList.Find(a => a.Id == currentApplicationThemeId) ?? preDefinedThemes.First(a => a.Id == Guid.Parse(_micaThemeId));
return availableThemes;
}
try
public async Task<CustomThemeMetadata> CreateNewCustomThemeAsync(string themeName, string accentColor, byte[] wallpaperData)
{
var existingThemeDictionary = _applicationResourceManager.GetLastResource();
if (wallpaperData == null || wallpaperData.Length == 0)
throw new CustomThemeCreationFailedException(Translator.Exception_CustomThemeMissingWallpaper);
if (existingThemeDictionary != null && existingThemeDictionary.TryGetValue("ThemeName", out object themeNameString))
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()
{
var themeName = themeNameString.ToString();
Id = Guid.NewGuid(),
Name = themeName,
AccentColorHex = accentColor
};
// Applying different theme.
if (themeName != applyingTheme.ThemeName)
{
var resourceDictionaryContent = await applyingTheme.GetThemeResourceDictionaryContentAsync();
// Save wallpaper.
// Filename would be the same as metadata id, in jpg format.
var resourceDictionary = XamlReader.Load(resourceDictionaryContent) as ResourceDictionary;
var themeFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(CustomThemeFolderName, CreationCollisionOption.OpenIfExists);
// Custom themes require special attention for background image because
// they share the same base theme resource dictionary.
var wallpaperFile = await themeFolder.CreateFileAsync($"{newTheme.Id}.jpg", CreationCollisionOption.ReplaceExisting);
await FileIO.WriteBytesAsync(wallpaperFile, wallpaperData);
if (applyingTheme is CustomAppTheme)
{
resourceDictionary["ThemeBackgroundImage"] = $"ms-appdata:///local/{CustomThemeFolderName}/{applyingTheme.Id}.jpg";
}
// Generate thumbnail for settings page.
_applicationResourceManager.RemoveResource(existingThemeDictionary);
_applicationResourceManager.AddResource(resourceDictionary);
var thumbnail = await wallpaperFile.GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode.PicturesView);
var thumbnailFile = await themeFolder.CreateFileAsync($"{newTheme.Id}_preview.jpg", CreationCollisionOption.ReplaceExisting);
bool isSystemTheme = applyingTheme is SystemAppTheme || applyingTheme is CustomAppTheme;
using (var readerStream = thumbnail.AsStreamForRead())
{
byte[] bytes = new byte[readerStream.Length];
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 :)
await readerStream.ReadExactlyAsync(bytes);
var savedElement = _configurationService.Get(UnderlyingThemeService.SelectedAppThemeKey, ApplicationElementTheme.Default);
RootTheme = savedElement;
var buffer = bytes.AsBuffer();
// 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();
await FileIO.WriteBufferAsync(thumbnailFile, buffer);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Apply theme failed -> {ex.Message}");
}
}
public async Task<List<AppThemeBase>> GetAvailableThemesAsync()
{
var availableThemes = new List<AppThemeBase>(preDefinedThemes);
// Save metadata.
var metadataFile = await themeFolder.CreateFileAsync($"{newTheme.Id}.json", CreationCollisionOption.ReplaceExisting);
var customThemes = await GetCurrentCustomThemesAsync();
var serialized = JsonSerializer.Serialize(newTheme, DomainModelsJsonContext.Default.CustomThemeMetadata);
await FileIO.WriteTextAsync(metadataFile, serialized);
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);
return newTheme;
}
// 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)
public async Task<List<CustomThemeMetadata>> GetCurrentCustomThemesAsync()
{
var metadata = await GetCustomMetadataAsync(theme).ConfigureAwait(false);
var results = new List<CustomThemeMetadata>();
if (metadata == null) continue;
var themeFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(CustomThemeFolderName, CreationCollisionOption.OpenIfExists);
results.Add(metadata);
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;
}
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();
}
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,62 +2,63 @@
using System.Linq;
using System.Net.Mail;
namespace Wino.Core.UWP.Services;
public static class ThumbnailService
namespace Wino.Core.UWP.Services
{
private static string[] knownCompanies = new string[]
public static class ThumbnailService
{
"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('@'))
private static string[] knownCompanies = new string[]
{
var splitted = address.Split('@');
"microsoft.com", "apple.com", "google.com", "steampowered.com", "airbnb.com", "youtube.com", "uber.com"
};
if (splitted.Length >= 2 && !string.IsNullOrEmpty(splitted[1]))
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('@'))
{
try
var splitted = address.Split('@');
if (splitted.Length >= 2 && !string.IsNullOrEmpty(splitted[1]))
{
return new MailAddress(address).Host;
}
catch (Exception)
{
// TODO: Exceptions are ignored for now.
try
{
return new MailAddress(address).Host;
}
catch (Exception)
{
// TODO: Exceptions are ignored for now.
}
}
}
return string.Empty;
}
return string.Empty;
}
public static Tuple<bool, string> CheckIsKnown(string host)
{
// Check known hosts.
// Apply company logo if available.
try
public static Tuple<bool, string> CheckIsKnown(string host)
{
var last = host.Split('.');
// Check known hosts.
// Apply company logo if available.
if (last.Length > 2)
host = $"{last[last.Length - 2]}.{last[last.Length - 1]}";
}
catch (Exception)
{
return new Tuple<bool, string>(false, host);
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);
}
return new Tuple<bool, string>(IsKnown(host), host);
public static string GetKnownHostImage(string host)
=> $"ms-appx:///Assets/Thumbnails/{host}.png";
}
public static string GetKnownHostImage(string host)
=> $"ms-appx:///Assets/Thumbnails/{host}.png";
}

View File

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

View File

@@ -21,343 +21,344 @@ using Wino.Messaging.Enums;
using Wino.Messaging.Server;
using Wino.Messaging.UI;
namespace Wino.Core.UWP.Services;
public class WinoServerConnectionManager :
IWinoServerConnectionManager<AppServiceConnection>,
IRecipient<WinoServerConnectionEstablished>
namespace Wino.Core.UWP.Services
{
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
public class WinoServerConnectionManager :
IWinoServerConnectionManager<AppServiceConnection>,
IRecipient<WinoServerConnectionEstablished>
{
get { return status; }
private set
{
Log.Information("Server connection status changed to {Status}.", value);
status = value;
StatusChanged?.Invoke(this, value);
}
}
private const int ServerConnectionTimeoutMs = 10000;
private AppServiceConnection _connection;
public AppServiceConnection Connection
{
get { return _connection; }
set
public event EventHandler<WinoServerConnectionStatus> StatusChanged;
public TaskCompletionSource<bool> ConnectingHandle { get; private set; }
private ILogger Logger => Logger.ForContext<WinoServerConnectionManager>();
private WinoServerConnectionStatus status;
public WinoServerConnectionStatus Status
{
if (_connection != null)
get { return status; }
private set
{
_connection.RequestReceived -= ServerMessageReceived;
_connection.ServiceClosed -= ServerDisconnected;
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;
}
_connection = value;
if (value == null)
if (Status == WinoServerConnectionStatus.Connecting)
{
Status = WinoServerConnectionStatus.Disconnected;
// 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
{
value.RequestReceived += ServerMessageReceived;
value.ServiceClosed += ServerDisconnected;
Status = WinoServerConnectionStatus.Connected;
Log.Information("FullTrustAppContract is not present in the system. Server connection is not possible.");
}
}
}
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;
return false;
}
if (Status == WinoServerConnectionStatus.Connecting)
public async Task InitializeAsync()
{
// A connection is already being established at the moment.
// No need to run another connection establishment process.
// Await the connecting handler if possible.
var isConnectionSuccessfull = await ConnectAsync();
if (ConnectingHandle != null)
if (isConnectionSuccessfull)
{
return await ConnectingHandle.Task;
Log.Information("ServerConnectionManager initialized successfully.");
}
else
{
Log.Error("ServerConnectionManager initialization failed.");
}
}
if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
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
{
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.");
serializedMessage = JsonSerializer.Serialize(message, _jsonSerializerOptions);
}
catch (OperationCanceledException canceledException)
catch (Exception serializationException)
{
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;
Logger.Error(serializationException, $"Failed to serialize client message for sending.");
return WinoServerResponse<TResponse>.CreateErrorResponse($"Failed to serialize message.\n{serializationException.Message}");
}
return true;
}
else
{
Log.Information("FullTrustAppContract is not present in the system. Server connection is not possible.");
}
AppServiceResponse response = null;
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)
try
{
switch (messageType)
var valueSet = new ValueSet
{
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.");
{ MessageConstants.MessageTypeKey, (int)MessageType.ServerMessage },
{ MessageConstants.MessageDataKey, serializedMessage },
{ MessageConstants.MessageDataTypeKey, message.GetType().Name }
};
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)
// Add additional parameters into ValueSet
if (parameters != null)
{
valueSet.Add(item.Key, item.Value);
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}");
}
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}");
}
}
// 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);
}
public void Receive(WinoServerConnectionEstablished message)
=> ConnectingHandle?.TrySetResult(true);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,91 +11,92 @@ using Wino.Mail.ViewModels.Data;
using Wino.Messaging.Client.Navigation;
using Wino.Messaging.UI;
namespace Wino.Views;
public sealed partial class ManageAccountsPage : ManageAccountsPageAbstract,
IRecipient<BackBreadcrumNavigationRequested>,
IRecipient<BreadcrumbNavigationRequested>,
IRecipient<MergedInboxRenamed>
namespace Wino.Views
{
public ObservableCollection<BreadcrumbNavigationItemViewModel> PageHistory { get; set; } = new ObservableCollection<BreadcrumbNavigationItemViewModel>();
public ManageAccountsPage()
public sealed partial class ManageAccountsPage : ManageAccountsPageAbstract,
IRecipient<BackBreadcrumNavigationRequested>,
IRecipient<BreadcrumbNavigationRequested>,
IRecipient<MergedInboxRenamed>
{
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 ObservableCollection<BreadcrumbNavigationItemViewModel> PageHistory { get; set; } = new ObservableCollection<BreadcrumbNavigationItemViewModel>();
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)
public ManageAccountsPage()
{
PageHistory.RemoveAt(PageHistory.Count - 1);
AccountPagesFrame.GoBack(new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromRight });
InitializeComponent();
}
}
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)
protected override void OnNavigatedTo(NavigationEventArgs e)
{
AccountPagesFrame.GoBack(new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromRight });
PageHistory.RemoveAt(PageHistory.Count - 1);
PageHistory[PageHistory.Count - 1].IsActive = true;
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 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();
void IRecipient<BreadcrumbNavigationRequested>.Receive(BreadcrumbNavigationRequested message)
{
var pageType = ViewModel.NavigationService.GetPageType(message.PageType);
if (activePage == null) return;
if (pageType == null) return;
activePage.Title = message.Account.Name;
}
AccountPagesFrame.Navigate(pageType, message.Parameter, new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromRight });
public void Receive(MergedInboxRenamed message)
{
// TODO: Find better way to retrieve page history from the stack for the merged account.
var activePage = PageHistory.LastOrDefault();
PageHistory.ForEach(a => a.IsActive = false);
if (activePage == null) return;
PageHistory.Add(new BreadcrumbNavigationItemViewModel(message, true));
}
activePage.Title = message.NewName;
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;
}
}
}

View File

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

View File

@@ -11,76 +11,77 @@ using Wino.Messaging.Client.Navigation;
using Wino.Views.Abstract;
using Wino.Views.Settings;
namespace Wino.Views;
public sealed partial class SettingsPage : SettingsPageAbstract, IRecipient<BreadcrumbNavigationRequested>
namespace Wino.Views
{
public ObservableCollection<BreadcrumbNavigationItemViewModel> PageHistory { get; set; } = [];
public SettingsPage()
public sealed partial class SettingsPage : SettingsPageAbstract, IRecipient<BreadcrumbNavigationRequested>
{
InitializeComponent();
}
public ObservableCollection<BreadcrumbNavigationItemViewModel> PageHistory { get; set; } = [];
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)
public SettingsPage()
{
switch (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)
{
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;
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;
}
}
}
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

@@ -3,9 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;
using Microsoft.Extensions.DependencyInjection;
using Nito.AsyncEx;
using Serilog;
@@ -25,242 +22,228 @@ using Wino.Core.Domain;
using Wino.Core.Domain.Interfaces;
using Wino.Services;
namespace Wino.Core.UWP;
public abstract class WinoApplication : Application
namespace Wino.Core.UWP
{
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; }
// Order matters.
private List<IInitializeAsync> initializeServices => new List<IInitializeAsync>()
public abstract class WinoApplication : Application
{
DatabaseService,
TranslationService,
ThemeService,
};
public new static WinoApplication Current => (WinoApplication)Application.Current;
public const string WinoLaunchLogPrefix = "[Wino Launch] ";
public abstract string AppCenterKey { get; }
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()
{
ConfigureAppCenter();
ConfigurePrelaunch();
Services = ConfigureServices();
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();
}
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))
protected WinoApplication()
{
if (Window.Current.Content == null)
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))
{
var mainFrame = new Frame();
if (Window.Current.Content == null)
{
var mainFrame = new Frame();
Window.Current.Content = mainFrame;
Window.Current.Content = mainFrame;
await ThemeService.InitializeAsync();
await ThemeService.InitializeAsync();
}
}
await HandleActivationAsync(args);
if (IsInteractiveLaunchArgs(args))
{
Window.Current.Activate();
LogActivation("Window activated");
}
}
await HandleActivationAsync(args);
if (IsInteractiveLaunchArgs(args))
public async Task HandleActivationAsync(object activationArgs)
{
Window.Current.Activate();
LogActivation("Window activated");
}
}
public async Task HandleActivationAsync(object activationArgs)
{
if (GetActivationHandlers() != null)
{
var activationHandler = GetActivationHandlers().FirstOrDefault(h => h.CanHandle(activationArgs)) ?? null;
if (activationHandler != null)
if (GetActivationHandlers() != null)
{
await activationHandler.HandleAsync(activationArgs);
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);
}
}
}
if (IsInteractiveLaunchArgs(activationArgs))
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
var defaultHandler = GetDefaultActivationHandler();
LogActivation($"OnLaunched -> {args.GetType().Name}, Kind -> {args.Kind}, PreviousExecutionState -> {args.PreviousExecutionState}, IsPrelaunch -> {args.PrelaunchActivated}");
if (defaultHandler.CanHandle(activationArgs))
if (!args.PrelaunchActivated)
{
await defaultHandler.HandleAsync(activationArgs);
await ActivateWinoAsync(args);
}
}
}
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
LogActivation($"OnLaunched -> {args.GetType().Name}, Kind -> {args.Kind}, PreviousExecutionState -> {args.PreviousExecutionState}, IsPrelaunch -> {args.PrelaunchActivated}");
if (!args.PrelaunchActivated)
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 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
protected override async void OnActivated(IActivatedEventArgs args)
{
var systemNavigationManagerPreview = SystemNavigationManagerPreview.GetForCurrentView();
base.OnActivated(args);
systemNavigationManagerPreview.CloseRequested -= OnApplicationCloseRequested;
systemNavigationManagerPreview.CloseRequested += OnApplicationCloseRequested;
Log.Information($"OnActivated -> {args.GetType().Name}, Kind -> {args.Kind}, Prev Execution State -> {args.PreviousExecutionState}");
await ActivateWinoAsync(args);
}
catch { }
}
private void ConfigurePrelaunch()
{
if (ApiInformation.IsMethodPresent("Windows.ApplicationModel.Core.CoreApplication", "EnablePrelaunch"))
CoreApplication.EnablePrelaunch(true);
}
private void OnAppUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e)
{
var parameters = new Dictionary<string, string>()
private void TryRegisterAppCloseChange()
{
{ "BaseMessage", e.Exception.GetBaseException().Message },
{ "BaseStackTrace", e.Exception.GetBaseException().StackTrace },
{ "StackTrace", e.Exception.StackTrace },
{ "Message", e.Exception.Message },
};
try
{
var systemNavigationManagerPreview = SystemNavigationManagerPreview.GetForCurrentView();
Log.Error(e.Exception, "[Wino Crash]");
Crashes.TrackError(e.Exception, parameters);
Analytics.TrackEvent("Wino Crashed", parameters);
}
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();
systemNavigationManagerPreview.CloseRequested -= OnApplicationCloseRequested;
systemNavigationManagerPreview.CloseRequested += OnApplicationCloseRequested;
}
catch { }
}
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 ConfigureAppCenter()
=> AppCenter.Start(AppCenterKey, typeof(Analytics), typeof(Crashes));
private void ConfigurePrelaunch()
{
if (ApiInformation.IsMethodPresent("Windows.ApplicationModel.Core.CoreApplication", "EnablePrelaunch"))
CoreApplication.EnablePrelaunch(true);
}
public void ConfigureLogging()
{
string logFilePath = Path.Combine(ApplicationData.Current.LocalFolder.Path, Constants.ClientLogFile);
LogInitializer.SetupLogger(logFilePath);
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);
}
}
}