Some changes.

This commit is contained in:
Burak Kaan Köse
2026-01-03 20:46:03 +01:00
parent 9877656eea
commit c8ef031e7d
6 changed files with 291 additions and 21 deletions
@@ -27,8 +27,19 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
private readonly IMailDialogService _dialogService;
private readonly IWinoRequestDelegator _winoRequestDelegator;
private readonly INavigationService _navigationService;
private readonly IUnderlyingThemeService _underlyingThemeService;
public CalendarSettings CurrentSettings { get; }
public INativeAppService NativeAppService => _nativeAppService;
[ObservableProperty]
public partial bool IsDarkWebviewRenderer { get; set; }
/// <summary>
/// Returns true if the current event has attachments.
/// Currently always returns false until attachments are implemented.
/// </summary>
public bool HasAttachments => false; // TODO: Implement when CalendarItem attachments are added
#region Details
@@ -40,6 +51,12 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
[NotifyPropertyChangedFor(nameof(CurrentRsvpStatus))]
public partial CalendarItemViewModel CurrentEvent { get; set; }
partial void OnCurrentEventChanged(CalendarItemViewModel value)
{
// Notify the view to re-render the description
Messenger.Send(new CalendarDescriptionRenderingRequested());
}
[ObservableProperty]
public partial CalendarItemViewModel SeriesParent { get; set; }
[ObservableProperty]
@@ -127,7 +144,8 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
IPreferencesService preferencesService,
IMailDialogService dialogService,
IWinoRequestDelegator winoRequestDelegator,
INavigationService navigationService)
INavigationService navigationService,
IUnderlyingThemeService underlyingThemeService)
{
_calendarService = calendarService;
_nativeAppService = nativeAppService;
@@ -135,8 +153,10 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
_dialogService = dialogService;
_winoRequestDelegator = winoRequestDelegator;
_navigationService = navigationService;
_underlyingThemeService = underlyingThemeService;
CurrentSettings = _preferencesService.GetCurrentCalendarSettings();
IsDarkWebviewRenderer = _underlyingThemeService.IsUnderlyingThemeDark();
// Initialize RSVP status options
RsvpStatusOptions.Add(new RsvpStatusOption(CalendarItemStatus.Accepted));
@@ -97,6 +97,10 @@
"CalendarEventRsvpPanel_SendReplyMessage": "Send a reply message",
"CalendarEventRsvpPanel_Tentative": "Tentative",
"CalendarEventRsvpPanel_Title": "Response Options",
"CalendarAttendeeStatus_Accepted": "Accepted",
"CalendarAttendeeStatus_Declined": "Declined",
"CalendarAttendeeStatus_NeedsAction": "Needs Action",
"CalendarAttendeeStatus_Tentative": "Tentative",
"CalendarEventDetails_Attachments": "Attachments",
"CalendarEventDetails_Details": "Details",
"CalendarEventDetails_EditSeries": "Edit Series",
@@ -110,4 +110,31 @@ public static class CalendarXamlHelpers
/// </summary>
public static bool HasOnlineMeetingLink(CalendarItemViewModel calendarItemViewModel)
=> calendarItemViewModel != null && !string.IsNullOrEmpty(calendarItemViewModel.CalendarItem?.HtmlLink);
/// <summary>
/// Returns the text representation of an attendee's status.
/// </summary>
public static string GetAttendeeStatusText(AttendeeStatus status)
{
return status switch
{
AttendeeStatus.Accepted => Translator.CalendarAttendeeStatus_Accepted,
AttendeeStatus.Declined => Translator.CalendarAttendeeStatus_Declined,
AttendeeStatus.Tentative => Translator.CalendarAttendeeStatus_Tentative,
AttendeeStatus.NeedsAction => Translator.CalendarAttendeeStatus_NeedsAction,
_ => string.Empty
};
}
/// <summary>
/// Returns visibility for attendee status badge.
/// Only shows status for non-organizers and when status is not NeedsAction.
/// </summary>
public static Microsoft.UI.Xaml.Visibility GetAttendeeStatusVisibility(AttendeeStatus status)
{
// Don't show "Needs Action" status as it's the default
return status == AttendeeStatus.NeedsAction
? Microsoft.UI.Xaml.Visibility.Collapsed
: Microsoft.UI.Xaml.Visibility.Visible;
}
}
@@ -162,7 +162,10 @@
Background="{ThemeResource DividerStrokeColorDefaultBrush}" />
<!-- RSVP Button -->
<Button Command="{x:Bind ViewModel.ToggleRsvpPanelCommand}" Style="{StaticResource TransparentActionButtonStyle}">
<Button
Background="{ThemeResource CardBackgroundFillColorDefault}"
Command="{x:Bind ViewModel.ToggleRsvpPanelCommand}"
Style="{StaticResource TransparentActionButtonStyle}">
<StackPanel Orientation="Horizontal" Spacing="8">
<ContentControl Content="{x:Bind ViewModel.CurrentRsvpStatus, Mode=OneWay}" ContentTemplateSelector="{StaticResource RsvpStatusIconSelector}" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.CurrentRsvpText, Mode=OneWay}" />
@@ -360,9 +363,9 @@
Visibility="{x:Bind ReadOnlyToggle.IsChecked, Mode=OneWay}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="16" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Title -->
@@ -373,13 +376,13 @@
<!-- Date and Duration -->
<TextBlock
Grid.Row="2"
Grid.Row="1"
Text="{x:Bind calendarHelpers:CalendarXamlHelpers.GetEventDetailsDateString(ViewModel.CurrentEvent, ViewModel.CurrentSettings), Mode=OneWay}"
TextWrapping="Wrap" />
<!-- Recurrence Info -->
<Grid
Grid.Row="3"
Grid.Row="2"
ColumnSpacing="6"
Visibility="{x:Bind ViewModel.CurrentEvent.IsRecurringParent, Mode=OneWay}">
<Grid.ColumnDefinitions>
@@ -395,6 +398,8 @@
TextWrapping="Wrap" />
</Grid>
<!-- WebView -->
<WebView2 x:Name="EventDetailsWebView" Grid.Row="3" />
</Grid>
<!-- Editable Event -->
@@ -459,19 +464,29 @@
FontSize="13"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind Email}" />
<Border
Grid.Row="2"
Padding="6,2"
HorizontalAlignment="Left"
Background="{ThemeResource AccentFillColorDefaultBrush}"
CornerRadius="4"
Visibility="{x:Bind IsOrganizer}">
<TextBlock
FontSize="11"
FontWeight="SemiBold"
Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"
Text="{x:Bind domain:Translator.CalendarEventDetails_Organizer}" />
</Border>
<StackPanel Grid.Row="2" Orientation="Horizontal" Spacing="6">
<Border
Padding="6,2"
Background="{ThemeResource AccentFillColorDefaultBrush}"
CornerRadius="4"
Visibility="{x:Bind IsOrganizer}">
<TextBlock
FontSize="11"
FontWeight="SemiBold"
Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"
Text="{x:Bind domain:Translator.CalendarEventDetails_Organizer}" />
</Border>
<Border
Padding="6,2"
Background="{ThemeResource CardStrokeColorDefaultBrush}"
CornerRadius="4"
Visibility="{x:Bind calendarHelpers:CalendarXamlHelpers.GetAttendeeStatusVisibility(AttendenceStatus)}">
<TextBlock
FontSize="11"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind calendarHelpers:CalendarXamlHelpers.GetAttendeeStatusText(AttendenceStatus)}" />
</Border>
</StackPanel>
</Grid>
</Grid>
</DataTemplate>
@@ -484,7 +499,8 @@
<Grid
x:Name="AttachmentsGrid"
Grid.Column="2"
Style="{StaticResource EventDetailsPanelGridStyle}">
Style="{StaticResource EventDetailsPanelGridStyle}"
Visibility="{x:Bind ViewModel.HasAttachments, Mode=OneWay}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
@@ -516,6 +532,8 @@
<Setter Target="PeopleGrid.(Grid.Row)" Value="0" />
<Setter Target="PeopleGrid.(Grid.Column)" Value="1" />
<Setter Target="PeopleGrid.(Grid.ColumnSpan)" Value="2" />
<!-- People grid spans both rows when attachments are hidden -->
<Setter Target="PeopleGrid.(Grid.RowSpan)" Value="2" />
<Setter Target="AttachmentsGrid.(Grid.Row)" Value="1" />
<Setter Target="AttachmentsGrid.(Grid.Column)" Value="1" />
<Setter Target="AttachmentsGrid.(Grid.ColumnSpan)" Value="2" />
@@ -1,11 +1,206 @@
using System;
using System.Text.Json;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
using Microsoft.Web.WebView2.Core;
using Windows.System;
using Wino.Core.Domain;
using Wino.Core.Domain.Interfaces;
using Wino.Mail.WinUI;
using Wino.Mail.WinUI.Extensions;
using Wino.Mail.WinUI.Views.Abstract;
using Wino.Messaging.Client.Calendar;
using Wino.Messaging.Client.Shell;
namespace Wino.Calendar.Views;
public sealed partial class EventDetailsPage : EventDetailsPageAbstract
public sealed partial class EventDetailsPage : EventDetailsPageAbstract,
IRecipient<ApplicationThemeChanged>,
IRecipient<CalendarDescriptionRenderingRequested>
{
private readonly IPreferencesService _preferencesService = App.Current.Services.GetService<IPreferencesService>()!;
private TaskCompletionSource<bool> DOMLoadedTask = new TaskCompletionSource<bool>();
private bool isChromiumDisposed = false;
public EventDetailsPage()
{
this.InitializeComponent();
InitializeComponent();
Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF");
Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation,msWebView2CodeCache");
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
EventDetailsWebView.CoreWebView2Initialized -= CoreWebViewInitialized;
EventDetailsWebView.CoreWebView2Initialized += CoreWebViewInitialized;
_ = EventDetailsWebView.EnsureCoreWebView2Async();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
DisposeWebView2();
}
private async void CoreWebViewInitialized(Microsoft.UI.Xaml.Controls.WebView2 sender, CoreWebView2InitializedEventArgs args)
{
if (EventDetailsWebView.CoreWebView2 == null) return;
var editorBundlePath = (await ViewModel.NativeAppService.GetEditorBundlePathAsync()).Replace("editor.html", string.Empty);
EventDetailsWebView.CoreWebView2.SetVirtualHostNameToFolderMapping("wino.mail", editorBundlePath, CoreWebView2HostResourceAccessKind.Allow);
EventDetailsWebView.CoreWebView2.DOMContentLoaded -= DOMContentLoaded;
EventDetailsWebView.CoreWebView2.DOMContentLoaded += DOMContentLoaded;
EventDetailsWebView.CoreWebView2.NewWindowRequested -= WindowRequested;
EventDetailsWebView.CoreWebView2.NewWindowRequested += WindowRequested;
EventDetailsWebView.NavigationStarting -= WebViewNavigationStarting;
EventDetailsWebView.NavigationStarting += WebViewNavigationStarting;
EventDetailsWebView.Source = new Uri("https://wino.mail/reader.html");
}
private void DOMContentLoaded(CoreWebView2 sender, CoreWebView2DOMContentLoadedEventArgs args)
{
DOMLoadedTask.TrySetResult(true);
_ = RenderDescriptionAsync();
}
private async Task RenderDescriptionAsync()
{
if (ViewModel?.CurrentEvent?.CalendarItem == null)
return;
await DOMLoadedTask.Task;
await UpdateEditorThemeAsync();
await UpdateReaderFontPropertiesAsync();
var description = ViewModel.CurrentEvent.CalendarItem.Description ?? string.Empty;
if (string.IsNullOrEmpty(description))
{
await EventDetailsWebView.ExecuteScriptFunctionAsync("RenderHTML", isChromiumDisposed, JsonSerializer.Serialize(" ", BasicTypesJsonContext.Default.String));
}
else
{
await EventDetailsWebView.ExecuteScriptFunctionAsync("RenderHTML", isChromiumDisposed,
JsonSerializer.Serialize(description, BasicTypesJsonContext.Default.String),
JsonSerializer.Serialize(true, BasicTypesJsonContext.Default.Boolean));
}
}
private async void WindowRequested(CoreWebView2 sender, CoreWebView2NewWindowRequestedEventArgs args)
{
args.Handled = true;
try
{
await Launcher.LaunchUriAsync(new Uri(args.Uri));
}
catch (Exception) { }
}
private async void WebViewNavigationStarting(Microsoft.UI.Xaml.Controls.WebView2 sender, CoreWebView2NavigationStartingEventArgs args)
{
if (args.Uri == "https://wino.mail/reader.html")
return;
args.Cancel = !args.Uri.StartsWith("data:text/html");
if (args.Cancel && Uri.TryCreate(args.Uri, UriKind.Absolute, out Uri? newUri) && newUri != null)
{
await Launcher.LaunchUriAsync(newUri);
}
}
private void DisposeWebView2()
{
if (EventDetailsWebView == null) return;
EventDetailsWebView.CoreWebView2Initialized -= CoreWebViewInitialized;
EventDetailsWebView.NavigationStarting -= WebViewNavigationStarting;
if (EventDetailsWebView.CoreWebView2 != null)
{
EventDetailsWebView.CoreWebView2.DOMContentLoaded -= DOMContentLoaded;
EventDetailsWebView.CoreWebView2.NewWindowRequested -= WindowRequested;
}
isChromiumDisposed = true;
EventDetailsWebView.Close();
}
private async Task UpdateEditorThemeAsync()
{
await DOMLoadedTask.Task;
if (ViewModel.IsDarkWebviewRenderer)
{
EventDetailsWebView.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark;
await InvokeScriptSafeAsync("SetDarkEditor();");
}
else
{
EventDetailsWebView.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Light;
await InvokeScriptSafeAsync("SetLightEditor();");
}
}
private async Task UpdateReaderFontPropertiesAsync()
{
await EventDetailsWebView.ExecuteScriptFunctionAsync("ChangeFontSize", isChromiumDisposed, JsonSerializer.Serialize(_preferencesService.ReaderFontSize, BasicTypesJsonContext.Default.Int32));
var fontName = _preferencesService.ReaderFont;
fontName += ", sans-serif";
await EventDetailsWebView.ExecuteScriptFunctionAsync("ChangeFontFamily", isChromiumDisposed, JsonSerializer.Serialize(fontName, BasicTypesJsonContext.Default.String));
}
private async Task<string> InvokeScriptSafeAsync(string function)
{
try
{
return await EventDetailsWebView.ExecuteScriptAsync(function);
}
catch (Exception) { }
return string.Empty;
}
void IRecipient<ApplicationThemeChanged>.Receive(ApplicationThemeChanged message)
{
ViewModel.IsDarkWebviewRenderer = message.IsUnderlyingThemeDark;
_ = UpdateEditorThemeAsync();
}
async void IRecipient<CalendarDescriptionRenderingRequested>.Receive(CalendarDescriptionRenderingRequested message)
{
await RenderDescriptionAsync();
}
protected override void RegisterRecipients()
{
base.RegisterRecipients();
WeakReferenceMessenger.Default.Register<ApplicationThemeChanged>(this);
WeakReferenceMessenger.Default.Register<CalendarDescriptionRenderingRequested>(this);
}
protected override void UnregisterRecipients()
{
base.UnregisterRecipients();
WeakReferenceMessenger.Default.Unregister<ApplicationThemeChanged>(this);
WeakReferenceMessenger.Default.Unregister<CalendarDescriptionRenderingRequested>(this);
}
}
@@ -0,0 +1,6 @@
namespace Wino.Messaging.Client.Calendar;
/// <summary>
/// Message to trigger rendering of calendar event description in WebView2.
/// </summary>
public record CalendarDescriptionRenderingRequested();