Handling of all day events and auto calendar sync on account creation.
This commit is contained in:
@@ -239,6 +239,13 @@ public partial class App : WinoApplication,
|
||||
if (windowManager.GetWindow(WinoWindowKind.Shell) is not ShellWindow shellWindow)
|
||||
return;
|
||||
|
||||
windowManager.HideWindow(shellWindow);
|
||||
if (ReferenceEquals(MainWindow, shellWindow))
|
||||
{
|
||||
MainWindow = null;
|
||||
InitializeNavigationDispatcher();
|
||||
}
|
||||
|
||||
shellWindow.PrepareForClose();
|
||||
shellWindow.Close();
|
||||
}
|
||||
@@ -748,7 +755,7 @@ public partial class App : WinoApplication,
|
||||
/// Creates the main window without activating it.
|
||||
/// Used for both normal launch and startup task launch (tray only).
|
||||
/// </summary>
|
||||
private void CreateWindow(Microsoft.UI.Xaml.LaunchActivatedEventArgs? args)
|
||||
private void CreateWindow(Microsoft.UI.Xaml.LaunchActivatedEventArgs? args, string? forcedLaunchArguments = null)
|
||||
{
|
||||
LogActivation("Creating main window.");
|
||||
|
||||
@@ -761,6 +768,12 @@ public partial class App : WinoApplication,
|
||||
|
||||
windowManager.SetPrimaryNavigationFrame(WinoWindowKind.Shell, shellWindow.GetMainFrame());
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(forcedLaunchArguments))
|
||||
{
|
||||
shellWindow.HandleAppActivation(forcedLaunchArguments);
|
||||
return;
|
||||
}
|
||||
|
||||
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
|
||||
|
||||
if (activationArgs.Kind == ExtendedActivationKind.Launch &&
|
||||
@@ -902,7 +915,6 @@ public partial class App : WinoApplication,
|
||||
_hasConfiguredAccounts = true;
|
||||
|
||||
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
|
||||
var navigationService = Services.GetRequiredService<INavigationService>();
|
||||
|
||||
// Only transition when the account was created from the WelcomeWindow.
|
||||
if (windowManager.GetWindow(WinoWindowKind.Welcome) == null)
|
||||
@@ -911,12 +923,20 @@ public partial class App : WinoApplication,
|
||||
MainWindow?.DispatcherQueue?.TryEnqueue(async () =>
|
||||
{
|
||||
// Create and activate ShellWindow — ActiveWindowChanged fires and rebinds the dispatcher.
|
||||
CreateWindow(null);
|
||||
CreateWindow(null, GetModeLaunchArgument(WinoApplicationMode.Mail));
|
||||
CloseWelcomeWindowIfPresent();
|
||||
navigationService.ChangeApplicationMode(Core.Domain.Enums.WinoApplicationMode.Mail);
|
||||
if (MainWindow != null)
|
||||
await ActivateWindowAsync(MainWindow);
|
||||
|
||||
if (message.Account.IsCalendarAccessGranted)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new NewCalendarSynchronizationRequested(new CalendarSynchronizationOptions
|
||||
{
|
||||
AccountId = message.Account.Id,
|
||||
Type = CalendarSynchronizationType.CalendarEvents
|
||||
}));
|
||||
}
|
||||
|
||||
RestartAutoSynchronizationLoop();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -159,8 +159,11 @@
|
||||
<!-- All-Day template for top area. -->
|
||||
<VisualState x:Name="AllDayEvent">
|
||||
<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.Margin" Value="0,0,4,0" />
|
||||
<Setter Target="MainGrid.MinHeight" Value="30" />
|
||||
<Setter Target="MainBorder.StrokeThickness" Value="0" />
|
||||
</VisualState.Setters>
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
<Grid>
|
||||
<Grid x:Name="TimedRoot" Visibility="Collapsed">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
@@ -88,8 +89,29 @@
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
<Border
|
||||
x:Name="TimedAllDayCornerHost"
|
||||
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"
|
||||
Background="Transparent">
|
||||
<ScrollViewer
|
||||
|
||||
@@ -51,6 +51,7 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
||||
private MonthCalendarLayoutResult _monthLayout = new(0, 0, [], []);
|
||||
private INotifyCollectionChanged? _observableItemsSource;
|
||||
private double _timedDayWidth;
|
||||
private double _timedAllDayHeight;
|
||||
private double _monthCellWidth;
|
||||
private double _monthCellHeight;
|
||||
private bool _hasPresentedState;
|
||||
@@ -101,12 +102,14 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
||||
private ObservableCollection<HeaderTextLayout> TimedHeaderTextsCollection { get; } = [];
|
||||
private ObservableCollection<HeaderTextLayout> MonthHeaderTextsCollection { get; } = [];
|
||||
private ObservableCollection<TimedItemLayout> TimedItemsCollection { get; } = [];
|
||||
private ObservableCollection<TimedItemLayout> TimedAllDayItemsCollection { get; } = [];
|
||||
private ObservableCollection<MonthCellLabelLayout> MonthCellLabelsCollection { get; } = [];
|
||||
private ObservableCollection<MonthItemLayout> MonthItemsCollection { get; } = [];
|
||||
|
||||
public IEnumerable<HeaderTextLayout> TimedHeaderTexts => TimedHeaderTextsCollection;
|
||||
public IEnumerable<HeaderTextLayout> MonthHeaderTexts => MonthHeaderTextsCollection;
|
||||
public IEnumerable<TimedItemLayout> TimedItems => TimedItemsCollection;
|
||||
public IEnumerable<TimedItemLayout> TimedAllDayItems => TimedAllDayItemsCollection;
|
||||
public IEnumerable<MonthCellLabelLayout> MonthCellLabels => MonthCellLabelsCollection;
|
||||
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());
|
||||
|
||||
partial void OnVisibleRangeChanged(VisibleDateRange? newValue) => RequestRefresh();
|
||||
@@ -309,9 +330,14 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
||||
var timedSurfaceWidth = GetTimedSurfaceWidth();
|
||||
|
||||
TimedDayWidth = _currentRange.Dates.Count == 0 ? 0d : timedSurfaceWidth / _currentRange.Dates.Count;
|
||||
TimedAllDayHeight = TimedCalendarLayoutCalculator.GetAllDayHeight(
|
||||
TimedCalendarLayoutCalculator.GetAllDayLaneCount(_currentRange.Dates, CurrentItems));
|
||||
TimedScrollContentGrid.Width = ActualWidth;
|
||||
TimedViewport.Width = timedSurfaceWidth;
|
||||
TimedViewport.Height = TimelineHeight;
|
||||
TimedAllDayHost.Width = timedSurfaceWidth;
|
||||
TimedAllDayItemsCanvas.Width = timedSurfaceWidth;
|
||||
TimedAllDayItemsCanvas.Height = TimedAllDayHeight;
|
||||
|
||||
_timedLayout = TimedCalendarLayoutCalculator.Calculate(_currentRange, CurrentItems, timedSurfaceWidth, GetHourHeight());
|
||||
|
||||
@@ -323,6 +349,12 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
||||
TimedDayWidth)));
|
||||
|
||||
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 =>
|
||||
{
|
||||
PrepareDisplayMetadata(item.Item, item.Date);
|
||||
@@ -330,8 +362,10 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
||||
return item;
|
||||
}));
|
||||
RenderHourLabels();
|
||||
RenderTimedAllDayItems();
|
||||
RenderTimedItems();
|
||||
|
||||
TimedAllDayCanvas.Invalidate();
|
||||
TimedHeaderCanvas.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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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()
|
||||
{
|
||||
MonthCellLabelsCanvas.Children.Clear();
|
||||
@@ -897,6 +977,7 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
||||
private void ResetTimedVisualState()
|
||||
{
|
||||
ResetAnimatedElement(TimedScrollViewer);
|
||||
ResetAnimatedElement(TimedAllDayHost);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
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)
|
||||
@@ -953,6 +1038,10 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
||||
private void StartTimedModeTransition(Compositor compositor)
|
||||
{
|
||||
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)
|
||||
@@ -968,6 +1057,10 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
||||
private void StartTimedRefreshTransition(Compositor compositor)
|
||||
{
|
||||
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)
|
||||
|
||||
@@ -29,8 +29,24 @@ internal sealed record TimedCalendarLayoutResult(IReadOnlyList<DateOnly> Visible
|
||||
|
||||
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 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)
|
||||
{
|
||||
var visibleDates = range.Dates;
|
||||
@@ -79,6 +95,11 @@ internal static class TimedCalendarLayoutCalculator
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.IsAllDayEvent)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var localStart = start.LocalDateTime;
|
||||
var localEnd = end.LocalDateTime;
|
||||
|
||||
@@ -101,6 +122,50 @@ internal static class TimedCalendarLayoutCalculator
|
||||
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)
|
||||
{
|
||||
if (segments.Count == 0)
|
||||
@@ -130,6 +195,41 @@ internal static class TimedCalendarLayoutCalculator
|
||||
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)
|
||||
{
|
||||
var columnEnds = new List<double>();
|
||||
|
||||
@@ -189,14 +189,7 @@ public class NewThemeService : INewThemeService
|
||||
|
||||
public FrameworkElement GetShellRootContent()
|
||||
{
|
||||
var window = GetThemeWindow();
|
||||
if (window is IWinoShellWindow shellWindow)
|
||||
return shellWindow.GetRootContent();
|
||||
|
||||
if (window?.Content is FrameworkElement frameworkElement)
|
||||
return frameworkElement;
|
||||
|
||||
throw new Exception("No root content found");
|
||||
return TryGetShellRootContent() ?? throw new Exception("No root content found");
|
||||
}
|
||||
|
||||
private bool isInitialized = false;
|
||||
@@ -680,10 +673,18 @@ public class NewThemeService : INewThemeService
|
||||
if (window == null)
|
||||
return null;
|
||||
|
||||
if (window is IWinoShellWindow shellWindow)
|
||||
return shellWindow.GetRootContent();
|
||||
try
|
||||
{
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user