Proper handling of DateTimeOffset, support for Multi-Day events and reacting to adding/removing events for the days.
This commit is contained in:
@@ -258,13 +258,6 @@ namespace Wino.Calendar.ViewModels
|
||||
private readonly IAccountService _accountService;
|
||||
private readonly ICalendarService _calendarService;
|
||||
|
||||
//public override void OnPageLoaded()
|
||||
//{
|
||||
// base.OnPageLoaded();
|
||||
|
||||
// TodayClicked();
|
||||
//}
|
||||
|
||||
#region Commands
|
||||
|
||||
[RelayCommand]
|
||||
@@ -341,7 +334,5 @@ namespace Wino.Calendar.ViewModels
|
||||
=> await ExecuteUIThread(() => IsCalendarEnabled = message.IsEnabled);
|
||||
|
||||
public void Receive(NavigateManageAccountsRequested message) => SelectedMenuItemIndex = 1;
|
||||
|
||||
//public void Receive(GoToCalendarDayMessage message) => SelectedMenuItemIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,12 @@ using System.Threading.Tasks;
|
||||
using CommunityToolkit.Diagnostics;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using MoreLinq;
|
||||
using Serilog;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Calendar.ViewModels.Interfaces;
|
||||
using Wino.Core.Domain.Collections;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
@@ -70,20 +72,16 @@ namespace Wino.Calendar.ViewModels
|
||||
}
|
||||
|
||||
private void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e)
|
||||
{
|
||||
// For all date ranges, update the events.
|
||||
foreach (var dayRange in DayRanges)
|
||||
{
|
||||
_ = InitializeCalendarEventsForDayRangeAsync(dayRange);
|
||||
}
|
||||
}
|
||||
=> FilterActiveCalendars(DayRanges);
|
||||
|
||||
private void UpdateAccountCalendarRequested(object sender, AccountCalendarViewModel e)
|
||||
=> FilterActiveCalendars(DayRanges);
|
||||
|
||||
private void FilterActiveCalendars(IEnumerable<DayRangeRenderModel> dayRangeRenderModels)
|
||||
{
|
||||
foreach (var range in DayRanges)
|
||||
{
|
||||
_ = InitializeCalendarEventsForDayRangeAsync(range);
|
||||
}
|
||||
var days = dayRangeRenderModels.SelectMany(a => a.CalendarDays);
|
||||
|
||||
days.ForEach(a => a.EventsCollection.FilterByCalendars(_accountCalendarStateService.ActiveCalendars.Select(a => a.Id)));
|
||||
}
|
||||
|
||||
// TODO: Replace when calendar settings are updated.
|
||||
@@ -297,6 +295,9 @@ namespace Wino.Calendar.ViewModels
|
||||
await InitializeCalendarEventsForDayRangeAsync(renderModel).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Filter by active calendars. This is a quick operation, and things are not on the UI yet.
|
||||
FilterActiveCalendars(renderModels);
|
||||
|
||||
CalendarLoadDirection animationDirection = calendarLoadDirection;
|
||||
|
||||
bool removeCurrent = calendarLoadDirection == CalendarLoadDirection.Replace;
|
||||
@@ -372,22 +373,39 @@ namespace Wino.Calendar.ViewModels
|
||||
}
|
||||
|
||||
// TODO...
|
||||
private async void EventsUpdatedInDayHeader(object sender, CalendarDayModel e)
|
||||
private void EventsUpdatedInDayHeader(object sender, CalendarDayModel e)
|
||||
{
|
||||
// Find the day range model that contains the day model.
|
||||
// TODO: Maybe optimize by just updating the day?
|
||||
|
||||
if (sender is DayRangeRenderModel dayRangeRenderModel)
|
||||
{
|
||||
await InitializeCalendarEventsForDayRangeAsync(dayRangeRenderModel);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async void OnCalendarEventAdded(CalendarItem calendarItem)
|
||||
{
|
||||
base.OnCalendarEventAdded(calendarItem);
|
||||
|
||||
//var dayRange = DayRanges.FirstOrDefault(a => a.CalendarDays.Contains(e));
|
||||
// test
|
||||
var calendar = await _calendarService.GetAccountCalendarAsync(Guid.Parse("9ead7613-dacb-4163-8d33-2e32e65008a1"));
|
||||
|
||||
//if (dayRange == null) return;
|
||||
calendarItem.AssignedCalendar = calendar;
|
||||
// Check if event falls into the current date range.
|
||||
|
||||
//await InitializeCalendarEventsForDayRangeAsync(dayRange);
|
||||
var loadedDateRange = GetLoadedDateRange();
|
||||
|
||||
if (loadedDateRange == null) return;
|
||||
|
||||
// Check whether this event falls into any of the loaded date ranges.
|
||||
|
||||
//if (calendarItem.Period.Start >= loadedDateRange.StartDate && calendarItem.Period.Start.Date <= loadedDateRange.EndDate)
|
||||
//{
|
||||
// // Find the day representation for the event.
|
||||
// var dayModel = DayRanges.SelectMany(a => a.CalendarDays).FirstOrDefault(a => a.RepresentingDate.Date == calendarItem.Period.Start.Date);
|
||||
// if (dayModel == null) return;
|
||||
|
||||
// var calendarItemViewModel = new CalendarItemViewModel(calendarItem);
|
||||
// await ExecuteUIThread(() =>
|
||||
// {
|
||||
// dayModel.EventsCollection.AddCalendarItem(calendarItemViewModel);
|
||||
// });
|
||||
//}
|
||||
}
|
||||
|
||||
private async Task InitializeCalendarEventsForDayRangeAsync(DayRangeRenderModel dayRangeRenderModel)
|
||||
@@ -401,38 +419,32 @@ namespace Wino.Calendar.ViewModels
|
||||
});
|
||||
}
|
||||
|
||||
// Load for each selected calendar from the state.
|
||||
var checkedCalendarViewModels = _accountCalendarStateService.GroupedAccountCalendars
|
||||
.SelectMany(a => a.AccountCalendars)
|
||||
.Where(b => b.IsChecked);
|
||||
// Initialization is done for all calendars, regardless whether they are actively selected or not.
|
||||
// This is because the filtering is cached internally of the calendar items in CalendarEventCollection.
|
||||
var allCalendars = _accountCalendarStateService.GroupedAccountCalendars.SelectMany(a => a.AccountCalendars);
|
||||
|
||||
foreach (var calendarViewModel in checkedCalendarViewModels)
|
||||
foreach (var calendarViewModel in allCalendars)
|
||||
{
|
||||
// Check all the events for the given date range and calendar.
|
||||
// Then find the day representation for all the events returned, and add to the collection.
|
||||
|
||||
var events = await _calendarService.GetCalendarEventsAsync(calendarViewModel,
|
||||
dayRangeRenderModel.Period.Start,
|
||||
dayRangeRenderModel.Period.End)
|
||||
.ConfigureAwait(false);
|
||||
var events = await _calendarService.GetCalendarEventsAsync(calendarViewModel, dayRangeRenderModel).ConfigureAwait(false);
|
||||
|
||||
var groupedEvents = events.GroupBy(a => a.StartTime.Date);
|
||||
|
||||
foreach (var group in groupedEvents)
|
||||
foreach (var @event in events)
|
||||
{
|
||||
var startDate = group.Key;
|
||||
// Find the days that the event falls into.
|
||||
// TODO: Multi-day events are not fully supported yet.
|
||||
|
||||
var calendarDayModel = dayRangeRenderModel.CalendarDays.FirstOrDefault(a => a.RepresentingDate.Date == startDate);
|
||||
var allDaysForEvent = dayRangeRenderModel.CalendarDays.Where(a => a.Period.OverlapsWith(@event.Period));
|
||||
|
||||
if (calendarDayModel == null) continue;
|
||||
|
||||
var calendarItemViewModels = group.Select(a => new CalendarItemViewModel(a));
|
||||
|
||||
await ExecuteUIThread(() =>
|
||||
foreach (var calendarDay in allDaysForEvent)
|
||||
{
|
||||
// Use range-based add for performance.
|
||||
calendarDayModel.EventsCollection.AddCalendarItemRange(calendarItemViewModels);
|
||||
});
|
||||
var calendarItemViewModel = new CalendarItemViewModel(@event);
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
calendarDay.EventsCollection.AddCalendarItem(calendarItemViewModel);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -532,7 +544,7 @@ namespace Wino.Calendar.ViewModels
|
||||
// TODO: This might need throttling due to slider in the settings page for hour height.
|
||||
// or make sure the slider does not update on each tick but on focus lost.
|
||||
|
||||
Messenger.Send(new LoadCalendarMessage(DateTime.UtcNow.Date, CalendarInitInitiative.App, true));
|
||||
// Messenger.Send(new LoadCalendarMessage(DateTime.UtcNow.Date, CalendarInitInitiative.App, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,15 @@ namespace Wino.Calendar.ViewModels.Data
|
||||
|
||||
public Guid Id => CalendarItem.Id;
|
||||
|
||||
public DateTimeOffset StartTime => CalendarItem.StartTime;
|
||||
public IAccountCalendar AssignedCalendar => CalendarItem.AssignedCalendar;
|
||||
|
||||
public int DurationInMinutes => CalendarItem.DurationInMinutes;
|
||||
public DateTime StartDate { get => CalendarItem.StartDate; set => CalendarItem.StartDate = value; }
|
||||
|
||||
public TimeRange Period => CalendarItem.Period;
|
||||
public DateTime EndDate => CalendarItem.EndDate;
|
||||
|
||||
public IAccountCalendar AssignedCalendar => ((ICalendarItem)CalendarItem).AssignedCalendar;
|
||||
public double DurationInSeconds { get => CalendarItem.DurationInSeconds; set => CalendarItem.DurationInSeconds = value; }
|
||||
|
||||
public ITimePeriod Period => CalendarItem.Period;
|
||||
|
||||
public CalendarItemViewModel(CalendarItem calendarItem)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
|
||||
@@ -17,5 +18,10 @@ namespace Wino.Calendar.ViewModels.Interfaces
|
||||
|
||||
public void AddAccountCalendar(AccountCalendarViewModel accountCalendar);
|
||||
public void RemoveAccountCalendar(AccountCalendarViewModel accountCalendar);
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration of currently selected calendars.
|
||||
/// </summary>
|
||||
IEnumerable<AccountCalendarViewModel> ActiveCalendars { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Itenso.TimePeriod;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
@@ -12,9 +13,6 @@ namespace Wino.Core.Domain.Collections
|
||||
public event EventHandler<ICalendarItem> CalendarItemAdded;
|
||||
public event EventHandler<ICalendarItem> CalendarItemRemoved;
|
||||
|
||||
public event EventHandler<List<ICalendarItem>> CalendarItemRangeAdded;
|
||||
public event EventHandler<List<ICalendarItem>> CalendarItemRangeRemoved;
|
||||
|
||||
public event EventHandler CalendarItemsCleared;
|
||||
|
||||
private ObservableRangeCollection<ICalendarItem> _internalRegularEvents = [];
|
||||
@@ -22,79 +20,97 @@ namespace Wino.Core.Domain.Collections
|
||||
|
||||
public ReadOnlyObservableCollection<ICalendarItem> RegularEvents { get; }
|
||||
public ReadOnlyObservableCollection<ICalendarItem> AllDayEvents { get; }
|
||||
public ITimePeriod Period { get; }
|
||||
|
||||
public CalendarEventCollection()
|
||||
private readonly List<ICalendarItem> _allItems = new List<ICalendarItem>();
|
||||
|
||||
public CalendarEventCollection(ITimePeriod period)
|
||||
{
|
||||
RegularEvents = new ReadOnlyObservableCollection<ICalendarItem>(_internalRegularEvents);
|
||||
AllDayEvents = new ReadOnlyObservableCollection<ICalendarItem>(_internalAllDayEvents);
|
||||
Period = period;
|
||||
}
|
||||
|
||||
public bool HasCalendarEvent(AccountCalendar accountCalendar)
|
||||
=> _allItems.Any(x => x.AssignedCalendar.Id == accountCalendar.Id);
|
||||
|
||||
public void FilterByCalendars(IEnumerable<Guid> visibleCalendarIds)
|
||||
{
|
||||
return _internalAllDayEvents.Any(x => x.AssignedCalendar.Id == accountCalendar.Id) ||
|
||||
_internalRegularEvents.Any(x => x.AssignedCalendar.Id == accountCalendar.Id);
|
||||
foreach (var item in _allItems)
|
||||
{
|
||||
var collection = GetProperCollectionForCalendarItem(item);
|
||||
|
||||
if (!visibleCalendarIds.Contains(item.AssignedCalendar.Id) && collection.Contains(item))
|
||||
{
|
||||
RemoveCalendarItemInternal(collection, item, false);
|
||||
}
|
||||
else if (visibleCalendarIds.Contains(item.AssignedCalendar.Id) && !collection.Contains(item))
|
||||
{
|
||||
AddCalendarItemInternal(collection, item, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddCalendarItemRange(IEnumerable<ICalendarItem> calendarItems)
|
||||
private ObservableRangeCollection<ICalendarItem> GetProperCollectionForCalendarItem(ICalendarItem calendarItem)
|
||||
{
|
||||
foreach (var calendarItem in calendarItems)
|
||||
{
|
||||
AddCalendarItem(calendarItem);
|
||||
}
|
||||
// Event duration is not simply enough to determine whether it's an all-day event or not.
|
||||
// Event may start at 11:00 PM and end next day at 11:00 PM. It's not an all-day event.
|
||||
// It's a multi-day event.
|
||||
|
||||
CalendarItemRangeAdded?.Invoke(this, new List<ICalendarItem>(calendarItems));
|
||||
}
|
||||
bool isAllDayEvent = calendarItem.Period.Duration.TotalDays == 1 && calendarItem.Period.Start.TimeOfDay == TimeSpan.Zero;
|
||||
|
||||
public void RemoveCalendarItemRange(IEnumerable<ICalendarItem> calendarItems)
|
||||
{
|
||||
foreach (var calendarItem in calendarItems)
|
||||
{
|
||||
RemoveCalendarItem(calendarItem);
|
||||
}
|
||||
|
||||
CalendarItemRangeRemoved?.Invoke(this, new List<ICalendarItem>(calendarItems));
|
||||
return isAllDayEvent ? _internalAllDayEvents : _internalRegularEvents;
|
||||
}
|
||||
|
||||
public void AddCalendarItem(ICalendarItem calendarItem)
|
||||
{
|
||||
var collection = GetProperCollectionForCalendarItem(calendarItem);
|
||||
AddCalendarItemInternal(collection, calendarItem);
|
||||
}
|
||||
|
||||
public void RemoveCalendarItem(ICalendarItem calendarItem)
|
||||
{
|
||||
var collection = GetProperCollectionForCalendarItem(calendarItem);
|
||||
RemoveCalendarItemInternal(collection, calendarItem);
|
||||
}
|
||||
|
||||
private void AddCalendarItemInternal(ObservableRangeCollection<ICalendarItem> collection, ICalendarItem calendarItem, bool create = true)
|
||||
{
|
||||
if (calendarItem is not ICalendarItemViewModel)
|
||||
throw new ArgumentException("CalendarItem must be of type ICalendarItemViewModel", nameof(calendarItem));
|
||||
|
||||
if (calendarItem.Period.Duration.TotalMinutes == 1440)
|
||||
collection.Add(calendarItem);
|
||||
|
||||
if (create)
|
||||
{
|
||||
_internalAllDayEvents.Add(calendarItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
_internalRegularEvents.Add(calendarItem);
|
||||
_allItems.Add(calendarItem);
|
||||
}
|
||||
|
||||
CalendarItemAdded?.Invoke(this, calendarItem);
|
||||
}
|
||||
|
||||
private void RemoveCalendarItemInternal(ObservableRangeCollection<ICalendarItem> collection, ICalendarItem calendarItem, bool destroy = true)
|
||||
{
|
||||
if (calendarItem is not ICalendarItemViewModel)
|
||||
throw new ArgumentException("CalendarItem must be of type ICalendarItemViewModel", nameof(calendarItem));
|
||||
|
||||
collection.Remove(calendarItem);
|
||||
|
||||
if (destroy)
|
||||
{
|
||||
_allItems.Remove(calendarItem);
|
||||
}
|
||||
|
||||
CalendarItemRemoved?.Invoke(this, calendarItem);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_internalAllDayEvents.Clear();
|
||||
_internalRegularEvents.Clear();
|
||||
_allItems.Clear();
|
||||
|
||||
CalendarItemsCleared?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void RemoveCalendarItem(ICalendarItem calendarItem)
|
||||
{
|
||||
if (calendarItem is not ICalendarItemViewModel)
|
||||
throw new ArgumentException("CalendarItem must be of type ICalendarItemViewModel", nameof(calendarItem));
|
||||
|
||||
if (calendarItem.Period.Duration.TotalMinutes == 1440)
|
||||
{
|
||||
_internalAllDayEvents.Remove(calendarItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
_internalRegularEvents.Remove(calendarItem);
|
||||
}
|
||||
|
||||
CalendarItemRemoved?.Invoke(this, calendarItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,18 +13,43 @@ namespace Wino.Core.Domain.Entities.Calendar
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Location { get; set; }
|
||||
public DateTimeOffset StartTime { get; set; }
|
||||
public int DurationInMinutes { get; set; }
|
||||
|
||||
public DateTime StartDate { get; set; }
|
||||
|
||||
public DateTime EndDate
|
||||
{
|
||||
get
|
||||
{
|
||||
return StartDate.AddSeconds(DurationInSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
public TimeSpan StartDateOffset { get; set; }
|
||||
public TimeSpan EndDateOffset { get; set; }
|
||||
|
||||
private ITimePeriod _period;
|
||||
public ITimePeriod Period
|
||||
{
|
||||
get
|
||||
{
|
||||
_period ??= new TimeRange(StartDate, EndDate);
|
||||
|
||||
return _period;
|
||||
}
|
||||
}
|
||||
|
||||
public double DurationInSeconds { get; set; }
|
||||
public string Recurrence { get; set; }
|
||||
|
||||
// TODO
|
||||
public string CustomEventColorHex { get; set; }
|
||||
public string HtmlLink { get; set; }
|
||||
public CalendarItemStatus Status { get; set; }
|
||||
public CalendarItemVisibility Visibility { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public DateTimeOffset UpdatedAt { get; set; }
|
||||
public Guid CalendarId { get; set; }
|
||||
|
||||
[Ignore]
|
||||
public TimeRange Period => new TimeRange(StartTime.Date, StartTime.Date.AddMinutes(DurationInMinutes));
|
||||
|
||||
[Ignore]
|
||||
public IAccountCalendar AssignedCalendar { get; set; }
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@ namespace Wino.Core.Domain.Interfaces
|
||||
{
|
||||
string Title { get; }
|
||||
Guid Id { get; }
|
||||
DateTimeOffset StartTime { get; }
|
||||
int DurationInMinutes { get; }
|
||||
TimeRange Period { get; }
|
||||
IAccountCalendar AssignedCalendar { get; }
|
||||
DateTime StartDate { get; set; }
|
||||
DateTime EndDate { get; }
|
||||
double DurationInSeconds { get; set; }
|
||||
ITimePeriod Period { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,20 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces
|
||||
{
|
||||
public interface ICalendarService
|
||||
{
|
||||
Task<List<AccountCalendar>> GetAccountCalendarsAsync(Guid accountId);
|
||||
Task<AccountCalendar> GetAccountCalendarAsync(Guid accountCalendarId);
|
||||
Task DeleteCalendarItemAsync(Guid calendarItemId);
|
||||
|
||||
Task DeleteAccountCalendarAsync(AccountCalendar accountCalendar);
|
||||
Task InsertAccountCalendarAsync(AccountCalendar accountCalendar);
|
||||
Task UpdateAccountCalendarAsync(AccountCalendar accountCalendar);
|
||||
Task CreateNewCalendarItemAsync(CalendarItem calendarItem, List<CalendarEventAttendee> attendees);
|
||||
Task<List<CalendarItem>> GetCalendarEventsAsync(IAccountCalendar calendar, DateTime rangeStart, DateTime rangeEnd);
|
||||
Task<List<CalendarItem>> GetCalendarEventsAsync(IAccountCalendar calendar, DayRangeRenderModel dayRangeRenderModel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,14 +10,15 @@ namespace Wino.Core.Domain.Models.Calendar
|
||||
/// </summary>
|
||||
public class CalendarDayModel
|
||||
{
|
||||
public TimeRange Period { get; }
|
||||
public CalendarEventCollection EventsCollection { get; } = new CalendarEventCollection();
|
||||
public ITimePeriod Period { get; }
|
||||
public CalendarEventCollection EventsCollection { get; }
|
||||
|
||||
public CalendarDayModel(DateTime representingDate, CalendarRenderOptions calendarRenderOptions)
|
||||
{
|
||||
RepresentingDate = representingDate;
|
||||
Period = new TimeRange(representingDate, representingDate.AddDays(1));
|
||||
CalendarRenderOptions = calendarRenderOptions;
|
||||
EventsCollection = new CalendarEventCollection(Period);
|
||||
}
|
||||
|
||||
public DateTime RepresentingDate { get; }
|
||||
|
||||
@@ -56,25 +56,16 @@ namespace Wino.Core.Domain.Models.Calendar
|
||||
private void RegisterCalendarDayEvents(CalendarDayModel calendarDayModel)
|
||||
{
|
||||
calendarDayModel.EventsCollection.CalendarItemAdded += CalendarItemAdded;
|
||||
calendarDayModel.EventsCollection.CalendarItemRangeRemoved += CalendarItemRangeRemoved;
|
||||
calendarDayModel.EventsCollection.CalendarItemRemoved += CalendarItemRemoved;
|
||||
calendarDayModel.EventsCollection.CalendarItemRangeAdded += CalendarItemRangeAdded;
|
||||
}
|
||||
|
||||
// TODO: These handlers have incorrect senders. They should be the CalendarDayModel.
|
||||
|
||||
private void CalendarItemRangeAdded(object sender, List<ICalendarItem> e)
|
||||
=> CalendarDayEventCollectionUpdated?.Invoke(this, sender as CalendarDayModel);
|
||||
|
||||
private void CalendarItemRemoved(object sender, ICalendarItem e)
|
||||
=> CalendarDayEventCollectionUpdated?.Invoke(this, sender as CalendarDayModel);
|
||||
|
||||
private void CalendarItemAdded(object sender, ICalendarItem e)
|
||||
=> CalendarDayEventCollectionUpdated?.Invoke(this, sender as CalendarDayModel);
|
||||
|
||||
private void CalendarItemRangeRemoved(object sender, List<ICalendarItem> e)
|
||||
=> CalendarDayEventCollectionUpdated?.Invoke(this, sender as CalendarDayModel);
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters all calendar item change listeners to draw the UI for calendar events.
|
||||
/// </summary>
|
||||
@@ -82,9 +73,7 @@ namespace Wino.Core.Domain.Models.Calendar
|
||||
{
|
||||
foreach (var day in CalendarDays)
|
||||
{
|
||||
day.EventsCollection.CalendarItemRangeRemoved -= CalendarItemRangeRemoved;
|
||||
day.EventsCollection.CalendarItemRemoved -= CalendarItemRemoved;
|
||||
day.EventsCollection.CalendarItemRangeAdded -= CalendarItemRangeAdded;
|
||||
day.EventsCollection.CalendarItemAdded -= CalendarItemAdded;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Messaging.Client.Calendar;
|
||||
|
||||
namespace Wino.Core.ViewModels
|
||||
@@ -8,6 +8,6 @@ namespace Wino.Core.ViewModels
|
||||
{
|
||||
public void Receive(CalendarEventAdded message) => OnCalendarEventAdded(message.CalendarItem);
|
||||
|
||||
protected virtual void OnCalendarEventAdded(ICalendarItem calendarItem) { }
|
||||
protected virtual void OnCalendarEventAdded(CalendarItem calendarItem) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,88 +209,74 @@ namespace Wino.Core.Extensions
|
||||
/// </summary>
|
||||
/// <param name="calendarEvent">The Google Calendar Event object.</param>
|
||||
/// <returns>The start DateTimeOffset of the event, or null if it cannot be determined.</returns>
|
||||
public static DateTimeOffset? GetEventStartDateTimeOffset(this Event calendarEvent)
|
||||
{
|
||||
if (calendarEvent == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
//public static DateTimeOffset GetEventStartDateTimeOffset(this Event calendarEvent)
|
||||
//{
|
||||
// return GetEventDateTimeOffset(calendarEvent.Start);
|
||||
//}
|
||||
|
||||
if (calendarEvent.Start != null)
|
||||
public static DateTimeOffset GetEventDateTimeOffset(EventDateTime calendarEvent)
|
||||
{
|
||||
if (calendarEvent != null)
|
||||
{
|
||||
if (calendarEvent.Start.DateTimeDateTimeOffset != null)
|
||||
if (calendarEvent.DateTimeDateTimeOffset != null)
|
||||
{
|
||||
return calendarEvent.Start.DateTimeDateTimeOffset; // Use the direct DateTimeOffset property!
|
||||
return calendarEvent.DateTimeDateTimeOffset.Value;
|
||||
}
|
||||
else if (calendarEvent.Start.Date != null)
|
||||
else if (calendarEvent.Date != null)
|
||||
{
|
||||
if (DateTime.TryParse(calendarEvent.Start.Date, out DateTime startDate))
|
||||
if (DateTime.TryParse(calendarEvent.Date, out DateTime eventDateTime))
|
||||
{
|
||||
// Date-only events are treated as UTC midnight
|
||||
return new DateTimeOffset(startDate, TimeSpan.Zero);
|
||||
return new DateTimeOffset(eventDateTime, TimeSpan.Zero);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
throw new Exception("Invalid date format in Google Calendar event date.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null; // Start time not found
|
||||
throw new Exception("Google Calendar event has no date.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the duration of a Google Calendar Event in minutes.
|
||||
/// Calculates the duration of a Google Calendar Event in seconds.
|
||||
/// Handles date-only and date-time events, but *does not* handle recurring events correctly.
|
||||
/// For recurring events, this method will return the duration of the *first* instance.
|
||||
/// </summary>
|
||||
/// <param name="calendarEvent">The Google Calendar Event object.</param>
|
||||
/// <returns>The duration of the event in minutes, or null if it cannot be determined.</returns>
|
||||
public static int? GetEventDurationInMinutes(this Event calendarEvent)
|
||||
{
|
||||
if (calendarEvent == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
//public static int GetEventDurationInSeconds(this Event calendarEvent)
|
||||
//{
|
||||
// var start = calendarEvent.GetEventStartDateTimeOffset();
|
||||
|
||||
DateTimeOffset? start = calendarEvent.GetEventStartDateTimeOffset();
|
||||
if (start == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// DateTimeOffset? end = null;
|
||||
// if (calendarEvent.End != null)
|
||||
// {
|
||||
// if (calendarEvent.End.DateTimeDateTimeOffset != null)
|
||||
// {
|
||||
// end = calendarEvent.End.DateTimeDateTimeOffset;
|
||||
// }
|
||||
// else if (calendarEvent.End.Date != null)
|
||||
// {
|
||||
// if (DateTime.TryParse(calendarEvent.End.Date, out DateTime endDate))
|
||||
// {
|
||||
// end = new DateTimeOffset(endDate, TimeSpan.Zero);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// throw new Exception("Invalid date format in Google Calendar event end date.");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
DateTimeOffset? end = null;
|
||||
if (calendarEvent.End != null)
|
||||
{
|
||||
if (calendarEvent.End.DateTimeDateTimeOffset != null)
|
||||
{
|
||||
end = calendarEvent.End.DateTimeDateTimeOffset;
|
||||
}
|
||||
else if (calendarEvent.End.Date != null)
|
||||
{
|
||||
if (DateTime.TryParse(calendarEvent.End.Date, out DateTime endDate))
|
||||
{
|
||||
end = new DateTimeOffset(endDate, TimeSpan.Zero);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (end == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int)(end.Value - start.Value).TotalMinutes;
|
||||
}
|
||||
// if (end == null) throw new Exception("Google Calendar event has no end date.");
|
||||
|
||||
// return (int)(end.Value - start).TotalSeconds;
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// RRULE, EXRULE, RDATE and EXDATE lines for a recurring event, as specified in RFC5545.
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>___ separated lines.</returns>
|
||||
public static string GetRecurrenceString(this Event calendarEvent)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Google.Apis.Calendar.v3.Data;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
@@ -33,22 +34,32 @@ namespace Wino.Core.Integration.Processors
|
||||
|
||||
public async Task<CalendarItem> CreateCalendarItemAsync(Event calendarEvent, AccountCalendar assignedCalendar, MailAccount organizerAccount)
|
||||
{
|
||||
var eventStartDateTimeOffset = GoogleIntegratorExtensions.GetEventDateTimeOffset(calendarEvent.Start);
|
||||
var eventEndDateTimeOffset = GoogleIntegratorExtensions.GetEventDateTimeOffset(calendarEvent.End);
|
||||
|
||||
var totalDurationInSeconds = (eventEndDateTimeOffset - eventStartDateTimeOffset).TotalSeconds;
|
||||
|
||||
var calendarItem = new CalendarItem()
|
||||
{
|
||||
CalendarId = assignedCalendar.Id,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
Description = calendarEvent.Description,
|
||||
StartTime = GoogleIntegratorExtensions.GetEventStartDateTimeOffset(calendarEvent) ?? throw new Exception("Event without a start time."),
|
||||
DurationInMinutes = GoogleIntegratorExtensions.GetEventDurationInMinutes(calendarEvent) ?? throw new Exception("Event without a duration."),
|
||||
Id = Guid.NewGuid(),
|
||||
StartDate = eventStartDateTimeOffset.DateTime,
|
||||
StartDateOffset = eventStartDateTimeOffset.Offset,
|
||||
EndDateOffset = eventEndDateTimeOffset.Offset,
|
||||
DurationInSeconds = totalDurationInSeconds,
|
||||
Location = calendarEvent.Location,
|
||||
Recurrence = GoogleIntegratorExtensions.GetRecurrenceString(calendarEvent),
|
||||
Status = GetStatus(calendarEvent.Status),
|
||||
Title = calendarEvent.Summary,
|
||||
UpdatedAt = DateTimeOffset.UtcNow,
|
||||
Visibility = GetVisibility(calendarEvent.Visibility),
|
||||
HtmlLink = calendarEvent.HtmlLink
|
||||
};
|
||||
|
||||
Debug.WriteLine($"({assignedCalendar.Name}) {calendarItem.Title}, Start: {calendarItem.StartDate.ToString("f")}, End: {calendarItem.EndDate.ToString("f")}");
|
||||
|
||||
// TODO: There are some edge cases with cancellation here.
|
||||
CalendarItemStatus GetStatus(string status)
|
||||
{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
|
||||
namespace Wino.Messaging.Client.Calendar
|
||||
{
|
||||
/// <summary>
|
||||
/// Raised when event is added to database.
|
||||
/// </summary>
|
||||
public record CalendarEventAdded(ICalendarItem CalendarItem);
|
||||
public record CalendarEventAdded(CalendarItem CalendarItem);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Ical.Net.CalendarComponents;
|
||||
using Ical.Net.DataTypes;
|
||||
using SqlKata;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Messaging.Client.Calendar;
|
||||
using Wino.Services.Extensions;
|
||||
|
||||
@@ -71,7 +73,7 @@ namespace Wino.Services
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<List<CalendarItem>> GetCalendarEventsAsync(IAccountCalendar calendar, DateTime rangeStart, DateTime rangeEnd)
|
||||
public async Task<List<CalendarItem>> GetCalendarEventsAsync(IAccountCalendar calendar, DayRangeRenderModel dayRangeRenderModel)
|
||||
{
|
||||
// TODO: We might need to implement caching here.
|
||||
// I don't know how much of the events we'll have in total, but this logic scans all events every time.
|
||||
@@ -84,19 +86,22 @@ namespace Wino.Services
|
||||
ev.AssignedCalendar = calendar;
|
||||
|
||||
// Parse recurrence rules
|
||||
var calendarEvent = new Ical.Net.CalendarComponents.CalendarEvent
|
||||
var calendarEvent = new CalendarEvent
|
||||
{
|
||||
Start = new CalDateTime(ev.StartTime.UtcDateTime),
|
||||
Duration = TimeSpan.FromMinutes(ev.DurationInMinutes),
|
||||
Start = new CalDateTime(ev.StartDate),
|
||||
End = new CalDateTime(ev.EndDate),
|
||||
};
|
||||
|
||||
if (string.IsNullOrEmpty(ev.Recurrence))
|
||||
{
|
||||
// No recurrence, only check if we fall into the date range.
|
||||
// All events are saved in UTC, so we need to convert the range to UTC as well.
|
||||
if (ev.StartTime.UtcDateTime < rangeEnd
|
||||
&& ev.StartTime.UtcDateTime.AddMinutes(ev.DurationInMinutes) > rangeStart)
|
||||
// No recurrence, only check if we fall into the given period.
|
||||
|
||||
if (ev.Period.OverlapsWith(dayRangeRenderModel.Period))
|
||||
{
|
||||
// TODO: We overlap, but this might be a multi-day event.
|
||||
// Should we split the events here or in panel?
|
||||
// For now just continue.
|
||||
|
||||
result.Add(ev);
|
||||
}
|
||||
}
|
||||
@@ -110,7 +115,7 @@ namespace Wino.Services
|
||||
}
|
||||
|
||||
// Calculate occurrences in the range.
|
||||
var occurrences = calendarEvent.GetOccurrences(rangeStart, rangeEnd);
|
||||
var occurrences = calendarEvent.GetOccurrences(dayRangeRenderModel.Period.Start, dayRangeRenderModel.Period.End);
|
||||
|
||||
foreach (var occurrence in occurrences)
|
||||
{
|
||||
@@ -121,5 +126,8 @@ namespace Wino.Services
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task<AccountCalendar> GetAccountCalendarAsync(Guid accountCalendarId)
|
||||
=> Connection.GetAsync<AccountCalendar>(accountCalendarId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user