diff --git a/Wino.Core.Domain/Interfaces/INotificationBuilder.cs b/Wino.Core.Domain/Interfaces/INotificationBuilder.cs
index 6b7fe872..aae5b2b2 100644
--- a/Wino.Core.Domain/Interfaces/INotificationBuilder.cs
+++ b/Wino.Core.Domain/Interfaces/INotificationBuilder.cs
@@ -31,6 +31,11 @@ public interface INotificationBuilder
/// Account that needs attention.
void CreateAttentionRequiredNotification(MailAccount account);
+ ///
+ /// Shows a notification when WebView2 runtime is unavailable.
+ ///
+ void CreateWebView2RuntimeMissingNotification();
+
///
/// Creates a calendar reminder toast for the specified calendar item.
///
diff --git a/Wino.Core.Domain/Interfaces/IWebView2RuntimeValidatorService.cs b/Wino.Core.Domain/Interfaces/IWebView2RuntimeValidatorService.cs
new file mode 100644
index 00000000..aafa0669
--- /dev/null
+++ b/Wino.Core.Domain/Interfaces/IWebView2RuntimeValidatorService.cs
@@ -0,0 +1,12 @@
+using System.Threading.Tasks;
+
+namespace Wino.Core.Domain.Interfaces;
+
+public interface IWebView2RuntimeValidatorService
+{
+ ///
+ /// Validates whether WebView2 runtime is installed and available for use.
+ ///
+ Task IsRuntimeAvailableAsync();
+}
+
diff --git a/Wino.Core.Domain/Translations/en_US/resources.json b/Wino.Core.Domain/Translations/en_US/resources.json
index ec1838e5..15012aa4 100644
--- a/Wino.Core.Domain/Translations/en_US/resources.json
+++ b/Wino.Core.Domain/Translations/en_US/resources.json
@@ -238,6 +238,8 @@
"Error_FailedToSetupSystemFolders_Title": "Failed to setup system folders",
"Exception_AccountNeedsAttention_Title": "Account needs attention",
"Exception_AccountNeedsAttention_Message": "'{0}' requires your attention to continue working.",
+ "Exception_WebView2RuntimeMissing_Message": "Wino Mail could not find Microsoft Edge WebView2 Runtime. Please install or repair the runtime to render message content correctly.",
+ "Exception_WebView2RuntimeMissing_Title": "WebView2 runtime is required",
"Exception_AuthenticationCanceled": "Authentication canceled",
"Exception_CustomThemeExists": "This theme already exists.",
"Exception_CustomThemeMissingName": "You must provide a name.",
diff --git a/Wino.Mail.ViewModels/MailAppShellViewModel.cs b/Wino.Mail.ViewModels/MailAppShellViewModel.cs
index 68d8226a..6482a979 100644
--- a/Wino.Mail.ViewModels/MailAppShellViewModel.cs
+++ b/Wino.Mail.ViewModels/MailAppShellViewModel.cs
@@ -77,6 +77,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
private readonly IWinoRequestDelegator _winoRequestDelegator;
private readonly IMailDialogService _dialogService;
private readonly IMimeFileService _mimeFileService;
+ private readonly IWebView2RuntimeValidatorService _webView2RuntimeValidatorService;
private readonly INativeAppService _nativeAppService;
private readonly IMailService _mailService;
@@ -98,7 +99,8 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
IFolderService folderService,
IStatePersistanceService statePersistanceService,
IConfigurationService configurationService,
- IStartupBehaviorService startupBehaviorService)
+ IStartupBehaviorService startupBehaviorService,
+ IWebView2RuntimeValidatorService webView2RuntimeValidatorService)
{
StatePersistenceService = statePersistanceService;
@@ -118,6 +120,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
_launchProtocolService = launchProtocolService;
_notificationBuilder = notificationBuilder;
_winoRequestDelegator = winoRequestDelegator;
+ _webView2RuntimeValidatorService = webView2RuntimeValidatorService;
}
protected override void OnDispatcherAssigned()
@@ -233,6 +236,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
await RecreateMenuItemsAsync();
await ProcessLaunchOptionsAsync();
+ await ValidateWebView2RuntimeAsync();
if (!Debugger.IsAttached)
{
@@ -242,6 +246,16 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
await MakeSureEnableStartupLaunchAsync();
}
+ private async Task ValidateWebView2RuntimeAsync()
+ {
+ var isRuntimeAvailable = await _webView2RuntimeValidatorService.IsRuntimeAvailableAsync();
+
+ if (!isRuntimeAvailable)
+ {
+ await ExecuteUIThread(() => _notificationBuilder.CreateWebView2RuntimeMissingNotification());
+ }
+ }
+
private async Task MakeSureEnableStartupLaunchAsync()
{
if (!_configurationService.Get(IsActivateStartupLaunchAskedKey, false))
diff --git a/Wino.Mail.WinUI/CoreUWPContainerSetup.cs b/Wino.Mail.WinUI/CoreUWPContainerSetup.cs
index fe804730..7c7ca562 100644
--- a/Wino.Mail.WinUI/CoreUWPContainerSetup.cs
+++ b/Wino.Mail.WinUI/CoreUWPContainerSetup.cs
@@ -29,6 +29,7 @@ public static class CoreUWPContainerSetup
services.AddTransient();
services.AddTransient();
services.AddTransient();
+ services.AddTransient();
services.AddTransient();
services.AddSingleton();
services.AddTransient();
diff --git a/Wino.Mail.WinUI/Services/NotificationBuilder.cs b/Wino.Mail.WinUI/Services/NotificationBuilder.cs
index dbf27526..c251a61e 100644
--- a/Wino.Mail.WinUI/Services/NotificationBuilder.cs
+++ b/Wino.Mail.WinUI/Services/NotificationBuilder.cs
@@ -252,6 +252,18 @@ public class NotificationBuilder : INotificationBuilder
builder.Show();
}
+ public void CreateWebView2RuntimeMissingNotification()
+ {
+ var builder = new ToastContentBuilder();
+ builder.SetToastScenario(ToastScenario.Default);
+
+ builder.AddText(Translator.Exception_WebView2RuntimeMissing_Title);
+ builder.AddText(Translator.Exception_WebView2RuntimeMissing_Message);
+
+ builder.AddButton(GetDismissButton());
+ builder.Show();
+ }
+
public Task CreateCalendarReminderNotificationAsync(CalendarItem calendarItem, long reminderDurationInSeconds)
{
if (calendarItem == null)
diff --git a/Wino.Mail.WinUI/Services/WebView2RuntimeValidatorService.cs b/Wino.Mail.WinUI/Services/WebView2RuntimeValidatorService.cs
new file mode 100644
index 00000000..5b8fb458
--- /dev/null
+++ b/Wino.Mail.WinUI/Services/WebView2RuntimeValidatorService.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.Web.WebView2.Core;
+using Serilog;
+using Wino.Core.Domain.Interfaces;
+
+namespace Wino.Mail.WinUI.Services;
+
+public class WebView2RuntimeValidatorService : IWebView2RuntimeValidatorService
+{
+ public Task IsRuntimeAvailableAsync()
+ {
+ try
+ {
+ var version = CoreWebView2Environment.GetAvailableBrowserVersionString();
+ var hasRuntime = !string.IsNullOrWhiteSpace(version);
+
+ return Task.FromResult(hasRuntime);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "WebView2 runtime validation failed.");
+ return Task.FromResult(false);
+ }
+ }
+}