Some more cleanup.

This commit is contained in:
Burak Kaan Köse
2026-04-05 13:18:50 +02:00
parent c1ab49fb1d
commit ca19297b92
22 changed files with 444 additions and 302 deletions
+147 -122
View File
@@ -1,133 +1,158 @@
# Wino Mail vNext Release Notes (vs. `main`)
# Wino Mail vNext Improvements
This document summarizes the areas that were merged and improved on `feature/vNext` relative to `main`, based on commit history.
This document summarizes the major improvements on `feature/vNext` compared to `main`, based on the commit history between the current branch and the merge-base with `main`.
## Major merged/improved areas
## Wino Calendar
### 1) Calendar management and scheduling experience
Calendar support was significantly expanded from foundational UI work to full account-integrated flows. The cycle includes calendar/mail mapping, richer calendar visuals, event details improvements, recurring occurrence summary work, and dedicated event compose/create flows. Calendar reliability also improved through delta-sync fixes, timezone correctness updates, duplicate-operation prevention, and better synchronization state handling across providers. Reminder handling became much more complete with snooze support spanning toast UX, service logic, and database persistence. Overall, calendar now behaves like a first-class product surface rather than an auxiliary feature.
Calendar has grown from an early implementation into a much more complete product area on this branch.
### 2) Contact management and people-centric UX
Contacts moved from incremental UI adjustments to robust management features across account and settings surfaces. The branch includes explicit contact-management commits, contact/settings integration updates, and data-model cleanup that removes legacy base64 contact storage patterns. Visual quality improvements (such as profile-image initials behavior and image preview controls) tightened the identity and people experience. In practice, this reduces friction when browsing and maintaining contact data and makes account-related contact operations more predictable.
### A full Wino Calendar experience
### 3) Synchronization architecture, correctness, and resilience
Synchronization paths were deeply refactored to address long-standing reliability issues. The branch adds generic error handling (including 404 and Outlook 429 handling), improves thread mapping across synchronizers, introduces explicit mail/calendar synchronizer state, and hardens CalDAV/IMAP behaviors with targeted fixes. Operation safety improved via better busy-state handling, duplicate-operation avoidance, and execution-error handling in rendering/operation pipelines. These changes collectively reduce failure surfaces and improve consistency under real-world server and network conditions.
- Added a dedicated Wino Calendar app entry, making calendar a first-class experience instead of a secondary add-on.
- Built out the calendar rendering experience with multiple rounds of rendering improvements, updated calendar view styling, calendar buttons, and better event visuals.
- Added event creation and full event compose flows, including follow-up improvements for attachments, attendees, recurrence summaries, RSVP actions, reminders, and event details.
- Improved support for all-day events, better display dates, occurrence handling, and mail-to-calendar mapping so calendar actions connect more naturally with messages and invitations.
### 4) Compose/editor, rendering, and draft pipeline improvements
Message composition and rendering received notable architectural cleanup. Work includes editor and toolbar refactors, message-based compose/render simplifications, Gmail drafting, and large Outlook attachment support through upload sessions. Local-draft behavior was refined with resend logic and grace-period protections, while MIME/header and template work improved message fidelity. Together, these changes make authoring and sending mail more stable for both common and heavy payload workflows.
### Local calendar support
### 5) Performance, data integrity, and test/build quality
The cycle introduced meaningful backend and tooling quality improvements: batch DB query mail fetching with in-memory caching, SQLite index additions, and foreign-key enforcement. Collection/thread update performance was optimized, and multiple targeted tests were added for calendar, IMAP, CalDAV, sanitization, and view-model behavior. CI support was improved via a dedicated PR workflow for WinUI/Core tests, and the WinUI project moved toward stricter warning discipline (`warnings as errors`). This area improves both runtime responsiveness and development confidence.
- Added local calendar operation coverage and supporting behavior for IMAP-backed/local calendar scenarios.
- Prevented duplicate operations by ignoring local calendar apply-changes in the wrong paths.
- Added busy-state support and metadata fetch flows so newly created accounts can initialize calendar data more reliably.
### 6) WinUI shell, settings, onboarding, and notification polish
The WinUI shell and app flow were modernized with startup window/onboarding wizard work, settings page refreshes, dialog/title bar improvements, and navigation fixes. Notification behavior was refined with app-entry routing for mail/calendar toasts, redundant-target cleanup, runtime toast dispatch fixes, and tailored image corrections. Additional quality-of-life updates (keyboard shortcuts, global mouse back listener, storage/settings navigability, and startup mode fixes) make daily use more polished and predictable.
### CalDAV sync
---
- Introduced a dedicated CalDAV synchronizer and supporting service/client work.
- Fixed CalDAV delta sync issues.
- Fixed CalDAV timezone issues.
- Added manual live CalDAV workflow tests to validate real-world sync behavior.
## Small changes (commit-by-commit, one-line summary)
This means local and self-hosted calendar scenarios are much better represented on this branch than on `main`.
- **44be3eb** — Final settings UI tweaks polished spacing/behavior for the latest shell experience.
- **3e73196** — Removed edit-account-details page to simplify account maintenance flows.
- **8548257** — Corrected update-notes behavior/content for clearer release messaging.
- **d9da326** — Renamed the database artifact to align naming with updated app data conventions.
- **d43e2b2** — Fixed tailored notification image handling so visuals render reliably.
- **9d94bad** — Fixed storage page navigation so users can reach storage settings consistently.
- **e4a224b** — Added/updated email templates to improve default composition output.
- **15400d4** — Improved keyboard shortcuts for faster power-user navigation and commands.
- **c1568d3** — Added live store update notifications to surface app-update availability.
- **a8f9b2d** — Delivered broad calendar quality improvements across UX and behavior.
- **1da3408** — Refactored HTML editor toolbar for cleaner structure and easier extensibility.
- **ebc35c3** — Added event creation capabilities to the calendar workflow.
- **d1f8163** — Refactored web editor internals and improved calendar occurrence summaries.
- **09f1cee** — Removed sqlite base64 contact storage from `AccountContact` to modernize data handling.
- **8e8b123** — Updated NuGet dependencies for compatibility, fixes, and maintenance.
- **9ec7b32** — Merged `feature/EventCompose` into `feature/vNext` to unify event compose work.
- **e94cce4** — Implemented main event compose functionality for calendar authoring.
- **6608bae** — Added initial scaffolding for event composition.
- **5904272** — Added specific handling for Outlook 429 responses to improve throttling resilience.
- **e1be644** — Delivered contact/settings integration updates for smoother account configuration.
- **51f6446** — Updated core title bar to reflect new menu-item structure.
- **24f7c26** — Refreshed dialog visuals for a more modern WinUI look.
- **1aaf4e8** — Expanded settings UI foundation for broader configuration coverage.
- **3d67637** — Consolidated intermediate branch work through merge integration.
- **aaa6e8a** — Removed migrations and introduced wizard-like onboarding steps.
- **db5ecd6** — Added a new startup window to improve initial app entry flow.
- **d45d3fa** — Implemented “Whats New” surface for post-update feature communication.
- **5b3739c** — Added snooze support for calendar reminders across UI/service/database layers.
- **e816e87** — Added/expanded contact management capabilities.
- **bdd3278** — Reworked folder structure organization for maintainability.
- **f35a433** — Fixed profile-image transparency edge case causing unwanted initials background.
- **2c9351f** — Fixed edge cases in `IsBusy` handling to prevent inconsistent UI state.
- **211faff** — Improved bulk mail operations by using property-change-driven updates.
- **11158fe** — Removed redundant notification target configuration.
- **76e3b72** — Fixed issues around mode switching and notification behavior.
- **2040d4a** — Optimized mail fetch pipeline with batched DB queries and memory caching.
- **0e742c7** — Resolved warnings and enforced warning-as-error discipline in WinUI.
- **d2fce5e** — Added PR GitHub Actions workflow for WinUI build + Core test validation.
- **5c510fd** — Removed single-entry/mode-launch behavior tied to Ctrl key startup.
- **e1ce856** — Fixed additional startup mode issues for more predictable launches.
- **4b22608** — Fixed badge creation behavior so badges are consistently generated for Wino Mail.
- **3a39266** — Simplified compose/rendering logic using clearer message-driven flows.
- **5d46ea7** — Routed mail/calendar toasts to correct app entries.
- **d51f4a7** — Added SQLite indexes and enforced foreign keys for performance/integrity.
- **79a8171** — Improved thread mapping logic across all synchronizer implementations.
- **c5a631d** — Added grace-period logic for local drafts to reduce accidental loss/conflicts.
- **33672ab** — Added local draft resend behavior and default app mode settings.
- **311b3c7** — Added dedicated Wino Calendar app entry point.
- **17ca32c** — Enabled large Outlook attachment sending via upload sessions.
- **9d3f0bd** — Added manual live coverage tests for `ImapSynchronizer`.
- **7f198ba** — Implemented explicit synchronizer state for mail and calendar items.
- **a912ada** — Fixed messaging issues tied to calendar add/delete operations.
- **317113a** — Fixed CalDAV timezone handling issues.
- **564cb0b** — Fixed double-initialization issue in calendar day views.
- **ab0810f** — Fixed CalDAV delta sync behavior.
- **7a13ae0** — Added manual live CalDAV workflow tests.
- **c8e1678** — Fixed `HtmlPreviewVisitor` regressions and added sanitization tests.
- **f49d276** — Added focused ViewModel tests for `WinoMailCollection`.
- **05112d6** — Ensured WebView2 runtime toast notifications are dispatched on UI thread.
- **fec49ce** — Improved UI-side cleanup when deleting an account.
- **31a7fae** — Added operation-execution error handling in rendering page flow.
- **dae7d04** — Added calendar metadata fetch after account creation.
- **d428a6c** — Ignored local calendar apply-changes in specific paths to prevent duplicates.
- **ff25db3** — Added busy-state support to calendar item view models.
- **2baa87d** — Added IMAP local calendar operation tests with in-memory DB.
- **42e5157** — Landed broad calendar implementation work across multiple components.
- **acf0f64** — Added CalDAV synchronizer and new IMAP setup/edit page.
- **64b9bfc** — Added flag changes to support UID-based IMAP synchronization.
- **744145b** — Refactored IMAP synchronization internals for stability.
- **4a0dcd2** — Removed obsolete project files.
- **92df726** — Batched flip-view date-range updates for programmatic calendar navigation.
- **dbd5812** — Fixed null handling in `WinoCalendarView` date-range updates.
- **884f000** — Added additional calendar feature plumbing and behaviors.
- **e936c43** — Improved search behavior and relevance/UX.
- **b01fa4e** — Improved event details page and calendar item update source handling.
- **96dcdc8** — Added auto-sync triggering and cancellation support.
- **96d2efb** — Removed semantic zoom support to simplify calendar interaction model.
- **37199d8** — Fixed cache bug preventing mail removal and improved drag/drop behavior.
- **52ee5f1** — Added/improved visuals for mail-calendar items and reminders.
- **870a5e2** — Added calendar-to-mail mapping integration.
- **10dd42b** — Fixed thread UI issues for better consistency.
- **0999c71** — Improved contacts UX, thread animations, and image preview controls.
- **e559a79** — Added generic 404 handling for synchronizer operations.
- **1747ed8** — Disabled Sentry logging for synchronizer exceptions.
- **22c6452** — Delivered editor optimizations for better responsiveness.
- **ad9b94d** — Removed INC registrations for list view items to reduce overhead.
- **9f13bcd** — Applied collection-level performance optimizations.
- **5bfa61a** — Added folder create/delete, storage settings, and thread UI adjustments.
- **2cd03d5** — Fixed thread selection issue involving unrealized containers.
- **c7fb648** — Improved thread selection interactions.
- **331b966** — Added synchronizer info panel in shell for visibility/diagnostics.
- **d28de50** — Fixed Outlook attachments, compose-page reuse, and MIME header details.
- **1ec8d5b** — Added Gmail draft support.
- **4374d19** — Improved threading behavior and related interaction logic.
- **071f1c9** — Refactored synchronizers broadly to address chronic reliability issues.
- **d1425ca** — Updated Claude permissions ignore configuration.
- **2fd600d** — Added partial busy-state handling for mark-as-read requests.
- **0eba778** — Added/updated mail update-source tracking.
- **b343152** — Landed exploratory internal experiments that informed later improvements.
- **31097e4** — Added reactions to calendar changes for better real-time UI updates.
- **319b0af** — Added global mouse back-button listener for app navigation.
- **f105c2f** — Added settings/manage-accounts navigation options.
- **7cc201f** — Added `ShowAs` stripe in calendar control template.
- **a23a99c** — Added quick “join online” affordance for meetings.
- **be6b23c** — Made panel usage AOT-safe to improve compatibility.
### API calendar sync for Outlook and Gmail
- Expanded Outlook calendar sync behavior, including broader sync windows and fixes around date/time handling.
- Improved Gmail drafting and mail/calendar integration so event-related actions work better across providers.
- Added mail and calendar synchronizer state tracking to make sync progress and error handling more reliable.
- Added auto calendar sync on account creation and broader auto-sync trigger and cancellation support.
### Calendar polish and reliability
- Fixed calendar crashes and null-handling issues in calendar view date range updates.
- Fixed double initialization in calendar day views.
- Improved reaction to calendar changes and calendar item update-source handling.
- Added reminder snooze support across toast UI, services, and database storage.
Overall, Wino Calendar is one of the biggest themes of this branch: richer UI, more complete event workflows, and real sync support across local, CalDAV, Outlook, and Gmail-backed scenarios.
## Wino Accounts
Wino Accounts was significantly expanded and polished on this branch.
### Account flows and identity
- Added sign in, sign out, and registration flows.
- Redesigned login and registration dialogs.
- Added privacy policy presentation during registration.
- Added forgot password and email confirmation flows.
- Pointed the app to the real API and improved profile caching.
### Account management and settings
- Added Wino account settings and a dedicated management page.
- Added a special navigation item for Wino Accounts.
- Added import functionality for Wino Accounts.
- Added a preference to hide the title bar Wino account button.
- Improved the top-shell account icon and signed-out identity visuals.
### Purchases and add-ons
- Added handling for Paddle purchases and add-ons.
- Added purchase-success deep linking.
- Added support for AI pack handling through the Microsoft Store.
### User-facing polish
- Redesigned the Wino Account flyout and menu with a more polished Fluent-style presentation.
- Improved account cleanup behavior when an account is deleted.
- Added account attention handling and better account details/settings behavior.
Compared to `main`, this branch turns Wino Accounts into a much more complete platform feature rather than a minimal sign-in surface.
## Improved Stability and Reliability
A large part of this branch is about making the app more dependable in everyday use.
### Synchronization stability
- Refactored synchronizers to address long-standing reliability issues.
- Improved thread mapping across synchronizers.
- Added generic 404 handling for synchronizers.
- Added specific Outlook 429 handling for rate-limit scenarios.
- Improved Outlook authentication and Outlook sync reliability.
- Improved Gmail synchronizer behavior.
- Added explicit mail and calendar synchronizer state support.
### Mail and data reliability
- Optimized mail fetching with batched database queries and in-memory caching.
- Added SQLite indexes and enabled foreign key enforcement.
- Switched away from the old mail item queue approach and returned to a simpler initial sync strategy.
- Improved local draft resend behavior and added grace-period handling for local drafts.
- Added better handling for large Outlook attachments via upload sessions.
- Fixed issues with sent/draft placement, loading mails with infinite scroll, selection cleanup, and deleted-object scenarios.
### UI and lifecycle stability
- Fixed mail rendering page disposal issues.
- Fixed WebView2 runtime toast dispatching on the UI thread.
- Fixed startup mode issues, single-instancing problems, and shell/navigation regressions.
- Fixed multiple thread selection, container, flicker, and context-menu issues.
- Fixed crashes and null-reference style issues in several calendar and shell flows.
### Engineering quality
- Added more tests across calendar, CalDAV, IMAP, view-model, sanitization, and account sync scenarios.
- Added a GitHub Actions workflow to build WinUI and run Core tests on pull requests.
- Resolved warnings and moved the WinUI project toward warnings-as-errors discipline.
- Added AOT compatibility work and related cleanup across the app.
The branch is not just adding features; it is also clearly reducing failure points throughout sync, rendering, navigation, and storage.
## Contacts, Settings, and General UX
This branch also improves the everyday product experience outside mail and calendar core flows.
### Contacts
- Added contacts management.
- Improved contacts UI and related thread/image preview behavior.
- Removed legacy SQLite base64 contact storage from `AccountContact`.
- Added contact picture handling support and supporting contact service improvements.
### Settings
- Added a dedicated settings shell and refactored settings home/navigation.
- Expanded settings UI and introduced new setting options.
- Added calendar settings into the settings experience.
- Improved account details/settings pages and storage settings navigation.
- Refined settings visuals, shell integration, and menu behavior.
### Onboarding and app experience
- Added a new startup window and a more guided onboarding flow with wizard-like steps.
- Added a "What's New" implementation for feature communication.
- Improved dialogs, title bar behavior, shell content, navigation, and shell polish across multiple iterations.
- Added live store update notifications.
- Improved keyboard shortcuts and related dialogs.
- Added tray icon support and better toast routing between mail and calendar app entries.
## Summary
Compared to `main`, `feature/vNext` delivers four major leaps:
1. Wino Calendar becomes a substantially more complete feature set, including local calendar support, CalDAV sync, and stronger Outlook and Gmail calendar integration.
2. Wino Accounts becomes a real product surface with better authentication flows, management, imports, purchases, and polish.
3. The app is more stable thanks to synchronization refactors, storage improvements, test expansion, and many crash and lifecycle fixes.
4. Contacts, settings, onboarding, and shell/navigation experience all feel more mature and more consistent.
In short, this branch is a broad product maturation release rather than a narrow feature drop.
@@ -9,22 +9,18 @@ public class ImapClientPoolException : Exception
{
}
public ImapClientPoolException(string message, CustomServerInformation customServerInformation, string protocolLog) : base(message)
public ImapClientPoolException(string message, CustomServerInformation customServerInformation) : base(message)
{
CustomServerInformation = customServerInformation;
ProtocolLog = protocolLog;
}
public ImapClientPoolException(string message, string protocolLog) : base(message)
public ImapClientPoolException(string message) : base(message)
{
ProtocolLog = protocolLog;
}
public ImapClientPoolException(Exception innerException, string protocolLog) : base(innerException.Message, innerException)
public ImapClientPoolException(Exception innerException) : base(innerException.Message, innerException)
{
ProtocolLog = protocolLog;
}
public CustomServerInformation CustomServerInformation { get; }
public string ProtocolLog { get; }
}
@@ -1,17 +0,0 @@
using Wino.Core.Domain.Models.AutoDiscovery;
namespace Wino.Core.Domain.Exceptions;
public class ImapConnectionFailedPackage
{
public ImapConnectionFailedPackage(string errorMessage, string protocolLog, AutoDiscoverySettings settings)
{
ErrorMessage = errorMessage;
ProtocolLog = protocolLog;
Settings = settings;
}
public AutoDiscoverySettings Settings { get; }
public string ErrorMessage { get; set; }
public string ProtocolLog { get; }
}
@@ -182,11 +182,6 @@ public interface IPreferencesService : INotifyPropertyChanged
/// </summary>
MailOperation RightHoverAction { get; set; }
/// <summary>
/// Setting: Whether Mailkit Protocol Logger is enabled for ImapTestService or not.
/// </summary>
bool IsMailkitProtocolLoggerEnabled { get; set; }
/// <summary>
/// Setting: Which entity id (merged account or folder) should be expanded automatically on startup.
/// </summary>
@@ -1,24 +1,21 @@
using System.IO;
using Wino.Core.Domain.Entities.Shared;
namespace Wino.Core.Domain.Models.Connectivity;
public class ImapClientPoolOptions
{
public Stream ProtocolLog { get; }
public CustomServerInformation ServerInformation { get; }
public bool IsTestPool { get; }
protected ImapClientPoolOptions(CustomServerInformation serverInformation, Stream protocolLog, bool isTestPool)
protected ImapClientPoolOptions(CustomServerInformation serverInformation, bool isTestPool)
{
ServerInformation = serverInformation;
ProtocolLog = protocolLog;
IsTestPool = isTestPool;
}
public static ImapClientPoolOptions CreateDefault(CustomServerInformation serverInformation, Stream protocolLog)
=> new(serverInformation, protocolLog, false);
public static ImapClientPoolOptions CreateDefault(CustomServerInformation serverInformation)
=> new(serverInformation, false);
public static ImapClientPoolOptions CreateTestPool(CustomServerInformation serverInformation, Stream protocolLog)
=> new(serverInformation, protocolLog, true);
public static ImapClientPoolOptions CreateTestPool(CustomServerInformation serverInformation)
=> new(serverInformation, true);
}
@@ -18,13 +18,10 @@ public class ImapConnectivityTestResults
public bool IsCertificateUIRequired { get; set; }
public string FailedReason { get; set; }
public string FailureProtocolLog { get; set; }
public static ImapConnectivityTestResults Success() => new ImapConnectivityTestResults() { IsSuccess = true };
public static ImapConnectivityTestResults Failure(Exception ex, string failureProtocolLog) => new ImapConnectivityTestResults()
public static ImapConnectivityTestResults Failure(Exception ex) => new ImapConnectivityTestResults()
{
FailedReason = string.Join(Environment.NewLine, ex.GetInnerExceptions().Select(e => e.Message)),
FailureProtocolLog = failureProtocolLog
FailedReason = string.Join(Environment.NewLine, ex.GetInnerExceptions().Select(e => e.Message))
};
public static ImapConnectivityTestResults CertificateUIRequired(string issuer,
@@ -664,7 +664,6 @@
"PaneLengthOption_Small": "Small",
"Photos": "Photos",
"PreparingFoldersMessage": "Preparing folders",
"ProtocolLogAvailable_Message": "Protocol logs are available for diagnostics.",
"ProviderDetail_Gmail_Description": "Google Account",
"ProviderDetail_iCloud_Description": "Apple iCloud Account",
"ProviderDetail_iCloud_Title": "iCloud",
+3 -28
View File
@@ -1,12 +1,10 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
@@ -48,7 +46,6 @@ public class ImapClientPool : IDisposable
private readonly ILogger _logger = Log.ForContext<ImapClientPool>();
private readonly CustomServerInformation _customServerInformation;
private readonly Stream _protocolLogStream;
private readonly ConcurrentDictionary<WinoImapClient, ImapClientState> _clientStates = new();
private readonly Channel<WinoImapClient> _availableClients;
private readonly CancellationTokenSource _maintenanceCts = new();
@@ -78,7 +75,6 @@ public class ImapClientPool : IDisposable
public ImapClientPool(ImapClientPoolOptions imapClientPoolOptions)
{
_customServerInformation = imapClientPoolOptions.ServerInformation;
_protocolLogStream = imapClientPoolOptions.ProtocolLog;
ImapClientPoolOptions = imapClientPoolOptions;
_quirks = ImapServerQuirks.Resolve(_customServerInformation.IncomingServer);
@@ -620,14 +616,7 @@ public class ImapClientPool : IDisposable
private WinoImapClient CreateNewClient()
{
IProtocolLogger protocolLogger = null;
if (_protocolLogStream != null)
{
protocolLogger = new ProtocolLogger(_protocolLogStream, leaveOpen: true);
}
var client = protocolLogger != null ? new WinoImapClient(protocolLogger) : new WinoImapClient();
var client = new WinoImapClient();
if (!string.IsNullOrEmpty(_customServerInformation.ProxyServer))
{
@@ -679,11 +668,9 @@ public class ImapClientPool : IDisposable
private ImapClientPoolException CreatePoolException(string message, Exception innerException = null)
{
var protocolLog = GetProtocolLogContent() ?? string.Empty;
return innerException == null
? new ImapClientPoolException(message, _customServerInformation, protocolLog)
: new ImapClientPoolException(innerException, protocolLog);
? new ImapClientPoolException(message, _customServerInformation)
: new ImapClientPoolException(innerException);
}
private static ImapImplementation CreateImplementation()
@@ -740,17 +727,6 @@ public class ImapClientPool : IDisposable
return true;
}
public string GetProtocolLogContent()
{
if (_protocolLogStream == null) return default;
if (_protocolLogStream.CanSeek)
_protocolLogStream.Seek(0, SeekOrigin.Begin);
using var reader = new StreamReader(_protocolLogStream, Encoding.UTF8, true, 1024, leaveOpen: true);
return reader.ReadToEnd();
}
// Legacy compatibility methods
public Task<bool> EnsureConnectedAsync(IImapClient client) =>
Task.FromResult(client.IsConnected);
@@ -784,7 +760,6 @@ public class ImapClientPool : IDisposable
_dedicatedIdleClient = null;
}
_protocolLogStream?.Dispose();
}
_disposedValue = true;
+1 -6
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Threading;
using MailKit;
using MailKit.Net.Imap;
@@ -24,11 +24,6 @@ public class WinoImapClient : ImapClient
HookEvents();
}
public WinoImapClient(IProtocolLogger protocolLogger) : base(protocolLogger)
{
HookEvents();
}
private void HookEvents()
{
Disconnected += ClientDisconnected;
+19 -58
View File
@@ -1,5 +1,3 @@
using System;
using System.IO;
using System.Threading.Tasks;
using MailKit.Net.Smtp;
using Wino.Core.Domain.Entities.Shared;
@@ -11,69 +9,32 @@ namespace Wino.Core.Services;
public class ImapTestService : IImapTestService
{
public const string ProtocolLogFileName = "ImapProtocolLog.log";
private readonly IPreferencesService _preferencesService;
private readonly IApplicationConfiguration _appInitializerService;
private Stream _protocolLogStream;
public ImapTestService(IPreferencesService preferencesService, IApplicationConfiguration appInitializerService)
public ImapTestService()
{
_preferencesService = preferencesService;
_appInitializerService = appInitializerService;
}
private void EnsureProtocolLogFileExists()
{
// Create new file for protocol logger.
var localAppFolderPath = _appInitializerService.ApplicationDataFolderPath;
var logFile = Path.Combine(localAppFolderPath, ProtocolLogFileName);
if (File.Exists(logFile))
File.Delete(logFile);
_protocolLogStream = File.Create(logFile);
}
public async Task TestImapConnectionAsync(CustomServerInformation serverInformation, bool allowSSLHandShake)
{
try
var poolOptions = ImapClientPoolOptions.CreateTestPool(serverInformation);
using (var clientPool = new ImapClientPool(poolOptions)
{
EnsureProtocolLogFileExists();
var poolOptions = ImapClientPoolOptions.CreateTestPool(serverInformation, _protocolLogStream);
var clientPool = new ImapClientPool(poolOptions)
{
ThrowOnSSLHandshakeCallback = !allowSSLHandShake
};
using (clientPool)
{
// This call will make sure that everything is authenticated + connected successfully.
var client = await clientPool.GetClientAsync();
clientPool.Release(client);
}
// Test SMTP connectivity.
using var smtpClient = new SmtpClient();
if (!smtpClient.IsConnected)
await smtpClient.ConnectAsync(serverInformation.OutgoingServer, int.Parse(serverInformation.OutgoingServerPort), MailKit.Security.SecureSocketOptions.Auto);
if (!smtpClient.IsAuthenticated)
await smtpClient.AuthenticateAsync(serverInformation.OutgoingServerUsername, serverInformation.OutgoingServerPassword);
}
catch (Exception)
ThrowOnSSLHandshakeCallback = !allowSSLHandShake
})
{
throw;
}
finally
{
_protocolLogStream?.Dispose();
// This call will make sure that everything is authenticated + connected successfully.
var client = await clientPool.GetClientAsync();
clientPool.Release(client);
}
// Test SMTP connectivity.
using var smtpClient = new SmtpClient();
if (!smtpClient.IsConnected)
await smtpClient.ConnectAsync(serverInformation.OutgoingServer, int.Parse(serverInformation.OutgoingServerPort), MailKit.Security.SecureSocketOptions.Auto);
if (!smtpClient.IsAuthenticated)
await smtpClient.AuthenticateAsync(serverInformation.OutgoingServerUsername, serverInformation.OutgoingServerPassword);
}
}
+3 -3
View File
@@ -114,13 +114,13 @@ public class SynchronizationManager : ISynchronizationManager
}
catch (ImapClientPoolException clientPoolException)
{
_logger.Error(clientPoolException, "IMAP connectivity test failed with protocol log");
return ImapConnectivityTestResults.Failure(clientPoolException, clientPoolException.ProtocolLog);
_logger.Error(clientPoolException, "IMAP connectivity test failed");
return ImapConnectivityTestResults.Failure(clientPoolException);
}
catch (Exception exception)
{
_logger.Error(exception, "IMAP connectivity test failed");
return ImapConnectivityTestResults.Failure(exception, string.Empty);
return ImapConnectivityTestResults.Failure(exception);
}
}
+1 -15
View File
@@ -83,7 +83,6 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
IAutoDiscoveryService autoDiscoveryService,
ICalendarService calendarService) : base(account, WeakReferenceMessenger.Default)
{
// Create client pool with account protocol log.
_imapChangeProcessor = imapChangeProcessor;
_applicationConfiguration = applicationConfiguration;
_unifiedSynchronizer = unifiedSynchronizer;
@@ -92,26 +91,13 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
_autoDiscoveryService = autoDiscoveryService;
_calendarService = calendarService;
var protocolLogStream = CreateAccountProtocolLogFileStream();
var poolOptions = ImapClientPoolOptions.CreateDefault(Account.ServerInformation, protocolLogStream);
var poolOptions = ImapClientPoolOptions.CreateDefault(Account.ServerInformation);
_clientPool = new ImapClientPool(poolOptions);
_localCalendarOperationHandler = new LocalCalendarOperationHandler(Account, _imapChangeProcessor, _calendarService, _applicationConfiguration.ApplicationDataFolderPath, "local");
_calDavCalendarOperationHandler = new CalDavCalendarOperationHandler(this, Account, _calendarService, _calDavClient);
}
private Stream CreateAccountProtocolLogFileStream()
{
if (Account == null) throw new ArgumentNullException(nameof(Account));
var logFile = Path.Combine(_applicationConfiguration.ApplicationDataFolderPath, $"Protocol_{Account.Address}_{Account.Id}.log");
// Each session should start a new log.
if (File.Exists(logFile)) File.Delete(logFile);
return new FileStream(logFile, FileMode.CreateNew);
}
/// <summary>
/// Returns UniqueId for the given mail copy id.
/// </summary>
@@ -28,10 +28,12 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
private readonly ICalDavClient _calDavClient;
private readonly IAccountService _accountService;
private readonly IMailDialogService _mailDialogService;
private readonly ISpecialImapProviderConfigResolver _specialImapProviderConfigResolver;
private readonly WelcomeWizardContext _wizardContext;
private ImapCalDavSettingsPageMode _pageMode;
private Guid _editingAccountId;
private SpecialImapProvider _editingSpecialImapProvider;
private TaskCompletionSource<ImapCalDavSetupResult> _completionSource;
private bool _isCompletionFinalized;
private bool _localOnlyInfoShown;
@@ -261,12 +263,14 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
ICalDavClient calDavClient,
IAccountService accountService,
IMailDialogService mailDialogService,
ISpecialImapProviderConfigResolver specialImapProviderConfigResolver,
WelcomeWizardContext wizardContext)
{
_autoDiscoveryService = autoDiscoveryService;
_calDavClient = calDavClient;
_accountService = accountService;
_mailDialogService = mailDialogService;
_specialImapProviderConfigResolver = specialImapProviderConfigResolver;
_wizardContext = wizardContext;
}
@@ -368,6 +372,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
if (!IsCalendarSupportEnabled || SelectedCalendarSupportMode != ImapCalendarSupportMode.CalDav)
throw new InvalidOperationException(Translator.ImapCalDavSettingsPage_CalDavNotRequiredMessage);
TryApplyKnownProviderSettingsIfNeeded(requireCompleteImapSettings: false, requireCompleteCalDavSettings: true);
var serverInformation = BuildServerInformation();
ValidateCalDavSettings(serverInformation);
await ValidateCalDavConnectivityAsync(serverInformation).ConfigureAwait(false);
@@ -505,8 +510,10 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
if (account == null)
throw new InvalidOperationException(Translator.Exception_NullAssignedAccount);
_editingSpecialImapProvider = account.SpecialImapProvider;
DisplayName = account.SenderName ?? string.Empty;
EmailAddress = account.Address ?? string.Empty;
ApplyProviderHint(_editingSpecialImapProvider);
ApplyServerInformation(account.ServerInformation);
@@ -539,10 +546,12 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
SelectedCalendarSupportMode = ImapCalendarSupportMode.CalDav;
var specialProvider = accountCreationDialogResult?.SpecialImapProviderDetails?.SpecialImapProvider ?? SpecialImapProvider.None;
_editingSpecialImapProvider = specialProvider;
ApplyProviderHint(specialProvider);
switch (specialProvider)
{
case SpecialImapProvider.iCloud:
ProviderHint = Translator.ImapCalDavSettingsPage_ICloudHint;
ApplySpecialProviderDefaults(
"imap.mail.me.com",
"993",
@@ -556,7 +565,6 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
Password);
break;
case SpecialImapProvider.Yahoo:
ProviderHint = Translator.ImapCalDavSettingsPage_YahooHint;
ApplySpecialProviderDefaults(
"imap.mail.yahoo.com",
"993",
@@ -569,9 +577,6 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
EmailAddress,
Password);
break;
default:
ProviderHint = string.Empty;
break;
}
}
@@ -645,6 +650,9 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
if (HasCompleteImapSettings())
return;
if (TryApplyKnownProviderSettingsIfNeeded(requireCompleteImapSettings: true, requireCompleteCalDavSettings: false))
return;
var minimalSettings = BuildMinimalSettingsOrThrow();
await AutoDiscoverAndApplySettingsAsync(minimalSettings).ConfigureAwait(false);
@@ -654,6 +662,9 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
private async Task AutoDiscoverAndApplySettingsAsync(AutoDiscoveryMinimalSettings minimalSettings)
{
if (TryApplyKnownProviderSettings(alwaysApplyForKnownProvider: true))
return;
var discoverySettings = await _autoDiscoveryService.GetAutoDiscoverySettings(minimalSettings).ConfigureAwait(false);
if (discoverySettings == null)
@@ -930,6 +941,96 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
if (string.IsNullOrWhiteSpace(serverInformation.CalDavPassword))
throw new InvalidOperationException(Translator.ImapCalDavSettingsPage_CalDavPasswordRequired);
}
private void ApplyProviderHint(SpecialImapProvider provider)
{
ProviderHint = provider switch
{
SpecialImapProvider.iCloud => Translator.ImapCalDavSettingsPage_ICloudHint,
SpecialImapProvider.Yahoo => Translator.ImapCalDavSettingsPage_YahooHint,
_ => string.Empty
};
}
private bool TryApplyKnownProviderSettingsIfNeeded(bool requireCompleteImapSettings, bool requireCompleteCalDavSettings)
{
var needsImapSettings = requireCompleteImapSettings && !HasCompleteImapSettings();
var needsCalDavSettings = requireCompleteCalDavSettings
&& IsCalendarSupportEnabled
&& SelectedCalendarSupportMode == ImapCalendarSupportMode.CalDav
&& !HasCompleteCalDavSettings();
if (!needsImapSettings && !needsCalDavSettings)
return false;
return TryApplyKnownProviderSettings(alwaysApplyForKnownProvider: false);
}
private bool TryApplyKnownProviderSettings(bool alwaysApplyForKnownProvider)
{
if (_editingSpecialImapProvider is not (SpecialImapProvider.iCloud or SpecialImapProvider.Yahoo))
return false;
var effectivePassword = GetKnownProviderPasswordCandidate();
if (string.IsNullOrWhiteSpace(EmailAddress) || string.IsNullOrWhiteSpace(effectivePassword))
return false;
if (!alwaysApplyForKnownProvider && HasCompleteImapSettings() && HasCompleteCalDavSettings())
return false;
var mode = IsCalendarSupportEnabled ? SelectedCalendarSupportMode : ImapCalendarSupportMode.Disabled;
var providerDetails = new SpecialImapProviderDetails(
EmailAddress.Trim(),
effectivePassword,
DisplayName.Trim(),
_editingSpecialImapProvider,
mode);
var serverInformation = _specialImapProviderConfigResolver.GetServerInformation(
new MailAccount
{
Address = EmailAddress.Trim(),
SenderName = DisplayName.Trim(),
ProviderType = MailProviderType.IMAP4,
SpecialImapProvider = _editingSpecialImapProvider,
IsCalendarAccessGranted = mode != ImapCalendarSupportMode.Disabled
},
new AccountCreationDialogResult(MailProviderType.IMAP4, DisplayName.Trim(), providerDetails, string.Empty));
if (serverInformation == null)
return false;
serverInformation.ProxyServer = (ProxyServer ?? string.Empty).Trim();
serverInformation.ProxyServerPort = (ProxyServerPort ?? string.Empty).Trim();
serverInformation.MaxConcurrentClients = MaxConcurrentClients <= 0 ? serverInformation.MaxConcurrentClients : MaxConcurrentClients;
ApplyServerInformation(serverInformation);
Password = effectivePassword;
return true;
}
private string GetKnownProviderPasswordCandidate()
{
if (!string.IsNullOrWhiteSpace(Password))
return Password;
if (!string.IsNullOrWhiteSpace(IncomingServerPassword))
return IncomingServerPassword;
if (!string.IsNullOrWhiteSpace(OutgoingServerPassword))
return OutgoingServerPassword;
return CalDavPassword ?? string.Empty;
}
private bool HasCompleteCalDavSettings()
=> !IsCalendarSupportEnabled
|| SelectedCalendarSupportMode != ImapCalendarSupportMode.CalDav
|| (!string.IsNullOrWhiteSpace(CalDavServiceUrl)
&& Uri.TryCreate(CalDavServiceUrl, UriKind.Absolute, out _)
&& !string.IsNullOrWhiteSpace(CalDavUsername)
&& !string.IsNullOrWhiteSpace(CalDavPassword));
private bool HasCompleteImapSettings()
=> !string.IsNullOrWhiteSpace(IncomingServer)
&& !string.IsNullOrWhiteSpace(IncomingServerPort)
+1
View File
@@ -231,6 +231,7 @@ public partial class App : WinoApplication,
if (windowManager.GetWindow(WinoWindowKind.Welcome) is not WelcomeWindow welcomeWindow)
return;
welcomeWindow.PrepareForClose();
welcomeWindow.AllowClose();
welcomeWindow.Close();
}
@@ -1,35 +1,35 @@
{
"sections": [
{
"title": "# Wino Calendar is here!",
"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/UpdateNotes/Images/Calendar.svg",
"imageWidth": 128,
"imageHeight": 128
},
{
"title": "# S/MIME Signing & Encryption",
"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/UpdateNotes/Images/Security.svg",
"imageWidth": 128,
"imageHeight": 128
},
{
"title": "# Threaded Mail View",
"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/UpdateNotes/Images/Thread.svg",
"imageWidth": 128,
"imageHeight": 128
},
{
"title": "# Smarter Notifications",
"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/UpdateNotes/Images/Notification.svg",
"imageWidth": 128,
"imageHeight": 128
},
{
"title": "# And much more...",
"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/UpdateNotes/Images/More.svg",
"imageWidth": 128,
+36 -4
View File
@@ -13,6 +13,8 @@ namespace Wino.Mail.WinUI;
public partial class BasePage : Page, IRecipient<LanguageChanged>
{
private bool _isPreparedForClose;
public void Receive(LanguageChanged message)
{
OnLanguageChanged();
@@ -31,6 +33,24 @@ public partial class BasePage : Page, IRecipient<LanguageChanged>
protected virtual void UnregisterRecipients() { }
public virtual CoreBaseViewModel? AssociatedViewModel => null;
public virtual void PrepareForClose()
{
if (_isPreparedForClose)
return;
_isPreparedForClose = true;
WeakReferenceMessenger.Default.Unregister<LanguageChanged>(this);
UnregisterRecipients();
}
protected void ResetPreparedForCloseState()
{
_isPreparedForClose = false;
}
protected bool IsPreparedForClose => _isPreparedForClose;
}
public abstract class BasePage<T> : BasePage where T : CoreBaseViewModel
@@ -63,6 +83,7 @@ public abstract class BasePage<T> : BasePage where T : CoreBaseViewModel
var mode = GetNavigationMode(e.NavigationMode);
var parameter = e.Parameter;
ResetPreparedForCloseState();
WeakReferenceMessenger.Default.Register<LanguageChanged>(this);
RegisterRecipients();
@@ -76,14 +97,25 @@ public abstract class BasePage<T> : BasePage where T : CoreBaseViewModel
var mode = GetNavigationMode(e.NavigationMode);
var parameter = e.Parameter;
WeakReferenceMessenger.Default.Unregister<LanguageChanged>(this);
UnregisterRecipients();
ViewModel.OnNavigatedFrom(mode, parameter);
PrepareForClose(mode, parameter);
GC.Collect();
}
public override void PrepareForClose()
{
PrepareForClose(WinoNavigationMode.New, null);
}
private void PrepareForClose(WinoNavigationMode mode, object? parameter)
{
if (IsPreparedForClose)
return;
base.PrepareForClose();
ViewModel.OnNavigatedFrom(mode, parameter!);
}
private WinoNavigationMode GetNavigationMode(NavigationMode mode)
{
return (WinoNavigationMode)mode;
@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
namespace Wino.Mail.WinUI.Helpers;
internal static class WindowCleanupHelper
{
public static void CleanupFrame(Frame? frame)
{
if (frame == null)
return;
CleanupObject(frame.Content);
frame.BackStack.Clear();
frame.ForwardStack.Clear();
frame.Content = null;
}
public static void CleanupObject(object? instance)
{
if (instance == null)
return;
var visited = new HashSet<object>(ReferenceEqualityComparer.Instance);
CleanupObject(instance, visited);
}
private static void CleanupObject(object? instance, HashSet<object> visited)
{
if (instance == null || !visited.Add(instance))
return;
switch (instance)
{
case Views.WinoAppShell shell:
shell.PrepareForWindowClose();
break;
case Frame frame:
CleanupFrame(frame);
break;
case BasePage page:
page.PrepareForClose();
break;
}
if (instance is DependencyObject dependencyObject)
{
var childCount = VisualTreeHelper.GetChildrenCount(dependencyObject);
for (int i = 0; i < childCount; i++)
{
CleanupObject(VisualTreeHelper.GetChild(dependencyObject, i), visited);
}
}
if (instance is IDisposable disposable)
{
disposable.Dispose();
}
}
private sealed class ReferenceEqualityComparer : IEqualityComparer<object>
{
public static ReferenceEqualityComparer Instance { get; } = new();
public new bool Equals(object? x, object? y) => ReferenceEquals(x, y);
public int GetHashCode(object obj) => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj);
}
}
@@ -221,12 +221,6 @@ public class PreferencesService(IConfigurationService configurationService) : Ob
set => SetPropertyAndSave(nameof(IsLoggingEnabled), value);
}
public bool IsMailkitProtocolLoggerEnabled
{
get => _configurationService.Get(nameof(IsMailkitProtocolLoggerEnabled), false);
set => SetPropertyAndSave(nameof(IsMailkitProtocolLoggerEnabled), value);
}
public bool IsGravatarEnabled
{
get => _configurationService.Get(nameof(IsGravatarEnabled), true);
+10
View File
@@ -16,6 +16,7 @@ using Wino.Core.Domain.Models.Synchronization;
using Wino.Extensions;
using Wino.Mail.WinUI.Activation;
using Wino.Mail.WinUI.Extensions;
using Wino.Mail.WinUI.Helpers;
using Wino.Mail.WinUI.Interfaces;
using Wino.Mail.WinUI.Models;
using Wino.Mail.WinUI.Views;
@@ -46,6 +47,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
private ITitleBarSearchHost? _activeTitleBarSearchHost;
private bool _isBackButtonVisibilityReady;
private bool _isSynchronizingTitleBarSearch;
private bool _isPreparedForClose;
public ShellWindow()
{
@@ -365,11 +367,18 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
public void PrepareForClose()
{
if (_isPreparedForClose)
return;
_isPreparedForClose = true;
if (MainShellFrame.Content is WinoAppShell shellPage)
{
shellPage.PrepareForWindowClose();
}
WindowCleanupHelper.CleanupFrame(MainShellFrame);
_allowClose = true;
}
@@ -384,6 +393,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
shellPage.PrepareForWindowClose();
}
WindowCleanupHelper.CleanupFrame(MainShellFrame);
UnregisterRecipients();
}
@@ -25,19 +25,21 @@ public sealed partial class WelcomeHostPage : WelcomeHostPageAbstract,
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
WeakReferenceMessenger.Default.Register<BreadcrumbNavigationRequested>(this);
WeakReferenceMessenger.Default.Register<BackBreadcrumNavigationRequested>(this);
ResetWizard();
}
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
protected override void RegisterRecipients()
{
base.RegisterRecipients();
WeakReferenceMessenger.Default.Register<BreadcrumbNavigationRequested>(this);
WeakReferenceMessenger.Default.Register<BackBreadcrumNavigationRequested>(this);
}
protected override void UnregisterRecipients()
{
base.UnregisterRecipients();
WeakReferenceMessenger.Default.Unregister<BreadcrumbNavigationRequested>(this);
WeakReferenceMessenger.Default.Unregister<BackBreadcrumNavigationRequested>(this);
base.OnNavigatingFrom(e);
}
public void Receive(BreadcrumbNavigationRequested message)
@@ -28,6 +28,7 @@ using Wino.Mail.ViewModels;
using Wino.Mail.ViewModels.Data;
using Wino.Mail.WinUI.ViewModels;
using Wino.Mail.WinUI.Controls;
using Wino.Mail.WinUI.Helpers;
using Wino.MenuFlyouts;
using Wino.MenuFlyouts.Context;
using Wino.Messaging.Client.Accounts;
@@ -127,6 +128,7 @@ public sealed partial class WinoAppShell : Views.Abstract.WinoAppShellAbstract,
navigationView.MenuItemsSource = null;
CalendarHostListView.ItemsSource = null;
WindowCleanupHelper.CleanupFrame(InnerShellFrame);
}
private void OnLoaded(object sender, RoutedEventArgs e)
+19
View File
@@ -3,6 +3,7 @@ using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Wino.Core.Domain.Interfaces;
using Wino.Mail.WinUI.Helpers;
using Wino.Mail.WinUI.Interfaces;
using WinUIEx;
@@ -11,6 +12,7 @@ namespace Wino.Mail.WinUI;
public sealed partial class WelcomeWindow : WindowEx
{
private bool _allowClose;
private bool _isPreparedForClose;
public Frame GetRootFrame() => RootFrame;
@@ -25,6 +27,7 @@ public sealed partial class WelcomeWindow : WindowEx
ConfigureWindowChrome();
AppWindow.Closing += OnAppWindowClosing;
Closed += OnWindowClosed;
}
private void ConfigureWindowChrome()
@@ -55,4 +58,20 @@ public sealed partial class WelcomeWindow : WindowEx
{
_allowClose = true;
}
public void PrepareForClose()
{
if (_isPreparedForClose)
return;
_isPreparedForClose = true;
WindowCleanupHelper.CleanupFrame(RootFrame);
}
private void OnWindowClosed(object sender, WindowEventArgs e)
{
Closed -= OnWindowClosed;
AppWindow.Closing -= OnAppWindowClosing;
PrepareForClose();
}
}