Whats new implementation.
This commit is contained in:
@@ -22,6 +22,7 @@ using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.Domain.Models.Updates;
|
||||
using Wino.Mail.Services;
|
||||
using Wino.Mail.ViewModels;
|
||||
using Wino.Mail.WinUI.Activation;
|
||||
@@ -153,9 +154,18 @@ public partial class App : WinoApplication,
|
||||
_preferencesService = Services.GetRequiredService<IPreferencesService>();
|
||||
_accountService = Services.GetRequiredService<IAccountService>();
|
||||
|
||||
// Check whether the new version requires a migration before starting sync.
|
||||
var updateManager = Services.GetRequiredService<IUpdateManager>();
|
||||
var updateNotes = await updateManager.GetLatestUpdateNotesAsync();
|
||||
bool hasPendingMigration = updateNotes.HasMigrations && updateManager.HasPendingMigrations();
|
||||
|
||||
_preferencesService.PreferenceChanged -= PreferencesServiceChanged;
|
||||
_preferencesService.PreferenceChanged += PreferencesServiceChanged;
|
||||
RestartAutoSynchronizationLoop();
|
||||
|
||||
// Hold off sync loop when a migration is required in startup-task (tray-only) mode.
|
||||
// In foreground mode the sync loop starts normally; the ViewModel dialog handles migrations before sync kicks in.
|
||||
if (!hasPendingMigration || !IsStartupTaskLaunch())
|
||||
RestartAutoSynchronizationLoop();
|
||||
|
||||
// Check if launched from toast notification.
|
||||
if (IsNotificationActivation(out AppNotificationActivatedEventArgs toastArgs))
|
||||
@@ -179,12 +189,21 @@ public partial class App : WinoApplication,
|
||||
// Otherwise, activate the window normally.
|
||||
if (isStartupTaskLaunch)
|
||||
{
|
||||
LogActivation("Launched by startup task. Window created but hidden (system tray only).");
|
||||
// Window is created but not activated. User can show it from system tray.
|
||||
if (hasPendingMigration)
|
||||
{
|
||||
// Notify the user to open the app to complete the update before sync can resume.
|
||||
Services.GetRequiredService<INotificationBuilder>().CreateMigrationRequiredNotification();
|
||||
LogActivation("Migration required for new version. Sync skipped. User notified via toast.");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogActivation("Launched by startup task. Window created but hidden (system tray only).");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal launch - show and activate the window.
|
||||
// The What's New dialog is shown from MailAppShellViewModel.OnNavigatedTo once XamlRoot is ready.
|
||||
MainWindow?.Activate();
|
||||
LogActivation("Window created and activated.");
|
||||
}
|
||||
@@ -222,6 +241,13 @@ public partial class App : WinoApplication,
|
||||
{
|
||||
var toastArguments = ToastArguments.Parse(toastArgs.Argument);
|
||||
|
||||
// Check migration notification activation first.
|
||||
if (toastArguments.Contains(Constants.ToastMigrationRequiredKey))
|
||||
{
|
||||
await HandleMigrationToastActivationAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check calendar reminder toast activation first.
|
||||
if (toastArguments.TryGetValue(Constants.ToastCalendarActionKey, out string calendarAction) &&
|
||||
calendarAction == Constants.ToastCalendarNavigateAction &&
|
||||
@@ -251,6 +277,29 @@ public partial class App : WinoApplication,
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles activation from the migration-required toast notification.
|
||||
/// Opens the app so the shell ViewModel can show the What's New dialog and run migrations.
|
||||
/// </summary>
|
||||
private async Task HandleMigrationToastActivationAsync()
|
||||
{
|
||||
LogActivation("Handling migration toast activation.");
|
||||
|
||||
if (!IsAppRunning())
|
||||
{
|
||||
await CreateAndActivateWindow(null!);
|
||||
}
|
||||
else
|
||||
{
|
||||
EnsureMainWindowVisibleAndForeground();
|
||||
}
|
||||
|
||||
// The MailAppShellViewModel.OnNavigatedTo will detect ShouldShowUpdateNotes() == true
|
||||
// and display the What's New dialog (including running migrations) once the XamlRoot is ready.
|
||||
// Restart sync in case it was blocked.
|
||||
RestartAutoSynchronizationLoop();
|
||||
}
|
||||
|
||||
private async Task HandleCalendarToastNavigationAsync(Guid calendarItemId)
|
||||
{
|
||||
var calendarService = Services.GetRequiredService<ICalendarService>();
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"hasMigrations": false,
|
||||
"sections": [
|
||||
{
|
||||
"title": "# Wino Calendar is here!",
|
||||
"description": "You can now create local or remote CalDAV-compatible calendars, manage recurring events, and respond to invitations — all from within Wino.",
|
||||
"imageUrl": "ms-appx:///Assets/AboutPageTile.png",
|
||||
"imageWidth": 128,
|
||||
"imageHeight": 128
|
||||
},
|
||||
{
|
||||
"title": "# S/MIME Signing & Encryption",
|
||||
"description": "Wino now supports signing and encrypting your emails with personal certificates. Keep your communications secure and verifiable.",
|
||||
"imageUrl": "ms-appx:///Assets/AboutPageTile.png",
|
||||
"imageWidth": 128,
|
||||
"imageHeight": 128
|
||||
},
|
||||
{
|
||||
"title": "# Threaded Mail View",
|
||||
"description": "Emails are now grouped by conversation, making it easier to follow long discussions without losing context.",
|
||||
"imageUrl": "ms-appx:///Assets/AboutPageTile.png",
|
||||
"imageWidth": 128,
|
||||
"imageHeight": 128
|
||||
},
|
||||
{
|
||||
"title": "# Smarter Notifications",
|
||||
"description": "Act on your emails directly from toast notifications — mark as read, delete, or archive without opening the app.",
|
||||
"imageUrl": "ms-appx:///Assets/AboutPageTile.png",
|
||||
"imageWidth": 128,
|
||||
"imageHeight": 128
|
||||
},
|
||||
{
|
||||
"title": "# And much more...",
|
||||
"description": "Folder management, swipe actions, keyboard shortcuts, a custom print dialog, and significant performance improvements are all included in this release.\n\nThank you for using Wino Mail!",
|
||||
"imageUrl": "ms-appx:///Assets/AboutPageTile.png",
|
||||
"imageWidth": 128,
|
||||
"imageHeight": 128
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<ContentDialog
|
||||
x:Class="Wino.Dialogs.WhatIsNewDialog"
|
||||
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"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:models="using:Wino.Core.Domain.Models.Updates"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
Style="{StaticResource WinoDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<ContentDialog.Resources>
|
||||
<x:Double x:Key="ContentDialogMinWidth">480</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxWidth">560</x:Double>
|
||||
<x:Double x:Key="ContentDialogMinHeight">480</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxHeight">700</x:Double>
|
||||
</ContentDialog.Resources>
|
||||
|
||||
<Grid RowSpacing="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<FlipView
|
||||
x:Name="UpdateFlipView"
|
||||
ItemsSource="{x:Bind Sections, Mode=OneTime}"
|
||||
SelectionChanged="OnFlipViewSelectionChanged">
|
||||
<FlipView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:UpdateNoteSection">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Spacing="16" Padding="12,8">
|
||||
<controls:MarkdownTextBlock
|
||||
Text="{x:Bind Title, Mode=OneTime}"
|
||||
HorizontalAlignment="Center" />
|
||||
<Image
|
||||
Source="{x:Bind ImageUrl, Mode=OneTime}"
|
||||
Width="{x:Bind ActualImageWidth, Mode=OneTime}"
|
||||
Height="{x:Bind ActualImageHeight, Mode=OneTime}"
|
||||
HorizontalAlignment="Center"
|
||||
Stretch="Uniform" />
|
||||
<controls:MarkdownTextBlock
|
||||
Text="{x:Bind Description, Mode=OneTime}"
|
||||
HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</DataTemplate>
|
||||
</FlipView.ItemTemplate>
|
||||
</FlipView>
|
||||
|
||||
<PipsPager
|
||||
x:Name="FlipViewPager"
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
SelectedPageIndex="0"
|
||||
SelectedIndexChanged="OnPipsPagerSelectedIndexChanged" />
|
||||
|
||||
<Button
|
||||
x:Name="GetStartedButton"
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Right"
|
||||
Content="{x:Bind domain:Translator.WhatIsNew_GetStartedButton}"
|
||||
Visibility="Collapsed"
|
||||
Click="OnGetStartedClicked" />
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
@@ -0,0 +1,72 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Windows.System;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Updates;
|
||||
|
||||
namespace Wino.Dialogs;
|
||||
|
||||
public sealed partial class WhatIsNewDialog : ContentDialog
|
||||
{
|
||||
private readonly IUpdateManager _updateManager;
|
||||
|
||||
public List<UpdateNoteSection> Sections { get; }
|
||||
|
||||
public WhatIsNewDialog(UpdateNotes notes, IUpdateManager updateManager)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_updateManager = updateManager;
|
||||
Sections = notes.Sections;
|
||||
|
||||
// Set the number of pages in the pip pager after sections are assigned.
|
||||
FlipViewPager.NumberOfPages = Sections.Count;
|
||||
|
||||
// Show the Get Started button immediately when there is only one page.
|
||||
if (Sections.Count <= 1)
|
||||
GetStartedButton.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyRoutedEventArgs e)
|
||||
{
|
||||
// Block ESC key to prevent accidental dismissal.
|
||||
if (e.Key == VirtualKey.Escape)
|
||||
{
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
private void OnFlipViewSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
int selectedIndex = UpdateFlipView.SelectedIndex;
|
||||
|
||||
// Keep pip pager in sync with the flip view.
|
||||
FlipViewPager.SelectedPageIndex = selectedIndex;
|
||||
|
||||
// Show Get Started button only on the last page.
|
||||
GetStartedButton.Visibility = selectedIndex == Sections.Count - 1
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void OnPipsPagerSelectedIndexChanged(PipsPager sender, PipsPagerSelectedIndexChangedEventArgs args)
|
||||
{
|
||||
UpdateFlipView.SelectedIndex = sender.SelectedPageIndex;
|
||||
}
|
||||
|
||||
private async void OnGetStartedClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
GetStartedButton.IsEnabled = false;
|
||||
|
||||
await _updateManager.RunPendingMigrationsAsync();
|
||||
_updateManager.MarkUpdateNotesAsSeen();
|
||||
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
@@ -27,9 +27,9 @@ public class DialogService : DialogServiceBase, IMailDialogService
|
||||
{
|
||||
public DialogService(INewThemeService themeService,
|
||||
IConfigurationService configurationService,
|
||||
IApplicationResourceManager<ResourceDictionary> applicationResourceManager) : base(themeService, configurationService, applicationResourceManager)
|
||||
IApplicationResourceManager<ResourceDictionary> applicationResourceManager,
|
||||
IUpdateManager updateManager) : base(themeService, configurationService, applicationResourceManager, updateManager)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public async Task<ICreateAccountAliasDialog> ShowCreateAccountAliasDialogAsync()
|
||||
|
||||
@@ -16,6 +16,7 @@ using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
using Wino.Core.Domain.Models.Common;
|
||||
using Wino.Core.Domain.Models.Printing;
|
||||
using Wino.Core.Domain.Models.Updates;
|
||||
using Wino.Dialogs;
|
||||
using Wino.Mail.WinUI.Dialogs;
|
||||
using Wino.Mail.WinUI.Extensions;
|
||||
@@ -30,14 +31,16 @@ public class DialogServiceBase : IDialogServiceBase
|
||||
|
||||
protected INewThemeService ThemeService { get; }
|
||||
protected IConfigurationService ConfigurationService { get; }
|
||||
protected IUpdateManager UpdateManager { get; }
|
||||
|
||||
protected IApplicationResourceManager<ResourceDictionary> ApplicationResourceManager { get; }
|
||||
|
||||
public DialogServiceBase(INewThemeService themeService, IConfigurationService configurationService, IApplicationResourceManager<ResourceDictionary> applicationResourceManager)
|
||||
public DialogServiceBase(INewThemeService themeService, IConfigurationService configurationService, IApplicationResourceManager<ResourceDictionary> applicationResourceManager, IUpdateManager updateManager)
|
||||
{
|
||||
ThemeService = themeService;
|
||||
ConfigurationService = configurationService;
|
||||
ApplicationResourceManager = applicationResourceManager;
|
||||
UpdateManager = updateManager;
|
||||
}
|
||||
|
||||
protected XamlRoot? GetXamlRoot()
|
||||
@@ -355,4 +358,14 @@ public class DialogServiceBase : IDialogServiceBase
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ShowWhatIsNewDialogAsync(UpdateNotes notes)
|
||||
{
|
||||
var dialog = new WhatIsNewDialog(notes, UpdateManager)
|
||||
{
|
||||
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
|
||||
};
|
||||
|
||||
await HandleDialogPresentationAsync(dialog);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,6 +317,20 @@ public class NotificationBuilder : INotificationBuilder
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void CreateMigrationRequiredNotification()
|
||||
{
|
||||
var builder = new ToastContentBuilder();
|
||||
builder.SetToastScenario(ToastScenario.Default);
|
||||
|
||||
builder.AddText(Translator.WhatIsNew_MigrationNotification_Title);
|
||||
builder.AddText(Translator.WhatIsNew_MigrationNotification_Message);
|
||||
|
||||
builder.AddArgument(Constants.ToastMigrationRequiredKey, bool.TrueString);
|
||||
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
||||
|
||||
ShowToast(builder);
|
||||
}
|
||||
|
||||
private static void ShowToast(ToastContentBuilder builder, string? tag = null)
|
||||
{
|
||||
var toastNotification = new ToastNotification(builder.GetToastContent().GetXml());
|
||||
|
||||
@@ -142,6 +142,7 @@
|
||||
<Content Include="AppThemes\Snowflake.xaml" />
|
||||
<Content Include="AppThemes\TestTheme.xaml" />
|
||||
<Content Include="Assets\ReleaseNotes\vnext.md" />
|
||||
<Content Include="Assets\UpdateNotes\vnext.json" />
|
||||
<Content Include="Assets\Wino_Icon.ico" />
|
||||
<Content Include="BackgroundImages\Acrylic.jpg" />
|
||||
<Content Include="BackgroundImages\Clouds.jpg" />
|
||||
|
||||
Reference in New Issue
Block a user