Merge pull request #329 from bkaankose/feature/NewStartup

Handling app termination and reworked dialog messages.
This commit is contained in:
Burak Kaan Köse
2024-08-22 18:41:38 +02:00
committed by GitHub
41 changed files with 717 additions and 516 deletions

View File

@@ -0,0 +1,10 @@
namespace Wino.Core.Domain.Enums
{
public enum WinoCustomMessageDialogIcon
{
Information,
Warning,
Error,
Question
}
}

View File

@@ -1,9 +0,0 @@
using System.Threading.Tasks;
namespace Wino.Core.Domain.Interfaces
{
public interface IConfirmationDialog
{
Task<bool> ShowDialogAsync(string title, string message, string approveButtonTitle);
}
}

View File

@@ -15,11 +15,10 @@ namespace Wino.Core.Domain.Interfaces
Task<byte[]> PickWindowsFileContentAsync(params object[] typeFilters);
Task<bool> ShowConfirmationDialogAsync(string question, string title, string confirmationButtonTitle);
Task<bool> ShowHardDeleteConfirmationAsync();
Task<IStoreRatingDialog> ShowRatingDialogAsync();
Task HandleSystemFolderConfigurationDialogAsync(Guid accountId, IFolderService folderService);
Task<bool> ShowCustomThemeBuilderDialogAsync();
Task ShowMessageAsync(string message, string title);
Task ShowMessageAsync(string message, string title, WinoCustomMessageDialogIcon icon);
void InfoBarMessage(string title, string message, InfoBarMessageType messageType);
void InfoBarMessage(string title, string message, InfoBarMessageType messageType, string actionButtonText, Action action);
@@ -53,6 +52,17 @@ namespace Wino.Core.Domain.Interfaces
/// </summary>
/// <returns>Signature information. Null if canceled.</returns>
Task<AccountSignature> ShowSignatureEditorDialog(AccountSignature signatureModel = null);
/// <summary>
/// Presents a dialog to the user for account alias creation/modification.
/// </summary>
/// <returns>Created alias model if not canceled.</returns>
Task<ICreateAccountAliasDialog> ShowCreateAccountAliasDialogAsync();
Task<bool> ShowWinoCustomMessageDialogAsync(string title,
string description,
string approveButtonText,
WinoCustomMessageDialogIcon? icon,
string cancelButtonText = "",
string dontAskAgainConfigurationKey = "");
}
}

View File

@@ -1,8 +0,0 @@
namespace Wino.Core.Domain.Interfaces
{
public interface IStoreRatingDialog
{
bool DontAskAgain { get; }
bool RateWinoClicked { get; }
}
}

View File

@@ -11,6 +11,13 @@
"AccountSettingsDialog_AccountNamePlaceholder": "eg. John Doe",
"AddHyperlink": "Add",
"AutoDiscoveryProgressMessage": "Searching for mail settings...",
"AppCloseBackgroundSynchronizationWarningTitle": "Background Synchronization",
"AppCloseTerminateBehaviorWarningMessageFirstLine": "You are terminating Wino Mail and your app close behavior is set to 'Terminate'.",
"AppCloseTerminateBehaviorWarningMessageSecondLine": "This will stop all background synchronizations and notifications.",
"AppCloseTerminateBehaviorWarningMessageThirdLine": "Do you want to go to App Preferences to set Wino Mail to run minimized or in the background?",
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Application has not been set to launch on Windows startup.",
"AppCloseStartupLaunchDisabledWarningMessageSecondLine": "This will cause you to miss notifications when you restart your computer.",
"AppCloseStartupLaunchDisabledWarningMessageThirdLine": "Do you want to go to App Preferences page to enable it?",
"BasicIMAPSetupDialog_AdvancedConfiguration": "Advanced Configuration",
"BasicIMAPSetupDialog_CredentialLocalMessage": "Your credentials will only be stored locally on your computer.",
"BasicIMAPSetupDialog_Description": "Some accounts require additional steps to sign in",
@@ -105,6 +112,9 @@
"DialogMessage_UnsubscribeConfirmationGoToWebsiteMessage": "To stop getting messages from {0}, go to their website to unsubscribe.",
"DialogMessage_UnsubscribeConfirmationGoToWebsiteConfirmButton": "Go to website",
"DialogMessage_UnsubscribeConfirmationMailtoMessage": "Do you want to stop getting messages from {0}? Wino will unsubscribe for you by sending an email from your email account to {1}.",
"DialogMessage_EnableStartupLaunchTitle": "Enable Startup Launch",
"DialogMessage_EnableStartupLaunchMessage": "Let Wino Mail automatically launch minimized on Windows startup to not miss any notifications.\n\nDo you want to enable startup launch?",
"DialogMessage_EnableStartupLaunchDeniedMessage": "You can enable startup launch from Settings -> App Preferences.",
"Dialog_DontAskAgain": "Don't ask again",
"CreateAccountAliasDialog_Title": "Create Account Alias",
"CreateAccountAliasDialog_Description": "Make sure your outgoing server allows sending mails from this alias.",

View File

@@ -78,6 +78,41 @@ namespace Wino.Core.Domain
/// </summary>
public static string AutoDiscoveryProgressMessage => Resources.GetTranslatedString(@"AutoDiscoveryProgressMessage");
/// <summary>
/// Background Synchronization
/// </summary>
public static string AppCloseBackgroundSynchronizationWarningTitle => Resources.GetTranslatedString(@"AppCloseBackgroundSynchronizationWarningTitle");
/// <summary>
/// You are terminating Wino Mail and your app close behavior is set to 'Terminate'.
/// </summary>
public static string AppCloseTerminateBehaviorWarningMessageFirstLine => Resources.GetTranslatedString(@"AppCloseTerminateBehaviorWarningMessageFirstLine");
/// <summary>
/// This will stop all background synchronizations and notifications.
/// </summary>
public static string AppCloseTerminateBehaviorWarningMessageSecondLine => Resources.GetTranslatedString(@"AppCloseTerminateBehaviorWarningMessageSecondLine");
/// <summary>
/// Do you want to go to App Preferences to set Wino Mail to run minimized or in the background?
/// </summary>
public static string AppCloseTerminateBehaviorWarningMessageThirdLine => Resources.GetTranslatedString(@"AppCloseTerminateBehaviorWarningMessageThirdLine");
/// <summary>
/// Application has not been set to launch on Windows startup.
/// </summary>
public static string AppCloseStartupLaunchDisabledWarningMessageFirstLine => Resources.GetTranslatedString(@"AppCloseStartupLaunchDisabledWarningMessageFirstLine");
/// <summary>
/// This will cause you to miss notifications when you restart your computer.
/// </summary>
public static string AppCloseStartupLaunchDisabledWarningMessageSecondLine => Resources.GetTranslatedString(@"AppCloseStartupLaunchDisabledWarningMessageSecondLine");
/// <summary>
/// Do you want to go to App Preferences page to enable it?
/// </summary>
public static string AppCloseStartupLaunchDisabledWarningMessageThirdLine => Resources.GetTranslatedString(@"AppCloseStartupLaunchDisabledWarningMessageThirdLine");
/// <summary>
/// Advanced Configuration
/// </summary>
@@ -548,6 +583,21 @@ namespace Wino.Core.Domain
/// </summary>
public static string DialogMessage_UnsubscribeConfirmationMailtoMessage => Resources.GetTranslatedString(@"DialogMessage_UnsubscribeConfirmationMailtoMessage");
/// <summary>
/// Enable Startup Launch
/// </summary>
public static string DialogMessage_EnableStartupLaunchTitle => Resources.GetTranslatedString(@"DialogMessage_EnableStartupLaunchTitle");
/// <summary>
/// Let Wino Mail automatically launch minimized on Windows startup to not miss any notifications. Do you want to enable startup launch?
/// </summary>
public static string DialogMessage_EnableStartupLaunchMessage => Resources.GetTranslatedString(@"DialogMessage_EnableStartupLaunchMessage");
/// <summary>
/// You can enable startup launch from Settings -> App Preferences.
/// </summary>
public static string DialogMessage_EnableStartupLaunchDeniedMessage => Resources.GetTranslatedString(@"DialogMessage_EnableStartupLaunchDeniedMessage");
/// <summary>
/// Don't ask again
/// </summary>

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.WinUI.Notifications;
using Serilog;
using Windows.Data.Xml.Dom;
using Windows.UI.Notifications;
using Wino.Core.Domain;
@@ -38,83 +39,90 @@ namespace Wino.Core.UWP.Services
{
var mailCount = downloadedMailItems.Count();
// If there are more than 3 mails, just display 1 general toast.
if (mailCount > 3)
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.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)
// If there are more than 3 mails, just display 1 general toast.
if (mailCount > 3)
{
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.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.Show();
}
await UpdateTaskbarIconBadgeAsync();
}
}
catch (Exception ex)
{
Log.Error(ex, "Failed to create notifications.");
}
}
@@ -142,10 +150,10 @@ namespace Wino.Core.UWP.Services
public async Task UpdateTaskbarIconBadgeAsync()
{
int totalUnreadCount = 0;
var badgeUpdater = BadgeUpdateManager.CreateBadgeUpdaterForApplication();
try
{
var badgeUpdater = BadgeUpdateManager.CreateBadgeUpdaterForApplication();
var accounts = await _accountService.GetAccountsAsync();
foreach (var account in accounts)
@@ -178,11 +186,9 @@ namespace Wino.Core.UWP.Services
else
badgeUpdater.Clear();
}
catch (System.Exception ex)
catch (Exception ex)
{
// TODO: Log exceptions.
badgeUpdater.Clear();
Log.Error(ex, "Error while updating taskbar badge.");
}
}

View File

@@ -23,9 +23,6 @@ namespace Wino.Core.UWP.Services
_dialogService = dialogService;
}
private void SetRated()
=> _configurationService.SetRoaming(RatedStorageKey, true);
private bool IsAskingThresholdExceeded()
{
var latestAskedDate = _configurationService.Get(LatestAskedKey, DateTime.MinValue);
@@ -62,15 +59,14 @@ namespace Wino.Core.UWP.Services
{
if (!IsAskingThresholdExceeded()) return;
var ratingDialogResult = await _dialogService.ShowRatingDialogAsync();
var isRateWinoApproved = await _dialogService.ShowWinoCustomMessageDialogAsync(Translator.StoreRatingDialog_Title,
Translator.StoreRatingDialog_MessageFirstLine,
Translator.Buttons_RateWino,
Domain.Enums.WinoCustomMessageDialogIcon.Question,
Translator.Buttons_No,
RatedStorageKey);
if (ratingDialogResult == null)
return;
if (ratingDialogResult.DontAskAgain)
SetRated();
if (ratingDialogResult.RateWinoClicked)
if (isRateWinoApproved)
{
// In case of failure of this call, we will navigate users to Store page directly.
@@ -107,7 +103,7 @@ namespace Wino.Core.UWP.Services
else
_dialogService.InfoBarMessage(Translator.Info_ReviewSuccessTitle, Translator.Info_ReviewNewMessage, Domain.Enums.InfoBarMessageType.Success);
SetRated();
_configurationService.Set(RatedStorageKey, true);
break;
case StoreRateAndReviewStatus.CanceledByUser:
break;

View File

@@ -151,15 +151,12 @@ namespace Wino.Core.MenuItems
return accountMenuItem;
}
public async Task ReplaceFoldersAsync(IEnumerable<IMenuItem> folders)
public void ReplaceFolders(IEnumerable<IMenuItem> folders)
{
await _dispatcher.ExecuteOnUIThread(() =>
{
ClearFolderAreaMenuItems();
ClearFolderAreaMenuItems();
Items.Add(new SeperatorItem());
AddRange(folders);
});
Items.Add(new SeperatorItem());
AddRange(folders);
}
/// <summary>
@@ -194,9 +191,11 @@ namespace Wino.Core.MenuItems
{
item.IsExpanded = false;
item.IsSelected = false;
Remove(item);
});
RemoveRange(itemsToRemove);
// RemoveRange(itemsToRemove);
}
}
}

View File

@@ -14,7 +14,7 @@ namespace Wino.Core.Services
public class DatabaseService : IDatabaseService
{
private const string DatabaseName = "Wino172.db";
private const string DatabaseName = "Wino180.db";
private bool _isInitialized = false;
private readonly IApplicationConfiguration _folderConfiguration;

View File

@@ -387,11 +387,6 @@ namespace Wino.Core.Services
if (configuration == null)
throw new ArgumentNullException(nameof(configuration));
var account = await _accountService.GetAccountAsync(accountId);
if (account == null)
throw new ArgumentNullException(nameof(account));
// Update system folders for this account.
await Task.WhenAll(UpdateSystemFolderInternalAsync(configuration.SentFolder, SpecialFolderType.Sent),
@@ -400,9 +395,8 @@ namespace Wino.Core.Services
UpdateSystemFolderInternalAsync(configuration.TrashFolder, SpecialFolderType.Deleted),
UpdateSystemFolderInternalAsync(configuration.ArchiveFolder, SpecialFolderType.Archive));
await _accountService.UpdateAccountAsync(account);
return account;
return await _accountService.GetAccountAsync(accountId).ConfigureAwait(false);
}
private Task UpdateSystemFolderInternalAsync(MailItemFolder folder, SpecialFolderType assignedSpecialFolderType)
@@ -492,13 +486,6 @@ namespace Wino.Core.Services
return;
}
var account = await _accountService.GetAccountAsync(folder.MailAccountId).ConfigureAwait(false);
if (account == null)
{
_logger.Warning("Account with id {MailAccountId} does not exist. Cannot update folder.", folder.MailAccountId);
return;
}
_logger.Debug("Updating folder {FolderName}", folder.Id, folder.FolderName);
await Connection.UpdateAsync(folder).ConfigureAwait(false);

View File

@@ -785,6 +785,7 @@ namespace Wino.Core.Synchronizers
EventHandler<MessageFlagsChangedEventArgs> MessageFlagsChangedHandler = async (s, e) =>
{
if (imapFolder == null) return;
if (e.UniqueId == null) return;
var localMailCopyId = MailkitClientExtensions.CreateUid(folder.Id, e.UniqueId.Value.Id);

View File

@@ -95,7 +95,9 @@ namespace Wino.Mail.ViewModels
{
// Discord disclaimer message about server.
if (stringUrl == DiscordChannelUrl)
await DialogService.ShowMessageAsync(Translator.DiscordChannelDisclaimerMessage, Translator.DiscordChannelDisclaimerTitle);
await DialogService.ShowMessageAsync(Translator.DiscordChannelDisclaimerMessage,
Translator.DiscordChannelDisclaimerTitle,
WinoCustomMessageDialogIcon.Warning);
await _nativeAppService.LaunchUriAsync(new Uri(stringUrl));
}

View File

@@ -98,14 +98,18 @@ namespace Wino.Mail.ViewModels
// Check existence.
if (AccountAliases.Any(a => a.AliasAddress == newAlias.AliasAddress))
{
await DialogService.ShowMessageAsync(Translator.DialogMessage_AliasExistsTitle, Translator.DialogMessage_AliasExistsMessage);
await DialogService.ShowMessageAsync(Translator.DialogMessage_AliasExistsTitle,
Translator.DialogMessage_AliasExistsMessage,
WinoCustomMessageDialogIcon.Warning);
return;
}
// Validate all addresses.
if (!EmailValidator.Validate(newAlias.AliasAddress) || (!string.IsNullOrEmpty(newAlias.ReplyToAddress) && !EmailValidator.Validate(newAlias.ReplyToAddress)))
{
await DialogService.ShowMessageAsync(Translator.DialogMessage_InvalidAliasMessage, Translator.DialogMessage_InvalidAliasTitle);
await DialogService.ShowMessageAsync(Translator.DialogMessage_InvalidAliasMessage,
Translator.DialogMessage_InvalidAliasTitle,
WinoCustomMessageDialogIcon.Warning);
return;
}
@@ -125,14 +129,18 @@ namespace Wino.Mail.ViewModels
// Primary aliases can't be deleted.
if (alias.IsPrimary)
{
await DialogService.ShowMessageAsync(Translator.Info_CantDeletePrimaryAliasMessage, Translator.GeneralTitle_Warning);
await DialogService.ShowMessageAsync(Translator.Info_CantDeletePrimaryAliasMessage,
Translator.GeneralTitle_Warning,
WinoCustomMessageDialogIcon.Warning);
return;
}
// Root aliases can't be deleted.
if (alias.IsRootAlias)
{
await DialogService.ShowMessageAsync(Translator.DialogMessage_CantDeleteRootAliasTitle, Translator.DialogMessage_CantDeleteRootAliasMessage);
await DialogService.ShowMessageAsync(Translator.DialogMessage_CantDeleteRootAliasTitle,
Translator.DialogMessage_CantDeleteRootAliasMessage,
WinoCustomMessageDialogIcon.Warning);
return;
}

View File

@@ -37,7 +37,9 @@ namespace Wino.Mail.ViewModels
IRecipient<MergedInboxRenamed>,
IRecipient<LanguageChanged>,
IRecipient<AccountMenuItemsReordered>,
IRecipient<AccountSynchronizationProgressUpdatedMessage>
IRecipient<AccountSynchronizationProgressUpdatedMessage>,
IRecipient<NavigateAppPreferencesRequested>,
IRecipient<AccountFolderConfigurationUpdated>
{
#region Menu Items
@@ -59,12 +61,16 @@ namespace Wino.Mail.ViewModels
#endregion
private const string IsActivateStartupLaunchAskedKey = nameof(IsActivateStartupLaunchAskedKey);
public IStatePersistanceService StatePersistenceService { get; }
public IWinoServerConnectionManager ServerConnectionManager { get; }
public IPreferencesService PreferencesService { get; }
public IWinoNavigationService NavigationService { get; }
private readonly IFolderService _folderService;
private readonly IConfigurationService _configurationService;
private readonly IStartupBehaviorService _startupBehaviorService;
private readonly IAccountService _accountService;
private readonly IContextMenuItemService _contextMenuItemService;
private readonly IStoreRatingService _storeRatingService;
@@ -98,7 +104,9 @@ namespace Wino.Mail.ViewModels
IWinoRequestDelegator winoRequestDelegator,
IFolderService folderService,
IStatePersistanceService statePersistanceService,
IWinoServerConnectionManager serverConnectionManager) : base(dialogService)
IWinoServerConnectionManager serverConnectionManager,
IConfigurationService configurationService,
IStartupBehaviorService startupBehaviorService) : base(dialogService)
{
StatePersistenceService = statePersistanceService;
ServerConnectionManager = serverConnectionManager;
@@ -115,6 +123,8 @@ namespace Wino.Mail.ViewModels
PreferencesService = preferencesService;
NavigationService = navigationService;
_configurationService = configurationService;
_startupBehaviorService = startupBehaviorService;
_backgroundTaskService = backgroundTaskService;
_mimeFileService = mimeFileService;
_nativeAppService = nativeAppService;
@@ -229,16 +239,56 @@ namespace Wino.Mail.ViewModels
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
{
base.OnNavigatedTo(mode, parameters);
await CreateFooterItemsAsync();
await RecreateMenuItemsAsync();
await ProcessLaunchOptionsAsync();
await ForceAllAccountSynchronizationsAsync();
await MakeSureEnableStartupLaunchAsync();
ConfigureBackgroundTasks();
}
private async Task MakeSureEnableStartupLaunchAsync()
{
if (!_configurationService.Get<bool>(IsActivateStartupLaunchAskedKey, false))
{
var currentBehavior = await _startupBehaviorService.GetCurrentStartupBehaviorAsync();
// User somehow already enabled Wino before the first launch.
if (currentBehavior == StartupBehaviorResult.Enabled)
{
_configurationService.Set(IsActivateStartupLaunchAskedKey, true);
return;
}
bool isAccepted = await DialogService.ShowWinoCustomMessageDialogAsync(Translator.DialogMessage_EnableStartupLaunchTitle,
Translator.DialogMessage_EnableStartupLaunchMessage,
Translator.Buttons_Yes,
WinoCustomMessageDialogIcon.Information,
Translator.Buttons_No);
bool shouldDisplayLaterOnMessage = !isAccepted;
if (isAccepted)
{
var behavior = await _startupBehaviorService.ToggleStartupBehavior(true);
shouldDisplayLaterOnMessage = behavior != StartupBehaviorResult.Enabled;
}
if (shouldDisplayLaterOnMessage)
{
await DialogService.ShowWinoCustomMessageDialogAsync(Translator.DialogMessage_EnableStartupLaunchTitle,
Translator.DialogMessage_EnableStartupLaunchDeniedMessage,
Translator.Buttons_Close,
WinoCustomMessageDialogIcon.Information);
}
_configurationService.Set(IsActivateStartupLaunchAskedKey, true);
}
}
private void ConfigureBackgroundTasks()
{
try
@@ -527,7 +577,7 @@ namespace Wino.Mail.ViewModels
StatePersistenceService.CoreWindowTitle = "Wino Mail";
}
public async Task MenuItemInvokedOrSelectedAsync(IMenuItem clickedMenuItem)
public async Task MenuItemInvokedOrSelectedAsync(IMenuItem clickedMenuItem, object parameter = null)
{
if (clickedMenuItem == null) return;
@@ -561,11 +611,11 @@ namespace Wino.Mail.ViewModels
}
else if (clickedMenuItem is SettingsItem)
{
NavigationService.Navigate(WinoPage.SettingsPage);
NavigationService.Navigate(WinoPage.SettingsPage, parameter, NavigationReferenceFrame.ShellFrame, NavigationTransitionType.None);
}
else if (clickedMenuItem is ManageAccountsMenuItem)
{
NavigationService.Navigate(WinoPage.AccountManagementPage);
NavigationService.Navigate(WinoPage.AccountManagementPage, parameter, NavigationReferenceFrame.ShellFrame, NavigationTransitionType.None);
}
else if (clickedMenuItem is IAccountMenuItem clickedAccountMenuItem && latestSelectedAccountMenuItem != clickedAccountMenuItem)
{
@@ -582,6 +632,9 @@ namespace Wino.Mail.ViewModels
await MenuItems.SetAccountMenuItemEnabledStatusAsync(false);
// Load account folder structure and replace the visible folders.
var folders = await _folderService.GetAccountFoldersForDisplayAsync(clickedBaseAccountMenuItem);
await ExecuteUIThread(() =>
{
clickedBaseAccountMenuItem.IsEnabled = false;
@@ -594,12 +647,10 @@ namespace Wino.Mail.ViewModels
clickedBaseAccountMenuItem.IsSelected = true;
latestSelectedAccountMenuItem = clickedBaseAccountMenuItem;
MenuItems.ReplaceFolders(folders);
});
// Load account folder structure and replace the visible folders.
var folders = await _folderService.GetAccountFoldersForDisplayAsync(clickedBaseAccountMenuItem);
await MenuItems.ReplaceFoldersAsync(folders);
await UpdateUnreadItemCountAsync();
await MenuItems.SetAccountMenuItemEnabledStatusAsync(true);
@@ -721,7 +772,19 @@ namespace Wino.Mail.ViewModels
if (!accounts.Any())
{
await DialogService.ShowMessageAsync(Translator.DialogMessage_NoAccountsForCreateMailMessage, Translator.DialogMessage_NoAccountsForCreateMailTitle);
var isManageAccountClicked = await DialogService.ShowWinoCustomMessageDialogAsync(Translator.DialogMessage_NoAccountsForCreateMailMessage,
Translator.DialogMessage_NoAccountsForCreateMailTitle,
Translator.MenuManageAccounts,
WinoCustomMessageDialogIcon.Information,
string.Empty);
if (isManageAccountClicked)
{
SelectedMenuItem = ManageAccountsMenuItem;
}
return;
}
@@ -850,7 +913,9 @@ namespace Wino.Mail.ViewModels
if (!accounts.Any())
{
await DialogService.ShowMessageAsync(Translator.DialogMessage_NoAccountsForCreateMailMessage, Translator.DialogMessage_NoAccountsForCreateMailTitle);
await DialogService.ShowMessageAsync(Translator.DialogMessage_NoAccountsForCreateMailMessage,
Translator.DialogMessage_NoAccountsForCreateMailTitle,
WinoCustomMessageDialogIcon.Warning);
}
else if (accounts.Count == 1)
{
@@ -892,6 +957,16 @@ namespace Wino.Mail.ViewModels
}
}
public async void Receive(AccountFolderConfigurationUpdated message)
{
// Reloading of folders is needed to re-create folder tree if the account is loaded.
if (MenuItems.TryGetAccountMenuItem(message.AccountId, out IAccountMenuItem accountMenuItem))
{
await ChangeLoadedAccountAsync(accountMenuItem, true);
}
}
public async void Receive(MergedInboxRenamed message)
{
var mergedInboxMenuItem = MenuItems.FirstOrDefault(a => a.EntityId == message.MergedInboxId);
@@ -961,5 +1036,10 @@ namespace Wino.Mail.ViewModels
await ExecuteUIThread(() => { accountMenuItem.SynchronizationProgress = message.Progress; });
}
public async void Receive(NavigateAppPreferencesRequested message)
{
await MenuItemInvokedOrSelectedAsync(SettingsItem, WinoPage.AppPreferencesPage);
}
}
}

View File

@@ -161,7 +161,9 @@ namespace Wino.Mail.ViewModels
if (!ToItems.Any())
{
await DialogService.ShowMessageAsync(Translator.DialogMessage_ComposerMissingRecipientMessage, Translator.DialogMessage_ComposerValidationFailedTitle);
await DialogService.ShowMessageAsync(Translator.DialogMessage_ComposerMissingRecipientMessage,
Translator.DialogMessage_ComposerValidationFailedTitle,
WinoCustomMessageDialogIcon.Warning);
return;
}
@@ -202,17 +204,7 @@ namespace Wino.Mail.ViewModels
await _worker.ExecuteAsync(draftSendPreparationRequest);
}
public async Task IncludeAttachmentAsync(MailAttachmentViewModel viewModel)
{
//if (bodyBuilder == null) return;
//bodyBuilder.Attachments.Add(viewModel.FileName, new MemoryStream(viewModel.Content));
//LoadAttachments();
IncludedAttachments.Add(viewModel);
}
private async Task UpdateMimeChangesAsync()
public async Task UpdateMimeChangesAsync()
{
if (isUpdatingMimeBlocked || CurrentMimeMessage == null || ComposingAccount == null || CurrentMailDraftItem == null) return;
@@ -336,13 +328,12 @@ namespace Wino.Mail.ViewModels
}
}
public override async void OnNavigatedFrom(NavigationMode mode, object parameters)
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
{
base.OnNavigatedFrom(mode, parameters);
await UpdateMimeChangesAsync().ConfigureAwait(false);
Messenger.Send(new KillChromiumRequested());
/// Do not put any code here.
/// Make sure to use Page's OnNavigatedTo instead.
}
public override async void OnNavigatedTo(NavigationMode mode, object parameters)

View File

@@ -6,13 +6,6 @@ namespace Wino.Mail.ViewModels.Messages
/// When the rendering page is active, but new item is requested to be rendered.
/// To not trigger navigation again and re-use existing Chromium.
/// </summary>
public class NewMailItemRenderingRequestedEvent
{
public NewMailItemRenderingRequestedEvent(MailItemViewModel mailItemViewModel)
{
MailItemViewModel = mailItemViewModel;
}
public MailItemViewModel MailItemViewModel { get; }
}
/// <param name="MailItemViewModel"></param>
public record NewMailItemRenderingRequestedEvent(MailItemViewModel MailItemViewModel);
}

View File

@@ -4,7 +4,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Wino.Controls"
xmlns:selectors="using:Wino.Selectors"
xmlns:wino="using:Wino">
xmlns:wino="using:Wino"
xmlns:dialogs="using:Wino.Core.Domain.Models.Dialogs"
xmlns:styles="using:Wino.Styles">
<Application.Resources>
<ResourceDictionary>
@@ -18,6 +20,7 @@
<ResourceDictionary Source="/Styles/CommandBarItems.xaml" />
<ResourceDictionary Source="/Styles/ItemContainerStyles.xaml" />
<ResourceDictionary Source="/Styles/WinoInfoBar.xaml" />
<styles:CustomMessageDialogStyles />
<ResourceDictionary>
@@ -166,6 +169,10 @@
</Setter>
</Style>
<!-- Attachment Icon Templates -->
<DataTemplate x:Key="NoneTemplate">
<Image Source="/Assets/FileTypes/type_none.png" />
</DataTemplate>

View File

@@ -21,6 +21,7 @@ using Windows.Foundation.Metadata;
using Windows.Storage;
using Windows.System.Profile;
using Windows.UI;
using Windows.UI.Core.Preview;
using Windows.UI.Notifications;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
@@ -38,6 +39,7 @@ using Wino.Core.UWP;
using Wino.Core.UWP.Services;
using Wino.Mail.ViewModels;
using Wino.Messaging.Client.Connection;
using Wino.Messaging.Client.Navigation;
using Wino.Messaging.Server;
using Wino.Services;
@@ -107,6 +109,78 @@ namespace Wino
WeakReferenceMessenger.Default.Register(this);
}
private async void ApplicationCloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e)
{
var deferral = e.GetDeferral();
// Wino should notify user on app close if:
// 1. User has at least 1 registered account.
// 2. Startup behavior is not Enabled.
// 3. Server terminate behavior is set to Terminate.
var accountService = Services.GetService<IAccountService>();
var accounts = await accountService.GetAccountsAsync();
if (accounts.Count > 0)
{
// User has some accounts. Check if Wino Server runs on system startup.
var dialogService = Services.GetService<IDialogService>();
var startupBehaviorService = Services.GetService<IStartupBehaviorService>();
var preferencesService = Services.GetService<IPreferencesService>();
var currentStartupBehavior = await startupBehaviorService.GetCurrentStartupBehaviorAsync();
bool? isGoToAppPreferencesRequested = null;
if (preferencesService.ServerTerminationBehavior == ServerBackgroundMode.Terminate)
{
// Starting the server is fine, but check if server termination behavior is set to terminate.
// This state will kill the server once the app is terminated.
isGoToAppPreferencesRequested = await _dialogService.ShowWinoCustomMessageDialogAsync(Translator.AppCloseBackgroundSynchronizationWarningTitle,
$"{Translator.AppCloseTerminateBehaviorWarningMessageFirstLine}\n{Translator.AppCloseTerminateBehaviorWarningMessageSecondLine}\n\n{Translator.AppCloseTerminateBehaviorWarningMessageThirdLine}",
Translator.Buttons_Yes,
WinoCustomMessageDialogIcon.Warning,
Translator.Buttons_No,
"DontAskTerminateServerBehavior");
}
if (isGoToAppPreferencesRequested == null && currentStartupBehavior != StartupBehaviorResult.Enabled)
{
// Startup behavior is not enabled.
isGoToAppPreferencesRequested = await dialogService.ShowWinoCustomMessageDialogAsync(Translator.AppCloseBackgroundSynchronizationWarningTitle,
$"{Translator.AppCloseStartupLaunchDisabledWarningMessageFirstLine}\n{Translator.AppCloseStartupLaunchDisabledWarningMessageSecondLine}\n\n{Translator.AppCloseStartupLaunchDisabledWarningMessageThirdLine}",
Translator.Buttons_Yes,
WinoCustomMessageDialogIcon.Warning,
Translator.Buttons_No,
"DontAskDisabledStartup");
}
if (isGoToAppPreferencesRequested == true)
{
WeakReferenceMessenger.Default.Send(new NavigateAppPreferencesRequested());
e.Handled = true;
}
else if (preferencesService.ServerTerminationBehavior == ServerBackgroundMode.Terminate)
{
try
{
var isServerKilled = await _appServiceConnectionManager.GetResponseAsync<bool, TerminateServerRequested>(new TerminateServerRequested());
Log.Information("Server is killed.");
}
catch (Exception ex)
{
Log.Error(ex, "Failed to kill server.");
}
}
}
deferral.Complete();
}
private async void OnResuming(object sender, object e)
{
// App Service connection was lost on suspension.
@@ -233,6 +307,7 @@ namespace Wino
LogActivation("Window is created.");
ConfigureTitleBar();
TryRegisterAppCloseChange();
}
protected override async void OnLaunched(LaunchActivatedEventArgs args)
@@ -245,6 +320,18 @@ namespace Wino
}
}
private void TryRegisterAppCloseChange()
{
try
{
var systemNavigationManagerPreview = SystemNavigationManagerPreview.GetForCurrentView();
systemNavigationManagerPreview.CloseRequested -= ApplicationCloseRequested;
systemNavigationManagerPreview.CloseRequested += ApplicationCloseRequested;
}
catch { }
}
protected override async void OnFileActivated(FileActivatedEventArgs args)
{
base.OnFileActivated(args);

View File

@@ -1,29 +0,0 @@
<ContentDialog
x:Class="Wino.Dialogs.ConfirmationDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:domain="using:Wino.Core.Domain"
Title="{x:Bind DialogTitle, Mode=OneWay}"
Style="{StaticResource WinoDialogStyle}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Closed="DialogClosed"
PrimaryButtonText="{x:Bind ApproveButtonTitle, Mode=OneWay}"
DefaultButton="Primary"
SecondaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}"
PrimaryButtonClick="ApproveClicked"
SecondaryButtonClick="CancelClicked"
mc:Ignorable="d">
<ContentDialog.Resources>
<x:Double x:Key="ContentDialogMinWidth">250</x:Double>
<x:Double x:Key="ContentDialogMaxWidth">500</x:Double>
<x:Double x:Key="ContentDialogMinHeight">200</x:Double>
<x:Double x:Key="ContentDialogMaxHeight">756</x:Double>
</ContentDialog.Resources>
<TextBlock Text="{x:Bind Message, Mode=OneWay}" TextWrapping="Wrap" />
</ContentDialog>

View File

@@ -1,80 +0,0 @@
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Interfaces;
namespace Wino.Dialogs
{
public sealed partial class ConfirmationDialog : ContentDialog, IConfirmationDialog
{
private TaskCompletionSource<bool> _completionSource;
#region Dependency Properties
public string DialogTitle
{
get { return (string)GetValue(DialogTitleProperty); }
set { SetValue(DialogTitleProperty, value); }
}
public static readonly DependencyProperty DialogTitleProperty = DependencyProperty.Register(nameof(DialogTitle), typeof(string), typeof(ConfirmationDialog), new PropertyMetadata(string.Empty));
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(nameof(Message), typeof(string), typeof(ConfirmationDialog), new PropertyMetadata(string.Empty));
public string ApproveButtonTitle
{
get { return (string)GetValue(ApproveButtonTitleProperty); }
set { SetValue(ApproveButtonTitleProperty, value); }
}
public static readonly DependencyProperty ApproveButtonTitleProperty = DependencyProperty.Register(nameof(ApproveButtonTitle), typeof(string), typeof(ConfirmationDialog), new PropertyMetadata(string.Empty));
#endregion
private bool _isApproved;
public ConfirmationDialog()
{
InitializeComponent();
}
public async Task<bool> ShowDialogAsync(string title, string message, string approveButtonTitle)
{
_completionSource = new TaskCompletionSource<bool>();
DialogTitle = title;
Message = message;
ApproveButtonTitle = approveButtonTitle;
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
ShowAsync();
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
return await _completionSource.Task;
}
private void DialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args)
{
_completionSource.TrySetResult(_isApproved);
}
private void ApproveClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
_isApproved = true;
Hide();
}
private void CancelClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
_isApproved = false;
Hide();
}
}
}

View File

@@ -0,0 +1,24 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Wino.Core.Domain.Enums;
namespace Wino.Dialogs
{
public partial class CustomMessageDialogInformationContainer : ObservableObject
{
[ObservableProperty]
private bool isDontAskChecked;
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

@@ -1,24 +0,0 @@
<ContentDialog
x:Class="Wino.Dialogs.StoreRatingDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Wino.Dialogs"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:domain="using:Wino.Core.Domain"
PrimaryButtonText="{x:Bind domain:Translator.Buttons_RateWino}"
SecondaryButtonText="{x:Bind domain:Translator.Buttons_No}"
DefaultButton="Primary"
PrimaryButtonClick="RateClicked"
Title="{x:Bind domain:Translator.StoreRatingDialog_Title}"
Style="{StaticResource WinoDialogStyle}">
<Grid>
<StackPanel Spacing="6">
<TextBlock Text="{x:Bind domain:Translator.StoreRatingDialog_MessageFirstLine}" />
<TextBlock Text="{x:Bind domain:Translator.StoreRatingDialog_MessageSecondLine}" />
<CheckBox IsChecked="{x:Bind DontAskAgain, Mode=TwoWay}" Content="{x:Bind domain:Translator.Dialog_DontAskAgain}" />
</StackPanel>
</Grid>
</ContentDialog>

View File

@@ -1,21 +0,0 @@
using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Interfaces;
namespace Wino.Dialogs
{
public sealed partial class StoreRatingDialog : ContentDialog, IStoreRatingDialog
{
public bool DontAskAgain { get; set; }
public bool RateWinoClicked { get; set; }
public StoreRatingDialog()
{
this.InitializeComponent();
}
private void RateClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
RateWinoClicked = true;
}
}
}

View File

@@ -1,25 +0,0 @@
<ContentDialog
x:Class="Wino.Dialogs.WinoMessageDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="{x:Bind DialogTitle, Mode=OneWay}"
xmlns:domain="using:Wino.Core.Domain"
Style="{StaticResource WinoDialogStyle}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
CloseButtonText="{x:Bind domain:Translator.Buttons_Close}"
DefaultButton="Close"
Closed="DialogClosed"
mc:Ignorable="d">
<ContentDialog.Resources>
<x:Double x:Key="ContentDialogMinWidth">250</x:Double>
<x:Double x:Key="ContentDialogMaxWidth">900</x:Double>
<x:Double x:Key="ContentDialogMinHeight">200</x:Double>
<x:Double x:Key="ContentDialogMaxHeight">756</x:Double>
</ContentDialog.Resources>
<TextBlock Text="{x:Bind Message, Mode=OneWay}" TextWrapping="Wrap" />
</ContentDialog>

View File

@@ -1,60 +0,0 @@
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace Wino.Dialogs
{
public sealed partial class WinoMessageDialog : ContentDialog
{
private TaskCompletionSource<bool> _completionSource;
#region Dependency Properties
public string DialogTitle
{
get { return (string)GetValue(DialogTitleProperty); }
set { SetValue(DialogTitleProperty, value); }
}
public static readonly DependencyProperty DialogTitleProperty = DependencyProperty.Register(nameof(DialogTitle), typeof(string), typeof(ConfirmationDialog), new PropertyMetadata(string.Empty));
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(nameof(Message), typeof(string), typeof(ConfirmationDialog), new PropertyMetadata(string.Empty));
#endregion
public WinoMessageDialog()
{
InitializeComponent();
}
public async Task<bool> ShowDialogAsync(string title, string message)
{
_completionSource = new TaskCompletionSource<bool>();
DialogTitle = title;
Message = message;
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
ShowAsync();
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
return await _completionSource.Task;
}
private void ApproveClicked(object sender, RoutedEventArgs e)
{
Hide();
}
private void DialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args)
{
_completionSource.TrySetResult(true);
}
}
}

View File

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

View File

@@ -8,6 +8,7 @@ using Microsoft.Toolkit.Uwp.Helpers;
using Serilog;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Wino.Core.Domain;
using Wino.Core.Domain.Entities;
@@ -18,9 +19,9 @@ using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.UWP.Extensions;
using Wino.Dialogs;
using Wino.Messaging.Client.Accounts;
using Wino.Messaging.Client.Shell;
using Wino.Messaging.Server;
using Wino.Messaging.UI;
namespace Wino.Services
{
@@ -29,26 +30,21 @@ namespace Wino.Services
private SemaphoreSlim _presentationSemaphore = new SemaphoreSlim(1);
private readonly IThemeService _themeService;
private readonly IConfigurationService _configurationService;
public DialogService(IThemeService themeService)
public DialogService(IThemeService themeService, IConfigurationService configurationService)
{
_themeService = themeService;
_configurationService = configurationService;
}
public void ShowNotSupportedMessage()
{
InfoBarMessage(Translator.Info_UnsupportedFunctionalityTitle, Translator.Info_UnsupportedFunctionalityDescription, InfoBarMessageType.Error);
}
=> InfoBarMessage(Translator.Info_UnsupportedFunctionalityTitle,
Translator.Info_UnsupportedFunctionalityDescription,
InfoBarMessageType.Error);
public async Task ShowMessageAsync(string message, string title)
{
var dialog = new WinoMessageDialog()
{
RequestedTheme = _themeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentation(() => dialog.ShowDialogAsync(title, message));
}
public Task ShowMessageAsync(string message, string title, WinoCustomMessageDialogIcon icon = WinoCustomMessageDialogIcon.Information)
=> ShowWinoCustomMessageDialogAsync(title, message, Translator.Buttons_Close, icon);
/// <summary>
/// Waits for PopupRoot to be available before presenting the dialog and returns the result after presentation.
@@ -75,40 +71,8 @@ namespace Wino.Services
return ContentDialogResult.None;
}
/// <summary>
/// Waits for PopupRoot to be available before executing the given Task that returns customized result.
/// </summary>
/// <param name="executionTask">Task that presents the dialog and returns result.</param>
/// <returns>Dialog result from the custom dialog.</returns>
private async Task<bool> HandleDialogPresentation(Func<Task<bool>> executionTask)
{
await _presentationSemaphore.WaitAsync();
try
{
return await executionTask();
}
catch (Exception ex)
{
Log.Error(ex, "Handling dialog service failed.");
}
finally
{
_presentationSemaphore.Release();
}
return false;
}
public async Task<bool> ShowConfirmationDialogAsync(string question, string title, string confirmationButtonTitle)
{
var dialog = new ConfirmationDialog()
{
RequestedTheme = _themeService.RootTheme.ToWindowsElementTheme()
};
return await HandleDialogPresentation(() => dialog.ShowDialogAsync(title, question, confirmationButtonTitle));
}
public Task<bool> ShowConfirmationDialogAsync(string question, string title, string confirmationButtonTitle)
=> ShowWinoCustomMessageDialogAsync(title, question, confirmationButtonTitle, WinoCustomMessageDialogIcon.Question, Translator.Buttons_Cancel, string.Empty);
public async Task<AccountCreationDialogResult> ShowNewAccountMailProviderDialogAsync(List<IProviderDetail> availableProviders)
{
@@ -204,18 +168,6 @@ namespace Wino.Services
return editAccountDialog.IsSaved ? editAccountDialog.Account : null;
}
public async Task<IStoreRatingDialog> ShowRatingDialogAsync()
{
var storeDialog = new StoreRatingDialog()
{
RequestedTheme = _themeService.RootTheme.ToWindowsElementTheme()
};
await HandleDialogPresentationAsync(storeDialog);
return storeDialog;
}
public async Task<ICreateAccountAliasDialog> ShowCreateAccountAliasDialogAsync()
{
var createAccountAliasDialog = new CreateAccountAliasDialog()
@@ -245,24 +197,18 @@ namespace Wino.Services
if (configuration != null)
{
var updatedAccount = await folderService.UpdateSystemFolderConfigurationAsync(accountId, configuration);
InfoBarMessage(Translator.SystemFolderConfigSetupSuccess_Title, Translator.SystemFolderConfigSetupSuccess_Message, InfoBarMessageType.Success);
// Update account menu item and force re-synchronization.
WeakReferenceMessenger.Default.Send(new AccountUpdatedMessage(updatedAccount));
WeakReferenceMessenger.Default.Send(new AccountFolderConfigurationUpdated(accountId));
var options = new SynchronizationOptions()
{
AccountId = updatedAccount.Id,
AccountId = accountId,
Type = SynchronizationType.Full,
};
WeakReferenceMessenger.Default.Send(new NewSynchronizationRequested(options, SynchronizationSource.Client));
}
if (configuration != null)
{
InfoBarMessage(Translator.SystemFolderConfigSetupSuccess_Title, Translator.SystemFolderConfigSetupSuccess_Message, InfoBarMessageType.Success);
}
}
catch (Exception ex)
{
@@ -331,7 +277,12 @@ namespace Wino.Services
return await file.ReadBytesAsync();
}
public Task<bool> ShowHardDeleteConfirmationAsync() => ShowConfirmationDialogAsync(Translator.DialogMessage_HardDeleteConfirmationMessage, Translator.DialogMessage_HardDeleteConfirmationTitle, Translator.Buttons_Yes);
public Task<bool> ShowHardDeleteConfirmationAsync()
=> ShowWinoCustomMessageDialogAsync(Translator.DialogMessage_HardDeleteConfirmationMessage,
Translator.DialogMessage_HardDeleteConfirmationTitle,
Translator.Buttons_Yes,
WinoCustomMessageDialogIcon.Warning,
Translator.Buttons_No);
public async Task<MailAccount> ShowAccountPickerDialogAsync(List<MailAccount> availableAccounts)
{
@@ -377,5 +328,48 @@ namespace Wino.Services
await HandleDialogPresentationAsync(accountReorderDialog);
}
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 = (Style)App.Current.Resources["WinoDialogStyle"],
RequestedTheme = _themeService.RootTheme.ToWindowsElementTheme(),
DefaultButton = ContentDialogButton.Primary,
PrimaryButtonText = approveButtonText,
ContentTemplate = (DataTemplate)App.Current.Resources["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;
}
}
}

View File

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Wino.Styles"
x:Class="Wino.Styles.CustomMessageDialogStyles"
xmlns:dialogs="using:Wino.Dialogs"
xmlns:domain="using:Wino.Core.Domain"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:selectors="using:Wino.Selectors">
<!-- Icon templates -->
<DataTemplate x:Key="WinoCustomMessageDialogInformationIconTemplate">
<Path
Fill="#0f80d7"
Data="F1 M 0 9.375 C 0 8.509115 0.110677 7.677409 0.332031 6.879883 C 0.553385 6.082357 0.867513 5.335287 1.274414 4.638672 C 1.681315 3.942059 2.169596 3.30892 2.739258 2.739258 C 3.308919 2.169598 3.942057 1.681316 4.638672 1.274414 C 5.335286 0.867514 6.082356 0.553387 6.879883 0.332031 C 7.677409 0.110678 8.509114 0 9.375 0 C 10.240885 0 11.072591 0.110678 11.870117 0.332031 C 12.667643 0.553387 13.414713 0.867514 14.111328 1.274414 C 14.807942 1.681316 15.44108 2.169598 16.010742 2.739258 C 16.580402 3.30892 17.068684 3.942059 17.475586 4.638672 C 17.882486 5.335287 18.196613 6.082357 18.417969 6.879883 C 18.639322 7.677409 18.75 8.509115 18.75 9.375 C 18.75 10.240886 18.637695 11.072592 18.413086 11.870117 C 18.188477 12.667644 17.872721 13.413086 17.46582 14.106445 C 17.058918 14.799805 16.570637 15.431315 16.000977 16.000977 C 15.431314 16.570639 14.799804 17.05892 14.106445 17.46582 C 13.413085 17.872721 12.666015 18.188477 11.865234 18.413086 C 11.064453 18.637695 10.234375 18.75 9.375 18.75 C 8.509114 18.75 7.675781 18.639322 6.875 18.417969 C 6.074219 18.196615 5.327148 17.882486 4.633789 17.475586 C 3.94043 17.068686 3.308919 16.580404 2.739258 16.010742 C 2.169596 15.441081 1.681315 14.80957 1.274414 14.116211 C 0.867513 13.422852 0.553385 12.675781 0.332031 11.875 C 0.110677 11.074219 0 10.240886 0 9.375 Z M 17.5 9.375 C 17.5 8.626303 17.403971 7.905273 17.211914 7.211914 C 17.019855 6.518556 16.746418 5.87077 16.391602 5.268555 C 16.036783 4.666342 15.613606 4.119467 15.12207 3.62793 C 14.630533 3.136395 14.083658 2.713217 13.481445 2.358398 C 12.879231 2.003582 12.231445 1.730145 11.538086 1.538086 C 10.844727 1.346029 10.123697 1.25 9.375 1.25 C 8.626302 1.25 7.905273 1.346029 7.211914 1.538086 C 6.518555 1.730145 5.870768 2.003582 5.268555 2.358398 C 4.666341 2.713217 4.119466 3.136395 3.62793 3.62793 C 3.136393 4.119467 2.713216 4.666342 2.358398 5.268555 C 2.003581 5.87077 1.730143 6.518556 1.538086 7.211914 C 1.346029 7.905273 1.25 8.626303 1.25 9.375 C 1.25 10.123698 1.346029 10.844727 1.538086 11.538086 C 1.730143 12.231445 2.001953 12.879232 2.353516 13.481445 C 2.705078 14.083659 3.128255 14.632162 3.623047 15.126953 C 4.117838 15.621745 4.666341 16.044922 5.268555 16.396484 C 5.870768 16.748047 6.518555 17.019857 7.211914 17.211914 C 7.905273 17.403971 8.626302 17.5 9.375 17.5 C 10.123697 17.5 10.844727 17.403971 11.538086 17.211914 C 12.231445 17.019857 12.879231 16.748047 13.481445 16.396484 C 14.083658 16.044922 14.63216 15.621745 15.126953 15.126953 C 15.621744 14.632162 16.044922 14.083659 16.396484 13.481445 C 16.748047 12.879232 17.019855 12.231445 17.211914 11.538086 C 17.403971 10.844727 17.5 10.123698 17.5 9.375 Z M 8.4375 5.625 C 8.4375 5.364584 8.528646 5.14323 8.710938 4.960938 C 8.893229 4.778646 9.114583 4.6875 9.375 4.6875 C 9.635416 4.6875 9.856771 4.778646 10.039062 4.960938 C 10.221354 5.14323 10.3125 5.364584 10.3125 5.625 C 10.3125 5.885417 10.221354 6.106771 10.039062 6.289062 C 9.856771 6.471354 9.635416 6.5625 9.375 6.5625 C 9.114583 6.5625 8.893229 6.471354 8.710938 6.289062 C 8.528646 6.106771 8.4375 5.885417 8.4375 5.625 Z M 8.75 13.125 L 8.75 8.125 C 8.75 7.95573 8.811849 7.809246 8.935547 7.685547 C 9.059244 7.56185 9.205729 7.5 9.375 7.5 C 9.544271 7.5 9.690755 7.56185 9.814453 7.685547 C 9.93815 7.809246 10 7.95573 10 8.125 L 10 13.125 C 10 13.294271 9.93815 13.440756 9.814453 13.564453 C 9.690755 13.688151 9.544271 13.75 9.375 13.75 C 9.205729 13.75 9.059244 13.688151 8.935547 13.564453 C 8.811849 13.440756 8.75 13.294271 8.75 13.125 Z "
VerticalAlignment="Center"
HorizontalAlignment="Center" />
</DataTemplate>
<DataTemplate x:Key="WinoCustomMessageDialogQuestionIconTemplate">
<Path
Fill="#0984e3"
Data="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8m8-6.5a6.5 6.5 0 1 0 0 13a6.5 6.5 0 0 0 0-13M6.92 6.085h.001a.749.749 0 1 1-1.342-.67c.169-.339.436-.701.849-.977C6.845 4.16 7.369 4 8 4a2.76 2.76 0 0 1 1.637.525c.503.377.863.965.863 1.725c0 .448-.115.83-.329 1.15c-.205.307-.47.513-.692.662c-.109.072-.22.138-.313.195l-.006.004a6 6 0 0 0-.26.16a1 1 0 0 0-.276.245a.75.75 0 0 1-1.248-.832c.184-.264.42-.489.692-.661q.154-.1.313-.195l.007-.004c.1-.061.182-.11.258-.161a1 1 0 0 0 .277-.245C8.96 6.514 9 6.427 9 6.25a.61.61 0 0 0-.262-.525A1.27 1.27 0 0 0 8 5.5c-.369 0-.595.09-.74.187a1 1 0 0 0-.34.398M9 11a1 1 0 1 1-2 0a1 1 0 0 1 2 0"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
</DataTemplate>
<DataTemplate x:Key="WinoCustomMessageDialogWarningIconTemplate">
<Path
Fill="#fdcb6e"
Data="F1 M 2.021484 18.769531 C 1.767578 18.769531 1.52832 18.720703 1.303711 18.623047 C 1.079102 18.525391 0.880534 18.391928 0.708008 18.222656 C 0.535482 18.053385 0.398763 17.856445 0.297852 17.631836 C 0.19694 17.407227 0.146484 17.167969 0.146484 16.914062 C 0.146484 16.614584 0.211589 16.328125 0.341797 16.054688 L 7.695312 1.347656 C 7.851562 1.035156 8.082682 0.784506 8.388672 0.595703 C 8.694661 0.406902 9.023438 0.3125 9.375 0.3125 C 9.726562 0.3125 10.055338 0.406902 10.361328 0.595703 C 10.667317 0.784506 10.898438 1.035156 11.054688 1.347656 L 18.408203 16.054688 C 18.53841 16.328125 18.603516 16.614584 18.603516 16.914062 C 18.603516 17.167969 18.553059 17.407227 18.452148 17.631836 C 18.351236 17.856445 18.216145 18.053385 18.046875 18.222656 C 17.877604 18.391928 17.679035 18.525391 17.451172 18.623047 C 17.223307 18.720703 16.982422 18.769531 16.728516 18.769531 Z M 16.728516 17.519531 C 16.884766 17.519531 17.027994 17.460938 17.158203 17.34375 C 17.28841 17.226562 17.353516 17.086588 17.353516 16.923828 C 17.353516 16.806641 17.330729 16.702475 17.285156 16.611328 L 9.931641 1.904297 C 9.879557 1.793621 9.80306 1.708984 9.702148 1.650391 C 9.601236 1.591797 9.492188 1.5625 9.375 1.5625 C 9.257812 1.5625 9.148763 1.593426 9.047852 1.655273 C 8.946939 1.717123 8.870442 1.800131 8.818359 1.904297 L 1.464844 16.611328 C 1.419271 16.702475 1.396484 16.803387 1.396484 16.914062 C 1.396484 17.083334 1.459961 17.226562 1.586914 17.34375 C 1.713867 17.460938 1.858724 17.519531 2.021484 17.519531 Z M 8.75 11.875 L 8.75 6.875 C 8.75 6.705729 8.811849 6.559245 8.935547 6.435547 C 9.059244 6.31185 9.205729 6.25 9.375 6.25 C 9.544271 6.25 9.690755 6.31185 9.814453 6.435547 C 9.93815 6.559245 10 6.705729 10 6.875 L 10 11.875 C 10 12.044271 9.93815 12.190756 9.814453 12.314453 C 9.690755 12.438151 9.544271 12.5 9.375 12.5 C 9.205729 12.5 9.059244 12.438151 8.935547 12.314453 C 8.811849 12.190756 8.75 12.044271 8.75 11.875 Z M 8.4375 14.375 C 8.4375 14.114584 8.528646 13.893229 8.710938 13.710938 C 8.893229 13.528646 9.114583 13.4375 9.375 13.4375 C 9.635416 13.4375 9.856771 13.528646 10.039062 13.710938 C 10.221354 13.893229 10.3125 14.114584 10.3125 14.375 C 10.3125 14.635417 10.221354 14.856771 10.039062 15.039062 C 9.856771 15.221354 9.635416 15.3125 9.375 15.3125 C 9.114583 15.3125 8.893229 15.221354 8.710938 15.039062 C 8.528646 14.856771 8.4375 14.635417 8.4375 14.375 Z "
VerticalAlignment="Center"
HorizontalAlignment="Center" />
</DataTemplate>
<DataTemplate x:Key="WinoCustomMessageDialogErrorIconTemplate">
<Path
Fill="#d63031"
Data="F1 M 18.75 9.375 C 18.75 10.240886 18.639322 11.072592 18.417969 11.870117 C 18.196613 12.667644 17.882486 13.414714 17.475586 14.111328 C 17.068684 14.807943 16.580402 15.441081 16.010742 16.010742 C 15.44108 16.580404 14.807942 17.068686 14.111328 17.475586 C 13.414713 17.882486 12.667643 18.196615 11.870117 18.417969 C 11.072591 18.639322 10.240885 18.75 9.375 18.75 C 8.509114 18.75 7.675781 18.639322 6.875 18.417969 C 6.074219 18.196615 5.327148 17.882486 4.633789 17.475586 C 3.94043 17.068686 3.308919 16.580404 2.739258 16.010742 C 2.169596 15.441081 1.681315 14.80957 1.274414 14.116211 C 0.867513 13.422852 0.553385 12.675781 0.332031 11.875 C 0.110677 11.074219 0 10.240886 0 9.375 C 0 8.509115 0.110677 7.677409 0.332031 6.879883 C 0.553385 6.082357 0.867513 5.335287 1.274414 4.638672 C 1.681315 3.942059 2.169596 3.30892 2.739258 2.739258 C 3.308919 2.169598 3.942057 1.681316 4.638672 1.274414 C 5.335286 0.867514 6.082356 0.553387 6.879883 0.332031 C 7.677409 0.110678 8.509114 0 9.375 0 C 10.234375 0 11.062825 0.112305 11.860352 0.336914 C 12.657877 0.561523 13.404947 0.877279 14.101562 1.28418 C 14.798176 1.691082 15.431314 2.179363 16.000977 2.749023 C 16.570637 3.318686 17.058918 3.951824 17.46582 4.648438 C 17.872721 5.345053 18.188477 6.092123 18.413086 6.889648 C 18.637695 7.687175 18.75 8.515625 18.75 9.375 Z M 17.5 9.375 C 17.5 8.626303 17.403971 7.905273 17.211914 7.211914 C 17.019855 6.518556 16.746418 5.87077 16.391602 5.268555 C 16.036783 4.666342 15.613606 4.119467 15.12207 3.62793 C 14.630533 3.136395 14.083658 2.713217 13.481445 2.358398 C 12.879231 2.003582 12.231445 1.730145 11.538086 1.538086 C 10.844727 1.346029 10.123697 1.25 9.375 1.25 C 8.626302 1.25 7.905273 1.346029 7.211914 1.538086 C 6.518555 1.730145 5.870768 2.003582 5.268555 2.358398 C 4.666341 2.713217 4.119466 3.136395 3.62793 3.62793 C 3.136393 4.119467 2.713216 4.666342 2.358398 5.268555 C 2.003581 5.87077 1.730143 6.518556 1.538086 7.211914 C 1.346029 7.905273 1.25 8.626303 1.25 9.375 C 1.25 10.123698 1.346029 10.844727 1.538086 11.538086 C 1.730143 12.231445 2.001953 12.879232 2.353516 13.481445 C 2.705078 14.083659 3.128255 14.632162 3.623047 15.126953 C 4.117838 15.621745 4.666341 16.044922 5.268555 16.396484 C 5.870768 16.748047 6.518555 17.019857 7.211914 17.211914 C 7.905273 17.403971 8.626302 17.5 9.375 17.5 C 10.123697 17.5 10.844727 17.403971 11.538086 17.211914 C 12.231445 17.019857 12.879231 16.74642 13.481445 16.391602 C 14.083658 16.036783 14.630533 15.613607 15.12207 15.12207 C 15.613606 14.630534 16.036783 14.083659 16.391602 13.481445 C 16.746418 12.879232 17.019855 12.231445 17.211914 11.538086 C 17.403971 10.844727 17.5 10.123698 17.5 9.375 Z M 13.4375 5.9375 C 13.4375 6.106771 13.37565 6.253256 13.251953 6.376953 L 10.253906 9.375 L 13.251953 12.373047 C 13.37565 12.496745 13.4375 12.643229 13.4375 12.8125 C 13.4375 12.981771 13.37565 13.128256 13.251953 13.251953 C 13.128254 13.375651 12.98177 13.4375 12.8125 13.4375 C 12.643229 13.4375 12.496744 13.375651 12.373047 13.251953 L 9.375 10.253906 L 6.376953 13.251953 C 6.253255 13.375651 6.106771 13.4375 5.9375 13.4375 C 5.768229 13.4375 5.621745 13.375651 5.498047 13.251953 C 5.374349 13.128256 5.3125 12.981771 5.3125 12.8125 C 5.3125 12.643229 5.374349 12.496745 5.498047 12.373047 L 8.496094 9.375 L 5.498047 6.376953 C 5.374349 6.253256 5.3125 6.106771 5.3125 5.9375 C 5.3125 5.768229 5.374349 5.621745 5.498047 5.498047 C 5.621745 5.37435 5.768229 5.3125 5.9375 5.3125 C 6.106771 5.3125 6.253255 5.37435 6.376953 5.498047 L 9.375 8.496094 L 12.373047 5.498047 C 12.496744 5.37435 12.643229 5.3125 12.8125 5.3125 C 12.98177 5.3125 13.128254 5.37435 13.251953 5.498047 C 13.37565 5.621745 13.4375 5.768229 13.4375 5.9375 Z "
VerticalAlignment="Center"
HorizontalAlignment="Center" />
</DataTemplate>
<selectors:CustomWinoMessageDialogIconSelector
x:Key="CustomWinoMessageDialogIconSelector"
InfoIconTemplate="{StaticResource WinoCustomMessageDialogInformationIconTemplate}"
QuestionIconTemplate="{StaticResource WinoCustomMessageDialogQuestionIconTemplate}"
WarningIconTemplate="{StaticResource WinoCustomMessageDialogWarningIconTemplate}"
ErrorIconTemplate="{StaticResource WinoCustomMessageDialogErrorIconTemplate}" />
<DataTemplate x:DataType="dialogs:CustomMessageDialogInformationContainer" x:Key="CustomWinoContentDialogTitleTemplate">
<Grid />
</DataTemplate>
<DataTemplate x:DataType="dialogs:CustomMessageDialogInformationContainer" x:Key="CustomWinoContentDialogContentTemplate">
<Grid RowSpacing="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Title -->
<StackPanel Orientation="Horizontal" Spacing="12">
<Viewbox Width="28" HorizontalAlignment="Left">
<ContentControl ContentTemplateSelector="{StaticResource CustomWinoMessageDialogIconSelector}" Content="{x:Bind Icon}" />
</Viewbox>
<TextBlock
Text="{x:Bind Title}"
HorizontalAlignment="Center"
Style="{StaticResource SubtitleTextBlockStyle}"
VerticalAlignment="Center" />
</StackPanel>
<!-- Description -->
<TextBlock
Text="{x:Bind Description}"
Grid.Row="1"
TextWrapping="WrapWholeWords" />
<CheckBox
Grid.Row="2"
Content="{x:Bind domain:Translator.Dialog_DontAskAgain}"
Visibility="{x:Bind IsDontAskAgainEnabled}"
IsChecked="{x:Bind IsDontAskChecked, Mode=TwoWay}" />
</Grid>
</DataTemplate>
</ResourceDictionary>

View File

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

View File

@@ -39,8 +39,7 @@ namespace Wino.Views
public sealed partial class ComposePage : ComposePageAbstract,
IRecipient<NavigationPaneModeChanged>,
IRecipient<CreateNewComposeMailRequested>,
IRecipient<ApplicationThemeChanged>,
IRecipient<KillChromiumRequested>
IRecipient<ApplicationThemeChanged>
{
public bool IsComposerDarkMode
{
@@ -240,7 +239,7 @@ namespace Wino.Views
{
var attachmentViewModel = await file.ToAttachmentViewModelAsync();
await ViewModel.IncludeAttachmentAsync(attachmentViewModel);
ViewModel.IncludedAttachments.Add(attachmentViewModel);
}
}
@@ -415,7 +414,6 @@ namespace Wino.Views
return await ExecuteScriptFunctionAsync("initializeJodit", fonts, composerFont, composerFontSize, readerFont, readerFontSize);
}
private void DisposeWebView2()
{
if (Chromium == null) return;
@@ -692,8 +690,12 @@ namespace Wino.Views
}
}
public void Receive(KillChromiumRequested message)
protected override async void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
base.OnNavigatingFrom(e);
await ViewModel.UpdateMimeChangesAsync();
DisposeDisposables();
DisposeWebView2();
}

View File

@@ -31,6 +31,16 @@ namespace Wino.Views
var initialRequest = new BreadcrumbNavigationRequested(Translator.MenuSettings, WinoPage.SettingOptionsPage);
PageHistory.Add(new BreadcrumbNavigationItemViewModel(initialRequest, true));
if (e.Parameter is WinoPage parameterPage)
{
switch (parameterPage)
{
case WinoPage.AppPreferencesPage:
WeakReferenceMessenger.Default.Send(new BreadcrumbNavigationRequested(Translator.SettingsAppPreferences_Title, WinoPage.AppPreferencesPage));
break;
}
}
}
public override void OnLanguageChanged()

View File

@@ -3,9 +3,9 @@
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<LangVersion>8.0</LangVersion>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
<!-- UWP WAM Authentication on Xbox needs this. -->
<UseDotNetNativeSharedAssemblyFrameworkPackage>false</UseDotNetNativeSharedAssemblyFrameworkPackage>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
<!-- UWP WAM Authentication on Xbox needs this. -->
<UseDotNetNativeSharedAssemblyFrameworkPackage>false</UseDotNetNativeSharedAssemblyFrameworkPackage>
</PropertyGroup>
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -89,7 +89,7 @@
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
<UseDotNetNativeSharedAssemblyFrameworkPackage>false</UseDotNetNativeSharedAssemblyFrameworkPackage>
<Use64BitCompiler>true</Use64BitCompiler>
<ShortcutGenericAnalysis>true</ShortcutGenericAnalysis>
<ShortcutGenericAnalysis>true</ShortcutGenericAnalysis>
<OutOfProcPDB>true</OutOfProcPDB>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
@@ -119,7 +119,6 @@
<Use64BitCompiler>true</Use64BitCompiler>
<OutOfProcPDB>true</OutOfProcPDB>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AdaptiveTriggerLibrary">
<Version>1.2.2</Version>
@@ -230,6 +229,7 @@
<DependentUpon>AccountReorderDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Dialogs\BaseAccountCreationDialog.cs" />
<Compile Include="Dialogs\CustomMessageDialogInformationContainer.cs" />
<Compile Include="Dialogs\CustomThemeBuilderDialog.xaml.cs">
<DependentUpon>CustomThemeBuilderDialog.xaml</DependentUpon>
</Compile>
@@ -245,15 +245,9 @@
<Compile Include="Dialogs\CreateAccountAliasDialog.xaml.cs">
<DependentUpon>CreateAccountAliasDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Dialogs\StoreRatingDialog.xaml.cs">
<DependentUpon>StoreRatingDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Dialogs\SystemFolderConfigurationDialog.xaml.cs">
<DependentUpon>SystemFolderConfigurationDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Dialogs\WinoMessageDialog.xaml.cs">
<DependentUpon>WinoMessageDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Dialogs\TextInputDialog.xaml.cs">
<DependentUpon>TextInputDialog.xaml</DependentUpon>
</Compile>
@@ -290,9 +284,6 @@
<Compile Include="Controls\WinoInfoBar.cs" />
<Compile Include="Controls\WinoNavigationViewItem.cs" />
<Compile Include="Converters\ReverseBooleanToVisibilityConverter.cs" />
<Compile Include="Dialogs\ConfirmationDialog.xaml.cs">
<DependentUpon>ConfirmationDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Dialogs\NewAccountDialog.xaml.cs">
<DependentUpon>NewAccountDialog.xaml</DependentUpon>
</Compile>
@@ -302,6 +293,7 @@
<Compile Include="Selectors\AccountProviderViewModelTemplateSelector.cs" />
<Compile Include="Selectors\AccountReorderTemplateSelector.cs" />
<Compile Include="Selectors\AppThemePreviewTemplateSelector.cs" />
<Compile Include="Selectors\CustomWinoMessageDialogIconSelector.cs" />
<Compile Include="Selectors\FileAttachmentTypeSelector.cs" />
<Compile Include="Selectors\MailItemContainerStyleSelector.cs" />
<Compile Include="Selectors\MailItemDisplayModePreviewTemplateSelector.cs" />
@@ -314,6 +306,9 @@
<Compile Include="Styles\CommandBarItems.xaml.cs">
<DependentUpon>CommandBarItems.xaml</DependentUpon>
</Compile>
<Compile Include="Styles\CustomMessageDialogStyles.xaml.cs">
<DependentUpon>CustomMessageDialogStyles.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Abstract\AboutPageAbstract.cs" />
<Compile Include="Views\Abstract\AccountDetailsPageAbstract.cs" />
<Compile Include="Views\Abstract\AccountManagementPageAbstract.cs" />
@@ -494,26 +489,14 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Dialogs\StoreRatingDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Dialogs\SystemFolderConfigurationDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Dialogs\WinoMessageDialog.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Dialogs\TextInputDialog.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Dialogs\ConfirmationDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Dialogs\NewAccountDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@@ -530,6 +513,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Styles\CustomMessageDialogStyles.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Styles\FontIcons.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
@@ -814,4 +801,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View File

@@ -0,0 +1,9 @@
using System;
namespace Wino.Messaging.Client.Accounts
{
/// <summary>
/// When account's special folder configuration is updated.
/// </summary>
public record AccountFolderConfigurationUpdated(Guid AccountId);
}

View File

@@ -1,7 +0,0 @@
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// Terminates all chromum instances.
/// </summary>
public record KillChromiumRequested;
}

View File

@@ -0,0 +1,7 @@
namespace Wino.Messaging.Client.Navigation
{
/// <summary>
/// Navigates user to Settings -> App Preferences.
/// </summary>
public record NavigateAppPreferencesRequested;
}

View File

@@ -0,0 +1,9 @@
using Wino.Core.Domain.Interfaces;
namespace Wino.Messaging.Server
{
/// <summary>
/// This message is sent to server to kill itself when UWP app is terminating.
/// </summary>
public record TerminateServerRequested : IClientMessage;
}

View File

@@ -110,5 +110,6 @@
<Capabilities>
<Capability Name="internetClient" />
<rescap:Capability Name="runFullTrust" />
<rescap:Capability Name="confirmAppClose" />
</Capabilities>
</Package>

View File

@@ -21,6 +21,7 @@ namespace Wino.Server.Core
nameof(ProtocolAuthorizationCallbackReceived) => App.Current.Services.GetService<ProtocolAuthActivationHandler>(),
nameof(SynchronizationExistenceCheckRequest) => App.Current.Services.GetService<SyncExistenceHandler>(),
nameof(ServerTerminationModeChanged) => App.Current.Services.GetService<ServerTerminationModeHandler>(),
nameof(TerminateServerRequested) => App.Current.Services.GetService<TerminateServerRequestHandler>(),
_ => throw new Exception($"Server handler for {typeName} is not registered."),
};
}
@@ -36,6 +37,7 @@ namespace Wino.Server.Core
serviceCollection.AddTransient<ProtocolAuthActivationHandler>();
serviceCollection.AddTransient<SyncExistenceHandler>();
serviceCollection.AddTransient<ServerTerminationModeHandler>();
serviceCollection.AddTransient<TerminateServerRequestHandler>();
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Serilog;
using Wino.Core.Domain.Models.Server;
using Wino.Messaging.Server;
using Wino.Server.Core;
namespace Wino.Server.MessageHandlers
{
public class TerminateServerRequestHandler : ServerMessageHandler<TerminateServerRequested, bool>
{
public override WinoServerResponse<bool> FailureDefaultResponse(Exception ex) => WinoServerResponse<bool>.CreateErrorResponse(ex.Message);
protected override Task<WinoServerResponse<bool>> HandleAsync(TerminateServerRequested message, CancellationToken cancellationToken = default)
{
// This handler is only doing the logging right now.
// Client will always expect success response.
// Server will be terminated in the server context once the client gets the response.
Log.Information("Terminate server is requested by client. Killing server.");
return Task.FromResult(WinoServerResponse<bool>.CreateSuccessResponse(true));
}
}
}

View File

@@ -2,6 +2,7 @@
using System.Diagnostics;
using System.Text.Json;
using System.Threading.Tasks;
using System.Windows;
using CommunityToolkit.Mvvm.Messaging;
using Serilog;
using Windows.ApplicationModel;
@@ -308,12 +309,28 @@ namespace Wino.Server
case nameof(ServerTerminationModeChanged):
await ExecuteServerMessageSafeAsync(args, JsonSerializer.Deserialize<ServerTerminationModeChanged>(messageJson, _jsonSerializerOptions));
break;
case nameof(TerminateServerRequested):
await ExecuteServerMessageSafeAsync(args, JsonSerializer.Deserialize<TerminateServerRequested>(messageJson, _jsonSerializerOptions));
KillServer();
break;
default:
Debug.WriteLine($"Missing handler for {typeName} in the server. Check ServerContext.cs - HandleServerMessageAsync.");
break;
}
}
private void KillServer()
{
DisposeConnection();
Application.Current.Dispatcher.Invoke(() =>
{
Application.Current.Shutdown();
});
}
/// <summary>
/// Executes ServerMessage coming from the UWP.
/// These requests are awaited and expected to return a response.