diff --git a/Wino.Mail.WinUI/Controls/Calendar/WinoCalendarControl.cs b/Wino.Mail.WinUI/Controls/Calendar/WinoCalendarControl.cs index d9876bda..2b6d4d0a 100644 --- a/Wino.Mail.WinUI/Controls/Calendar/WinoCalendarControl.cs +++ b/Wino.Mail.WinUI/Controls/Calendar/WinoCalendarControl.cs @@ -222,8 +222,15 @@ public partial class WinoCalendarControl : Control private void UpdateIdleState() { - InternalFlipView.Opacity = IsFlipIdle ? 0 : 1; - IdleGrid.Visibility = IsFlipIdle ? Visibility.Visible : Visibility.Collapsed; + if (InternalFlipView != null) + { + InternalFlipView.Opacity = IsFlipIdle ? 0 : 1; + } + + if (IdleGrid != null) + { + IdleGrid.Visibility = IsFlipIdle ? Visibility.Visible : Visibility.Collapsed; + } } private void ActiveTimelineCellUnselected(object sender, TimelineCellUnselectedArgs e) diff --git a/Wino.Mail.WinUI/Controls/Calendar/WinoCalendarFlipView.cs b/Wino.Mail.WinUI/Controls/Calendar/WinoCalendarFlipView.cs index 4017139c..f4b0dc1c 100644 --- a/Wino.Mail.WinUI/Controls/Calendar/WinoCalendarFlipView.cs +++ b/Wino.Mail.WinUI/Controls/Calendar/WinoCalendarFlipView.cs @@ -49,6 +49,8 @@ public partial class WinoCalendarFlipView : CustomCalendarFlipView internal event EventHandler? ProgrammaticNavigationCompleted; + private INotifyCollectionChanged? _trackedItemsSource; + public WinoCalendarFlipView() { RegisterPropertyChangedCallback(ItemsSourceProperty, new DependencyPropertyChangedCallback(OnItemsSourceChanged)); @@ -64,10 +66,19 @@ public partial class WinoCalendarFlipView : CustomCalendarFlipView private void RegisterItemsSourceChange() { - if (GetItemsSource() is INotifyCollectionChanged notifyCollectionChanged) + if (_trackedItemsSource != null) { - notifyCollectionChanged.CollectionChanged += ItemsSourceUpdated; + _trackedItemsSource.CollectionChanged -= ItemsSourceUpdated; } + + _trackedItemsSource = GetItemsSource(); + + if (_trackedItemsSource != null) + { + _trackedItemsSource.CollectionChanged += ItemsSourceUpdated; + } + + UpdateIdleState(); } protected override void OnSelectedItemChanged(object oldValue, object newValue) @@ -92,7 +103,13 @@ public partial class WinoCalendarFlipView : CustomCalendarFlipView private void ItemsSourceUpdated(object sender, NotifyCollectionChangedEventArgs e) { - IsIdle = e.Action == NotifyCollectionChangedAction.Reset || e.Action == NotifyCollectionChangedAction.Replace; + UpdateIdleState(); + } + + private void UpdateIdleState() + { + var itemsSource = GetItemsSource(); + IsIdle = itemsSource == null || itemsSource.Count == 0; } private void UpdateActiveElements() diff --git a/Wino.Mail.WinUI/Services/AccountCalendarStateService.cs b/Wino.Mail.WinUI/Services/AccountCalendarStateService.cs index a8fbfd98..30da4ee0 100644 --- a/Wino.Mail.WinUI/Services/AccountCalendarStateService.cs +++ b/Wino.Mail.WinUI/Services/AccountCalendarStateService.cs @@ -25,6 +25,8 @@ public partial class AccountCalendarStateService : ObservableRecipient, IRecipient, IRecipient { + private readonly object _calendarStateLock = new(); + public IDispatcher? Dispatcher { get; set; } public event EventHandler? CollectiveAccountGroupSelectionStateChanged; @@ -43,9 +45,13 @@ public partial class AccountCalendarStateService : ObservableRecipient, { get { - return _internalGroupedAccountCalendars - .SelectMany(a => a.AccountCalendars) - .Where(b => b.IsChecked); + lock (_calendarStateLock) + { + return _internalGroupedAccountCalendars + .SelectMany(a => a.AccountCalendars) + .Where(b => b.IsChecked) + .ToList(); + } } } @@ -53,8 +59,12 @@ public partial class AccountCalendarStateService : ObservableRecipient, { get { - return _internalGroupedAccountCalendars - .SelectMany(a => a.AccountCalendars); + lock (_calendarStateLock) + { + return _internalGroupedAccountCalendars + .SelectMany(a => a.AccountCalendars) + .ToList(); + } } } @@ -84,109 +94,124 @@ public partial class AccountCalendarStateService : ObservableRecipient, public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar) { - groupedAccountCalendar.CalendarSelectionStateChanged += SingleCalendarSelectionStateChanged; - groupedAccountCalendar.CollectiveSelectionStateChanged += SingleGroupCalendarCollectiveStateChanged; - - _internalGroupedAccountCalendars.Add(groupedAccountCalendar); - - // Maintain the grouped calendars collection - var group = _internalGroupedCalendars.FirstOrDefault>(g => g.Key.Id == groupedAccountCalendar.Account.Id); - if (group == null) + lock (_calendarStateLock) { - _internalGroupedCalendars.Add(new ObservableGroup(groupedAccountCalendar.Account, groupedAccountCalendar.AccountCalendars)); - } - else - { - foreach (var calendar in groupedAccountCalendar.AccountCalendars) + groupedAccountCalendar.CalendarSelectionStateChanged += SingleCalendarSelectionStateChanged; + groupedAccountCalendar.CollectiveSelectionStateChanged += SingleGroupCalendarCollectiveStateChanged; + + _internalGroupedAccountCalendars.Add(groupedAccountCalendar); + + // Maintain the grouped calendars collection + var group = _internalGroupedCalendars.FirstOrDefault>(g => g.Key.Id == groupedAccountCalendar.Account.Id); + if (group == null) { - group.Add(calendar); + _internalGroupedCalendars.Add(new ObservableGroup(groupedAccountCalendar.Account, groupedAccountCalendar.AccountCalendars)); + } + else + { + foreach (var calendar in groupedAccountCalendar.AccountCalendars) + { + group.Add(calendar); + } } } } public void RemoveGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar) { - groupedAccountCalendar.CalendarSelectionStateChanged -= SingleCalendarSelectionStateChanged; - groupedAccountCalendar.CollectiveSelectionStateChanged -= SingleGroupCalendarCollectiveStateChanged; - - _internalGroupedAccountCalendars.Remove(groupedAccountCalendar); - - // Maintain the grouped calendars collection - var group = _internalGroupedCalendars.FirstOrDefault>(g => g.Key.Id == groupedAccountCalendar.Account.Id); - if (group != null) + lock (_calendarStateLock) { - foreach (var calendar in groupedAccountCalendar.AccountCalendars.ToList()) - { - group.Remove(calendar); - } + groupedAccountCalendar.CalendarSelectionStateChanged -= SingleCalendarSelectionStateChanged; + groupedAccountCalendar.CollectiveSelectionStateChanged -= SingleGroupCalendarCollectiveStateChanged; - if (group.Count == 0) + _internalGroupedAccountCalendars.Remove(groupedAccountCalendar); + + // Maintain the grouped calendars collection + var group = _internalGroupedCalendars.FirstOrDefault>(g => g.Key.Id == groupedAccountCalendar.Account.Id); + if (group != null) { - _internalGroupedCalendars.Remove(group); + foreach (var calendar in groupedAccountCalendar.AccountCalendars.ToList()) + { + group.Remove(calendar); + } + + if (group.Count == 0) + { + _internalGroupedCalendars.Remove(group); + } } } } public void ClearGroupedAccountCalendars() { - while (_internalGroupedAccountCalendars.Any()) + lock (_calendarStateLock) { - RemoveGroupedAccountCalendar(_internalGroupedAccountCalendars[0]); + while (_internalGroupedAccountCalendars.Any()) + { + RemoveGroupedAccountCalendar(_internalGroupedAccountCalendars[0]); + } } } 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) + lock (_calendarStateLock) { - // If the group doesn't exist, create it. - group = new GroupedAccountCalendarViewModel(accountCalendar.Account, new[] { accountCalendar }); - AddGroupedAccountCalendar(group); - } - else - { - group.AccountCalendars.Add(accountCalendar); + // Find the group that this calendar belongs to. + var group = _internalGroupedAccountCalendars.FirstOrDefault(g => g.Account.Id == accountCalendar.Account.Id); - // Maintain the grouped calendars collection - var calendarGroup = _internalGroupedCalendars.FirstOrDefault>(g => g.Key.Id == accountCalendar.Account.Id); - if (calendarGroup == null) + if (group == null) { - _internalGroupedCalendars.Add(new ObservableGroup(accountCalendar.Account, new[] { accountCalendar })); + // If the group doesn't exist, create it. + group = new GroupedAccountCalendarViewModel(accountCalendar.Account, new[] { accountCalendar }); + AddGroupedAccountCalendar(group); } else { - calendarGroup.Add(accountCalendar); + group.AccountCalendars.Add(accountCalendar); + + // Maintain the grouped calendars collection + var calendarGroup = _internalGroupedCalendars.FirstOrDefault>(g => g.Key.Id == accountCalendar.Account.Id); + if (calendarGroup == null) + { + _internalGroupedCalendars.Add(new ObservableGroup(accountCalendar.Account, new[] { accountCalendar })); + } + else + { + calendarGroup.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); - - // Maintain the grouped calendars collection - var calendarGroup = _internalGroupedCalendars.FirstOrDefault>(g => g.Key.Id == accountCalendar.Account.Id); - if (calendarGroup != null) + lock (_calendarStateLock) { - calendarGroup.Remove(accountCalendar); + var group = _internalGroupedAccountCalendars.FirstOrDefault(g => g.Account.Id == accountCalendar.Account.Id); - if (calendarGroup.Count == 0) + // We don't expect but just in case. + if (group == null) return; + + group.AccountCalendars.Remove(accountCalendar); + + // Maintain the grouped calendars collection + var calendarGroup = _internalGroupedCalendars.FirstOrDefault>(g => g.Key.Id == accountCalendar.Account.Id); + if (calendarGroup != null) { - _internalGroupedCalendars.Remove(calendarGroup); - } - } + calendarGroup.Remove(accountCalendar); - if (group.AccountCalendars.Count == 0) - { - RemoveGroupedAccountCalendar(group); + if (calendarGroup.Count == 0) + { + _internalGroupedCalendars.Remove(calendarGroup); + } + } + + if (group.AccountCalendars.Count == 0) + { + RemoveGroupedAccountCalendar(group); + } } } @@ -289,7 +314,11 @@ public partial class AccountCalendarStateService : ObservableRecipient, { await Dispatcher.ExecuteOnUIThread(() => { - var groupedAccount = _internalGroupedAccountCalendars.FirstOrDefault(a => a.Account.Id == removedAccountId); + GroupedAccountCalendarViewModel? groupedAccount; + lock (_calendarStateLock) + { + groupedAccount = _internalGroupedAccountCalendars.FirstOrDefault(a => a.Account.Id == removedAccountId); + } if (groupedAccount != null) { @@ -299,7 +328,11 @@ public partial class AccountCalendarStateService : ObservableRecipient, } else { - var groupedAccount = _internalGroupedAccountCalendars.FirstOrDefault(a => a.Account.Id == removedAccountId); + GroupedAccountCalendarViewModel? groupedAccount; + lock (_calendarStateLock) + { + groupedAccount = _internalGroupedAccountCalendars.FirstOrDefault(a => a.Account.Id == removedAccountId); + } if (groupedAccount != null) { diff --git a/Wino.Mail.WinUI/Services/NavigationService.cs b/Wino.Mail.WinUI/Services/NavigationService.cs index a55214f7..71bde73e 100644 --- a/Wino.Mail.WinUI/Services/NavigationService.cs +++ b/Wino.Mail.WinUI/Services/NavigationService.cs @@ -35,6 +35,21 @@ public class NavigationService : NavigationServiceBase, INavigationService WinoPage.ComposePage }; + private static readonly WinoPage[] MailOnlyPages = + [ + WinoPage.MailListPage, + WinoPage.MailRenderingPage, + WinoPage.ComposePage, + WinoPage.IdlePage, + WinoPage.WelcomePage + ]; + + private static readonly WinoPage[] CalendarOnlyPages = + [ + WinoPage.CalendarPage, + WinoPage.EventDetailsPage + ]; + public NavigationService(IStatePersistanceService statePersistanceService) { _statePersistanceService = statePersistanceService; @@ -140,9 +155,17 @@ public class NavigationService : NavigationServiceBase, INavigationService var pageType = GetPageType(page); if (pageType == null) return false; - var currentApplicationMode = GetCoreFrame(NavigationReferenceFrame.ShellFrame)?.Content?.GetType() == typeof(MailAppShell) - ? WinoApplicationMode.Mail - : WinoApplicationMode.Calendar; + var currentApplicationMode = _statePersistanceService.ApplicationMode; + + if (currentApplicationMode == WinoApplicationMode.Calendar && IsMailOnlyPage(page)) + { + return false; + } + + if (currentApplicationMode == WinoApplicationMode.Mail && IsCalendarOnlyPage(page)) + { + return false; + } _statePersistanceService.IsReadingMail = _renderingPageTypes.Contains(page); _statePersistanceService.IsEventDetailsVisible = page == WinoPage.EventDetailsPage; @@ -248,6 +271,12 @@ public class NavigationService : NavigationServiceBase, INavigationService return false; } + private static bool IsMailOnlyPage(WinoPage page) + => MailOnlyPages.Contains(page); + + private static bool IsCalendarOnlyPage(WinoPage page) + => CalendarOnlyPages.Contains(page); + private static LoadCalendarMessage CreateLoadCalendarMessage(CalendarPageNavigationArgs args) { var targetDate = args.RequestDefaultNavigation