Handling of multi-day events, new rendering etc.

This commit is contained in:
Burak Kaan Köse
2025-01-04 11:39:32 +01:00
parent 48ba4cdf42
commit a7674d436d
33 changed files with 842 additions and 382 deletions

View File

@@ -5,6 +5,7 @@ using System.Linq;
using Itenso.TimePeriod;
using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Calendar;
namespace Wino.Core.Domain.Collections
{
@@ -19,14 +20,16 @@ namespace Wino.Core.Domain.Collections
private ObservableRangeCollection<ICalendarItem> _internalAllDayEvents = [];
public ReadOnlyObservableCollection<ICalendarItem> RegularEvents { get; }
public ReadOnlyObservableCollection<ICalendarItem> AllDayEvents { get; }
public ReadOnlyObservableCollection<ICalendarItem> AllDayEvents { get; } // TODO: Rename this to include multi-day events.
public ITimePeriod Period { get; }
public CalendarSettings Settings { get; }
private readonly List<ICalendarItem> _allItems = new List<ICalendarItem>();
public CalendarEventCollection(ITimePeriod period)
public CalendarEventCollection(ITimePeriod period, CalendarSettings settings)
{
Period = period;
Settings = settings;
RegularEvents = new ReadOnlyObservableCollection<ICalendarItem>(_internalRegularEvents);
AllDayEvents = new ReadOnlyObservableCollection<ICalendarItem>(_internalAllDayEvents);
@@ -44,40 +47,67 @@ namespace Wino.Core.Domain.Collections
{
foreach (var item in _allItems)
{
var collection = GetProperCollectionForCalendarItem(item);
var collections = GetProperCollectionsForCalendarItem(item);
if (!visibleCalendarIds.Contains(item.AssignedCalendar.Id) && collection.Contains(item))
foreach (var collection in collections)
{
RemoveCalendarItemInternal(collection, item, false);
}
else if (visibleCalendarIds.Contains(item.AssignedCalendar.Id) && !collection.Contains(item))
{
AddCalendarItemInternal(collection, item, false);
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);
}
}
}
}
private ObservableRangeCollection<ICalendarItem> GetProperCollectionForCalendarItem(ICalendarItem calendarItem)
private IEnumerable<ObservableRangeCollection<ICalendarItem>> GetProperCollectionsForCalendarItem(ICalendarItem 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.
// All-day events go to all days.
// Multi-day events go to both.
// Anything else goes to regular.
bool isAllDayEvent = calendarItem.Period.Duration.TotalDays == 1 && calendarItem.Period.Start.TimeOfDay == TimeSpan.Zero;
return isAllDayEvent ? _internalAllDayEvents : _internalRegularEvents;
if (calendarItem.IsAllDayEvent)
{
return [_internalAllDayEvents];
}
else if (calendarItem.IsMultiDayEvent)
{
if (Settings.GhostRenderAllDayItems)
{
return [_internalRegularEvents, _internalAllDayEvents];
}
else
{
return [_internalAllDayEvents];
}
}
else
{
return [_internalRegularEvents];
}
}
public void AddCalendarItem(ICalendarItem calendarItem)
{
var collection = GetProperCollectionForCalendarItem(calendarItem);
AddCalendarItemInternal(collection, calendarItem);
var collections = GetProperCollectionsForCalendarItem(calendarItem);
foreach (var collection in collections)
{
AddCalendarItemInternal(collection, calendarItem);
}
}
public void RemoveCalendarItem(ICalendarItem calendarItem)
{
var collection = GetProperCollectionForCalendarItem(calendarItem);
RemoveCalendarItemInternal(collection, calendarItem);
var collections = GetProperCollectionsForCalendarItem(calendarItem);
foreach (var collection in collections)
{
RemoveCalendarItemInternal(collection, calendarItem);
}
}
private void AddCalendarItemInternal(ObservableRangeCollection<ICalendarItem> collection, ICalendarItem calendarItem, bool create = true)

View File

@@ -0,0 +1,42 @@
using System.Linq;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Calendar;
namespace Wino.Core.Domain.Collections
{
public class DayRangeCollection : ObservableRangeCollection<DayRangeRenderModel>
{
/// <summary>
/// Gets the range of dates that are currently displayed in the collection.
/// </summary>
public DateRange DisplayRange
{
get
{
if (Count == 0) return null;
var minimumLoadedDate = this[0].CalendarRenderOptions.DateRange.StartDate;
var maximumLoadedDate = this[Count - 1].CalendarRenderOptions.DateRange.EndDate;
return new DateRange(minimumLoadedDate, maximumLoadedDate);
}
}
public void RemoveCalendarItem(ICalendarItem calendarItem)
{
foreach (var dayRange in this)
{
}
}
public void AddCalendarItem(ICalendarItem calendarItem)
{
foreach (var dayRange in this)
{
var calendarDayModel = dayRange.CalendarDays.FirstOrDefault(x => x.Period.HasInside(calendarItem.Period.Start));
calendarDayModel?.EventsCollection.AddCalendarItem(calendarItem);
}
}
}
}

View File

@@ -10,6 +10,7 @@ namespace Wino.Core.Domain.Entities.Calendar
{
[PrimaryKey]
public Guid Id { get; set; }
public string RemoteEventId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Location { get; set; }
@@ -38,25 +39,51 @@ namespace Wino.Core.Domain.Entities.Calendar
}
}
/// <summary>
/// Events that starts at midnight and ends at midnight are considered all-day events.
/// </summary>
public bool IsAllDayEvent
{
get
{
return StartDate.TimeOfDay == TimeSpan.Zero && EndDate.TimeOfDay == TimeSpan.Zero;
return
StartDate.TimeOfDay == TimeSpan.Zero &&
EndDate.TimeOfDay == TimeSpan.Zero;
}
}
/// <summary>
/// Events that are not all-day events and last more than one day are considered multi-day events.
/// </summary>
public bool IsMultiDayEvent
{
get
{
return StartDate.Date != EndDate.Date;
return Period.Duration.TotalDays >= 1 && !IsAllDayEvent;
}
}
public double DurationInSeconds { get; set; }
public string Recurrence { get; set; }
/// <summary>
/// The id of the parent calendar item of the recurring event.
/// Exceptional instances are stored as a separate calendar item.
/// This makes the calendar item a child of the recurring event.s
/// </summary>
public Guid? RecurringCalendarItemId { get; set; }
/// <summary>
/// Indicates read-only events. Default is false.
/// </summary>
public bool IsLocked { get; set; }
/// <summary>
/// Hidden events must not be displayed to the user.
/// This usually happens when a child instance of recurring parent hapens.
/// </summary>
public bool IsHidden { get; set; }
// TODO
public string CustomEventColorHex { get; set; }
public string HtmlLink { get; set; }

View File

@@ -17,5 +17,6 @@ namespace Wino.Core.Domain.Interfaces
Task UpdateAccountCalendarAsync(AccountCalendar accountCalendar);
Task CreateNewCalendarItemAsync(CalendarItem calendarItem, List<CalendarEventAttendee> attendees);
Task<List<CalendarItem>> GetCalendarEventsAsync(IAccountCalendar calendar, DayRangeRenderModel dayRangeRenderModel);
Task<CalendarItem> GetCalendarItemAsync(Guid accountCalendarId, string remoteEventId);
}
}

View File

@@ -187,6 +187,7 @@ namespace Wino.Core.Domain.Interfaces
DayOfWeek WorkingDayStart { get; set; }
DayOfWeek WorkingDayEnd { get; set; }
double HourHeight { get; set; }
bool GhostRenderAllDayEvents { get; set; }
CalendarSettings GetCurrentCalendarSettings();

View File

@@ -18,7 +18,7 @@ namespace Wino.Core.Domain.Models.Calendar
RepresentingDate = representingDate;
Period = new TimeRange(representingDate, representingDate.AddDays(1));
CalendarRenderOptions = calendarRenderOptions;
EventsCollection = new CalendarEventCollection(Period);
EventsCollection = new CalendarEventCollection(Period, calendarRenderOptions.CalendarSettings);
}
public DateTime RepresentingDate { get; }

View File

@@ -11,7 +11,8 @@ namespace Wino.Core.Domain.Models.Calendar
TimeSpan WorkingHourEnd,
double HourHeight,
DayHeaderDisplayType DayHeaderDisplayType,
CultureInfo CultureInfo)
CultureInfo CultureInfo,
bool GhostRenderAllDayItems)
{
public TimeSpan? GetTimeSpan(string selectedTime)
{

View File

@@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using Itenso.TimePeriod;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Domain.Models.Calendar
{
@@ -13,8 +11,6 @@ namespace Wino.Core.Domain.Models.Calendar
/// </summary>
public class DayRangeRenderModel
{
public event EventHandler<CalendarDayModel> CalendarDayEventCollectionUpdated;
public ITimePeriod Period { get; }
public List<CalendarDayModel> CalendarDays { get; } = [];
public List<DayHeaderRenderModel> DayHeaders { get; } = [];
@@ -29,8 +25,6 @@ namespace Wino.Core.Domain.Models.Calendar
var representingDate = calendarRenderOptions.DateRange.StartDate.AddDays(i);
var calendarDayModel = new CalendarDayModel(representingDate, calendarRenderOptions);
RegisterCalendarDayEvents(calendarDayModel);
CalendarDays.Add(calendarDayModel);
}
@@ -53,29 +47,11 @@ namespace Wino.Core.Domain.Models.Calendar
}
}
private void RegisterCalendarDayEvents(CalendarDayModel calendarDayModel)
{
calendarDayModel.EventsCollection.CalendarItemAdded += CalendarItemAdded;
calendarDayModel.EventsCollection.CalendarItemRemoved += CalendarItemRemoved;
}
// TODO: These handlers have incorrect senders. They should be the 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);
/// <summary>
/// Unregisters all calendar item change listeners to draw the UI for calendar events.
/// </summary>
public void UnregisterAll()
{
foreach (var day in CalendarDays)
{
day.EventsCollection.CalendarItemRemoved -= CalendarItemRemoved;
day.EventsCollection.CalendarItemAdded -= CalendarItemAdded;
}
}
//public void AddEvent(ICalendarItem calendarEventModel)
//{
// var calendarDayModel = CalendarDays.FirstOrDefault(x => x.Period.HasInside(calendarEventModel.Period.Start));
// calendarDayModel?.EventsCollection.AddCalendarItem(calendarEventModel);
//}
}
}

View File

@@ -131,6 +131,7 @@
"DialogMessage_EnableStartupLaunchDeniedMessage": "You can enable startup launch from Settings -> App Preferences.",
"Dialog_DontAskAgain": "Don't ask again",
"CalendarAllDayEventSummary": "all-day events",
"CalendarItemAllDay": "all day",
"CreateAccountAliasDialog_Title": "Create Account Alias",
"CreateAccountAliasDialog_Description": "Make sure your outgoing server allows sending mails from this alias.",
"CreateAccountAliasDialog_AliasAddress": "Address",