Live store update notifications.

This commit is contained in:
Burak Kaan Köse
2026-03-08 11:22:41 +01:00
parent a8f9b2d126
commit c1568d33e6
18 changed files with 341 additions and 37 deletions
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
@@ -67,6 +67,9 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
public bool IsVerticalCalendar => StatePersistenceService.CalendarDisplayType == CalendarDisplayType.Month; public bool IsVerticalCalendar => StatePersistenceService.CalendarDisplayType == CalendarDisplayType.Month;
[ObservableProperty]
private bool isStoreUpdateItemVisible;
// For updating account calendars asynchronously. // For updating account calendars asynchronously.
private SemaphoreSlim _accountCalendarUpdateSemaphoreSlim = new(1); private SemaphoreSlim _accountCalendarUpdateSemaphoreSlim = new(1);
@@ -77,12 +80,14 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
IAccountCalendarStateService accountCalendarStateService, IAccountCalendarStateService accountCalendarStateService,
INavigationService navigationService, INavigationService navigationService,
IMailDialogService dialogService, IMailDialogService dialogService,
IUpdateManager updateManager) IUpdateManager updateManager,
IStoreUpdateService storeUpdateService)
{ {
_accountService = accountService; _accountService = accountService;
_calendarService = calendarService; _calendarService = calendarService;
_dialogService = dialogService; _dialogService = dialogService;
_updateManager = updateManager; _updateManager = updateManager;
_storeUpdateService = storeUpdateService;
AccountCalendarStateService = accountCalendarStateService; AccountCalendarStateService = accountCalendarStateService;
AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested; AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested;
@@ -100,6 +105,7 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
base.OnDispatcherAssigned(); base.OnDispatcherAssigned();
AccountCalendarStateService.Dispatcher = Dispatcher; AccountCalendarStateService.Dispatcher = Dispatcher;
_ = RefreshFooterItemsAsync(false);
} }
private void PrefefencesChanged(object sender, string e) private void PrefefencesChanged(object sender, string e)
@@ -115,10 +121,23 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
} }
} }
private async void PreferencesServiceChanged(object sender, string e)
{
if (e == nameof(IPreferencesService.IsStoreUpdateNotificationsEnabled))
{
await RefreshFooterItemsAsync(false);
}
}
public override async void OnNavigatedTo(NavigationMode mode, object parameters) public override async void OnNavigatedTo(NavigationMode mode, object parameters)
{ {
base.OnNavigatedTo(mode, parameters); base.OnNavigatedTo(mode, parameters);
PreferencesService.PreferenceChanged -= PreferencesServiceChanged;
PreferencesService.PreferenceChanged += PreferencesServiceChanged;
await RefreshFooterItemsAsync(mode == NavigationMode.New);
// Preserve the existing calendar shell frame state when the user switches // Preserve the existing calendar shell frame state when the user switches
// between Mail and Calendar modes. Back/forward restoration should not // between Mail and Calendar modes. Back/forward restoration should not
// force a new CalendarPage navigation, otherwise pages like // force a new CalendarPage navigation, otherwise pages like
@@ -141,6 +160,13 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
TodayClicked(); TodayClicked();
} }
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
{
base.OnNavigatedFrom(mode, parameters);
PreferencesService.PreferenceChanged -= PreferencesServiceChanged;
}
private async Task ShowWhatIsNewIfNeededAsync() private async Task ShowWhatIsNewIfNeededAsync()
{ {
if (!_updateManager.ShouldShowUpdateNotes()) if (!_updateManager.ShouldShowUpdateNotes())
@@ -154,6 +180,22 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
await _dialogService.ShowWhatIsNewDialogAsync(notes); await _dialogService.ShowWhatIsNewDialogAsync(notes);
} }
private async Task RefreshFooterItemsAsync(bool showNotification)
{
await _storeUpdateService.RefreshAvailabilityAsync(showNotification).ConfigureAwait(false);
await ExecuteUIThread(() =>
{
IsStoreUpdateItemVisible = _storeUpdateService.HasAvailableUpdate && PreferencesService.IsStoreUpdateNotificationsEnabled;
});
}
private async Task StartStoreUpdateAsync()
{
await _storeUpdateService.StartUpdateAsync().ConfigureAwait(false);
await RefreshFooterItemsAsync(false).ConfigureAwait(false);
}
private async void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e) private async void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e)
{ {
// When using three-state checkbox, multiple accounts will be selected/unselected at the same time. // When using three-state checkbox, multiple accounts will be selected/unselected at the same time.
@@ -211,40 +253,34 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
private void ForceNavigateCalendarDate() private void ForceNavigateCalendarDate()
{ {
if (SelectedMenuItemIndex == -1) var args = new CalendarPageNavigationArgs()
{ {
var args = new CalendarPageNavigationArgs() NavigationDate = _navigationDate ?? DateTime.Now.Date
{ };
NavigationDate = _navigationDate ?? DateTime.Now.Date
};
// Already on calendar. Just navigate. NavigationService.Navigate(WinoPage.CalendarPage, args);
NavigationService.Navigate(WinoPage.CalendarPage, args); _navigationDate = null;
_navigationDate = null;
}
else
{
SelectedMenuItemIndex = -1;
}
} }
partial void OnSelectedMenuItemIndexChanged(int oldValue, int newValue) partial void OnSelectedMenuItemIndexChanged(int oldValue, int newValue)
{ {
switch (newValue) if (newValue < 0)
return;
if (newValue == 0)
{ {
case -1: NavigationService.Navigate(WinoPage.ManageAccountsPage);
ForceNavigateCalendarDate();
break;
case 0:
NavigationService.Navigate(WinoPage.ManageAccountsPage);
break;
case 1:
NavigationService.Navigate(WinoPage.SettingsPage);
break;
default:
break;
} }
else if (newValue == 1)
{
NavigationService.Navigate(WinoPage.SettingsPage);
}
else if (IsStoreUpdateItemVisible && newValue == 2)
{
_ = StartStoreUpdateAsync();
}
SelectedMenuItemIndex = -1;
} }
[RelayCommand] [RelayCommand]
@@ -301,6 +337,7 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
private readonly ICalendarService _calendarService; private readonly ICalendarService _calendarService;
private readonly IMailDialogService _dialogService; private readonly IMailDialogService _dialogService;
private readonly IUpdateManager _updateManager; private readonly IUpdateManager _updateManager;
private readonly IStoreUpdateService _storeUpdateService;
#region Commands #region Commands
@@ -476,7 +513,7 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
public async void Receive(CalendarEnableStatusChangedMessage message) public async void Receive(CalendarEnableStatusChangedMessage message)
=> await ExecuteUIThread(() => IsCalendarEnabled = message.IsEnabled); => await ExecuteUIThread(() => IsCalendarEnabled = message.IsEnabled);
public void Receive(NavigateManageAccountsRequested message) => SelectedMenuItemIndex = 1; public void Receive(NavigateManageAccountsRequested message) => SelectedMenuItemIndex = 0;
public void Receive(CalendarDisplayTypeChangedMessage message) public void Receive(CalendarDisplayTypeChangedMessage message)
{ {
@@ -539,3 +576,12 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
return (startDate, startDate.AddMinutes(30)); return (startDate, startDate.AddMinutes(30));
} }
} }
+4 -1
View File
@@ -1,4 +1,4 @@
namespace Wino.Core.Domain; namespace Wino.Core.Domain;
public static class Constants public static class Constants
{ {
@@ -21,6 +21,8 @@ public static class Constants
public const string ToastModeKey = nameof(ToastModeKey); public const string ToastModeKey = nameof(ToastModeKey);
public const string ToastModeMail = nameof(ToastModeMail); public const string ToastModeMail = nameof(ToastModeMail);
public const string ToastModeCalendar = nameof(ToastModeCalendar); public const string ToastModeCalendar = nameof(ToastModeCalendar);
public const string ToastStoreUpdateActionKey = nameof(ToastStoreUpdateActionKey);
public const string ToastStoreUpdateActionInstall = nameof(ToastStoreUpdateActionInstall);
public const string ClientLogFile = "Client_.log"; public const string ClientLogFile = "Client_.log";
public const string ServerLogFile = "Server_.log"; public const string ServerLogFile = "Server_.log";
public const string LogArchiveFileName = "WinoLogs.zip"; public const string LogArchiveFileName = "WinoLogs.zip";
@@ -28,3 +30,4 @@ public static class Constants
public const string WinoMailIdentiifer = nameof(WinoMailIdentiifer); public const string WinoMailIdentiifer = nameof(WinoMailIdentiifer);
public const string WinoCalendarIdentifier = nameof(WinoCalendarIdentifier); public const string WinoCalendarIdentifier = nameof(WinoCalendarIdentifier);
} }
@@ -36,8 +36,14 @@ public interface INotificationBuilder
/// </summary> /// </summary>
void CreateWebView2RuntimeMissingNotification(); void CreateWebView2RuntimeMissingNotification();
/// <summary>
/// Shows a notification when a Microsoft Store update is available.
/// </summary>
void CreateStoreUpdateNotification();
/// <summary> /// <summary>
/// Creates a calendar reminder toast for the specified calendar item. /// Creates a calendar reminder toast for the specified calendar item.
/// </summary> /// </summary>
Task CreateCalendarReminderNotificationAsync(CalendarItem calendarItem, long reminderDurationInSeconds); Task CreateCalendarReminderNotificationAsync(CalendarItem calendarItem, long reminderDurationInSeconds);
} }
@@ -1,4 +1,4 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Calendar; using Wino.Core.Domain.Models.Calendar;
@@ -57,6 +57,11 @@ public interface IPreferencesService : INotifyPropertyChanged
/// </summary> /// </summary>
WinoApplicationMode DefaultApplicationMode { get; set; } WinoApplicationMode DefaultApplicationMode { get; set; }
/// <summary>
/// Setting: Whether Microsoft Store update notifications should be shown.
/// </summary>
bool IsStoreUpdateNotificationsEnabled { get; set; }
#endregion #endregion
#region Mail #region Mail
@@ -241,3 +246,4 @@ public interface IPreferencesService : INotifyPropertyChanged
#endregion #endregion
} }
@@ -0,0 +1,12 @@
using System.Threading.Tasks;
namespace Wino.Core.Domain.Interfaces;
public interface IStoreUpdateService
{
bool HasAvailableUpdate { get; }
Task<bool> RefreshAvailabilityAsync(bool showNotification = false);
Task<bool> StartUpdateAsync();
}
@@ -0,0 +1,3 @@
namespace Wino.Core.Domain.MenuItems;
public class StoreUpdateMenuItem : MenuItemBase { }
@@ -599,6 +599,7 @@
"MenuNewMail": "New Mail", "MenuNewMail": "New Mail",
"MenuRate": "Rate Wino", "MenuRate": "Rate Wino",
"MenuSettings": "Settings", "MenuSettings": "Settings",
"MenuUpdateAvailable": "Update Available",
"MergedAccountCommonFolderArchive": "Archive", "MergedAccountCommonFolderArchive": "Archive",
"MergedAccountCommonFolderDraft": "Draft", "MergedAccountCommonFolderDraft": "Draft",
"MergedAccountCommonFolderInbox": "Inbox", "MergedAccountCommonFolderInbox": "Inbox",
@@ -622,6 +623,8 @@
"Notifications_MultipleNotificationsTitle": "New Mail", "Notifications_MultipleNotificationsTitle": "New Mail",
"Notifications_WinoUpdatedMessage": "Checkout new version {0}", "Notifications_WinoUpdatedMessage": "Checkout new version {0}",
"Notifications_WinoUpdatedTitle": "Wino Mail has been updated.", "Notifications_WinoUpdatedTitle": "Wino Mail has been updated.",
"Notifications_StoreUpdateAvailableTitle": "Update available",
"Notifications_StoreUpdateAvailableMessage": "A newer version of Wino Mail is ready to install from Microsoft Store.",
"OnlineSearchFailed_Message": "Failed to perform search\n{0}\n\nListing offline mails.", "OnlineSearchFailed_Message": "Failed to perform search\n{0}\n\nListing offline mails.",
"OnlineSearchTry_Line1": "Can't find what you are looking for?", "OnlineSearchTry_Line1": "Can't find what you are looking for?",
"OnlineSearchTry_Line2": "Try online search.", "OnlineSearchTry_Line2": "Try online search.",
@@ -700,6 +703,8 @@
"SettingsAppPreferences_StartupBehavior_FatalError": "Fatal error occurred while changing the startup mode for Wino Mail.", "SettingsAppPreferences_StartupBehavior_FatalError": "Fatal error occurred while changing the startup mode for Wino Mail.",
"SettingsAppPreferences_StartupBehavior_Title": "Start minimized on Windows startup", "SettingsAppPreferences_StartupBehavior_Title": "Start minimized on Windows startup",
"SettingsAppPreferences_Title": "App Preferences", "SettingsAppPreferences_Title": "App Preferences",
"SettingsAppPreferences_StoreUpdateNotifications_Title": "Store update notifications",
"SettingsAppPreferences_StoreUpdateNotifications_Description": "Show notifications and footer actions when a Microsoft Store update is available.",
"SettingsAutoSelectNextItem_Description": "Select the next item after you delete or move a mail.", "SettingsAutoSelectNextItem_Description": "Select the next item after you delete or move a mail.",
"SettingsAutoSelectNextItem_Title": "Auto select next item", "SettingsAutoSelectNextItem_Title": "Auto select next item",
"SettingsAvailableThemes_Description": "Select a theme from Wino's own collection for your taste or apply your own themes.", "SettingsAvailableThemes_Description": "Select a theme from Wino's own collection for your taste or apply your own themes.",
@@ -1117,3 +1122,5 @@
"AccountSetup_TryAgainButton": "Try Again", "AccountSetup_TryAgainButton": "Try Again",
"ImapCalDavSettings_AutoDiscoveryFailed": "Auto-discovery failed. Please enter settings manually in the Advanced tab." "ImapCalDavSettings_AutoDiscoveryFailed": "Auto-discovery failed. Please enter settings manually in the Advanced tab."
} }
+47 -5
View File
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@@ -55,6 +55,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
private readonly SettingsItem SettingsItem = new SettingsItem(); private readonly SettingsItem SettingsItem = new SettingsItem();
private readonly ManageAccountsMenuItem ManageAccountsMenuItem = new ManageAccountsMenuItem(); private readonly ManageAccountsMenuItem ManageAccountsMenuItem = new ManageAccountsMenuItem();
private readonly ContactsMenuItem ContactsMenuItem = new ContactsMenuItem(); private readonly ContactsMenuItem ContactsMenuItem = new ContactsMenuItem();
private readonly StoreUpdateMenuItem StoreUpdateMenuItem = new StoreUpdateMenuItem();
public IMenuItem CreateMailMenuItem = new NewMailMenuItem(); public IMenuItem CreateMailMenuItem = new NewMailMenuItem();
@@ -79,6 +80,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
private readonly IMimeFileService _mimeFileService; private readonly IMimeFileService _mimeFileService;
private readonly IWebView2RuntimeValidatorService _webView2RuntimeValidatorService; private readonly IWebView2RuntimeValidatorService _webView2RuntimeValidatorService;
private readonly IUpdateManager _updateManager; private readonly IUpdateManager _updateManager;
private readonly IStoreUpdateService _storeUpdateService;
private readonly INativeAppService _nativeAppService; private readonly INativeAppService _nativeAppService;
private readonly IMailService _mailService; private readonly IMailService _mailService;
@@ -102,7 +104,8 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
IConfigurationService configurationService, IConfigurationService configurationService,
IStartupBehaviorService startupBehaviorService, IStartupBehaviorService startupBehaviorService,
IWebView2RuntimeValidatorService webView2RuntimeValidatorService, IWebView2RuntimeValidatorService webView2RuntimeValidatorService,
IUpdateManager updateManager) IUpdateManager updateManager,
IStoreUpdateService storeUpdateService)
{ {
StatePersistenceService = statePersistanceService; StatePersistenceService = statePersistanceService;
@@ -124,6 +127,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
_winoRequestDelegator = winoRequestDelegator; _winoRequestDelegator = winoRequestDelegator;
_webView2RuntimeValidatorService = webView2RuntimeValidatorService; _webView2RuntimeValidatorService = webView2RuntimeValidatorService;
_updateManager = updateManager; _updateManager = updateManager;
_storeUpdateService = storeUpdateService;
} }
protected override void OnDispatcherAssigned() protected override void OnDispatcherAssigned()
@@ -142,8 +146,10 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
return _contextMenuItemService.GetFolderContextMenuActions(folder); return _contextMenuItemService.GetFolderContextMenuActions(folder);
} }
private async Task CreateFooterItemsAsync() private async Task CreateFooterItemsAsync(bool showNotification = false)
{ {
await _storeUpdateService.RefreshAvailabilityAsync(showNotification).ConfigureAwait(false);
await ExecuteUIThread(() => await ExecuteUIThread(() =>
{ {
// TODO: Selected footer item container still remains selected after re-creation. // TODO: Selected footer item container still remains selected after re-creation.
@@ -159,6 +165,12 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
FooterItems.Add(ContactsMenuItem); FooterItems.Add(ContactsMenuItem);
FooterItems.Add(ManageAccountsMenuItem); FooterItems.Add(ManageAccountsMenuItem);
if (_storeUpdateService.HasAvailableUpdate && PreferencesService.IsStoreUpdateNotificationsEnabled)
{
FooterItems.Add(StoreUpdateMenuItem);
}
FooterItems.Add(SettingsItem); FooterItems.Add(SettingsItem);
}); });
} }
@@ -223,10 +235,21 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
} }
} }
private async void PreferencesServiceChanged(object sender, string e)
{
if (e == nameof(IPreferencesService.IsStoreUpdateNotificationsEnabled))
{
await CreateFooterItemsAsync();
}
}
public override async void OnNavigatedTo(NavigationMode mode, object parameters) public override async void OnNavigatedTo(NavigationMode mode, object parameters)
{ {
base.OnNavigatedTo(mode, parameters); base.OnNavigatedTo(mode, parameters);
PreferencesService.PreferenceChanged -= PreferencesServiceChanged;
PreferencesService.PreferenceChanged += PreferencesServiceChanged;
if (mode == NavigationMode.Back) if (mode == NavigationMode.Back)
{ {
// Preserve current mail/folder selection and active rendering page when // Preserve current mail/folder selection and active rendering page when
@@ -243,7 +266,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
return; return;
} }
await CreateFooterItemsAsync(); await CreateFooterItemsAsync(true);
await RecreateMenuItemsAsync(); await RecreateMenuItemsAsync();
await ProcessLaunchOptionsAsync(); await ProcessLaunchOptionsAsync();
@@ -268,6 +291,13 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
} }
} }
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
{
base.OnNavigatedFrom(mode, parameters);
PreferencesService.PreferenceChanged -= PreferencesServiceChanged;
}
private async Task ShowWhatIsNewIfNeededAsync() private async Task ShowWhatIsNewIfNeededAsync()
{ {
if (!_updateManager.ShouldShowUpdateNotes()) if (!_updateManager.ShouldShowUpdateNotes())
@@ -638,6 +668,11 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
// Don't navigate to merged account if it's already selected. Preserve user's already selected folder. // Don't navigate to merged account if it's already selected. Preserve user's already selected folder.
await ChangeLoadedAccountAsync(clickedMergedAccountMenuItem, true); await ChangeLoadedAccountAsync(clickedMergedAccountMenuItem, true);
} }
else if (clickedMenuItem is StoreUpdateMenuItem)
{
await _storeUpdateService.StartUpdateAsync().ConfigureAwait(false);
await CreateFooterItemsAsync().ConfigureAwait(false);
}
else if (clickedMenuItem is SettingsItem) else if (clickedMenuItem is SettingsItem)
{ {
NavigationService.Navigate(WinoPage.SettingsPage, parameter, NavigationReferenceFrame.InnerShellFrame, NavigationTransitionType.None); NavigationService.Navigate(WinoPage.SettingsPage, parameter, NavigationReferenceFrame.InnerShellFrame, NavigationTransitionType.None);
@@ -1051,7 +1086,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
public async void Receive(LanguageChanged message) public async void Receive(LanguageChanged message)
{ {
await CreateFooterItemsAsync(); await CreateFooterItemsAsync(true);
await RecreateMenuItemsAsync(); await RecreateMenuItemsAsync();
await RestoreSelectedAccountAfterMenuRefreshAsync(false); await RestoreSelectedAccountAfterMenuRefreshAsync(false);
} }
@@ -1245,3 +1280,10 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
}); });
} }
} }
+24
View File
@@ -296,6 +296,13 @@ public partial class App : WinoApplication,
{ {
var toastArguments = ToastArguments.Parse(toastArgs.Argument); var toastArguments = ToastArguments.Parse(toastArgs.Argument);
if (toastArguments.TryGetValue(Constants.ToastStoreUpdateActionKey, out string storeUpdateAction) &&
storeUpdateAction == Constants.ToastStoreUpdateActionInstall)
{
await HandleStoreUpdateToastAsync();
return;
}
// Check calendar reminder toast activation first. // Check calendar reminder toast activation first.
if (toastArguments.TryGetValue(Constants.ToastCalendarActionKey, out string calendarAction) && if (toastArguments.TryGetValue(Constants.ToastCalendarActionKey, out string calendarAction) &&
toastArguments.TryGetValue(Constants.ToastCalendarItemIdKey, out string calendarItemIdString) && toastArguments.TryGetValue(Constants.ToastCalendarItemIdKey, out string calendarItemIdString) &&
@@ -333,6 +340,21 @@ public partial class App : WinoApplication,
} }
} }
private async Task HandleStoreUpdateToastAsync()
{
if (!IsAppRunning())
{
await CreateAndActivateWindow(null!);
}
else
{
EnsureMainWindowVisibleAndForeground();
}
var storeUpdateService = Services.GetRequiredService<IStoreUpdateService>();
await storeUpdateService.StartUpdateAsync();
}
private async Task HandleCalendarToastNavigationAsync(Guid calendarItemId) private async Task HandleCalendarToastNavigationAsync(Guid calendarItemId)
{ {
var calendarService = Services.GetRequiredService<ICalendarService>(); var calendarService = Services.GetRequiredService<ICalendarService>();
@@ -997,3 +1019,5 @@ public partial class App : WinoApplication,
return null; return null;
} }
} }
+3
View File
@@ -31,6 +31,7 @@ public static class CoreUWPContainerSetup
services.AddTransient<IConfigurationService, ConfigurationService>(); services.AddTransient<IConfigurationService, ConfigurationService>();
services.AddTransient<IFileService, FileService>(); services.AddTransient<IFileService, FileService>();
services.AddTransient<IStoreRatingService, StoreRatingService>(); services.AddTransient<IStoreRatingService, StoreRatingService>();
services.AddSingleton<IStoreUpdateService, StoreUpdateService>();
services.AddTransient<IKeyPressService, KeyPressService>(); services.AddTransient<IKeyPressService, KeyPressService>();
services.AddTransient<IWebView2RuntimeValidatorService, WebView2RuntimeValidatorService>(); services.AddTransient<IWebView2RuntimeValidatorService, WebView2RuntimeValidatorService>();
services.AddTransient<INotificationBuilder, NotificationBuilder>(); services.AddTransient<INotificationBuilder, NotificationBuilder>();
@@ -53,3 +54,5 @@ public static class CoreUWPContainerSetup
services.AddTransient(typeof(KeyboardShortcutsPageViewModel)); services.AddTransient(typeof(KeyboardShortcutsPageViewModel));
} }
} }
+4 -1
View File
@@ -387,7 +387,8 @@
NewMailTemplate="{StaticResource CreateNewMailTemplate}" NewMailTemplate="{StaticResource CreateNewMailTemplate}"
RatingItemTemplate="{StaticResource RatingItemTemplate}" RatingItemTemplate="{StaticResource RatingItemTemplate}"
SeperatorTemplate="{StaticResource SeperatorTemplate}" SeperatorTemplate="{StaticResource SeperatorTemplate}"
SettingsItemTemplate="{StaticResource SettingsItemTemplate}" /> SettingsItemTemplate="{StaticResource SettingsItemTemplate}"
StoreUpdateItemTemplate="{StaticResource StoreUpdateItemTemplate}" />
</Page.Resources> </Page.Resources>
<Grid <Grid
@@ -477,3 +478,5 @@
</Grid> </Grid>
</abstract:MailAppShellAbstract> </abstract:MailAppShellAbstract>
@@ -15,6 +15,7 @@ public partial class NavigationMenuTemplateSelector : DataTemplateSelector
public DataTemplate MergedAccountMoreExpansionItemTemplate { get; set; } = null!; public DataTemplate MergedAccountMoreExpansionItemTemplate { get; set; } = null!;
public DataTemplate FolderMenuTemplate { get; set; } = null!; public DataTemplate FolderMenuTemplate { get; set; } = null!;
public DataTemplate SettingsItemTemplate { get; set; } = null!; public DataTemplate SettingsItemTemplate { get; set; } = null!;
public DataTemplate StoreUpdateItemTemplate { get; set; } = null!;
public DataTemplate MoreItemsFolderTemplate { get; set; } = null!; public DataTemplate MoreItemsFolderTemplate { get; set; } = null!;
public DataTemplate RatingItemTemplate { get; set; } = null!; public DataTemplate RatingItemTemplate { get; set; } = null!;
public DataTemplate CreateNewFolderTemplate { get; set; } = null!; public DataTemplate CreateNewFolderTemplate { get; set; } = null!;
@@ -32,6 +33,8 @@ public partial class NavigationMenuTemplateSelector : DataTemplateSelector
return ContactsMenuItemTemplate; return ContactsMenuItemTemplate;
else if (item is SettingsItem) else if (item is SettingsItem)
return SettingsItemTemplate; return SettingsItemTemplate;
else if (item is StoreUpdateMenuItem)
return StoreUpdateItemTemplate;
else if (item is SeperatorItem) else if (item is SeperatorItem)
return SeperatorTemplate; return SeperatorTemplate;
else if (item is AccountMenuItem) else if (item is AccountMenuItem)
@@ -55,3 +58,5 @@ public partial class NavigationMenuTemplateSelector : DataTemplateSelector
return MenuItemTemplate; return MenuItemTemplate;
} }
} }
@@ -293,6 +293,19 @@ public class NotificationBuilder : INotificationBuilder
ShowToast(builder); ShowToast(builder);
} }
public void CreateStoreUpdateNotification()
{
var builder = new ToastContentBuilder();
builder.SetToastScenario(ToastScenario.Default);
builder.AddText(Translator.Notifications_StoreUpdateAvailableTitle);
builder.AddText(Translator.Notifications_StoreUpdateAvailableMessage);
builder.AddArgument(Constants.ToastStoreUpdateActionKey, Constants.ToastStoreUpdateActionInstall);
builder.AddButton(GetDismissButton());
ShowToast(builder, "store-update-available");
}
public Task CreateCalendarReminderNotificationAsync(CalendarItem calendarItem, long reminderDurationInSeconds) public Task CreateCalendarReminderNotificationAsync(CalendarItem calendarItem, long reminderDurationInSeconds)
{ {
if (calendarItem == null) if (calendarItem == null)
@@ -437,3 +450,4 @@ public class NotificationBuilder : INotificationBuilder
return SupportedIconScales.OrderBy(s => Math.Abs(s - requestedScale)).First(); return SupportedIconScales.OrderBy(s => Math.Abs(s - requestedScale)).First();
} }
} }
@@ -308,6 +308,11 @@ public class PreferencesService(IConfigurationService configurationService) : Ob
set => SetPropertyAndSave(nameof(EmailSyncIntervalMinutes), value); set => SetPropertyAndSave(nameof(EmailSyncIntervalMinutes), value);
} }
public bool IsStoreUpdateNotificationsEnabled
{
get => _configurationService.Get(nameof(IsStoreUpdateNotificationsEnabled), true);
set => SetPropertyAndSave(nameof(IsStoreUpdateNotificationsEnabled), value);
}
public WinoApplicationMode DefaultApplicationMode public WinoApplicationMode DefaultApplicationMode
{ {
get get
@@ -357,3 +362,5 @@ public class PreferencesService(IConfigurationService configurationService) : Ob
return daysOfWeek; return daysOfWeek;
} }
} }
@@ -0,0 +1,94 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Windows.Services.Store;
using Wino.Core.Domain.Interfaces;
namespace Wino.Mail.WinUI.Services;
public class StoreUpdateService : IStoreUpdateService
{
private const string NotificationShownKeyFormat = "StoreUpdateNotificationShown_{0}";
private readonly IConfigurationService _configurationService;
private readonly INotificationBuilder _notificationBuilder;
private readonly IPreferencesService _preferencesService;
private readonly INativeAppService _nativeAppService;
private readonly SemaphoreSlim _refreshSemaphore = new(1, 1);
private readonly StoreContext _storeContext = StoreContext.GetDefault();
public bool HasAvailableUpdate { get; private set; }
public StoreUpdateService(IConfigurationService configurationService,
INotificationBuilder notificationBuilder,
IPreferencesService preferencesService,
INativeAppService nativeAppService)
{
_configurationService = configurationService;
_notificationBuilder = notificationBuilder;
_preferencesService = preferencesService;
_nativeAppService = nativeAppService;
}
public async Task<bool> RefreshAvailabilityAsync(bool showNotification = false)
{
await _refreshSemaphore.WaitAsync().ConfigureAwait(false);
try
{
var updates = await _storeContext.GetAppAndOptionalStorePackageUpdatesAsync();
HasAvailableUpdate = updates?.Count > 0;
if (showNotification &&
HasAvailableUpdate &&
_preferencesService.IsStoreUpdateNotificationsEnabled &&
!HasShownNotificationForCurrentVersion())
{
_notificationBuilder.CreateStoreUpdateNotification();
MarkNotificationShownForCurrentVersion();
}
return HasAvailableUpdate;
}
catch
{
HasAvailableUpdate = false;
return false;
}
finally
{
_refreshSemaphore.Release();
}
}
public async Task<bool> StartUpdateAsync()
{
try
{
var updates = await _storeContext.GetAppAndOptionalStorePackageUpdatesAsync();
if (updates == null || updates.Count == 0)
{
HasAvailableUpdate = false;
return false;
}
await _storeContext.RequestDownloadAndInstallStorePackageUpdatesAsync(updates);
await RefreshAvailabilityAsync(false).ConfigureAwait(false);
return true;
}
catch
{
return false;
}
}
private bool HasShownNotificationForCurrentVersion()
=> _configurationService.Get(GetNotificationShownKey(), false);
private void MarkNotificationShownForCurrentVersion()
=> _configurationService.Set(GetNotificationShownKey(), true);
private string GetNotificationShownKey()
=> string.Format(NotificationShownKeyFormat, _nativeAppService.GetFullAppVersion().Replace(".", "_"));
}
+12
View File
@@ -62,6 +62,14 @@
</coreControls:WinoNavigationViewItem.Icon> </coreControls:WinoNavigationViewItem.Icon>
</coreControls:WinoNavigationViewItem> </coreControls:WinoNavigationViewItem>
</DataTemplate> </DataTemplate>
<!-- Store Update Item -->
<DataTemplate x:Key="StoreUpdateItemTemplate" x:DataType="menu:StoreUpdateMenuItem">
<coreControls:WinoNavigationViewItem Content="{x:Bind domain:Translator.MenuUpdateAvailable}" DataContext="{x:Bind}">
<muxc:NavigationViewItem.Icon>
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE896;" />
</muxc:NavigationViewItem.Icon>
</coreControls:WinoNavigationViewItem>
</DataTemplate>
<!-- Rating Item --> <!-- Rating Item -->
<DataTemplate x:Key="RatingItemTemplate" x:DataType="menu:RateMenuItem"> <DataTemplate x:Key="RatingItemTemplate" x:DataType="menu:RateMenuItem">
@@ -271,3 +279,7 @@
<!--#endregion--> <!--#endregion-->
</ResourceDictionary> </ResourceDictionary>
@@ -304,6 +304,12 @@
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.MenuSettings}" /> <TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.MenuSettings}" />
</StackPanel> </StackPanel>
</ListViewItem> </ListViewItem>
<ListViewItem Visibility="{x:Bind ViewModel.IsStoreUpdateItemVisible, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="12">
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE896;" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.MenuUpdateAvailable}" />
</StackPanel>
</ListViewItem>
</ListView.Items> </ListView.Items>
</ListView> </ListView>
</Grid> </Grid>
@@ -398,3 +404,5 @@
</abstract:CalendarAppShellAbstract> </abstract:CalendarAppShellAbstract>
@@ -47,6 +47,12 @@
<PathIcon 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.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 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 9.375 10 C 9.205729 10 9.059244 9.938151 8.935547 9.814453 C 8.811849 9.690756 8.75 9.544271 8.75 9.375 L 8.75 4.375 C 8.75 4.20573 8.811849 4.059246 8.935547 3.935547 C 9.059244 3.81185 9.205729 3.75 9.375 3.75 C 9.544271 3.75 9.690755 3.81185 9.814453 3.935547 C 9.93815 4.059246 10 4.20573 10 4.375 L 10 8.75 L 13.125 8.75 C 13.294271 8.75 13.440755 8.81185 13.564453 8.935547 C 13.68815 9.059245 13.75 9.205729 13.75 9.375 C 13.75 9.544271 13.68815 9.690756 13.564453 9.814453 C 13.440755 9.938151 13.294271 10 13.125 10 Z " /> <PathIcon 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.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 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 9.375 10 C 9.205729 10 9.059244 9.938151 8.935547 9.814453 C 8.811849 9.690756 8.75 9.544271 8.75 9.375 L 8.75 4.375 C 8.75 4.20573 8.811849 4.059246 8.935547 3.935547 C 9.059244 3.81185 9.205729 3.75 9.375 3.75 C 9.544271 3.75 9.690755 3.81185 9.814453 3.935547 C 9.93815 4.059246 10 4.20573 10 4.375 L 10 8.75 L 13.125 8.75 C 13.294271 8.75 13.440755 8.81185 13.564453 8.935547 C 13.68815 9.059245 13.75 9.205729 13.75 9.375 C 13.75 9.544271 13.68815 9.690756 13.564453 9.814453 C 13.440755 9.938151 13.294271 10 13.125 10 Z " />
</controls:SettingsCard.HeaderIcon> </controls:SettingsCard.HeaderIcon>
</controls:SettingsCard> </controls:SettingsCard>
<controls:SettingsCard Description="{x:Bind domain:Translator.SettingsAppPreferences_StoreUpdateNotifications_Description}" Header="{x:Bind domain:Translator.SettingsAppPreferences_StoreUpdateNotifications_Title}">
<ToggleSwitch IsOn="{x:Bind ViewModel.PreferencesService.IsStoreUpdateNotificationsEnabled, Mode=TwoWay}" />
<controls:SettingsCard.HeaderIcon>
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE896;" />
</controls:SettingsCard.HeaderIcon>
</controls:SettingsCard>
</StackPanel> </StackPanel>
<VisualStateManager.VisualStateGroups> <VisualStateManager.VisualStateGroups>
@@ -64,3 +70,6 @@
</VisualStateManager.VisualStateGroups> </VisualStateManager.VisualStateGroups>
</ScrollViewer> </ScrollViewer>
</abstract:AppPreferencesPageAbstract> </abstract:AppPreferencesPageAbstract>