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();