Encapsulation of grouped account selection events and collective events.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user