Files
Wino-Mail/Wino.Calendar.ViewModels/AppShellViewModel.cs

367 lines
12 KiB
C#
Raw Normal View History

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;
using Wino.Core.Domain.Enums;
2025-01-06 21:56:33 +01:00
using Wino.Core.Domain.Extensions;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Calendar;
using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.ViewModels;
using Wino.Messaging.Client.Calendar;
using Wino.Messaging.Client.Navigation;
using Wino.Messaging.Server;
2025-05-18 14:06:25 +02:00
namespace Wino.Calendar.ViewModels;
public partial class AppShellViewModel : CalendarBaseViewModel,
IRecipient<VisibleDateRangeChangedMessage>,
IRecipient<CalendarEnableStatusChangedMessage>,
IRecipient<NavigateManageAccountsRequested>,
IRecipient<CalendarDisplayTypeChangedMessage>,
IRecipient<DetailsPageStateChangedMessage>
{
2025-05-18 14:06:25 +02:00
public IPreferencesService PreferencesService { get; }
public IStatePersistanceService StatePersistenceService { get; }
public IAccountCalendarStateService AccountCalendarStateService { get; }
public INavigationService NavigationService { get; }
public IWinoServerConnectionManager ServerConnectionManager { get; }
[ObservableProperty]
private bool _isEventDetailsPageActive;
[ObservableProperty]
private int _selectedMenuItemIndex = -1;
[ObservableProperty]
private bool isCalendarEnabled;
/// <summary>
/// Gets or sets the active connection status of the Wino server.
/// </summary>
[ObservableProperty]
private WinoServerConnectionStatus activeConnectionStatus;
/// <summary>
/// Gets or sets the display date of the calendar.
/// </summary>
[ObservableProperty]
private DateTimeOffset _displayDate;
/// <summary>
/// Gets or sets the highlighted range in the CalendarView and displayed date range in FlipView.
/// </summary>
[ObservableProperty]
private DateRange highlightedDateRange;
[ObservableProperty]
private ObservableRangeCollection<string> dateNavigationHeaderItems = [];
[ObservableProperty]
private int _selectedDateNavigationHeaderIndex;
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,
ICalendarService calendarService,
IAccountCalendarStateService accountCalendarStateService,
INavigationService navigationService,
IWinoServerConnectionManager serverConnectionManager)
{
2025-05-18 14:06:25 +02:00
_accountService = accountService;
_calendarService = calendarService;
2025-05-18 14:06:25 +02:00
AccountCalendarStateService = accountCalendarStateService;
AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested;
AccountCalendarStateService.CollectiveAccountGroupSelectionStateChanged += AccountCalendarStateCollectivelyChanged;
2025-05-18 14:06:25 +02:00
NavigationService = navigationService;
ServerConnectionManager = serverConnectionManager;
PreferencesService = preferencesService;
2025-05-18 14:06:25 +02:00
StatePersistenceService = statePersistanceService;
StatePersistenceService.StatePropertyChanged += PrefefencesChanged;
}
2025-05-18 14:06:25 +02:00
private void SelectedCalendarItemsChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
throw new NotImplementedException();
}
2025-05-18 14:06:25 +02:00
private void PrefefencesChanged(object sender, string e)
{
if (e == nameof(StatePersistenceService.CalendarDisplayType))
{
2025-05-18 14:06:25 +02:00
Messenger.Send(new CalendarDisplayTypeChangedMessage(StatePersistenceService.CalendarDisplayType));
2025-01-01 17:28:29 +01:00
2025-05-18 14:06:25 +02:00
// Change the calendar.
DateClicked(new CalendarViewDayClickedEventArgs(GetDisplayTypeSwitchDate()));
}
2025-05-18 14:06:25 +02:00
}
2025-05-18 14:06:25 +02:00
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
{
base.OnNavigatedTo(mode, parameters);
2025-05-18 14:06:25 +02:00
UpdateDateNavigationHeaderItems();
2025-05-18 14:06:25 +02:00
await InitializeAccountCalendarsAsync();
2025-05-18 14:06:25 +02:00
TodayClicked();
}
2025-05-18 14:06:25 +02:00
private async void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e)
{
// 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.
2025-05-18 14:06:25 +02:00
// Update all calendar states at once.
try
{
await _accountCalendarUpdateSemaphoreSlim.WaitAsync();
2025-05-18 14:06:25 +02:00
foreach (var calendar in e.AccountCalendars)
{
2025-05-18 14:06:25 +02:00
await _calendarService.UpdateAccountCalendarAsync(calendar.AccountCalendar).ConfigureAwait(false);
}
}
2025-05-18 14:06:25 +02:00
catch (Exception ex)
{
2025-05-18 14:06:25 +02:00
Log.Error(ex, "Error while waiting for account calendar update semaphore.");
}
finally
{
_accountCalendarUpdateSemaphoreSlim.Release();
}
}
2025-05-18 14:06:25 +02:00
private async void UpdateAccountCalendarRequested(object sender, AccountCalendarViewModel e)
=> await _calendarService.UpdateAccountCalendarAsync(e.AccountCalendar).ConfigureAwait(false);
2025-05-18 14:06:25 +02:00
private async Task InitializeAccountCalendarsAsync()
{
await Dispatcher.ExecuteOnUIThread(() => AccountCalendarStateService.ClearGroupedAccountCalendar());
2025-05-18 14:06:25 +02:00
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
2025-05-18 14:06:25 +02:00
foreach (var account in accounts)
{
var accountCalendars = await _calendarService.GetAccountCalendarsAsync(account.Id).ConfigureAwait(false);
var calendarViewModels = new List<AccountCalendarViewModel>();
2025-05-18 14:06:25 +02:00
foreach (var calendar in accountCalendars)
{
var calendarViewModel = new AccountCalendarViewModel(account, calendar);
2025-05-18 14:06:25 +02:00
calendarViewModels.Add(calendarViewModel);
}
2025-05-18 14:06:25 +02:00
var groupedAccountCalendarViewModel = new GroupedAccountCalendarViewModel(account, calendarViewModels);
await Dispatcher.ExecuteOnUIThread(() =>
{
AccountCalendarStateService.AddGroupedAccountCalendar(groupedAccountCalendarViewModel);
});
}
2025-05-18 14:06:25 +02:00
}
2025-05-18 14:06:25 +02:00
private void ForceNavigateCalendarDate()
{
if (SelectedMenuItemIndex == -1)
{
2025-05-18 14:06:25 +02:00
var args = new CalendarPageNavigationArgs()
{
2025-05-18 14:06:25 +02:00
NavigationDate = _navigationDate ?? DateTime.Now.Date
};
2025-05-18 14:06:25 +02:00
// Already on calendar. Just navigate.
NavigationService.Navigate(WinoPage.CalendarPage, args);
2025-05-18 14:06:25 +02:00
_navigationDate = null;
}
2025-05-18 14:06:25 +02:00
else
{
2025-05-18 14:06:25 +02:00
SelectedMenuItemIndex = -1;
}
2025-05-18 14:06:25 +02:00
}
2025-05-18 14:06:25 +02:00
partial void OnSelectedMenuItemIndexChanged(int oldValue, int newValue)
{
switch (newValue)
{
2025-05-18 14:06:25 +02:00
case -1:
ForceNavigateCalendarDate();
break;
case 0:
NavigationService.Navigate(WinoPage.ManageAccountsPage);
break;
case 1:
NavigationService.Navigate(WinoPage.SettingsPage);
break;
default:
break;
}
}
[RelayCommand]
private async Task Sync()
{
// Sync all calendars.
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
2025-05-18 14:06:25 +02:00
foreach (var account in accounts)
{
var t = new NewCalendarSynchronizationRequested(new CalendarSynchronizationOptions()
{
2025-05-18 14:06:25 +02:00
AccountId = account.Id,
Type = CalendarSynchronizationType.CalendarMetadata
}, SynchronizationSource.Client);
2025-05-18 14:06:25 +02:00
Messenger.Send(t);
}
2025-05-18 14:06:25 +02:00
}
2025-05-18 14:06:25 +02:00
/// <summary>
/// When calendar type switches, we need to navigate to the most ideal date.
/// This method returns that date.
/// </summary>
private DateTime GetDisplayTypeSwitchDate()
{
var settings = PreferencesService.GetCurrentCalendarSettings();
switch (StatePersistenceService.CalendarDisplayType)
{
2025-05-18 14:06:25 +02:00
case CalendarDisplayType.Day:
if (HighlightedDateRange.IsInRange(DateTime.Now)) return DateTime.Now.Date;
return HighlightedDateRange.StartDate;
case CalendarDisplayType.Week:
if (HighlightedDateRange == null || HighlightedDateRange.IsInRange(DateTime.Now))
{
return DateTime.Now.Date.GetWeekStartDateForDate(settings.FirstDayOfWeek);
}
2025-05-18 14:06:25 +02:00
return HighlightedDateRange.StartDate.GetWeekStartDateForDate(settings.FirstDayOfWeek);
case CalendarDisplayType.WorkWeek:
break;
case CalendarDisplayType.Month:
break;
case CalendarDisplayType.Year:
break;
default:
break;
}
2025-05-18 14:06:25 +02:00
return DateTime.Today.Date;
}
2025-05-18 14:06:25 +02:00
private DateTime? _navigationDate;
private readonly IAccountService _accountService;
private readonly ICalendarService _calendarService;
2025-05-18 14:06:25 +02:00
#region Commands
2025-05-18 14:06:25 +02:00
[RelayCommand]
private void TodayClicked()
{
_navigationDate = DateTime.Now.Date;
2025-05-18 14:06:25 +02:00
ForceNavigateCalendarDate();
}
2025-05-18 14:06:25 +02:00
[RelayCommand]
public void ManageAccounts() => NavigationService.Navigate(WinoPage.AccountManagementPage);
2025-05-18 14:06:25 +02:00
[RelayCommand]
private Task ReconnectServerAsync() => ServerConnectionManager.ConnectAsync();
2025-05-18 14:06:25 +02:00
[RelayCommand]
private void DateClicked(CalendarViewDayClickedEventArgs clickedDateArgs)
{
_navigationDate = clickedDateArgs.ClickedDate;
2025-05-18 14:06:25 +02:00
ForceNavigateCalendarDate();
}
2025-05-18 14:06:25 +02:00
#endregion
2025-05-18 14:06:25 +02:00
public void Receive(VisibleDateRangeChangedMessage message) => HighlightedDateRange = message.DateRange;
2025-05-18 14:06:25 +02:00
/// <summary>
/// Sets the header navigation items based on visible date range and calendar type.
/// </summary>
private void UpdateDateNavigationHeaderItems()
{
DateNavigationHeaderItems.Clear();
2025-05-18 14:06:25 +02:00
// TODO: From settings
var testInfo = new CultureInfo("en-US");
2025-05-18 14:06:25 +02:00
switch (StatePersistenceService.CalendarDisplayType)
{
case CalendarDisplayType.Day:
case CalendarDisplayType.Week:
case CalendarDisplayType.WorkWeek:
case CalendarDisplayType.Month:
DateNavigationHeaderItems.ReplaceRange(testInfo.DateTimeFormat.MonthNames);
break;
case CalendarDisplayType.Year:
break;
default:
break;
}
2025-05-18 14:06:25 +02:00
SetDateNavigationHeaderItems();
}
2025-05-18 14:06:25 +02:00
partial void OnHighlightedDateRangeChanged(DateRange value) => SetDateNavigationHeaderItems();
2025-05-18 14:06:25 +02:00
private void SetDateNavigationHeaderItems()
{
if (HighlightedDateRange == null) return;
2025-05-18 14:06:25 +02:00
if (DateNavigationHeaderItems.Count == 0)
{
UpdateDateNavigationHeaderItems();
}
2025-05-18 14:06:25 +02:00
// TODO: Year view
var monthIndex = HighlightedDateRange.GetMostVisibleMonthIndex();
SelectedDateNavigationHeaderIndex = Math.Max(monthIndex - 1, -1);
}
public async void Receive(CalendarEnableStatusChangedMessage message)
=> await ExecuteUIThread(() => IsCalendarEnabled = message.IsEnabled);
2025-05-18 14:06:25 +02:00
public void Receive(NavigateManageAccountsRequested message) => SelectedMenuItemIndex = 1;
2025-01-01 17:28:29 +01:00
2025-05-18 14:06:25 +02:00
public void Receive(CalendarDisplayTypeChangedMessage message) => OnPropertyChanged(nameof(IsVerticalCalendar));
2025-01-14 00:53:54 +01:00
2025-05-18 14:06:25 +02:00
public async void Receive(DetailsPageStateChangedMessage message)
{
await ExecuteUIThread(() =>
2025-01-01 17:28:29 +01:00
{
2025-05-18 14:06:25 +02:00
IsEventDetailsPageActive = message.IsActivated;
2025-01-14 00:53:54 +01:00
2025-05-18 14:06:25 +02:00
// TODO: This is for Wino Mail. Generalize this later on.
StatePersistenceService.IsReaderNarrowed = message.IsActivated;
StatePersistenceService.IsReadingMail = message.IsActivated;
});
}
}