Handling of all day events and auto calendar sync on account creation.
This commit is contained in:
@@ -33,8 +33,10 @@ public partial class CalendarItemViewModel : ObservableObject, ICalendarItem, IC
|
|||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
// When setting from UI (in local time), convert to event's timezone for storage.
|
// All-day events use floating dates and should not shift across timezones.
|
||||||
CalendarItem.StartDate = value.ToTimeZoneFromLocal(CalendarItem.StartTimeZone);
|
CalendarItem.StartDate = CalendarItem.IsAllDayEvent
|
||||||
|
? value.Date
|
||||||
|
: value.ToTimeZoneFromLocal(CalendarItem.StartTimeZone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,8 +78,12 @@ public static class DateTimeExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static DateTime GetLocalStartDate(this CalendarItem calendarItem)
|
public static DateTime GetLocalStartDate(this CalendarItem calendarItem)
|
||||||
=> calendarItem.StartDate.ToLocalTimeFromTimeZone(calendarItem.StartTimeZone);
|
=> calendarItem.IsAllDayEvent
|
||||||
|
? calendarItem.StartDate
|
||||||
|
: calendarItem.StartDate.ToLocalTimeFromTimeZone(calendarItem.StartTimeZone);
|
||||||
|
|
||||||
public static DateTime GetLocalEndDate(this CalendarItem calendarItem)
|
public static DateTime GetLocalEndDate(this CalendarItem calendarItem)
|
||||||
=> calendarItem.EndDate.ToLocalTimeFromTimeZone(calendarItem.EndTimeZone);
|
=> calendarItem.IsAllDayEvent
|
||||||
|
? calendarItem.EndDate
|
||||||
|
: calendarItem.EndDate.ToLocalTimeFromTimeZone(calendarItem.EndTimeZone);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Wino.Calendar.ViewModels.Data;
|
||||||
|
using Wino.Core.Extensions;
|
||||||
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
|
using Google.Apis.Calendar.v3.Data;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Wino.Core.Tests.Synchronizers;
|
||||||
|
|
||||||
|
public sealed class CalendarItemTimeZoneDisplayTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void AllDayEvents_KeepTheirOriginalCalendarDates_ForDisplay()
|
||||||
|
{
|
||||||
|
var calendarItem = new CalendarItem
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Title = "National Sovereignty and Children's Day",
|
||||||
|
StartDate = new DateTime(2026, 4, 23, 0, 0, 0),
|
||||||
|
DurationInSeconds = TimeSpan.FromDays(1).TotalSeconds,
|
||||||
|
StartTimeZone = "Turkey Standard Time",
|
||||||
|
EndTimeZone = "Turkey Standard Time"
|
||||||
|
};
|
||||||
|
|
||||||
|
calendarItem.IsAllDayEvent.Should().BeTrue();
|
||||||
|
calendarItem.LocalStartDate.Should().Be(new DateTime(2026, 4, 23, 0, 0, 0));
|
||||||
|
calendarItem.LocalEndDate.Should().Be(new DateTime(2026, 4, 24, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EditingAllDayEventDate_DoesNotApplyTimezoneConversion()
|
||||||
|
{
|
||||||
|
var calendarItem = new CalendarItem
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Title = "Holiday",
|
||||||
|
StartDate = new DateTime(2026, 4, 23, 0, 0, 0),
|
||||||
|
DurationInSeconds = TimeSpan.FromDays(1).TotalSeconds,
|
||||||
|
StartTimeZone = "Turkey Standard Time",
|
||||||
|
EndTimeZone = "Turkey Standard Time"
|
||||||
|
};
|
||||||
|
|
||||||
|
var viewModel = new CalendarItemViewModel(calendarItem);
|
||||||
|
|
||||||
|
viewModel.StartDate = new DateTime(2026, 4, 24, 0, 0, 0);
|
||||||
|
|
||||||
|
calendarItem.StartDate.Should().Be(new DateTime(2026, 4, 24, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GmailDateOnlyEvents_KeepFloatingCalendarDates()
|
||||||
|
{
|
||||||
|
var start = new EventDateTime { Date = "2026-04-23" };
|
||||||
|
var end = new EventDateTime { Date = "2026-04-24" };
|
||||||
|
|
||||||
|
GoogleIntegratorExtensions.GetEventLocalDateTime(start).Should().Be(new DateTime(2026, 4, 23, 0, 0, 0));
|
||||||
|
GoogleIntegratorExtensions.GetEventLocalDateTime(end).Should().Be(new DateTime(2026, 4, 24, 0, 0, 0));
|
||||||
|
|
||||||
|
GoogleIntegratorExtensions.GetEventDateTimeOffset(start)!.Value.UtcDateTime.Should().Be(new DateTime(2026, 4, 23, 0, 0, 0, DateTimeKind.Utc));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -180,6 +180,31 @@ public static class GoogleIntegratorExtensions
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static DateTime? GetEventLocalDateTime(EventDateTime calendarEvent)
|
||||||
|
{
|
||||||
|
if (calendarEvent == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (calendarEvent.DateTimeDateTimeOffset != null)
|
||||||
|
{
|
||||||
|
return DateTime.SpecifyKind(calendarEvent.DateTimeDateTimeOffset.Value.DateTime, DateTimeKind.Unspecified);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (calendarEvent.Date != null)
|
||||||
|
{
|
||||||
|
if (DateTime.TryParse(calendarEvent.Date, out DateTime eventDateTime))
|
||||||
|
{
|
||||||
|
return DateTime.SpecifyKind(eventDateTime, DateTimeKind.Unspecified);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("Invalid date format in Google Calendar event date.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extracts the timezone string from EventDateTime.
|
/// Extracts the timezone string from EventDateTime.
|
||||||
/// Returns null for all-day events or if timezone is not specified.
|
/// Returns null for all-day events or if timezone is not specified.
|
||||||
|
|||||||
@@ -335,6 +335,21 @@ public static class OutlookIntegratorExtensions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static DateTime GetLocalDateTimeFromDateTimeTimeZone(DateTimeTimeZone dateTimeTimeZone)
|
||||||
|
{
|
||||||
|
if (dateTimeTimeZone == null || string.IsNullOrEmpty(dateTimeTimeZone.DateTime))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("DateTimeTimeZone or DateTime is null or empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DateTime.TryParse(dateTimeTimeZone.DateTime, out DateTime parsedDateTime))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("DateTime string is not in a valid format.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return DateTime.SpecifyKind(parsedDateTime, DateTimeKind.Unspecified);
|
||||||
|
}
|
||||||
|
|
||||||
private static AttendeeStatus GetAttendeeStatus(ResponseType? responseType)
|
private static AttendeeStatus GetAttendeeStatus(ResponseType? responseType)
|
||||||
{
|
{
|
||||||
return responseType switch
|
return responseType switch
|
||||||
|
|||||||
@@ -66,12 +66,14 @@ public class GmailChangeProcessor : DefaultChangeProcessor, IGmailChangeProcesso
|
|||||||
// We don't have this event yet. Create a new one.
|
// We don't have this event yet. Create a new one.
|
||||||
var eventStartDateTimeOffset = GoogleIntegratorExtensions.GetEventDateTimeOffset(calendarEvent.Start);
|
var eventStartDateTimeOffset = GoogleIntegratorExtensions.GetEventDateTimeOffset(calendarEvent.Start);
|
||||||
var eventEndDateTimeOffset = GoogleIntegratorExtensions.GetEventDateTimeOffset(calendarEvent.End);
|
var eventEndDateTimeOffset = GoogleIntegratorExtensions.GetEventDateTimeOffset(calendarEvent.End);
|
||||||
|
var eventStartLocalDateTime = GoogleIntegratorExtensions.GetEventLocalDateTime(calendarEvent.Start);
|
||||||
|
var eventEndLocalDateTime = GoogleIntegratorExtensions.GetEventLocalDateTime(calendarEvent.End);
|
||||||
|
|
||||||
double totalDurationInSeconds = 0;
|
double totalDurationInSeconds = 0;
|
||||||
|
|
||||||
if (eventStartDateTimeOffset != null && eventEndDateTimeOffset != null)
|
if (eventStartLocalDateTime != null && eventEndLocalDateTime != null)
|
||||||
{
|
{
|
||||||
totalDurationInSeconds = (eventEndDateTimeOffset.Value - eventStartDateTimeOffset.Value).TotalSeconds;
|
totalDurationInSeconds = (eventEndLocalDateTime.Value - eventStartLocalDateTime.Value).TotalSeconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
CalendarItem calendarItem = null;
|
CalendarItem calendarItem = null;
|
||||||
@@ -97,7 +99,7 @@ public class GmailChangeProcessor : DefaultChangeProcessor, IGmailChangeProcesso
|
|||||||
CreatedAt = DateTimeOffset.UtcNow,
|
CreatedAt = DateTimeOffset.UtcNow,
|
||||||
Description = calendarEvent.Description ?? parentRecurringEvent.Description,
|
Description = calendarEvent.Description ?? parentRecurringEvent.Description,
|
||||||
Id = Guid.NewGuid(),
|
Id = Guid.NewGuid(),
|
||||||
StartDate = eventStartDateTimeOffset.Value.UtcDateTime,
|
StartDate = eventStartLocalDateTime.Value,
|
||||||
DurationInSeconds = totalDurationInSeconds,
|
DurationInSeconds = totalDurationInSeconds,
|
||||||
Location = string.IsNullOrEmpty(calendarEvent.Location) ? parentRecurringEvent.Location : calendarEvent.Location,
|
Location = string.IsNullOrEmpty(calendarEvent.Location) ? parentRecurringEvent.Location : calendarEvent.Location,
|
||||||
|
|
||||||
@@ -136,7 +138,7 @@ public class GmailChangeProcessor : DefaultChangeProcessor, IGmailChangeProcesso
|
|||||||
CreatedAt = DateTimeOffset.UtcNow,
|
CreatedAt = DateTimeOffset.UtcNow,
|
||||||
Description = calendarEvent.Description,
|
Description = calendarEvent.Description,
|
||||||
Id = Guid.NewGuid(),
|
Id = Guid.NewGuid(),
|
||||||
StartDate = eventStartDateTimeOffset.Value.UtcDateTime,
|
StartDate = eventStartLocalDateTime.Value,
|
||||||
DurationInSeconds = totalDurationInSeconds,
|
DurationInSeconds = totalDurationInSeconds,
|
||||||
Location = calendarEvent.Location,
|
Location = calendarEvent.Location,
|
||||||
|
|
||||||
|
|||||||
@@ -59,14 +59,15 @@ public class OutlookChangeProcessor(IDatabaseService databaseService,
|
|||||||
savingItem = new CalendarItem() { Id = savingItemId };
|
savingItem = new CalendarItem() { Id = savingItemId };
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTimeOffset eventStartDateTimeOffset = OutlookIntegratorExtensions.GetDateTimeOffsetFromDateTimeTimeZone(calendarEvent.Start);
|
var eventStartLocalDateTime = OutlookIntegratorExtensions.GetLocalDateTimeFromDateTimeTimeZone(calendarEvent.Start);
|
||||||
DateTimeOffset eventEndDateTimeOffset = OutlookIntegratorExtensions.GetDateTimeOffsetFromDateTimeTimeZone(calendarEvent.End);
|
var eventEndLocalDateTime = OutlookIntegratorExtensions.GetLocalDateTimeFromDateTimeTimeZone(calendarEvent.End);
|
||||||
|
|
||||||
var durationInSeconds = (eventEndDateTimeOffset - eventStartDateTimeOffset).TotalSeconds;
|
var durationInSeconds = (eventEndLocalDateTime - eventStartLocalDateTime).TotalSeconds;
|
||||||
|
|
||||||
// Store dates as UTC in the database
|
// Store the wall-clock values exactly as Outlook returned them for the event timezone.
|
||||||
|
// Timed events are converted for display later, while all-day events stay as floating dates.
|
||||||
savingItem.RemoteEventId = calendarEvent.Id.WithClientTrackingId(calendarEvent.TransactionId.GetClientTrackingId());
|
savingItem.RemoteEventId = calendarEvent.Id.WithClientTrackingId(calendarEvent.TransactionId.GetClientTrackingId());
|
||||||
savingItem.StartDate = eventStartDateTimeOffset.UtcDateTime;
|
savingItem.StartDate = eventStartLocalDateTime;
|
||||||
savingItem.DurationInSeconds = durationInSeconds;
|
savingItem.DurationInSeconds = durationInSeconds;
|
||||||
|
|
||||||
// Store the timezone information from the event
|
// Store the timezone information from the event
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
|||||||
private readonly INativeAppService _nativeAppService;
|
private readonly INativeAppService _nativeAppService;
|
||||||
private readonly IMailService _mailService;
|
private readonly IMailService _mailService;
|
||||||
private bool _hasRegisteredPersistentRecipients;
|
private bool _hasRegisteredPersistentRecipients;
|
||||||
|
private readonly SemaphoreSlim _menuRefreshSemaphore = new(1, 1);
|
||||||
|
|
||||||
private readonly SemaphoreSlim accountInitFolderUpdateSlim = new SemaphoreSlim(1);
|
private readonly SemaphoreSlim accountInitFolderUpdateSlim = new SemaphoreSlim(1);
|
||||||
|
|
||||||
@@ -970,13 +971,21 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
|||||||
|
|
||||||
private async Task RecreateMenuItemsAsync()
|
private async Task RecreateMenuItemsAsync()
|
||||||
{
|
{
|
||||||
await ExecuteUIThread(() =>
|
await _menuRefreshSemaphore.WaitAsync().ConfigureAwait(false);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
MenuItems.Clear();
|
await ExecuteUIThread(() =>
|
||||||
MenuItems.Add(CreateMailMenuItem);
|
{
|
||||||
});
|
MenuItems.Clear();
|
||||||
|
MenuItems.Add(CreateMailMenuItem);
|
||||||
|
});
|
||||||
|
|
||||||
await LoadAccountsAsync();
|
await LoadAccountsAsync();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_menuRefreshSemaphore.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RestoreSelectedAccountAfterMenuRefreshAsync(bool automaticallyNavigateFirstItem)
|
private async Task RestoreSelectedAccountAfterMenuRefreshAsync(bool automaticallyNavigateFirstItem)
|
||||||
|
|||||||
@@ -239,6 +239,13 @@ public partial class App : WinoApplication,
|
|||||||
if (windowManager.GetWindow(WinoWindowKind.Shell) is not ShellWindow shellWindow)
|
if (windowManager.GetWindow(WinoWindowKind.Shell) is not ShellWindow shellWindow)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
windowManager.HideWindow(shellWindow);
|
||||||
|
if (ReferenceEquals(MainWindow, shellWindow))
|
||||||
|
{
|
||||||
|
MainWindow = null;
|
||||||
|
InitializeNavigationDispatcher();
|
||||||
|
}
|
||||||
|
|
||||||
shellWindow.PrepareForClose();
|
shellWindow.PrepareForClose();
|
||||||
shellWindow.Close();
|
shellWindow.Close();
|
||||||
}
|
}
|
||||||
@@ -748,7 +755,7 @@ public partial class App : WinoApplication,
|
|||||||
/// Creates the main window without activating it.
|
/// Creates the main window without activating it.
|
||||||
/// Used for both normal launch and startup task launch (tray only).
|
/// Used for both normal launch and startup task launch (tray only).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void CreateWindow(Microsoft.UI.Xaml.LaunchActivatedEventArgs? args)
|
private void CreateWindow(Microsoft.UI.Xaml.LaunchActivatedEventArgs? args, string? forcedLaunchArguments = null)
|
||||||
{
|
{
|
||||||
LogActivation("Creating main window.");
|
LogActivation("Creating main window.");
|
||||||
|
|
||||||
@@ -761,6 +768,12 @@ public partial class App : WinoApplication,
|
|||||||
|
|
||||||
windowManager.SetPrimaryNavigationFrame(WinoWindowKind.Shell, shellWindow.GetMainFrame());
|
windowManager.SetPrimaryNavigationFrame(WinoWindowKind.Shell, shellWindow.GetMainFrame());
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(forcedLaunchArguments))
|
||||||
|
{
|
||||||
|
shellWindow.HandleAppActivation(forcedLaunchArguments);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
|
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
|
||||||
|
|
||||||
if (activationArgs.Kind == ExtendedActivationKind.Launch &&
|
if (activationArgs.Kind == ExtendedActivationKind.Launch &&
|
||||||
@@ -902,7 +915,6 @@ public partial class App : WinoApplication,
|
|||||||
_hasConfiguredAccounts = true;
|
_hasConfiguredAccounts = true;
|
||||||
|
|
||||||
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
|
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
|
||||||
var navigationService = Services.GetRequiredService<INavigationService>();
|
|
||||||
|
|
||||||
// Only transition when the account was created from the WelcomeWindow.
|
// Only transition when the account was created from the WelcomeWindow.
|
||||||
if (windowManager.GetWindow(WinoWindowKind.Welcome) == null)
|
if (windowManager.GetWindow(WinoWindowKind.Welcome) == null)
|
||||||
@@ -911,12 +923,20 @@ public partial class App : WinoApplication,
|
|||||||
MainWindow?.DispatcherQueue?.TryEnqueue(async () =>
|
MainWindow?.DispatcherQueue?.TryEnqueue(async () =>
|
||||||
{
|
{
|
||||||
// Create and activate ShellWindow — ActiveWindowChanged fires and rebinds the dispatcher.
|
// Create and activate ShellWindow — ActiveWindowChanged fires and rebinds the dispatcher.
|
||||||
CreateWindow(null);
|
CreateWindow(null, GetModeLaunchArgument(WinoApplicationMode.Mail));
|
||||||
CloseWelcomeWindowIfPresent();
|
CloseWelcomeWindowIfPresent();
|
||||||
navigationService.ChangeApplicationMode(Core.Domain.Enums.WinoApplicationMode.Mail);
|
|
||||||
if (MainWindow != null)
|
if (MainWindow != null)
|
||||||
await ActivateWindowAsync(MainWindow);
|
await ActivateWindowAsync(MainWindow);
|
||||||
|
|
||||||
|
if (message.Account.IsCalendarAccessGranted)
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Send(new NewCalendarSynchronizationRequested(new CalendarSynchronizationOptions
|
||||||
|
{
|
||||||
|
AccountId = message.Account.Id,
|
||||||
|
Type = CalendarSynchronizationType.CalendarEvents
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
RestartAutoSynchronizationLoop();
|
RestartAutoSynchronizationLoop();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,8 +159,11 @@
|
|||||||
<!-- All-Day template for top area. -->
|
<!-- All-Day template for top area. -->
|
||||||
<VisualState x:Name="AllDayEvent">
|
<VisualState x:Name="AllDayEvent">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
|
<Setter Target="EventTitleTextblock.HorizontalAlignment" Value="Stretch" />
|
||||||
|
<Setter Target="EventTitleTextblock.HorizontalTextAlignment" Value="Left" />
|
||||||
|
<Setter Target="EventTitleTextblock.Margin" Value="6,0" />
|
||||||
<Setter Target="AttributeStack.VerticalAlignment" Value="Center" />
|
<Setter Target="AttributeStack.VerticalAlignment" Value="Center" />
|
||||||
|
<Setter Target="AttributeStack.Margin" Value="0,0,4,0" />
|
||||||
<Setter Target="MainGrid.MinHeight" Value="30" />
|
<Setter Target="MainGrid.MinHeight" Value="30" />
|
||||||
<Setter Target="MainBorder.StrokeThickness" Value="0" />
|
<Setter Target="MainBorder.StrokeThickness" Value="0" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
<Grid>
|
<Grid>
|
||||||
<Grid x:Name="TimedRoot" Visibility="Collapsed">
|
<Grid x:Name="TimedRoot" Visibility="Collapsed">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
@@ -88,8 +89,29 @@
|
|||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid
|
<Border
|
||||||
|
x:Name="TimedAllDayCornerHost"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
Height="{x:Bind TimedAllDayHeight, Mode=OneWay}"
|
||||||
|
Background="{ThemeResource LayerFillColorDefaultBrush}"
|
||||||
|
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||||
|
BorderThickness="0,0,1,1"
|
||||||
|
Visibility="{x:Bind HasTimedAllDayItems, Mode=OneWay}" />
|
||||||
|
|
||||||
|
<Grid
|
||||||
|
x:Name="TimedAllDayHost"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="1"
|
||||||
|
Height="{x:Bind TimedAllDayHeight, Mode=OneWay}"
|
||||||
|
Background="{ThemeResource LayerFillColorDefaultBrush}"
|
||||||
|
Visibility="{x:Bind HasTimedAllDayItems, Mode=OneWay}">
|
||||||
|
<skia:SKXamlCanvas x:Name="TimedAllDayCanvas" PaintSurface="TimedAllDayCanvasPaintSurface" />
|
||||||
|
<Canvas x:Name="TimedAllDayItemsCanvas" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid
|
||||||
|
Grid.Row="2"
|
||||||
Grid.ColumnSpan="2"
|
Grid.ColumnSpan="2"
|
||||||
Background="Transparent">
|
Background="Transparent">
|
||||||
<ScrollViewer
|
<ScrollViewer
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
|||||||
private MonthCalendarLayoutResult _monthLayout = new(0, 0, [], []);
|
private MonthCalendarLayoutResult _monthLayout = new(0, 0, [], []);
|
||||||
private INotifyCollectionChanged? _observableItemsSource;
|
private INotifyCollectionChanged? _observableItemsSource;
|
||||||
private double _timedDayWidth;
|
private double _timedDayWidth;
|
||||||
|
private double _timedAllDayHeight;
|
||||||
private double _monthCellWidth;
|
private double _monthCellWidth;
|
||||||
private double _monthCellHeight;
|
private double _monthCellHeight;
|
||||||
private bool _hasPresentedState;
|
private bool _hasPresentedState;
|
||||||
@@ -101,12 +102,14 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
|||||||
private ObservableCollection<HeaderTextLayout> TimedHeaderTextsCollection { get; } = [];
|
private ObservableCollection<HeaderTextLayout> TimedHeaderTextsCollection { get; } = [];
|
||||||
private ObservableCollection<HeaderTextLayout> MonthHeaderTextsCollection { get; } = [];
|
private ObservableCollection<HeaderTextLayout> MonthHeaderTextsCollection { get; } = [];
|
||||||
private ObservableCollection<TimedItemLayout> TimedItemsCollection { get; } = [];
|
private ObservableCollection<TimedItemLayout> TimedItemsCollection { get; } = [];
|
||||||
|
private ObservableCollection<TimedItemLayout> TimedAllDayItemsCollection { get; } = [];
|
||||||
private ObservableCollection<MonthCellLabelLayout> MonthCellLabelsCollection { get; } = [];
|
private ObservableCollection<MonthCellLabelLayout> MonthCellLabelsCollection { get; } = [];
|
||||||
private ObservableCollection<MonthItemLayout> MonthItemsCollection { get; } = [];
|
private ObservableCollection<MonthItemLayout> MonthItemsCollection { get; } = [];
|
||||||
|
|
||||||
public IEnumerable<HeaderTextLayout> TimedHeaderTexts => TimedHeaderTextsCollection;
|
public IEnumerable<HeaderTextLayout> TimedHeaderTexts => TimedHeaderTextsCollection;
|
||||||
public IEnumerable<HeaderTextLayout> MonthHeaderTexts => MonthHeaderTextsCollection;
|
public IEnumerable<HeaderTextLayout> MonthHeaderTexts => MonthHeaderTextsCollection;
|
||||||
public IEnumerable<TimedItemLayout> TimedItems => TimedItemsCollection;
|
public IEnumerable<TimedItemLayout> TimedItems => TimedItemsCollection;
|
||||||
|
public IEnumerable<TimedItemLayout> TimedAllDayItems => TimedAllDayItemsCollection;
|
||||||
public IEnumerable<MonthCellLabelLayout> MonthCellLabels => MonthCellLabelsCollection;
|
public IEnumerable<MonthCellLabelLayout> MonthCellLabels => MonthCellLabelsCollection;
|
||||||
public IEnumerable<MonthItemLayout> MonthItems => MonthItemsCollection;
|
public IEnumerable<MonthItemLayout> MonthItems => MonthItemsCollection;
|
||||||
|
|
||||||
@@ -155,6 +158,24 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double TimedAllDayHeight
|
||||||
|
{
|
||||||
|
get => _timedAllDayHeight;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
if (_timedAllDayHeight == value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_timedAllDayHeight = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(HasTimedAllDayItems));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasTimedAllDayItems => TimedAllDayHeight > 0d;
|
||||||
|
|
||||||
public double TimelineHeight => TimedCalendarLayoutCalculator.GetTimelineHeight(GetHourHeight());
|
public double TimelineHeight => TimedCalendarLayoutCalculator.GetTimelineHeight(GetHourHeight());
|
||||||
|
|
||||||
partial void OnVisibleRangeChanged(VisibleDateRange? newValue) => RequestRefresh();
|
partial void OnVisibleRangeChanged(VisibleDateRange? newValue) => RequestRefresh();
|
||||||
@@ -309,9 +330,14 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
|||||||
var timedSurfaceWidth = GetTimedSurfaceWidth();
|
var timedSurfaceWidth = GetTimedSurfaceWidth();
|
||||||
|
|
||||||
TimedDayWidth = _currentRange.Dates.Count == 0 ? 0d : timedSurfaceWidth / _currentRange.Dates.Count;
|
TimedDayWidth = _currentRange.Dates.Count == 0 ? 0d : timedSurfaceWidth / _currentRange.Dates.Count;
|
||||||
|
TimedAllDayHeight = TimedCalendarLayoutCalculator.GetAllDayHeight(
|
||||||
|
TimedCalendarLayoutCalculator.GetAllDayLaneCount(_currentRange.Dates, CurrentItems));
|
||||||
TimedScrollContentGrid.Width = ActualWidth;
|
TimedScrollContentGrid.Width = ActualWidth;
|
||||||
TimedViewport.Width = timedSurfaceWidth;
|
TimedViewport.Width = timedSurfaceWidth;
|
||||||
TimedViewport.Height = TimelineHeight;
|
TimedViewport.Height = TimelineHeight;
|
||||||
|
TimedAllDayHost.Width = timedSurfaceWidth;
|
||||||
|
TimedAllDayItemsCanvas.Width = timedSurfaceWidth;
|
||||||
|
TimedAllDayItemsCanvas.Height = TimedAllDayHeight;
|
||||||
|
|
||||||
_timedLayout = TimedCalendarLayoutCalculator.Calculate(_currentRange, CurrentItems, timedSurfaceWidth, GetHourHeight());
|
_timedLayout = TimedCalendarLayoutCalculator.Calculate(_currentRange, CurrentItems, timedSurfaceWidth, GetHourHeight());
|
||||||
|
|
||||||
@@ -323,6 +349,12 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
|||||||
TimedDayWidth)));
|
TimedDayWidth)));
|
||||||
|
|
||||||
var eventTemplate = (DataTemplate)Resources["CalendarEventTemplate"];
|
var eventTemplate = (DataTemplate)Resources["CalendarEventTemplate"];
|
||||||
|
ReplaceCollection(TimedAllDayItemsCollection, TimedCalendarLayoutCalculator.CalculateAllDayItems(_currentRange, CurrentItems, timedSurfaceWidth).Select(item =>
|
||||||
|
{
|
||||||
|
PrepareDisplayMetadata(item.Item, item.Date);
|
||||||
|
item.Template = eventTemplate;
|
||||||
|
return item;
|
||||||
|
}));
|
||||||
ReplaceCollection(TimedItemsCollection, _timedLayout.Items.Select(item =>
|
ReplaceCollection(TimedItemsCollection, _timedLayout.Items.Select(item =>
|
||||||
{
|
{
|
||||||
PrepareDisplayMetadata(item.Item, item.Date);
|
PrepareDisplayMetadata(item.Item, item.Date);
|
||||||
@@ -330,8 +362,10 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
|||||||
return item;
|
return item;
|
||||||
}));
|
}));
|
||||||
RenderHourLabels();
|
RenderHourLabels();
|
||||||
|
RenderTimedAllDayItems();
|
||||||
RenderTimedItems();
|
RenderTimedItems();
|
||||||
|
|
||||||
|
TimedAllDayCanvas.Invalidate();
|
||||||
TimedHeaderCanvas.Invalidate();
|
TimedHeaderCanvas.Invalidate();
|
||||||
TimedStructureCanvas.Invalidate();
|
TimedStructureCanvas.Invalidate();
|
||||||
}
|
}
|
||||||
@@ -470,6 +504,32 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
|||||||
canvas.DrawLine(0, height - 1, e.Info.Width, height - 1, borderPaint);
|
canvas.DrawLine(0, height - 1, e.Info.Width, height - 1, borderPaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TimedAllDayCanvasPaintSurface(object? sender, SKPaintSurfaceEventArgs e)
|
||||||
|
{
|
||||||
|
using var borderPaint = CreateLinePaint();
|
||||||
|
var canvas = e.Surface.Canvas;
|
||||||
|
canvas.Clear(SKColors.Transparent);
|
||||||
|
|
||||||
|
var timedSurfaceWidth = GetTimedSurfaceWidth();
|
||||||
|
|
||||||
|
if (_timedLayout.VisibleDates.Count == 0 || timedSurfaceWidth <= 0 || TimedAllDayHeight <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var scaleX = (float)(e.Info.Width / timedSurfaceWidth);
|
||||||
|
var height = e.Info.Height;
|
||||||
|
var dayWidth = (float)(_timedLayout.DayWidth * scaleX);
|
||||||
|
|
||||||
|
for (var index = 1; index < _timedLayout.VisibleDates.Count; index++)
|
||||||
|
{
|
||||||
|
var x = dayWidth * index;
|
||||||
|
canvas.DrawLine(x, 0, x, height, borderPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.DrawLine(0, height - 1, e.Info.Width, height - 1, borderPaint);
|
||||||
|
}
|
||||||
|
|
||||||
private void TimedStructureCanvasPaintSurface(object? sender, SKPaintSurfaceEventArgs e)
|
private void TimedStructureCanvasPaintSurface(object? sender, SKPaintSurfaceEventArgs e)
|
||||||
{
|
{
|
||||||
using var linePaint = CreateLinePaint();
|
using var linePaint = CreateLinePaint();
|
||||||
@@ -645,6 +705,26 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RenderTimedAllDayItems()
|
||||||
|
{
|
||||||
|
TimedAllDayItemsCanvas.Children.Clear();
|
||||||
|
|
||||||
|
foreach (var item in TimedAllDayItemsCollection)
|
||||||
|
{
|
||||||
|
var presenter = new ContentPresenter
|
||||||
|
{
|
||||||
|
Width = item.Bounds.Width,
|
||||||
|
Height = item.Bounds.Height,
|
||||||
|
Content = item.Item,
|
||||||
|
ContentTemplate = item.Template
|
||||||
|
};
|
||||||
|
|
||||||
|
Canvas.SetLeft(presenter, item.Bounds.X);
|
||||||
|
Canvas.SetTop(presenter, item.Bounds.Y);
|
||||||
|
TimedAllDayItemsCanvas.Children.Add(presenter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void RenderMonthCellLabels()
|
private void RenderMonthCellLabels()
|
||||||
{
|
{
|
||||||
MonthCellLabelsCanvas.Children.Clear();
|
MonthCellLabelsCanvas.Children.Clear();
|
||||||
@@ -897,6 +977,7 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
|||||||
private void ResetTimedVisualState()
|
private void ResetTimedVisualState()
|
||||||
{
|
{
|
||||||
ResetAnimatedElement(TimedScrollViewer);
|
ResetAnimatedElement(TimedScrollViewer);
|
||||||
|
ResetAnimatedElement(TimedAllDayHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void StartNavigationTransition(Compositor compositor, Visual visual, int direction, double width)
|
private static void StartNavigationTransition(Compositor compositor, Visual visual, int direction, double width)
|
||||||
@@ -926,6 +1007,10 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
|||||||
var clipInset = (float)Math.Max(18d, Math.Min(64d, width * 0.05d));
|
var clipInset = (float)Math.Max(18d, Math.Min(64d, width * 0.05d));
|
||||||
|
|
||||||
StartTimedElementTransition(compositor, TimedScrollViewer, signedTravel, 0f, 0.68f, TimeSpan.FromMilliseconds(240), direction >= 0 ? 0f : clipInset, direction >= 0 ? clipInset : 0f, animateScale: false);
|
StartTimedElementTransition(compositor, TimedScrollViewer, signedTravel, 0f, 0.68f, TimeSpan.FromMilliseconds(240), direction >= 0 ? 0f : clipInset, direction >= 0 ? clipInset : 0f, animateScale: false);
|
||||||
|
if (HasTimedAllDayItems)
|
||||||
|
{
|
||||||
|
StartTimedElementTransition(compositor, TimedAllDayHost, signedTravel, 0f, 0.68f, TimeSpan.FromMilliseconds(240), direction >= 0 ? 0f : clipInset, direction >= 0 ? clipInset : 0f, animateScale: false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void StartModeTransition(Compositor compositor, Visual visual)
|
private static void StartModeTransition(Compositor compositor, Visual visual)
|
||||||
@@ -953,6 +1038,10 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
|||||||
private void StartTimedModeTransition(Compositor compositor)
|
private void StartTimedModeTransition(Compositor compositor)
|
||||||
{
|
{
|
||||||
StartTimedElementTransition(compositor, TimedScrollViewer, 0f, 18f, 0f, TimeSpan.FromMilliseconds(240), 0f, 0f, animateScale: false);
|
StartTimedElementTransition(compositor, TimedScrollViewer, 0f, 18f, 0f, TimeSpan.FromMilliseconds(240), 0f, 0f, animateScale: false);
|
||||||
|
if (HasTimedAllDayItems)
|
||||||
|
{
|
||||||
|
StartTimedElementTransition(compositor, TimedAllDayHost, 0f, 18f, 0f, TimeSpan.FromMilliseconds(240), 0f, 0f, animateScale: false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void StartRefreshTransition(Compositor compositor, Visual visual)
|
private static void StartRefreshTransition(Compositor compositor, Visual visual)
|
||||||
@@ -968,6 +1057,10 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
|||||||
private void StartTimedRefreshTransition(Compositor compositor)
|
private void StartTimedRefreshTransition(Compositor compositor)
|
||||||
{
|
{
|
||||||
StartOpacityTransition(compositor, ElementCompositionPreview.GetElementVisual(TimedScrollViewer), 0.8f, TimeSpan.FromMilliseconds(160));
|
StartOpacityTransition(compositor, ElementCompositionPreview.GetElementVisual(TimedScrollViewer), 0.8f, TimeSpan.FromMilliseconds(160));
|
||||||
|
if (HasTimedAllDayItems)
|
||||||
|
{
|
||||||
|
StartOpacityTransition(compositor, ElementCompositionPreview.GetElementVisual(TimedAllDayHost), 0.8f, TimeSpan.FromMilliseconds(160));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PrepareAnimatedVisual(Visual visual, UIElement target)
|
private static void PrepareAnimatedVisual(Visual visual, UIElement target)
|
||||||
|
|||||||
@@ -29,8 +29,24 @@ internal sealed record TimedCalendarLayoutResult(IReadOnlyList<DateOnly> Visible
|
|||||||
|
|
||||||
internal static class TimedCalendarLayoutCalculator
|
internal static class TimedCalendarLayoutCalculator
|
||||||
{
|
{
|
||||||
|
private const double AllDayItemHeight = 28d;
|
||||||
|
private const double AllDayItemGap = 4d;
|
||||||
|
private const double AllDaySectionPadding = 6d;
|
||||||
|
|
||||||
public static double GetTimelineHeight(double hourHeight) => hourHeight * 24d;
|
public static double GetTimelineHeight(double hourHeight) => hourHeight * 24d;
|
||||||
|
|
||||||
|
public static double GetAllDayHeight(int laneCount)
|
||||||
|
{
|
||||||
|
if (laneCount <= 0)
|
||||||
|
{
|
||||||
|
return 0d;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (AllDaySectionPadding * 2d) +
|
||||||
|
(laneCount * AllDayItemHeight) +
|
||||||
|
((laneCount - 1) * AllDayItemGap);
|
||||||
|
}
|
||||||
|
|
||||||
public static TimedCalendarLayoutResult Calculate(VisibleDateRange range, IEnumerable<CalendarItemViewModel> items, double availableWidth, double hourHeight)
|
public static TimedCalendarLayoutResult Calculate(VisibleDateRange range, IEnumerable<CalendarItemViewModel> items, double availableWidth, double hourHeight)
|
||||||
{
|
{
|
||||||
var visibleDates = range.Dates;
|
var visibleDates = range.Dates;
|
||||||
@@ -79,6 +95,11 @@ internal static class TimedCalendarLayoutCalculator
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.IsAllDayEvent)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var localStart = start.LocalDateTime;
|
var localStart = start.LocalDateTime;
|
||||||
var localEnd = end.LocalDateTime;
|
var localEnd = end.LocalDateTime;
|
||||||
|
|
||||||
@@ -101,6 +122,50 @@ internal static class TimedCalendarLayoutCalculator
|
|||||||
return segments;
|
return segments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyList<TimedItemLayout> CalculateAllDayItems(VisibleDateRange range, IEnumerable<CalendarItemViewModel> items, double availableWidth)
|
||||||
|
{
|
||||||
|
var visibleDates = range.Dates;
|
||||||
|
var dayWidth = visibleDates.Count == 0 ? 0d : availableWidth / visibleDates.Count;
|
||||||
|
var layouts = new List<TimedItemLayout>();
|
||||||
|
|
||||||
|
for (var dayIndex = 0; dayIndex < visibleDates.Count; dayIndex++)
|
||||||
|
{
|
||||||
|
var date = visibleDates[dayIndex];
|
||||||
|
var dayItems = BuildAllDayItems(items, date)
|
||||||
|
.OrderBy(item => item.StartDate)
|
||||||
|
.ThenBy(item => item.EndDate)
|
||||||
|
.ThenBy(item => item.Title)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
for (var rowIndex = 0; rowIndex < dayItems.Count; rowIndex++)
|
||||||
|
{
|
||||||
|
var y = AllDaySectionPadding + (rowIndex * (AllDayItemHeight + AllDayItemGap));
|
||||||
|
var x = (dayIndex * dayWidth) + 2d;
|
||||||
|
var width = Math.Max(0d, dayWidth - 4d);
|
||||||
|
|
||||||
|
layouts.Add(new TimedItemLayout(
|
||||||
|
dayItems[rowIndex],
|
||||||
|
dayIndex,
|
||||||
|
date,
|
||||||
|
new LayoutRect(x, y, width, AllDayItemHeight)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return layouts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int GetAllDayLaneCount(IReadOnlyList<DateOnly> visibleDates, IEnumerable<CalendarItemViewModel> items)
|
||||||
|
{
|
||||||
|
var laneCount = 0;
|
||||||
|
|
||||||
|
foreach (var date in visibleDates)
|
||||||
|
{
|
||||||
|
laneCount = Math.Max(laneCount, BuildAllDayItems(items, date).Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
return laneCount;
|
||||||
|
}
|
||||||
|
|
||||||
private static IEnumerable<List<Segment>> BuildClusters(List<Segment> segments)
|
private static IEnumerable<List<Segment>> BuildClusters(List<Segment> segments)
|
||||||
{
|
{
|
||||||
if (segments.Count == 0)
|
if (segments.Count == 0)
|
||||||
@@ -130,6 +195,41 @@ internal static class TimedCalendarLayoutCalculator
|
|||||||
yield return cluster;
|
yield return cluster;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<CalendarItemViewModel> BuildAllDayItems(IEnumerable<CalendarItemViewModel> items, DateOnly date)
|
||||||
|
{
|
||||||
|
var dayStart = date.ToDateTime(TimeOnly.MinValue);
|
||||||
|
var dayEnd = dayStart.AddDays(1);
|
||||||
|
var allDayItems = new List<CalendarItemViewModel>();
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (!item.IsAllDayEvent)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CalendarItemAccessor.TryGetTimeRange(item, out var start, out var end))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var localStart = start.LocalDateTime;
|
||||||
|
var localEnd = end.LocalDateTime;
|
||||||
|
|
||||||
|
if (localEnd <= localStart)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localStart < dayEnd && localEnd > dayStart)
|
||||||
|
{
|
||||||
|
allDayItems.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allDayItems;
|
||||||
|
}
|
||||||
|
|
||||||
private static void AssignColumns(List<Segment> segments)
|
private static void AssignColumns(List<Segment> segments)
|
||||||
{
|
{
|
||||||
var columnEnds = new List<double>();
|
var columnEnds = new List<double>();
|
||||||
|
|||||||
@@ -189,14 +189,7 @@ public class NewThemeService : INewThemeService
|
|||||||
|
|
||||||
public FrameworkElement GetShellRootContent()
|
public FrameworkElement GetShellRootContent()
|
||||||
{
|
{
|
||||||
var window = GetThemeWindow();
|
return TryGetShellRootContent() ?? throw new Exception("No root content found");
|
||||||
if (window is IWinoShellWindow shellWindow)
|
|
||||||
return shellWindow.GetRootContent();
|
|
||||||
|
|
||||||
if (window?.Content is FrameworkElement frameworkElement)
|
|
||||||
return frameworkElement;
|
|
||||||
|
|
||||||
throw new Exception("No root content found");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool isInitialized = false;
|
private bool isInitialized = false;
|
||||||
@@ -680,10 +673,18 @@ public class NewThemeService : INewThemeService
|
|||||||
if (window == null)
|
if (window == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (window is IWinoShellWindow shellWindow)
|
try
|
||||||
return shellWindow.GetRootContent();
|
{
|
||||||
|
if (window is IWinoShellWindow shellWindow)
|
||||||
|
return shellWindow.GetRootContent();
|
||||||
|
|
||||||
return window.Content as FrameworkElement;
|
return window.Content as FrameworkElement;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"Skipping root content lookup for closed window: {ex.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ApplyThemeToActiveWindowAsync()
|
public async Task ApplyThemeToActiveWindowAsync()
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user