Single isntances and some updates shit.

This commit is contained in:
Burak Kaan Köse
2026-01-06 11:11:37 +01:00
parent d279c0a8dd
commit f8333aab10
18 changed files with 482 additions and 732 deletions
@@ -13,6 +13,7 @@ public class CalendarEventCollection
{
public event EventHandler<ICalendarItem> CalendarItemAdded;
public event EventHandler<ICalendarItem> CalendarItemRemoved;
public event EventHandler<ICalendarItem> CalendarItemUpdated;
public event EventHandler CalendarItemsCleared;
@@ -116,9 +117,13 @@ public class CalendarEventCollection
private void AddCalendarItemInternal(ObservableRangeCollection<ICalendarItem> collection, ICalendarItem calendarItem, bool create = true)
{
if (calendarItem is not ICalendarItemViewModel)
if (calendarItem is not ICalendarItemViewModel viewModel)
throw new ArgumentException("CalendarItem must be of type ICalendarItemViewModel", nameof(calendarItem));
// Set the displaying context for proper title calculation
viewModel.DisplayingPeriod = Period;
viewModel.CalendarSettings = Settings;
collection.Add(calendarItem);
if (create)
@@ -144,6 +149,53 @@ public class CalendarEventCollection
CalendarItemRemoved?.Invoke(this, calendarItem);
}
/// <summary>
/// Updates an existing calendar item in-place. If the item's type changed (all-day vs regular),
/// it will be moved to the appropriate collection.
/// </summary>
/// <param name="calendarItem">The updated calendar item data.</param>
/// <returns>True if the item was found and updated; false otherwise.</returns>
public bool UpdateCalendarItem(CalendarItem calendarItem)
{
var existingItem = _allItems.FirstOrDefault(x => x.Id == calendarItem.Id);
if (existingItem == null)
return false;
// Get the collections this item is currently in (before update)
var oldCollections = GetProperCollectionsForCalendarItem(existingItem).ToList();
// Update the underlying data
if (existingItem is ICalendarItemViewModel viewModel)
{
viewModel.UpdateFrom(calendarItem);
}
// Get the collections this item should be in (after update)
var newCollections = GetProperCollectionsForCalendarItem(existingItem).ToList();
// Check if the collections changed
var collectionsToRemoveFrom = oldCollections.Except(newCollections).ToList();
var collectionsToAddTo = newCollections.Except(oldCollections).ToList();
// Remove from old collections that are no longer applicable
foreach (var collection in collectionsToRemoveFrom)
{
collection.Remove(existingItem);
}
// Add to new collections that are now applicable
foreach (var collection in collectionsToAddTo)
{
if (!collection.Contains(existingItem))
{
collection.Add(existingItem);
}
}
CalendarItemUpdated?.Invoke(this, existingItem);
return true;
}
public void Clear()
{
_internalAllDayEvents.Clear();
@@ -4,6 +4,7 @@ using Itenso.TimePeriod;
using SQLite;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Calendar;
namespace Wino.Core.Domain.Entities.Calendar;
@@ -72,9 +73,7 @@ public class CalendarItem : ICalendarItem
}
/// <summary>
/// Events that are either an exceptional instance of a recurring event or occurrences.
/// IsOccurrence is used to display occurrence instances of parent recurring events.
/// IsOccurrence == false && IsRecurringChild == true => exceptional single instance.
/// Events that are child instances of a recurring event (occurrences or exceptions).
/// </summary>
public bool IsRecurringChild
{
@@ -85,7 +84,7 @@ public class CalendarItem : ICalendarItem
}
/// <summary>
/// Events that are either an exceptional instance of a recurring event or occurrences.
/// Events that are part of a recurring series (either as parent or child).
/// </summary>
public bool IsRecurringEvent => IsRecurringChild || IsRecurringParent;
@@ -140,12 +139,12 @@ public class CalendarItem : ICalendarItem
public string HtmlLink { get; set; }
public CalendarItemStatus Status { get; set; }
public CalendarItemVisibility Visibility { get; set; }
/// <summary>
/// Indicates how the event should be shown in the calendar (Free, Busy, Tentative, etc.).
/// </summary>
public CalendarItemShowAs ShowAs { get; set; } = CalendarItemShowAs.Busy;
public DateTimeOffset CreatedAt { get; set; }
public DateTimeOffset UpdatedAt { get; set; }
public Guid CalendarId { get; set; }
@@ -154,51 +153,11 @@ public class CalendarItem : ICalendarItem
public IAccountCalendar AssignedCalendar { get; set; }
/// <summary>
/// Whether this item does not really exist in the database or not.
/// These are used to display occurrence instances of parent recurring events.
/// Id to load information related to this event (attendees, reminders, etc.).
/// For child events, if they have their own data, use their own Id.
/// For events that share data with their parent, return parent's Id.
/// </summary>
[Ignore]
public bool IsOccurrence { get; set; }
/// <summary>
/// Id to load information related to this event.
/// Occurrences tracked by the parent recurring event if they are not exceptional instances.
/// Recurring children here are exceptional instances. They have their own info in the database including Id.
/// </summary>
public Guid EventTrackingId => IsOccurrence ? RecurringCalendarItemId.Value : Id;
public CalendarItem CreateRecurrence(DateTime startDate, double durationInSeconds)
{
// Create a copy with the new start date and duration
return new CalendarItem
{
Id = Guid.NewGuid(),
Title = Title,
Description = Description,
Location = Location,
StartDate = startDate,
DurationInSeconds = durationInSeconds,
Recurrence = Recurrence,
OrganizerDisplayName = OrganizerDisplayName,
OrganizerEmail = OrganizerEmail,
RecurringCalendarItemId = Id,
AssignedCalendar = AssignedCalendar,
CalendarId = CalendarId,
CreatedAt = CreatedAt,
UpdatedAt = UpdatedAt,
Visibility = Visibility,
Status = Status,
CustomEventColorHex = CustomEventColorHex,
HtmlLink = HtmlLink,
StartTimeZone = StartTimeZone,
EndTimeZone = EndTimeZone,
RemoteEventId = RemoteEventId,
IsHidden = IsHidden,
IsLocked = IsLocked,
IsOccurrence = true
};
}
public Guid EventTrackingId => Id;
/// <summary>
/// Gets the start date converted to user's local timezone for display.
@@ -219,10 +178,10 @@ public class CalendarItem : ICalendarItem
{
var sourceTimeZone = TimeZoneInfo.FindSystemTimeZoneById(StartTimeZone);
var localTimeZone = TimeZoneInfo.Local;
// Ensure DateTime is Unspecified kind before conversion
var unspecifiedDateTime = DateTime.SpecifyKind(StartDate, DateTimeKind.Unspecified);
// Convert from source timezone to local timezone
return TimeZoneInfo.ConvertTime(unspecifiedDateTime, sourceTimeZone, localTimeZone);
}
@@ -253,10 +212,10 @@ public class CalendarItem : ICalendarItem
{
var sourceTimeZone = TimeZoneInfo.FindSystemTimeZoneById(EndTimeZone);
var localTimeZone = TimeZoneInfo.Local;
// Ensure DateTime is Unspecified kind before conversion
var unspecifiedDateTime = DateTime.SpecifyKind(EndDate, DateTimeKind.Unspecified);
// Convert from source timezone to local timezone
return TimeZoneInfo.ConvertTime(unspecifiedDateTime, sourceTimeZone, localTimeZone);
}
@@ -267,4 +226,6 @@ public class CalendarItem : ICalendarItem
}
}
}
public string GetDisplayTitle(ITimePeriod displayingPeriod, CalendarSettings calendarSettings) => Period.ToString();
}
@@ -1,5 +1,6 @@
using System;
using Itenso.TimePeriod;
using Wino.Core.Domain.Models.Calendar;
namespace Wino.Core.Domain.Interfaces;
@@ -19,4 +20,13 @@ public interface ICalendarItem
bool IsRecurringChild { get; }
bool IsRecurringParent { get; }
bool IsRecurringEvent { get; }
/// <summary>
/// Gets the display title for this calendar item when rendered in a specific day.
/// For multi-day events, includes start/end time indicators.
/// </summary>
/// <param name="displayingPeriod">The period of the day where this item is being rendered.</param>
/// <param name="calendarSettings">Calendar settings for time formatting.</param>
/// <returns>The formatted title string.</returns>
string GetDisplayTitle(ITimePeriod displayingPeriod, CalendarSettings calendarSettings);
}
@@ -1,4 +1,8 @@
namespace Wino.Core.Domain.Interfaces;
using Itenso.TimePeriod;
using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Models.Calendar;
namespace Wino.Core.Domain.Interfaces;
/// <summary>
/// Temporarily to enforce CalendarItemViewModel. Used in CalendarEventCollection.
@@ -6,4 +10,21 @@
public interface ICalendarItemViewModel
{
bool IsSelected { get; set; }
/// <summary>
/// The period of the day where this item is currently being displayed.
/// </summary>
ITimePeriod DisplayingPeriod { get; set; }
/// <summary>
/// Calendar settings for time formatting.
/// </summary>
CalendarSettings CalendarSettings { get; set; }
/// <summary>
/// Updates the view model's underlying CalendarItem from new data.
/// This allows in-place updates without removing and re-adding items.
/// </summary>
/// <param name="calendarItem">The updated calendar item data.</param>
void UpdateFrom(CalendarItem calendarItem);
}
@@ -24,16 +24,8 @@ public interface ICalendarService
/// </summary>
/// <param name="calendar">The calendar to retrieve events from.</param>
/// <param name="period">The time period to query events for.</param>
/// <returns>List of calendar items including regular events and recurring event occurrences.</returns>
/// <returns>List of calendar items that fall within the requested period.</returns>
Task<List<CalendarItem>> GetCalendarEventsAsync(IAccountCalendar calendar, ITimePeriod period);
/// <summary>
/// Expands a recurring calendar item to check if any of its occurrences fall within the given periods.
/// </summary>
/// <param name="calendarItem">The calendar item to expand (can be recurring or non-recurring).</param>
/// <param name="periods">The list of periods to check against.</param>
/// <returns>List of calendar items (either the original item or expanded recurrence instances) that fall within the periods.</returns>
Task<List<CalendarItem>> GetExpandedRecurringEventsForPeriodsAsync(CalendarItem calendarItem, IEnumerable<ITimePeriod> periods);
Task<CalendarItem> GetCalendarItemAsync(Guid accountCalendarId, string remoteEventId);
Task UpdateCalendarDeltaSynchronizationToken(Guid calendarId, string deltaToken);