Encapsulation of grouped account selection events and collective events.

This commit is contained in:
Burak Kaan Köse
2024-12-29 19:37:36 +01:00
parent eef2ee1baa
commit f7bfbd5080
16 changed files with 242 additions and 49 deletions

View File

@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Serilog;
using Wino.Calendar.ViewModels.Data;
using Wino.Calendar.ViewModels.Interfaces;
using Wino.Core.Domain.Collections;
@@ -65,6 +67,9 @@ namespace Wino.Calendar.ViewModels
public bool IsVerticalCalendar => StatePersistenceService.CalendarDisplayType == CalendarDisplayType.Month;
// For updating account calendars asynchronously.
private SemaphoreSlim _accountCalendarUpdateSemaphoreSlim = new(1);
public AppShellViewModel(IPreferencesService preferencesService,
IStatePersistanceService statePersistanceService,
IAccountService accountService,
@@ -77,6 +82,9 @@ namespace Wino.Calendar.ViewModels
_calendarService = calendarService;
AccountCalendarStateService = accountCalendarStateService;
AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested;
AccountCalendarStateService.CollectiveAccountGroupSelectionStateChanged += AccountCalendarStateCollectivelyChanged;
NavigationService = navigationService;
ServerConnectionManager = serverConnectionManager;
PreferencesService = preferencesService;
@@ -104,16 +112,33 @@ namespace Wino.Calendar.ViewModels
UpdateDateNavigationHeaderItems();
await InitializeAccountCalendarsAsync();
TodayClicked();
}
private void AddGroupedAccountCalendarViewModel(GroupedAccountCalendarViewModel groupedAccountCalendarViewModel)
private async void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e)
{
foreach (var calendarViewModel in groupedAccountCalendarViewModel.AccountCalendars)
{
calendarViewModel.CalendarSelectionStateChanged += UpdateAccountCalendarRequested;
}
// When using three-state checkbox, multiple accounts will be selected/unselected at the same time.
// Reporting all these changes one by one to the UI is not efficient and may cause problems in the future.
AccountCalendarStateService.GroupedAccountCalendars.Add(groupedAccountCalendarViewModel);
// Update all calendar states at once.
try
{
await _accountCalendarUpdateSemaphoreSlim.WaitAsync();
foreach (var calendar in e.AccountCalendars)
{
await _calendarService.UpdateAccountCalendarAsync(calendar.AccountCalendar).ConfigureAwait(false);
}
}
catch (Exception ex)
{
Log.Error(ex, "Error while waiting for account calendar update semaphore.");
}
finally
{
_accountCalendarUpdateSemaphoreSlim.Release();
}
}
private async void UpdateAccountCalendarRequested(object sender, AccountCalendarViewModel e)
@@ -121,7 +146,7 @@ namespace Wino.Calendar.ViewModels
private async Task InitializeAccountCalendarsAsync()
{
await Dispatcher.ExecuteOnUIThread(() => AccountCalendarStateService.GroupedAccountCalendars.Clear());
await Dispatcher.ExecuteOnUIThread(() => AccountCalendarStateService.ClearGroupedAccountCalendar());
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
@@ -141,7 +166,7 @@ namespace Wino.Calendar.ViewModels
await Dispatcher.ExecuteOnUIThread(() =>
{
AddGroupedAccountCalendarViewModel(groupedAccountCalendarViewModel);
AccountCalendarStateService.AddGroupedAccountCalendar(groupedAccountCalendarViewModel);
});
}
}
@@ -233,12 +258,12 @@ namespace Wino.Calendar.ViewModels
private readonly IAccountService _accountService;
private readonly ICalendarService _calendarService;
public override void OnPageLoaded()
{
base.OnPageLoaded();
//public override void OnPageLoaded()
//{
// base.OnPageLoaded();
TodayClicked();
}
// TodayClicked();
//}
#region Commands

View File

@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using CommunityToolkit.Diagnostics;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Wino.Calendar.ViewModels.Data;
using Wino.Calendar.ViewModels.Interfaces;
using Wino.Core.Domain.Collections;
using Wino.Core.Domain.Enums;
@@ -211,8 +212,6 @@ namespace Wino.Calendar.ViewModels
}
}
// Create day ranges for each flip item until we reach the total days to load.
int totalFlipItemCount = (int)Math.Ceiling((double)flipLoadRange.TotalDays / eachFlipItemCount);
@@ -233,15 +232,7 @@ namespace Wino.Calendar.ViewModels
foreach (var renderModel in renderModels)
{
foreach (var day in renderModel.CalendarDays)
{
var events = await _calendarService.GetCalendarEventsAsync(Guid.Parse("9ead7613-dacb-4163-8d33-2e32e65008a1"), day.Period.Start, day.Period.End).ConfigureAwait(false);
foreach (var calendarItem in events)
{
day.EventsCollection.Add(calendarItem);
}
}
await InitializeCalendarEventsAsync(renderModel).ConfigureAwait(false);
}
CalendarLoadDirection animationDirection = calendarLoadDirection;
@@ -326,6 +317,42 @@ namespace Wino.Calendar.ViewModels
}
}
private async Task InitializeCalendarEventsAsync(DayRangeRenderModel dayRangeRenderModel)
{
// Load for each selected calendar from the state.
var checkedCalendarViewModels = _accountCalendarStateService.GroupedAccountCalendars
.SelectMany(a => a.AccountCalendars)
.Where(b => b.IsChecked);
foreach (var calendarViewModel in checkedCalendarViewModels)
{
// Check all the events for the given date range and calendar.
// Then find the day representation for all the events returned, and add to the collection.
var events = await _calendarService.GetCalendarEventsAsync(calendarViewModel,
dayRangeRenderModel.Period.Start,
dayRangeRenderModel.Period.End)
.ConfigureAwait(false);
foreach (var calendarItem in events)
{
var calendarDayModel = dayRangeRenderModel.CalendarDays.FirstOrDefault(a => a.RepresentingDate.Date == calendarItem.StartTime.Date);
if (calendarDayModel == null) continue;
var calendarItemViewModel = new CalendarItemViewModel(calendarItem);
await ExecuteUIThread(() =>
{
// TODO: EventsCollection should not take CalendarItem, but CalendarItemViewModel.
// Enforce it later on.
calendarDayModel.EventsCollection.Add(calendarItemViewModel);
});
}
}
}
private async Task TryConsolidateItemsAsync()
{
// Check if trimming is necessary

View File

@@ -8,8 +8,6 @@ namespace Wino.Calendar.ViewModels.Data
{
public partial class AccountCalendarViewModel : ObservableObject, IAccountCalendar
{
public event EventHandler<AccountCalendarViewModel> CalendarSelectionStateChanged;
public MailAccount Account { get; }
public AccountCalendar AccountCalendar { get; }
@@ -24,11 +22,7 @@ namespace Wino.Calendar.ViewModels.Data
[ObservableProperty]
private bool _isChecked;
partial void OnIsCheckedChanged(bool value)
{
IsExtended = value;
CalendarSelectionStateChanged?.Invoke(this, this);
}
partial void OnIsCheckedChanged(bool value) => IsExtended = value;
public string Name
{
@@ -71,5 +65,6 @@ namespace Wino.Calendar.ViewModels.Data
get => AccountCalendar.RemoteCalendarId;
set => SetProperty(AccountCalendar.RemoteCalendarId, value, AccountCalendar, (u, r) => u.RemoteCalendarId = r);
}
public Guid Id { get => ((IAccountCalendar)AccountCalendar).Id; set => ((IAccountCalendar)AccountCalendar).Id = value; }
}
}

View File

@@ -1,13 +1,14 @@
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using Itenso.TimePeriod;
using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Interfaces;
namespace Wino.Calendar.ViewModels.Data
{
public partial class CalendarItemViewModel : ObservableObject, ICalendarItem
public partial class CalendarItemViewModel : ObservableObject, ICalendarItem, ICalendarItemViewModel
{
public ICalendarItem CalendarItem { get; }
public CalendarItem CalendarItem { get; }
public string Title => CalendarItem.Title;
@@ -19,7 +20,9 @@ namespace Wino.Calendar.ViewModels.Data
public TimeRange Period => CalendarItem.Period;
public CalendarItemViewModel(ICalendarItem calendarItem)
public IAccountCalendar AssignedCalendar => ((ICalendarItem)CalendarItem).AssignedCalendar;
public CalendarItemViewModel(CalendarItem calendarItem)
{
CalendarItem = calendarItem;
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
@@ -9,6 +10,9 @@ namespace Wino.Calendar.ViewModels.Data
{
public partial class GroupedAccountCalendarViewModel : ObservableObject
{
public event EventHandler CollectiveSelectionStateChanged;
public event EventHandler<AccountCalendarViewModel> CalendarSelectionStateChanged;
public MailAccount Account { get; }
public ObservableCollection<AccountCalendarViewModel> AccountCalendars { get; }
@@ -59,6 +63,7 @@ namespace Wino.Calendar.ViewModels.Data
if (e.PropertyName == nameof(AccountCalendarViewModel.IsChecked))
{
ManageIsCheckedState();
UpdateCalendarCheckedState(viewModel, viewModel.IsChecked, true);
}
}
}
@@ -73,6 +78,8 @@ namespace Wino.Calendar.ViewModels.Data
private void ManageIsCheckedState()
{
if (_isExternalPropChangeBlocked) return;
_isExternalPropChangeBlocked = true;
if (AccountCalendars.All(c => c.IsChecked))
@@ -95,22 +102,45 @@ namespace Wino.Calendar.ViewModels.Data
{
if (_isExternalPropChangeBlocked) return;
// Update is triggered by user on the three-state checkbox.
// We should not report all changes one by one.
_isExternalPropChangeBlocked = true;
if (newValue == null)
{
// Only primary calendars must be checked.
foreach (var calendar in AccountCalendars)
{
calendar.IsChecked = calendar.IsPrimary;
UpdateCalendarCheckedState(calendar, calendar.IsPrimary);
}
}
else
{
foreach (var calendar in AccountCalendars)
{
calendar.IsChecked = newValue.GetValueOrDefault();
UpdateCalendarCheckedState(calendar, newValue.GetValueOrDefault());
}
}
_isExternalPropChangeBlocked = false;
CollectiveSelectionStateChanged?.Invoke(this, EventArgs.Empty);
}
private void UpdateCalendarCheckedState(AccountCalendarViewModel accountCalendarViewModel, bool newValue, bool ignoreValueCheck = false)
{
var currentValue = accountCalendarViewModel.IsChecked;
if (currentValue == newValue && !ignoreValueCheck) return;
accountCalendarViewModel.IsChecked = newValue;
// No need to report.
if (_isExternalPropChangeBlocked == true) return;
CalendarSelectionStateChanged?.Invoke(this, accountCalendarViewModel);
}
}
}

View File

@@ -1,10 +1,21 @@
using System.Collections.ObjectModel;
using System;
using System.Collections.ObjectModel;
using Wino.Calendar.ViewModels.Data;
namespace Wino.Calendar.ViewModels.Interfaces
{
public interface IAccountCalendarStateService
{
ObservableCollection<GroupedAccountCalendarViewModel> GroupedAccountCalendars { get; }
ReadOnlyObservableCollection<GroupedAccountCalendarViewModel> GroupedAccountCalendars { get; }
event EventHandler<GroupedAccountCalendarViewModel> CollectiveAccountGroupSelectionStateChanged;
event EventHandler<AccountCalendarViewModel> AccountCalendarSelectionStateChanged;
public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar);
public void RemoveGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar);
public void ClearGroupedAccountCalendar();
public void AddAccountCalendar(AccountCalendarViewModel accountCalendar);
public void RemoveAccountCalendar(AccountCalendarViewModel accountCalendar);
}
}