Whats new implementation.

This commit is contained in:
Burak Kaan Köse
2026-03-02 00:44:29 +01:00
parent e816e87f61
commit d45d3faa89
21 changed files with 496 additions and 9 deletions
+52 -3
View File
@@ -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();
}
}
+2 -2
View File
@@ -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()
+14 -1
View File
@@ -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());
+1
View File
@@ -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" />