diff --git a/Wino.Calendar.ViewModels/CalendarPageViewModel.cs b/Wino.Calendar.ViewModels/CalendarPageViewModel.cs index 2a0a8d95..b58fc99b 100644 --- a/Wino.Calendar.ViewModels/CalendarPageViewModel.cs +++ b/Wino.Calendar.ViewModels/CalendarPageViewModel.cs @@ -863,17 +863,29 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel, // Check if event falls into the current date range. if (DayRanges.DisplayRange == null) return; - // Check whether this event falls into any of the loaded date ranges. - var allDaysForEvent = DayRanges.SelectMany(a => a.CalendarDays).Where(a => a.Period.OverlapsWith(calendarItem.Period)); + // Get all periods from the visible day ranges + var visiblePeriods = DayRanges.Select(dr => dr.Period).ToList(); - foreach (var calendarDay in allDaysForEvent) + // For recurring events, expand them to check if any occurrences fall within visible periods + // For regular events, just check if they overlap with any period + var matchingItems = await _calendarService.GetExpandedRecurringEventsForPeriodsAsync(calendarItem, visiblePeriods); + + foreach (var item in matchingItems) { - var calendarItemViewModel = new CalendarItemViewModel(calendarItem); + // Find the days that the event falls into + var allDaysForEvent = DayRanges + .SelectMany(a => a.CalendarDays) + .Where(a => a.Period.OverlapsWith(item.Period)); - await ExecuteUIThread(() => + foreach (var calendarDay in allDaysForEvent) { - calendarDay.EventsCollection.AddCalendarItem(calendarItemViewModel); - }); + var calendarItemViewModel = new CalendarItemViewModel(item); + + await ExecuteUIThread(() => + { + calendarDay.EventsCollection.AddCalendarItem(calendarItemViewModel); + }); + } } FilterActiveCalendars(DayRanges); diff --git a/Wino.Core.Domain/Interfaces/ICalendarService.cs b/Wino.Core.Domain/Interfaces/ICalendarService.cs index b43c2ccd..73750110 100644 --- a/Wino.Core.Domain/Interfaces/ICalendarService.cs +++ b/Wino.Core.Domain/Interfaces/ICalendarService.cs @@ -25,6 +25,14 @@ public interface ICalendarService /// The time period to query events for. /// List of calendar items including regular events and recurring event occurrences. Task> GetCalendarEventsAsync(IAccountCalendar calendar, ITimePeriod period); + + /// + /// Expands a recurring calendar item to check if any of its occurrences fall within the given periods. + /// + /// The calendar item to expand (can be recurring or non-recurring). + /// The list of periods to check against. + /// List of calendar items (either the original item or expanded recurrence instances) that fall within the periods. + Task> GetExpandedRecurringEventsForPeriodsAsync(CalendarItem calendarItem, IEnumerable periods); Task GetCalendarItemAsync(Guid accountCalendarId, string remoteEventId); Task UpdateCalendarDeltaSynchronizationToken(Guid calendarId, string deltaToken); diff --git a/Wino.Services/CalendarService.cs b/Wino.Services/CalendarService.cs index b2dbff90..962e4d19 100644 --- a/Wino.Services/CalendarService.cs +++ b/Wino.Services/CalendarService.cs @@ -269,6 +269,70 @@ public class CalendarService : BaseDatabaseService, ICalendarService deltaToken, calendarId); } + /// + /// Expands a recurring calendar item to check if any of its occurrences fall within the given periods. + /// For non-recurring events, returns the item if it overlaps with any period. + /// For recurring events, expands occurrences and returns those that fall within any of the periods. + /// + /// The calendar item to expand (can be recurring or non-recurring). + /// The list of periods to check against. + /// List of calendar items (either the original item or expanded recurrence instances) that fall within the periods. + public async Task> GetExpandedRecurringEventsForPeriodsAsync(CalendarItem calendarItem, IEnumerable periods) + { + var result = new List(); + + if (calendarItem == null || periods == null || !periods.Any()) + { + return result; + } + + // Ensure AssignedCalendar is loaded + if (calendarItem.AssignedCalendar == null) + { + calendarItem.AssignedCalendar = await GetAccountCalendarAsync(calendarItem.CalendarId); + } + + // For non-recurring events, check if it overlaps with any of the provided periods + if (string.IsNullOrEmpty(calendarItem.Recurrence)) + { + foreach (var period in periods) + { + if (calendarItem.Period.OverlapsWith(period)) + { + result.Add(calendarItem); + break; // Add it only once + } + } + } + else + { + // For recurring events, expand occurrences for the combined date range of all periods + // Find the minimum and maximum dates across all periods + var minDate = periods.Min(p => p.Start); + var maxDate = periods.Max(p => p.End); + + var combinedPeriod = new TimeRange(minDate, maxDate); + + // Expand the recurring event for the combined period + var expandedOccurrences = await ExpandRecurringEventAsync(calendarItem, combinedPeriod); + + // Filter occurrences that fall within any of the individual periods + foreach (var occurrence in expandedOccurrences) + { + foreach (var period in periods) + { + if (occurrence.Period.OverlapsWith(period)) + { + result.Add(occurrence); + break; // Add it only once even if it overlaps with multiple periods + } + } + } + } + + return result; + } + public Task> GetAttendeesAsync(Guid calendarEventTrackingId) => Connection.Table().Where(x => x.CalendarItemId == calendarEventTrackingId).ToListAsync();