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

@@ -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);
}
}
}

View File

@@ -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; }
}

View File

@@ -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; }
}
}

View File

@@ -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);
}
}

View File

@@ -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; }

View File

@@ -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;
}
}