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.Globalization;
using System.Linq;
@@ -67,6 +67,9 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
public bool IsVerticalCalendar => StatePersistenceService.CalendarDisplayType == CalendarDisplayType.Month;
[ObservableProperty]
private bool isStoreUpdateItemVisible;
// For updating account calendars asynchronously.
private SemaphoreSlim _accountCalendarUpdateSemaphoreSlim = new(1);
@@ -77,12 +80,14 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
IAccountCalendarStateService accountCalendarStateService,
INavigationService navigationService,
IMailDialogService dialogService,
IUpdateManager updateManager)
IUpdateManager updateManager,
IStoreUpdateService storeUpdateService)
{
_accountService = accountService;
_calendarService = calendarService;
_dialogService = dialogService;
_updateManager = updateManager;
_storeUpdateService = storeUpdateService;
AccountCalendarStateService = accountCalendarStateService;
AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested;
@@ -100,6 +105,7 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
base.OnDispatcherAssigned();
AccountCalendarStateService.Dispatcher = Dispatcher;
_ = RefreshFooterItemsAsync(false);
}
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)
{
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
// between Mail and Calendar modes. Back/forward restoration should not
// force a new CalendarPage navigation, otherwise pages like
@@ -141,6 +160,13 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
TodayClicked();
}
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
{
base.OnNavigatedFrom(mode, parameters);
PreferencesService.PreferenceChanged -= PreferencesServiceChanged;
}
private async Task ShowWhatIsNewIfNeededAsync()
{
if (!_updateManager.ShouldShowUpdateNotes())
@@ -154,6 +180,22 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
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)
{
// 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()
{
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);
_navigationDate = null;
}
else
{
SelectedMenuItemIndex = -1;
}
NavigationService.Navigate(WinoPage.CalendarPage, args);
_navigationDate = null;
}
partial void OnSelectedMenuItemIndexChanged(int oldValue, int newValue)
{
switch (newValue)
if (newValue < 0)
return;
if (newValue == 0)
{
case -1:
ForceNavigateCalendarDate();
break;
case 0:
NavigationService.Navigate(WinoPage.ManageAccountsPage);
break;
case 1:
NavigationService.Navigate(WinoPage.SettingsPage);
break;
default:
break;
NavigationService.Navigate(WinoPage.ManageAccountsPage);
}
else if (newValue == 1)
{
NavigationService.Navigate(WinoPage.SettingsPage);
}
else if (IsStoreUpdateItemVisible && newValue == 2)
{
_ = StartStoreUpdateAsync();
}
SelectedMenuItemIndex = -1;
}
[RelayCommand]
@@ -301,6 +337,7 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
private readonly ICalendarService _calendarService;
private readonly IMailDialogService _dialogService;
private readonly IUpdateManager _updateManager;
private readonly IStoreUpdateService _storeUpdateService;
#region Commands
@@ -476,7 +513,7 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
public async void Receive(CalendarEnableStatusChangedMessage message)
=> await ExecuteUIThread(() => IsCalendarEnabled = message.IsEnabled);
public void Receive(NavigateManageAccountsRequested message) => SelectedMenuItemIndex = 1;
public void Receive(NavigateManageAccountsRequested message) => SelectedMenuItemIndex = 0;
public void Receive(CalendarDisplayTypeChangedMessage message)
{
@@ -539,3 +576,12 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
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
{
@@ -21,6 +21,8 @@ public static class Constants
public const string ToastModeKey = nameof(ToastModeKey);
public const string ToastModeMail = nameof(ToastModeMail);
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 ServerLogFile = "Server_.log";
public const string LogArchiveFileName = "WinoLogs.zip";
@@ -28,3 +30,4 @@ public static class Constants
public const string WinoMailIdentiifer = nameof(WinoMailIdentiifer);
public const string WinoCalendarIdentifier = nameof(WinoCalendarIdentifier);
}
@@ -36,8 +36,14 @@ public interface INotificationBuilder
/// </summary>
void CreateWebView2RuntimeMissingNotification();
/// <summary>
/// Shows a notification when a Microsoft Store update is available.
/// </summary>
void CreateStoreUpdateNotification();
/// <summary>
/// Creates a calendar reminder toast for the specified calendar item.
/// </summary>
Task CreateCalendarReminderNotificationAsync(CalendarItem calendarItem, long reminderDurationInSeconds);
}
@@ -1,4 +1,4 @@
using System;
using System;
using System.ComponentModel;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Calendar;
@@ -57,6 +57,11 @@ public interface IPreferencesService : INotifyPropertyChanged
/// </summary>
WinoApplicationMode DefaultApplicationMode { get; set; }
/// <summary>
/// Setting: Whether Microsoft Store update notifications should be shown.
/// </summary>
bool IsStoreUpdateNotificationsEnabled { get; set; }
#endregion
#region Mail
@@ -241,3 +246,4 @@ public interface IPreferencesService : INotifyPropertyChanged
#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",
"MenuRate": "Rate Wino",
"MenuSettings": "Settings",
"MenuUpdateAvailable": "Update Available",
"MergedAccountCommonFolderArchive": "Archive",
"MergedAccountCommonFolderDraft": "Draft",
"MergedAccountCommonFolderInbox": "Inbox",
@@ -622,6 +623,8 @@
"Notifications_MultipleNotificationsTitle": "New Mail",
"Notifications_WinoUpdatedMessage": "Checkout new version {0}",
"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.",
"OnlineSearchTry_Line1": "Can't find what you are looking for?",
"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_Title": "Start minimized on Windows startup",
"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_Title": "Auto select next item",
"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",
"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.Diagnostics;
using System.Linq;
@@ -55,6 +55,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
private readonly SettingsItem SettingsItem = new SettingsItem();
private readonly ManageAccountsMenuItem ManageAccountsMenuItem = new ManageAccountsMenuItem();
private readonly ContactsMenuItem ContactsMenuItem = new ContactsMenuItem();
private readonly StoreUpdateMenuItem StoreUpdateMenuItem = new StoreUpdateMenuItem();
public IMenuItem CreateMailMenuItem = new NewMailMenuItem();
@@ -79,6 +80,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
private readonly IMimeFileService _mimeFileService;
private readonly IWebView2RuntimeValidatorService _webView2RuntimeValidatorService;
private readonly IUpdateManager _updateManager;
private readonly IStoreUpdateService _storeUpdateService;
private readonly INativeAppService _nativeAppService;
private readonly IMailService _mailService;
@@ -102,7 +104,8 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
IConfigurationService configurationService,
IStartupBehaviorService startupBehaviorService,
IWebView2RuntimeValidatorService webView2RuntimeValidatorService,
IUpdateManager updateManager)
IUpdateManager updateManager,
IStoreUpdateService storeUpdateService)
{
StatePersistenceService = statePersistanceService;
@@ -124,6 +127,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
_winoRequestDelegator = winoRequestDelegator;
_webView2RuntimeValidatorService = webView2RuntimeValidatorService;
_updateManager = updateManager;
_storeUpdateService = storeUpdateService;
}
protected override void OnDispatcherAssigned()
@@ -142,8 +146,10 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
return _contextMenuItemService.GetFolderContextMenuActions(folder);
}
private async Task CreateFooterItemsAsync()
private async Task CreateFooterItemsAsync(bool showNotification = false)
{
await _storeUpdateService.RefreshAvailabilityAsync(showNotification).ConfigureAwait(false);
await ExecuteUIThread(() =>
{
// 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(ManageAccountsMenuItem);
if (_storeUpdateService.HasAvailableUpdate && PreferencesService.IsStoreUpdateNotificationsEnabled)
{
FooterItems.Add(StoreUpdateMenuItem);
}
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)
{
base.OnNavigatedTo(mode, parameters);
PreferencesService.PreferenceChanged -= PreferencesServiceChanged;
PreferencesService.PreferenceChanged += PreferencesServiceChanged;
if (mode == NavigationMode.Back)
{
// Preserve current mail/folder selection and active rendering page when
@@ -243,7 +266,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
return;
}
await CreateFooterItemsAsync();
await CreateFooterItemsAsync(true);
await RecreateMenuItemsAsync();
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()
{
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.
await ChangeLoadedAccountAsync(clickedMergedAccountMenuItem, true);
}
else if (clickedMenuItem is StoreUpdateMenuItem)
{
await _storeUpdateService.StartUpdateAsync().ConfigureAwait(false);
await CreateFooterItemsAsync().ConfigureAwait(false);
}
else if (clickedMenuItem is SettingsItem)
{
NavigationService.Navigate(WinoPage.SettingsPage, parameter, NavigationReferenceFrame.InnerShellFrame, NavigationTransitionType.None);
@@ -1051,7 +1086,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
public async void Receive(LanguageChanged message)
{
await CreateFooterItemsAsync();
await CreateFooterItemsAsync(true);
await RecreateMenuItemsAsync();
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);
if (toastArguments.TryGetValue(Constants.ToastStoreUpdateActionKey, out string storeUpdateAction) &&
storeUpdateAction == Constants.ToastStoreUpdateActionInstall)
{
await HandleStoreUpdateToastAsync();
return;
}
// Check calendar reminder toast activation first.
if (toastArguments.TryGetValue(Constants.ToastCalendarActionKey, out string calendarAction) &&
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)
{
var calendarService = Services.GetRequiredService<ICalendarService>();
@@ -997,3 +1019,5 @@ public partial class App : WinoApplication,
return null;
}
}
+3
View File
@@ -31,6 +31,7 @@ public static class CoreUWPContainerSetup
services.AddTransient<IConfigurationService, ConfigurationService>();
services.AddTransient<IFileService, FileService>();
services.AddTransient<IStoreRatingService, StoreRatingService>();
services.AddSingleton<IStoreUpdateService, StoreUpdateService>();
services.AddTransient<IKeyPressService, KeyPressService>();
services.AddTransient<IWebView2RuntimeValidatorService, WebView2RuntimeValidatorService>();
services.AddTransient<INotificationBuilder, NotificationBuilder>();
@@ -53,3 +54,5 @@ public static class CoreUWPContainerSetup
services.AddTransient(typeof(KeyboardShortcutsPageViewModel));
}
}
+4 -1
View File
@@ -387,7 +387,8 @@
NewMailTemplate="{StaticResource CreateNewMailTemplate}"
RatingItemTemplate="{StaticResource RatingItemTemplate}"
SeperatorTemplate="{StaticResource SeperatorTemplate}"
SettingsItemTemplate="{StaticResource SettingsItemTemplate}" />
SettingsItemTemplate="{StaticResource SettingsItemTemplate}"
StoreUpdateItemTemplate="{StaticResource StoreUpdateItemTemplate}" />
</Page.Resources>
<Grid
@@ -477,3 +478,5 @@
</Grid>
</abstract:MailAppShellAbstract>
@@ -15,6 +15,7 @@ public partial class NavigationMenuTemplateSelector : DataTemplateSelector
public DataTemplate MergedAccountMoreExpansionItemTemplate { get; set; } = null!;
public DataTemplate FolderMenuTemplate { get; set; } = null!;
public DataTemplate SettingsItemTemplate { get; set; } = null!;
public DataTemplate StoreUpdateItemTemplate { get; set; } = null!;
public DataTemplate MoreItemsFolderTemplate { get; set; } = null!;
public DataTemplate RatingItemTemplate { get; set; } = null!;
public DataTemplate CreateNewFolderTemplate { get; set; } = null!;
@@ -32,6 +33,8 @@ public partial class NavigationMenuTemplateSelector : DataTemplateSelector
return ContactsMenuItemTemplate;
else if (item is SettingsItem)
return SettingsItemTemplate;
else if (item is StoreUpdateMenuItem)
return StoreUpdateItemTemplate;
else if (item is SeperatorItem)
return SeperatorTemplate;
else if (item is AccountMenuItem)
@@ -55,3 +58,5 @@ public partial class NavigationMenuTemplateSelector : DataTemplateSelector
return MenuItemTemplate;
}
}
@@ -293,6 +293,19 @@ public class NotificationBuilder : INotificationBuilder
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)
{
if (calendarItem == null)
@@ -437,3 +450,4 @@ public class NotificationBuilder : INotificationBuilder
return SupportedIconScales.OrderBy(s => Math.Abs(s - requestedScale)).First();
}
}
@@ -308,6 +308,11 @@ public class PreferencesService(IConfigurationService configurationService) : Ob
set => SetPropertyAndSave(nameof(EmailSyncIntervalMinutes), value);
}
public bool IsStoreUpdateNotificationsEnabled
{
get => _configurationService.Get(nameof(IsStoreUpdateNotificationsEnabled), true);
set => SetPropertyAndSave(nameof(IsStoreUpdateNotificationsEnabled), value);
}
public WinoApplicationMode DefaultApplicationMode
{
get
@@ -357,3 +362,5 @@ public class PreferencesService(IConfigurationService configurationService) : Ob
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>
</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 -->
<DataTemplate x:Key="RatingItemTemplate" x:DataType="menu:RateMenuItem">
@@ -271,3 +279,7 @@
<!--#endregion-->
</ResourceDictionary>
@@ -304,6 +304,12 @@
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.MenuSettings}" />
</StackPanel>
</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>
</Grid>
@@ -398,3 +404,5 @@
</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 " />
</controls:SettingsCard.HeaderIcon>
</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>
<VisualStateManager.VisualStateGroups>
@@ -64,3 +70,6 @@
</VisualStateManager.VisualStateGroups>
</ScrollViewer>
</abstract:AppPreferencesPageAbstract>