Proper handling of DateTimeOffset, support for Multi-Day events and reacting to adding/removing events for the days.

This commit is contained in:
Burak Kaan Köse
2024-12-30 23:10:51 +01:00
parent 8cc7d46d7b
commit 8fd09bcad4
23 changed files with 340 additions and 234 deletions

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic;
using Windows.UI.Xaml;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Collections;
using Wino.Core.Domain.Interfaces;
@@ -76,9 +75,6 @@ namespace Wino.Calendar.Controls
collection.CalendarItemAdded += SingleEventUpdated;
collection.CalendarItemRemoved += SingleEventUpdated;
collection.CalendarItemRangeAdded += CollectionOfEventsUpdated;
collection.CalendarItemRangeRemoved += CollectionOfEventsUpdated;
collection.CalendarItemsCleared += EventsCleared;
}
@@ -87,14 +83,10 @@ namespace Wino.Calendar.Controls
collection.CalendarItemAdded -= SingleEventUpdated;
collection.CalendarItemRemoved -= SingleEventUpdated;
collection.CalendarItemRangeAdded -= CollectionOfEventsUpdated;
collection.CalendarItemRangeRemoved -= CollectionOfEventsUpdated;
collection.CalendarItemsCleared -= EventsCleared;
}
private void SingleEventUpdated(object sender, ICalendarItem calendarItem) => UpdateCollectionVisuals();
private void CollectionOfEventsUpdated(object sender, List<ICalendarItem> calendarItems) => UpdateCollectionVisuals();
private void EventsCleared(object sender, System.EventArgs e) => UpdateCollectionVisuals();
private void UpdateCollectionVisuals()

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Wino.Calendar.Args;
@@ -95,8 +94,6 @@ namespace Wino.Calendar.Controls
{
if (canvas == null) return;
Debug.WriteLine("Deregister active canvas.");
canvas.SelectedDateTime = null;
canvas.TimelineCellSelected -= ActiveTimelineCellSelected;
canvas.TimelineCellUnselected -= ActiveTimelineCellUnselected;
@@ -106,8 +103,6 @@ namespace Wino.Calendar.Controls
{
if (canvas == null) return;
Debug.WriteLine("Register new canvas.");
canvas.SelectedDateTime = null;
canvas.TimelineCellSelected += ActiveTimelineCellSelected;
canvas.TimelineCellUnselected += ActiveTimelineCellUnselected;
@@ -127,13 +122,6 @@ namespace Wino.Calendar.Controls
InternalFlipView = GetTemplateChild(PART_WinoFlipView) as WinoCalendarFlipView;
}
private void FlipViewsActiveTimelineCanvasChanged(object sender, WinoDayTimelineCanvas e)
{
ActiveCanvas = e;
SelectedFlipViewDayRange = InternalFlipView.SelectedItem as DayRangeRenderModel;
}
private void ActiveTimelineCellUnselected(object sender, TimelineCellUnselectedArgs e)
=> TimelineCellUnselected?.Invoke(this, e);

View File

@@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Itenso.TimePeriod;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@@ -21,6 +22,14 @@ namespace Wino.Calendar.Controls
public static readonly DependencyProperty EventItemMarginProperty = DependencyProperty.Register(nameof(EventItemMargin), typeof(Thickness), typeof(WinoCalendarPanel), new PropertyMetadata(new Thickness(0, 0, 0, 0)));
public static readonly DependencyProperty HourHeightProperty = DependencyProperty.Register(nameof(HourHeight), typeof(double), typeof(WinoCalendarPanel), new PropertyMetadata(0d));
public static readonly DependencyProperty PeriodProperty = DependencyProperty.Register(nameof(Period), typeof(ITimePeriod), typeof(WinoCalendarPanel), new PropertyMetadata(null));
public ITimePeriod Period
{
get { return (ITimePeriod)GetValue(PeriodProperty); }
set { SetValue(PeriodProperty, value); }
}
public double HourHeight
{
@@ -41,11 +50,18 @@ namespace Wino.Calendar.Controls
private double GetChildTopMargin(ICalendarItem calendarItemViewModel, double availableHeight)
{
var childStart = calendarItemViewModel.StartTime;
var childStart = calendarItemViewModel.StartDate;
double totalMinutes = 1440;
double minutesFromStart = (childStart - childStart.DateTime.Date).TotalMinutes;
return (minutesFromStart / totalMinutes) * availableHeight;
if (childStart <= Period.Start)
{
// Event started before or exactly at the periods tart. This might be a multi-day event.
// We can simply consider event must not have a top margin.
return 0d;
}
double minutesFromStart = (childStart - Period.Start).TotalMinutes;
return (minutesFromStart / 1440) * availableHeight;
}
private double GetChildWidth(CalendarItemMeasurement calendarItemMeasurement, double availableWidth)
@@ -56,16 +72,48 @@ namespace Wino.Calendar.Controls
private double GetChildLeftMargin(CalendarItemMeasurement calendarItemMeasurement, double availableWidth)
=> availableWidth * calendarItemMeasurement.Left;
private double GetChildHeight(DateTimeOffset childStart, DateTimeOffset childEnd)
private double GetChildHeight(ICalendarItem child)
{
double totalMinutes = 1440;
double childDurationInMinutes = 0d;
double availableHeight = HourHeight * 24;
double childDuration = (childEnd - childStart).TotalMinutes;
return (childDuration / totalMinutes) * availableHeight;
var childStart = child.Period.Start;
var childEnd = child.Period.End;
// Multi-day event.
if (childStart < Period.Start)
{
if (childEnd >= Period.End)
{
// Event spans the whole period.
return availableHeight;
}
else
{
// Check how many of the event falls into the current period.
childDurationInMinutes = (childEnd - Period.Start).TotalMinutes;
}
}
else
{
childDurationInMinutes = (childEnd - childStart).TotalMinutes;
}
return (childDurationInMinutes / 1440) * availableHeight;
}
protected override Size MeasureOverride(Size availableSize)
{
ResetMeasurements();
return base.MeasureOverride(availableSize);
}
protected override Size ArrangeOverride(Size finalSize)
{
if (Period == null || HourHeight == 0d) return finalSize;
// Measure/arrange each child height and width.
// This is a vertical calendar. Therefore the height of each child is the duration of the event.
// Children weights for left and right will be saved if they don't exist.
@@ -99,7 +147,7 @@ namespace Wino.Calendar.Controls
var childMeasurement = _measurements[child.Id];
double childHeight = Math.Max(0, GetChildHeight(child.StartTime, child.StartTime.AddMinutes(child.DurationInMinutes)));
double childHeight = Math.Max(0, GetChildHeight(child));
double childWidth = Math.Max(0, GetChildWidth(childMeasurement, finalSize.Width));
double childTop = Math.Max(0, GetChildTopMargin(child, availableHeight));
double childLeft = Math.Max(0, GetChildLeftMargin(childMeasurement, availableWidth));
@@ -142,7 +190,7 @@ namespace Wino.Calendar.Controls
var columns = new List<List<ICalendarItem>>();
DateTime? lastEventEnding = null;
foreach (var ev in events.OrderBy(ev => ev.Period.Start).ThenBy(ev => ev.Period.End))
foreach (var ev in events.OrderBy(ev => ev.StartDate).ThenBy(ev => ev.EndDate))
{
if (ev.Period.Start >= lastEventEnding)
{

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
@@ -21,6 +22,16 @@ namespace Wino.Calendar.Services
private ObservableCollection<GroupedAccountCalendarViewModel> _internalGroupedAccountCalendars = new ObservableCollection<GroupedAccountCalendarViewModel>();
public IEnumerable<AccountCalendarViewModel> ActiveCalendars
{
get
{
return GroupedAccountCalendars
.SelectMany(a => a.AccountCalendars)
.Where(b => b.IsChecked);
}
}
public AccountCalendarStateService()
{
GroupedAccountCalendars = new ReadOnlyObservableCollection<GroupedAccountCalendarViewModel>(_internalGroupedAccountCalendars);

View File

@@ -17,7 +17,7 @@
<ResourceDictionary x:Key="Dark">
<!-- CalendarControl -->
<SolidColorBrush x:Key="CalendarSeperatorBrush">#000000</SolidColorBrush>
<SolidColorBrush x:Key="CalendarSeperatorBrush">#525252</SolidColorBrush>
<SolidColorBrush x:Key="CalendarFieldWorkingHoursBackgroundBrush">#32262626</SolidColorBrush>
<SolidColorBrush x:Key="CalendarFieldSelectedBackgroundBrush">#121212</SolidColorBrush>

View File

@@ -13,7 +13,11 @@
<!-- Default Calendar Item View Model Template -->
<DataTemplate x:Key="CalendarItemViewModelItemTemplate" x:DataType="data:CalendarItemViewModel">
<Grid Background="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(AssignedCalendar.BackgroundColorHex), Mode=OneWay}" CornerRadius="4">
<Grid
Background="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(AssignedCalendar.BackgroundColorHex), Mode=OneWay}"
BorderBrush="{ThemeResource CalendarSeperatorBrush}"
BorderThickness="1"
CornerRadius="6">
<TextBlock
Margin="2,0"
HorizontalAlignment="Center"
@@ -44,7 +48,7 @@
<ItemsControl ItemTemplate="{StaticResource CalendarItemViewModelItemTemplate}" ItemsSource="{x:Bind EventsCollection.RegularEvents}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:WinoCalendarPanel HourHeight="{Binding Path=CalendarRenderOptions.CalendarSettings.HourHeight}" />
<controls:WinoCalendarPanel HourHeight="{Binding Path=CalendarRenderOptions.CalendarSettings.HourHeight}" Period="{Binding Path=Period}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>

View File

@@ -44,11 +44,6 @@ namespace Wino.Calendar.Views
}
//public void Receive(GoToCalendarDayMessage message)
//{
// CalendarView.GoToDay(message.DateTime);
//}
private void PreviousDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoPreviousDateRequestedMessage());
private void NextDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoNextDateRequestedMessage());

View File

@@ -1,5 +1,6 @@
using System;
using CommunityToolkit.Mvvm.Messaging;
using Itenso.TimePeriod;
using Microsoft.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
@@ -16,7 +17,7 @@ namespace Wino.Calendar.Views
IRecipient<GoNextDateRequestedMessage>,
IRecipient<GoPreviousDateRequestedMessage>
{
private DateTime? selectedDateTime;
private DateTimeOffset? selectedDateTime;
public CalendarPage()
{
InitializeComponent();
@@ -50,7 +51,13 @@ namespace Wino.Calendar.Views
private void CellSelected(object sender, TimelineCellSelectedArgs e)
{
// Selected date is in Local kind.
selectedDateTime = e.ClickedDate;
var utc = DateTime.SpecifyKind(e.ClickedDate, DateTimeKind.Utc);
var unspecified = DateTime.SpecifyKind(e.ClickedDate, DateTimeKind.Unspecified);
var putc = new TimeRange(utc, utc.AddMinutes(30));
var punspecified = new TimeRange(unspecified, unspecified.AddMinutes(30));
// TODO: Popup is not positioned well on daily view.
TeachingTipPositionerGrid.Width = e.CellSize.Width;
@@ -59,8 +66,19 @@ namespace Wino.Calendar.Views
Canvas.SetLeft(TeachingTipPositionerGrid, e.PositionerPoint.X);
Canvas.SetTop(TeachingTipPositionerGrid, e.PositionerPoint.Y);
// TODO: End time can be from settings.
// WeakReferenceMessenger.Default.Send(new CalendarEventAdded(new CalendarItem(selectedDateTime.Value, selectedDateTime.Value.AddMinutes(30))));
//var testCalendarItem = new CalendarItem
//{
// CalendarId = Guid.Parse("9ead7613-dacb-4163-8d33-2e32e65008a1"),
// StartTime = selectedDateTime.Value, // All events are saved in UTC.
// DurationInMinutes = 30,
// CreatedAt = DateTime.UtcNow,
// Description = "Test Description",
// Location = "Poland",
// Title = "Test event",
// Id = Guid.NewGuid()
//};
//WeakReferenceMessenger.Default.Send(new CalendarEventAdded(testCalendarItem));
NewEventTip.IsOpen = true;
}