diff --git a/Wino.Calendar.ViewModels/CalendarPageViewModel.cs b/Wino.Calendar.ViewModels/CalendarPageViewModel.cs index ca306576..e0c5942a 100644 --- a/Wino.Calendar.ViewModels/CalendarPageViewModel.cs +++ b/Wino.Calendar.ViewModels/CalendarPageViewModel.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using CommunityToolkit.Diagnostics; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; +using Serilog; using Wino.Calendar.ViewModels.Data; using Wino.Calendar.ViewModels.Interfaces; using Wino.Core.Domain.Collections; @@ -64,8 +65,26 @@ namespace Wino.Calendar.ViewModels _accountCalendarStateService = accountCalendarStateService; _preferencesService = preferencesService; + _accountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested; + _accountCalendarStateService.CollectiveAccountGroupSelectionStateChanged += AccountCalendarStateCollectivelyChanged; } + private void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e) + { + // For all date ranges, update the events. + foreach (var dayRange in DayRanges) + { + _ = InitializeCalendarEventsForDayRangeAsync(dayRange); + } + } + + private void UpdateAccountCalendarRequested(object sender, AccountCalendarViewModel e) + { + foreach (var range in DayRanges) + { + _ = InitializeCalendarEventsForDayRangeAsync(range); + } + } // TODO: Replace when calendar settings are updated. // Should be a field ideally. @@ -125,9 +144,8 @@ namespace Wino.Calendar.ViewModels if (ShouldResetDayRanges(message)) { - DayRanges.Clear(); - Debug.WriteLine("Will reset day ranges."); + await ClearDayRangeModelsAsync(); } else if (ShouldScrollToItem(message)) { @@ -145,6 +163,7 @@ namespace Wino.Calendar.ViewModels } catch (Exception ex) { + Log.Error(ex, "Error while loading calendar."); Debugger.Break(); } finally @@ -165,6 +184,47 @@ namespace Wino.Calendar.ViewModels return new DateRange(minimumLoadedDate, maximumLoadedDate); } + private async Task AddDayRangeModelAsync(DayRangeRenderModel dayRangeRenderModel) + { + dayRangeRenderModel.CalendarDayEventCollectionUpdated -= EventsUpdatedInDayHeader; + dayRangeRenderModel.CalendarDayEventCollectionUpdated += EventsUpdatedInDayHeader; + + await ExecuteUIThread(() => + { + DayRanges.Add(dayRangeRenderModel); + }); + } + + private async Task InsertDayRangeModelAsync(DayRangeRenderModel dayRangeRenderModel, int index) + { + dayRangeRenderModel.CalendarDayEventCollectionUpdated -= EventsUpdatedInDayHeader; + dayRangeRenderModel.CalendarDayEventCollectionUpdated += EventsUpdatedInDayHeader; + + await ExecuteUIThread(() => + { + DayRanges.Insert(index, dayRangeRenderModel); + }); + } + + private async Task RemoveDayRangeModelAsync(DayRangeRenderModel dayRangeRenderModel) + { + dayRangeRenderModel.CalendarDayEventCollectionUpdated -= EventsUpdatedInDayHeader; + dayRangeRenderModel.UnregisterAll(); + + await ExecuteUIThread(() => + { + DayRanges.Remove(dayRangeRenderModel); + }); + } + + private async Task ClearDayRangeModelsAsync() + { + while (DayRanges.Count > 0) + { + await RemoveDayRangeModelAsync(DayRanges[0]); + } + } + private async Task RenderDatesAsync(CalendarInitInitiative calendarInitInitiative, DateTime? loadingDisplayDate = null, CalendarLoadDirection calendarLoadDirection = CalendarLoadDirection.Replace) @@ -232,10 +292,9 @@ namespace Wino.Calendar.ViewModels } // Dates are loaded. Now load the events for them. - foreach (var renderModel in renderModels) { - await InitializeCalendarEventsAsync(renderModel).ConfigureAwait(false); + await InitializeCalendarEventsForDayRangeAsync(renderModel).ConfigureAwait(false); } CalendarLoadDirection animationDirection = calendarLoadDirection; @@ -251,11 +310,12 @@ namespace Wino.Calendar.ViewModels isLoadMoreBlocked = true; // Remove all other dates except this one. + var rangesToRemove = DayRanges.Where(a => a != SelectedDayRange).ToList(); - await ExecuteUIThread(() => + foreach (var range in rangesToRemove) { - DayRanges.RemoveRange(DayRanges.Where(a => a != SelectedDayRange).ToList()); - }); + await RemoveDayRangeModelAsync(range); + } animationDirection = displayDate <= SelectedDayRange?.CalendarRenderOptions.DateRange.StartDate ? CalendarLoadDirection.Previous : CalendarLoadDirection.Next; @@ -263,13 +323,10 @@ namespace Wino.Calendar.ViewModels if (animationDirection == CalendarLoadDirection.Next) { - await ExecuteUIThread(() => + foreach (var item in renderModels) { - foreach (var item in renderModels) - { - DayRanges.Add(item); - } - }); + await AddDayRangeModelAsync(item); + } } else if (animationDirection == CalendarLoadDirection.Previous) { @@ -281,10 +338,7 @@ namespace Wino.Calendar.ViewModels // Insert each render model in reverse order. for (int i = renderModels.Count - 1; i >= 0; i--) { - await ExecuteUIThread(() => - { - DayRanges.Insert(0, renderModels[i]); - }); + await InsertDayRangeModelAsync(renderModels[i], 0); } } @@ -297,10 +351,7 @@ namespace Wino.Calendar.ViewModels if (removeCurrent) { - await ExecuteUIThread(() => - { - DayRanges.Remove(SelectedDayRange); - }); + await RemoveDayRangeModelAsync(SelectedDayRange); } // TODO... @@ -320,8 +371,36 @@ namespace Wino.Calendar.ViewModels } } - private async Task InitializeCalendarEventsAsync(DayRangeRenderModel dayRangeRenderModel) + // TODO... + private async void EventsUpdatedInDayHeader(object sender, CalendarDayModel e) { + // Find the day range model that contains the day model. + // TODO: Maybe optimize by just updating the day? + + if (sender is DayRangeRenderModel dayRangeRenderModel) + { + await InitializeCalendarEventsForDayRangeAsync(dayRangeRenderModel); + } + + + //var dayRange = DayRanges.FirstOrDefault(a => a.CalendarDays.Contains(e)); + + //if (dayRange == null) return; + + //await InitializeCalendarEventsForDayRangeAsync(dayRange); + } + + private async Task InitializeCalendarEventsForDayRangeAsync(DayRangeRenderModel dayRangeRenderModel) + { + // Clear all events first for all days. + foreach (var day in dayRangeRenderModel.CalendarDays) + { + await ExecuteUIThread(() => + { + day.EventsCollection.Clear(); + }); + } + // Load for each selected calendar from the state. var checkedCalendarViewModels = _accountCalendarStateService.GroupedAccountCalendars .SelectMany(a => a.AccountCalendars) @@ -352,9 +431,7 @@ namespace Wino.Calendar.ViewModels await ExecuteUIThread(() => { // Use range-based add for performance. - // No need to report changes since at this point nothing is rendered on the UI. - - calendarDayModel.EventsCollection.AddCalendarItemRange(calendarItemViewModels, reportChange: false); + calendarDayModel.EventsCollection.AddCalendarItemRange(calendarItemViewModels); }); } } diff --git a/Wino.Calendar/Controls/AllDayItemsControl.xaml.cs b/Wino.Calendar/Controls/AllDayItemsControl.xaml.cs index 3f44ba77..f6b7924d 100644 --- a/Wino.Calendar/Controls/AllDayItemsControl.xaml.cs +++ b/Wino.Calendar/Controls/AllDayItemsControl.xaml.cs @@ -78,6 +78,8 @@ namespace Wino.Calendar.Controls collection.CalendarItemRangeAdded += CollectionOfEventsUpdated; collection.CalendarItemRangeRemoved += CollectionOfEventsUpdated; + + collection.CalendarItemsCleared += EventsCleared; } private void UnregisterEventCollectionChanged(CalendarEventCollection collection) @@ -87,10 +89,13 @@ namespace Wino.Calendar.Controls collection.CalendarItemRangeAdded -= CollectionOfEventsUpdated; collection.CalendarItemRangeRemoved -= CollectionOfEventsUpdated; + + collection.CalendarItemsCleared -= EventsCleared; } private void SingleEventUpdated(object sender, ICalendarItem calendarItem) => UpdateCollectionVisuals(); private void CollectionOfEventsUpdated(object sender, List calendarItems) => UpdateCollectionVisuals(); + private void EventsCleared(object sender, System.EventArgs e) => UpdateCollectionVisuals(); private void UpdateCollectionVisuals() { diff --git a/Wino.Calendar/Controls/WinoCalendarPanel.cs b/Wino.Calendar/Controls/WinoCalendarPanel.cs index 1b646d6e..ca9d3e21 100644 --- a/Wino.Calendar/Controls/WinoCalendarPanel.cs +++ b/Wino.Calendar/Controls/WinoCalendarPanel.cs @@ -20,7 +20,6 @@ namespace Wino.Calendar.Controls private readonly Dictionary _measurements = new Dictionary(); public static readonly DependencyProperty EventItemMarginProperty = DependencyProperty.Register(nameof(EventItemMargin), typeof(Thickness), typeof(WinoCalendarPanel), new PropertyMetadata(new Thickness(0, 0, 0, 0))); - // public static readonly DependencyProperty RepresentingDateProperty = DependencyProperty.Register(nameof(RepresentingDate), typeof(DateTime), typeof(WinoCalendarControl), new PropertyMetadata(DateTime.MinValue)); public static readonly DependencyProperty HourHeightProperty = DependencyProperty.Register(nameof(HourHeight), typeof(double), typeof(WinoCalendarPanel), new PropertyMetadata(0d)); public double HourHeight @@ -35,44 +34,6 @@ namespace Wino.Calendar.Controls set { SetValue(EventItemMarginProperty, value); } } - //private static void OnDayChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - //{ - // if (d is WinoCalendarPanel control) - // { - // // We need to listen for new events being added or removed from the collection to reset measurements. - // if (e.OldValue is CalendarDayModel oldDayModel) - // { - // control.DetachCollection(oldDayModel.EventsCollection.RegularEvents); - // } - - // if (e.NewValue is CalendarDayModel newDayModel) - // { - // control.AttachCollection(newDayModel.EventsCollection.RegularEvents); - // } - - // control.ResetMeasurements(); - // control.UpdateLayout(); - // } - //} - - //private void AttachCollection(IEnumerable events) - //{ - // if (events is INotifyCollectionChanged collection) - // { - // // var t = new Grid(); - - // collection.CollectionChanged += EventCollectionChanged; - // } - //} - - //private void DetachCollection(IEnumerable events) - //{ - // if (events is INotifyCollectionChanged collection) - // { - // collection.CollectionChanged -= EventCollectionChanged; - // } - //} - private void ResetMeasurements() => _measurements.Clear(); // No need to handle actions. Each action requires a full measurement update. diff --git a/Wino.Core.Domain/Collections/CalendarEventCollection.cs b/Wino.Core.Domain/Collections/CalendarEventCollection.cs index 653d3f55..4084e850 100644 --- a/Wino.Core.Domain/Collections/CalendarEventCollection.cs +++ b/Wino.Core.Domain/Collections/CalendarEventCollection.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; +using Wino.Core.Domain.Entities.Calendar; using Wino.Core.Domain.Interfaces; namespace Wino.Core.Domain.Collections @@ -13,6 +15,8 @@ namespace Wino.Core.Domain.Collections public event EventHandler> CalendarItemRangeAdded; public event EventHandler> CalendarItemRangeRemoved; + public event EventHandler CalendarItemsCleared; + private ObservableRangeCollection _internalRegularEvents = []; private ObservableRangeCollection _internalAllDayEvents = []; @@ -25,27 +29,33 @@ namespace Wino.Core.Domain.Collections AllDayEvents = new ReadOnlyObservableCollection(_internalAllDayEvents); } - public void AddCalendarItemRange(IEnumerable calendarItems, bool reportChange = true) + public bool HasCalendarEvent(AccountCalendar accountCalendar) + { + return _internalAllDayEvents.Any(x => x.AssignedCalendar.Id == accountCalendar.Id) || + _internalRegularEvents.Any(x => x.AssignedCalendar.Id == accountCalendar.Id); + } + + public void AddCalendarItemRange(IEnumerable calendarItems) { foreach (var calendarItem in calendarItems) { - AddCalendarItem(calendarItem, reportChange: false); + AddCalendarItem(calendarItem); } CalendarItemRangeAdded?.Invoke(this, new List(calendarItems)); } - public void RemoveCalendarItemRange(IEnumerable calendarItems, bool reportChange = true) + public void RemoveCalendarItemRange(IEnumerable calendarItems) { foreach (var calendarItem in calendarItems) { - RemoveCalendarItem(calendarItem, reportChange); + RemoveCalendarItem(calendarItem); } CalendarItemRangeRemoved?.Invoke(this, new List(calendarItems)); } - public void AddCalendarItem(ICalendarItem calendarItem, bool reportChange = true) + public void AddCalendarItem(ICalendarItem calendarItem) { if (calendarItem is not ICalendarItemViewModel) throw new ArgumentException("CalendarItem must be of type ICalendarItemViewModel", nameof(calendarItem)); @@ -59,13 +69,18 @@ namespace Wino.Core.Domain.Collections _internalRegularEvents.Add(calendarItem); } - if (reportChange) - { - CalendarItemAdded?.Invoke(this, calendarItem); - } + CalendarItemAdded?.Invoke(this, calendarItem); } - public void RemoveCalendarItem(ICalendarItem calendarItem, bool reportChange = true) + public void Clear() + { + _internalAllDayEvents.Clear(); + _internalRegularEvents.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)); @@ -79,10 +94,7 @@ namespace Wino.Core.Domain.Collections _internalRegularEvents.Remove(calendarItem); } - if (reportChange) - { - CalendarItemRemoved?.Invoke(this, calendarItem); - } + CalendarItemRemoved?.Invoke(this, calendarItem); } } } diff --git a/Wino.Core.Domain/Models/Calendar/DayRangeRenderModel.cs b/Wino.Core.Domain/Models/Calendar/DayRangeRenderModel.cs index 139cb8fc..75025205 100644 --- a/Wino.Core.Domain/Models/Calendar/DayRangeRenderModel.cs +++ b/Wino.Core.Domain/Models/Calendar/DayRangeRenderModel.cs @@ -3,6 +3,7 @@ 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 { @@ -28,6 +29,8 @@ namespace Wino.Core.Domain.Models.Calendar var representingDate = calendarRenderOptions.DateRange.StartDate.AddDays(i); var calendarDayModel = new CalendarDayModel(representingDate, calendarRenderOptions); + RegisterCalendarDayEvents(calendarDayModel); + CalendarDays.Add(calendarDayModel); } @@ -50,38 +53,40 @@ 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; - //} + private void RegisterCalendarDayEvents(CalendarDayModel calendarDayModel) + { + calendarDayModel.EventsCollection.CalendarItemAdded += CalendarItemAdded; + calendarDayModel.EventsCollection.CalendarItemRangeRemoved += CalendarItemRangeRemoved; + calendarDayModel.EventsCollection.CalendarItemRemoved += CalendarItemRemoved; + calendarDayModel.EventsCollection.CalendarItemRangeAdded += CalendarItemRangeAdded; + } - //private void CalendarItemRangeAdded(object sender, List e) - // => CalendarDayEventCollectionUpdated?.Invoke(this, sender as CalendarDayModel); + // 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 CalendarItemRangeAdded(object sender, List e) + => CalendarDayEventCollectionUpdated?.Invoke(this, sender as CalendarDayModel); - //private void CalendarItemAdded(object sender, ICalendarItem e) - // => CalendarDayEventCollectionUpdated?.Invoke(this, sender as CalendarDayModel); + private void CalendarItemRemoved(object sender, ICalendarItem e) + => CalendarDayEventCollectionUpdated?.Invoke(this, sender as CalendarDayModel); - //private void CalendarItemRangeRemoved(object sender, List e) - // => CalendarDayEventCollectionUpdated?.Invoke(this, sender as CalendarDayModel); + private void CalendarItemAdded(object sender, ICalendarItem e) + => CalendarDayEventCollectionUpdated?.Invoke(this, sender as CalendarDayModel); - ///// - ///// Unregisters all calendar item change listeners to draw the UI for calendar events. - ///// - //public void UnregisterAll() - //{ - // foreach (var day in CalendarDays) - // { - // day.EventsCollection.CalendarItemRangeRemoved -= CalendarItemRangeRemoved; - // day.EventsCollection.CalendarItemRemoved -= CalendarItemRemoved; - // day.EventsCollection.CalendarItemRangeAdded -= CalendarItemRangeAdded; - // day.EventsCollection.CalendarItemAdded -= CalendarItemAdded; - // } - //} + private void CalendarItemRangeRemoved(object sender, List e) + => CalendarDayEventCollectionUpdated?.Invoke(this, sender as CalendarDayModel); + + /// + /// Unregisters all calendar item change listeners to draw the UI for calendar events. + /// + public void UnregisterAll() + { + foreach (var day in CalendarDays) + { + day.EventsCollection.CalendarItemRangeRemoved -= CalendarItemRangeRemoved; + day.EventsCollection.CalendarItemRemoved -= CalendarItemRemoved; + day.EventsCollection.CalendarItemRangeAdded -= CalendarItemRangeAdded; + day.EventsCollection.CalendarItemAdded -= CalendarItemAdded; + } + } } }