From c4e561dee6002e8a7e16c03d2b760107f8b0f398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Sun, 18 May 2025 14:06:25 +0200 Subject: [PATCH] dotnet format refactorings. --- Wino.Calendar.ViewModels/AppShellViewModel.cs | 617 ++++--- .../CalendarPageViewModel.cs | 1617 ++++++++--------- .../CalendarSettingsPageViewModel.cs | 201 +- .../CalendarViewModelContainerSetup.cs | 11 +- .../Data/AccountCalendarViewModel.cs | 117 +- .../Data/CalendarItemViewModel.cs | 69 +- .../Data/GroupedAccountCalendarViewModel.cs | 233 ++- .../EventDetailsPageViewModel.cs | 163 +- .../IAccountCalendarStateService.cs | 33 +- .../CalendarItemDoubleTappedMessage.cs | 17 +- .../CalendarItemRightTappedMessage.cs | 17 +- .../Messages/CalendarItemTappedMessage.cs | 21 +- .../Activation/DefaultActivationHandler.cs | 21 +- .../Args/TimelineCellSelectedArgs.cs | 67 +- .../Args/TimelineCellUnselectedArgs.cs | 13 +- .../Controls/CalendarItemCommandBarFlyout.cs | 39 +- .../Controls/CalendarItemControl.xaml.cs | 293 ++- .../Controls/CustomCalendarFlipView.cs | 61 +- Wino.Calendar/Controls/DayColumnControl.cs | 125 +- Wino.Calendar/Controls/DayHeaderControl.cs | 83 +- Wino.Calendar/Controls/WinoCalendarControl.cs | 551 +++--- .../Controls/WinoCalendarFlipView.cs | 315 ++-- Wino.Calendar/Controls/WinoCalendarPanel.cs | 467 +++-- .../WinoCalendarTypeSelectorControl.cs | 137 +- Wino.Calendar/Controls/WinoCalendarView.cs | 225 ++- .../Controls/WinoDayTimelineCanvas.cs | 507 +++--- Wino.Calendar/Helpers/CalendarXamlHelpers.cs | 171 +- Wino.Calendar/MainPage.xaml.cs | 17 +- .../Models/CalendarItemMeasurement.cs | 25 +- .../CustomAreaCalendarItemSelector.cs | 23 +- .../WinoCalendarItemTemplateSelector.cs | 47 +- .../Services/AccountCalendarStateService.cs | 167 +- Wino.Calendar/Services/DialogService.cs | 13 +- Wino.Calendar/Services/NavigationService.cs | 81 +- Wino.Calendar/Services/ProviderService.cs | 45 +- .../Services/SettingsBuilderService.cs | 25 +- .../Styles/WinoCalendarResources.xaml.cs | 11 +- .../Abstract/AccountDetailsPageAbstract.cs | 7 +- .../Abstract/AccountManagementPageAbstract.cs | 7 +- .../Views/Abstract/AppShellAbstract.cs | 7 +- .../Views/Abstract/CalendarPageAbstract.cs | 7 +- .../Abstract/CalendarSettingsPageAbstract.cs | 7 +- .../Abstract/EventDetailsPageAbstract.cs | 7 +- .../Abstract/PersonalizationPageAbstract.cs | 7 +- .../Account/AccountManagementPage.xaml.cs | 11 +- Wino.Calendar/Views/AppShell.xaml.cs | 83 +- Wino.Calendar/Views/CalendarPage.xaml.cs | 279 ++- Wino.Calendar/Views/EventDetailsPage.xaml.cs | 11 +- .../Views/Settings/AccountDetailsPage.xaml.cs | 11 +- .../Settings/CalendarSettingsPage.xaml.cs | 11 +- .../Settings/PersonalizationPage.xaml.cs | 11 +- .../Models/DomainModelsJsonContext.cs | 2 +- Wino.Core.UWP/BasePage.cs | 3 + .../Services/WinoServerConnectionManager.cs | 3 + Wino.Core.ViewModels/AboutPageViewModel.cs | 3 + .../PersonalizationPageViewModel.cs | 3 + Wino.Core.ViewModels/SettingsViewModel.cs | 2 +- Wino.Core/Requests/Bundles/RequestBundle.cs | 3 + .../Synchronizers/OutlookSynchronizer.cs | 2 + 59 files changed, 3549 insertions(+), 3583 deletions(-) diff --git a/Wino.Calendar.ViewModels/AppShellViewModel.cs b/Wino.Calendar.ViewModels/AppShellViewModel.cs index 885ae6f0..a7454eb5 100644 --- a/Wino.Calendar.ViewModels/AppShellViewModel.cs +++ b/Wino.Calendar.ViewModels/AppShellViewModel.cs @@ -21,347 +21,346 @@ using Wino.Messaging.Client.Calendar; using Wino.Messaging.Client.Navigation; using Wino.Messaging.Server; -namespace Wino.Calendar.ViewModels +namespace Wino.Calendar.ViewModels; + +public partial class AppShellViewModel : CalendarBaseViewModel, + IRecipient, + IRecipient, + IRecipient, + IRecipient, + IRecipient { - public partial class AppShellViewModel : CalendarBaseViewModel, - IRecipient, - IRecipient, - IRecipient, - IRecipient, - IRecipient + 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; + + /// + /// Gets or sets the active connection status of the Wino server. + /// + [ObservableProperty] + private WinoServerConnectionStatus activeConnectionStatus; + + /// + /// Gets or sets the display date of the calendar. + /// + [ObservableProperty] + private DateTimeOffset _displayDate; + + /// + /// Gets or sets the highlighted range in the CalendarView and displayed date range in FlipView. + /// + [ObservableProperty] + private DateRange highlightedDateRange; + + [ObservableProperty] + private ObservableRangeCollection 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) { - public IPreferencesService PreferencesService { get; } - public IStatePersistanceService StatePersistenceService { get; } - public IAccountCalendarStateService AccountCalendarStateService { get; } - public INavigationService NavigationService { get; } - public IWinoServerConnectionManager ServerConnectionManager { get; } + _accountService = accountService; + _calendarService = calendarService; - [ObservableProperty] - private bool _isEventDetailsPageActive; + AccountCalendarStateService = accountCalendarStateService; + AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested; + AccountCalendarStateService.CollectiveAccountGroupSelectionStateChanged += AccountCalendarStateCollectivelyChanged; - [ObservableProperty] - private int _selectedMenuItemIndex = -1; + NavigationService = navigationService; + ServerConnectionManager = serverConnectionManager; + PreferencesService = preferencesService; - [ObservableProperty] - private bool isCalendarEnabled; + StatePersistenceService = statePersistanceService; + StatePersistenceService.StatePropertyChanged += PrefefencesChanged; + } - /// - /// Gets or sets the active connection status of the Wino server. - /// - [ObservableProperty] - private WinoServerConnectionStatus activeConnectionStatus; + private void SelectedCalendarItemsChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + throw new NotImplementedException(); + } - /// - /// Gets or sets the display date of the calendar. - /// - [ObservableProperty] - private DateTimeOffset _displayDate; - - /// - /// Gets or sets the highlighted range in the CalendarView and displayed date range in FlipView. - /// - [ObservableProperty] - private DateRange highlightedDateRange; - - [ObservableProperty] - private ObservableRangeCollection 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) + private void PrefefencesChanged(object sender, string e) + { + if (e == nameof(StatePersistenceService.CalendarDisplayType)) { - _accountService = accountService; - _calendarService = calendarService; + Messenger.Send(new CalendarDisplayTypeChangedMessage(StatePersistenceService.CalendarDisplayType)); - AccountCalendarStateService = accountCalendarStateService; - AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested; - AccountCalendarStateService.CollectiveAccountGroupSelectionStateChanged += AccountCalendarStateCollectivelyChanged; - NavigationService = navigationService; - ServerConnectionManager = serverConnectionManager; - PreferencesService = preferencesService; - - StatePersistenceService = statePersistanceService; - StatePersistenceService.StatePropertyChanged += PrefefencesChanged; + // Change the calendar. + DateClicked(new CalendarViewDayClickedEventArgs(GetDisplayTypeSwitchDate())); } + } - private void SelectedCalendarItemsChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) - { - throw new NotImplementedException(); - } + public override async void OnNavigatedTo(NavigationMode mode, object parameters) + { + base.OnNavigatedTo(mode, parameters); - private void PrefefencesChanged(object sender, string e) + UpdateDateNavigationHeaderItems(); + + await InitializeAccountCalendarsAsync(); + + TodayClicked(); + } + + 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. + + // Update all calendar states at once. + try { - if (e == nameof(StatePersistenceService.CalendarDisplayType)) + await _accountCalendarUpdateSemaphoreSlim.WaitAsync(); + + foreach (var calendar in e.AccountCalendars) { - Messenger.Send(new CalendarDisplayTypeChangedMessage(StatePersistenceService.CalendarDisplayType)); - - - // Change the calendar. - DateClicked(new CalendarViewDayClickedEventArgs(GetDisplayTypeSwitchDate())); + await _calendarService.UpdateAccountCalendarAsync(calendar.AccountCalendar).ConfigureAwait(false); } } - - public override async void OnNavigatedTo(NavigationMode mode, object parameters) + catch (Exception ex) { - base.OnNavigatedTo(mode, parameters); - - UpdateDateNavigationHeaderItems(); - - await InitializeAccountCalendarsAsync(); - - TodayClicked(); + Log.Error(ex, "Error while waiting for account calendar update semaphore."); } - - private async void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e) + finally { - // 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. - - // 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(); - } + _accountCalendarUpdateSemaphoreSlim.Release(); } + } - private async void UpdateAccountCalendarRequested(object sender, AccountCalendarViewModel e) - => await _calendarService.UpdateAccountCalendarAsync(e.AccountCalendar).ConfigureAwait(false); + private async void UpdateAccountCalendarRequested(object sender, AccountCalendarViewModel e) + => await _calendarService.UpdateAccountCalendarAsync(e.AccountCalendar).ConfigureAwait(false); - private async Task InitializeAccountCalendarsAsync() + private async Task InitializeAccountCalendarsAsync() + { + await Dispatcher.ExecuteOnUIThread(() => AccountCalendarStateService.ClearGroupedAccountCalendar()); + + var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false); + + foreach (var account in accounts) { - await Dispatcher.ExecuteOnUIThread(() => AccountCalendarStateService.ClearGroupedAccountCalendar()); + var accountCalendars = await _calendarService.GetAccountCalendarsAsync(account.Id).ConfigureAwait(false); + var calendarViewModels = new List(); - var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false); - - foreach (var account in accounts) + foreach (var calendar in accountCalendars) { - var accountCalendars = await _calendarService.GetAccountCalendarsAsync(account.Id).ConfigureAwait(false); - var calendarViewModels = new List(); + var calendarViewModel = new AccountCalendarViewModel(account, calendar); - foreach (var calendar in accountCalendars) - { - var calendarViewModel = new AccountCalendarViewModel(account, calendar); - - calendarViewModels.Add(calendarViewModel); - } - - var groupedAccountCalendarViewModel = new GroupedAccountCalendarViewModel(account, calendarViewModels); - - await Dispatcher.ExecuteOnUIThread(() => - { - AccountCalendarStateService.AddGroupedAccountCalendar(groupedAccountCalendarViewModel); - }); - } - } - - private void ForceNavigateCalendarDate() - { - if (SelectedMenuItemIndex == -1) - { - var args = new CalendarPageNavigationArgs() - { - NavigationDate = _navigationDate ?? DateTime.Now.Date - }; - - // Already on calendar. Just navigate. - NavigationService.Navigate(WinoPage.CalendarPage, args); - - _navigationDate = null; - } - else - { - SelectedMenuItemIndex = -1; - } - } - - partial void OnSelectedMenuItemIndexChanged(int oldValue, int newValue) - { - switch (newValue) - { - 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); - - foreach (var account in accounts) - { - var t = new NewCalendarSynchronizationRequested(new CalendarSynchronizationOptions() - { - AccountId = account.Id, - Type = CalendarSynchronizationType.CalendarMetadata - }, SynchronizationSource.Client); - - Messenger.Send(t); - } - } - - /// - /// When calendar type switches, we need to navigate to the most ideal date. - /// This method returns that date. - /// - private DateTime GetDisplayTypeSwitchDate() - { - var settings = PreferencesService.GetCurrentCalendarSettings(); - switch (StatePersistenceService.CalendarDisplayType) - { - 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); - } - - return HighlightedDateRange.StartDate.GetWeekStartDateForDate(settings.FirstDayOfWeek); - case CalendarDisplayType.WorkWeek: - break; - case CalendarDisplayType.Month: - break; - case CalendarDisplayType.Year: - break; - default: - break; + calendarViewModels.Add(calendarViewModel); } - return DateTime.Today.Date; - } + var groupedAccountCalendarViewModel = new GroupedAccountCalendarViewModel(account, calendarViewModels); - private DateTime? _navigationDate; - private readonly IAccountService _accountService; - private readonly ICalendarService _calendarService; - - #region Commands - - [RelayCommand] - private void TodayClicked() - { - _navigationDate = DateTime.Now.Date; - - ForceNavigateCalendarDate(); - } - - [RelayCommand] - public void ManageAccounts() => NavigationService.Navigate(WinoPage.AccountManagementPage); - - [RelayCommand] - private Task ReconnectServerAsync() => ServerConnectionManager.ConnectAsync(); - - [RelayCommand] - private void DateClicked(CalendarViewDayClickedEventArgs clickedDateArgs) - { - _navigationDate = clickedDateArgs.ClickedDate; - - ForceNavigateCalendarDate(); - } - - #endregion - - public void Receive(VisibleDateRangeChangedMessage message) => HighlightedDateRange = message.DateRange; - - /// - /// Sets the header navigation items based on visible date range and calendar type. - /// - private void UpdateDateNavigationHeaderItems() - { - DateNavigationHeaderItems.Clear(); - - // TODO: From settings - var testInfo = new CultureInfo("en-US"); - - switch (StatePersistenceService.CalendarDisplayType) + await Dispatcher.ExecuteOnUIThread(() => { - case CalendarDisplayType.Day: - case CalendarDisplayType.Week: - case CalendarDisplayType.WorkWeek: - case CalendarDisplayType.Month: - DateNavigationHeaderItems.ReplaceRange(testInfo.DateTimeFormat.MonthNames); - break; - case CalendarDisplayType.Year: - break; - default: - break; - } - - SetDateNavigationHeaderItems(); - } - - partial void OnHighlightedDateRangeChanged(DateRange value) => SetDateNavigationHeaderItems(); - - private void SetDateNavigationHeaderItems() - { - if (HighlightedDateRange == null) return; - - if (DateNavigationHeaderItems.Count == 0) - { - UpdateDateNavigationHeaderItems(); - } - - // TODO: Year view - var monthIndex = HighlightedDateRange.GetMostVisibleMonthIndex(); - - SelectedDateNavigationHeaderIndex = Math.Max(monthIndex - 1, -1); - } - - public async void Receive(CalendarEnableStatusChangedMessage message) - => await ExecuteUIThread(() => IsCalendarEnabled = message.IsEnabled); - - public void Receive(NavigateManageAccountsRequested message) => SelectedMenuItemIndex = 1; - - public void Receive(CalendarDisplayTypeChangedMessage message) => OnPropertyChanged(nameof(IsVerticalCalendar)); - - public async void Receive(DetailsPageStateChangedMessage message) - { - await ExecuteUIThread(() => - { - IsEventDetailsPageActive = message.IsActivated; - - // TODO: This is for Wino Mail. Generalize this later on. - StatePersistenceService.IsReaderNarrowed = message.IsActivated; - StatePersistenceService.IsReadingMail = message.IsActivated; + AccountCalendarStateService.AddGroupedAccountCalendar(groupedAccountCalendarViewModel); }); } } + + private void ForceNavigateCalendarDate() + { + if (SelectedMenuItemIndex == -1) + { + var args = new CalendarPageNavigationArgs() + { + NavigationDate = _navigationDate ?? DateTime.Now.Date + }; + + // Already on calendar. Just navigate. + NavigationService.Navigate(WinoPage.CalendarPage, args); + + _navigationDate = null; + } + else + { + SelectedMenuItemIndex = -1; + } + } + + partial void OnSelectedMenuItemIndexChanged(int oldValue, int newValue) + { + switch (newValue) + { + 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); + + foreach (var account in accounts) + { + var t = new NewCalendarSynchronizationRequested(new CalendarSynchronizationOptions() + { + AccountId = account.Id, + Type = CalendarSynchronizationType.CalendarMetadata + }, SynchronizationSource.Client); + + Messenger.Send(t); + } + } + + /// + /// When calendar type switches, we need to navigate to the most ideal date. + /// This method returns that date. + /// + private DateTime GetDisplayTypeSwitchDate() + { + var settings = PreferencesService.GetCurrentCalendarSettings(); + switch (StatePersistenceService.CalendarDisplayType) + { + 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); + } + + return HighlightedDateRange.StartDate.GetWeekStartDateForDate(settings.FirstDayOfWeek); + case CalendarDisplayType.WorkWeek: + break; + case CalendarDisplayType.Month: + break; + case CalendarDisplayType.Year: + break; + default: + break; + } + + return DateTime.Today.Date; + } + + private DateTime? _navigationDate; + private readonly IAccountService _accountService; + private readonly ICalendarService _calendarService; + + #region Commands + + [RelayCommand] + private void TodayClicked() + { + _navigationDate = DateTime.Now.Date; + + ForceNavigateCalendarDate(); + } + + [RelayCommand] + public void ManageAccounts() => NavigationService.Navigate(WinoPage.AccountManagementPage); + + [RelayCommand] + private Task ReconnectServerAsync() => ServerConnectionManager.ConnectAsync(); + + [RelayCommand] + private void DateClicked(CalendarViewDayClickedEventArgs clickedDateArgs) + { + _navigationDate = clickedDateArgs.ClickedDate; + + ForceNavigateCalendarDate(); + } + + #endregion + + public void Receive(VisibleDateRangeChangedMessage message) => HighlightedDateRange = message.DateRange; + + /// + /// Sets the header navigation items based on visible date range and calendar type. + /// + private void UpdateDateNavigationHeaderItems() + { + DateNavigationHeaderItems.Clear(); + + // TODO: From settings + var testInfo = new CultureInfo("en-US"); + + 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; + } + + SetDateNavigationHeaderItems(); + } + + partial void OnHighlightedDateRangeChanged(DateRange value) => SetDateNavigationHeaderItems(); + + private void SetDateNavigationHeaderItems() + { + if (HighlightedDateRange == null) return; + + if (DateNavigationHeaderItems.Count == 0) + { + UpdateDateNavigationHeaderItems(); + } + + // TODO: Year view + var monthIndex = HighlightedDateRange.GetMostVisibleMonthIndex(); + + SelectedDateNavigationHeaderIndex = Math.Max(monthIndex - 1, -1); + } + + public async void Receive(CalendarEnableStatusChangedMessage message) + => await ExecuteUIThread(() => IsCalendarEnabled = message.IsEnabled); + + public void Receive(NavigateManageAccountsRequested message) => SelectedMenuItemIndex = 1; + + public void Receive(CalendarDisplayTypeChangedMessage message) => OnPropertyChanged(nameof(IsVerticalCalendar)); + + public async void Receive(DetailsPageStateChangedMessage message) + { + await ExecuteUIThread(() => + { + IsEventDetailsPageActive = message.IsActivated; + + // TODO: This is for Wino Mail. Generalize this later on. + StatePersistenceService.IsReaderNarrowed = message.IsActivated; + StatePersistenceService.IsReadingMail = message.IsActivated; + }); + } } diff --git a/Wino.Calendar.ViewModels/CalendarPageViewModel.cs b/Wino.Calendar.ViewModels/CalendarPageViewModel.cs index 9a977c06..99cd7a94 100644 --- a/Wino.Calendar.ViewModels/CalendarPageViewModel.cs +++ b/Wino.Calendar.ViewModels/CalendarPageViewModel.cs @@ -23,849 +23,848 @@ using Wino.Core.Domain.Models.Navigation; using Wino.Core.ViewModels; using Wino.Messaging.Client.Calendar; -namespace Wino.Calendar.ViewModels +namespace Wino.Calendar.ViewModels; + +public partial class CalendarPageViewModel : CalendarBaseViewModel, + IRecipient, + IRecipient, + IRecipient, + IRecipient, + IRecipient, + IRecipient { - public partial class CalendarPageViewModel : CalendarBaseViewModel, - IRecipient, - IRecipient, - IRecipient, - IRecipient, - IRecipient, - IRecipient + #region Quick Event Creation + + [ObservableProperty] + private bool _isQuickEventDialogOpen; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(SelectedQuickEventAccountCalendarName))] + [NotifyCanExecuteChangedFor(nameof(SaveQuickEventCommand))] + private AccountCalendarViewModel _selectedQuickEventAccountCalendar; + + public string SelectedQuickEventAccountCalendarName { - #region Quick Event Creation - - [ObservableProperty] - private bool _isQuickEventDialogOpen; - - [ObservableProperty] - [NotifyPropertyChangedFor(nameof(SelectedQuickEventAccountCalendarName))] - [NotifyCanExecuteChangedFor(nameof(SaveQuickEventCommand))] - private AccountCalendarViewModel _selectedQuickEventAccountCalendar; - - public string SelectedQuickEventAccountCalendarName + get { - get + return SelectedQuickEventAccountCalendar == null ? "Pick a calendar" : SelectedQuickEventAccountCalendar.Name; + } + } + + [ObservableProperty] + private List _hourSelectionStrings; + + // To be able to revert the values when the user enters an invalid time. + private string _previousSelectedStartTimeString; + private string _previousSelectedEndTimeString; + + [ObservableProperty] + private DateTime? _selectedQuickEventDate; + + [ObservableProperty] + private bool _isAllDay; + + [ObservableProperty] + [NotifyCanExecuteChangedFor(nameof(SaveQuickEventCommand))] + private string _selectedStartTimeString; + + [ObservableProperty] + [NotifyCanExecuteChangedFor(nameof(SaveQuickEventCommand))] + private string _selectedEndTimeString; + + [ObservableProperty] + private string _location; + + [ObservableProperty] + [NotifyCanExecuteChangedFor(nameof(SaveQuickEventCommand))] + private string _eventName; + + public DateTime QuickEventStartTime => SelectedQuickEventDate.Value.Date.Add(CurrentSettings.GetTimeSpan(SelectedStartTimeString).Value); + public DateTime QuickEventEndTime => SelectedQuickEventDate.Value.Date.Add(CurrentSettings.GetTimeSpan(SelectedEndTimeString).Value); + + public bool CanSaveQuickEvent => SelectedQuickEventAccountCalendar != null && + !string.IsNullOrWhiteSpace(EventName) && + !string.IsNullOrWhiteSpace(SelectedStartTimeString) && + !string.IsNullOrWhiteSpace(SelectedEndTimeString) && + QuickEventEndTime > QuickEventStartTime; + + #endregion + + #region Data Initialization + + [ObservableProperty] + private CalendarOrientation _calendarOrientation = CalendarOrientation.Horizontal; + + [ObservableProperty] + private DayRangeCollection _dayRanges = []; + + [ObservableProperty] + private int _selectedDateRangeIndex; + + [ObservableProperty] + private DayRangeRenderModel _selectedDayRange; + + [ObservableProperty] + private bool _isCalendarEnabled = true; + + #endregion + + #region Event Details + + public event EventHandler DetailsShowCalendarItemChanged; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(IsEventDetailsVisible))] + private CalendarItemViewModel _displayDetailsCalendarItemViewModel; + + public bool IsEventDetailsVisible => DisplayDetailsCalendarItemViewModel != null; + + #endregion + + // TODO: Get rid of some of the items if we have too many. + private const int maxDayRangeSize = 10; + + private readonly ICalendarService _calendarService; + private readonly INavigationService _navigationService; + private readonly IKeyPressService _keyPressService; + private readonly IPreferencesService _preferencesService; + + // Store latest rendered options. + private CalendarDisplayType _currentDisplayType; + private int _displayDayCount; + + private SemaphoreSlim _calendarLoadingSemaphore = new(1); + private bool isLoadMoreBlocked = false; + + [ObservableProperty] + private CalendarSettings _currentSettings; + + public IStatePersistanceService StatePersistanceService { get; } + public IAccountCalendarStateService AccountCalendarStateService { get; } + + public CalendarPageViewModel(IStatePersistanceService statePersistanceService, + ICalendarService calendarService, + INavigationService navigationService, + IKeyPressService keyPressService, + IAccountCalendarStateService accountCalendarStateService, + IPreferencesService preferencesService) + { + StatePersistanceService = statePersistanceService; + AccountCalendarStateService = accountCalendarStateService; + + _calendarService = calendarService; + _navigationService = navigationService; + _keyPressService = keyPressService; + _preferencesService = preferencesService; + + AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested; + AccountCalendarStateService.CollectiveAccountGroupSelectionStateChanged += AccountCalendarStateCollectivelyChanged; + } + + private void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e) + => FilterActiveCalendars(DayRanges); + + private void UpdateAccountCalendarRequested(object sender, AccountCalendarViewModel e) + => FilterActiveCalendars(DayRanges); + + private async void FilterActiveCalendars(IEnumerable dayRangeRenderModels) + { + await ExecuteUIThread(() => + { + var days = dayRangeRenderModels.SelectMany(a => a.CalendarDays); + + days.ForEach(a => a.EventsCollection.FilterByCalendars(AccountCalendarStateService.ActiveCalendars.Select(a => a.Id))); + + DisplayDetailsCalendarItemViewModel = null; + }); + } + + // TODO: Replace when calendar settings are updated. + // Should be a field ideally. + private BaseCalendarTypeDrawingStrategy GetDrawingStrategy(CalendarDisplayType displayType) + { + return displayType switch + { + CalendarDisplayType.Day => new DayCalendarDrawingStrategy(CurrentSettings), + CalendarDisplayType.Week => new WeekCalendarDrawingStrategy(CurrentSettings), + CalendarDisplayType.Month => new MonthCalendarDrawingStrategy(CurrentSettings), + _ => throw new NotImplementedException(), + }; + } + + public override void OnNavigatedFrom(NavigationMode mode, object parameters) + { + // Do not call base method because that will unregister messenger recipient. + // This is a singleton view model and should not be unregistered. + } + + public override void OnNavigatedTo(NavigationMode mode, object parameters) + { + base.OnNavigatedTo(mode, parameters); + + if (mode == NavigationMode.Back) return; + + RefreshSettings(); + + // Automatically select the first primary calendar for quick event dialog. + SelectedQuickEventAccountCalendar = AccountCalendarStateService.ActiveCalendars.FirstOrDefault(a => a.IsPrimary); + } + + [RelayCommand] + private void NavigateSeries() + { + if (DisplayDetailsCalendarItemViewModel == null) return; + + NavigateEvent(DisplayDetailsCalendarItemViewModel, CalendarEventTargetType.Series); + } + + [RelayCommand] + private void NavigateEventDetails() + { + if (DisplayDetailsCalendarItemViewModel == null) return; + + NavigateEvent(DisplayDetailsCalendarItemViewModel, CalendarEventTargetType.Single); + } + + private void NavigateEvent(CalendarItemViewModel calendarItemViewModel, CalendarEventTargetType calendarEventTargetType) + { + var target = new CalendarItemTarget(calendarItemViewModel.CalendarItem, calendarEventTargetType); + _navigationService.Navigate(WinoPage.EventDetailsPage, target); + } + + [RelayCommand(AllowConcurrentExecutions = false, CanExecute = nameof(CanSaveQuickEvent))] + private async Task SaveQuickEventAsync() + { + var durationSeconds = (QuickEventEndTime - QuickEventStartTime).TotalSeconds; + + var testCalendarItem = new CalendarItem + { + CalendarId = SelectedQuickEventAccountCalendar.Id, + StartDate = QuickEventStartTime, + DurationInSeconds = durationSeconds, + CreatedAt = DateTime.UtcNow, + Description = string.Empty, + Location = Location, + Title = EventName, + Id = Guid.NewGuid() + }; + + IsQuickEventDialogOpen = false; + await _calendarService.CreateNewCalendarItemAsync(testCalendarItem, null); + + // TODO: Create the request with the synchronizer. + } + + [RelayCommand] + private void MoreDetails() + { + // TODO: Navigate to advanced event creation page with existing parameters. + } + + public void SelectQuickEventTimeRange(TimeSpan startTime, TimeSpan endTime) + { + IsAllDay = false; + + SelectedStartTimeString = CurrentSettings.GetTimeString(startTime); + SelectedEndTimeString = CurrentSettings.GetTimeString(endTime); + } + + // Manage event detail popup context and select-unselect the proper items. + // Item selection rules are defined in the selection method. + partial void OnDisplayDetailsCalendarItemViewModelChanging(CalendarItemViewModel oldValue, CalendarItemViewModel newValue) + { + if (oldValue != null) + { + UnselectCalendarItem(oldValue); + } + + if (newValue != null) + { + SelectCalendarItem(newValue); + } + } + + // Notify view that the detail context changed. + // This will align the event detail popup to the selected event. + partial void OnDisplayDetailsCalendarItemViewModelChanged(CalendarItemViewModel value) + => DetailsShowCalendarItemChanged?.Invoke(this, EventArgs.Empty); + + private void RefreshSettings() + { + CurrentSettings = _preferencesService.GetCurrentCalendarSettings(); + + // Populate the hour selection strings. + var timeStrings = new List(); + + for (int hour = 0; hour < 24; hour++) + { + for (int minute = 0; minute < 60; minute += 30) { - return SelectedQuickEventAccountCalendar == null ? "Pick a calendar" : SelectedQuickEventAccountCalendar.Name; - } - } + var time = new DateTime(1, 1, 1, hour, minute, 0); - [ObservableProperty] - private List _hourSelectionStrings; - - // To be able to revert the values when the user enters an invalid time. - private string _previousSelectedStartTimeString; - private string _previousSelectedEndTimeString; - - [ObservableProperty] - private DateTime? _selectedQuickEventDate; - - [ObservableProperty] - private bool _isAllDay; - - [ObservableProperty] - [NotifyCanExecuteChangedFor(nameof(SaveQuickEventCommand))] - private string _selectedStartTimeString; - - [ObservableProperty] - [NotifyCanExecuteChangedFor(nameof(SaveQuickEventCommand))] - private string _selectedEndTimeString; - - [ObservableProperty] - private string _location; - - [ObservableProperty] - [NotifyCanExecuteChangedFor(nameof(SaveQuickEventCommand))] - private string _eventName; - - public DateTime QuickEventStartTime => SelectedQuickEventDate.Value.Date.Add(CurrentSettings.GetTimeSpan(SelectedStartTimeString).Value); - public DateTime QuickEventEndTime => SelectedQuickEventDate.Value.Date.Add(CurrentSettings.GetTimeSpan(SelectedEndTimeString).Value); - - public bool CanSaveQuickEvent => SelectedQuickEventAccountCalendar != null && - !string.IsNullOrWhiteSpace(EventName) && - !string.IsNullOrWhiteSpace(SelectedStartTimeString) && - !string.IsNullOrWhiteSpace(SelectedEndTimeString) && - QuickEventEndTime > QuickEventStartTime; - - #endregion - - #region Data Initialization - - [ObservableProperty] - private CalendarOrientation _calendarOrientation = CalendarOrientation.Horizontal; - - [ObservableProperty] - private DayRangeCollection _dayRanges = []; - - [ObservableProperty] - private int _selectedDateRangeIndex; - - [ObservableProperty] - private DayRangeRenderModel _selectedDayRange; - - [ObservableProperty] - private bool _isCalendarEnabled = true; - - #endregion - - #region Event Details - - public event EventHandler DetailsShowCalendarItemChanged; - - [ObservableProperty] - [NotifyPropertyChangedFor(nameof(IsEventDetailsVisible))] - private CalendarItemViewModel _displayDetailsCalendarItemViewModel; - - public bool IsEventDetailsVisible => DisplayDetailsCalendarItemViewModel != null; - - #endregion - - // TODO: Get rid of some of the items if we have too many. - private const int maxDayRangeSize = 10; - - private readonly ICalendarService _calendarService; - private readonly INavigationService _navigationService; - private readonly IKeyPressService _keyPressService; - private readonly IPreferencesService _preferencesService; - - // Store latest rendered options. - private CalendarDisplayType _currentDisplayType; - private int _displayDayCount; - - private SemaphoreSlim _calendarLoadingSemaphore = new(1); - private bool isLoadMoreBlocked = false; - - [ObservableProperty] - private CalendarSettings _currentSettings; - - public IStatePersistanceService StatePersistanceService { get; } - public IAccountCalendarStateService AccountCalendarStateService { get; } - - public CalendarPageViewModel(IStatePersistanceService statePersistanceService, - ICalendarService calendarService, - INavigationService navigationService, - IKeyPressService keyPressService, - IAccountCalendarStateService accountCalendarStateService, - IPreferencesService preferencesService) - { - StatePersistanceService = statePersistanceService; - AccountCalendarStateService = accountCalendarStateService; - - _calendarService = calendarService; - _navigationService = navigationService; - _keyPressService = keyPressService; - _preferencesService = preferencesService; - - AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested; - AccountCalendarStateService.CollectiveAccountGroupSelectionStateChanged += AccountCalendarStateCollectivelyChanged; - } - - private void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e) - => FilterActiveCalendars(DayRanges); - - private void UpdateAccountCalendarRequested(object sender, AccountCalendarViewModel e) - => FilterActiveCalendars(DayRanges); - - private async void FilterActiveCalendars(IEnumerable dayRangeRenderModels) - { - await ExecuteUIThread(() => - { - var days = dayRangeRenderModels.SelectMany(a => a.CalendarDays); - - days.ForEach(a => a.EventsCollection.FilterByCalendars(AccountCalendarStateService.ActiveCalendars.Select(a => a.Id))); - - DisplayDetailsCalendarItemViewModel = null; - }); - } - - // TODO: Replace when calendar settings are updated. - // Should be a field ideally. - private BaseCalendarTypeDrawingStrategy GetDrawingStrategy(CalendarDisplayType displayType) - { - return displayType switch - { - CalendarDisplayType.Day => new DayCalendarDrawingStrategy(CurrentSettings), - CalendarDisplayType.Week => new WeekCalendarDrawingStrategy(CurrentSettings), - CalendarDisplayType.Month => new MonthCalendarDrawingStrategy(CurrentSettings), - _ => throw new NotImplementedException(), - }; - } - - public override void OnNavigatedFrom(NavigationMode mode, object parameters) - { - // Do not call base method because that will unregister messenger recipient. - // This is a singleton view model and should not be unregistered. - } - - public override void OnNavigatedTo(NavigationMode mode, object parameters) - { - base.OnNavigatedTo(mode, parameters); - - if (mode == NavigationMode.Back) return; - - RefreshSettings(); - - // Automatically select the first primary calendar for quick event dialog. - SelectedQuickEventAccountCalendar = AccountCalendarStateService.ActiveCalendars.FirstOrDefault(a => a.IsPrimary); - } - - [RelayCommand] - private void NavigateSeries() - { - if (DisplayDetailsCalendarItemViewModel == null) return; - - NavigateEvent(DisplayDetailsCalendarItemViewModel, CalendarEventTargetType.Series); - } - - [RelayCommand] - private void NavigateEventDetails() - { - if (DisplayDetailsCalendarItemViewModel == null) return; - - NavigateEvent(DisplayDetailsCalendarItemViewModel, CalendarEventTargetType.Single); - } - - private void NavigateEvent(CalendarItemViewModel calendarItemViewModel, CalendarEventTargetType calendarEventTargetType) - { - var target = new CalendarItemTarget(calendarItemViewModel.CalendarItem, calendarEventTargetType); - _navigationService.Navigate(WinoPage.EventDetailsPage, target); - } - - [RelayCommand(AllowConcurrentExecutions = false, CanExecute = nameof(CanSaveQuickEvent))] - private async Task SaveQuickEventAsync() - { - var durationSeconds = (QuickEventEndTime - QuickEventStartTime).TotalSeconds; - - var testCalendarItem = new CalendarItem - { - CalendarId = SelectedQuickEventAccountCalendar.Id, - StartDate = QuickEventStartTime, - DurationInSeconds = durationSeconds, - CreatedAt = DateTime.UtcNow, - Description = string.Empty, - Location = Location, - Title = EventName, - Id = Guid.NewGuid() - }; - - IsQuickEventDialogOpen = false; - await _calendarService.CreateNewCalendarItemAsync(testCalendarItem, null); - - // TODO: Create the request with the synchronizer. - } - - [RelayCommand] - private void MoreDetails() - { - // TODO: Navigate to advanced event creation page with existing parameters. - } - - public void SelectQuickEventTimeRange(TimeSpan startTime, TimeSpan endTime) - { - IsAllDay = false; - - SelectedStartTimeString = CurrentSettings.GetTimeString(startTime); - SelectedEndTimeString = CurrentSettings.GetTimeString(endTime); - } - - // Manage event detail popup context and select-unselect the proper items. - // Item selection rules are defined in the selection method. - partial void OnDisplayDetailsCalendarItemViewModelChanging(CalendarItemViewModel oldValue, CalendarItemViewModel newValue) - { - if (oldValue != null) - { - UnselectCalendarItem(oldValue); - } - - if (newValue != null) - { - SelectCalendarItem(newValue); - } - } - - // Notify view that the detail context changed. - // This will align the event detail popup to the selected event. - partial void OnDisplayDetailsCalendarItemViewModelChanged(CalendarItemViewModel value) - => DetailsShowCalendarItemChanged?.Invoke(this, EventArgs.Empty); - - private void RefreshSettings() - { - CurrentSettings = _preferencesService.GetCurrentCalendarSettings(); - - // Populate the hour selection strings. - var timeStrings = new List(); - - for (int hour = 0; hour < 24; hour++) - { - for (int minute = 0; minute < 60; minute += 30) + if (CurrentSettings.DayHeaderDisplayType == DayHeaderDisplayType.TwentyFourHour) { - var time = new DateTime(1, 1, 1, hour, minute, 0); - - if (CurrentSettings.DayHeaderDisplayType == DayHeaderDisplayType.TwentyFourHour) - { - timeStrings.Add(time.ToString("HH:mm")); - } - else - { - timeStrings.Add(time.ToString("h:mm tt")); - } + timeStrings.Add(time.ToString("HH:mm")); + } + else + { + timeStrings.Add(time.ToString("h:mm tt")); } } - - HourSelectionStrings = timeStrings; } - partial void OnIsCalendarEnabledChanging(bool oldValue, bool newValue) => Messenger.Send(new CalendarEnableStatusChangedMessage(newValue)); + HourSelectionStrings = timeStrings; + } - private bool ShouldResetDayRanges(LoadCalendarMessage message) + partial void OnIsCalendarEnabledChanging(bool oldValue, bool newValue) => Messenger.Send(new CalendarEnableStatusChangedMessage(newValue)); + + private bool ShouldResetDayRanges(LoadCalendarMessage message) + { + if (message.ForceRedraw) return true; + + // Never reset if the initiative is from the app. + if (message.CalendarInitInitiative == CalendarInitInitiative.App) return false; + + // 1. Display type is different. + // 2. Day display count is different. + // 3. Display date is not in the visible range. + + if (DayRanges.DisplayRange == null) return false; + + return + (_currentDisplayType != StatePersistanceService.CalendarDisplayType || + _displayDayCount != StatePersistanceService.DayDisplayCount || + !(message.DisplayDate >= DayRanges.DisplayRange.StartDate && message.DisplayDate <= DayRanges.DisplayRange.EndDate)); + } + + private void AdjustCalendarOrientation() + { + // Orientation only changes when we should reset. + // Handle the FlipView orientation here. + // We don't want to change the orientation while the item manipulation is going on. + // That causes a glitch in the UI. + + bool isRequestedVerticalCalendar = StatePersistanceService.CalendarDisplayType == CalendarDisplayType.Month; + bool isLastRenderedVerticalCalendar = _currentDisplayType == CalendarDisplayType.Month; + + if (isRequestedVerticalCalendar && !isLastRenderedVerticalCalendar) { - if (message.ForceRedraw) return true; - - // Never reset if the initiative is from the app. - if (message.CalendarInitInitiative == CalendarInitInitiative.App) return false; - - // 1. Display type is different. - // 2. Day display count is different. - // 3. Display date is not in the visible range. - - if (DayRanges.DisplayRange == null) return false; - - return - (_currentDisplayType != StatePersistanceService.CalendarDisplayType || - _displayDayCount != StatePersistanceService.DayDisplayCount || - !(message.DisplayDate >= DayRanges.DisplayRange.StartDate && message.DisplayDate <= DayRanges.DisplayRange.EndDate)); + CalendarOrientation = CalendarOrientation.Vertical; } - - private void AdjustCalendarOrientation() + else { - // Orientation only changes when we should reset. - // Handle the FlipView orientation here. - // We don't want to change the orientation while the item manipulation is going on. - // That causes a glitch in the UI. + CalendarOrientation = CalendarOrientation.Horizontal; + } + } - bool isRequestedVerticalCalendar = StatePersistanceService.CalendarDisplayType == CalendarDisplayType.Month; - bool isLastRenderedVerticalCalendar = _currentDisplayType == CalendarDisplayType.Month; + public async void Receive(LoadCalendarMessage message) + { + await _calendarLoadingSemaphore.WaitAsync(); - if (isRequestedVerticalCalendar && !isLastRenderedVerticalCalendar) + try + { + await ExecuteUIThread(() => IsCalendarEnabled = false); + + if (ShouldResetDayRanges(message)) { - CalendarOrientation = CalendarOrientation.Vertical; + Debug.WriteLine("Will reset day ranges."); + await ClearDayRangeModelsAsync(); + } + else if (ShouldScrollToItem(message)) + { + // Scroll to the selected date. + Messenger.Send(new ScrollToDateMessage(message.DisplayDate)); + Debug.WriteLine("Scrolling to selected date."); + return; + } + + AdjustCalendarOrientation(); + + // This will replace the whole collection because the user initiated a new render. + await RenderDatesAsync(message.CalendarInitInitiative, + message.DisplayDate, + CalendarLoadDirection.Replace); + + // Scroll to the current hour. + Messenger.Send(new ScrollToHourMessage(TimeSpan.FromHours(DateTime.Now.Hour))); + } + catch (Exception ex) + { + Log.Error(ex, "Error while loading calendar."); + Debugger.Break(); + } + finally + { + _calendarLoadingSemaphore.Release(); + + await ExecuteUIThread(() => IsCalendarEnabled = true); + } + } + + + private async Task AddDayRangeModelAsync(DayRangeRenderModel dayRangeRenderModel) + { + if (dayRangeRenderModel == null) return; + + await ExecuteUIThread(() => + { + DayRanges.Add(dayRangeRenderModel); + }); + } + + private async Task InsertDayRangeModelAsync(DayRangeRenderModel dayRangeRenderModel, int index) + { + if (dayRangeRenderModel == null) return; + + await ExecuteUIThread(() => + { + DayRanges.Insert(index, dayRangeRenderModel); + }); + } + + private async Task RemoveDayRangeModelAsync(DayRangeRenderModel dayRangeRenderModel) + { + if (dayRangeRenderModel == null) return; + + await ExecuteUIThread(() => + { + DayRanges.Remove(dayRangeRenderModel); + }); + } + + private async Task ClearDayRangeModelsAsync() + { + await ExecuteUIThread(() => + { + DayRanges.Clear(); + }); + } + + private async Task RenderDatesAsync(CalendarInitInitiative calendarInitInitiative, + DateTime? loadingDisplayDate = null, + CalendarLoadDirection calendarLoadDirection = CalendarLoadDirection.Replace) + { + isLoadMoreBlocked = calendarLoadDirection == CalendarLoadDirection.Replace; + + // This is the part we arrange the flip view calendar logic. + + /* Loading for a month of the selected date is fine. + * If the selected date is in the loaded range, we'll just change the selected flip index to scroll. + * If the selected date is not in the loaded range: + * 1. Detect the direction of the scroll. + * 2. Load the next month. + * 3. Replace existing month with the new month. + */ + + // 2 things are important: How many items should 1 flip have, and, where we should start loading? + + // User initiated renders must always have a date to start with. + if (calendarInitInitiative == CalendarInitInitiative.User) Guard.IsNotNull(loadingDisplayDate, nameof(loadingDisplayDate)); + + var strategy = GetDrawingStrategy(StatePersistanceService.CalendarDisplayType); + var displayDate = loadingDisplayDate.GetValueOrDefault(); + + // How many days should be placed in 1 flip view item? + int eachFlipItemCount = strategy.GetRenderDayCount(displayDate, StatePersistanceService.DayDisplayCount); + + DateRange flipLoadRange = null; + + + if (calendarInitInitiative == CalendarInitInitiative.User || DayRanges.DisplayRange == null) + { + flipLoadRange = strategy.GetRenderDateRange(displayDate, StatePersistanceService.DayDisplayCount); + } + else + { + // App is trying to load. + // This should be based on direction. We'll load the next or previous range. + // DisplayDate is either the start or end date of the current visible range. + + if (calendarLoadDirection == CalendarLoadDirection.Previous) + { + flipLoadRange = strategy.GetPreviousDateRange(DayRanges.DisplayRange, StatePersistanceService.DayDisplayCount); } else { - CalendarOrientation = CalendarOrientation.Horizontal; + flipLoadRange = strategy.GetNextDateRange(DayRanges.DisplayRange, StatePersistanceService.DayDisplayCount); } } - public async void Receive(LoadCalendarMessage message) + // Create day ranges for each flip item until we reach the total days to load. + int totalFlipItemCount = (int)Math.Ceiling((double)flipLoadRange.TotalDays / eachFlipItemCount); + + List renderModels = new(); + + for (int i = 0; i < totalFlipItemCount; i++) + { + var startDate = flipLoadRange.StartDate.AddDays(i * eachFlipItemCount); + var endDate = startDate.AddDays(eachFlipItemCount); + + var range = new DateRange(startDate, endDate); + var renderOptions = new CalendarRenderOptions(range, CurrentSettings); + + var dayRangeHeaderModel = new DayRangeRenderModel(renderOptions); + renderModels.Add(dayRangeHeaderModel); + } + + // Dates are loaded. Now load the events for them. + foreach (var renderModel in renderModels) + { + await InitializeCalendarEventsForDayRangeAsync(renderModel).ConfigureAwait(false); + } + + // Filter by active calendars. This is a quick operation, and things are not on the UI yet. + FilterActiveCalendars(renderModels); + + CalendarLoadDirection animationDirection = calendarLoadDirection; + + //bool removeCurrent = calendarLoadDirection == CalendarLoadDirection.Replace; + + if (calendarLoadDirection == CalendarLoadDirection.Replace) + { + // New date ranges are being replaced. + // We must preserve existing selection if any, add the items before/after the current one, remove the current one. + // This will make sure the new dates are animated in the correct direction. + + isLoadMoreBlocked = true; + + // Remove all other dates except this one. + var rangesToRemove = DayRanges.Where(a => a != SelectedDayRange).ToList(); + + foreach (var range in rangesToRemove) + { + await RemoveDayRangeModelAsync(range); + } + + animationDirection = displayDate <= SelectedDayRange?.CalendarRenderOptions.DateRange.StartDate ? + CalendarLoadDirection.Previous : CalendarLoadDirection.Next; + } + + if (animationDirection == CalendarLoadDirection.Next) + { + foreach (var item in renderModels) + { + await AddDayRangeModelAsync(item); + } + } + else if (animationDirection == CalendarLoadDirection.Previous) + { + // Wait for the animation to finish. + // Otherwise it somehow shutters a little, which is annoying. + + // if (!removeCurrent) await Task.Delay(350); + + // Insert each render model in reverse order. + for (int i = renderModels.Count - 1; i >= 0; i--) + { + await InsertDayRangeModelAsync(renderModels[i], 0); + } + } + + Debug.WriteLine($"Flip count: ({DayRanges.Count})"); + + foreach (var item in DayRanges) + { + Debug.WriteLine($"- {item.CalendarRenderOptions.DateRange.ToString()}"); + } + + //if (removeCurrent) + //{ + // await RemoveDayRangeModelAsync(SelectedDayRange); + //} + + // TODO... + // await TryConsolidateItemsAsync(); + + isLoadMoreBlocked = false; + + // Only scroll if the render is initiated by user. + // Otherwise we'll scroll to the app rendered invisible date range. + if (calendarInitInitiative == CalendarInitInitiative.User) + { + // Save the current settings for the page for later comparison. + _currentDisplayType = StatePersistanceService.CalendarDisplayType; + _displayDayCount = StatePersistanceService.DayDisplayCount; + + Messenger.Send(new ScrollToDateMessage(displayDate)); + } + } + + protected override async void OnCalendarItemAdded(CalendarItem calendarItem) + { + base.OnCalendarItemAdded(calendarItem); + + // 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)); + + foreach (var calendarDay in allDaysForEvent) + { + var calendarItemViewModel = new CalendarItemViewModel(calendarItem); + + await ExecuteUIThread(() => + { + calendarDay.EventsCollection.AddCalendarItem(calendarItemViewModel); + }); + } + + FilterActiveCalendars(DayRanges); + } + + private async Task InitializeCalendarEventsForDayRangeAsync(DayRangeRenderModel dayRangeRenderModel) + { + // Clear all events first for all days. + foreach (var day in dayRangeRenderModel.CalendarDays) + { + await ExecuteUIThread(() => + { + day.EventsCollection.Clear(); + }); + } + + // Initialization is done for all calendars, regardless whether they are actively selected or not. + // This is because the filtering is cached internally of the calendar items in CalendarEventCollection. + var allCalendars = AccountCalendarStateService.GroupedAccountCalendars.SelectMany(a => a.AccountCalendars); + + foreach (var calendarViewModel in allCalendars) + { + // 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).ConfigureAwait(false); + + foreach (var @event in events) + { + // Find the days that the event falls into. + var allDaysForEvent = dayRangeRenderModel.CalendarDays.Where(a => a.Period.OverlapsWith(@event.Period)); + + foreach (var calendarDay in allDaysForEvent) + { + var calendarItemViewModel = new CalendarItemViewModel(@event); + await ExecuteUIThread(() => + { + calendarDay.EventsCollection.AddCalendarItem(calendarItemViewModel); + }); + } + } + } + } + + private async Task TryConsolidateItemsAsync() + { + // Check if trimming is necessary + if (DayRanges.Count > maxDayRangeSize) + { + Debug.WriteLine("Trimming items."); + + isLoadMoreBlocked = true; + + var removeCount = DayRanges.Count - maxDayRangeSize; + + await Task.Delay(500); + + // Right shifted, remove from the start. + if (SelectedDateRangeIndex > DayRanges.Count / 2) + { + DayRanges.RemoveRange(DayRanges.Take(removeCount).ToList()); + } + else + { + // Left shifted, remove from the end. + DayRanges.RemoveRange(DayRanges.Skip(DayRanges.Count - removeCount).Take(removeCount)); + } + + SelectedDateRangeIndex = DayRanges.IndexOf(SelectedDayRange); + } + } + + private bool ShouldScrollToItem(LoadCalendarMessage message) + { + // Never scroll if the initiative is from the app. + if (message.CalendarInitInitiative == CalendarInitInitiative.App) return false; + + // Nothing to scroll. + if (DayRanges.Count == 0) return false; + + if (DayRanges.DisplayRange == null) return false; + + var selectedDate = message.DisplayDate; + + return selectedDate >= DayRanges.DisplayRange.StartDate && selectedDate <= DayRanges.DisplayRange.EndDate; + } + + partial void OnIsAllDayChanged(bool value) + { + if (value) + { + SelectedStartTimeString = HourSelectionStrings.FirstOrDefault(); + SelectedEndTimeString = HourSelectionStrings.FirstOrDefault(); + } + else + { + SelectedStartTimeString = _previousSelectedStartTimeString; + SelectedEndTimeString = _previousSelectedEndTimeString; + } + } + + partial void OnSelectedStartTimeStringChanged(string newValue) + { + var parsedTime = CurrentSettings.GetTimeSpan(newValue); + + if (parsedTime == null) + { + SelectedStartTimeString = _previousSelectedStartTimeString; + } + else if (IsAllDay) + { + _previousSelectedStartTimeString = newValue; + } + } + + partial void OnSelectedEndTimeStringChanged(string newValue) + { + var parsedTime = CurrentSettings.GetTimeSpan(newValue); + + if (parsedTime == null) + { + SelectedEndTimeString = _previousSelectedStartTimeString; + } + else if (IsAllDay) + { + _previousSelectedEndTimeString = newValue; + } + } + + partial void OnSelectedDayRangeChanged(DayRangeRenderModel value) + { + DisplayDetailsCalendarItemViewModel = null; + + if (DayRanges.Count == 0 || SelectedDateRangeIndex < 0) return; + + var selectedRange = DayRanges[SelectedDateRangeIndex]; + + Messenger.Send(new VisibleDateRangeChangedMessage(new DateRange(selectedRange.Period.Start, selectedRange.Period.End))); + + if (isLoadMoreBlocked) return; + + _ = LoadMoreAsync(); + } + + private async Task LoadMoreAsync() + { + try { await _calendarLoadingSemaphore.WaitAsync(); - try + // Depending on the selected index, we'll load more dates. + // Day ranges may change while the async update is in progress. + // Therefore we wait for semaphore to be released before we continue. + // There is no need to load more if the current index is not in ideal position. + + if (SelectedDateRangeIndex == 0) { - await ExecuteUIThread(() => IsCalendarEnabled = false); - - if (ShouldResetDayRanges(message)) - { - Debug.WriteLine("Will reset day ranges."); - await ClearDayRangeModelsAsync(); - } - else if (ShouldScrollToItem(message)) - { - // Scroll to the selected date. - Messenger.Send(new ScrollToDateMessage(message.DisplayDate)); - Debug.WriteLine("Scrolling to selected date."); - return; - } - - AdjustCalendarOrientation(); - - // This will replace the whole collection because the user initiated a new render. - await RenderDatesAsync(message.CalendarInitInitiative, - message.DisplayDate, - CalendarLoadDirection.Replace); - - // Scroll to the current hour. - Messenger.Send(new ScrollToHourMessage(TimeSpan.FromHours(DateTime.Now.Hour))); + await RenderDatesAsync(CalendarInitInitiative.App, calendarLoadDirection: CalendarLoadDirection.Previous); } - catch (Exception ex) + else if (SelectedDateRangeIndex == DayRanges.Count - 1) { - Log.Error(ex, "Error while loading calendar."); - Debugger.Break(); - } - finally - { - _calendarLoadingSemaphore.Release(); - - await ExecuteUIThread(() => IsCalendarEnabled = true); + await RenderDatesAsync(CalendarInitInitiative.App, calendarLoadDirection: CalendarLoadDirection.Next); } } - - - private async Task AddDayRangeModelAsync(DayRangeRenderModel dayRangeRenderModel) + catch (Exception) { - if (dayRangeRenderModel == null) return; - - await ExecuteUIThread(() => - { - DayRanges.Add(dayRangeRenderModel); - }); + Debugger.Break(); } - - private async Task InsertDayRangeModelAsync(DayRangeRenderModel dayRangeRenderModel, int index) + finally { - if (dayRangeRenderModel == null) return; - - await ExecuteUIThread(() => - { - DayRanges.Insert(index, dayRangeRenderModel); - }); - } - - private async Task RemoveDayRangeModelAsync(DayRangeRenderModel dayRangeRenderModel) - { - if (dayRangeRenderModel == null) return; - - await ExecuteUIThread(() => - { - DayRanges.Remove(dayRangeRenderModel); - }); - } - - private async Task ClearDayRangeModelsAsync() - { - await ExecuteUIThread(() => - { - DayRanges.Clear(); - }); - } - - private async Task RenderDatesAsync(CalendarInitInitiative calendarInitInitiative, - DateTime? loadingDisplayDate = null, - CalendarLoadDirection calendarLoadDirection = CalendarLoadDirection.Replace) - { - isLoadMoreBlocked = calendarLoadDirection == CalendarLoadDirection.Replace; - - // This is the part we arrange the flip view calendar logic. - - /* Loading for a month of the selected date is fine. - * If the selected date is in the loaded range, we'll just change the selected flip index to scroll. - * If the selected date is not in the loaded range: - * 1. Detect the direction of the scroll. - * 2. Load the next month. - * 3. Replace existing month with the new month. - */ - - // 2 things are important: How many items should 1 flip have, and, where we should start loading? - - // User initiated renders must always have a date to start with. - if (calendarInitInitiative == CalendarInitInitiative.User) Guard.IsNotNull(loadingDisplayDate, nameof(loadingDisplayDate)); - - var strategy = GetDrawingStrategy(StatePersistanceService.CalendarDisplayType); - var displayDate = loadingDisplayDate.GetValueOrDefault(); - - // How many days should be placed in 1 flip view item? - int eachFlipItemCount = strategy.GetRenderDayCount(displayDate, StatePersistanceService.DayDisplayCount); - - DateRange flipLoadRange = null; - - - if (calendarInitInitiative == CalendarInitInitiative.User || DayRanges.DisplayRange == null) - { - flipLoadRange = strategy.GetRenderDateRange(displayDate, StatePersistanceService.DayDisplayCount); - } - else - { - // App is trying to load. - // This should be based on direction. We'll load the next or previous range. - // DisplayDate is either the start or end date of the current visible range. - - if (calendarLoadDirection == CalendarLoadDirection.Previous) - { - flipLoadRange = strategy.GetPreviousDateRange(DayRanges.DisplayRange, StatePersistanceService.DayDisplayCount); - } - else - { - flipLoadRange = strategy.GetNextDateRange(DayRanges.DisplayRange, StatePersistanceService.DayDisplayCount); - } - } - - // Create day ranges for each flip item until we reach the total days to load. - int totalFlipItemCount = (int)Math.Ceiling((double)flipLoadRange.TotalDays / eachFlipItemCount); - - List renderModels = new(); - - for (int i = 0; i < totalFlipItemCount; i++) - { - var startDate = flipLoadRange.StartDate.AddDays(i * eachFlipItemCount); - var endDate = startDate.AddDays(eachFlipItemCount); - - var range = new DateRange(startDate, endDate); - var renderOptions = new CalendarRenderOptions(range, CurrentSettings); - - var dayRangeHeaderModel = new DayRangeRenderModel(renderOptions); - renderModels.Add(dayRangeHeaderModel); - } - - // Dates are loaded. Now load the events for them. - foreach (var renderModel in renderModels) - { - await InitializeCalendarEventsForDayRangeAsync(renderModel).ConfigureAwait(false); - } - - // Filter by active calendars. This is a quick operation, and things are not on the UI yet. - FilterActiveCalendars(renderModels); - - CalendarLoadDirection animationDirection = calendarLoadDirection; - - //bool removeCurrent = calendarLoadDirection == CalendarLoadDirection.Replace; - - if (calendarLoadDirection == CalendarLoadDirection.Replace) - { - // New date ranges are being replaced. - // We must preserve existing selection if any, add the items before/after the current one, remove the current one. - // This will make sure the new dates are animated in the correct direction. - - isLoadMoreBlocked = true; - - // Remove all other dates except this one. - var rangesToRemove = DayRanges.Where(a => a != SelectedDayRange).ToList(); - - foreach (var range in rangesToRemove) - { - await RemoveDayRangeModelAsync(range); - } - - animationDirection = displayDate <= SelectedDayRange?.CalendarRenderOptions.DateRange.StartDate ? - CalendarLoadDirection.Previous : CalendarLoadDirection.Next; - } - - if (animationDirection == CalendarLoadDirection.Next) - { - foreach (var item in renderModels) - { - await AddDayRangeModelAsync(item); - } - } - else if (animationDirection == CalendarLoadDirection.Previous) - { - // Wait for the animation to finish. - // Otherwise it somehow shutters a little, which is annoying. - - // if (!removeCurrent) await Task.Delay(350); - - // Insert each render model in reverse order. - for (int i = renderModels.Count - 1; i >= 0; i--) - { - await InsertDayRangeModelAsync(renderModels[i], 0); - } - } - - Debug.WriteLine($"Flip count: ({DayRanges.Count})"); - - foreach (var item in DayRanges) - { - Debug.WriteLine($"- {item.CalendarRenderOptions.DateRange.ToString()}"); - } - - //if (removeCurrent) - //{ - // await RemoveDayRangeModelAsync(SelectedDayRange); - //} - - // TODO... - // await TryConsolidateItemsAsync(); - - isLoadMoreBlocked = false; - - // Only scroll if the render is initiated by user. - // Otherwise we'll scroll to the app rendered invisible date range. - if (calendarInitInitiative == CalendarInitInitiative.User) - { - // Save the current settings for the page for later comparison. - _currentDisplayType = StatePersistanceService.CalendarDisplayType; - _displayDayCount = StatePersistanceService.DayDisplayCount; - - Messenger.Send(new ScrollToDateMessage(displayDate)); - } - } - - protected override async void OnCalendarItemAdded(CalendarItem calendarItem) - { - base.OnCalendarItemAdded(calendarItem); - - // 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)); - - foreach (var calendarDay in allDaysForEvent) - { - var calendarItemViewModel = new CalendarItemViewModel(calendarItem); - - await ExecuteUIThread(() => - { - calendarDay.EventsCollection.AddCalendarItem(calendarItemViewModel); - }); - } - - FilterActiveCalendars(DayRanges); - } - - private async Task InitializeCalendarEventsForDayRangeAsync(DayRangeRenderModel dayRangeRenderModel) - { - // Clear all events first for all days. - foreach (var day in dayRangeRenderModel.CalendarDays) - { - await ExecuteUIThread(() => - { - day.EventsCollection.Clear(); - }); - } - - // Initialization is done for all calendars, regardless whether they are actively selected or not. - // This is because the filtering is cached internally of the calendar items in CalendarEventCollection. - var allCalendars = AccountCalendarStateService.GroupedAccountCalendars.SelectMany(a => a.AccountCalendars); - - foreach (var calendarViewModel in allCalendars) - { - // 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).ConfigureAwait(false); - - foreach (var @event in events) - { - // Find the days that the event falls into. - var allDaysForEvent = dayRangeRenderModel.CalendarDays.Where(a => a.Period.OverlapsWith(@event.Period)); - - foreach (var calendarDay in allDaysForEvent) - { - var calendarItemViewModel = new CalendarItemViewModel(@event); - await ExecuteUIThread(() => - { - calendarDay.EventsCollection.AddCalendarItem(calendarItemViewModel); - }); - } - } - } - } - - private async Task TryConsolidateItemsAsync() - { - // Check if trimming is necessary - if (DayRanges.Count > maxDayRangeSize) - { - Debug.WriteLine("Trimming items."); - - isLoadMoreBlocked = true; - - var removeCount = DayRanges.Count - maxDayRangeSize; - - await Task.Delay(500); - - // Right shifted, remove from the start. - if (SelectedDateRangeIndex > DayRanges.Count / 2) - { - DayRanges.RemoveRange(DayRanges.Take(removeCount).ToList()); - } - else - { - // Left shifted, remove from the end. - DayRanges.RemoveRange(DayRanges.Skip(DayRanges.Count - removeCount).Take(removeCount)); - } - - SelectedDateRangeIndex = DayRanges.IndexOf(SelectedDayRange); - } - } - - private bool ShouldScrollToItem(LoadCalendarMessage message) - { - // Never scroll if the initiative is from the app. - if (message.CalendarInitInitiative == CalendarInitInitiative.App) return false; - - // Nothing to scroll. - if (DayRanges.Count == 0) return false; - - if (DayRanges.DisplayRange == null) return false; - - var selectedDate = message.DisplayDate; - - return selectedDate >= DayRanges.DisplayRange.StartDate && selectedDate <= DayRanges.DisplayRange.EndDate; - } - - partial void OnIsAllDayChanged(bool value) - { - if (value) - { - SelectedStartTimeString = HourSelectionStrings.FirstOrDefault(); - SelectedEndTimeString = HourSelectionStrings.FirstOrDefault(); - } - else - { - SelectedStartTimeString = _previousSelectedStartTimeString; - SelectedEndTimeString = _previousSelectedEndTimeString; - } - } - - partial void OnSelectedStartTimeStringChanged(string newValue) - { - var parsedTime = CurrentSettings.GetTimeSpan(newValue); - - if (parsedTime == null) - { - SelectedStartTimeString = _previousSelectedStartTimeString; - } - else if (IsAllDay) - { - _previousSelectedStartTimeString = newValue; - } - } - - partial void OnSelectedEndTimeStringChanged(string newValue) - { - var parsedTime = CurrentSettings.GetTimeSpan(newValue); - - if (parsedTime == null) - { - SelectedEndTimeString = _previousSelectedStartTimeString; - } - else if (IsAllDay) - { - _previousSelectedEndTimeString = newValue; - } - } - - partial void OnSelectedDayRangeChanged(DayRangeRenderModel value) - { - DisplayDetailsCalendarItemViewModel = null; - - if (DayRanges.Count == 0 || SelectedDateRangeIndex < 0) return; - - var selectedRange = DayRanges[SelectedDateRangeIndex]; - - Messenger.Send(new VisibleDateRangeChangedMessage(new DateRange(selectedRange.Period.Start, selectedRange.Period.End))); - - if (isLoadMoreBlocked) return; - - _ = LoadMoreAsync(); - } - - private async Task LoadMoreAsync() - { - try - { - await _calendarLoadingSemaphore.WaitAsync(); - - // Depending on the selected index, we'll load more dates. - // Day ranges may change while the async update is in progress. - // Therefore we wait for semaphore to be released before we continue. - // There is no need to load more if the current index is not in ideal position. - - if (SelectedDateRangeIndex == 0) - { - await RenderDatesAsync(CalendarInitInitiative.App, calendarLoadDirection: CalendarLoadDirection.Previous); - } - else if (SelectedDateRangeIndex == DayRanges.Count - 1) - { - await RenderDatesAsync(CalendarInitInitiative.App, calendarLoadDirection: CalendarLoadDirection.Next); - } - } - catch (Exception) - { - Debugger.Break(); - } - finally - { - _calendarLoadingSemaphore.Release(); - } - } - - public void Receive(CalendarSettingsUpdatedMessage message) - { - RefreshSettings(); - - // TODO: This might need throttling due to slider in the settings page for hour height. - // or make sure the slider does not update on each tick but on focus lost. - - // Messenger.Send(new LoadCalendarMessage(DateTime.UtcNow.Date, CalendarInitInitiative.App, true)); - } - - private IEnumerable GetCalendarItems(CalendarItemViewModel calendarItemViewModel, CalendarDayModel selectedDay) - { - // All-day and multi-day events are selected collectively. - // Recurring events must be selected as a single instance. - // We need to find the day that the event is in, and then select the event. - - if (!calendarItemViewModel.IsRecurringEvent) - { - return [calendarItemViewModel]; - } - else - { - return DayRanges - .SelectMany(a => a.CalendarDays) - .Select(b => b.EventsCollection.GetCalendarItem(calendarItemViewModel.Id)) - .Where(c => c != null) - .Cast() - .Distinct(); - } - } - - private void UnselectCalendarItem(CalendarItemViewModel calendarItemViewModel, CalendarDayModel calendarDay = null) - { - if (calendarItemViewModel == null) return; - - var itemsToUnselect = GetCalendarItems(calendarItemViewModel, calendarDay); - - foreach (var item in itemsToUnselect) - { - item.IsSelected = false; - } - } - - private void SelectCalendarItem(CalendarItemViewModel calendarItemViewModel, CalendarDayModel calendarDay = null) - { - if (calendarItemViewModel == null) return; - - var itemsToSelect = GetCalendarItems(calendarItemViewModel, calendarDay); - - foreach (var item in itemsToSelect) - { - item.IsSelected = true; - } - } - - public void Receive(CalendarItemTappedMessage message) - { - if (message.CalendarItemViewModel == null) return; - - DisplayDetailsCalendarItemViewModel = message.CalendarItemViewModel; - } - - public void Receive(CalendarItemDoubleTappedMessage message) => NavigateEvent(message.CalendarItemViewModel, CalendarEventTargetType.Single); - - public void Receive(CalendarItemRightTappedMessage message) - { - - } - - public async void Receive(CalendarItemDeleted message) - { - // Each deleted recurrence will report for it's own. - - await ExecuteUIThread(() => - { - var deletedItem = message.CalendarItem; - - // Event might be spreaded into multiple days. - // Remove from all. - - // var calendarItems = GetCalendarItems(deletedItem.Id); - }); + _calendarLoadingSemaphore.Release(); } } + + public void Receive(CalendarSettingsUpdatedMessage message) + { + RefreshSettings(); + + // TODO: This might need throttling due to slider in the settings page for hour height. + // or make sure the slider does not update on each tick but on focus lost. + + // Messenger.Send(new LoadCalendarMessage(DateTime.UtcNow.Date, CalendarInitInitiative.App, true)); + } + + private IEnumerable GetCalendarItems(CalendarItemViewModel calendarItemViewModel, CalendarDayModel selectedDay) + { + // All-day and multi-day events are selected collectively. + // Recurring events must be selected as a single instance. + // We need to find the day that the event is in, and then select the event. + + if (!calendarItemViewModel.IsRecurringEvent) + { + return [calendarItemViewModel]; + } + else + { + return DayRanges + .SelectMany(a => a.CalendarDays) + .Select(b => b.EventsCollection.GetCalendarItem(calendarItemViewModel.Id)) + .Where(c => c != null) + .Cast() + .Distinct(); + } + } + + private void UnselectCalendarItem(CalendarItemViewModel calendarItemViewModel, CalendarDayModel calendarDay = null) + { + if (calendarItemViewModel == null) return; + + var itemsToUnselect = GetCalendarItems(calendarItemViewModel, calendarDay); + + foreach (var item in itemsToUnselect) + { + item.IsSelected = false; + } + } + + private void SelectCalendarItem(CalendarItemViewModel calendarItemViewModel, CalendarDayModel calendarDay = null) + { + if (calendarItemViewModel == null) return; + + var itemsToSelect = GetCalendarItems(calendarItemViewModel, calendarDay); + + foreach (var item in itemsToSelect) + { + item.IsSelected = true; + } + } + + public void Receive(CalendarItemTappedMessage message) + { + if (message.CalendarItemViewModel == null) return; + + DisplayDetailsCalendarItemViewModel = message.CalendarItemViewModel; + } + + public void Receive(CalendarItemDoubleTappedMessage message) => NavigateEvent(message.CalendarItemViewModel, CalendarEventTargetType.Single); + + public void Receive(CalendarItemRightTappedMessage message) + { + + } + + public async void Receive(CalendarItemDeleted message) + { + // Each deleted recurrence will report for it's own. + + await ExecuteUIThread(() => + { + var deletedItem = message.CalendarItem; + + // Event might be spreaded into multiple days. + // Remove from all. + + // var calendarItems = GetCalendarItems(deletedItem.Id); + }); + } } diff --git a/Wino.Calendar.ViewModels/CalendarSettingsPageViewModel.cs b/Wino.Calendar.ViewModels/CalendarSettingsPageViewModel.cs index b9c21f97..4d0075c7 100644 --- a/Wino.Calendar.ViewModels/CalendarSettingsPageViewModel.cs +++ b/Wino.Calendar.ViewModels/CalendarSettingsPageViewModel.cs @@ -8,120 +8,119 @@ using Wino.Core.Domain.Translations; using Wino.Core.ViewModels; using Wino.Messaging.Client.Calendar; -namespace Wino.Calendar.ViewModels +namespace Wino.Calendar.ViewModels; + +public partial class CalendarSettingsPageViewModel : CalendarBaseViewModel { - public partial class CalendarSettingsPageViewModel : CalendarBaseViewModel + [ObservableProperty] + private double _cellHourHeight; + + [ObservableProperty] + private int _selectedFirstDayOfWeekIndex; + + [ObservableProperty] + private bool _is24HourHeaders; + + [ObservableProperty] + private TimeSpan _workingHourStart; + + [ObservableProperty] + private TimeSpan _workingHourEnd; + + [ObservableProperty] + private List _dayNames = []; + + [ObservableProperty] + private int _workingDayStartIndex; + + [ObservableProperty] + private int _workingDayEndIndex; + + public IPreferencesService PreferencesService { get; } + + private readonly bool _isLoaded = false; + + public CalendarSettingsPageViewModel(IPreferencesService preferencesService) { - [ObservableProperty] - private double _cellHourHeight; + PreferencesService = preferencesService; - [ObservableProperty] - private int _selectedFirstDayOfWeekIndex; + var currentLanguageLanguageCode = WinoTranslationDictionary.GetLanguageFileNameRelativePath(preferencesService.CurrentLanguage); - [ObservableProperty] - private bool _is24HourHeaders; + var cultureInfo = new CultureInfo(currentLanguageLanguageCode); - [ObservableProperty] - private TimeSpan _workingHourStart; - - [ObservableProperty] - private TimeSpan _workingHourEnd; - - [ObservableProperty] - private List _dayNames = []; - - [ObservableProperty] - private int _workingDayStartIndex; - - [ObservableProperty] - private int _workingDayEndIndex; - - public IPreferencesService PreferencesService { get; } - - private readonly bool _isLoaded = false; - - public CalendarSettingsPageViewModel(IPreferencesService preferencesService) + // Populate the day names list + for (var i = 0; i < 7; i++) { - PreferencesService = preferencesService; - - var currentLanguageLanguageCode = WinoTranslationDictionary.GetLanguageFileNameRelativePath(preferencesService.CurrentLanguage); - - var cultureInfo = new CultureInfo(currentLanguageLanguageCode); - - // Populate the day names list - for (var i = 0; i < 7; i++) - { - _dayNames.Add(cultureInfo.DateTimeFormat.DayNames[i]); - } - - var cultureFirstDayName = cultureInfo.DateTimeFormat.GetDayName(preferencesService.FirstDayOfWeek); - - _selectedFirstDayOfWeekIndex = _dayNames.IndexOf(cultureFirstDayName); - _is24HourHeaders = preferencesService.Prefer24HourTimeFormat; - _workingHourStart = preferencesService.WorkingHourStart; - _workingHourEnd = preferencesService.WorkingHourEnd; - _cellHourHeight = preferencesService.HourHeight; - - _workingDayStartIndex = _dayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayStart)); - _workingDayEndIndex = _dayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayEnd)); - - _isLoaded = true; + _dayNames.Add(cultureInfo.DateTimeFormat.DayNames[i]); } - partial void OnCellHourHeightChanged(double oldValue, double newValue) => SaveSettings(); - partial void OnIs24HourHeadersChanged(bool value) => SaveSettings(); - partial void OnSelectedFirstDayOfWeekIndexChanged(int value) => SaveSettings(); - partial void OnWorkingHourStartChanged(TimeSpan value) => SaveSettings(); - partial void OnWorkingHourEndChanged(TimeSpan value) => SaveSettings(); - partial void OnWorkingDayStartIndexChanged(int value) => SaveSettings(); - partial void OnWorkingDayEndIndexChanged(int value) => SaveSettings(); + var cultureFirstDayName = cultureInfo.DateTimeFormat.GetDayName(preferencesService.FirstDayOfWeek); - public void SaveSettings() + _selectedFirstDayOfWeekIndex = _dayNames.IndexOf(cultureFirstDayName); + _is24HourHeaders = preferencesService.Prefer24HourTimeFormat; + _workingHourStart = preferencesService.WorkingHourStart; + _workingHourEnd = preferencesService.WorkingHourEnd; + _cellHourHeight = preferencesService.HourHeight; + + _workingDayStartIndex = _dayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayStart)); + _workingDayEndIndex = _dayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayEnd)); + + _isLoaded = true; + } + + partial void OnCellHourHeightChanged(double oldValue, double newValue) => SaveSettings(); + partial void OnIs24HourHeadersChanged(bool value) => SaveSettings(); + partial void OnSelectedFirstDayOfWeekIndexChanged(int value) => SaveSettings(); + partial void OnWorkingHourStartChanged(TimeSpan value) => SaveSettings(); + partial void OnWorkingHourEndChanged(TimeSpan value) => SaveSettings(); + partial void OnWorkingDayStartIndexChanged(int value) => SaveSettings(); + partial void OnWorkingDayEndIndexChanged(int value) => SaveSettings(); + + public void SaveSettings() + { + if (!_isLoaded) return; + + PreferencesService.FirstDayOfWeek = SelectedFirstDayOfWeekIndex switch { - if (!_isLoaded) return; + 0 => DayOfWeek.Sunday, + 1 => DayOfWeek.Monday, + 2 => DayOfWeek.Tuesday, + 3 => DayOfWeek.Wednesday, + 4 => DayOfWeek.Thursday, + 5 => DayOfWeek.Friday, + 6 => DayOfWeek.Saturday, + _ => throw new ArgumentOutOfRangeException() + }; - PreferencesService.FirstDayOfWeek = SelectedFirstDayOfWeekIndex switch - { - 0 => DayOfWeek.Sunday, - 1 => DayOfWeek.Monday, - 2 => DayOfWeek.Tuesday, - 3 => DayOfWeek.Wednesday, - 4 => DayOfWeek.Thursday, - 5 => DayOfWeek.Friday, - 6 => DayOfWeek.Saturday, - _ => throw new ArgumentOutOfRangeException() - }; + PreferencesService.WorkingDayStart = WorkingDayStartIndex switch + { + 0 => DayOfWeek.Sunday, + 1 => DayOfWeek.Monday, + 2 => DayOfWeek.Tuesday, + 3 => DayOfWeek.Wednesday, + 4 => DayOfWeek.Thursday, + 5 => DayOfWeek.Friday, + 6 => DayOfWeek.Saturday, + _ => throw new ArgumentOutOfRangeException() + }; - PreferencesService.WorkingDayStart = WorkingDayStartIndex switch - { - 0 => DayOfWeek.Sunday, - 1 => DayOfWeek.Monday, - 2 => DayOfWeek.Tuesday, - 3 => DayOfWeek.Wednesday, - 4 => DayOfWeek.Thursday, - 5 => DayOfWeek.Friday, - 6 => DayOfWeek.Saturday, - _ => throw new ArgumentOutOfRangeException() - }; + PreferencesService.WorkingDayEnd = WorkingDayEndIndex switch + { + 0 => DayOfWeek.Sunday, + 1 => DayOfWeek.Monday, + 2 => DayOfWeek.Tuesday, + 3 => DayOfWeek.Wednesday, + 4 => DayOfWeek.Thursday, + 5 => DayOfWeek.Friday, + 6 => DayOfWeek.Saturday, + _ => throw new ArgumentOutOfRangeException() + }; - PreferencesService.WorkingDayEnd = WorkingDayEndIndex switch - { - 0 => DayOfWeek.Sunday, - 1 => DayOfWeek.Monday, - 2 => DayOfWeek.Tuesday, - 3 => DayOfWeek.Wednesday, - 4 => DayOfWeek.Thursday, - 5 => DayOfWeek.Friday, - 6 => DayOfWeek.Saturday, - _ => throw new ArgumentOutOfRangeException() - }; + PreferencesService.Prefer24HourTimeFormat = Is24HourHeaders; + PreferencesService.WorkingHourStart = WorkingHourStart; + PreferencesService.WorkingHourEnd = WorkingHourEnd; + PreferencesService.HourHeight = CellHourHeight; - PreferencesService.Prefer24HourTimeFormat = Is24HourHeaders; - PreferencesService.WorkingHourStart = WorkingHourStart; - PreferencesService.WorkingHourEnd = WorkingHourEnd; - PreferencesService.HourHeight = CellHourHeight; - - Messenger.Send(new CalendarSettingsUpdatedMessage()); - } + Messenger.Send(new CalendarSettingsUpdatedMessage()); } } diff --git a/Wino.Calendar.ViewModels/CalendarViewModelContainerSetup.cs b/Wino.Calendar.ViewModels/CalendarViewModelContainerSetup.cs index 4fb5a466..2f8b452d 100644 --- a/Wino.Calendar.ViewModels/CalendarViewModelContainerSetup.cs +++ b/Wino.Calendar.ViewModels/CalendarViewModelContainerSetup.cs @@ -1,13 +1,12 @@ using Microsoft.Extensions.DependencyInjection; using Wino.Core; -namespace Wino.Calendar.ViewModels +namespace Wino.Calendar.ViewModels; + +public static class CalendarViewModelContainerSetup { - public static class CalendarViewModelContainerSetup + public static void RegisterCalendarViewModelServices(this IServiceCollection services) { - public static void RegisterCalendarViewModelServices(this IServiceCollection services) - { - services.RegisterCoreServices(); - } + services.RegisterCoreServices(); } } diff --git a/Wino.Calendar.ViewModels/Data/AccountCalendarViewModel.cs b/Wino.Calendar.ViewModels/Data/AccountCalendarViewModel.cs index 98903ed6..df55f72f 100644 --- a/Wino.Calendar.ViewModels/Data/AccountCalendarViewModel.cs +++ b/Wino.Calendar.ViewModels/Data/AccountCalendarViewModel.cs @@ -4,67 +4,66 @@ using Wino.Core.Domain.Entities.Calendar; using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Interfaces; -namespace Wino.Calendar.ViewModels.Data +namespace Wino.Calendar.ViewModels.Data; + +public partial class AccountCalendarViewModel : ObservableObject, IAccountCalendar { - public partial class AccountCalendarViewModel : ObservableObject, IAccountCalendar + public MailAccount Account { get; } + public AccountCalendar AccountCalendar { get; } + + public AccountCalendarViewModel(MailAccount account, AccountCalendar accountCalendar) { - public MailAccount Account { get; } - public AccountCalendar AccountCalendar { get; } + Account = account; + AccountCalendar = accountCalendar; - public AccountCalendarViewModel(MailAccount account, AccountCalendar accountCalendar) - { - Account = account; - AccountCalendar = accountCalendar; - - IsChecked = accountCalendar.IsExtended; - } - - [ObservableProperty] - private bool _isChecked; - - partial void OnIsCheckedChanged(bool value) => IsExtended = value; - - public string Name - { - get => AccountCalendar.Name; - set => SetProperty(AccountCalendar.Name, value, AccountCalendar, (u, n) => u.Name = n); - } - - public string TextColorHex - { - get => AccountCalendar.TextColorHex; - set => SetProperty(AccountCalendar.TextColorHex, value, AccountCalendar, (u, t) => u.TextColorHex = t); - } - - public string BackgroundColorHex - { - get => AccountCalendar.BackgroundColorHex; - set => SetProperty(AccountCalendar.BackgroundColorHex, value, AccountCalendar, (u, b) => u.BackgroundColorHex = b); - } - - public bool IsExtended - { - get => AccountCalendar.IsExtended; - set => SetProperty(AccountCalendar.IsExtended, value, AccountCalendar, (u, i) => u.IsExtended = i); - } - - public bool IsPrimary - { - get => AccountCalendar.IsPrimary; - set => SetProperty(AccountCalendar.IsPrimary, value, AccountCalendar, (u, i) => u.IsPrimary = i); - } - - public Guid AccountId - { - get => AccountCalendar.AccountId; - set => SetProperty(AccountCalendar.AccountId, value, AccountCalendar, (u, a) => u.AccountId = a); - } - - public string RemoteCalendarId - { - 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; } + IsChecked = accountCalendar.IsExtended; } + + [ObservableProperty] + private bool _isChecked; + + partial void OnIsCheckedChanged(bool value) => IsExtended = value; + + public string Name + { + get => AccountCalendar.Name; + set => SetProperty(AccountCalendar.Name, value, AccountCalendar, (u, n) => u.Name = n); + } + + public string TextColorHex + { + get => AccountCalendar.TextColorHex; + set => SetProperty(AccountCalendar.TextColorHex, value, AccountCalendar, (u, t) => u.TextColorHex = t); + } + + public string BackgroundColorHex + { + get => AccountCalendar.BackgroundColorHex; + set => SetProperty(AccountCalendar.BackgroundColorHex, value, AccountCalendar, (u, b) => u.BackgroundColorHex = b); + } + + public bool IsExtended + { + get => AccountCalendar.IsExtended; + set => SetProperty(AccountCalendar.IsExtended, value, AccountCalendar, (u, i) => u.IsExtended = i); + } + + public bool IsPrimary + { + get => AccountCalendar.IsPrimary; + set => SetProperty(AccountCalendar.IsPrimary, value, AccountCalendar, (u, i) => u.IsPrimary = i); + } + + public Guid AccountId + { + get => AccountCalendar.AccountId; + set => SetProperty(AccountCalendar.AccountId, value, AccountCalendar, (u, a) => u.AccountId = a); + } + + public string RemoteCalendarId + { + 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; } } diff --git a/Wino.Calendar.ViewModels/Data/CalendarItemViewModel.cs b/Wino.Calendar.ViewModels/Data/CalendarItemViewModel.cs index edd0cb9c..f402a562 100644 --- a/Wino.Calendar.ViewModels/Data/CalendarItemViewModel.cs +++ b/Wino.Calendar.ViewModels/Data/CalendarItemViewModel.cs @@ -5,42 +5,41 @@ using Itenso.TimePeriod; using Wino.Core.Domain.Entities.Calendar; using Wino.Core.Domain.Interfaces; -namespace Wino.Calendar.ViewModels.Data +namespace Wino.Calendar.ViewModels.Data; + +public partial class CalendarItemViewModel : ObservableObject, ICalendarItem, ICalendarItemViewModel { - public partial class CalendarItemViewModel : ObservableObject, ICalendarItem, ICalendarItemViewModel + public CalendarItem CalendarItem { get; } + + public string Title => CalendarItem.Title; + + public Guid Id => CalendarItem.Id; + + public IAccountCalendar AssignedCalendar => CalendarItem.AssignedCalendar; + + public DateTime StartDate { get => CalendarItem.StartDate; set => CalendarItem.StartDate = value; } + + public DateTime EndDate => CalendarItem.EndDate; + + public double DurationInSeconds { get => CalendarItem.DurationInSeconds; set => CalendarItem.DurationInSeconds = value; } + + public ITimePeriod Period => CalendarItem.Period; + + public bool IsAllDayEvent => CalendarItem.IsAllDayEvent; + public bool IsMultiDayEvent => CalendarItem.IsMultiDayEvent; + public bool IsRecurringEvent => CalendarItem.IsRecurringEvent; + public bool IsRecurringChild => CalendarItem.IsRecurringChild; + public bool IsRecurringParent => CalendarItem.IsRecurringParent; + + [ObservableProperty] + private bool _isSelected; + + public ObservableCollection Attendees { get; } = new ObservableCollection(); + + public CalendarItemViewModel(CalendarItem calendarItem) { - public CalendarItem CalendarItem { get; } - - public string Title => CalendarItem.Title; - - public Guid Id => CalendarItem.Id; - - public IAccountCalendar AssignedCalendar => CalendarItem.AssignedCalendar; - - public DateTime StartDate { get => CalendarItem.StartDate; set => CalendarItem.StartDate = value; } - - public DateTime EndDate => CalendarItem.EndDate; - - public double DurationInSeconds { get => CalendarItem.DurationInSeconds; set => CalendarItem.DurationInSeconds = value; } - - public ITimePeriod Period => CalendarItem.Period; - - public bool IsAllDayEvent => CalendarItem.IsAllDayEvent; - public bool IsMultiDayEvent => CalendarItem.IsMultiDayEvent; - public bool IsRecurringEvent => CalendarItem.IsRecurringEvent; - public bool IsRecurringChild => CalendarItem.IsRecurringChild; - public bool IsRecurringParent => CalendarItem.IsRecurringParent; - - [ObservableProperty] - private bool _isSelected; - - public ObservableCollection Attendees { get; } = new ObservableCollection(); - - public CalendarItemViewModel(CalendarItem calendarItem) - { - CalendarItem = calendarItem; - } - - public override string ToString() => CalendarItem.Title; + CalendarItem = calendarItem; } + + public override string ToString() => CalendarItem.Title; } diff --git a/Wino.Calendar.ViewModels/Data/GroupedAccountCalendarViewModel.cs b/Wino.Calendar.ViewModels/Data/GroupedAccountCalendarViewModel.cs index b789bdab..92b388ba 100644 --- a/Wino.Calendar.ViewModels/Data/GroupedAccountCalendarViewModel.cs +++ b/Wino.Calendar.ViewModels/Data/GroupedAccountCalendarViewModel.cs @@ -6,141 +6,140 @@ using System.Linq; using CommunityToolkit.Mvvm.ComponentModel; using Wino.Core.Domain.Entities.Shared; -namespace Wino.Calendar.ViewModels.Data +namespace Wino.Calendar.ViewModels.Data; + +public partial class GroupedAccountCalendarViewModel : ObservableObject { - public partial class GroupedAccountCalendarViewModel : ObservableObject + public event EventHandler CollectiveSelectionStateChanged; + public event EventHandler CalendarSelectionStateChanged; + + public MailAccount Account { get; } + public ObservableCollection AccountCalendars { get; } + + public GroupedAccountCalendarViewModel(MailAccount account, IEnumerable calendarViewModels) { - public event EventHandler CollectiveSelectionStateChanged; - public event EventHandler CalendarSelectionStateChanged; + Account = account; + AccountCalendars = new ObservableCollection(calendarViewModels); - public MailAccount Account { get; } - public ObservableCollection AccountCalendars { get; } + ManageIsCheckedState(); - public GroupedAccountCalendarViewModel(MailAccount account, IEnumerable calendarViewModels) + foreach (var calendarViewModel in calendarViewModels) { - Account = account; - AccountCalendars = new ObservableCollection(calendarViewModels); - - ManageIsCheckedState(); - - foreach (var calendarViewModel in calendarViewModels) - { - calendarViewModel.PropertyChanged += CalendarPropertyChanged; - } - - AccountCalendars.CollectionChanged += CalendarListUpdated; + calendarViewModel.PropertyChanged += CalendarPropertyChanged; } - private void CalendarListUpdated(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + AccountCalendars.CollectionChanged += CalendarListUpdated; + } + + private void CalendarListUpdated(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + if (e.Action == NotifyCollectionChangedAction.Add) { - if (e.Action == NotifyCollectionChangedAction.Add) + foreach (AccountCalendarViewModel calendar in e.NewItems) { - foreach (AccountCalendarViewModel calendar in e.NewItems) - { - calendar.PropertyChanged += CalendarPropertyChanged; - } - } - else if (e.Action == NotifyCollectionChangedAction.Remove) - { - foreach (AccountCalendarViewModel calendar in e.OldItems) - { - calendar.PropertyChanged -= CalendarPropertyChanged; - } - } - else if (e.Action == NotifyCollectionChangedAction.Reset) - { - foreach (AccountCalendarViewModel calendar in e.OldItems) - { - calendar.PropertyChanged -= CalendarPropertyChanged; - } + calendar.PropertyChanged += CalendarPropertyChanged; } } - - private void CalendarPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + else if (e.Action == NotifyCollectionChangedAction.Remove) { - if (sender is AccountCalendarViewModel viewModel) + foreach (AccountCalendarViewModel calendar in e.OldItems) { - if (e.PropertyName == nameof(AccountCalendarViewModel.IsChecked)) - { - ManageIsCheckedState(); - UpdateCalendarCheckedState(viewModel, viewModel.IsChecked, true); - } + calendar.PropertyChanged -= CalendarPropertyChanged; } } - - [ObservableProperty] - private bool _isExpanded = true; - - [ObservableProperty] - private bool? isCheckedState = true; - - private bool _isExternalPropChangeBlocked = false; - - private void ManageIsCheckedState() + else if (e.Action == NotifyCollectionChangedAction.Reset) { - if (_isExternalPropChangeBlocked) return; - - _isExternalPropChangeBlocked = true; - - if (AccountCalendars.All(c => c.IsChecked)) + foreach (AccountCalendarViewModel calendar in e.OldItems) { - IsCheckedState = true; + calendar.PropertyChanged -= CalendarPropertyChanged; } - else if (AccountCalendars.All(c => !c.IsChecked)) - { - IsCheckedState = false; - } - else - { - IsCheckedState = null; - } - - _isExternalPropChangeBlocked = false; - } - - partial void OnIsCheckedStateChanged(bool? newValue) - { - 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) - { - UpdateCalendarCheckedState(calendar, calendar.IsPrimary); - } - } - else - { - foreach (var calendar in AccountCalendars) - { - 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); } } + + private void CalendarPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (sender is AccountCalendarViewModel viewModel) + { + if (e.PropertyName == nameof(AccountCalendarViewModel.IsChecked)) + { + ManageIsCheckedState(); + UpdateCalendarCheckedState(viewModel, viewModel.IsChecked, true); + } + } + } + + [ObservableProperty] + private bool _isExpanded = true; + + [ObservableProperty] + private bool? isCheckedState = true; + + private bool _isExternalPropChangeBlocked = false; + + private void ManageIsCheckedState() + { + if (_isExternalPropChangeBlocked) return; + + _isExternalPropChangeBlocked = true; + + if (AccountCalendars.All(c => c.IsChecked)) + { + IsCheckedState = true; + } + else if (AccountCalendars.All(c => !c.IsChecked)) + { + IsCheckedState = false; + } + else + { + IsCheckedState = null; + } + + _isExternalPropChangeBlocked = false; + } + + partial void OnIsCheckedStateChanged(bool? newValue) + { + 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) + { + UpdateCalendarCheckedState(calendar, calendar.IsPrimary); + } + } + else + { + foreach (var calendar in AccountCalendars) + { + 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); + } } diff --git a/Wino.Calendar.ViewModels/EventDetailsPageViewModel.cs b/Wino.Calendar.ViewModels/EventDetailsPageViewModel.cs index 852c0205..f57d797e 100644 --- a/Wino.Calendar.ViewModels/EventDetailsPageViewModel.cs +++ b/Wino.Calendar.ViewModels/EventDetailsPageViewModel.cs @@ -12,105 +12,104 @@ using Wino.Core.Domain.Models.Navigation; using Wino.Core.ViewModels; using Wino.Messaging.Client.Calendar; -namespace Wino.Calendar.ViewModels +namespace Wino.Calendar.ViewModels; + +public partial class EventDetailsPageViewModel : CalendarBaseViewModel { - public partial class EventDetailsPageViewModel : CalendarBaseViewModel + private readonly ICalendarService _calendarService; + private readonly INativeAppService _nativeAppService; + private readonly IPreferencesService _preferencesService; + + public CalendarSettings CurrentSettings { get; } + + #region Details + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(CanViewSeries))] + private CalendarItemViewModel _currentEvent; + + [ObservableProperty] + private CalendarItemViewModel _seriesParent; + + public bool CanViewSeries => CurrentEvent?.IsRecurringChild ?? false; + + #endregion + + public EventDetailsPageViewModel(ICalendarService calendarService, INativeAppService nativeAppService, IPreferencesService preferencesService) { - private readonly ICalendarService _calendarService; - private readonly INativeAppService _nativeAppService; - private readonly IPreferencesService _preferencesService; + _calendarService = calendarService; + _nativeAppService = nativeAppService; + _preferencesService = preferencesService; - public CalendarSettings CurrentSettings { get; } + CurrentSettings = _preferencesService.GetCurrentCalendarSettings(); + } - #region Details + public override async void OnNavigatedTo(NavigationMode mode, object parameters) + { + base.OnNavigatedTo(mode, parameters); - [ObservableProperty] - [NotifyPropertyChangedFor(nameof(CanViewSeries))] - private CalendarItemViewModel _currentEvent; + Messenger.Send(new DetailsPageStateChangedMessage(true)); - [ObservableProperty] - private CalendarItemViewModel _seriesParent; + if (parameters == null || parameters is not CalendarItemTarget args) + return; - public bool CanViewSeries => CurrentEvent?.IsRecurringChild ?? false; + await LoadCalendarItemTargetAsync(args); + } - #endregion - - public EventDetailsPageViewModel(ICalendarService calendarService, INativeAppService nativeAppService, IPreferencesService preferencesService) + private async Task LoadCalendarItemTargetAsync(CalendarItemTarget target) + { + try { - _calendarService = calendarService; - _nativeAppService = nativeAppService; - _preferencesService = preferencesService; + var currentEventItem = await _calendarService.GetCalendarItemTargetAsync(target); - CurrentSettings = _preferencesService.GetCurrentCalendarSettings(); - } - - public override async void OnNavigatedTo(NavigationMode mode, object parameters) - { - base.OnNavigatedTo(mode, parameters); - - Messenger.Send(new DetailsPageStateChangedMessage(true)); - - if (parameters == null || parameters is not CalendarItemTarget args) + if (currentEventItem == null) return; - await LoadCalendarItemTargetAsync(args); - } + CurrentEvent = new CalendarItemViewModel(currentEventItem); - private async Task LoadCalendarItemTargetAsync(CalendarItemTarget target) - { - try + var attendees = await _calendarService.GetAttendeesAsync(currentEventItem.EventTrackingId); + + foreach (var item in attendees) { - var currentEventItem = await _calendarService.GetCalendarItemTargetAsync(target); - - if (currentEventItem == null) - return; - - CurrentEvent = new CalendarItemViewModel(currentEventItem); - - var attendees = await _calendarService.GetAttendeesAsync(currentEventItem.EventTrackingId); - - foreach (var item in attendees) - { - CurrentEvent.Attendees.Add(item); - } - } - catch (Exception ex) - { - Debug.WriteLine(ex.Message); + CurrentEvent.Attendees.Add(item); } } - - public override void OnNavigatedFrom(NavigationMode mode, object parameters) + catch (Exception ex) { - base.OnNavigatedFrom(mode, parameters); - - Messenger.Send(new DetailsPageStateChangedMessage(false)); - } - - [RelayCommand] - private async Task SaveAsync() - { - - } - - [RelayCommand] - private async Task DeleteAsync() - { - - } - - [RelayCommand] - private Task JoinOnline() - { - if (CurrentEvent == null || string.IsNullOrEmpty(CurrentEvent.CalendarItem.HtmlLink)) return Task.CompletedTask; - - return _nativeAppService.LaunchUriAsync(new Uri(CurrentEvent.CalendarItem.HtmlLink)); - } - - [RelayCommand] - private async Task Respond(CalendarItemStatus status) - { - if (CurrentEvent == null) return; + Debug.WriteLine(ex.Message); } } + + public override void OnNavigatedFrom(NavigationMode mode, object parameters) + { + base.OnNavigatedFrom(mode, parameters); + + Messenger.Send(new DetailsPageStateChangedMessage(false)); + } + + [RelayCommand] + private async Task SaveAsync() + { + + } + + [RelayCommand] + private async Task DeleteAsync() + { + + } + + [RelayCommand] + private Task JoinOnline() + { + if (CurrentEvent == null || string.IsNullOrEmpty(CurrentEvent.CalendarItem.HtmlLink)) return Task.CompletedTask; + + return _nativeAppService.LaunchUriAsync(new Uri(CurrentEvent.CalendarItem.HtmlLink)); + } + + [RelayCommand] + private async Task Respond(CalendarItemStatus status) + { + if (CurrentEvent == null) return; + } } diff --git a/Wino.Calendar.ViewModels/Interfaces/IAccountCalendarStateService.cs b/Wino.Calendar.ViewModels/Interfaces/IAccountCalendarStateService.cs index 1a11a327..9aaf17a4 100644 --- a/Wino.Calendar.ViewModels/Interfaces/IAccountCalendarStateService.cs +++ b/Wino.Calendar.ViewModels/Interfaces/IAccountCalendarStateService.cs @@ -6,26 +6,25 @@ using System.Linq; using Wino.Calendar.ViewModels.Data; using Wino.Core.Domain.Entities.Shared; -namespace Wino.Calendar.ViewModels.Interfaces +namespace Wino.Calendar.ViewModels.Interfaces; + +public interface IAccountCalendarStateService : INotifyPropertyChanged { - public interface IAccountCalendarStateService : INotifyPropertyChanged - { - ReadOnlyObservableCollection GroupedAccountCalendars { get; } + ReadOnlyObservableCollection GroupedAccountCalendars { get; } - event EventHandler CollectiveAccountGroupSelectionStateChanged; - event EventHandler AccountCalendarSelectionStateChanged; + event EventHandler CollectiveAccountGroupSelectionStateChanged; + event EventHandler AccountCalendarSelectionStateChanged; - public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar); - public void RemoveGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar); - public void ClearGroupedAccountCalendar(); + public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar); + public void RemoveGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar); + public void ClearGroupedAccountCalendar(); - public void AddAccountCalendar(AccountCalendarViewModel accountCalendar); - public void RemoveAccountCalendar(AccountCalendarViewModel accountCalendar); + public void AddAccountCalendar(AccountCalendarViewModel accountCalendar); + public void RemoveAccountCalendar(AccountCalendarViewModel accountCalendar); - /// - /// Enumeration of currently selected calendars. - /// - IEnumerable ActiveCalendars { get; } - IEnumerable> GroupedAccountCalendarsEnumerable { get; } - } + /// + /// Enumeration of currently selected calendars. + /// + IEnumerable ActiveCalendars { get; } + IEnumerable> GroupedAccountCalendarsEnumerable { get; } } diff --git a/Wino.Calendar.ViewModels/Messages/CalendarItemDoubleTappedMessage.cs b/Wino.Calendar.ViewModels/Messages/CalendarItemDoubleTappedMessage.cs index 7898adb9..a9b08764 100644 --- a/Wino.Calendar.ViewModels/Messages/CalendarItemDoubleTappedMessage.cs +++ b/Wino.Calendar.ViewModels/Messages/CalendarItemDoubleTappedMessage.cs @@ -1,14 +1,13 @@ using Wino.Calendar.ViewModels.Data; -namespace Wino.Calendar.ViewModels.Messages -{ - public class CalendarItemDoubleTappedMessage - { - public CalendarItemDoubleTappedMessage(CalendarItemViewModel calendarItemViewModel) - { - CalendarItemViewModel = calendarItemViewModel; - } +namespace Wino.Calendar.ViewModels.Messages; - public CalendarItemViewModel CalendarItemViewModel { get; } +public class CalendarItemDoubleTappedMessage +{ + public CalendarItemDoubleTappedMessage(CalendarItemViewModel calendarItemViewModel) + { + CalendarItemViewModel = calendarItemViewModel; } + + public CalendarItemViewModel CalendarItemViewModel { get; } } diff --git a/Wino.Calendar.ViewModels/Messages/CalendarItemRightTappedMessage.cs b/Wino.Calendar.ViewModels/Messages/CalendarItemRightTappedMessage.cs index 9d4fe8e5..ae1f4a74 100644 --- a/Wino.Calendar.ViewModels/Messages/CalendarItemRightTappedMessage.cs +++ b/Wino.Calendar.ViewModels/Messages/CalendarItemRightTappedMessage.cs @@ -1,14 +1,13 @@ using Wino.Calendar.ViewModels.Data; -namespace Wino.Calendar.ViewModels.Messages -{ - public class CalendarItemRightTappedMessage - { - public CalendarItemRightTappedMessage(CalendarItemViewModel calendarItemViewModel) - { - CalendarItemViewModel = calendarItemViewModel; - } +namespace Wino.Calendar.ViewModels.Messages; - public CalendarItemViewModel CalendarItemViewModel { get; } +public class CalendarItemRightTappedMessage +{ + public CalendarItemRightTappedMessage(CalendarItemViewModel calendarItemViewModel) + { + CalendarItemViewModel = calendarItemViewModel; } + + public CalendarItemViewModel CalendarItemViewModel { get; } } diff --git a/Wino.Calendar.ViewModels/Messages/CalendarItemTappedMessage.cs b/Wino.Calendar.ViewModels/Messages/CalendarItemTappedMessage.cs index cd79dbe0..05f2d99c 100644 --- a/Wino.Calendar.ViewModels/Messages/CalendarItemTappedMessage.cs +++ b/Wino.Calendar.ViewModels/Messages/CalendarItemTappedMessage.cs @@ -1,17 +1,16 @@ using Wino.Calendar.ViewModels.Data; using Wino.Core.Domain.Models.Calendar; -namespace Wino.Calendar.ViewModels.Messages -{ - public class CalendarItemTappedMessage - { - public CalendarItemTappedMessage(CalendarItemViewModel calendarItemViewModel, CalendarDayModel clickedPeriod) - { - CalendarItemViewModel = calendarItemViewModel; - ClickedPeriod = clickedPeriod; - } +namespace Wino.Calendar.ViewModels.Messages; - public CalendarItemViewModel CalendarItemViewModel { get; } - public CalendarDayModel ClickedPeriod { get; } +public class CalendarItemTappedMessage +{ + public CalendarItemTappedMessage(CalendarItemViewModel calendarItemViewModel, CalendarDayModel clickedPeriod) + { + CalendarItemViewModel = calendarItemViewModel; + ClickedPeriod = clickedPeriod; } + + public CalendarItemViewModel CalendarItemViewModel { get; } + public CalendarDayModel ClickedPeriod { get; } } diff --git a/Wino.Calendar/Activation/DefaultActivationHandler.cs b/Wino.Calendar/Activation/DefaultActivationHandler.cs index 52e8fcfd..e2d1b8f7 100644 --- a/Wino.Calendar/Activation/DefaultActivationHandler.cs +++ b/Wino.Calendar/Activation/DefaultActivationHandler.cs @@ -6,19 +6,18 @@ using Windows.UI.Xaml.Media.Animation; using Wino.Activation; using Wino.Calendar.Views; -namespace Wino.Calendar.Activation +namespace Wino.Calendar.Activation; + +public class DefaultActivationHandler : ActivationHandler { - public class DefaultActivationHandler : ActivationHandler + protected override Task HandleInternalAsync(IActivatedEventArgs args) { - protected override Task HandleInternalAsync(IActivatedEventArgs args) - { - (Window.Current.Content as Frame).Navigate(typeof(AppShell), null, new DrillInNavigationTransitionInfo()); + (Window.Current.Content as Frame).Navigate(typeof(AppShell), null, new DrillInNavigationTransitionInfo()); - return Task.CompletedTask; - } - - // Only navigate if Frame content doesn't exist. - protected override bool CanHandleInternal(IActivatedEventArgs args) - => (Window.Current?.Content as Frame)?.Content == null; + return Task.CompletedTask; } + + // Only navigate if Frame content doesn't exist. + protected override bool CanHandleInternal(IActivatedEventArgs args) + => (Window.Current?.Content as Frame)?.Content == null; } diff --git a/Wino.Calendar/Args/TimelineCellSelectedArgs.cs b/Wino.Calendar/Args/TimelineCellSelectedArgs.cs index 31f9e270..481e0182 100644 --- a/Wino.Calendar/Args/TimelineCellSelectedArgs.cs +++ b/Wino.Calendar/Args/TimelineCellSelectedArgs.cs @@ -1,41 +1,40 @@ using System; using Windows.Foundation; -namespace Wino.Calendar.Args +namespace Wino.Calendar.Args; + +/// +/// When a new timeline cell is selected. +/// +public class TimelineCellSelectedArgs : EventArgs { - /// - /// When a new timeline cell is selected. - /// - public class TimelineCellSelectedArgs : EventArgs + public TimelineCellSelectedArgs(DateTime clickedDate, Point canvasPoint, Point positionerPoint, Size cellSize) { - public TimelineCellSelectedArgs(DateTime clickedDate, Point canvasPoint, Point positionerPoint, Size cellSize) - { - ClickedDate = clickedDate; - CanvasPoint = canvasPoint; - PositionerPoint = positionerPoint; - CellSize = cellSize; - } - - /// - /// Clicked date and time information for the cell. - /// - public DateTime ClickedDate { get; set; } - - /// - /// Position relative to the cell drawing part of the canvas. - /// Used to detect clicked cell from the position. - /// - public Point CanvasPoint { get; } - - /// - /// Position relative to the main root positioner element of the drawing canvas. - /// Used to show the create event dialog teaching tip in correct position. - /// - public Point PositionerPoint { get; } - - /// - /// Size of the cell. - /// - public Size CellSize { get; } + ClickedDate = clickedDate; + CanvasPoint = canvasPoint; + PositionerPoint = positionerPoint; + CellSize = cellSize; } + + /// + /// Clicked date and time information for the cell. + /// + public DateTime ClickedDate { get; set; } + + /// + /// Position relative to the cell drawing part of the canvas. + /// Used to detect clicked cell from the position. + /// + public Point CanvasPoint { get; } + + /// + /// Position relative to the main root positioner element of the drawing canvas. + /// Used to show the create event dialog teaching tip in correct position. + /// + public Point PositionerPoint { get; } + + /// + /// Size of the cell. + /// + public Size CellSize { get; } } diff --git a/Wino.Calendar/Args/TimelineCellUnselectedArgs.cs b/Wino.Calendar/Args/TimelineCellUnselectedArgs.cs index 7541a831..7cbeb841 100644 --- a/Wino.Calendar/Args/TimelineCellUnselectedArgs.cs +++ b/Wino.Calendar/Args/TimelineCellUnselectedArgs.cs @@ -1,9 +1,8 @@ using System; -namespace Wino.Calendar.Args -{ - /// - /// When selected timeline cell is unselected. - /// - public class TimelineCellUnselectedArgs : EventArgs { } -} +namespace Wino.Calendar.Args; + +/// +/// When selected timeline cell is unselected. +/// +public class TimelineCellUnselectedArgs : EventArgs { } diff --git a/Wino.Calendar/Controls/CalendarItemCommandBarFlyout.cs b/Wino.Calendar/Controls/CalendarItemCommandBarFlyout.cs index 52cc9c9d..407a9ed5 100644 --- a/Wino.Calendar/Controls/CalendarItemCommandBarFlyout.cs +++ b/Wino.Calendar/Controls/CalendarItemCommandBarFlyout.cs @@ -2,30 +2,29 @@ using Windows.UI.Xaml; using Wino.Calendar.ViewModels.Data; -namespace Wino.Calendar.Controls +namespace Wino.Calendar.Controls; + +public partial class CalendarItemCommandBarFlyout : CommandBarFlyout { - public class CalendarItemCommandBarFlyout : CommandBarFlyout + public static readonly DependencyProperty ItemProperty = DependencyProperty.Register(nameof(Item), typeof(CalendarItemViewModel), typeof(CalendarItemCommandBarFlyout), new PropertyMetadata(null, new PropertyChangedCallback(OnItemChanged))); + + public CalendarItemViewModel Item { - public static readonly DependencyProperty ItemProperty = DependencyProperty.Register(nameof(Item), typeof(CalendarItemViewModel), typeof(CalendarItemCommandBarFlyout), new PropertyMetadata(null, new PropertyChangedCallback(OnItemChanged))); + get { return (CalendarItemViewModel)GetValue(ItemProperty); } + set { SetValue(ItemProperty, value); } + } - public CalendarItemViewModel Item + + private static void OnItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is CalendarItemCommandBarFlyout flyout) { - get { return (CalendarItemViewModel)GetValue(ItemProperty); } - set { SetValue(ItemProperty, value); } - } - - - private static void OnItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is CalendarItemCommandBarFlyout flyout) - { - flyout.UpdateMenuItems(); - } - } - - private void UpdateMenuItems() - { - + flyout.UpdateMenuItems(); } } + + private void UpdateMenuItems() + { + + } } diff --git a/Wino.Calendar/Controls/CalendarItemControl.xaml.cs b/Wino.Calendar/Controls/CalendarItemControl.xaml.cs index b8bb0559..dbd2333b 100644 --- a/Wino.Calendar/Controls/CalendarItemControl.xaml.cs +++ b/Wino.Calendar/Controls/CalendarItemControl.xaml.cs @@ -9,190 +9,189 @@ using Wino.Calendar.ViewModels.Messages; using Wino.Core.Domain; using Wino.Core.Domain.Models.Calendar; -namespace Wino.Calendar.Controls +namespace Wino.Calendar.Controls; + +public sealed partial class CalendarItemControl : UserControl { - public sealed partial class CalendarItemControl : UserControl + // Single tap has a delay to report double taps properly. + private bool isSingleTap = false; + + public static readonly DependencyProperty CalendarItemProperty = DependencyProperty.Register(nameof(CalendarItem), typeof(CalendarItemViewModel), typeof(CalendarItemControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarItemChanged))); + public static readonly DependencyProperty IsDraggingProperty = DependencyProperty.Register(nameof(IsDragging), typeof(bool), typeof(CalendarItemControl), new PropertyMetadata(false)); + public static readonly DependencyProperty IsCustomEventAreaProperty = DependencyProperty.Register(nameof(IsCustomEventArea), typeof(bool), typeof(CalendarItemControl), new PropertyMetadata(false)); + public static readonly DependencyProperty CalendarItemTitleProperty = DependencyProperty.Register(nameof(CalendarItemTitle), typeof(string), typeof(CalendarItemControl), new PropertyMetadata(string.Empty)); + public static readonly DependencyProperty DisplayingDateProperty = DependencyProperty.Register(nameof(DisplayingDate), typeof(CalendarDayModel), typeof(CalendarItemControl), new PropertyMetadata(null, new PropertyChangedCallback(OnDisplayDateChanged))); + + /// + /// Whether the control is displaying as regular event or all-multi day area in the day control. + /// + public bool IsCustomEventArea { - // Single tap has a delay to report double taps properly. - private bool isSingleTap = false; + get { return (bool)GetValue(IsCustomEventAreaProperty); } + set { SetValue(IsCustomEventAreaProperty, value); } + } - public static readonly DependencyProperty CalendarItemProperty = DependencyProperty.Register(nameof(CalendarItem), typeof(CalendarItemViewModel), typeof(CalendarItemControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarItemChanged))); - public static readonly DependencyProperty IsDraggingProperty = DependencyProperty.Register(nameof(IsDragging), typeof(bool), typeof(CalendarItemControl), new PropertyMetadata(false)); - public static readonly DependencyProperty IsCustomEventAreaProperty = DependencyProperty.Register(nameof(IsCustomEventArea), typeof(bool), typeof(CalendarItemControl), new PropertyMetadata(false)); - public static readonly DependencyProperty CalendarItemTitleProperty = DependencyProperty.Register(nameof(CalendarItemTitle), typeof(string), typeof(CalendarItemControl), new PropertyMetadata(string.Empty)); - public static readonly DependencyProperty DisplayingDateProperty = DependencyProperty.Register(nameof(DisplayingDate), typeof(CalendarDayModel), typeof(CalendarItemControl), new PropertyMetadata(null, new PropertyChangedCallback(OnDisplayDateChanged))); + /// + /// Day that the calendar item is rendered at. + /// It's needed for title manipulation and some other adjustments later on. + /// + public CalendarDayModel DisplayingDate + { + get { return (CalendarDayModel)GetValue(DisplayingDateProperty); } + set { SetValue(DisplayingDateProperty, value); } + } - /// - /// Whether the control is displaying as regular event or all-multi day area in the day control. - /// - public bool IsCustomEventArea + public string CalendarItemTitle + { + get { return (string)GetValue(CalendarItemTitleProperty); } + set { SetValue(CalendarItemTitleProperty, value); } + } + + public CalendarItemViewModel CalendarItem + { + get { return (CalendarItemViewModel)GetValue(CalendarItemProperty); } + set { SetValue(CalendarItemProperty, value); } + } + + public bool IsDragging + { + get { return (bool)GetValue(IsDraggingProperty); } + set { SetValue(IsDraggingProperty, value); } + } + + public CalendarItemControl() + { + InitializeComponent(); + } + + private static void OnDisplayDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is CalendarItemControl control) { - get { return (bool)GetValue(IsCustomEventAreaProperty); } - set { SetValue(IsCustomEventAreaProperty, value); } + control.UpdateControlVisuals(); } + } - /// - /// Day that the calendar item is rendered at. - /// It's needed for title manipulation and some other adjustments later on. - /// - public CalendarDayModel DisplayingDate + private static void OnCalendarItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is CalendarItemControl control) { - get { return (CalendarDayModel)GetValue(DisplayingDateProperty); } - set { SetValue(DisplayingDateProperty, value); } + control.UpdateControlVisuals(); } + } - public string CalendarItemTitle - { - get { return (string)GetValue(CalendarItemTitleProperty); } - set { SetValue(CalendarItemTitleProperty, value); } - } + private void UpdateControlVisuals() + { + // Depending on the calendar item's duration and attributes, we might need to change the display title. + // 1. Multi-Day events should display the start date and end date. + // 2. Multi-Day events that occupy the whole day just shows 'all day'. + // 3. Other events should display the title. - public CalendarItemViewModel CalendarItem - { - get { return (CalendarItemViewModel)GetValue(CalendarItemProperty); } - set { SetValue(CalendarItemProperty, value); } - } + if (CalendarItem == null) return; + if (DisplayingDate == null) return; - public bool IsDragging + if (CalendarItem.IsMultiDayEvent) { - get { return (bool)GetValue(IsDraggingProperty); } - set { SetValue(IsDraggingProperty, value); } - } + // Multi day events are divided into 3 categories: + // 1. All day events + // 2. Events that started after the period. + // 3. Events that started before the period and finishes within the period. - public CalendarItemControl() - { - InitializeComponent(); - } + var periodRelation = CalendarItem.Period.GetRelation(DisplayingDate.Period); - private static void OnDisplayDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is CalendarItemControl control) + if (periodRelation == Itenso.TimePeriod.PeriodRelation.StartInside || + periodRelation == PeriodRelation.EnclosingStartTouching) { - control.UpdateControlVisuals(); + // hour -> title + CalendarItemTitle = $"{DisplayingDate.CalendarRenderOptions.CalendarSettings.GetTimeString(CalendarItem.StartDate.TimeOfDay)} -> {CalendarItem.Title}"; } - } - - private static void OnCalendarItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is CalendarItemControl control) + else if ( + periodRelation == PeriodRelation.EndInside || + periodRelation == PeriodRelation.EnclosingEndTouching) { - control.UpdateControlVisuals(); + // title <- hour + CalendarItemTitle = $"{CalendarItem.Title} <- {DisplayingDate.CalendarRenderOptions.CalendarSettings.GetTimeString(CalendarItem.EndDate.TimeOfDay)}"; } - } - - private void UpdateControlVisuals() - { - // Depending on the calendar item's duration and attributes, we might need to change the display title. - // 1. Multi-Day events should display the start date and end date. - // 2. Multi-Day events that occupy the whole day just shows 'all day'. - // 3. Other events should display the title. - - if (CalendarItem == null) return; - if (DisplayingDate == null) return; - - if (CalendarItem.IsMultiDayEvent) + else if (periodRelation == PeriodRelation.Enclosing) { - // Multi day events are divided into 3 categories: - // 1. All day events - // 2. Events that started after the period. - // 3. Events that started before the period and finishes within the period. + // This event goes all day and it's multi-day. + // Item must be hidden in the calendar but displayed on the custom area at the top. - var periodRelation = CalendarItem.Period.GetRelation(DisplayingDate.Period); - - if (periodRelation == Itenso.TimePeriod.PeriodRelation.StartInside || - periodRelation == PeriodRelation.EnclosingStartTouching) - { - // hour -> title - CalendarItemTitle = $"{DisplayingDate.CalendarRenderOptions.CalendarSettings.GetTimeString(CalendarItem.StartDate.TimeOfDay)} -> {CalendarItem.Title}"; - } - else if ( - periodRelation == PeriodRelation.EndInside || - periodRelation == PeriodRelation.EnclosingEndTouching) - { - // title <- hour - CalendarItemTitle = $"{CalendarItem.Title} <- {DisplayingDate.CalendarRenderOptions.CalendarSettings.GetTimeString(CalendarItem.EndDate.TimeOfDay)}"; - } - else if (periodRelation == PeriodRelation.Enclosing) - { - // This event goes all day and it's multi-day. - // Item must be hidden in the calendar but displayed on the custom area at the top. - - CalendarItemTitle = $"{Translator.CalendarItemAllDay} {CalendarItem.Title}"; - } - else - { - // Not expected, but there it is. - CalendarItemTitle = CalendarItem.Title; - } - - // Debug.WriteLine($"{CalendarItem.Title} Period relation with {DisplayingDate.Period.ToString()}: {periodRelation}"); + CalendarItemTitle = $"{Translator.CalendarItemAllDay} {CalendarItem.Title}"; } else { + // Not expected, but there it is. CalendarItemTitle = CalendarItem.Title; } - UpdateVisualStates(); + // Debug.WriteLine($"{CalendarItem.Title} Period relation with {DisplayingDate.Period.ToString()}: {periodRelation}"); + } + else + { + CalendarItemTitle = CalendarItem.Title; } - private void UpdateVisualStates() - { - if (CalendarItem == null) return; + UpdateVisualStates(); + } - if (CalendarItem.IsAllDayEvent) + private void UpdateVisualStates() + { + if (CalendarItem == null) return; + + if (CalendarItem.IsAllDayEvent) + { + VisualStateManager.GoToState(this, "AllDayEvent", true); + } + else if (CalendarItem.IsMultiDayEvent) + { + if (IsCustomEventArea) { - VisualStateManager.GoToState(this, "AllDayEvent", true); - } - else if (CalendarItem.IsMultiDayEvent) - { - if (IsCustomEventArea) - { - VisualStateManager.GoToState(this, "CustomAreaMultiDayEvent", true); - } - else - { - // Hide it. - VisualStateManager.GoToState(this, "MultiDayEvent", true); - } + VisualStateManager.GoToState(this, "CustomAreaMultiDayEvent", true); } else { - VisualStateManager.GoToState(this, "RegularEvent", true); + // Hide it. + VisualStateManager.GoToState(this, "MultiDayEvent", true); } } - - private void ControlDragStarting(UIElement sender, DragStartingEventArgs args) => IsDragging = true; - - private void ControlDropped(UIElement sender, DropCompletedEventArgs args) => IsDragging = false; - - private async void ControlTapped(object sender, TappedRoutedEventArgs e) + else { - if (CalendarItem == null) return; - - isSingleTap = true; - - await Task.Delay(100); - - if (isSingleTap) - { - WeakReferenceMessenger.Default.Send(new CalendarItemTappedMessage(CalendarItem, DisplayingDate)); - } - } - - private void ControlDoubleTapped(object sender, DoubleTappedRoutedEventArgs e) - { - if (CalendarItem == null) return; - - isSingleTap = false; - - WeakReferenceMessenger.Default.Send(new CalendarItemDoubleTappedMessage(CalendarItem)); - } - - private void ControlRightTapped(object sender, RightTappedRoutedEventArgs e) - { - if (CalendarItem == null) return; - - WeakReferenceMessenger.Default.Send(new CalendarItemRightTappedMessage(CalendarItem)); + VisualStateManager.GoToState(this, "RegularEvent", true); } } + + private void ControlDragStarting(UIElement sender, DragStartingEventArgs args) => IsDragging = true; + + private void ControlDropped(UIElement sender, DropCompletedEventArgs args) => IsDragging = false; + + private async void ControlTapped(object sender, TappedRoutedEventArgs e) + { + if (CalendarItem == null) return; + + isSingleTap = true; + + await Task.Delay(100); + + if (isSingleTap) + { + WeakReferenceMessenger.Default.Send(new CalendarItemTappedMessage(CalendarItem, DisplayingDate)); + } + } + + private void ControlDoubleTapped(object sender, DoubleTappedRoutedEventArgs e) + { + if (CalendarItem == null) return; + + isSingleTap = false; + + WeakReferenceMessenger.Default.Send(new CalendarItemDoubleTappedMessage(CalendarItem)); + } + + private void ControlRightTapped(object sender, RightTappedRoutedEventArgs e) + { + if (CalendarItem == null) return; + + WeakReferenceMessenger.Default.Send(new CalendarItemRightTappedMessage(CalendarItem)); + } } diff --git a/Wino.Calendar/Controls/CustomCalendarFlipView.cs b/Wino.Calendar/Controls/CustomCalendarFlipView.cs index 11382393..8d73220e 100644 --- a/Wino.Calendar/Controls/CustomCalendarFlipView.cs +++ b/Wino.Calendar/Controls/CustomCalendarFlipView.cs @@ -1,43 +1,42 @@ using Windows.UI.Xaml.Automation.Peers; using Windows.UI.Xaml.Controls; -namespace Wino.Calendar.Controls +namespace Wino.Calendar.Controls; + +/// +/// FlipView that hides the navigation buttons and exposes methods to navigate to the next and previous items with animations. +/// +public partial class CustomCalendarFlipView : FlipView { - /// - /// FlipView that hides the navigation buttons and exposes methods to navigate to the next and previous items with animations. - /// - public class CustomCalendarFlipView : FlipView + private const string PART_PreviousButton = "PreviousButtonHorizontal"; + private const string PART_NextButton = "NextButtonHorizontal"; + + private Button PreviousButton; + private Button NextButton; + + protected override void OnApplyTemplate() { - private const string PART_PreviousButton = "PreviousButtonHorizontal"; - private const string PART_NextButton = "NextButtonHorizontal"; + base.OnApplyTemplate(); - private Button PreviousButton; - private Button NextButton; + PreviousButton = GetTemplateChild(PART_PreviousButton) as Button; + NextButton = GetTemplateChild(PART_NextButton) as Button; - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); + // Hide navigation buttons + PreviousButton.Opacity = NextButton.Opacity = 0; + PreviousButton.IsHitTestVisible = NextButton.IsHitTestVisible = false; - PreviousButton = GetTemplateChild(PART_PreviousButton) as Button; - NextButton = GetTemplateChild(PART_NextButton) as Button; + var t = FindName("ScrollingHost"); + } - // Hide navigation buttons - PreviousButton.Opacity = NextButton.Opacity = 0; - PreviousButton.IsHitTestVisible = NextButton.IsHitTestVisible = false; + public void GoPreviousFlip() + { + var backPeer = new ButtonAutomationPeer(PreviousButton); + backPeer.Invoke(); + } - var t = FindName("ScrollingHost"); - } - - public void GoPreviousFlip() - { - var backPeer = new ButtonAutomationPeer(PreviousButton); - backPeer.Invoke(); - } - - public void GoNextFlip() - { - var nextPeer = new ButtonAutomationPeer(NextButton); - nextPeer.Invoke(); - } + public void GoNextFlip() + { + var nextPeer = new ButtonAutomationPeer(NextButton); + nextPeer.Invoke(); } } diff --git a/Wino.Calendar/Controls/DayColumnControl.cs b/Wino.Calendar/Controls/DayColumnControl.cs index 4af07f94..a3f3cb78 100644 --- a/Wino.Calendar/Controls/DayColumnControl.cs +++ b/Wino.Calendar/Controls/DayColumnControl.cs @@ -3,76 +3,75 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Wino.Core.Domain.Models.Calendar; -namespace Wino.Calendar.Controls +namespace Wino.Calendar.Controls; + +public partial class DayColumnControl : Control { - public class DayColumnControl : Control + private const string PART_HeaderDateDayText = nameof(PART_HeaderDateDayText); + private const string PART_IsTodayBorder = nameof(PART_IsTodayBorder); + private const string PART_ColumnHeaderText = nameof(PART_ColumnHeaderText); + + private const string PART_AllDayItemsControl = nameof(PART_AllDayItemsControl); + + private const string TodayState = nameof(TodayState); + private const string NotTodayState = nameof(NotTodayState); + + private TextBlock HeaderDateDayText; + private TextBlock ColumnHeaderText; + private Border IsTodayBorder; + private ItemsControl AllDayItemsControl; + + public CalendarDayModel DayModel { - private const string PART_HeaderDateDayText = nameof(PART_HeaderDateDayText); - private const string PART_IsTodayBorder = nameof(PART_IsTodayBorder); - private const string PART_ColumnHeaderText = nameof(PART_ColumnHeaderText); + get { return (CalendarDayModel)GetValue(DayModelProperty); } + set { SetValue(DayModelProperty, value); } + } - private const string PART_AllDayItemsControl = nameof(PART_AllDayItemsControl); + public static readonly DependencyProperty DayModelProperty = DependencyProperty.Register(nameof(DayModel), typeof(CalendarDayModel), typeof(DayColumnControl), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged))); - private const string TodayState = nameof(TodayState); - private const string NotTodayState = nameof(NotTodayState); + public DayColumnControl() + { + DefaultStyleKey = typeof(DayColumnControl); + } - private TextBlock HeaderDateDayText; - private TextBlock ColumnHeaderText; - private Border IsTodayBorder; - private ItemsControl AllDayItemsControl; + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); - public CalendarDayModel DayModel + HeaderDateDayText = GetTemplateChild(PART_HeaderDateDayText) as TextBlock; + ColumnHeaderText = GetTemplateChild(PART_ColumnHeaderText) as TextBlock; + IsTodayBorder = GetTemplateChild(PART_IsTodayBorder) as Border; + AllDayItemsControl = GetTemplateChild(PART_AllDayItemsControl) as ItemsControl; + + UpdateValues(); + } + + private static void OnRenderingPropertiesChanged(DependencyObject control, DependencyPropertyChangedEventArgs e) + { + if (control is DayColumnControl columnControl) { - get { return (CalendarDayModel)GetValue(DayModelProperty); } - set { SetValue(DayModelProperty, value); } - } - - public static readonly DependencyProperty DayModelProperty = DependencyProperty.Register(nameof(DayModel), typeof(CalendarDayModel), typeof(DayColumnControl), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged))); - - public DayColumnControl() - { - DefaultStyleKey = typeof(DayColumnControl); - } - - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - HeaderDateDayText = GetTemplateChild(PART_HeaderDateDayText) as TextBlock; - ColumnHeaderText = GetTemplateChild(PART_ColumnHeaderText) as TextBlock; - IsTodayBorder = GetTemplateChild(PART_IsTodayBorder) as Border; - AllDayItemsControl = GetTemplateChild(PART_AllDayItemsControl) as ItemsControl; - - UpdateValues(); - } - - private static void OnRenderingPropertiesChanged(DependencyObject control, DependencyPropertyChangedEventArgs e) - { - if (control is DayColumnControl columnControl) - { - columnControl.UpdateValues(); - } - } - - private void UpdateValues() - { - if (HeaderDateDayText == null || IsTodayBorder == null || DayModel == null) return; - - HeaderDateDayText.Text = DayModel.RepresentingDate.Day.ToString(); - - // Monthly template does not use it. - if (ColumnHeaderText != null) - { - ColumnHeaderText.Text = DayModel.RepresentingDate.ToString("dddd", DayModel.CalendarRenderOptions.CalendarSettings.CultureInfo); - } - - AllDayItemsControl.ItemsSource = DayModel.EventsCollection.AllDayEvents; - - bool isToday = DayModel.RepresentingDate.Date == DateTime.Now.Date; - - VisualStateManager.GoToState(this, isToday ? TodayState : NotTodayState, false); - - UpdateLayout(); + columnControl.UpdateValues(); } } + + private void UpdateValues() + { + if (HeaderDateDayText == null || IsTodayBorder == null || DayModel == null) return; + + HeaderDateDayText.Text = DayModel.RepresentingDate.Day.ToString(); + + // Monthly template does not use it. + if (ColumnHeaderText != null) + { + ColumnHeaderText.Text = DayModel.RepresentingDate.ToString("dddd", DayModel.CalendarRenderOptions.CalendarSettings.CultureInfo); + } + + AllDayItemsControl.ItemsSource = DayModel.EventsCollection.AllDayEvents; + + bool isToday = DayModel.RepresentingDate.Date == DateTime.Now.Date; + + VisualStateManager.GoToState(this, isToday ? TodayState : NotTodayState, false); + + UpdateLayout(); + } } diff --git a/Wino.Calendar/Controls/DayHeaderControl.cs b/Wino.Calendar/Controls/DayHeaderControl.cs index 1a5d09f8..567cdaef 100644 --- a/Wino.Calendar/Controls/DayHeaderControl.cs +++ b/Wino.Calendar/Controls/DayHeaderControl.cs @@ -3,55 +3,54 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Wino.Core.Domain.Enums; -namespace Wino.Calendar.Controls +namespace Wino.Calendar.Controls; + +public partial class DayHeaderControl : Control { - public class DayHeaderControl : Control + private const string PART_DayHeaderTextBlock = nameof(PART_DayHeaderTextBlock); + private TextBlock HeaderTextblock; + + public DayHeaderDisplayType DisplayType { - private const string PART_DayHeaderTextBlock = nameof(PART_DayHeaderTextBlock); - private TextBlock HeaderTextblock; + get { return (DayHeaderDisplayType)GetValue(DisplayTypeProperty); } + set { SetValue(DisplayTypeProperty, value); } + } - public DayHeaderDisplayType DisplayType + public DateTime Date + { + get { return (DateTime)GetValue(DateProperty); } + set { SetValue(DateProperty, value); } + } + + public static readonly DependencyProperty DateProperty = DependencyProperty.Register(nameof(Date), typeof(DateTime), typeof(DayHeaderControl), new PropertyMetadata(default(DateTime), new PropertyChangedCallback(OnHeaderPropertyChanged))); + public static readonly DependencyProperty DisplayTypeProperty = DependencyProperty.Register(nameof(DisplayType), typeof(DayHeaderDisplayType), typeof(DayHeaderControl), new PropertyMetadata(DayHeaderDisplayType.TwentyFourHour, new PropertyChangedCallback(OnHeaderPropertyChanged))); + + public DayHeaderControl() + { + DefaultStyleKey = typeof(DayHeaderControl); + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + HeaderTextblock = GetTemplateChild(PART_DayHeaderTextBlock) as TextBlock; + UpdateHeaderText(); + } + + private static void OnHeaderPropertyChanged(DependencyObject control, DependencyPropertyChangedEventArgs e) + { + if (control is DayHeaderControl headerControl) { - get { return (DayHeaderDisplayType)GetValue(DisplayTypeProperty); } - set { SetValue(DisplayTypeProperty, value); } + headerControl.UpdateHeaderText(); } + } - public DateTime Date + private void UpdateHeaderText() + { + if (HeaderTextblock != null) { - get { return (DateTime)GetValue(DateProperty); } - set { SetValue(DateProperty, value); } - } - - public static readonly DependencyProperty DateProperty = DependencyProperty.Register(nameof(Date), typeof(DateTime), typeof(DayHeaderControl), new PropertyMetadata(default(DateTime), new PropertyChangedCallback(OnHeaderPropertyChanged))); - public static readonly DependencyProperty DisplayTypeProperty = DependencyProperty.Register(nameof(DisplayType), typeof(DayHeaderDisplayType), typeof(DayHeaderControl), new PropertyMetadata(DayHeaderDisplayType.TwentyFourHour, new PropertyChangedCallback(OnHeaderPropertyChanged))); - - public DayHeaderControl() - { - DefaultStyleKey = typeof(DayHeaderControl); - } - - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - HeaderTextblock = GetTemplateChild(PART_DayHeaderTextBlock) as TextBlock; - UpdateHeaderText(); - } - - private static void OnHeaderPropertyChanged(DependencyObject control, DependencyPropertyChangedEventArgs e) - { - if (control is DayHeaderControl headerControl) - { - headerControl.UpdateHeaderText(); - } - } - - private void UpdateHeaderText() - { - if (HeaderTextblock != null) - { - HeaderTextblock.Text = DisplayType == DayHeaderDisplayType.TwelveHour ? Date.ToString("h tt") : Date.ToString("HH:mm"); - } + HeaderTextblock.Text = DisplayType == DayHeaderDisplayType.TwelveHour ? Date.ToString("h tt") : Date.ToString("HH:mm"); } } } diff --git a/Wino.Calendar/Controls/WinoCalendarControl.cs b/Wino.Calendar/Controls/WinoCalendarControl.cs index 1e0cb1b7..e2930e6c 100644 --- a/Wino.Calendar/Controls/WinoCalendarControl.cs +++ b/Wino.Calendar/Controls/WinoCalendarControl.cs @@ -10,291 +10,290 @@ using Wino.Core.Domain.Enums; using Wino.Core.Domain.Models.Calendar; using Wino.Helpers; -namespace Wino.Calendar.Controls +namespace Wino.Calendar.Controls; + +public partial class WinoCalendarControl : Control { - public class WinoCalendarControl : Control + private const string PART_WinoFlipView = nameof(PART_WinoFlipView); + private const string PART_IdleGrid = nameof(PART_IdleGrid); + + public event EventHandler TimelineCellSelected; + public event EventHandler TimelineCellUnselected; + + public event EventHandler ScrollPositionChanging; + + #region Dependency Properties + + public static readonly DependencyProperty DayRangesProperty = DependencyProperty.Register(nameof(DayRanges), typeof(ObservableCollection), typeof(WinoCalendarControl), new PropertyMetadata(null)); + public static readonly DependencyProperty SelectedFlipViewIndexProperty = DependencyProperty.Register(nameof(SelectedFlipViewIndex), typeof(int), typeof(WinoCalendarControl), new PropertyMetadata(-1)); + public static readonly DependencyProperty SelectedFlipViewDayRangeProperty = DependencyProperty.Register(nameof(SelectedFlipViewDayRange), typeof(DayRangeRenderModel), typeof(WinoCalendarControl), new PropertyMetadata(null)); + public static readonly DependencyProperty ActiveCanvasProperty = DependencyProperty.Register(nameof(ActiveCanvas), typeof(WinoDayTimelineCanvas), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnActiveCanvasChanged))); + public static readonly DependencyProperty IsFlipIdleProperty = DependencyProperty.Register(nameof(IsFlipIdle), typeof(bool), typeof(WinoCalendarControl), new PropertyMetadata(true, new PropertyChangedCallback(OnIdleStateChanged))); + public static readonly DependencyProperty ActiveScrollViewerProperty = DependencyProperty.Register(nameof(ActiveScrollViewer), typeof(ScrollViewer), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnActiveVerticalScrollViewerChanged))); + + public static readonly DependencyProperty VerticalItemsPanelTemplateProperty = DependencyProperty.Register(nameof(VerticalItemsPanelTemplate), typeof(ItemsPanelTemplate), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated))); + public static readonly DependencyProperty HorizontalItemsPanelTemplateProperty = DependencyProperty.Register(nameof(HorizontalItemsPanelTemplate), typeof(ItemsPanelTemplate), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated))); + public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(nameof(Orientation), typeof(CalendarOrientation), typeof(WinoCalendarControl), new PropertyMetadata(CalendarOrientation.Horizontal, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated))); + public static readonly DependencyProperty DisplayTypeProperty = DependencyProperty.Register(nameof(DisplayType), typeof(CalendarDisplayType), typeof(WinoCalendarControl), new PropertyMetadata(CalendarDisplayType.Day)); + + /// + /// Gets or sets the day-week-month-year display type. + /// Orientation is not determined by this property, but Orientation property. + /// This property is used to determine the template to use for the calendar. + /// + public CalendarDisplayType DisplayType { - private const string PART_WinoFlipView = nameof(PART_WinoFlipView); - private const string PART_IdleGrid = nameof(PART_IdleGrid); + get { return (CalendarDisplayType)GetValue(DisplayTypeProperty); } + set { SetValue(DisplayTypeProperty, value); } + } - public event EventHandler TimelineCellSelected; - public event EventHandler TimelineCellUnselected; + public CalendarOrientation Orientation + { + get { return (CalendarOrientation)GetValue(OrientationProperty); } + set { SetValue(OrientationProperty, value); } + } - public event EventHandler ScrollPositionChanging; + public ItemsPanelTemplate VerticalItemsPanelTemplate + { + get { return (ItemsPanelTemplate)GetValue(VerticalItemsPanelTemplateProperty); } + set { SetValue(VerticalItemsPanelTemplateProperty, value); } + } - #region Dependency Properties + public ItemsPanelTemplate HorizontalItemsPanelTemplate + { + get { return (ItemsPanelTemplate)GetValue(HorizontalItemsPanelTemplateProperty); } + set { SetValue(HorizontalItemsPanelTemplateProperty, value); } + } - public static readonly DependencyProperty DayRangesProperty = DependencyProperty.Register(nameof(DayRanges), typeof(ObservableCollection), typeof(WinoCalendarControl), new PropertyMetadata(null)); - public static readonly DependencyProperty SelectedFlipViewIndexProperty = DependencyProperty.Register(nameof(SelectedFlipViewIndex), typeof(int), typeof(WinoCalendarControl), new PropertyMetadata(-1)); - public static readonly DependencyProperty SelectedFlipViewDayRangeProperty = DependencyProperty.Register(nameof(SelectedFlipViewDayRange), typeof(DayRangeRenderModel), typeof(WinoCalendarControl), new PropertyMetadata(null)); - public static readonly DependencyProperty ActiveCanvasProperty = DependencyProperty.Register(nameof(ActiveCanvas), typeof(WinoDayTimelineCanvas), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnActiveCanvasChanged))); - public static readonly DependencyProperty IsFlipIdleProperty = DependencyProperty.Register(nameof(IsFlipIdle), typeof(bool), typeof(WinoCalendarControl), new PropertyMetadata(true, new PropertyChangedCallback(OnIdleStateChanged))); - public static readonly DependencyProperty ActiveScrollViewerProperty = DependencyProperty.Register(nameof(ActiveScrollViewer), typeof(ScrollViewer), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnActiveVerticalScrollViewerChanged))); + public DayRangeRenderModel SelectedFlipViewDayRange + { + get { return (DayRangeRenderModel)GetValue(SelectedFlipViewDayRangeProperty); } + set { SetValue(SelectedFlipViewDayRangeProperty, value); } + } - public static readonly DependencyProperty VerticalItemsPanelTemplateProperty = DependencyProperty.Register(nameof(VerticalItemsPanelTemplate), typeof(ItemsPanelTemplate), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated))); - public static readonly DependencyProperty HorizontalItemsPanelTemplateProperty = DependencyProperty.Register(nameof(HorizontalItemsPanelTemplate), typeof(ItemsPanelTemplate), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated))); - public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(nameof(Orientation), typeof(CalendarOrientation), typeof(WinoCalendarControl), new PropertyMetadata(CalendarOrientation.Horizontal, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated))); - public static readonly DependencyProperty DisplayTypeProperty = DependencyProperty.Register(nameof(DisplayType), typeof(CalendarDisplayType), typeof(WinoCalendarControl), new PropertyMetadata(CalendarDisplayType.Day)); + public ScrollViewer ActiveScrollViewer + { + get { return (ScrollViewer)GetValue(ActiveScrollViewerProperty); } + set { SetValue(ActiveScrollViewerProperty, value); } + } - /// - /// Gets or sets the day-week-month-year display type. - /// Orientation is not determined by this property, but Orientation property. - /// This property is used to determine the template to use for the calendar. - /// - public CalendarDisplayType DisplayType + public WinoDayTimelineCanvas ActiveCanvas + { + get { return (WinoDayTimelineCanvas)GetValue(ActiveCanvasProperty); } + set { SetValue(ActiveCanvasProperty, value); } + } + + public bool IsFlipIdle + { + get { return (bool)GetValue(IsFlipIdleProperty); } + set { SetValue(IsFlipIdleProperty, value); } + } + + /// + /// Gets or sets the collection of day ranges to render. + /// Each day range usually represents a week, but it may support other ranges. + /// + public ObservableCollection DayRanges + { + get { return (ObservableCollection)GetValue(DayRangesProperty); } + set { SetValue(DayRangesProperty, value); } + } + + public int SelectedFlipViewIndex + { + get { return (int)GetValue(SelectedFlipViewIndexProperty); } + set { SetValue(SelectedFlipViewIndexProperty, value); } + } + + #endregion + + private WinoCalendarFlipView InternalFlipView; + private Grid IdleGrid; + + public WinoCalendarControl() + { + DefaultStyleKey = typeof(WinoCalendarControl); + SizeChanged += CalendarSizeChanged; + } + + private static void OnCalendarOrientationPropertiesUpdated(DependencyObject calendar, DependencyPropertyChangedEventArgs e) + { + if (calendar is WinoCalendarControl control) { - get { return (CalendarDisplayType)GetValue(DisplayTypeProperty); } - set { SetValue(DisplayTypeProperty, value); } - } - - public CalendarOrientation Orientation - { - get { return (CalendarOrientation)GetValue(OrientationProperty); } - set { SetValue(OrientationProperty, value); } - } - - public ItemsPanelTemplate VerticalItemsPanelTemplate - { - get { return (ItemsPanelTemplate)GetValue(VerticalItemsPanelTemplateProperty); } - set { SetValue(VerticalItemsPanelTemplateProperty, value); } - } - - public ItemsPanelTemplate HorizontalItemsPanelTemplate - { - get { return (ItemsPanelTemplate)GetValue(HorizontalItemsPanelTemplateProperty); } - set { SetValue(HorizontalItemsPanelTemplateProperty, value); } - } - - public DayRangeRenderModel SelectedFlipViewDayRange - { - get { return (DayRangeRenderModel)GetValue(SelectedFlipViewDayRangeProperty); } - set { SetValue(SelectedFlipViewDayRangeProperty, value); } - } - - public ScrollViewer ActiveScrollViewer - { - get { return (ScrollViewer)GetValue(ActiveScrollViewerProperty); } - set { SetValue(ActiveScrollViewerProperty, value); } - } - - public WinoDayTimelineCanvas ActiveCanvas - { - get { return (WinoDayTimelineCanvas)GetValue(ActiveCanvasProperty); } - set { SetValue(ActiveCanvasProperty, value); } - } - - public bool IsFlipIdle - { - get { return (bool)GetValue(IsFlipIdleProperty); } - set { SetValue(IsFlipIdleProperty, value); } - } - - /// - /// Gets or sets the collection of day ranges to render. - /// Each day range usually represents a week, but it may support other ranges. - /// - public ObservableCollection DayRanges - { - get { return (ObservableCollection)GetValue(DayRangesProperty); } - set { SetValue(DayRangesProperty, value); } - } - - public int SelectedFlipViewIndex - { - get { return (int)GetValue(SelectedFlipViewIndexProperty); } - set { SetValue(SelectedFlipViewIndexProperty, value); } - } - - #endregion - - private WinoCalendarFlipView InternalFlipView; - private Grid IdleGrid; - - public WinoCalendarControl() - { - DefaultStyleKey = typeof(WinoCalendarControl); - SizeChanged += CalendarSizeChanged; - } - - private static void OnCalendarOrientationPropertiesUpdated(DependencyObject calendar, DependencyPropertyChangedEventArgs e) - { - if (calendar is WinoCalendarControl control) - { - control.ManageCalendarOrientation(); - } - } - - private static void OnIdleStateChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e) - { - if (calendar is WinoCalendarControl calendarControl) - { - calendarControl.UpdateIdleState(); - } - } - - - private static void OnActiveVerticalScrollViewerChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e) - { - if (calendar is WinoCalendarControl calendarControl) - { - if (e.OldValue is ScrollViewer oldScrollViewer) - { - calendarControl.DeregisterScrollChanges(oldScrollViewer); - } - - if (e.NewValue is ScrollViewer newScrollViewer) - { - calendarControl.RegisterScrollChanges(newScrollViewer); - } - - calendarControl.ManageHighlightedDateRange(); - } - } - - - private static void OnActiveCanvasChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e) - { - if (calendar is WinoCalendarControl calendarControl) - { - if (e.OldValue is WinoDayTimelineCanvas oldCanvas) - { - // Dismiss any selection on the old canvas. - calendarControl.DeregisterCanvas(oldCanvas); - } - - if (e.NewValue is WinoDayTimelineCanvas newCanvas) - { - calendarControl.RegisterCanvas(newCanvas); - } - - calendarControl.ManageHighlightedDateRange(); - } - } - - private void ManageCalendarOrientation() - { - if (InternalFlipView == null || HorizontalItemsPanelTemplate == null || VerticalItemsPanelTemplate == null) return; - - InternalFlipView.ItemsPanel = Orientation == CalendarOrientation.Horizontal ? HorizontalItemsPanelTemplate : VerticalItemsPanelTemplate; - } - - private void ManageHighlightedDateRange() - => SelectedFlipViewDayRange = InternalFlipView.SelectedItem as DayRangeRenderModel; - - private void DeregisterCanvas(WinoDayTimelineCanvas canvas) - { - if (canvas == null) return; - - canvas.SelectedDateTime = null; - canvas.TimelineCellSelected -= ActiveTimelineCellSelected; - canvas.TimelineCellUnselected -= ActiveTimelineCellUnselected; - } - - private void RegisterCanvas(WinoDayTimelineCanvas canvas) - { - if (canvas == null) return; - - canvas.SelectedDateTime = null; - canvas.TimelineCellSelected += ActiveTimelineCellSelected; - canvas.TimelineCellUnselected += ActiveTimelineCellUnselected; - } - - private void RegisterScrollChanges(ScrollViewer scrollViewer) - { - if (scrollViewer == null) return; - - scrollViewer.ViewChanging += ScrollViewChanging; - } - - private void DeregisterScrollChanges(ScrollViewer scrollViewer) - { - if (scrollViewer == null) return; - - scrollViewer.ViewChanging -= ScrollViewChanging; - } - - private void ScrollViewChanging(object sender, ScrollViewerViewChangingEventArgs e) - => ScrollPositionChanging?.Invoke(this, EventArgs.Empty); - - private void CalendarSizeChanged(object sender, SizeChangedEventArgs e) - { - if (ActiveCanvas == null) return; - - ActiveCanvas.SelectedDateTime = null; - } - - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - InternalFlipView = GetTemplateChild(PART_WinoFlipView) as WinoCalendarFlipView; - IdleGrid = GetTemplateChild(PART_IdleGrid) as Grid; - - UpdateIdleState(); - ManageCalendarOrientation(); - } - - private void UpdateIdleState() - { - InternalFlipView.Opacity = IsFlipIdle ? 0 : 1; - IdleGrid.Visibility = IsFlipIdle ? Visibility.Visible : Visibility.Collapsed; - } - - private void ActiveTimelineCellUnselected(object sender, TimelineCellUnselectedArgs e) - => TimelineCellUnselected?.Invoke(this, e); - - private void ActiveTimelineCellSelected(object sender, TimelineCellSelectedArgs e) - => TimelineCellSelected?.Invoke(this, e); - - public void NavigateToDay(DateTime dateTime) => InternalFlipView.NavigateToDay(dateTime); - - public async void NavigateToHour(TimeSpan timeSpan) - { - if (ActiveScrollViewer == null) return; - - // Total height of the FlipViewItem is the same as vertical ScrollViewer to position day headers. - - await Task.Yield(); - await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () => - { - double hourHeght = 60; - double totalHeight = ActiveScrollViewer.ScrollableHeight; - double scrollPosition = timeSpan.TotalHours * hourHeght; - - ActiveScrollViewer.ChangeView(null, scrollPosition, null, disableAnimation: false); - }); - } - public void ResetTimelineSelection() - { - if (ActiveCanvas == null) return; - - ActiveCanvas.SelectedDateTime = null; - } - - public void GoNextRange() - { - if (InternalFlipView == null) return; - - InternalFlipView.GoNextFlip(); - } - - public void GoPreviousRange() - { - if (InternalFlipView == null) return; - - InternalFlipView.GoPreviousFlip(); - } - - public void UnselectActiveTimelineCell() - { - if (ActiveCanvas == null) return; - - ActiveCanvas.SelectedDateTime = null; - } - - public CalendarItemControl GetCalendarItemControl(CalendarItemViewModel calendarItemViewModel) - { - return this.FindDescendants().FirstOrDefault(a => a.CalendarItem == calendarItemViewModel); + control.ManageCalendarOrientation(); } } + + private static void OnIdleStateChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e) + { + if (calendar is WinoCalendarControl calendarControl) + { + calendarControl.UpdateIdleState(); + } + } + + + private static void OnActiveVerticalScrollViewerChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e) + { + if (calendar is WinoCalendarControl calendarControl) + { + if (e.OldValue is ScrollViewer oldScrollViewer) + { + calendarControl.DeregisterScrollChanges(oldScrollViewer); + } + + if (e.NewValue is ScrollViewer newScrollViewer) + { + calendarControl.RegisterScrollChanges(newScrollViewer); + } + + calendarControl.ManageHighlightedDateRange(); + } + } + + + private static void OnActiveCanvasChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e) + { + if (calendar is WinoCalendarControl calendarControl) + { + if (e.OldValue is WinoDayTimelineCanvas oldCanvas) + { + // Dismiss any selection on the old canvas. + calendarControl.DeregisterCanvas(oldCanvas); + } + + if (e.NewValue is WinoDayTimelineCanvas newCanvas) + { + calendarControl.RegisterCanvas(newCanvas); + } + + calendarControl.ManageHighlightedDateRange(); + } + } + + private void ManageCalendarOrientation() + { + if (InternalFlipView == null || HorizontalItemsPanelTemplate == null || VerticalItemsPanelTemplate == null) return; + + InternalFlipView.ItemsPanel = Orientation == CalendarOrientation.Horizontal ? HorizontalItemsPanelTemplate : VerticalItemsPanelTemplate; + } + + private void ManageHighlightedDateRange() + => SelectedFlipViewDayRange = InternalFlipView.SelectedItem as DayRangeRenderModel; + + private void DeregisterCanvas(WinoDayTimelineCanvas canvas) + { + if (canvas == null) return; + + canvas.SelectedDateTime = null; + canvas.TimelineCellSelected -= ActiveTimelineCellSelected; + canvas.TimelineCellUnselected -= ActiveTimelineCellUnselected; + } + + private void RegisterCanvas(WinoDayTimelineCanvas canvas) + { + if (canvas == null) return; + + canvas.SelectedDateTime = null; + canvas.TimelineCellSelected += ActiveTimelineCellSelected; + canvas.TimelineCellUnselected += ActiveTimelineCellUnselected; + } + + private void RegisterScrollChanges(ScrollViewer scrollViewer) + { + if (scrollViewer == null) return; + + scrollViewer.ViewChanging += ScrollViewChanging; + } + + private void DeregisterScrollChanges(ScrollViewer scrollViewer) + { + if (scrollViewer == null) return; + + scrollViewer.ViewChanging -= ScrollViewChanging; + } + + private void ScrollViewChanging(object sender, ScrollViewerViewChangingEventArgs e) + => ScrollPositionChanging?.Invoke(this, EventArgs.Empty); + + private void CalendarSizeChanged(object sender, SizeChangedEventArgs e) + { + if (ActiveCanvas == null) return; + + ActiveCanvas.SelectedDateTime = null; + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + InternalFlipView = GetTemplateChild(PART_WinoFlipView) as WinoCalendarFlipView; + IdleGrid = GetTemplateChild(PART_IdleGrid) as Grid; + + UpdateIdleState(); + ManageCalendarOrientation(); + } + + private void UpdateIdleState() + { + InternalFlipView.Opacity = IsFlipIdle ? 0 : 1; + IdleGrid.Visibility = IsFlipIdle ? Visibility.Visible : Visibility.Collapsed; + } + + private void ActiveTimelineCellUnselected(object sender, TimelineCellUnselectedArgs e) + => TimelineCellUnselected?.Invoke(this, e); + + private void ActiveTimelineCellSelected(object sender, TimelineCellSelectedArgs e) + => TimelineCellSelected?.Invoke(this, e); + + public void NavigateToDay(DateTime dateTime) => InternalFlipView.NavigateToDay(dateTime); + + public async void NavigateToHour(TimeSpan timeSpan) + { + if (ActiveScrollViewer == null) return; + + // Total height of the FlipViewItem is the same as vertical ScrollViewer to position day headers. + + await Task.Yield(); + await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () => + { + double hourHeght = 60; + double totalHeight = ActiveScrollViewer.ScrollableHeight; + double scrollPosition = timeSpan.TotalHours * hourHeght; + + ActiveScrollViewer.ChangeView(null, scrollPosition, null, disableAnimation: false); + }); + } + public void ResetTimelineSelection() + { + if (ActiveCanvas == null) return; + + ActiveCanvas.SelectedDateTime = null; + } + + public void GoNextRange() + { + if (InternalFlipView == null) return; + + InternalFlipView.GoNextFlip(); + } + + public void GoPreviousRange() + { + if (InternalFlipView == null) return; + + InternalFlipView.GoPreviousFlip(); + } + + public void UnselectActiveTimelineCell() + { + if (ActiveCanvas == null) return; + + ActiveCanvas.SelectedDateTime = null; + } + + public CalendarItemControl GetCalendarItemControl(CalendarItemViewModel calendarItemViewModel) + { + return this.FindDescendants().FirstOrDefault(a => a.CalendarItem == calendarItemViewModel); + } } diff --git a/Wino.Calendar/Controls/WinoCalendarFlipView.cs b/Wino.Calendar/Controls/WinoCalendarFlipView.cs index 14dea2fe..679490d4 100644 --- a/Wino.Calendar/Controls/WinoCalendarFlipView.cs +++ b/Wino.Calendar/Controls/WinoCalendarFlipView.cs @@ -8,179 +8,178 @@ using Windows.UI.Xaml.Controls; using Wino.Core.Domain.Collections; using Wino.Core.Domain.Models.Calendar; -namespace Wino.Calendar.Controls +namespace Wino.Calendar.Controls; + +public partial class WinoCalendarFlipView : CustomCalendarFlipView { - public class WinoCalendarFlipView : CustomCalendarFlipView + public static readonly DependencyProperty IsIdleProperty = DependencyProperty.Register(nameof(IsIdle), typeof(bool), typeof(WinoCalendarFlipView), new PropertyMetadata(true)); + public static readonly DependencyProperty ActiveCanvasProperty = DependencyProperty.Register(nameof(ActiveCanvas), typeof(WinoDayTimelineCanvas), typeof(WinoCalendarFlipView), new PropertyMetadata(null)); + public static readonly DependencyProperty ActiveVerticalScrollViewerProperty = DependencyProperty.Register(nameof(ActiveVerticalScrollViewer), typeof(ScrollViewer), typeof(WinoCalendarFlipView), new PropertyMetadata(null)); + + /// + /// Gets or sets the active canvas that is currently displayed in the flip view. + /// Each day-range of flip view item has a canvas that displays the day timeline. + /// + public WinoDayTimelineCanvas ActiveCanvas { - public static readonly DependencyProperty IsIdleProperty = DependencyProperty.Register(nameof(IsIdle), typeof(bool), typeof(WinoCalendarFlipView), new PropertyMetadata(true)); - public static readonly DependencyProperty ActiveCanvasProperty = DependencyProperty.Register(nameof(ActiveCanvas), typeof(WinoDayTimelineCanvas), typeof(WinoCalendarFlipView), new PropertyMetadata(null)); - public static readonly DependencyProperty ActiveVerticalScrollViewerProperty = DependencyProperty.Register(nameof(ActiveVerticalScrollViewer), typeof(ScrollViewer), typeof(WinoCalendarFlipView), new PropertyMetadata(null)); + get { return (WinoDayTimelineCanvas)GetValue(ActiveCanvasProperty); } + set { SetValue(ActiveCanvasProperty, value); } + } - /// - /// Gets or sets the active canvas that is currently displayed in the flip view. - /// Each day-range of flip view item has a canvas that displays the day timeline. - /// - public WinoDayTimelineCanvas ActiveCanvas + /// + /// Gets or sets the scroll viewer that is currently active in the flip view. + /// It's the vertical scroll that scrolls the timeline only, not the header part that belongs + /// to parent FlipView control. + /// + public ScrollViewer ActiveVerticalScrollViewer + { + get { return (ScrollViewer)GetValue(ActiveVerticalScrollViewerProperty); } + set { SetValue(ActiveVerticalScrollViewerProperty, value); } + } + + public bool IsIdle + { + get { return (bool)GetValue(IsIdleProperty); } + set { SetValue(IsIdleProperty, value); } + } + + public WinoCalendarFlipView() + { + RegisterPropertyChangedCallback(SelectedIndexProperty, new DependencyPropertyChangedCallback(OnSelectedIndexUpdated)); + RegisterPropertyChangedCallback(ItemsSourceProperty, new DependencyPropertyChangedCallback(OnItemsSourceChanged)); + } + + private static void OnItemsSourceChanged(DependencyObject d, DependencyProperty e) + { + if (d is WinoCalendarFlipView flipView) { - get { return (WinoDayTimelineCanvas)GetValue(ActiveCanvasProperty); } - set { SetValue(ActiveCanvasProperty, value); } + flipView.RegisterItemsSourceChange(); + } + } + + private static void OnSelectedIndexUpdated(DependencyObject d, DependencyProperty e) + { + if (d is WinoCalendarFlipView flipView) + { + flipView.UpdateActiveCanvas(); + flipView.UpdateActiveScrollViewer(); + } + } + + private void RegisterItemsSourceChange() + { + if (GetItemsSource() is INotifyCollectionChanged notifyCollectionChanged) + { + notifyCollectionChanged.CollectionChanged += ItemsSourceUpdated; + } + } + + private void ItemsSourceUpdated(object sender, NotifyCollectionChangedEventArgs e) + { + IsIdle = e.Action == NotifyCollectionChangedAction.Reset || e.Action == NotifyCollectionChangedAction.Replace; + } + + private async Task GetCurrentFlipViewItem() + { + // TODO: Refactor this mechanism by listening to PrepareContainerForItemOverride and Loaded events together. + while (ContainerFromIndex(SelectedIndex) == null) + { + await Task.Delay(100); } - /// - /// Gets or sets the scroll viewer that is currently active in the flip view. - /// It's the vertical scroll that scrolls the timeline only, not the header part that belongs - /// to parent FlipView control. - /// - public ScrollViewer ActiveVerticalScrollViewer - { - get { return (ScrollViewer)GetValue(ActiveVerticalScrollViewerProperty); } - set { SetValue(ActiveVerticalScrollViewerProperty, value); } - } + return ContainerFromIndex(SelectedIndex) as FlipViewItem; - public bool IsIdle - { - get { return (bool)GetValue(IsIdleProperty); } - set { SetValue(IsIdleProperty, value); } - } - public WinoCalendarFlipView() - { - RegisterPropertyChangedCallback(SelectedIndexProperty, new DependencyPropertyChangedCallback(OnSelectedIndexUpdated)); - RegisterPropertyChangedCallback(ItemsSourceProperty, new DependencyPropertyChangedCallback(OnItemsSourceChanged)); - } + } - private static void OnItemsSourceChanged(DependencyObject d, DependencyProperty e) + private void UpdateActiveScrollViewer() + { + if (SelectedIndex < 0) + ActiveVerticalScrollViewer = null; + else { - if (d is WinoCalendarFlipView flipView) + GetCurrentFlipViewItem().ContinueWith(task => { - flipView.RegisterItemsSourceChange(); - } - } - - private static void OnSelectedIndexUpdated(DependencyObject d, DependencyProperty e) - { - if (d is WinoCalendarFlipView flipView) - { - flipView.UpdateActiveCanvas(); - flipView.UpdateActiveScrollViewer(); - } - } - - private void RegisterItemsSourceChange() - { - if (GetItemsSource() is INotifyCollectionChanged notifyCollectionChanged) - { - notifyCollectionChanged.CollectionChanged += ItemsSourceUpdated; - } - } - - private void ItemsSourceUpdated(object sender, NotifyCollectionChangedEventArgs e) - { - IsIdle = e.Action == NotifyCollectionChangedAction.Reset || e.Action == NotifyCollectionChangedAction.Replace; - } - - private async Task GetCurrentFlipViewItem() - { - // TODO: Refactor this mechanism by listening to PrepareContainerForItemOverride and Loaded events together. - while (ContainerFromIndex(SelectedIndex) == null) - { - await Task.Delay(100); - } - - return ContainerFromIndex(SelectedIndex) as FlipViewItem; - - - } - - private void UpdateActiveScrollViewer() - { - if (SelectedIndex < 0) - ActiveVerticalScrollViewer = null; - else - { - GetCurrentFlipViewItem().ContinueWith(task => + if (task.IsCompletedSuccessfully) { - if (task.IsCompletedSuccessfully) + var flipViewItem = task.Result; + + _ = Dispatcher.TryRunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { - var flipViewItem = task.Result; - - _ = Dispatcher.TryRunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => - { - ActiveVerticalScrollViewer = flipViewItem.FindDescendant(); - }); - } - }); - } - } - - public void UpdateActiveCanvas() - { - if (SelectedIndex < 0) - ActiveCanvas = null; - else - { - GetCurrentFlipViewItem().ContinueWith(task => - { - if (task.IsCompletedSuccessfully) - { - var flipViewItem = task.Result; - - _ = Dispatcher.TryRunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => - { - ActiveCanvas = flipViewItem.FindDescendant(); - }); - } - }); - } - } - - /// - /// Navigates to the specified date in the calendar. - /// - /// Date to navigate. - public async void NavigateToDay(DateTime dateTime) - { - await Task.Yield(); - - await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () => - { - // Find the day range that contains the date. - var dayRange = GetItemsSource()?.FirstOrDefault(a => a.CalendarDays.Any(b => b.RepresentingDate.Date == dateTime.Date)); - - if (dayRange != null) - { - var navigationItemIndex = GetItemsSource().IndexOf(dayRange); - - if (Math.Abs(navigationItemIndex - SelectedIndex) > 4) - { - // Difference between dates are high. - // No need to animate this much, just go without animating. - - SelectedIndex = navigationItemIndex; - } - else - { - // Until we reach the day in the flip, simulate next-prev button clicks. - // This will make sure the FlipView animations are triggered. - // Setting SelectedIndex directly doesn't trigger the animations. - - while (SelectedIndex != navigationItemIndex) - { - if (SelectedIndex > navigationItemIndex) - { - GoPreviousFlip(); - } - else - { - GoNextFlip(); - } - } - } + ActiveVerticalScrollViewer = flipViewItem.FindDescendant(); + }); } }); } - - private ObservableRangeCollection GetItemsSource() - => ItemsSource as ObservableRangeCollection; } + + public void UpdateActiveCanvas() + { + if (SelectedIndex < 0) + ActiveCanvas = null; + else + { + GetCurrentFlipViewItem().ContinueWith(task => + { + if (task.IsCompletedSuccessfully) + { + var flipViewItem = task.Result; + + _ = Dispatcher.TryRunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => + { + ActiveCanvas = flipViewItem.FindDescendant(); + }); + } + }); + } + } + + /// + /// Navigates to the specified date in the calendar. + /// + /// Date to navigate. + public async void NavigateToDay(DateTime dateTime) + { + await Task.Yield(); + + await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () => + { + // Find the day range that contains the date. + var dayRange = GetItemsSource()?.FirstOrDefault(a => a.CalendarDays.Any(b => b.RepresentingDate.Date == dateTime.Date)); + + if (dayRange != null) + { + var navigationItemIndex = GetItemsSource().IndexOf(dayRange); + + if (Math.Abs(navigationItemIndex - SelectedIndex) > 4) + { + // Difference between dates are high. + // No need to animate this much, just go without animating. + + SelectedIndex = navigationItemIndex; + } + else + { + // Until we reach the day in the flip, simulate next-prev button clicks. + // This will make sure the FlipView animations are triggered. + // Setting SelectedIndex directly doesn't trigger the animations. + + while (SelectedIndex != navigationItemIndex) + { + if (SelectedIndex > navigationItemIndex) + { + GoPreviousFlip(); + } + else + { + GoNextFlip(); + } + } + } + } + }); + } + + private ObservableRangeCollection GetItemsSource() + => ItemsSource as ObservableRangeCollection; } diff --git a/Wino.Calendar/Controls/WinoCalendarPanel.cs b/Wino.Calendar/Controls/WinoCalendarPanel.cs index b4c32080..c76801ea 100644 --- a/Wino.Calendar/Controls/WinoCalendarPanel.cs +++ b/Wino.Calendar/Controls/WinoCalendarPanel.cs @@ -11,284 +11,283 @@ using Wino.Calendar.Models; using Wino.Calendar.ViewModels.Data; using Wino.Core.Domain.Interfaces; -namespace Wino.Calendar.Controls +namespace Wino.Calendar.Controls; + +public partial class WinoCalendarPanel : Panel { - public class WinoCalendarPanel : Panel + private const double LastItemRightExtraMargin = 12d; + + // Store each ICalendarItem measurements by their Id. + 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 HourHeightProperty = DependencyProperty.Register(nameof(HourHeight), typeof(double), typeof(WinoCalendarPanel), new PropertyMetadata(0d)); + public static readonly DependencyProperty PeriodProperty = DependencyProperty.Register(nameof(Period), typeof(ITimePeriod), typeof(WinoCalendarPanel), new PropertyMetadata(null)); + + public ITimePeriod Period { - private const double LastItemRightExtraMargin = 12d; + get { return (ITimePeriod)GetValue(PeriodProperty); } + set { SetValue(PeriodProperty, value); } + } - // Store each ICalendarItem measurements by their Id. - private readonly Dictionary _measurements = new Dictionary(); + public double HourHeight + { + get { return (double)GetValue(HourHeightProperty); } + set { SetValue(HourHeightProperty, value); } + } - 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 HourHeightProperty = DependencyProperty.Register(nameof(HourHeight), typeof(double), typeof(WinoCalendarPanel), new PropertyMetadata(0d)); - public static readonly DependencyProperty PeriodProperty = DependencyProperty.Register(nameof(Period), typeof(ITimePeriod), typeof(WinoCalendarPanel), new PropertyMetadata(null)); + public Thickness EventItemMargin + { + get { return (Thickness)GetValue(EventItemMarginProperty); } + set { SetValue(EventItemMarginProperty, value); } + } - public ITimePeriod Period + private void ResetMeasurements() => _measurements.Clear(); + + private double GetChildTopMargin(ICalendarItem calendarItemViewModel, double availableHeight) + { + var childStart = calendarItemViewModel.StartDate; + + if (childStart <= Period.Start) { - get { return (ITimePeriod)GetValue(PeriodProperty); } - set { SetValue(PeriodProperty, value); } + // Event started before or exactly at the periods tart. This might be a multi-day event. + // We can simply consider event must not have a top margin. + + return 0d; } - public double HourHeight + double minutesFromStart = (childStart - Period.Start).TotalMinutes; + return (minutesFromStart / 1440) * availableHeight; + } + + private double GetChildWidth(CalendarItemMeasurement calendarItemMeasurement, double availableWidth) + { + return (calendarItemMeasurement.Right - calendarItemMeasurement.Left) * availableWidth; + } + + private double GetChildLeftMargin(CalendarItemMeasurement calendarItemMeasurement, double availableWidth) + => availableWidth * calendarItemMeasurement.Left; + + private double GetChildHeight(ICalendarItem child) + { + // All day events are not measured. + if (child.IsAllDayEvent) return 0; + + double childDurationInMinutes = 0d; + double availableHeight = HourHeight * 24; + + var periodRelation = child.Period.GetRelation(Period); + + // Debug.WriteLine($"Render relation of {child.Title} ({child.Period.Start} - {child.Period.End}) is {periodRelation} with {Period.Start.Day}"); + + if (!child.IsMultiDayEvent) { - get { return (double)GetValue(HourHeightProperty); } - set { SetValue(HourHeightProperty, value); } + childDurationInMinutes = child.Period.Duration.TotalMinutes; + } + else + { + // Multi-day event. + // Check how many of the event falls into the current period. + childDurationInMinutes = (child.Period.End - Period.Start).TotalMinutes; } - public Thickness EventItemMargin + return (childDurationInMinutes / 1440) * availableHeight; + } + + protected override Size MeasureOverride(Size availableSize) + { + ResetMeasurements(); + return base.MeasureOverride(availableSize); + } + + protected override Size ArrangeOverride(Size finalSize) + { + if (Period == null || HourHeight == 0d) return finalSize; + + // Measure/arrange each child height and width. + // This is a vertical calendar. Therefore the height of each child is the duration of the event. + // Children weights for left and right will be saved if they don't exist. + // This is important because we don't want to measure the weights again. + // They don't change until new event is added or removed. + // Width of the each child may depend on the rectangle packing algorithm. + // Children are first categorized into columns. Then each column is shifted to the left until + // no overlap occurs. The width of each child is calculated based on the number of columns it spans. + + double availableHeight = finalSize.Height; + double availableWidth = finalSize.Width; + + var calendarControls = Children.Cast(); + + if (!calendarControls.Any()) return base.ArrangeOverride(finalSize); + + var events = calendarControls.Select(a => a.Content as CalendarItemViewModel); + + LayoutEvents(events); + + foreach (var control in calendarControls) { - get { return (Thickness)GetValue(EventItemMarginProperty); } - set { SetValue(EventItemMarginProperty, value); } - } + // We can't arrange this child. + if (!(control.Content is ICalendarItem child)) continue; - private void ResetMeasurements() => _measurements.Clear(); + bool isHorizontallyLastItem = false; - private double GetChildTopMargin(ICalendarItem calendarItemViewModel, double availableHeight) - { - var childStart = calendarItemViewModel.StartDate; + double childWidth = 0, + childHeight = Math.Max(0, GetChildHeight(child)), + childTop = Math.Max(0, GetChildTopMargin(child, availableHeight)), + childLeft = 0; - if (childStart <= Period.Start) - { - // Event started before or exactly at the periods tart. This might be a multi-day event. - // We can simply consider event must not have a top margin. + // No need to measure anything here. + if (childHeight == 0) continue; - return 0d; - } - - double minutesFromStart = (childStart - Period.Start).TotalMinutes; - return (minutesFromStart / 1440) * availableHeight; - } - - private double GetChildWidth(CalendarItemMeasurement calendarItemMeasurement, double availableWidth) - { - return (calendarItemMeasurement.Right - calendarItemMeasurement.Left) * availableWidth; - } - - private double GetChildLeftMargin(CalendarItemMeasurement calendarItemMeasurement, double availableWidth) - => availableWidth * calendarItemMeasurement.Left; - - private double GetChildHeight(ICalendarItem child) - { - // All day events are not measured. - if (child.IsAllDayEvent) return 0; - - double childDurationInMinutes = 0d; - double availableHeight = HourHeight * 24; - - var periodRelation = child.Period.GetRelation(Period); - - // Debug.WriteLine($"Render relation of {child.Title} ({child.Period.Start} - {child.Period.End}) is {periodRelation} with {Period.Start.Day}"); - - if (!child.IsMultiDayEvent) - { - childDurationInMinutes = child.Period.Duration.TotalMinutes; - } - else + if (!_measurements.ContainsKey(child)) { // Multi-day event. - // Check how many of the event falls into the current period. - childDurationInMinutes = (child.Period.End - Period.Start).TotalMinutes; - } - return (childDurationInMinutes / 1440) * availableHeight; - } - - protected override Size MeasureOverride(Size availableSize) - { - ResetMeasurements(); - return base.MeasureOverride(availableSize); - } - - protected override Size ArrangeOverride(Size finalSize) - { - if (Period == null || HourHeight == 0d) return finalSize; - - // Measure/arrange each child height and width. - // This is a vertical calendar. Therefore the height of each child is the duration of the event. - // Children weights for left and right will be saved if they don't exist. - // This is important because we don't want to measure the weights again. - // They don't change until new event is added or removed. - // Width of the each child may depend on the rectangle packing algorithm. - // Children are first categorized into columns. Then each column is shifted to the left until - // no overlap occurs. The width of each child is calculated based on the number of columns it spans. - - double availableHeight = finalSize.Height; - double availableWidth = finalSize.Width; - - var calendarControls = Children.Cast(); - - if (!calendarControls.Any()) return base.ArrangeOverride(finalSize); - - var events = calendarControls.Select(a => a.Content as CalendarItemViewModel); - - LayoutEvents(events); - - foreach (var control in calendarControls) - { - // We can't arrange this child. - if (!(control.Content is ICalendarItem child)) continue; - - bool isHorizontallyLastItem = false; - - double childWidth = 0, - childHeight = Math.Max(0, GetChildHeight(child)), - childTop = Math.Max(0, GetChildTopMargin(child, availableHeight)), - childLeft = 0; - - // No need to measure anything here. - if (childHeight == 0) continue; - - if (!_measurements.ContainsKey(child)) - { - // Multi-day event. - - childLeft = 0; - childWidth = availableWidth; - } - else - { - var childMeasurement = _measurements[child]; - - childWidth = Math.Max(0, GetChildWidth(childMeasurement, finalSize.Width)); - childLeft = Math.Max(0, GetChildLeftMargin(childMeasurement, availableWidth)); - - isHorizontallyLastItem = childMeasurement.Right == 1; - } - - // Add additional right margin to items that falls on the right edge of the panel. - double extraRightMargin = 0; - - // Multi-day events don't have any margin and their hit test is disabled. - if (!child.IsMultiDayEvent) - { - // Max of 5% of the width or 20px max. - extraRightMargin = isHorizontallyLastItem ? Math.Max(LastItemRightExtraMargin, finalSize.Width * 5 / 100) : 0; - } - - if (childWidth < 0) childWidth = 1; - - // Regular events must have 2px margin - if (!child.IsMultiDayEvent && !child.IsAllDayEvent) - { - childLeft += 2; - childTop += 2; - childHeight -= 2; - childWidth -= 2; - } - - var arrangementRect = new Rect(childLeft + EventItemMargin.Left, childTop + EventItemMargin.Top, Math.Max(childWidth - extraRightMargin, 1), childHeight); - - // Make sure measured size will fit in the arranged box. - var measureSize = arrangementRect.ToSize(); - control.Measure(measureSize); - control.Arrange(arrangementRect); - - //Debug.WriteLine($"{child.Title}, Measured: {measureSize}, Arranged: {arrangementRect}"); - } - - - return finalSize; - } - - #region ColumSpanning and Packing Algorithm - - private void AddOrUpdateMeasurement(ICalendarItem calendarItem, CalendarItemMeasurement measurement) - { - if (_measurements.ContainsKey(calendarItem)) - { - _measurements[calendarItem] = measurement; + childLeft = 0; + childWidth = availableWidth; } else { - _measurements.Add(calendarItem, measurement); + var childMeasurement = _measurements[child]; + + childWidth = Math.Max(0, GetChildWidth(childMeasurement, finalSize.Width)); + childLeft = Math.Max(0, GetChildLeftMargin(childMeasurement, availableWidth)); + + isHorizontallyLastItem = childMeasurement.Right == 1; } + + // Add additional right margin to items that falls on the right edge of the panel. + double extraRightMargin = 0; + + // Multi-day events don't have any margin and their hit test is disabled. + if (!child.IsMultiDayEvent) + { + // Max of 5% of the width or 20px max. + extraRightMargin = isHorizontallyLastItem ? Math.Max(LastItemRightExtraMargin, finalSize.Width * 5 / 100) : 0; + } + + if (childWidth < 0) childWidth = 1; + + // Regular events must have 2px margin + if (!child.IsMultiDayEvent && !child.IsAllDayEvent) + { + childLeft += 2; + childTop += 2; + childHeight -= 2; + childWidth -= 2; + } + + var arrangementRect = new Rect(childLeft + EventItemMargin.Left, childTop + EventItemMargin.Top, Math.Max(childWidth - extraRightMargin, 1), childHeight); + + // Make sure measured size will fit in the arranged box. + var measureSize = arrangementRect.ToSize(); + control.Measure(measureSize); + control.Arrange(arrangementRect); + + //Debug.WriteLine($"{child.Title}, Measured: {measureSize}, Arranged: {arrangementRect}"); } - // Pick the left and right positions of each event, such that there are no overlap. - private void LayoutEvents(IEnumerable events) + + return finalSize; + } + + #region ColumSpanning and Packing Algorithm + + private void AddOrUpdateMeasurement(ICalendarItem calendarItem, CalendarItemMeasurement measurement) + { + if (_measurements.ContainsKey(calendarItem)) { - var columns = new List>(); - DateTime? lastEventEnding = null; + _measurements[calendarItem] = measurement; + } + else + { + _measurements.Add(calendarItem, measurement); + } + } - foreach (var ev in events.OrderBy(ev => ev.StartDate).ThenBy(ev => ev.EndDate)) - { - // Multi-day events are not measured. - if (ev.IsMultiDayEvent) continue; + // Pick the left and right positions of each event, such that there are no overlap. + private void LayoutEvents(IEnumerable events) + { + var columns = new List>(); + DateTime? lastEventEnding = null; - if (ev.Period.Start >= lastEventEnding) - { - PackEvents(columns); - columns.Clear(); - lastEventEnding = null; - } + foreach (var ev in events.OrderBy(ev => ev.StartDate).ThenBy(ev => ev.EndDate)) + { + // Multi-day events are not measured. + if (ev.IsMultiDayEvent) continue; - bool placed = false; - - foreach (var col in columns) - { - if (!col.Last().Period.OverlapsWith(ev.Period)) - { - col.Add(ev); - placed = true; - break; - } - } - if (!placed) - { - columns.Add(new List { ev }); - } - if (lastEventEnding == null || ev.Period.End > lastEventEnding.Value) - { - lastEventEnding = ev.Period.End; - } - } - if (columns.Count > 0) + if (ev.Period.Start >= lastEventEnding) { PackEvents(columns); + columns.Clear(); + lastEventEnding = null; } - } - // Set the left and right positions for each event in the connected group. - private void PackEvents(List> columns) - { - float numColumns = columns.Count; - int iColumn = 0; + bool placed = false; foreach (var col in columns) { - foreach (var ev in col) + if (!col.Last().Period.OverlapsWith(ev.Period)) { - int colSpan = ExpandEvent(ev, iColumn, columns); - - var leftWeight = iColumn / numColumns; - var rightWeight = (iColumn + colSpan) / numColumns; - - AddOrUpdateMeasurement(ev, new CalendarItemMeasurement(leftWeight, rightWeight)); + col.Add(ev); + placed = true; + break; } - - iColumn++; } - } - - // Checks how many columns the event can expand into, without colliding with other events. - private int ExpandEvent(ICalendarItem ev, int iColumn, List> columns) - { - int colSpan = 1; - - foreach (var col in columns.Skip(iColumn + 1)) + if (!placed) { - foreach (var ev1 in col) - { - if (ev1.Period.OverlapsWith(ev.Period)) return colSpan; - } + columns.Add(new List { ev }); + } + if (lastEventEnding == null || ev.Period.End > lastEventEnding.Value) + { + lastEventEnding = ev.Period.End; + } + } + if (columns.Count > 0) + { + PackEvents(columns); + } + } - colSpan++; + // Set the left and right positions for each event in the connected group. + private void PackEvents(List> columns) + { + float numColumns = columns.Count; + int iColumn = 0; + + foreach (var col in columns) + { + foreach (var ev in col) + { + int colSpan = ExpandEvent(ev, iColumn, columns); + + var leftWeight = iColumn / numColumns; + var rightWeight = (iColumn + colSpan) / numColumns; + + AddOrUpdateMeasurement(ev, new CalendarItemMeasurement(leftWeight, rightWeight)); } - return colSpan; + iColumn++; + } + } + + // Checks how many columns the event can expand into, without colliding with other events. + private int ExpandEvent(ICalendarItem ev, int iColumn, List> columns) + { + int colSpan = 1; + + foreach (var col in columns.Skip(iColumn + 1)) + { + foreach (var ev1 in col) + { + if (ev1.Period.OverlapsWith(ev.Period)) return colSpan; + } + + colSpan++; } - #endregion + return colSpan; } + + #endregion } diff --git a/Wino.Calendar/Controls/WinoCalendarTypeSelectorControl.cs b/Wino.Calendar/Controls/WinoCalendarTypeSelectorControl.cs index 14169438..899e32da 100644 --- a/Wino.Calendar/Controls/WinoCalendarTypeSelectorControl.cs +++ b/Wino.Calendar/Controls/WinoCalendarTypeSelectorControl.cs @@ -4,89 +4,88 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Wino.Core.Domain.Enums; -namespace Wino.Calendar.Controls +namespace Wino.Calendar.Controls; + +public partial class WinoCalendarTypeSelectorControl : Control { - public class WinoCalendarTypeSelectorControl : Control + private const string PART_TodayButton = nameof(PART_TodayButton); + private const string PART_DayToggle = nameof(PART_DayToggle); + private const string PART_WeekToggle = nameof(PART_WeekToggle); + private const string PART_MonthToggle = nameof(PART_MonthToggle); + private const string PART_YearToggle = nameof(PART_YearToggle); + + public static readonly DependencyProperty SelectedTypeProperty = DependencyProperty.Register(nameof(SelectedType), typeof(CalendarDisplayType), typeof(WinoCalendarTypeSelectorControl), new PropertyMetadata(CalendarDisplayType.Week)); + public static readonly DependencyProperty DisplayDayCountProperty = DependencyProperty.Register(nameof(DisplayDayCount), typeof(int), typeof(WinoCalendarTypeSelectorControl), new PropertyMetadata(0)); + public static readonly DependencyProperty TodayClickedCommandProperty = DependencyProperty.Register(nameof(TodayClickedCommand), typeof(ICommand), typeof(WinoCalendarTypeSelectorControl), new PropertyMetadata(null)); + + public ICommand TodayClickedCommand { - private const string PART_TodayButton = nameof(PART_TodayButton); - private const string PART_DayToggle = nameof(PART_DayToggle); - private const string PART_WeekToggle = nameof(PART_WeekToggle); - private const string PART_MonthToggle = nameof(PART_MonthToggle); - private const string PART_YearToggle = nameof(PART_YearToggle); + get { return (ICommand)GetValue(TodayClickedCommandProperty); } + set { SetValue(TodayClickedCommandProperty, value); } + } - public static readonly DependencyProperty SelectedTypeProperty = DependencyProperty.Register(nameof(SelectedType), typeof(CalendarDisplayType), typeof(WinoCalendarTypeSelectorControl), new PropertyMetadata(CalendarDisplayType.Week)); - public static readonly DependencyProperty DisplayDayCountProperty = DependencyProperty.Register(nameof(DisplayDayCount), typeof(int), typeof(WinoCalendarTypeSelectorControl), new PropertyMetadata(0)); - public static readonly DependencyProperty TodayClickedCommandProperty = DependencyProperty.Register(nameof(TodayClickedCommand), typeof(ICommand), typeof(WinoCalendarTypeSelectorControl), new PropertyMetadata(null)); + public CalendarDisplayType SelectedType + { + get { return (CalendarDisplayType)GetValue(SelectedTypeProperty); } + set { SetValue(SelectedTypeProperty, value); } + } - public ICommand TodayClickedCommand - { - get { return (ICommand)GetValue(TodayClickedCommandProperty); } - set { SetValue(TodayClickedCommandProperty, value); } - } + public int DisplayDayCount + { + get { return (int)GetValue(DisplayDayCountProperty); } + set { SetValue(DisplayDayCountProperty, value); } + } - public CalendarDisplayType SelectedType - { - get { return (CalendarDisplayType)GetValue(SelectedTypeProperty); } - set { SetValue(SelectedTypeProperty, value); } - } + private AppBarButton _todayButton; + private AppBarToggleButton _dayToggle; + private AppBarToggleButton _weekToggle; + private AppBarToggleButton _monthToggle; + private AppBarToggleButton _yearToggle; - public int DisplayDayCount - { - get { return (int)GetValue(DisplayDayCountProperty); } - set { SetValue(DisplayDayCountProperty, value); } - } + public WinoCalendarTypeSelectorControl() + { + DefaultStyleKey = typeof(WinoCalendarTypeSelectorControl); + } - private AppBarButton _todayButton; - private AppBarToggleButton _dayToggle; - private AppBarToggleButton _weekToggle; - private AppBarToggleButton _monthToggle; - private AppBarToggleButton _yearToggle; + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); - public WinoCalendarTypeSelectorControl() - { - DefaultStyleKey = typeof(WinoCalendarTypeSelectorControl); - } + _todayButton = GetTemplateChild(PART_TodayButton) as AppBarButton; + _dayToggle = GetTemplateChild(PART_DayToggle) as AppBarToggleButton; + _weekToggle = GetTemplateChild(PART_WeekToggle) as AppBarToggleButton; + _monthToggle = GetTemplateChild(PART_MonthToggle) as AppBarToggleButton; + _yearToggle = GetTemplateChild(PART_YearToggle) as AppBarToggleButton; - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); + Guard.IsNotNull(_todayButton, nameof(_todayButton)); + Guard.IsNotNull(_dayToggle, nameof(_dayToggle)); + Guard.IsNotNull(_weekToggle, nameof(_weekToggle)); + Guard.IsNotNull(_monthToggle, nameof(_monthToggle)); + Guard.IsNotNull(_yearToggle, nameof(_yearToggle)); - _todayButton = GetTemplateChild(PART_TodayButton) as AppBarButton; - _dayToggle = GetTemplateChild(PART_DayToggle) as AppBarToggleButton; - _weekToggle = GetTemplateChild(PART_WeekToggle) as AppBarToggleButton; - _monthToggle = GetTemplateChild(PART_MonthToggle) as AppBarToggleButton; - _yearToggle = GetTemplateChild(PART_YearToggle) as AppBarToggleButton; + _todayButton.Click += TodayClicked; - Guard.IsNotNull(_todayButton, nameof(_todayButton)); - Guard.IsNotNull(_dayToggle, nameof(_dayToggle)); - Guard.IsNotNull(_weekToggle, nameof(_weekToggle)); - Guard.IsNotNull(_monthToggle, nameof(_monthToggle)); - Guard.IsNotNull(_yearToggle, nameof(_yearToggle)); + _dayToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Day); }; + _weekToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Week); }; + _monthToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Month); }; + _yearToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Year); }; - _todayButton.Click += TodayClicked; + UpdateToggleButtonStates(); + } - _dayToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Day); }; - _weekToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Week); }; - _monthToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Month); }; - _yearToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Year); }; + private void TodayClicked(object sender, RoutedEventArgs e) => TodayClickedCommand?.Execute(null); - UpdateToggleButtonStates(); - } + private void SetSelectedType(CalendarDisplayType type) + { + SelectedType = type; + UpdateToggleButtonStates(); + } - private void TodayClicked(object sender, RoutedEventArgs e) => TodayClickedCommand?.Execute(null); - - private void SetSelectedType(CalendarDisplayType type) - { - SelectedType = type; - UpdateToggleButtonStates(); - } - - private void UpdateToggleButtonStates() - { - _dayToggle.IsChecked = SelectedType == CalendarDisplayType.Day; - _weekToggle.IsChecked = SelectedType == CalendarDisplayType.Week; - _monthToggle.IsChecked = SelectedType == CalendarDisplayType.Month; - _yearToggle.IsChecked = SelectedType == CalendarDisplayType.Year; - } + private void UpdateToggleButtonStates() + { + _dayToggle.IsChecked = SelectedType == CalendarDisplayType.Day; + _weekToggle.IsChecked = SelectedType == CalendarDisplayType.Week; + _monthToggle.IsChecked = SelectedType == CalendarDisplayType.Month; + _yearToggle.IsChecked = SelectedType == CalendarDisplayType.Year; } } diff --git a/Wino.Calendar/Controls/WinoCalendarView.cs b/Wino.Calendar/Controls/WinoCalendarView.cs index fb4e7440..6048d03c 100644 --- a/Wino.Calendar/Controls/WinoCalendarView.cs +++ b/Wino.Calendar/Controls/WinoCalendarView.cs @@ -8,140 +8,139 @@ using Windows.UI.Xaml.Media; using Wino.Core.Domain.Models.Calendar; using Wino.Helpers; -namespace Wino.Calendar.Controls +namespace Wino.Calendar.Controls; + +public partial class WinoCalendarView : Control { - public class WinoCalendarView : Control + private const string PART_DayViewItemBorder = nameof(PART_DayViewItemBorder); + private const string PART_CalendarView = nameof(PART_CalendarView); + + public static readonly DependencyProperty HighlightedDateRangeProperty = DependencyProperty.Register(nameof(HighlightedDateRange), typeof(DateRange), typeof(WinoCalendarView), new PropertyMetadata(null, new PropertyChangedCallback(OnHighlightedDateRangeChanged))); + public static readonly DependencyProperty VisibleDateBackgroundProperty = DependencyProperty.Register(nameof(VisibleDateBackground), typeof(Brush), typeof(WinoCalendarView), new PropertyMetadata(null, new PropertyChangedCallback(OnPropertiesChanged))); + public static readonly DependencyProperty DateClickedCommandProperty = DependencyProperty.Register(nameof(DateClickedCommand), typeof(ICommand), typeof(WinoCalendarView), new PropertyMetadata(null)); + public static readonly DependencyProperty TodayBackgroundColorProperty = DependencyProperty.Register(nameof(TodayBackgroundColor), typeof(Color), typeof(WinoCalendarView), new PropertyMetadata(null)); + + public Color TodayBackgroundColor { - private const string PART_DayViewItemBorder = nameof(PART_DayViewItemBorder); - private const string PART_CalendarView = nameof(PART_CalendarView); + get { return (Color)GetValue(TodayBackgroundColorProperty); } + set { SetValue(TodayBackgroundColorProperty, value); } + } - public static readonly DependencyProperty HighlightedDateRangeProperty = DependencyProperty.Register(nameof(HighlightedDateRange), typeof(DateRange), typeof(WinoCalendarView), new PropertyMetadata(null, new PropertyChangedCallback(OnHighlightedDateRangeChanged))); - public static readonly DependencyProperty VisibleDateBackgroundProperty = DependencyProperty.Register(nameof(VisibleDateBackground), typeof(Brush), typeof(WinoCalendarView), new PropertyMetadata(null, new PropertyChangedCallback(OnPropertiesChanged))); - public static readonly DependencyProperty DateClickedCommandProperty = DependencyProperty.Register(nameof(DateClickedCommand), typeof(ICommand), typeof(WinoCalendarView), new PropertyMetadata(null)); - public static readonly DependencyProperty TodayBackgroundColorProperty = DependencyProperty.Register(nameof(TodayBackgroundColor), typeof(Color), typeof(WinoCalendarView), new PropertyMetadata(null)); + /// + /// Gets or sets the command to execute when a date is picked. + /// Unused. + /// + public ICommand DateClickedCommand + { + get { return (ICommand)GetValue(DateClickedCommandProperty); } + set { SetValue(DateClickedCommandProperty, value); } + } - public Color TodayBackgroundColor + /// + /// Gets or sets the highlighted range of dates. + /// + public DateRange HighlightedDateRange + { + get { return (DateRange)GetValue(HighlightedDateRangeProperty); } + set { SetValue(HighlightedDateRangeProperty, value); } + } + + public Brush VisibleDateBackground + { + get { return (Brush)GetValue(VisibleDateBackgroundProperty); } + set { SetValue(VisibleDateBackgroundProperty, value); } + } + + + + private CalendarView CalendarView; + + public WinoCalendarView() + { + DefaultStyleKey = typeof(WinoCalendarView); + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + CalendarView = GetTemplateChild(PART_CalendarView) as CalendarView; + + Guard.IsNotNull(CalendarView, nameof(CalendarView)); + + CalendarView.SelectedDatesChanged -= InternalCalendarViewSelectionChanged; + CalendarView.SelectedDatesChanged += InternalCalendarViewSelectionChanged; + + // TODO: Should come from settings. + CalendarView.FirstDayOfWeek = Windows.Globalization.DayOfWeek.Monday; + + // Everytime display mode changes, update the visible date range backgrounds. + // If users go back from year -> month -> day, we need to update the visible date range backgrounds. + + CalendarView.RegisterPropertyChangedCallback(CalendarView.DisplayModeProperty, (s, e) => UpdateVisibleDateRangeBackgrounds()); + } + + private void InternalCalendarViewSelectionChanged(CalendarView sender, CalendarViewSelectedDatesChangedEventArgs args) + { + if (args.AddedDates?.Count > 0) { - get { return (Color)GetValue(TodayBackgroundColorProperty); } - set { SetValue(TodayBackgroundColorProperty, value); } + var clickedDate = args.AddedDates[0].Date; + SetInnerDisplayDate(clickedDate); + + var clickArgs = new CalendarViewDayClickedEventArgs(clickedDate); + DateClickedCommand?.Execute(clickArgs); } - /// - /// Gets or sets the command to execute when a date is picked. - /// Unused. - /// - public ICommand DateClickedCommand + // Reset selection, we don't show selected dates but react to them. + CalendarView.SelectedDates.Clear(); + } + + private static void OnPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is WinoCalendarView control) { - get { return (ICommand)GetValue(DateClickedCommandProperty); } - set { SetValue(DateClickedCommandProperty, value); } + control.UpdateVisibleDateRangeBackgrounds(); } + } - /// - /// Gets or sets the highlighted range of dates. - /// - public DateRange HighlightedDateRange + private void SetInnerDisplayDate(DateTime dateTime) => CalendarView?.SetDisplayDate(dateTime); + + // Changing selected dates will trigger the selection changed event. + // It will behave like user clicked the date. + public void GoToDay(DateTime dateTime) => CalendarView.SelectedDates.Add(dateTime); + + private static void OnHighlightedDateRangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is WinoCalendarView control) { - get { return (DateRange)GetValue(HighlightedDateRangeProperty); } - set { SetValue(HighlightedDateRangeProperty, value); } + control.SetInnerDisplayDate(control.HighlightedDateRange.StartDate); + control.UpdateVisibleDateRangeBackgrounds(); } + } - public Brush VisibleDateBackground + public void UpdateVisibleDateRangeBackgrounds() + { + if (HighlightedDateRange == null || VisibleDateBackground == null || TodayBackgroundColor == null || CalendarView == null) return; + + var markDateCalendarDayItems = WinoVisualTreeHelper.FindDescendants(CalendarView); + + foreach (var calendarDayItem in markDateCalendarDayItems) { - get { return (Brush)GetValue(VisibleDateBackgroundProperty); } - set { SetValue(VisibleDateBackgroundProperty, value); } - } + var border = WinoVisualTreeHelper.GetChildObject(calendarDayItem, PART_DayViewItemBorder); + if (border == null) return; - - private CalendarView CalendarView; - - public WinoCalendarView() - { - DefaultStyleKey = typeof(WinoCalendarView); - } - - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - CalendarView = GetTemplateChild(PART_CalendarView) as CalendarView; - - Guard.IsNotNull(CalendarView, nameof(CalendarView)); - - CalendarView.SelectedDatesChanged -= InternalCalendarViewSelectionChanged; - CalendarView.SelectedDatesChanged += InternalCalendarViewSelectionChanged; - - // TODO: Should come from settings. - CalendarView.FirstDayOfWeek = Windows.Globalization.DayOfWeek.Monday; - - // Everytime display mode changes, update the visible date range backgrounds. - // If users go back from year -> month -> day, we need to update the visible date range backgrounds. - - CalendarView.RegisterPropertyChangedCallback(CalendarView.DisplayModeProperty, (s, e) => UpdateVisibleDateRangeBackgrounds()); - } - - private void InternalCalendarViewSelectionChanged(CalendarView sender, CalendarViewSelectedDatesChangedEventArgs args) - { - if (args.AddedDates?.Count > 0) + if (calendarDayItem.Date.Date == DateTime.Today.Date) { - var clickedDate = args.AddedDates[0].Date; - SetInnerDisplayDate(clickedDate); - - var clickArgs = new CalendarViewDayClickedEventArgs(clickedDate); - DateClickedCommand?.Execute(clickArgs); + border.Background = new SolidColorBrush(TodayBackgroundColor); } - - // Reset selection, we don't show selected dates but react to them. - CalendarView.SelectedDates.Clear(); - } - - private static void OnPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is WinoCalendarView control) + else if (calendarDayItem.Date.Date >= HighlightedDateRange.StartDate.Date && calendarDayItem.Date.Date < HighlightedDateRange.EndDate.Date) { - control.UpdateVisibleDateRangeBackgrounds(); + border.Background = VisibleDateBackground; } - } - - private void SetInnerDisplayDate(DateTime dateTime) => CalendarView?.SetDisplayDate(dateTime); - - // Changing selected dates will trigger the selection changed event. - // It will behave like user clicked the date. - public void GoToDay(DateTime dateTime) => CalendarView.SelectedDates.Add(dateTime); - - private static void OnHighlightedDateRangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is WinoCalendarView control) + else { - control.SetInnerDisplayDate(control.HighlightedDateRange.StartDate); - control.UpdateVisibleDateRangeBackgrounds(); - } - } - - public void UpdateVisibleDateRangeBackgrounds() - { - if (HighlightedDateRange == null || VisibleDateBackground == null || TodayBackgroundColor == null || CalendarView == null) return; - - var markDateCalendarDayItems = WinoVisualTreeHelper.FindDescendants(CalendarView); - - foreach (var calendarDayItem in markDateCalendarDayItems) - { - var border = WinoVisualTreeHelper.GetChildObject(calendarDayItem, PART_DayViewItemBorder); - - if (border == null) return; - - if (calendarDayItem.Date.Date == DateTime.Today.Date) - { - border.Background = new SolidColorBrush(TodayBackgroundColor); - } - else if (calendarDayItem.Date.Date >= HighlightedDateRange.StartDate.Date && calendarDayItem.Date.Date < HighlightedDateRange.EndDate.Date) - { - border.Background = VisibleDateBackground; - } - else - { - border.Background = null; - } + border.Background = null; } } } diff --git a/Wino.Calendar/Controls/WinoDayTimelineCanvas.cs b/Wino.Calendar/Controls/WinoDayTimelineCanvas.cs index e77088fe..66b3bdd8 100644 --- a/Wino.Calendar/Controls/WinoDayTimelineCanvas.cs +++ b/Wino.Calendar/Controls/WinoDayTimelineCanvas.cs @@ -10,269 +10,268 @@ using Windows.UI.Xaml.Media; using Wino.Calendar.Args; using Wino.Core.Domain.Models.Calendar; -namespace Wino.Calendar.Controls +namespace Wino.Calendar.Controls; + +public partial class WinoDayTimelineCanvas : Control, IDisposable { - public class WinoDayTimelineCanvas : Control, IDisposable + public event EventHandler TimelineCellSelected; + public event EventHandler TimelineCellUnselected; + + private const string PART_InternalCanvas = nameof(PART_InternalCanvas); + private CanvasControl Canvas; + + public static readonly DependencyProperty RenderOptionsProperty = DependencyProperty.Register(nameof(RenderOptions), typeof(CalendarRenderOptions), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged))); + public static readonly DependencyProperty SeperatorColorProperty = DependencyProperty.Register(nameof(SeperatorColor), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged))); + public static readonly DependencyProperty HalfHourSeperatorColorProperty = DependencyProperty.Register(nameof(HalfHourSeperatorColor), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged))); + public static readonly DependencyProperty SelectedCellBackgroundBrushProperty = DependencyProperty.Register(nameof(SelectedCellBackgroundBrush), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged))); + public static readonly DependencyProperty WorkingHourCellBackgroundColorProperty = DependencyProperty.Register(nameof(WorkingHourCellBackgroundColor), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged))); + public static readonly DependencyProperty SelectedDateTimeProperty = DependencyProperty.Register(nameof(SelectedDateTime), typeof(DateTime?), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedDateTimeChanged))); + public static readonly DependencyProperty PositionerUIElementProperty = DependencyProperty.Register(nameof(PositionerUIElement), typeof(UIElement), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null)); + + public UIElement PositionerUIElement { - public event EventHandler TimelineCellSelected; - public event EventHandler TimelineCellUnselected; + get { return (UIElement)GetValue(PositionerUIElementProperty); } + set { SetValue(PositionerUIElementProperty, value); } + } - private const string PART_InternalCanvas = nameof(PART_InternalCanvas); - private CanvasControl Canvas; + public CalendarRenderOptions RenderOptions + { + get { return (CalendarRenderOptions)GetValue(RenderOptionsProperty); } + set { SetValue(RenderOptionsProperty, value); } + } - public static readonly DependencyProperty RenderOptionsProperty = DependencyProperty.Register(nameof(RenderOptions), typeof(CalendarRenderOptions), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged))); - public static readonly DependencyProperty SeperatorColorProperty = DependencyProperty.Register(nameof(SeperatorColor), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged))); - public static readonly DependencyProperty HalfHourSeperatorColorProperty = DependencyProperty.Register(nameof(HalfHourSeperatorColor), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged))); - public static readonly DependencyProperty SelectedCellBackgroundBrushProperty = DependencyProperty.Register(nameof(SelectedCellBackgroundBrush), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged))); - public static readonly DependencyProperty WorkingHourCellBackgroundColorProperty = DependencyProperty.Register(nameof(WorkingHourCellBackgroundColor), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged))); - public static readonly DependencyProperty SelectedDateTimeProperty = DependencyProperty.Register(nameof(SelectedDateTime), typeof(DateTime?), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedDateTimeChanged))); - public static readonly DependencyProperty PositionerUIElementProperty = DependencyProperty.Register(nameof(PositionerUIElement), typeof(UIElement), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null)); + public SolidColorBrush HalfHourSeperatorColor + { + get { return (SolidColorBrush)GetValue(HalfHourSeperatorColorProperty); } + set { SetValue(HalfHourSeperatorColorProperty, value); } + } - public UIElement PositionerUIElement + public SolidColorBrush SeperatorColor + { + get { return (SolidColorBrush)GetValue(SeperatorColorProperty); } + set { SetValue(SeperatorColorProperty, value); } + } + + public SolidColorBrush WorkingHourCellBackgroundColor + { + get { return (SolidColorBrush)GetValue(WorkingHourCellBackgroundColorProperty); } + set { SetValue(WorkingHourCellBackgroundColorProperty, value); } + } + + public SolidColorBrush SelectedCellBackgroundBrush + { + get { return (SolidColorBrush)GetValue(SelectedCellBackgroundBrushProperty); } + set { SetValue(SelectedCellBackgroundBrushProperty, value); } + } + + public DateTime? SelectedDateTime + { + get { return (DateTime?)GetValue(SelectedDateTimeProperty); } + set { SetValue(SelectedDateTimeProperty, value); } + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + Canvas = GetTemplateChild(PART_InternalCanvas) as CanvasControl; + + // TODO: These will leak. Dispose them properly when needed. + Canvas.Draw += OnCanvasDraw; + Canvas.PointerPressed += OnCanvasPointerPressed; + + ForceDraw(); + } + + private static void OnSelectedDateTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is WinoDayTimelineCanvas control) { - get { return (UIElement)GetValue(PositionerUIElementProperty); } - set { SetValue(PositionerUIElementProperty, value); } - } - - public CalendarRenderOptions RenderOptions - { - get { return (CalendarRenderOptions)GetValue(RenderOptionsProperty); } - set { SetValue(RenderOptionsProperty, value); } - } - - public SolidColorBrush HalfHourSeperatorColor - { - get { return (SolidColorBrush)GetValue(HalfHourSeperatorColorProperty); } - set { SetValue(HalfHourSeperatorColorProperty, value); } - } - - public SolidColorBrush SeperatorColor - { - get { return (SolidColorBrush)GetValue(SeperatorColorProperty); } - set { SetValue(SeperatorColorProperty, value); } - } - - public SolidColorBrush WorkingHourCellBackgroundColor - { - get { return (SolidColorBrush)GetValue(WorkingHourCellBackgroundColorProperty); } - set { SetValue(WorkingHourCellBackgroundColorProperty, value); } - } - - public SolidColorBrush SelectedCellBackgroundBrush - { - get { return (SolidColorBrush)GetValue(SelectedCellBackgroundBrushProperty); } - set { SetValue(SelectedCellBackgroundBrushProperty, value); } - } - - public DateTime? SelectedDateTime - { - get { return (DateTime?)GetValue(SelectedDateTimeProperty); } - set { SetValue(SelectedDateTimeProperty, value); } - } - - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - Canvas = GetTemplateChild(PART_InternalCanvas) as CanvasControl; - - // TODO: These will leak. Dispose them properly when needed. - Canvas.Draw += OnCanvasDraw; - Canvas.PointerPressed += OnCanvasPointerPressed; - - ForceDraw(); - } - - private static void OnSelectedDateTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is WinoDayTimelineCanvas control) + if (e.OldValue != null && e.NewValue == null) { - if (e.OldValue != null && e.NewValue == null) - { - control.RaiseCellUnselected(); - } - - control.ForceDraw(); - } - } - - private void RaiseCellUnselected() - { - TimelineCellUnselected?.Invoke(this, new TimelineCellUnselectedArgs()); - } - - private void OnCanvasPointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e) - { - if (RenderOptions == null) return; - - var hourHeight = RenderOptions.CalendarSettings.HourHeight; - - // When users click to cell we need to find the day, hour and minutes (first 30 minutes or second 30 minutes) that it represents on the timeline. - - PointerPoint positionerRootPoint = e.GetCurrentPoint(PositionerUIElement); - PointerPoint canvasPointerPoint = e.GetCurrentPoint(Canvas); - - Point touchPoint = canvasPointerPoint.Position; - - var singleDayWidth = (Canvas.ActualWidth / RenderOptions.TotalDayCount); - - int day = (int)(touchPoint.X / singleDayWidth); - int hour = (int)(touchPoint.Y / hourHeight); - - bool isSecondHalf = touchPoint.Y % hourHeight > (hourHeight / 2); - - var diffX = positionerRootPoint.Position.X - touchPoint.X; - var diffY = positionerRootPoint.Position.Y - touchPoint.Y; - - var cellStartRelativePositionX = diffX + (day * singleDayWidth); - var cellEndRelativePositionX = cellStartRelativePositionX + singleDayWidth; - - var cellStartRelativePositionY = diffY + (hour * hourHeight) + (isSecondHalf ? hourHeight / 2 : 0); - var cellEndRelativePositionY = cellStartRelativePositionY + (isSecondHalf ? (hourHeight / 2) : hourHeight); - - var cellSize = new Size(cellEndRelativePositionX - cellStartRelativePositionX, hourHeight / 2); - var positionerPoint = new Point(cellStartRelativePositionX, cellStartRelativePositionY); - - var clickedDateTime = RenderOptions.DateRange.StartDate.AddDays(day).AddHours(hour).AddMinutes(isSecondHalf ? 30 : 0); - - // If there is already a selected date, in order to mimic the popup behavior, we need to dismiss the previous selection first. - // Next click will be a new selection. - - // Raise the events directly here instead of DP to not lose pointer position. - if (clickedDateTime == SelectedDateTime || SelectedDateTime != null) - { - SelectedDateTime = null; - } - else - { - SelectedDateTime = clickedDateTime; - TimelineCellSelected?.Invoke(this, new TimelineCellSelectedArgs(clickedDateTime, touchPoint, positionerPoint, cellSize)); + control.RaiseCellUnselected(); } - Debug.WriteLine($"Clicked: {clickedDateTime}"); - } - - public WinoDayTimelineCanvas() - { - DefaultStyleKey = typeof(WinoDayTimelineCanvas); - } - - private static void OnRenderingPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is WinoDayTimelineCanvas control) - { - control.ForceDraw(); - } - } - - private void ForceDraw() => Canvas?.Invalidate(); - - private bool CanDrawTimeline() - { - return RenderOptions != null - && Canvas != null - && Canvas.ReadyToDraw - && WorkingHourCellBackgroundColor != null - && SeperatorColor != null - && HalfHourSeperatorColor != null - && SelectedCellBackgroundBrush != null; - } - - private void OnCanvasDraw(CanvasControl sender, CanvasDrawEventArgs args) - { - if (!CanDrawTimeline()) return; - - int hours = 24; - - double canvasWidth = Canvas.ActualWidth; - double canvasHeight = Canvas.ActualHeight; - - if (canvasWidth == 0 || canvasHeight == 0) return; - - // Calculate the width of each rectangle (1 day column) - // Equal distribution of the whole width. - double rectWidth = canvasWidth / RenderOptions.TotalDayCount; - - // Calculate the height of each rectangle (1 hour row) - double rectHeight = RenderOptions.CalendarSettings.HourHeight; - - // Define stroke and fill colors - var strokeColor = SeperatorColor.Color; - float strokeThickness = 0.5f; - - for (int day = 0; day < RenderOptions.TotalDayCount; day++) - { - var currentDay = RenderOptions.DateRange.StartDate.AddDays(day); - - bool isWorkingDay = RenderOptions.CalendarSettings.WorkingDays.Contains(currentDay.DayOfWeek); - - // Loop through each hour (rows) - for (int hour = 0; hour < hours; hour++) - { - var renderTime = TimeSpan.FromHours(hour); - - var representingDateTime = currentDay.AddHours(hour); - - // Calculate the position and size of the rectangle - double x = day * rectWidth; - double y = hour * rectHeight; - - var rectangle = new Rect(x, y, rectWidth, rectHeight); - - // Draw the rectangle border. - // This is the main rectangle. - args.DrawingSession.DrawRectangle(rectangle, strokeColor, strokeThickness); - - // Fill another rectangle with the working hour background color - // This rectangle must be placed with -1 margin to prevent invisible borders of the main rectangle. - if (isWorkingDay && renderTime >= RenderOptions.CalendarSettings.WorkingHourStart && renderTime <= RenderOptions.CalendarSettings.WorkingHourEnd) - { - var backgroundRectangle = new Rect(x + 1, y + 1, rectWidth - 1, rectHeight - 1); - - args.DrawingSession.DrawRectangle(backgroundRectangle, strokeColor, strokeThickness); - args.DrawingSession.FillRectangle(backgroundRectangle, WorkingHourCellBackgroundColor.Color); - } - - // Draw a line in the center of the rectangle for representing half hours. - double lineY = y + rectHeight / 2; - - args.DrawingSession.DrawLine((float)x, (float)lineY, (float)(x + rectWidth), (float)lineY, HalfHourSeperatorColor.Color, strokeThickness, new CanvasStrokeStyle() - { - DashStyle = CanvasDashStyle.Dot - }); - } - - // Draw selected item background color for the date if possible. - if (SelectedDateTime != null) - { - var selectedDateTime = SelectedDateTime.Value; - if (selectedDateTime.Date == currentDay.Date) - { - var selectionRectHeight = rectHeight / 2; - var selectedY = selectedDateTime.Hour * rectHeight + (selectedDateTime.Minute / 60) * rectHeight; - - // Second half of the hour is selected. - if (selectedDateTime.TimeOfDay.Minutes == 30) - { - selectedY += rectHeight / 2; - } - - var selectedRectangle = new Rect(day * rectWidth, selectedY, rectWidth, selectionRectHeight); - args.DrawingSession.FillRectangle(selectedRectangle, SelectedCellBackgroundBrush.Color); - } - } - } - } - - public void Dispose() - { - if (Canvas == null) return; - - Canvas.Draw -= OnCanvasDraw; - Canvas.PointerPressed -= OnCanvasPointerPressed; - Canvas.RemoveFromVisualTree(); - - Canvas = null; + control.ForceDraw(); } } + + private void RaiseCellUnselected() + { + TimelineCellUnselected?.Invoke(this, new TimelineCellUnselectedArgs()); + } + + private void OnCanvasPointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e) + { + if (RenderOptions == null) return; + + var hourHeight = RenderOptions.CalendarSettings.HourHeight; + + // When users click to cell we need to find the day, hour and minutes (first 30 minutes or second 30 minutes) that it represents on the timeline. + + PointerPoint positionerRootPoint = e.GetCurrentPoint(PositionerUIElement); + PointerPoint canvasPointerPoint = e.GetCurrentPoint(Canvas); + + Point touchPoint = canvasPointerPoint.Position; + + var singleDayWidth = (Canvas.ActualWidth / RenderOptions.TotalDayCount); + + int day = (int)(touchPoint.X / singleDayWidth); + int hour = (int)(touchPoint.Y / hourHeight); + + bool isSecondHalf = touchPoint.Y % hourHeight > (hourHeight / 2); + + var diffX = positionerRootPoint.Position.X - touchPoint.X; + var diffY = positionerRootPoint.Position.Y - touchPoint.Y; + + var cellStartRelativePositionX = diffX + (day * singleDayWidth); + var cellEndRelativePositionX = cellStartRelativePositionX + singleDayWidth; + + var cellStartRelativePositionY = diffY + (hour * hourHeight) + (isSecondHalf ? hourHeight / 2 : 0); + var cellEndRelativePositionY = cellStartRelativePositionY + (isSecondHalf ? (hourHeight / 2) : hourHeight); + + var cellSize = new Size(cellEndRelativePositionX - cellStartRelativePositionX, hourHeight / 2); + var positionerPoint = new Point(cellStartRelativePositionX, cellStartRelativePositionY); + + var clickedDateTime = RenderOptions.DateRange.StartDate.AddDays(day).AddHours(hour).AddMinutes(isSecondHalf ? 30 : 0); + + // If there is already a selected date, in order to mimic the popup behavior, we need to dismiss the previous selection first. + // Next click will be a new selection. + + // Raise the events directly here instead of DP to not lose pointer position. + if (clickedDateTime == SelectedDateTime || SelectedDateTime != null) + { + SelectedDateTime = null; + } + else + { + SelectedDateTime = clickedDateTime; + TimelineCellSelected?.Invoke(this, new TimelineCellSelectedArgs(clickedDateTime, touchPoint, positionerPoint, cellSize)); + } + + Debug.WriteLine($"Clicked: {clickedDateTime}"); + } + + public WinoDayTimelineCanvas() + { + DefaultStyleKey = typeof(WinoDayTimelineCanvas); + } + + private static void OnRenderingPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is WinoDayTimelineCanvas control) + { + control.ForceDraw(); + } + } + + private void ForceDraw() => Canvas?.Invalidate(); + + private bool CanDrawTimeline() + { + return RenderOptions != null + && Canvas != null + && Canvas.ReadyToDraw + && WorkingHourCellBackgroundColor != null + && SeperatorColor != null + && HalfHourSeperatorColor != null + && SelectedCellBackgroundBrush != null; + } + + private void OnCanvasDraw(CanvasControl sender, CanvasDrawEventArgs args) + { + if (!CanDrawTimeline()) return; + + int hours = 24; + + double canvasWidth = Canvas.ActualWidth; + double canvasHeight = Canvas.ActualHeight; + + if (canvasWidth == 0 || canvasHeight == 0) return; + + // Calculate the width of each rectangle (1 day column) + // Equal distribution of the whole width. + double rectWidth = canvasWidth / RenderOptions.TotalDayCount; + + // Calculate the height of each rectangle (1 hour row) + double rectHeight = RenderOptions.CalendarSettings.HourHeight; + + // Define stroke and fill colors + var strokeColor = SeperatorColor.Color; + float strokeThickness = 0.5f; + + for (int day = 0; day < RenderOptions.TotalDayCount; day++) + { + var currentDay = RenderOptions.DateRange.StartDate.AddDays(day); + + bool isWorkingDay = RenderOptions.CalendarSettings.WorkingDays.Contains(currentDay.DayOfWeek); + + // Loop through each hour (rows) + for (int hour = 0; hour < hours; hour++) + { + var renderTime = TimeSpan.FromHours(hour); + + var representingDateTime = currentDay.AddHours(hour); + + // Calculate the position and size of the rectangle + double x = day * rectWidth; + double y = hour * rectHeight; + + var rectangle = new Rect(x, y, rectWidth, rectHeight); + + // Draw the rectangle border. + // This is the main rectangle. + args.DrawingSession.DrawRectangle(rectangle, strokeColor, strokeThickness); + + // Fill another rectangle with the working hour background color + // This rectangle must be placed with -1 margin to prevent invisible borders of the main rectangle. + if (isWorkingDay && renderTime >= RenderOptions.CalendarSettings.WorkingHourStart && renderTime <= RenderOptions.CalendarSettings.WorkingHourEnd) + { + var backgroundRectangle = new Rect(x + 1, y + 1, rectWidth - 1, rectHeight - 1); + + args.DrawingSession.DrawRectangle(backgroundRectangle, strokeColor, strokeThickness); + args.DrawingSession.FillRectangle(backgroundRectangle, WorkingHourCellBackgroundColor.Color); + } + + // Draw a line in the center of the rectangle for representing half hours. + double lineY = y + rectHeight / 2; + + args.DrawingSession.DrawLine((float)x, (float)lineY, (float)(x + rectWidth), (float)lineY, HalfHourSeperatorColor.Color, strokeThickness, new CanvasStrokeStyle() + { + DashStyle = CanvasDashStyle.Dot + }); + } + + // Draw selected item background color for the date if possible. + if (SelectedDateTime != null) + { + var selectedDateTime = SelectedDateTime.Value; + if (selectedDateTime.Date == currentDay.Date) + { + var selectionRectHeight = rectHeight / 2; + var selectedY = selectedDateTime.Hour * rectHeight + (selectedDateTime.Minute / 60) * rectHeight; + + // Second half of the hour is selected. + if (selectedDateTime.TimeOfDay.Minutes == 30) + { + selectedY += rectHeight / 2; + } + + var selectedRectangle = new Rect(day * rectWidth, selectedY, rectWidth, selectionRectHeight); + args.DrawingSession.FillRectangle(selectedRectangle, SelectedCellBackgroundBrush.Color); + } + } + } + } + + public void Dispose() + { + if (Canvas == null) return; + + Canvas.Draw -= OnCanvasDraw; + Canvas.PointerPressed -= OnCanvasPointerPressed; + Canvas.RemoveFromVisualTree(); + + Canvas = null; + } } diff --git a/Wino.Calendar/Helpers/CalendarXamlHelpers.cs b/Wino.Calendar/Helpers/CalendarXamlHelpers.cs index 5133d40d..cdc7c4d8 100644 --- a/Wino.Calendar/Helpers/CalendarXamlHelpers.cs +++ b/Wino.Calendar/Helpers/CalendarXamlHelpers.cs @@ -10,98 +10,97 @@ using Wino.Core.Domain.Enums; using Wino.Core.Domain.Models.Calendar; using Wino.Helpers; -namespace Wino.Calendar.Helpers +namespace Wino.Calendar.Helpers; + +public static class CalendarXamlHelpers { - public static class CalendarXamlHelpers + public static CalendarItemViewModel GetFirstAllDayEvent(CalendarEventCollection collection) + => (CalendarItemViewModel)collection.AllDayEvents.FirstOrDefault(); + + /// + /// Returns full date + duration info in Event Details page details title. + /// + public static string GetEventDetailsDateString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings) { - public static CalendarItemViewModel GetFirstAllDayEvent(CalendarEventCollection collection) - => (CalendarItemViewModel)collection.AllDayEvents.FirstOrDefault(); + if (calendarItemViewModel == null || settings == null) return string.Empty; - /// - /// Returns full date + duration info in Event Details page details title. - /// - public static string GetEventDetailsDateString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings) + var start = calendarItemViewModel.Period.Start; + var end = calendarItemViewModel.Period.End; + + string timeFormat = settings.DayHeaderDisplayType == DayHeaderDisplayType.TwelveHour ? "h:mm tt" : "HH:mm"; + string dateFormat = settings.DayHeaderDisplayType == DayHeaderDisplayType.TwelveHour ? "dddd, dd MMMM h:mm tt" : "dddd, dd MMMM HH:mm"; + + if (calendarItemViewModel.IsMultiDayEvent) { - if (calendarItemViewModel == null || settings == null) return string.Empty; - - var start = calendarItemViewModel.Period.Start; - var end = calendarItemViewModel.Period.End; - - string timeFormat = settings.DayHeaderDisplayType == DayHeaderDisplayType.TwelveHour ? "h:mm tt" : "HH:mm"; - string dateFormat = settings.DayHeaderDisplayType == DayHeaderDisplayType.TwelveHour ? "dddd, dd MMMM h:mm tt" : "dddd, dd MMMM HH:mm"; - - if (calendarItemViewModel.IsMultiDayEvent) - { - return $"{start.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)} - {end.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)}"; - } - else - { - return $"{start.ToString(dateFormat, settings.CultureInfo)} - {end.ToString(timeFormat, settings.CultureInfo)}"; - } + return $"{start.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)} - {end.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)}"; } - - public static string GetRecurrenceString(CalendarItemViewModel calendarItemViewModel) + else { - if (calendarItemViewModel == null || !calendarItemViewModel.IsRecurringChild) return string.Empty; - - // Parse recurrence rules - var calendarEvent = new CalendarEvent - { - Start = new CalDateTime(calendarItemViewModel.StartDate), - End = new CalDateTime(calendarItemViewModel.EndDate), - }; - - var recurrenceLines = Regex.Split(calendarItemViewModel.CalendarItem.Recurrence, Constants.CalendarEventRecurrenceRuleSeperator); - - foreach (var line in recurrenceLines) - { - calendarEvent.RecurrenceRules.Add(new RecurrencePattern(line)); - } - - if (calendarEvent.RecurrenceRules == null || !calendarEvent.RecurrenceRules.Any()) - { - return "No recurrence pattern."; - } - - var recurrenceRule = calendarEvent.RecurrenceRules.First(); - var daysOfWeek = string.Join(", ", recurrenceRule.ByDay.Select(day => day.DayOfWeek.ToString())); - string timeZone = calendarEvent.DtStart.TzId ?? "UTC"; - - return $"Every {daysOfWeek}, effective {calendarEvent.DtStart.Value.ToShortDateString()} " + - $"from {calendarEvent.DtStart.Value.ToShortTimeString()} to {calendarEvent.DtEnd.Value.ToShortTimeString()} " + - $"{timeZone}."; - } - - public static string GetDetailsPopupDurationString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings) - { - if (calendarItemViewModel == null || settings == null) return string.Empty; - - // Single event in a day. - if (!calendarItemViewModel.IsAllDayEvent && !calendarItemViewModel.IsMultiDayEvent) - { - return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} {settings.GetTimeString(calendarItemViewModel.Period.Duration)}"; - } - else if (calendarItemViewModel.IsMultiDayEvent) - { - return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} - {calendarItemViewModel.Period.End.ToString("d", settings.CultureInfo)}"; - } - else - { - // All day event. - return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} ({Translator.CalendarItemAllDay})"; - } - } - - public static PopupPlacementMode GetDesiredPlacementModeForEventsDetailsPopup( - CalendarItemViewModel calendarItemViewModel, - CalendarDisplayType calendarDisplayType) - { - if (calendarItemViewModel == null) return PopupPlacementMode.Auto; - - // All and/or multi day events always go to the top of the screen. - if (calendarItemViewModel.IsAllDayEvent || calendarItemViewModel.IsMultiDayEvent) return PopupPlacementMode.Bottom; - - return XamlHelpers.GetPlaccementModeForCalendarType(calendarDisplayType); + return $"{start.ToString(dateFormat, settings.CultureInfo)} - {end.ToString(timeFormat, settings.CultureInfo)}"; } } + + public static string GetRecurrenceString(CalendarItemViewModel calendarItemViewModel) + { + if (calendarItemViewModel == null || !calendarItemViewModel.IsRecurringChild) return string.Empty; + + // Parse recurrence rules + var calendarEvent = new CalendarEvent + { + Start = new CalDateTime(calendarItemViewModel.StartDate), + End = new CalDateTime(calendarItemViewModel.EndDate), + }; + + var recurrenceLines = Regex.Split(calendarItemViewModel.CalendarItem.Recurrence, Constants.CalendarEventRecurrenceRuleSeperator); + + foreach (var line in recurrenceLines) + { + calendarEvent.RecurrenceRules.Add(new RecurrencePattern(line)); + } + + if (calendarEvent.RecurrenceRules == null || !calendarEvent.RecurrenceRules.Any()) + { + return "No recurrence pattern."; + } + + var recurrenceRule = calendarEvent.RecurrenceRules.First(); + var daysOfWeek = string.Join(", ", recurrenceRule.ByDay.Select(day => day.DayOfWeek.ToString())); + string timeZone = calendarEvent.DtStart.TzId ?? "UTC"; + + return $"Every {daysOfWeek}, effective {calendarEvent.DtStart.Value.ToShortDateString()} " + + $"from {calendarEvent.DtStart.Value.ToShortTimeString()} to {calendarEvent.DtEnd.Value.ToShortTimeString()} " + + $"{timeZone}."; + } + + public static string GetDetailsPopupDurationString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings) + { + if (calendarItemViewModel == null || settings == null) return string.Empty; + + // Single event in a day. + if (!calendarItemViewModel.IsAllDayEvent && !calendarItemViewModel.IsMultiDayEvent) + { + return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} {settings.GetTimeString(calendarItemViewModel.Period.Duration)}"; + } + else if (calendarItemViewModel.IsMultiDayEvent) + { + return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} - {calendarItemViewModel.Period.End.ToString("d", settings.CultureInfo)}"; + } + else + { + // All day event. + return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} ({Translator.CalendarItemAllDay})"; + } + } + + public static PopupPlacementMode GetDesiredPlacementModeForEventsDetailsPopup( + CalendarItemViewModel calendarItemViewModel, + CalendarDisplayType calendarDisplayType) + { + if (calendarItemViewModel == null) return PopupPlacementMode.Auto; + + // All and/or multi day events always go to the top of the screen. + if (calendarItemViewModel.IsAllDayEvent || calendarItemViewModel.IsMultiDayEvent) return PopupPlacementMode.Bottom; + + return XamlHelpers.GetPlaccementModeForCalendarType(calendarDisplayType); + } } diff --git a/Wino.Calendar/MainPage.xaml.cs b/Wino.Calendar/MainPage.xaml.cs index 281f422f..7fb5bdf3 100644 --- a/Wino.Calendar/MainPage.xaml.cs +++ b/Wino.Calendar/MainPage.xaml.cs @@ -15,16 +15,15 @@ using Windows.UI.Xaml.Navigation; // The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409 -namespace Wino.Calendar +namespace Wino.Calendar; + +/// +/// An empty page that can be used on its own or navigated to within a Frame. +/// +public sealed partial class MainPage : Page { - /// - /// An empty page that can be used on its own or navigated to within a Frame. - /// - public sealed partial class MainPage : Page + public MainPage() { - public MainPage() - { - this.InitializeComponent(); - } + this.InitializeComponent(); } } diff --git a/Wino.Calendar/Models/CalendarItemMeasurement.cs b/Wino.Calendar/Models/CalendarItemMeasurement.cs index b9a30d5b..dbb8cf9b 100644 --- a/Wino.Calendar/Models/CalendarItemMeasurement.cs +++ b/Wino.Calendar/Models/CalendarItemMeasurement.cs @@ -1,17 +1,16 @@ -namespace Wino.Calendar.Models +namespace Wino.Calendar.Models; + +public struct CalendarItemMeasurement { - public struct CalendarItemMeasurement + // Where to start? + public double Left { get; set; } + + // Extend until where? + public double Right { get; set; } + + public CalendarItemMeasurement(double left, double right) { - // Where to start? - public double Left { get; set; } - - // Extend until where? - public double Right { get; set; } - - public CalendarItemMeasurement(double left, double right) - { - Left = left; - Right = right; - } + Left = left; + Right = right; } } diff --git a/Wino.Calendar/Selectors/CustomAreaCalendarItemSelector.cs b/Wino.Calendar/Selectors/CustomAreaCalendarItemSelector.cs index b85019c3..253a82a6 100644 --- a/Wino.Calendar/Selectors/CustomAreaCalendarItemSelector.cs +++ b/Wino.Calendar/Selectors/CustomAreaCalendarItemSelector.cs @@ -2,21 +2,20 @@ using Windows.UI.Xaml.Controls; using Wino.Calendar.ViewModels.Data; -namespace Wino.Calendar.Selectors +namespace Wino.Calendar.Selectors; + +public partial class CustomAreaCalendarItemSelector : DataTemplateSelector { - public class CustomAreaCalendarItemSelector : DataTemplateSelector + public DataTemplate AllDayTemplate { get; set; } + public DataTemplate MultiDayTemplate { get; set; } + + protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) { - public DataTemplate AllDayTemplate { get; set; } - public DataTemplate MultiDayTemplate { get; set; } - - protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) + if (item is CalendarItemViewModel calendarItemViewModel) { - if (item is CalendarItemViewModel calendarItemViewModel) - { - return calendarItemViewModel.IsMultiDayEvent ? MultiDayTemplate : AllDayTemplate; - } - - return base.SelectTemplateCore(item, container); + return calendarItemViewModel.IsMultiDayEvent ? MultiDayTemplate : AllDayTemplate; } + + return base.SelectTemplateCore(item, container); } } diff --git a/Wino.Calendar/Selectors/WinoCalendarItemTemplateSelector.cs b/Wino.Calendar/Selectors/WinoCalendarItemTemplateSelector.cs index 974559af..0474a188 100644 --- a/Wino.Calendar/Selectors/WinoCalendarItemTemplateSelector.cs +++ b/Wino.Calendar/Selectors/WinoCalendarItemTemplateSelector.cs @@ -2,33 +2,32 @@ using Windows.UI.Xaml.Controls; using Wino.Core.Domain.Enums; -namespace Wino.Calendar.Selectors +namespace Wino.Calendar.Selectors; + +public partial class WinoCalendarItemTemplateSelector : DataTemplateSelector { - public class WinoCalendarItemTemplateSelector : DataTemplateSelector + public CalendarDisplayType DisplayType { get; set; } + + public DataTemplate DayWeekWorkWeekTemplate { get; set; } + public DataTemplate MonthlyTemplate { get; set; } + + + protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) { - public CalendarDisplayType DisplayType { get; set; } - - public DataTemplate DayWeekWorkWeekTemplate { get; set; } - public DataTemplate MonthlyTemplate { get; set; } - - - protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) + switch (DisplayType) { - switch (DisplayType) - { - case CalendarDisplayType.Day: - case CalendarDisplayType.Week: - case CalendarDisplayType.WorkWeek: - return DayWeekWorkWeekTemplate; - case CalendarDisplayType.Month: - return MonthlyTemplate; - case CalendarDisplayType.Year: - break; - default: - break; - } - - return base.SelectTemplateCore(item, container); + case CalendarDisplayType.Day: + case CalendarDisplayType.Week: + case CalendarDisplayType.WorkWeek: + return DayWeekWorkWeekTemplate; + case CalendarDisplayType.Month: + return MonthlyTemplate; + case CalendarDisplayType.Year: + break; + default: + break; } + + return base.SelectTemplateCore(item, container); } } diff --git a/Wino.Calendar/Services/AccountCalendarStateService.cs b/Wino.Calendar/Services/AccountCalendarStateService.cs index cf4300a3..cf58b2f3 100644 --- a/Wino.Calendar/Services/AccountCalendarStateService.cs +++ b/Wino.Calendar/Services/AccountCalendarStateService.cs @@ -7,108 +7,107 @@ using Wino.Calendar.ViewModels.Data; using Wino.Calendar.ViewModels.Interfaces; using Wino.Core.Domain.Entities.Shared; -namespace Wino.Calendar.Services +namespace Wino.Calendar.Services; + +/// +/// Encapsulated state manager for collectively managing the state of account calendars. +/// Callers must react to the events to update their state only from this service. +/// +public partial class AccountCalendarStateService : ObservableObject, IAccountCalendarStateService { - /// - /// Encapsulated state manager for collectively managing the state of account calendars. - /// Callers must react to the events to update their state only from this service. - /// - public partial class AccountCalendarStateService : ObservableObject, IAccountCalendarStateService + public event EventHandler CollectiveAccountGroupSelectionStateChanged; + public event EventHandler AccountCalendarSelectionStateChanged; + + [ObservableProperty] + public partial ReadOnlyObservableCollection GroupedAccountCalendars { get; set; } + + private ObservableCollection _internalGroupedAccountCalendars = new ObservableCollection(); + + public IEnumerable ActiveCalendars { - public event EventHandler CollectiveAccountGroupSelectionStateChanged; - public event EventHandler AccountCalendarSelectionStateChanged; - - [ObservableProperty] - private ReadOnlyObservableCollection groupedAccountCalendars; - - private ObservableCollection _internalGroupedAccountCalendars = new ObservableCollection(); - - public IEnumerable ActiveCalendars + get { - get - { - return GroupedAccountCalendars - .SelectMany(a => a.AccountCalendars) - .Where(b => b.IsChecked); - } + return GroupedAccountCalendars + .SelectMany(a => a.AccountCalendars) + .Where(b => b.IsChecked); } + } - public IEnumerable> GroupedAccountCalendarsEnumerable + public IEnumerable> GroupedAccountCalendarsEnumerable + { + get { - get - { - return GroupedAccountCalendars - .Select(a => a.AccountCalendars) - .SelectMany(b => b) - .GroupBy(c => c.Account); - } + return GroupedAccountCalendars + .Select(a => a.AccountCalendars) + .SelectMany(b => b) + .GroupBy(c => c.Account); } + } - public AccountCalendarStateService() + public AccountCalendarStateService() + { + GroupedAccountCalendars = new ReadOnlyObservableCollection(_internalGroupedAccountCalendars); + } + + private void SingleGroupCalendarCollectiveStateChanged(object sender, EventArgs e) + => CollectiveAccountGroupSelectionStateChanged?.Invoke(this, sender as GroupedAccountCalendarViewModel); + + private void SingleCalendarSelectionStateChanged(object sender, AccountCalendarViewModel e) + => AccountCalendarSelectionStateChanged?.Invoke(this, e); + + public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar) + { + groupedAccountCalendar.CalendarSelectionStateChanged += SingleCalendarSelectionStateChanged; + groupedAccountCalendar.CollectiveSelectionStateChanged += SingleGroupCalendarCollectiveStateChanged; + + _internalGroupedAccountCalendars.Add(groupedAccountCalendar); + } + + public void RemoveGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar) + { + groupedAccountCalendar.CalendarSelectionStateChanged -= SingleCalendarSelectionStateChanged; + groupedAccountCalendar.CollectiveSelectionStateChanged -= SingleGroupCalendarCollectiveStateChanged; + + _internalGroupedAccountCalendars.Remove(groupedAccountCalendar); + } + + public void ClearGroupedAccountCalendar() + { + foreach (var groupedAccountCalendar in _internalGroupedAccountCalendars) { - GroupedAccountCalendars = new ReadOnlyObservableCollection(_internalGroupedAccountCalendars); + RemoveGroupedAccountCalendar(groupedAccountCalendar); } + } - private void SingleGroupCalendarCollectiveStateChanged(object sender, EventArgs e) - => CollectiveAccountGroupSelectionStateChanged?.Invoke(this, sender as GroupedAccountCalendarViewModel); + public void AddAccountCalendar(AccountCalendarViewModel accountCalendar) + { + // Find the group that this calendar belongs to. + var group = _internalGroupedAccountCalendars.FirstOrDefault(g => g.Account.Id == accountCalendar.Account.Id); - private void SingleCalendarSelectionStateChanged(object sender, AccountCalendarViewModel e) - => AccountCalendarSelectionStateChanged?.Invoke(this, e); - - public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar) + if (group == null) { - groupedAccountCalendar.CalendarSelectionStateChanged += SingleCalendarSelectionStateChanged; - groupedAccountCalendar.CollectiveSelectionStateChanged += SingleGroupCalendarCollectiveStateChanged; - - _internalGroupedAccountCalendars.Add(groupedAccountCalendar); + // If the group doesn't exist, create it. + group = new GroupedAccountCalendarViewModel(accountCalendar.Account, new[] { accountCalendar }); + AddGroupedAccountCalendar(group); } - - public void RemoveGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar) + else { - groupedAccountCalendar.CalendarSelectionStateChanged -= SingleCalendarSelectionStateChanged; - groupedAccountCalendar.CollectiveSelectionStateChanged -= SingleGroupCalendarCollectiveStateChanged; - - _internalGroupedAccountCalendars.Remove(groupedAccountCalendar); + group.AccountCalendars.Add(accountCalendar); } + } - public void ClearGroupedAccountCalendar() + public void RemoveAccountCalendar(AccountCalendarViewModel accountCalendar) + { + var group = _internalGroupedAccountCalendars.FirstOrDefault(g => g.Account.Id == accountCalendar.Account.Id); + + // We don't expect but just in case. + if (group == null) return; + + group.AccountCalendars.Remove(accountCalendar); + + if (group.AccountCalendars.Count == 0) { - foreach (var groupedAccountCalendar in _internalGroupedAccountCalendars) - { - RemoveGroupedAccountCalendar(groupedAccountCalendar); - } - } - - public void AddAccountCalendar(AccountCalendarViewModel accountCalendar) - { - // Find the group that this calendar belongs to. - var group = _internalGroupedAccountCalendars.FirstOrDefault(g => g.Account.Id == accountCalendar.Account.Id); - - if (group == null) - { - // If the group doesn't exist, create it. - group = new GroupedAccountCalendarViewModel(accountCalendar.Account, new[] { accountCalendar }); - AddGroupedAccountCalendar(group); - } - else - { - group.AccountCalendars.Add(accountCalendar); - } - } - - public void RemoveAccountCalendar(AccountCalendarViewModel accountCalendar) - { - var group = _internalGroupedAccountCalendars.FirstOrDefault(g => g.Account.Id == accountCalendar.Account.Id); - - // We don't expect but just in case. - if (group == null) return; - - group.AccountCalendars.Remove(accountCalendar); - - if (group.AccountCalendars.Count == 0) - { - RemoveGroupedAccountCalendar(group); - } + RemoveGroupedAccountCalendar(group); } } } diff --git a/Wino.Calendar/Services/DialogService.cs b/Wino.Calendar/Services/DialogService.cs index 0fdd80b3..5a8c8965 100644 --- a/Wino.Calendar/Services/DialogService.cs +++ b/Wino.Calendar/Services/DialogService.cs @@ -2,14 +2,13 @@ using Wino.Core.Domain.Interfaces; using Wino.Core.UWP.Services; -namespace Wino.Calendar.Services +namespace Wino.Calendar.Services; + +public class DialogService : DialogServiceBase, ICalendarDialogService { - public class DialogService : DialogServiceBase, ICalendarDialogService + public DialogService(IThemeService themeService, + IConfigurationService configurationService, + IApplicationResourceManager applicationResourceManager) : base(themeService, configurationService, applicationResourceManager) { - public DialogService(IThemeService themeService, - IConfigurationService configurationService, - IApplicationResourceManager applicationResourceManager) : base(themeService, configurationService, applicationResourceManager) - { - } } } diff --git a/Wino.Calendar/Services/NavigationService.cs b/Wino.Calendar/Services/NavigationService.cs index 2544dc0b..f409c4b3 100644 --- a/Wino.Calendar/Services/NavigationService.cs +++ b/Wino.Calendar/Services/NavigationService.cs @@ -10,54 +10,53 @@ using Wino.Core.Domain.Models.Navigation; using Wino.Core.UWP.Services; using Wino.Views; -namespace Wino.Calendar.Services +namespace Wino.Calendar.Services; + +public class NavigationService : NavigationServiceBase, INavigationService { - public class NavigationService : NavigationServiceBase, INavigationService + public Type GetPageType(WinoPage winoPage) { - public Type GetPageType(WinoPage winoPage) + return winoPage switch { - return winoPage switch - { - WinoPage.CalendarPage => typeof(CalendarPage), - WinoPage.SettingsPage => typeof(SettingsPage), - WinoPage.CalendarSettingsPage => typeof(CalendarSettingsPage), - WinoPage.AccountManagementPage => typeof(AccountManagementPage), - WinoPage.ManageAccountsPage => typeof(ManageAccountsPage), - WinoPage.PersonalizationPage => typeof(PersonalizationPage), - WinoPage.AccountDetailsPage => typeof(AccountDetailsPage), - WinoPage.EventDetailsPage => typeof(EventDetailsPage), - _ => throw new Exception("Page is not implemented yet."), - }; - } + WinoPage.CalendarPage => typeof(CalendarPage), + WinoPage.SettingsPage => typeof(SettingsPage), + WinoPage.CalendarSettingsPage => typeof(CalendarSettingsPage), + WinoPage.AccountManagementPage => typeof(AccountManagementPage), + WinoPage.ManageAccountsPage => typeof(ManageAccountsPage), + WinoPage.PersonalizationPage => typeof(PersonalizationPage), + WinoPage.AccountDetailsPage => typeof(AccountDetailsPage), + WinoPage.EventDetailsPage => typeof(EventDetailsPage), + _ => throw new Exception("Page is not implemented yet."), + }; + } - public void GoBack() + public void GoBack() + { + if (Window.Current.Content is Frame appFrame && appFrame.Content is AppShell shellPage) { - if (Window.Current.Content is Frame appFrame && appFrame.Content is AppShell shellPage) - { - var shellFrame = shellPage.GetShellFrame(); + var shellFrame = shellPage.GetShellFrame(); - if (shellFrame.CanGoBack) - { - shellFrame.GoBack(); - } + if (shellFrame.CanGoBack) + { + shellFrame.GoBack(); } } - - public bool Navigate(WinoPage page, object parameter = null, NavigationReferenceFrame frame = NavigationReferenceFrame.ShellFrame, NavigationTransitionType transition = NavigationTransitionType.None) - { - // All navigations are performed on shell frame for calendar. - - if (Window.Current.Content is Frame appFrame && appFrame.Content is AppShell shellPage) - { - var shellFrame = shellPage.GetShellFrame(); - - var pageType = GetPageType(page); - - shellFrame.Navigate(pageType, parameter); - return true; - } - - return false; - } + } + + public bool Navigate(WinoPage page, object parameter = null, NavigationReferenceFrame frame = NavigationReferenceFrame.ShellFrame, NavigationTransitionType transition = NavigationTransitionType.None) + { + // All navigations are performed on shell frame for calendar. + + if (Window.Current.Content is Frame appFrame && appFrame.Content is AppShell shellPage) + { + var shellFrame = shellPage.GetShellFrame(); + + var pageType = GetPageType(page); + + shellFrame.Navigate(pageType, parameter); + return true; + } + + return false; } } diff --git a/Wino.Calendar/Services/ProviderService.cs b/Wino.Calendar/Services/ProviderService.cs index 648ea3a0..e0cac8ad 100644 --- a/Wino.Calendar/Services/ProviderService.cs +++ b/Wino.Calendar/Services/ProviderService.cs @@ -4,33 +4,32 @@ using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Accounts; -namespace Wino.Calendar.Services +namespace Wino.Calendar.Services; + +public class ProviderService : IProviderService { - public class ProviderService : IProviderService + public IProviderDetail GetProviderDetail(MailProviderType type) { - public IProviderDetail GetProviderDetail(MailProviderType type) - { - var details = GetAvailableProviders(); + var details = GetAvailableProviders(); - return details.FirstOrDefault(a => a.Type == type); + return details.FirstOrDefault(a => a.Type == type); + } + + public List GetAvailableProviders() + { + var providerList = new List(); + + var providers = new MailProviderType[] + { + MailProviderType.Outlook, + MailProviderType.Gmail + }; + + foreach (var type in providers) + { + providerList.Add(new ProviderDetail(type, SpecialImapProvider.None)); } - public List GetAvailableProviders() - { - var providerList = new List(); - - var providers = new MailProviderType[] - { - MailProviderType.Outlook, - MailProviderType.Gmail - }; - - foreach (var type in providers) - { - providerList.Add(new ProviderDetail(type, SpecialImapProvider.None)); - } - - return providerList; - } + return providerList; } } diff --git a/Wino.Calendar/Services/SettingsBuilderService.cs b/Wino.Calendar/Services/SettingsBuilderService.cs index fbb767ca..953a4031 100644 --- a/Wino.Calendar/Services/SettingsBuilderService.cs +++ b/Wino.Calendar/Services/SettingsBuilderService.cs @@ -4,18 +4,17 @@ using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Settings; -namespace Wino.Calendar.Services +namespace Wino.Calendar.Services; + +public class SettingsBuilderService : ISettingsBuilderService { - public class SettingsBuilderService : ISettingsBuilderService - { - public List GetSettingItems() - => new List() - { - new SettingOption(Translator.SettingsManageAccountSettings_Title, Translator.SettingsManageAccountSettings_Description, WinoPage.AccountManagementPage,"F1 M 3.75 5 L 3.75 4.902344 C 3.75 4.225262 3.885091 3.588867 4.155273 2.993164 C 4.425456 2.397461 4.790039 1.878256 5.249023 1.435547 C 5.708008 0.99284 6.238606 0.642904 6.84082 0.385742 C 7.443034 0.128582 8.079427 0 8.75 0 C 9.420572 0 10.056966 0.128582 10.65918 0.385742 C 11.261393 0.642904 11.791992 0.99284 12.250977 1.435547 C 12.709961 1.878256 13.074544 2.397461 13.344727 2.993164 C 13.614908 3.588867 13.75 4.225262 13.75 4.902344 C 13.75 5.397137 13.689778 5.87077 13.569336 6.323242 C 13.448893 6.775717 13.258463 7.213542 12.998047 7.636719 C 12.229817 7.799479 11.510416 8.079428 10.839844 8.476562 C 10.169271 8.873698 9.583333 9.378256 9.082031 9.990234 C 9.042969 9.996745 9.005533 10 8.969727 10 C 8.933919 10 8.899739 10 8.867188 10 L 8.652344 10 C 7.97526 10 7.338867 9.864909 6.743164 9.594727 C 6.147461 9.324545 5.628255 8.959961 5.185547 8.500977 C 4.742838 8.041993 4.392903 7.511395 4.135742 6.90918 C 3.878581 6.306967 3.75 5.670573 3.75 5 Z M 12.5 5 L 12.5 4.921875 C 12.5 4.414062 12.399088 3.937176 12.197266 3.491211 C 11.995442 3.045248 11.722005 2.65625 11.376953 2.324219 C 11.0319 1.992188 10.633138 1.730145 10.180664 1.538086 C 9.728189 1.346029 9.251302 1.25 8.75 1.25 C 8.229166 1.25 7.740885 1.347656 7.285156 1.542969 C 6.829427 1.738281 6.432292 2.005209 6.09375 2.34375 C 5.755208 2.682293 5.488281 3.079428 5.292969 3.535156 C 5.097656 3.990887 5 4.479167 5 5 C 5 5.520834 5.097656 6.009115 5.292969 6.464844 C 5.488281 6.920573 5.755208 7.317709 6.09375 7.65625 C 6.432292 7.994792 6.829427 8.261719 7.285156 8.457031 C 7.740885 8.652344 8.229166 8.75 8.75 8.75 C 9.270833 8.75 9.759114 8.652344 10.214844 8.457031 C 10.670572 8.261719 11.067708 7.994792 11.40625 7.65625 C 11.744791 7.317709 12.011719 6.920573 12.207031 6.464844 C 12.402344 6.009115 12.5 5.520834 12.5 5 Z M 12.148438 19.365234 C 11.777344 19.208984 11.41276 19.010416 11.054688 18.769531 C 11.113281 18.561197 11.170247 18.357748 11.225586 18.15918 C 11.280924 17.960611 11.308594 17.753906 11.308594 17.539062 C 11.308594 17.252604 11.259766 16.977539 11.162109 16.713867 C 11.064453 16.450195 10.930989 16.210938 10.761719 15.996094 C 10.592447 15.78125 10.390625 15.597331 10.15625 15.444336 C 9.921875 15.291342 9.664713 15.182292 9.384766 15.117188 L 8.779297 14.980469 C 8.759766 14.778646 8.75 14.576823 8.75 14.375 C 8.75 14.179688 8.759766 13.977865 8.779297 13.769531 L 9.384766 13.632812 C 9.664713 13.567709 9.921875 13.458659 10.15625 13.305664 C 10.390625 13.15267 10.592447 12.96875 10.761719 12.753906 C 10.930989 12.539062 11.064453 12.299805 11.162109 12.036133 C 11.259766 11.772461 11.308594 11.497396 11.308594 11.210938 C 11.308594 10.996094 11.280924 10.789389 11.225586 10.59082 C 11.170247 10.392253 11.113281 10.188803 11.054688 9.980469 C 11.41276 9.739584 11.777344 9.541016 12.148438 9.384766 C 12.324219 9.560547 12.488606 9.720053 12.641602 9.863281 C 12.794596 10.006511 12.957356 10.131836 13.129883 10.239258 C 13.302408 10.34668 13.487955 10.429688 13.686523 10.488281 C 13.885091 10.546875 14.114583 10.576172 14.375 10.576172 C 14.635416 10.576172 14.866535 10.546875 15.068359 10.488281 C 15.270182 10.429688 15.455729 10.34668 15.625 10.239258 C 15.79427 10.131836 15.955402 10.006511 16.108398 9.863281 C 16.261393 9.720053 16.425781 9.560547 16.601562 9.384766 C 16.972656 9.541016 17.337238 9.739584 17.695312 9.980469 C 17.636719 10.188803 17.579752 10.392253 17.524414 10.59082 C 17.469074 10.789389 17.441406 10.996094 17.441406 11.210938 C 17.441406 11.497396 17.488605 11.772461 17.583008 12.036133 C 17.677408 12.299805 17.810871 12.539062 17.983398 12.753906 C 18.155924 12.96875 18.359375 13.15267 18.59375 13.305664 C 18.828125 13.458659 19.085285 13.567709 19.365234 13.632812 L 19.970703 13.769531 C 19.990234 13.977865 20 14.179688 20 14.375 C 20 14.576823 19.990234 14.778646 19.970703 14.980469 L 19.365234 15.117188 C 19.085285 15.182292 18.828125 15.291342 18.59375 15.444336 C 18.359375 15.597331 18.155924 15.78125 17.983398 15.996094 C 17.810871 16.210938 17.677408 16.450195 17.583008 16.713867 C 17.488605 16.977539 17.441406 17.252604 17.441406 17.539062 C 17.441406 17.753906 17.469074 17.960611 17.524414 18.15918 C 17.579752 18.357748 17.636719 18.561197 17.695312 18.769531 C 17.337238 19.010416 16.972656 19.208984 16.601562 19.365234 C 16.425781 19.189453 16.261393 19.029947 16.108398 18.886719 C 15.955402 18.74349 15.79427 18.618164 15.625 18.510742 C 15.455729 18.40332 15.270182 18.320312 15.068359 18.261719 C 14.866535 18.203125 14.635416 18.173828 14.375 18.173828 C 14.108072 18.173828 13.875324 18.203125 13.676758 18.261719 C 13.478189 18.320312 13.294271 18.40332 13.125 18.510742 C 12.955729 18.618164 12.794596 18.74349 12.641602 18.886719 C 12.488606 19.029947 12.324219 19.189453 12.148438 19.365234 Z M 0 13.75 C 0 13.404948 0.065104 13.081055 0.195312 12.77832 C 0.325521 12.475586 0.504557 12.210287 0.732422 11.982422 C 0.960286 11.754558 1.225586 11.575521 1.52832 11.445312 C 1.831055 11.315104 2.154948 11.25 2.5 11.25 L 8.251953 11.25 C 8.043619 11.660156 7.880859 12.076823 7.763672 12.5 L 2.5 12.5 C 2.324219 12.5 2.159831 12.532553 2.006836 12.597656 C 1.853841 12.662761 1.722005 12.750651 1.611328 12.861328 C 1.500651 12.972006 1.41276 13.103842 1.347656 13.256836 C 1.282552 13.409831 1.25 13.574219 1.25 13.75 C 1.25 14.420573 1.360677 15.008139 1.582031 15.512695 C 1.803385 16.017252 2.102865 16.455078 2.480469 16.826172 C 2.858073 17.197266 3.297526 17.50651 3.798828 17.753906 C 4.30013 18.001303 4.827474 18.198242 5.380859 18.344727 C 5.934244 18.491211 6.50065 18.595377 7.080078 18.657227 C 7.659505 18.719076 8.216146 18.75 8.75 18.75 L 9.072266 18.75 C 9.449869 19.199219 9.866536 19.5931 10.322266 19.931641 C 10.061849 19.964193 9.801432 19.983725 9.541016 19.990234 C 9.280599 19.996744 9.016927 20 8.75 20 C 8.190104 20 7.618814 19.973959 7.036133 19.921875 C 6.45345 19.869791 5.878906 19.775391 5.3125 19.638672 C 4.746094 19.501953 4.197591 19.319662 3.666992 19.091797 C 3.136393 18.863932 2.646484 18.574219 2.197266 18.222656 C 1.474609 17.66276 0.927734 17.005209 0.556641 16.25 C 0.185547 15.494792 0 14.661458 0 13.75 Z M 15.625 14.375 C 15.625 14.205729 15.592447 14.044597 15.527344 13.891602 C 15.462239 13.738607 15.372721 13.605144 15.258789 13.491211 C 15.144855 13.377279 15.011393 13.287761 14.858398 13.222656 C 14.705403 13.157553 14.544271 13.125 14.375 13.125 C 14.199219 13.125 14.036458 13.157553 13.886719 13.222656 C 13.736979 13.287761 13.605143 13.377279 13.491211 13.491211 C 13.377278 13.605144 13.28776 13.736979 13.222656 13.886719 C 13.157551 14.036459 13.124999 14.199219 13.125 14.375 C 13.124999 14.550781 13.157551 14.71517 13.222656 14.868164 C 13.28776 15.021159 13.37565 15.152995 13.486328 15.263672 C 13.597004 15.37435 13.72884 15.46224 13.881836 15.527344 C 14.03483 15.592448 14.199219 15.625 14.375 15.625 C 14.550781 15.625 14.713541 15.592448 14.863281 15.527344 C 15.01302 15.46224 15.144855 15.372722 15.258789 15.258789 C 15.372721 15.144857 15.462239 15.013021 15.527344 14.863281 C 15.592447 14.713542 15.625 14.550781 15.625 14.375 Z "), - new SettingOption(Translator.SettingsAppPreferences_Title, Translator.SettingsAppPreferences_Description, WinoPage.AppPreferencesPage,"F1 M 15.078125 1.25 C 15.566406 1.25 16.033527 1.349285 16.479492 1.547852 C 16.925455 1.74642 17.31608 2.013348 17.651367 2.348633 C 17.986652 2.68392 18.25358 3.074545 18.452148 3.520508 C 18.650715 3.966473 18.75 4.433594 18.75 4.921875 L 18.75 15.078125 C 18.75 15.566406 18.650715 16.033529 18.452148 16.479492 C 18.25358 16.925455 17.986652 17.31608 17.651367 17.651367 C 17.31608 17.986654 16.925455 18.25358 16.479492 18.452148 C 16.033527 18.650717 15.566406 18.75 15.078125 18.75 L 4.921875 18.75 C 4.433594 18.75 3.966471 18.650717 3.520508 18.452148 C 3.074544 18.25358 2.683919 17.986654 2.348633 17.651367 C 2.013346 17.31608 1.746419 16.925455 1.547852 16.479492 C 1.349284 16.033529 1.25 15.566406 1.25 15.078125 L 1.25 4.921875 C 1.25 4.433594 1.349284 3.966473 1.547852 3.520508 C 1.746419 3.074545 2.013346 2.68392 2.348633 2.348633 C 2.683919 2.013348 3.074544 1.74642 3.520508 1.547852 C 3.966471 1.349285 4.433594 1.25 4.921875 1.25 Z M 4.951172 2.5 C 4.625651 2.5 4.314778 2.566732 4.018555 2.700195 C 3.722331 2.83366 3.461914 3.012695 3.237305 3.237305 C 3.012695 3.461914 2.833659 3.722332 2.700195 4.018555 C 2.566732 4.314779 2.5 4.625651 2.5 4.951172 L 2.5 5 L 17.5 5 L 17.5 4.951172 C 17.5 4.625651 17.433268 4.314779 17.299805 4.018555 C 17.16634 3.722332 16.987305 3.461914 16.762695 3.237305 C 16.538086 3.012695 16.277668 2.83366 15.981445 2.700195 C 15.685221 2.566732 15.374349 2.5 15.048828 2.5 Z M 15.048828 17.5 C 15.374349 17.5 15.685221 17.433268 15.981445 17.299805 C 16.277668 17.166342 16.538086 16.987305 16.762695 16.762695 C 16.987305 16.538086 17.16634 16.27767 17.299805 15.981445 C 17.433268 15.685222 17.5 15.37435 17.5 15.048828 L 17.5 6.25 L 2.5 6.25 L 2.5 15.048828 C 2.5 15.37435 2.566732 15.685222 2.700195 15.981445 C 2.833659 16.27767 3.012695 16.538086 3.237305 16.762695 C 3.461914 16.987305 3.722331 17.166342 4.018555 17.299805 C 4.314778 17.433268 4.625651 17.5 4.951172 17.5 Z M 12.724609 8.935547 C 12.724609 9.195964 12.762044 9.446615 12.836914 9.6875 C 12.911783 9.928386 13.020832 10.151367 13.164062 10.356445 C 13.307291 10.561523 13.476562 10.742188 13.671875 10.898438 C 13.867188 11.054688 14.088541 11.178386 14.335938 11.269531 C 14.348957 11.367188 14.358723 11.466472 14.365234 11.567383 C 14.371744 11.668295 14.375 11.770834 14.375 11.875 C 14.375 11.979167 14.371744 12.081706 14.365234 12.182617 C 14.358723 12.283529 14.348957 12.382812 14.335938 12.480469 C 14.088541 12.571615 13.867188 12.695312 13.671875 12.851562 C 13.476562 13.007812 13.307291 13.188477 13.164062 13.393555 C 13.020832 13.598633 12.911783 13.821615 12.836914 14.0625 C 12.762044 14.303386 12.724609 14.554037 12.724609 14.814453 C 12.724609 14.886068 12.727864 14.960938 12.734375 15.039062 C 12.740885 15.117188 12.75065 15.192058 12.763672 15.263672 C 12.470703 15.511068 12.145182 15.712891 11.787109 15.869141 C 11.546224 15.621745 11.274414 15.43457 10.97168 15.307617 C 10.668945 15.180664 10.345052 15.117188 10 15.117188 C 9.654947 15.117188 9.331055 15.180664 9.02832 15.307617 C 8.725586 15.43457 8.453775 15.621745 8.212891 15.869141 C 7.854817 15.712891 7.529296 15.511068 7.236328 15.263672 C 7.249349 15.192058 7.259114 15.117188 7.265625 15.039062 C 7.272135 14.960938 7.275391 14.886068 7.275391 14.814453 C 7.275391 14.554037 7.237956 14.303386 7.163086 14.0625 C 7.088216 13.821615 6.979167 13.598633 6.835938 13.393555 C 6.692708 13.188477 6.52181 13.007812 6.323242 12.851562 C 6.124674 12.695312 5.904948 12.571615 5.664062 12.480469 C 5.651042 12.382812 5.641276 12.283529 5.634766 12.182617 C 5.628255 12.081706 5.625 11.979167 5.625 11.875 C 5.625 11.770834 5.628255 11.668295 5.634766 11.567383 C 5.641276 11.466472 5.651042 11.367188 5.664062 11.269531 C 5.904948 11.178386 6.124674 11.054688 6.323242 10.898438 C 6.52181 10.742188 6.692708 10.561523 6.835938 10.356445 C 6.979167 10.151367 7.088216 9.928386 7.163086 9.6875 C 7.237956 9.446615 7.275391 9.195964 7.275391 8.935547 C 7.275391 8.863933 7.272135 8.789062 7.265625 8.710938 C 7.259114 8.632812 7.249349 8.557943 7.236328 8.486328 C 7.529296 8.238933 7.854817 8.037109 8.212891 7.880859 C 8.440755 8.121745 8.712564 8.307292 9.02832 8.4375 C 9.344075 8.567709 9.667969 8.632812 10 8.632812 C 10.332031 8.632812 10.655924 8.567709 10.97168 8.4375 C 11.287435 8.307292 11.559244 8.121745 11.787109 7.880859 C 12.145182 8.037109 12.470703 8.238933 12.763672 8.486328 C 12.75065 8.557943 12.740885 8.632812 12.734375 8.710938 C 12.727864 8.789062 12.724609 8.863933 12.724609 8.935547 Z M 10.9375 11.875 C 10.9375 11.614584 10.846354 11.393229 10.664062 11.210938 C 10.481771 11.028646 10.260416 10.9375 10 10.9375 C 9.739583 10.9375 9.518229 11.028646 9.335938 11.210938 C 9.153646 11.393229 9.0625 11.614584 9.0625 11.875 C 9.0625 12.135417 9.153646 12.356771 9.335938 12.539062 C 9.518229 12.721354 9.739583 12.8125 10 12.8125 C 10.260416 12.8125 10.481771 12.721354 10.664062 12.539062 C 10.846354 12.356771 10.9375 12.135417 10.9375 11.875 Z "), - new SettingOption(Translator.SettingsPersonalization_Title, Translator.SettingsPersonalization_Description, WinoPage.PersonalizationPage,"F1 M 10 17.5 L 10 18.75 L 12.5 18.75 L 12.5 20 L 6.25 20 L 6.25 18.75 L 8.75 18.75 L 8.75 17.5 L 0 17.5 L 0 6.25 L 10 6.25 L 8.740234 7.5 L 1.25 7.5 L 1.25 16.25 L 17.5 16.25 L 17.5 8.75 L 18.75 7.5 L 18.75 17.5 Z M 5 13.75 C 5.175781 13.75 5.338542 13.717448 5.488281 13.652344 C 5.638021 13.58724 5.769856 13.497722 5.883789 13.383789 C 5.997721 13.269857 6.087239 13.138021 6.152344 12.988281 C 6.217448 12.838542 6.25 12.675781 6.25 12.5 C 6.25 12.18099 6.306966 11.878256 6.420898 11.591797 C 6.53483 11.305339 6.69108 11.051433 6.889648 10.830078 C 7.088216 10.608725 7.322591 10.424805 7.592773 10.27832 C 7.862955 10.131836 8.157552 10.042318 8.476562 10.009766 L 15.419922 3.066406 C 15.602213 2.884115 15.813802 2.744141 16.054688 2.646484 C 16.295572 2.548828 16.542969 2.5 16.796875 2.5 C 17.063801 2.5 17.31608 2.550457 17.553711 2.651367 C 17.79134 2.752279 17.998047 2.890625 18.173828 3.066406 C 18.349609 3.242188 18.487955 3.448895 18.588867 3.686523 C 18.689777 3.924154 18.740234 4.179688 18.740234 4.453125 C 18.740234 4.707031 18.691406 4.954428 18.59375 5.195312 C 18.496094 5.436199 18.356119 5.647787 18.173828 5.830078 L 11.230469 12.773438 C 11.197916 13.085938 11.110025 13.378906 10.966797 13.652344 C 10.823567 13.925781 10.639648 14.161784 10.415039 14.360352 C 10.19043 14.55892 9.936523 14.71517 9.65332 14.829102 C 9.370117 14.943034 9.06901 15 8.75 15 L 2.5 15 L 2.5 13.75 Z M 16.796875 3.75 C 16.608072 3.75 16.445312 3.818359 16.308594 3.955078 L 11.962891 8.300781 C 12.333984 8.58724 12.66276 8.916016 12.949219 9.287109 L 17.294922 4.941406 C 17.431641 4.804688 17.5 4.641928 17.5 4.453125 C 17.5 4.257812 17.430012 4.091797 17.290039 3.955078 C 17.150064 3.818359 16.985676 3.75 16.796875 3.75 Z M 10.175781 10.087891 C 10.572916 10.348308 10.901692 10.677084 11.162109 11.074219 L 12.060547 10.185547 C 11.787109 9.794922 11.455078 9.462891 11.064453 9.189453 Z M 10 12.5 C 10 12.324219 9.967447 12.161459 9.902344 12.011719 C 9.837239 11.861979 9.747721 11.730144 9.633789 11.616211 C 9.519856 11.502279 9.388021 11.412761 9.238281 11.347656 C 9.088541 11.282553 8.925781 11.25 8.75 11.25 C 8.574219 11.25 8.411458 11.282553 8.261719 11.347656 C 8.111979 11.412761 7.980143 11.502279 7.866211 11.616211 C 7.752278 11.730144 7.66276 11.861979 7.597656 12.011719 C 7.532552 12.161459 7.5 12.324219 7.5 12.5 C 7.5 12.942709 7.389322 13.359375 7.167969 13.75 L 8.75 13.75 C 8.919271 13.75 9.080403 13.717448 9.233398 13.652344 C 9.386393 13.58724 9.519856 13.497722 9.633789 13.383789 C 9.747721 13.269857 9.837239 13.136394 9.902344 12.983398 C 9.967447 12.830404 10 12.669271 10 12.5 Z "), - new SettingOption(Translator.SettingsCalendarSettings_Title, Translator.SettingsCalendarSettings_Description, WinoPage.CalendarSettingsPage,"F1 M 1.25 3.75 C 1.25 3.404949 1.315104 3.081055 1.445312 2.77832 C 1.575521 2.475586 1.754557 2.210287 1.982422 1.982422 C 2.210286 1.754559 2.475586 1.575521 2.77832 1.445312 C 3.081055 1.315105 3.404948 1.25 3.75 1.25 L 16.25 1.25 C 16.595051 1.25 16.918945 1.315105 17.22168 1.445312 C 17.524414 1.575521 17.789713 1.754559 18.017578 1.982422 C 18.245441 2.210287 18.424479 2.475586 18.554688 2.77832 C 18.684895 3.081055 18.75 3.404949 18.75 3.75 L 18.75 5 L 1.25 5 Z M 3.75 18.75 C 3.404948 18.75 3.081055 18.684896 2.77832 18.554688 C 2.475586 18.424479 2.210286 18.245443 1.982422 18.017578 C 1.754557 17.789713 1.575521 17.524414 1.445312 17.22168 C 1.315104 16.918945 1.25 16.595053 1.25 16.25 L 1.25 6.25 L 18.75 6.25 L 18.75 16.25 C 18.75 16.595053 18.684895 16.918945 18.554688 17.22168 C 18.424479 17.524414 18.245441 17.789713 18.017578 18.017578 C 17.789713 18.245443 17.524414 18.424479 17.22168 18.554688 C 16.918945 18.684896 16.595051 18.75 16.25 18.75 Z M 7.65625 9.990234 C 7.65625 9.794922 7.618815 9.612631 7.543945 9.443359 C 7.469075 9.274089 7.368164 9.125977 7.241211 8.999023 C 7.114258 8.87207 6.966146 8.772787 6.796875 8.701172 C 6.627604 8.629558 6.445312 8.59375 6.25 8.59375 C 6.054688 8.59375 5.870768 8.631186 5.698242 8.706055 C 5.525716 8.780925 5.375977 8.881836 5.249023 9.008789 C 5.12207 9.135742 5.022786 9.283854 4.951172 9.453125 C 4.879557 9.622396 4.84375 9.804688 4.84375 10 C 4.84375 10.195312 4.879557 10.377604 4.951172 10.546875 C 5.022786 10.716146 5.12207 10.864258 5.249023 10.991211 C 5.375977 11.118164 5.524088 11.219076 5.693359 11.293945 C 5.86263 11.368815 6.044922 11.40625 6.240234 11.40625 C 6.442057 11.40625 6.629231 11.370443 6.801758 11.298828 C 6.974283 11.227214 7.124023 11.12793 7.250977 11.000977 C 7.377929 10.874023 7.477213 10.724284 7.548828 10.551758 C 7.620442 10.379232 7.65625 10.192058 7.65625 9.990234 Z M 11.40625 9.990234 C 11.40625 9.794922 11.368814 9.612631 11.293945 9.443359 C 11.219075 9.274089 11.118164 9.125977 10.991211 8.999023 C 10.864258 8.87207 10.716146 8.772787 10.546875 8.701172 C 10.377604 8.629558 10.195312 8.59375 10 8.59375 C 9.804688 8.59375 9.620768 8.631186 9.448242 8.706055 C 9.275716 8.780925 9.125977 8.881836 8.999023 9.008789 C 8.87207 9.135742 8.772786 9.283854 8.701172 9.453125 C 8.629557 9.622396 8.59375 9.804688 8.59375 10 C 8.59375 10.195312 8.629557 10.377604 8.701172 10.546875 C 8.772786 10.716146 8.87207 10.864258 8.999023 10.991211 C 9.125977 11.118164 9.274088 11.219076 9.443359 11.293945 C 9.61263 11.368815 9.794922 11.40625 9.990234 11.40625 C 10.192057 11.40625 10.379231 11.370443 10.551758 11.298828 C 10.724283 11.227214 10.874023 11.12793 11.000977 11.000977 C 11.12793 10.874023 11.227213 10.724284 11.298828 10.551758 C 11.370442 10.379232 11.40625 10.192058 11.40625 9.990234 Z M 15.15625 9.990234 C 15.15625 9.794922 15.118814 9.612631 15.043945 9.443359 C 14.969075 9.274089 14.868164 9.125977 14.741211 8.999023 C 14.614258 8.87207 14.466146 8.772787 14.296875 8.701172 C 14.127604 8.629558 13.945312 8.59375 13.75 8.59375 C 13.554688 8.59375 13.370768 8.631186 13.198242 8.706055 C 13.025716 8.780925 12.875977 8.881836 12.749023 9.008789 C 12.62207 9.135742 12.522786 9.283854 12.451172 9.453125 C 12.379557 9.622396 12.34375 9.804688 12.34375 10 C 12.34375 10.195312 12.379557 10.377604 12.451172 10.546875 C 12.522786 10.716146 12.62207 10.864258 12.749023 10.991211 C 12.875977 11.118164 13.024088 11.219076 13.193359 11.293945 C 13.362629 11.368815 13.544921 11.40625 13.740234 11.40625 C 13.942057 11.40625 14.129231 11.370443 14.301758 11.298828 C 14.474283 11.227214 14.624022 11.12793 14.750977 11.000977 C 14.877929 10.874023 14.977213 10.724284 15.048828 10.551758 C 15.120442 10.379232 15.15625 10.192058 15.15625 9.990234 Z M 7.65625 13.740234 C 7.65625 13.544922 7.618815 13.362631 7.543945 13.193359 C 7.469075 13.024089 7.368164 12.875977 7.241211 12.749023 C 7.114258 12.62207 6.966146 12.522787 6.796875 12.451172 C 6.627604 12.379558 6.445312 12.34375 6.25 12.34375 C 6.054688 12.34375 5.870768 12.381186 5.698242 12.456055 C 5.525716 12.530925 5.375977 12.631836 5.249023 12.758789 C 5.12207 12.885742 5.022786 13.033854 4.951172 13.203125 C 4.879557 13.372396 4.84375 13.554688 4.84375 13.75 C 4.84375 13.945312 4.879557 14.127604 4.951172 14.296875 C 5.022786 14.466146 5.12207 14.614258 5.249023 14.741211 C 5.375977 14.868164 5.524088 14.969076 5.693359 15.043945 C 5.86263 15.118815 6.044922 15.15625 6.240234 15.15625 C 6.442057 15.15625 6.629231 15.120443 6.801758 15.048828 C 6.974283 14.977214 7.124023 14.87793 7.250977 14.750977 C 7.377929 14.624023 7.477213 14.474284 7.548828 14.301758 C 7.620442 14.129232 7.65625 13.942058 7.65625 13.740234 Z M 11.40625 13.740234 C 11.40625 13.544922 11.368814 13.362631 11.293945 13.193359 C 11.219075 13.024089 11.118164 12.875977 10.991211 12.749023 C 10.864258 12.62207 10.716146 12.522787 10.546875 12.451172 C 10.377604 12.379558 10.195312 12.34375 10 12.34375 C 9.804688 12.34375 9.620768 12.381186 9.448242 12.456055 C 9.275716 12.530925 9.125977 12.631836 8.999023 12.758789 C 8.87207 12.885742 8.772786 13.033854 8.701172 13.203125 C 8.629557 13.372396 8.59375 13.554688 8.59375 13.75 C 8.59375 13.945312 8.629557 14.127604 8.701172 14.296875 C 8.772786 14.466146 8.87207 14.614258 8.999023 14.741211 C 9.125977 14.868164 9.274088 14.969076 9.443359 15.043945 C 9.61263 15.118815 9.794922 15.15625 9.990234 15.15625 C 10.192057 15.15625 10.379231 15.120443 10.551758 15.048828 C 10.724283 14.977214 10.874023 14.87793 11.000977 14.750977 C 11.12793 14.624023 11.227213 14.474284 11.298828 14.301758 C 11.370442 14.129232 11.40625 13.942058 11.40625 13.740234 Z "), - new SettingOption(Translator.SettingsAbout_Title, Translator.SettingsAbout_Description, WinoPage.AboutPage,"F1 M 9.375 18.75 C 8.509114 18.75 7.677409 18.639322 6.879883 18.417969 C 6.082356 18.196615 5.335286 17.882486 4.638672 17.475586 C 3.942057 17.068686 3.308919 16.580404 2.739258 16.010742 C 2.169596 15.441081 1.681315 14.807943 1.274414 14.111328 C 0.867513 13.414714 0.553385 12.667644 0.332031 11.870117 C 0.110677 11.072592 0 10.240886 0 9.375 C 0 8.509115 0.110677 7.677409 0.332031 6.879883 C 0.553385 6.082357 0.867513 5.335287 1.274414 4.638672 C 1.681315 3.942059 2.169596 3.30892 2.739258 2.739258 C 3.308919 2.169598 3.942057 1.681316 4.638672 1.274414 C 5.335286 0.867514 6.082356 0.553387 6.879883 0.332031 C 7.677409 0.110678 8.509114 0 9.375 0 C 10.240885 0 11.072591 0.110678 11.870117 0.332031 C 12.667643 0.553387 13.414713 0.867514 14.111328 1.274414 C 14.807942 1.681316 15.44108 2.169598 16.010742 2.739258 C 16.580402 3.30892 17.068684 3.942059 17.475586 4.638672 C 17.882486 5.335287 18.196613 6.082357 18.417969 6.879883 C 18.639322 7.677409 18.75 8.509115 18.75 9.375 C 18.75 10.240886 18.639322 11.072592 18.417969 11.870117 C 18.196613 12.667644 17.882486 13.414714 17.475586 14.111328 C 17.068684 14.807943 16.580402 15.441081 16.010742 16.010742 C 15.44108 16.580404 14.807942 17.068686 14.111328 17.475586 C 13.414713 17.882486 12.667643 18.196615 11.870117 18.417969 C 11.072591 18.639322 10.240885 18.75 9.375 18.75 Z M 9.375 1.25 C 8.626302 1.25 7.906901 1.347656 7.216797 1.542969 C 6.526692 1.738281 5.880533 2.011719 5.27832 2.363281 C 4.676106 2.714844 4.127604 3.138021 3.632812 3.632812 C 3.138021 4.127604 2.714844 4.676107 2.363281 5.27832 C 2.011719 5.880534 1.738281 6.52832 1.542969 7.22168 C 1.347656 7.915039 1.25 8.632812 1.25 9.375 C 1.25 10.117188 1.347656 10.834961 1.542969 11.52832 C 1.738281 12.22168 2.011719 12.869467 2.363281 13.47168 C 2.714844 14.073894 3.138021 14.622396 3.632812 15.117188 C 4.127604 15.611979 4.676106 16.035156 5.27832 16.386719 C 5.880533 16.738281 6.526692 17.011719 7.216797 17.207031 C 7.906901 17.402344 8.626302 17.5 9.375 17.5 C 10.117188 17.5 10.834961 17.402344 11.52832 17.207031 C 12.221679 17.011719 12.869465 16.738281 13.47168 16.386719 C 14.073893 16.035156 14.622396 15.611979 15.117188 15.117188 C 15.611979 14.622396 16.035156 14.073894 16.386719 13.47168 C 16.738281 12.869467 17.011719 12.223308 17.207031 11.533203 C 17.402344 10.8431 17.5 10.123698 17.5 9.375 C 17.5 8.632812 17.402344 7.915039 17.207031 7.22168 C 17.011719 6.52832 16.738281 5.880534 16.386719 5.27832 C 16.035156 4.676107 15.611979 4.127604 15.117188 3.632812 C 14.622396 3.138021 14.073893 2.714844 13.47168 2.363281 C 12.869465 2.011719 12.221679 1.738281 11.52832 1.542969 C 10.834961 1.347656 10.117188 1.25 9.375 1.25 Z M 8.75 7.5 L 10 7.5 L 10 13.75 L 8.75 13.75 Z M 8.75 5 L 10 5 L 10 6.25 L 8.75 6.25 Z "), - }; - } + public List GetSettingItems() + => new List() + { + new SettingOption(Translator.SettingsManageAccountSettings_Title, Translator.SettingsManageAccountSettings_Description, WinoPage.AccountManagementPage,"F1 M 3.75 5 L 3.75 4.902344 C 3.75 4.225262 3.885091 3.588867 4.155273 2.993164 C 4.425456 2.397461 4.790039 1.878256 5.249023 1.435547 C 5.708008 0.99284 6.238606 0.642904 6.84082 0.385742 C 7.443034 0.128582 8.079427 0 8.75 0 C 9.420572 0 10.056966 0.128582 10.65918 0.385742 C 11.261393 0.642904 11.791992 0.99284 12.250977 1.435547 C 12.709961 1.878256 13.074544 2.397461 13.344727 2.993164 C 13.614908 3.588867 13.75 4.225262 13.75 4.902344 C 13.75 5.397137 13.689778 5.87077 13.569336 6.323242 C 13.448893 6.775717 13.258463 7.213542 12.998047 7.636719 C 12.229817 7.799479 11.510416 8.079428 10.839844 8.476562 C 10.169271 8.873698 9.583333 9.378256 9.082031 9.990234 C 9.042969 9.996745 9.005533 10 8.969727 10 C 8.933919 10 8.899739 10 8.867188 10 L 8.652344 10 C 7.97526 10 7.338867 9.864909 6.743164 9.594727 C 6.147461 9.324545 5.628255 8.959961 5.185547 8.500977 C 4.742838 8.041993 4.392903 7.511395 4.135742 6.90918 C 3.878581 6.306967 3.75 5.670573 3.75 5 Z M 12.5 5 L 12.5 4.921875 C 12.5 4.414062 12.399088 3.937176 12.197266 3.491211 C 11.995442 3.045248 11.722005 2.65625 11.376953 2.324219 C 11.0319 1.992188 10.633138 1.730145 10.180664 1.538086 C 9.728189 1.346029 9.251302 1.25 8.75 1.25 C 8.229166 1.25 7.740885 1.347656 7.285156 1.542969 C 6.829427 1.738281 6.432292 2.005209 6.09375 2.34375 C 5.755208 2.682293 5.488281 3.079428 5.292969 3.535156 C 5.097656 3.990887 5 4.479167 5 5 C 5 5.520834 5.097656 6.009115 5.292969 6.464844 C 5.488281 6.920573 5.755208 7.317709 6.09375 7.65625 C 6.432292 7.994792 6.829427 8.261719 7.285156 8.457031 C 7.740885 8.652344 8.229166 8.75 8.75 8.75 C 9.270833 8.75 9.759114 8.652344 10.214844 8.457031 C 10.670572 8.261719 11.067708 7.994792 11.40625 7.65625 C 11.744791 7.317709 12.011719 6.920573 12.207031 6.464844 C 12.402344 6.009115 12.5 5.520834 12.5 5 Z M 12.148438 19.365234 C 11.777344 19.208984 11.41276 19.010416 11.054688 18.769531 C 11.113281 18.561197 11.170247 18.357748 11.225586 18.15918 C 11.280924 17.960611 11.308594 17.753906 11.308594 17.539062 C 11.308594 17.252604 11.259766 16.977539 11.162109 16.713867 C 11.064453 16.450195 10.930989 16.210938 10.761719 15.996094 C 10.592447 15.78125 10.390625 15.597331 10.15625 15.444336 C 9.921875 15.291342 9.664713 15.182292 9.384766 15.117188 L 8.779297 14.980469 C 8.759766 14.778646 8.75 14.576823 8.75 14.375 C 8.75 14.179688 8.759766 13.977865 8.779297 13.769531 L 9.384766 13.632812 C 9.664713 13.567709 9.921875 13.458659 10.15625 13.305664 C 10.390625 13.15267 10.592447 12.96875 10.761719 12.753906 C 10.930989 12.539062 11.064453 12.299805 11.162109 12.036133 C 11.259766 11.772461 11.308594 11.497396 11.308594 11.210938 C 11.308594 10.996094 11.280924 10.789389 11.225586 10.59082 C 11.170247 10.392253 11.113281 10.188803 11.054688 9.980469 C 11.41276 9.739584 11.777344 9.541016 12.148438 9.384766 C 12.324219 9.560547 12.488606 9.720053 12.641602 9.863281 C 12.794596 10.006511 12.957356 10.131836 13.129883 10.239258 C 13.302408 10.34668 13.487955 10.429688 13.686523 10.488281 C 13.885091 10.546875 14.114583 10.576172 14.375 10.576172 C 14.635416 10.576172 14.866535 10.546875 15.068359 10.488281 C 15.270182 10.429688 15.455729 10.34668 15.625 10.239258 C 15.79427 10.131836 15.955402 10.006511 16.108398 9.863281 C 16.261393 9.720053 16.425781 9.560547 16.601562 9.384766 C 16.972656 9.541016 17.337238 9.739584 17.695312 9.980469 C 17.636719 10.188803 17.579752 10.392253 17.524414 10.59082 C 17.469074 10.789389 17.441406 10.996094 17.441406 11.210938 C 17.441406 11.497396 17.488605 11.772461 17.583008 12.036133 C 17.677408 12.299805 17.810871 12.539062 17.983398 12.753906 C 18.155924 12.96875 18.359375 13.15267 18.59375 13.305664 C 18.828125 13.458659 19.085285 13.567709 19.365234 13.632812 L 19.970703 13.769531 C 19.990234 13.977865 20 14.179688 20 14.375 C 20 14.576823 19.990234 14.778646 19.970703 14.980469 L 19.365234 15.117188 C 19.085285 15.182292 18.828125 15.291342 18.59375 15.444336 C 18.359375 15.597331 18.155924 15.78125 17.983398 15.996094 C 17.810871 16.210938 17.677408 16.450195 17.583008 16.713867 C 17.488605 16.977539 17.441406 17.252604 17.441406 17.539062 C 17.441406 17.753906 17.469074 17.960611 17.524414 18.15918 C 17.579752 18.357748 17.636719 18.561197 17.695312 18.769531 C 17.337238 19.010416 16.972656 19.208984 16.601562 19.365234 C 16.425781 19.189453 16.261393 19.029947 16.108398 18.886719 C 15.955402 18.74349 15.79427 18.618164 15.625 18.510742 C 15.455729 18.40332 15.270182 18.320312 15.068359 18.261719 C 14.866535 18.203125 14.635416 18.173828 14.375 18.173828 C 14.108072 18.173828 13.875324 18.203125 13.676758 18.261719 C 13.478189 18.320312 13.294271 18.40332 13.125 18.510742 C 12.955729 18.618164 12.794596 18.74349 12.641602 18.886719 C 12.488606 19.029947 12.324219 19.189453 12.148438 19.365234 Z M 0 13.75 C 0 13.404948 0.065104 13.081055 0.195312 12.77832 C 0.325521 12.475586 0.504557 12.210287 0.732422 11.982422 C 0.960286 11.754558 1.225586 11.575521 1.52832 11.445312 C 1.831055 11.315104 2.154948 11.25 2.5 11.25 L 8.251953 11.25 C 8.043619 11.660156 7.880859 12.076823 7.763672 12.5 L 2.5 12.5 C 2.324219 12.5 2.159831 12.532553 2.006836 12.597656 C 1.853841 12.662761 1.722005 12.750651 1.611328 12.861328 C 1.500651 12.972006 1.41276 13.103842 1.347656 13.256836 C 1.282552 13.409831 1.25 13.574219 1.25 13.75 C 1.25 14.420573 1.360677 15.008139 1.582031 15.512695 C 1.803385 16.017252 2.102865 16.455078 2.480469 16.826172 C 2.858073 17.197266 3.297526 17.50651 3.798828 17.753906 C 4.30013 18.001303 4.827474 18.198242 5.380859 18.344727 C 5.934244 18.491211 6.50065 18.595377 7.080078 18.657227 C 7.659505 18.719076 8.216146 18.75 8.75 18.75 L 9.072266 18.75 C 9.449869 19.199219 9.866536 19.5931 10.322266 19.931641 C 10.061849 19.964193 9.801432 19.983725 9.541016 19.990234 C 9.280599 19.996744 9.016927 20 8.75 20 C 8.190104 20 7.618814 19.973959 7.036133 19.921875 C 6.45345 19.869791 5.878906 19.775391 5.3125 19.638672 C 4.746094 19.501953 4.197591 19.319662 3.666992 19.091797 C 3.136393 18.863932 2.646484 18.574219 2.197266 18.222656 C 1.474609 17.66276 0.927734 17.005209 0.556641 16.25 C 0.185547 15.494792 0 14.661458 0 13.75 Z M 15.625 14.375 C 15.625 14.205729 15.592447 14.044597 15.527344 13.891602 C 15.462239 13.738607 15.372721 13.605144 15.258789 13.491211 C 15.144855 13.377279 15.011393 13.287761 14.858398 13.222656 C 14.705403 13.157553 14.544271 13.125 14.375 13.125 C 14.199219 13.125 14.036458 13.157553 13.886719 13.222656 C 13.736979 13.287761 13.605143 13.377279 13.491211 13.491211 C 13.377278 13.605144 13.28776 13.736979 13.222656 13.886719 C 13.157551 14.036459 13.124999 14.199219 13.125 14.375 C 13.124999 14.550781 13.157551 14.71517 13.222656 14.868164 C 13.28776 15.021159 13.37565 15.152995 13.486328 15.263672 C 13.597004 15.37435 13.72884 15.46224 13.881836 15.527344 C 14.03483 15.592448 14.199219 15.625 14.375 15.625 C 14.550781 15.625 14.713541 15.592448 14.863281 15.527344 C 15.01302 15.46224 15.144855 15.372722 15.258789 15.258789 C 15.372721 15.144857 15.462239 15.013021 15.527344 14.863281 C 15.592447 14.713542 15.625 14.550781 15.625 14.375 Z "), + new SettingOption(Translator.SettingsAppPreferences_Title, Translator.SettingsAppPreferences_Description, WinoPage.AppPreferencesPage,"F1 M 15.078125 1.25 C 15.566406 1.25 16.033527 1.349285 16.479492 1.547852 C 16.925455 1.74642 17.31608 2.013348 17.651367 2.348633 C 17.986652 2.68392 18.25358 3.074545 18.452148 3.520508 C 18.650715 3.966473 18.75 4.433594 18.75 4.921875 L 18.75 15.078125 C 18.75 15.566406 18.650715 16.033529 18.452148 16.479492 C 18.25358 16.925455 17.986652 17.31608 17.651367 17.651367 C 17.31608 17.986654 16.925455 18.25358 16.479492 18.452148 C 16.033527 18.650717 15.566406 18.75 15.078125 18.75 L 4.921875 18.75 C 4.433594 18.75 3.966471 18.650717 3.520508 18.452148 C 3.074544 18.25358 2.683919 17.986654 2.348633 17.651367 C 2.013346 17.31608 1.746419 16.925455 1.547852 16.479492 C 1.349284 16.033529 1.25 15.566406 1.25 15.078125 L 1.25 4.921875 C 1.25 4.433594 1.349284 3.966473 1.547852 3.520508 C 1.746419 3.074545 2.013346 2.68392 2.348633 2.348633 C 2.683919 2.013348 3.074544 1.74642 3.520508 1.547852 C 3.966471 1.349285 4.433594 1.25 4.921875 1.25 Z M 4.951172 2.5 C 4.625651 2.5 4.314778 2.566732 4.018555 2.700195 C 3.722331 2.83366 3.461914 3.012695 3.237305 3.237305 C 3.012695 3.461914 2.833659 3.722332 2.700195 4.018555 C 2.566732 4.314779 2.5 4.625651 2.5 4.951172 L 2.5 5 L 17.5 5 L 17.5 4.951172 C 17.5 4.625651 17.433268 4.314779 17.299805 4.018555 C 17.16634 3.722332 16.987305 3.461914 16.762695 3.237305 C 16.538086 3.012695 16.277668 2.83366 15.981445 2.700195 C 15.685221 2.566732 15.374349 2.5 15.048828 2.5 Z M 15.048828 17.5 C 15.374349 17.5 15.685221 17.433268 15.981445 17.299805 C 16.277668 17.166342 16.538086 16.987305 16.762695 16.762695 C 16.987305 16.538086 17.16634 16.27767 17.299805 15.981445 C 17.433268 15.685222 17.5 15.37435 17.5 15.048828 L 17.5 6.25 L 2.5 6.25 L 2.5 15.048828 C 2.5 15.37435 2.566732 15.685222 2.700195 15.981445 C 2.833659 16.27767 3.012695 16.538086 3.237305 16.762695 C 3.461914 16.987305 3.722331 17.166342 4.018555 17.299805 C 4.314778 17.433268 4.625651 17.5 4.951172 17.5 Z M 12.724609 8.935547 C 12.724609 9.195964 12.762044 9.446615 12.836914 9.6875 C 12.911783 9.928386 13.020832 10.151367 13.164062 10.356445 C 13.307291 10.561523 13.476562 10.742188 13.671875 10.898438 C 13.867188 11.054688 14.088541 11.178386 14.335938 11.269531 C 14.348957 11.367188 14.358723 11.466472 14.365234 11.567383 C 14.371744 11.668295 14.375 11.770834 14.375 11.875 C 14.375 11.979167 14.371744 12.081706 14.365234 12.182617 C 14.358723 12.283529 14.348957 12.382812 14.335938 12.480469 C 14.088541 12.571615 13.867188 12.695312 13.671875 12.851562 C 13.476562 13.007812 13.307291 13.188477 13.164062 13.393555 C 13.020832 13.598633 12.911783 13.821615 12.836914 14.0625 C 12.762044 14.303386 12.724609 14.554037 12.724609 14.814453 C 12.724609 14.886068 12.727864 14.960938 12.734375 15.039062 C 12.740885 15.117188 12.75065 15.192058 12.763672 15.263672 C 12.470703 15.511068 12.145182 15.712891 11.787109 15.869141 C 11.546224 15.621745 11.274414 15.43457 10.97168 15.307617 C 10.668945 15.180664 10.345052 15.117188 10 15.117188 C 9.654947 15.117188 9.331055 15.180664 9.02832 15.307617 C 8.725586 15.43457 8.453775 15.621745 8.212891 15.869141 C 7.854817 15.712891 7.529296 15.511068 7.236328 15.263672 C 7.249349 15.192058 7.259114 15.117188 7.265625 15.039062 C 7.272135 14.960938 7.275391 14.886068 7.275391 14.814453 C 7.275391 14.554037 7.237956 14.303386 7.163086 14.0625 C 7.088216 13.821615 6.979167 13.598633 6.835938 13.393555 C 6.692708 13.188477 6.52181 13.007812 6.323242 12.851562 C 6.124674 12.695312 5.904948 12.571615 5.664062 12.480469 C 5.651042 12.382812 5.641276 12.283529 5.634766 12.182617 C 5.628255 12.081706 5.625 11.979167 5.625 11.875 C 5.625 11.770834 5.628255 11.668295 5.634766 11.567383 C 5.641276 11.466472 5.651042 11.367188 5.664062 11.269531 C 5.904948 11.178386 6.124674 11.054688 6.323242 10.898438 C 6.52181 10.742188 6.692708 10.561523 6.835938 10.356445 C 6.979167 10.151367 7.088216 9.928386 7.163086 9.6875 C 7.237956 9.446615 7.275391 9.195964 7.275391 8.935547 C 7.275391 8.863933 7.272135 8.789062 7.265625 8.710938 C 7.259114 8.632812 7.249349 8.557943 7.236328 8.486328 C 7.529296 8.238933 7.854817 8.037109 8.212891 7.880859 C 8.440755 8.121745 8.712564 8.307292 9.02832 8.4375 C 9.344075 8.567709 9.667969 8.632812 10 8.632812 C 10.332031 8.632812 10.655924 8.567709 10.97168 8.4375 C 11.287435 8.307292 11.559244 8.121745 11.787109 7.880859 C 12.145182 8.037109 12.470703 8.238933 12.763672 8.486328 C 12.75065 8.557943 12.740885 8.632812 12.734375 8.710938 C 12.727864 8.789062 12.724609 8.863933 12.724609 8.935547 Z M 10.9375 11.875 C 10.9375 11.614584 10.846354 11.393229 10.664062 11.210938 C 10.481771 11.028646 10.260416 10.9375 10 10.9375 C 9.739583 10.9375 9.518229 11.028646 9.335938 11.210938 C 9.153646 11.393229 9.0625 11.614584 9.0625 11.875 C 9.0625 12.135417 9.153646 12.356771 9.335938 12.539062 C 9.518229 12.721354 9.739583 12.8125 10 12.8125 C 10.260416 12.8125 10.481771 12.721354 10.664062 12.539062 C 10.846354 12.356771 10.9375 12.135417 10.9375 11.875 Z "), + new SettingOption(Translator.SettingsPersonalization_Title, Translator.SettingsPersonalization_Description, WinoPage.PersonalizationPage,"F1 M 10 17.5 L 10 18.75 L 12.5 18.75 L 12.5 20 L 6.25 20 L 6.25 18.75 L 8.75 18.75 L 8.75 17.5 L 0 17.5 L 0 6.25 L 10 6.25 L 8.740234 7.5 L 1.25 7.5 L 1.25 16.25 L 17.5 16.25 L 17.5 8.75 L 18.75 7.5 L 18.75 17.5 Z M 5 13.75 C 5.175781 13.75 5.338542 13.717448 5.488281 13.652344 C 5.638021 13.58724 5.769856 13.497722 5.883789 13.383789 C 5.997721 13.269857 6.087239 13.138021 6.152344 12.988281 C 6.217448 12.838542 6.25 12.675781 6.25 12.5 C 6.25 12.18099 6.306966 11.878256 6.420898 11.591797 C 6.53483 11.305339 6.69108 11.051433 6.889648 10.830078 C 7.088216 10.608725 7.322591 10.424805 7.592773 10.27832 C 7.862955 10.131836 8.157552 10.042318 8.476562 10.009766 L 15.419922 3.066406 C 15.602213 2.884115 15.813802 2.744141 16.054688 2.646484 C 16.295572 2.548828 16.542969 2.5 16.796875 2.5 C 17.063801 2.5 17.31608 2.550457 17.553711 2.651367 C 17.79134 2.752279 17.998047 2.890625 18.173828 3.066406 C 18.349609 3.242188 18.487955 3.448895 18.588867 3.686523 C 18.689777 3.924154 18.740234 4.179688 18.740234 4.453125 C 18.740234 4.707031 18.691406 4.954428 18.59375 5.195312 C 18.496094 5.436199 18.356119 5.647787 18.173828 5.830078 L 11.230469 12.773438 C 11.197916 13.085938 11.110025 13.378906 10.966797 13.652344 C 10.823567 13.925781 10.639648 14.161784 10.415039 14.360352 C 10.19043 14.55892 9.936523 14.71517 9.65332 14.829102 C 9.370117 14.943034 9.06901 15 8.75 15 L 2.5 15 L 2.5 13.75 Z M 16.796875 3.75 C 16.608072 3.75 16.445312 3.818359 16.308594 3.955078 L 11.962891 8.300781 C 12.333984 8.58724 12.66276 8.916016 12.949219 9.287109 L 17.294922 4.941406 C 17.431641 4.804688 17.5 4.641928 17.5 4.453125 C 17.5 4.257812 17.430012 4.091797 17.290039 3.955078 C 17.150064 3.818359 16.985676 3.75 16.796875 3.75 Z M 10.175781 10.087891 C 10.572916 10.348308 10.901692 10.677084 11.162109 11.074219 L 12.060547 10.185547 C 11.787109 9.794922 11.455078 9.462891 11.064453 9.189453 Z M 10 12.5 C 10 12.324219 9.967447 12.161459 9.902344 12.011719 C 9.837239 11.861979 9.747721 11.730144 9.633789 11.616211 C 9.519856 11.502279 9.388021 11.412761 9.238281 11.347656 C 9.088541 11.282553 8.925781 11.25 8.75 11.25 C 8.574219 11.25 8.411458 11.282553 8.261719 11.347656 C 8.111979 11.412761 7.980143 11.502279 7.866211 11.616211 C 7.752278 11.730144 7.66276 11.861979 7.597656 12.011719 C 7.532552 12.161459 7.5 12.324219 7.5 12.5 C 7.5 12.942709 7.389322 13.359375 7.167969 13.75 L 8.75 13.75 C 8.919271 13.75 9.080403 13.717448 9.233398 13.652344 C 9.386393 13.58724 9.519856 13.497722 9.633789 13.383789 C 9.747721 13.269857 9.837239 13.136394 9.902344 12.983398 C 9.967447 12.830404 10 12.669271 10 12.5 Z "), + new SettingOption(Translator.SettingsCalendarSettings_Title, Translator.SettingsCalendarSettings_Description, WinoPage.CalendarSettingsPage,"F1 M 1.25 3.75 C 1.25 3.404949 1.315104 3.081055 1.445312 2.77832 C 1.575521 2.475586 1.754557 2.210287 1.982422 1.982422 C 2.210286 1.754559 2.475586 1.575521 2.77832 1.445312 C 3.081055 1.315105 3.404948 1.25 3.75 1.25 L 16.25 1.25 C 16.595051 1.25 16.918945 1.315105 17.22168 1.445312 C 17.524414 1.575521 17.789713 1.754559 18.017578 1.982422 C 18.245441 2.210287 18.424479 2.475586 18.554688 2.77832 C 18.684895 3.081055 18.75 3.404949 18.75 3.75 L 18.75 5 L 1.25 5 Z M 3.75 18.75 C 3.404948 18.75 3.081055 18.684896 2.77832 18.554688 C 2.475586 18.424479 2.210286 18.245443 1.982422 18.017578 C 1.754557 17.789713 1.575521 17.524414 1.445312 17.22168 C 1.315104 16.918945 1.25 16.595053 1.25 16.25 L 1.25 6.25 L 18.75 6.25 L 18.75 16.25 C 18.75 16.595053 18.684895 16.918945 18.554688 17.22168 C 18.424479 17.524414 18.245441 17.789713 18.017578 18.017578 C 17.789713 18.245443 17.524414 18.424479 17.22168 18.554688 C 16.918945 18.684896 16.595051 18.75 16.25 18.75 Z M 7.65625 9.990234 C 7.65625 9.794922 7.618815 9.612631 7.543945 9.443359 C 7.469075 9.274089 7.368164 9.125977 7.241211 8.999023 C 7.114258 8.87207 6.966146 8.772787 6.796875 8.701172 C 6.627604 8.629558 6.445312 8.59375 6.25 8.59375 C 6.054688 8.59375 5.870768 8.631186 5.698242 8.706055 C 5.525716 8.780925 5.375977 8.881836 5.249023 9.008789 C 5.12207 9.135742 5.022786 9.283854 4.951172 9.453125 C 4.879557 9.622396 4.84375 9.804688 4.84375 10 C 4.84375 10.195312 4.879557 10.377604 4.951172 10.546875 C 5.022786 10.716146 5.12207 10.864258 5.249023 10.991211 C 5.375977 11.118164 5.524088 11.219076 5.693359 11.293945 C 5.86263 11.368815 6.044922 11.40625 6.240234 11.40625 C 6.442057 11.40625 6.629231 11.370443 6.801758 11.298828 C 6.974283 11.227214 7.124023 11.12793 7.250977 11.000977 C 7.377929 10.874023 7.477213 10.724284 7.548828 10.551758 C 7.620442 10.379232 7.65625 10.192058 7.65625 9.990234 Z M 11.40625 9.990234 C 11.40625 9.794922 11.368814 9.612631 11.293945 9.443359 C 11.219075 9.274089 11.118164 9.125977 10.991211 8.999023 C 10.864258 8.87207 10.716146 8.772787 10.546875 8.701172 C 10.377604 8.629558 10.195312 8.59375 10 8.59375 C 9.804688 8.59375 9.620768 8.631186 9.448242 8.706055 C 9.275716 8.780925 9.125977 8.881836 8.999023 9.008789 C 8.87207 9.135742 8.772786 9.283854 8.701172 9.453125 C 8.629557 9.622396 8.59375 9.804688 8.59375 10 C 8.59375 10.195312 8.629557 10.377604 8.701172 10.546875 C 8.772786 10.716146 8.87207 10.864258 8.999023 10.991211 C 9.125977 11.118164 9.274088 11.219076 9.443359 11.293945 C 9.61263 11.368815 9.794922 11.40625 9.990234 11.40625 C 10.192057 11.40625 10.379231 11.370443 10.551758 11.298828 C 10.724283 11.227214 10.874023 11.12793 11.000977 11.000977 C 11.12793 10.874023 11.227213 10.724284 11.298828 10.551758 C 11.370442 10.379232 11.40625 10.192058 11.40625 9.990234 Z M 15.15625 9.990234 C 15.15625 9.794922 15.118814 9.612631 15.043945 9.443359 C 14.969075 9.274089 14.868164 9.125977 14.741211 8.999023 C 14.614258 8.87207 14.466146 8.772787 14.296875 8.701172 C 14.127604 8.629558 13.945312 8.59375 13.75 8.59375 C 13.554688 8.59375 13.370768 8.631186 13.198242 8.706055 C 13.025716 8.780925 12.875977 8.881836 12.749023 9.008789 C 12.62207 9.135742 12.522786 9.283854 12.451172 9.453125 C 12.379557 9.622396 12.34375 9.804688 12.34375 10 C 12.34375 10.195312 12.379557 10.377604 12.451172 10.546875 C 12.522786 10.716146 12.62207 10.864258 12.749023 10.991211 C 12.875977 11.118164 13.024088 11.219076 13.193359 11.293945 C 13.362629 11.368815 13.544921 11.40625 13.740234 11.40625 C 13.942057 11.40625 14.129231 11.370443 14.301758 11.298828 C 14.474283 11.227214 14.624022 11.12793 14.750977 11.000977 C 14.877929 10.874023 14.977213 10.724284 15.048828 10.551758 C 15.120442 10.379232 15.15625 10.192058 15.15625 9.990234 Z M 7.65625 13.740234 C 7.65625 13.544922 7.618815 13.362631 7.543945 13.193359 C 7.469075 13.024089 7.368164 12.875977 7.241211 12.749023 C 7.114258 12.62207 6.966146 12.522787 6.796875 12.451172 C 6.627604 12.379558 6.445312 12.34375 6.25 12.34375 C 6.054688 12.34375 5.870768 12.381186 5.698242 12.456055 C 5.525716 12.530925 5.375977 12.631836 5.249023 12.758789 C 5.12207 12.885742 5.022786 13.033854 4.951172 13.203125 C 4.879557 13.372396 4.84375 13.554688 4.84375 13.75 C 4.84375 13.945312 4.879557 14.127604 4.951172 14.296875 C 5.022786 14.466146 5.12207 14.614258 5.249023 14.741211 C 5.375977 14.868164 5.524088 14.969076 5.693359 15.043945 C 5.86263 15.118815 6.044922 15.15625 6.240234 15.15625 C 6.442057 15.15625 6.629231 15.120443 6.801758 15.048828 C 6.974283 14.977214 7.124023 14.87793 7.250977 14.750977 C 7.377929 14.624023 7.477213 14.474284 7.548828 14.301758 C 7.620442 14.129232 7.65625 13.942058 7.65625 13.740234 Z M 11.40625 13.740234 C 11.40625 13.544922 11.368814 13.362631 11.293945 13.193359 C 11.219075 13.024089 11.118164 12.875977 10.991211 12.749023 C 10.864258 12.62207 10.716146 12.522787 10.546875 12.451172 C 10.377604 12.379558 10.195312 12.34375 10 12.34375 C 9.804688 12.34375 9.620768 12.381186 9.448242 12.456055 C 9.275716 12.530925 9.125977 12.631836 8.999023 12.758789 C 8.87207 12.885742 8.772786 13.033854 8.701172 13.203125 C 8.629557 13.372396 8.59375 13.554688 8.59375 13.75 C 8.59375 13.945312 8.629557 14.127604 8.701172 14.296875 C 8.772786 14.466146 8.87207 14.614258 8.999023 14.741211 C 9.125977 14.868164 9.274088 14.969076 9.443359 15.043945 C 9.61263 15.118815 9.794922 15.15625 9.990234 15.15625 C 10.192057 15.15625 10.379231 15.120443 10.551758 15.048828 C 10.724283 14.977214 10.874023 14.87793 11.000977 14.750977 C 11.12793 14.624023 11.227213 14.474284 11.298828 14.301758 C 11.370442 14.129232 11.40625 13.942058 11.40625 13.740234 Z "), + new SettingOption(Translator.SettingsAbout_Title, Translator.SettingsAbout_Description, WinoPage.AboutPage,"F1 M 9.375 18.75 C 8.509114 18.75 7.677409 18.639322 6.879883 18.417969 C 6.082356 18.196615 5.335286 17.882486 4.638672 17.475586 C 3.942057 17.068686 3.308919 16.580404 2.739258 16.010742 C 2.169596 15.441081 1.681315 14.807943 1.274414 14.111328 C 0.867513 13.414714 0.553385 12.667644 0.332031 11.870117 C 0.110677 11.072592 0 10.240886 0 9.375 C 0 8.509115 0.110677 7.677409 0.332031 6.879883 C 0.553385 6.082357 0.867513 5.335287 1.274414 4.638672 C 1.681315 3.942059 2.169596 3.30892 2.739258 2.739258 C 3.308919 2.169598 3.942057 1.681316 4.638672 1.274414 C 5.335286 0.867514 6.082356 0.553387 6.879883 0.332031 C 7.677409 0.110678 8.509114 0 9.375 0 C 10.240885 0 11.072591 0.110678 11.870117 0.332031 C 12.667643 0.553387 13.414713 0.867514 14.111328 1.274414 C 14.807942 1.681316 15.44108 2.169598 16.010742 2.739258 C 16.580402 3.30892 17.068684 3.942059 17.475586 4.638672 C 17.882486 5.335287 18.196613 6.082357 18.417969 6.879883 C 18.639322 7.677409 18.75 8.509115 18.75 9.375 C 18.75 10.240886 18.639322 11.072592 18.417969 11.870117 C 18.196613 12.667644 17.882486 13.414714 17.475586 14.111328 C 17.068684 14.807943 16.580402 15.441081 16.010742 16.010742 C 15.44108 16.580404 14.807942 17.068686 14.111328 17.475586 C 13.414713 17.882486 12.667643 18.196615 11.870117 18.417969 C 11.072591 18.639322 10.240885 18.75 9.375 18.75 Z M 9.375 1.25 C 8.626302 1.25 7.906901 1.347656 7.216797 1.542969 C 6.526692 1.738281 5.880533 2.011719 5.27832 2.363281 C 4.676106 2.714844 4.127604 3.138021 3.632812 3.632812 C 3.138021 4.127604 2.714844 4.676107 2.363281 5.27832 C 2.011719 5.880534 1.738281 6.52832 1.542969 7.22168 C 1.347656 7.915039 1.25 8.632812 1.25 9.375 C 1.25 10.117188 1.347656 10.834961 1.542969 11.52832 C 1.738281 12.22168 2.011719 12.869467 2.363281 13.47168 C 2.714844 14.073894 3.138021 14.622396 3.632812 15.117188 C 4.127604 15.611979 4.676106 16.035156 5.27832 16.386719 C 5.880533 16.738281 6.526692 17.011719 7.216797 17.207031 C 7.906901 17.402344 8.626302 17.5 9.375 17.5 C 10.117188 17.5 10.834961 17.402344 11.52832 17.207031 C 12.221679 17.011719 12.869465 16.738281 13.47168 16.386719 C 14.073893 16.035156 14.622396 15.611979 15.117188 15.117188 C 15.611979 14.622396 16.035156 14.073894 16.386719 13.47168 C 16.738281 12.869467 17.011719 12.223308 17.207031 11.533203 C 17.402344 10.8431 17.5 10.123698 17.5 9.375 C 17.5 8.632812 17.402344 7.915039 17.207031 7.22168 C 17.011719 6.52832 16.738281 5.880534 16.386719 5.27832 C 16.035156 4.676107 15.611979 4.127604 15.117188 3.632812 C 14.622396 3.138021 14.073893 2.714844 13.47168 2.363281 C 12.869465 2.011719 12.221679 1.738281 11.52832 1.542969 C 10.834961 1.347656 10.117188 1.25 9.375 1.25 Z M 8.75 7.5 L 10 7.5 L 10 13.75 L 8.75 13.75 Z M 8.75 5 L 10 5 L 10 6.25 L 8.75 6.25 Z "), + }; } diff --git a/Wino.Calendar/Styles/WinoCalendarResources.xaml.cs b/Wino.Calendar/Styles/WinoCalendarResources.xaml.cs index 7a24cde1..fd055e7b 100644 --- a/Wino.Calendar/Styles/WinoCalendarResources.xaml.cs +++ b/Wino.Calendar/Styles/WinoCalendarResources.xaml.cs @@ -1,12 +1,11 @@ using Windows.UI.Xaml; -namespace Wino.Calendar.Styles +namespace Wino.Calendar.Styles; + +public sealed partial class WinoCalendarResources : ResourceDictionary { - public sealed partial class WinoCalendarResources : ResourceDictionary + public WinoCalendarResources() { - public WinoCalendarResources() - { - this.InitializeComponent(); - } + this.InitializeComponent(); } } diff --git a/Wino.Calendar/Views/Abstract/AccountDetailsPageAbstract.cs b/Wino.Calendar/Views/Abstract/AccountDetailsPageAbstract.cs index ab7aa247..de113056 100644 --- a/Wino.Calendar/Views/Abstract/AccountDetailsPageAbstract.cs +++ b/Wino.Calendar/Views/Abstract/AccountDetailsPageAbstract.cs @@ -1,7 +1,6 @@ using Wino.Calendar.ViewModels; using Wino.Core.UWP; -namespace Wino.Calendar.Views.Abstract -{ - public abstract class AccountDetailsPageAbstract : BasePage { } -} +namespace Wino.Calendar.Views.Abstract; + +public abstract class AccountDetailsPageAbstract : BasePage { } diff --git a/Wino.Calendar/Views/Abstract/AccountManagementPageAbstract.cs b/Wino.Calendar/Views/Abstract/AccountManagementPageAbstract.cs index 50509dd2..e7edd065 100644 --- a/Wino.Calendar/Views/Abstract/AccountManagementPageAbstract.cs +++ b/Wino.Calendar/Views/Abstract/AccountManagementPageAbstract.cs @@ -1,7 +1,6 @@ using Wino.Calendar.ViewModels; using Wino.Core.UWP; -namespace Wino.Calendar.Views.Abstract -{ - public class AccountManagementPageAbstract : BasePage { } -} +namespace Wino.Calendar.Views.Abstract; + +public partial class AccountManagementPageAbstract : BasePage { } diff --git a/Wino.Calendar/Views/Abstract/AppShellAbstract.cs b/Wino.Calendar/Views/Abstract/AppShellAbstract.cs index 39eb8c5c..8ab81b95 100644 --- a/Wino.Calendar/Views/Abstract/AppShellAbstract.cs +++ b/Wino.Calendar/Views/Abstract/AppShellAbstract.cs @@ -1,7 +1,6 @@ using Wino.Calendar.ViewModels; using Wino.Core.UWP; -namespace Wino.Calendar.Views.Abstract -{ - public abstract class AppShellAbstract : BasePage { } -} +namespace Wino.Calendar.Views.Abstract; + +public abstract class AppShellAbstract : BasePage { } diff --git a/Wino.Calendar/Views/Abstract/CalendarPageAbstract.cs b/Wino.Calendar/Views/Abstract/CalendarPageAbstract.cs index 9c40d5d6..abd1fb5f 100644 --- a/Wino.Calendar/Views/Abstract/CalendarPageAbstract.cs +++ b/Wino.Calendar/Views/Abstract/CalendarPageAbstract.cs @@ -1,7 +1,6 @@ using Wino.Calendar.ViewModels; using Wino.Core.UWP; -namespace Wino.Calendar.Views.Abstract -{ - public abstract class CalendarPageAbstract : BasePage { } -} +namespace Wino.Calendar.Views.Abstract; + +public abstract class CalendarPageAbstract : BasePage { } diff --git a/Wino.Calendar/Views/Abstract/CalendarSettingsPageAbstract.cs b/Wino.Calendar/Views/Abstract/CalendarSettingsPageAbstract.cs index 00c511a6..47eef1d4 100644 --- a/Wino.Calendar/Views/Abstract/CalendarSettingsPageAbstract.cs +++ b/Wino.Calendar/Views/Abstract/CalendarSettingsPageAbstract.cs @@ -1,7 +1,6 @@ using Wino.Calendar.ViewModels; using Wino.Core.UWP; -namespace Wino.Calendar.Views.Abstract -{ - public abstract class CalendarSettingsPageAbstract : BasePage { } -} +namespace Wino.Calendar.Views.Abstract; + +public abstract class CalendarSettingsPageAbstract : BasePage { } diff --git a/Wino.Calendar/Views/Abstract/EventDetailsPageAbstract.cs b/Wino.Calendar/Views/Abstract/EventDetailsPageAbstract.cs index 512d46b5..9b9df6bb 100644 --- a/Wino.Calendar/Views/Abstract/EventDetailsPageAbstract.cs +++ b/Wino.Calendar/Views/Abstract/EventDetailsPageAbstract.cs @@ -1,7 +1,6 @@ using Wino.Calendar.ViewModels; using Wino.Core.UWP; -namespace Wino.Calendar.Views.Abstract -{ - public abstract class EventDetailsPageAbstract : BasePage { } -} +namespace Wino.Calendar.Views.Abstract; + +public abstract class EventDetailsPageAbstract : BasePage { } diff --git a/Wino.Calendar/Views/Abstract/PersonalizationPageAbstract.cs b/Wino.Calendar/Views/Abstract/PersonalizationPageAbstract.cs index 495dd8b9..b970f09a 100644 --- a/Wino.Calendar/Views/Abstract/PersonalizationPageAbstract.cs +++ b/Wino.Calendar/Views/Abstract/PersonalizationPageAbstract.cs @@ -1,7 +1,6 @@ using Wino.Core.UWP; using Wino.Core.ViewModels; -namespace Wino.Calendar.Views.Abstract -{ - public class PersonalizationPageAbstract : BasePage { } -} +namespace Wino.Calendar.Views.Abstract; + +public partial class PersonalizationPageAbstract : BasePage { } diff --git a/Wino.Calendar/Views/Account/AccountManagementPage.xaml.cs b/Wino.Calendar/Views/Account/AccountManagementPage.xaml.cs index 97344170..4e63cca5 100644 --- a/Wino.Calendar/Views/Account/AccountManagementPage.xaml.cs +++ b/Wino.Calendar/Views/Account/AccountManagementPage.xaml.cs @@ -1,12 +1,11 @@ using Wino.Calendar.Views.Abstract; -namespace Wino.Calendar.Views.Account +namespace Wino.Calendar.Views.Account; + +public sealed partial class AccountManagementPage : AccountManagementPageAbstract { - public sealed partial class AccountManagementPage : AccountManagementPageAbstract + public AccountManagementPage() { - public AccountManagementPage() - { - InitializeComponent(); - } + InitializeComponent(); } } diff --git a/Wino.Calendar/Views/AppShell.xaml.cs b/Wino.Calendar/Views/AppShell.xaml.cs index e9c579f5..b68a2ca9 100644 --- a/Wino.Calendar/Views/AppShell.xaml.cs +++ b/Wino.Calendar/Views/AppShell.xaml.cs @@ -5,50 +5,49 @@ using Wino.Calendar.Views.Abstract; using Wino.Core.UWP; using Wino.Messaging.Client.Calendar; -namespace Wino.Calendar.Views +namespace Wino.Calendar.Views; + +public sealed partial class AppShell : AppShellAbstract, + IRecipient { - public sealed partial class AppShell : AppShellAbstract, - IRecipient + private const string STATE_HorizontalCalendar = "HorizontalCalendar"; + private const string STATE_VerticalCalendar = "VerticalCalendar"; + + public Frame GetShellFrame() => ShellFrame; + + public AppShell() { - private const string STATE_HorizontalCalendar = "HorizontalCalendar"; - private const string STATE_VerticalCalendar = "VerticalCalendar"; + InitializeComponent(); - public Frame GetShellFrame() => ShellFrame; - - public AppShell() - { - InitializeComponent(); - - Window.Current.SetTitleBar(DragArea); - ManageCalendarDisplayType(); - } - - private void ManageCalendarDisplayType() - { - // Go to different states based on the display type. - if (ViewModel.IsVerticalCalendar) - { - VisualStateManager.GoToState(this, STATE_VerticalCalendar, false); - } - else - { - VisualStateManager.GoToState(this, STATE_HorizontalCalendar, false); - } - } - - private void PreviousDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoPreviousDateRequestedMessage()); - - private void NextDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoNextDateRequestedMessage()); - - public void Receive(CalendarDisplayTypeChangedMessage message) - { - ManageCalendarDisplayType(); - } - - private void ShellFrameContentNavigated(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e) - => RealAppBar.ShellFrameContent = (e.Content as BasePage).ShellContent; - - private void AppBarBackButtonClicked(Core.UWP.Controls.WinoAppTitleBar sender, RoutedEventArgs args) - => ViewModel.NavigationService.GoBack(); + Window.Current.SetTitleBar(DragArea); + ManageCalendarDisplayType(); } + + private void ManageCalendarDisplayType() + { + // Go to different states based on the display type. + if (ViewModel.IsVerticalCalendar) + { + VisualStateManager.GoToState(this, STATE_VerticalCalendar, false); + } + else + { + VisualStateManager.GoToState(this, STATE_HorizontalCalendar, false); + } + } + + private void PreviousDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoPreviousDateRequestedMessage()); + + private void NextDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoNextDateRequestedMessage()); + + public void Receive(CalendarDisplayTypeChangedMessage message) + { + ManageCalendarDisplayType(); + } + + private void ShellFrameContentNavigated(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e) + => RealAppBar.ShellFrameContent = (e.Content as BasePage).ShellContent; + + private void AppBarBackButtonClicked(Core.UWP.Controls.WinoAppTitleBar sender, RoutedEventArgs args) + => ViewModel.NavigationService.GoBack(); } diff --git a/Wino.Calendar/Views/CalendarPage.xaml.cs b/Wino.Calendar/Views/CalendarPage.xaml.cs index 25a9d8ae..d8ce61ee 100644 --- a/Wino.Calendar/Views/CalendarPage.xaml.cs +++ b/Wino.Calendar/Views/CalendarPage.xaml.cs @@ -9,153 +9,152 @@ using Wino.Core.Domain.Enums; using Wino.Core.Domain.Models.Calendar; using Wino.Messaging.Client.Calendar; -namespace Wino.Calendar.Views +namespace Wino.Calendar.Views; + +public sealed partial class CalendarPage : CalendarPageAbstract, + IRecipient, + IRecipient, + IRecipient, + IRecipient { - public sealed partial class CalendarPage : CalendarPageAbstract, - IRecipient, - IRecipient, - IRecipient, - IRecipient + private const int PopupDialogOffset = 12; + + public CalendarPage() { - private const int PopupDialogOffset = 12; + InitializeComponent(); + NavigationCacheMode = NavigationCacheMode.Enabled; - public CalendarPage() + ViewModel.DetailsShowCalendarItemChanged += CalendarItemDetailContextChanged; + } + + private void CalendarItemDetailContextChanged(object sender, EventArgs e) + { + if (ViewModel.DisplayDetailsCalendarItemViewModel != null) { - InitializeComponent(); - NavigationCacheMode = NavigationCacheMode.Enabled; + var control = CalendarControl.GetCalendarItemControl(ViewModel.DisplayDetailsCalendarItemViewModel); - ViewModel.DetailsShowCalendarItemChanged += CalendarItemDetailContextChanged; - } - - private void CalendarItemDetailContextChanged(object sender, EventArgs e) - { - if (ViewModel.DisplayDetailsCalendarItemViewModel != null) + if (control != null) { - var control = CalendarControl.GetCalendarItemControl(ViewModel.DisplayDetailsCalendarItemViewModel); - - if (control != null) - { - EventDetailsPopup.PlacementTarget = control; - } + EventDetailsPopup.PlacementTarget = control; } } - - public void Receive(ScrollToHourMessage message) => CalendarControl.NavigateToHour(message.TimeSpan); - public void Receive(ScrollToDateMessage message) => CalendarControl.NavigateToDay(message.Date); - public void Receive(GoNextDateRequestedMessage message) => CalendarControl.GoNextRange(); - public void Receive(GoPreviousDateRequestedMessage message) => CalendarControl.GoPreviousRange(); - - protected override void OnNavigatedTo(NavigationEventArgs e) - { - base.OnNavigatedTo(e); - - if (e.NavigationMode == NavigationMode.Back) return; - - if (e.Parameter is CalendarPageNavigationArgs args) - { - if (args.RequestDefaultNavigation) - { - // Go today. - WeakReferenceMessenger.Default.Send(new LoadCalendarMessage(DateTime.Now.Date, CalendarInitInitiative.App)); - } - else - { - // Go specified date. - WeakReferenceMessenger.Default.Send(new LoadCalendarMessage(args.NavigationDate, CalendarInitInitiative.User)); - } - } - } - - private void CellSelected(object sender, TimelineCellSelectedArgs e) - { - // Dismiss event details if exists and cancel the selection. - // This is to prevent the event details from being displayed when the user clicks somewhere else. - - if (EventDetailsPopup.IsOpen) - { - CalendarControl.UnselectActiveTimelineCell(); - ViewModel.DisplayDetailsCalendarItemViewModel = null; - - return; - } - - ViewModel.SelectedQuickEventDate = e.ClickedDate; - - TeachingTipPositionerGrid.Width = e.CellSize.Width; - TeachingTipPositionerGrid.Height = e.CellSize.Height; - - Canvas.SetLeft(TeachingTipPositionerGrid, e.PositionerPoint.X); - Canvas.SetTop(TeachingTipPositionerGrid, e.PositionerPoint.Y); - - // Adjust the start and end time in the flyout. - var startTime = ViewModel.SelectedQuickEventDate.Value.TimeOfDay; - var endTime = startTime.Add(TimeSpan.FromMinutes(30)); - - ViewModel.SelectQuickEventTimeRange(startTime, endTime); - - QuickEventPopupDialog.IsOpen = true; - } - - private void CellUnselected(object sender, TimelineCellUnselectedArgs e) - { - QuickEventPopupDialog.IsOpen = false; - } - - private void QuickEventAccountSelectorSelectionChanged(object sender, SelectionChangedEventArgs e) - { - QuickEventAccountSelectorFlyout.Hide(); - } - - private void QuickEventPopupClosed(object sender, object e) - { - // Reset the timeline selection when the tip is closed. - CalendarControl.ResetTimelineSelection(); - } - - private void PopupPlacementChanged(object sender, object e) - { - if (sender is Popup senderPopup) - { - // When the quick event Popup is positioned for different calendar types, - // we must adjust the offset to make sure the tip is not hidden and has nice - // spacing from the cell. - - switch (senderPopup.ActualPlacement) - { - case PopupPlacementMode.Top: - senderPopup.VerticalOffset = PopupDialogOffset * -1; - break; - case PopupPlacementMode.Bottom: - senderPopup.VerticalOffset = PopupDialogOffset; - break; - case PopupPlacementMode.Left: - senderPopup.HorizontalOffset = PopupDialogOffset * -1; - break; - case PopupPlacementMode.Right: - senderPopup.HorizontalOffset = PopupDialogOffset; - break; - default: - break; - } - } - - } - - private void StartTimeDurationSubmitted(ComboBox sender, ComboBoxTextSubmittedEventArgs args) - => ViewModel.SelectedStartTimeString = args.Text; - - private void EndTimeDurationSubmitted(ComboBox sender, ComboBoxTextSubmittedEventArgs args) - => ViewModel.SelectedEndTimeString = args.Text; - - private void EventDetailsPopupClosed(object sender, object e) - { - ViewModel.DisplayDetailsCalendarItemViewModel = null; - } - - private void CalendarScrolling(object sender, EventArgs e) - { - // In case of scrolling, we must dismiss the event details dialog. - ViewModel.DisplayDetailsCalendarItemViewModel = null; - } + } + + public void Receive(ScrollToHourMessage message) => CalendarControl.NavigateToHour(message.TimeSpan); + public void Receive(ScrollToDateMessage message) => CalendarControl.NavigateToDay(message.Date); + public void Receive(GoNextDateRequestedMessage message) => CalendarControl.GoNextRange(); + public void Receive(GoPreviousDateRequestedMessage message) => CalendarControl.GoPreviousRange(); + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + base.OnNavigatedTo(e); + + if (e.NavigationMode == NavigationMode.Back) return; + + if (e.Parameter is CalendarPageNavigationArgs args) + { + if (args.RequestDefaultNavigation) + { + // Go today. + WeakReferenceMessenger.Default.Send(new LoadCalendarMessage(DateTime.Now.Date, CalendarInitInitiative.App)); + } + else + { + // Go specified date. + WeakReferenceMessenger.Default.Send(new LoadCalendarMessage(args.NavigationDate, CalendarInitInitiative.User)); + } + } + } + + private void CellSelected(object sender, TimelineCellSelectedArgs e) + { + // Dismiss event details if exists and cancel the selection. + // This is to prevent the event details from being displayed when the user clicks somewhere else. + + if (EventDetailsPopup.IsOpen) + { + CalendarControl.UnselectActiveTimelineCell(); + ViewModel.DisplayDetailsCalendarItemViewModel = null; + + return; + } + + ViewModel.SelectedQuickEventDate = e.ClickedDate; + + TeachingTipPositionerGrid.Width = e.CellSize.Width; + TeachingTipPositionerGrid.Height = e.CellSize.Height; + + Canvas.SetLeft(TeachingTipPositionerGrid, e.PositionerPoint.X); + Canvas.SetTop(TeachingTipPositionerGrid, e.PositionerPoint.Y); + + // Adjust the start and end time in the flyout. + var startTime = ViewModel.SelectedQuickEventDate.Value.TimeOfDay; + var endTime = startTime.Add(TimeSpan.FromMinutes(30)); + + ViewModel.SelectQuickEventTimeRange(startTime, endTime); + + QuickEventPopupDialog.IsOpen = true; + } + + private void CellUnselected(object sender, TimelineCellUnselectedArgs e) + { + QuickEventPopupDialog.IsOpen = false; + } + + private void QuickEventAccountSelectorSelectionChanged(object sender, SelectionChangedEventArgs e) + { + QuickEventAccountSelectorFlyout.Hide(); + } + + private void QuickEventPopupClosed(object sender, object e) + { + // Reset the timeline selection when the tip is closed. + CalendarControl.ResetTimelineSelection(); + } + + private void PopupPlacementChanged(object sender, object e) + { + if (sender is Popup senderPopup) + { + // When the quick event Popup is positioned for different calendar types, + // we must adjust the offset to make sure the tip is not hidden and has nice + // spacing from the cell. + + switch (senderPopup.ActualPlacement) + { + case PopupPlacementMode.Top: + senderPopup.VerticalOffset = PopupDialogOffset * -1; + break; + case PopupPlacementMode.Bottom: + senderPopup.VerticalOffset = PopupDialogOffset; + break; + case PopupPlacementMode.Left: + senderPopup.HorizontalOffset = PopupDialogOffset * -1; + break; + case PopupPlacementMode.Right: + senderPopup.HorizontalOffset = PopupDialogOffset; + break; + default: + break; + } + } + + } + + private void StartTimeDurationSubmitted(ComboBox sender, ComboBoxTextSubmittedEventArgs args) + => ViewModel.SelectedStartTimeString = args.Text; + + private void EndTimeDurationSubmitted(ComboBox sender, ComboBoxTextSubmittedEventArgs args) + => ViewModel.SelectedEndTimeString = args.Text; + + private void EventDetailsPopupClosed(object sender, object e) + { + ViewModel.DisplayDetailsCalendarItemViewModel = null; + } + + private void CalendarScrolling(object sender, EventArgs e) + { + // In case of scrolling, we must dismiss the event details dialog. + ViewModel.DisplayDetailsCalendarItemViewModel = null; } } diff --git a/Wino.Calendar/Views/EventDetailsPage.xaml.cs b/Wino.Calendar/Views/EventDetailsPage.xaml.cs index e7ffa405..ac3cff0a 100644 --- a/Wino.Calendar/Views/EventDetailsPage.xaml.cs +++ b/Wino.Calendar/Views/EventDetailsPage.xaml.cs @@ -1,13 +1,12 @@ using Wino.Calendar.Views.Abstract; -namespace Wino.Calendar.Views +namespace Wino.Calendar.Views; + +public sealed partial class EventDetailsPage : EventDetailsPageAbstract { - public sealed partial class EventDetailsPage : EventDetailsPageAbstract + public EventDetailsPage() { - public EventDetailsPage() - { - this.InitializeComponent(); - } + this.InitializeComponent(); } } diff --git a/Wino.Calendar/Views/Settings/AccountDetailsPage.xaml.cs b/Wino.Calendar/Views/Settings/AccountDetailsPage.xaml.cs index ae135ef2..0cb918b0 100644 --- a/Wino.Calendar/Views/Settings/AccountDetailsPage.xaml.cs +++ b/Wino.Calendar/Views/Settings/AccountDetailsPage.xaml.cs @@ -1,12 +1,11 @@ using Wino.Calendar.Views.Abstract; -namespace Wino.Calendar.Views.Settings +namespace Wino.Calendar.Views.Settings; + +public sealed partial class AccountDetailsPage : AccountDetailsPageAbstract { - public sealed partial class AccountDetailsPage : AccountDetailsPageAbstract + public AccountDetailsPage() { - public AccountDetailsPage() - { - this.InitializeComponent(); - } + this.InitializeComponent(); } } diff --git a/Wino.Calendar/Views/Settings/CalendarSettingsPage.xaml.cs b/Wino.Calendar/Views/Settings/CalendarSettingsPage.xaml.cs index bf3f728d..1114ea5d 100644 --- a/Wino.Calendar/Views/Settings/CalendarSettingsPage.xaml.cs +++ b/Wino.Calendar/Views/Settings/CalendarSettingsPage.xaml.cs @@ -1,13 +1,12 @@ using Wino.Calendar.Views.Abstract; -namespace Wino.Calendar.Views.Settings +namespace Wino.Calendar.Views.Settings; + +public sealed partial class CalendarSettingsPage : CalendarSettingsPageAbstract { - public sealed partial class CalendarSettingsPage : CalendarSettingsPageAbstract + public CalendarSettingsPage() { - public CalendarSettingsPage() - { - InitializeComponent(); - } + InitializeComponent(); } } diff --git a/Wino.Calendar/Views/Settings/PersonalizationPage.xaml.cs b/Wino.Calendar/Views/Settings/PersonalizationPage.xaml.cs index 93efabb8..a9518467 100644 --- a/Wino.Calendar/Views/Settings/PersonalizationPage.xaml.cs +++ b/Wino.Calendar/Views/Settings/PersonalizationPage.xaml.cs @@ -1,12 +1,11 @@ using Wino.Calendar.Views.Abstract; -namespace Wino.Calendar.Views.Settings +namespace Wino.Calendar.Views.Settings; + +public sealed partial class PersonalizationPage : PersonalizationPageAbstract { - public sealed partial class PersonalizationPage : PersonalizationPageAbstract + public PersonalizationPage() { - public PersonalizationPage() - { - this.InitializeComponent(); - } + this.InitializeComponent(); } } diff --git a/Wino.Core.Domain/Models/DomainModelsJsonContext.cs b/Wino.Core.Domain/Models/DomainModelsJsonContext.cs index 7e9c5fa8..3e38742a 100644 --- a/Wino.Core.Domain/Models/DomainModelsJsonContext.cs +++ b/Wino.Core.Domain/Models/DomainModelsJsonContext.cs @@ -10,4 +10,4 @@ namespace Wino.Core.Domain.Models; [JsonSerializable(typeof(CustomThemeMetadata))] [JsonSerializable(typeof(WebViewMessage))] [JsonSerializable(typeof(List))] -public partial class DomainModelsJsonContext: JsonSerializerContext; +public partial class DomainModelsJsonContext : JsonSerializerContext; diff --git a/Wino.Core.UWP/BasePage.cs b/Wino.Core.UWP/BasePage.cs index ace946c9..c39e454c 100644 --- a/Wino.Core.UWP/BasePage.cs +++ b/Wino.Core.UWP/BasePage.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using CommunityToolkit.Mvvm.Messaging; using Microsoft.Extensions.DependencyInjection; using Windows.UI.Xaml; @@ -53,6 +54,8 @@ public abstract class BasePage : BasePage where T : CoreBaseViewModel Debug.WriteLine($"Disposed {GetType().Name}"); } + [RequiresDynamicCode("AOT")] + [RequiresUnreferencedCode("AOT")] protected override void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); diff --git a/Wino.Core.UWP/Services/WinoServerConnectionManager.cs b/Wino.Core.UWP/Services/WinoServerConnectionManager.cs index 7d6d6c4d..49309940 100644 --- a/Wino.Core.UWP/Services/WinoServerConnectionManager.cs +++ b/Wino.Core.UWP/Services/WinoServerConnectionManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -288,6 +289,8 @@ public class WinoServerConnectionManager : public Task> GetResponseAsync(TRequestType message, CancellationToken cancellationToken = default) where TRequestType : IClientMessage => GetResponseInternalAsync(message, cancellationToken: cancellationToken); + [RequiresDynamicCode("Calls System.Text.Json.JsonSerializer.Serialize(TValue, JsonSerializerOptions)")] + [RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.Serialize(TValue, JsonSerializerOptions)")] private async Task> GetResponseInternalAsync(TRequestType message, Dictionary parameters = null, CancellationToken cancellationToken = default) diff --git a/Wino.Core.ViewModels/AboutPageViewModel.cs b/Wino.Core.ViewModels/AboutPageViewModel.cs index 6e59210a..26579f61 100644 --- a/Wino.Core.ViewModels/AboutPageViewModel.cs +++ b/Wino.Core.ViewModels/AboutPageViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using CommunityToolkit.Mvvm.Input; using Serilog; @@ -46,6 +47,8 @@ public partial class AboutPageViewModel : CoreBaseViewModel PreferencesService = preferencesService; } + [RequiresDynamicCode("AOT")] + [RequiresUnreferencedCode("AOT")] protected override void OnActivated() { base.OnActivated(); diff --git a/Wino.Core.ViewModels/PersonalizationPageViewModel.cs b/Wino.Core.ViewModels/PersonalizationPageViewModel.cs index b377979d..7fc857de 100644 --- a/Wino.Core.ViewModels/PersonalizationPageViewModel.cs +++ b/Wino.Core.ViewModels/PersonalizationPageViewModel.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; @@ -200,6 +201,8 @@ public partial class PersonalizationPageViewModel : CoreBaseViewModel SelectedAppTheme = AppThemes.Find(a => a.Id == _themeService.CurrentApplicationThemeId); } + [RequiresDynamicCode("AOT")] + [RequiresUnreferencedCode("AOT")] protected override async void OnActivated() { base.OnActivated(); diff --git a/Wino.Core.ViewModels/SettingsViewModel.cs b/Wino.Core.ViewModels/SettingsViewModel.cs index c83a9710..35d3a2bf 100644 --- a/Wino.Core.ViewModels/SettingsViewModel.cs +++ b/Wino.Core.ViewModels/SettingsViewModel.cs @@ -4,7 +4,7 @@ namespace Wino.Core.ViewModels; public class SettingsDialogViewModel : CoreBaseViewModel { - public SettingsDialogViewModel(IMailDialogService dialogService) + public SettingsDialogViewModel(IMailDialogService dialogService) { } } diff --git a/Wino.Core/Requests/Bundles/RequestBundle.cs b/Wino.Core/Requests/Bundles/RequestBundle.cs index b950449c..75ab6321 100644 --- a/Wino.Core/Requests/Bundles/RequestBundle.cs +++ b/Wino.Core/Requests/Bundles/RequestBundle.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Net.Http; using System.Text.Json; using System.Threading; @@ -20,6 +21,8 @@ public record HttpRequestBundle(TRequest NativeRequest, IUIChangeReque /// Batch request that is generated by base synchronizer. public record HttpRequestBundle(TRequest NativeRequest, IRequestBase Request) : HttpRequestBundle(NativeRequest, Request) { + [RequiresDynamicCode("AOT")] + [RequiresUnreferencedCode("AOT")] public async Task DeserializeBundleAsync(HttpResponseMessage httpResponse, CancellationToken cancellationToken = default) { var content = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); diff --git a/Wino.Core/Synchronizers/OutlookSynchronizer.cs b/Wino.Core/Synchronizers/OutlookSynchronizer.cs index 528790c1..a2ab4bd9 100644 --- a/Wino.Core/Synchronizers/OutlookSynchronizer.cs +++ b/Wino.Core/Synchronizers/OutlookSynchronizer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Net; @@ -464,6 +465,7 @@ public class OutlookSynchronizer : WinoSynchronizer(String, CancellationToken)")] private async Task DeserializeGraphBatchResponseAsync(BatchResponseContentCollection collection, string requestId, CancellationToken cancellationToken = default) where T : IParsable, new() { // This deserialization may throw generalException in case of failure.