diff --git a/Wino.Calendar.ViewModels/CalendarAppShellViewModel.cs b/Wino.Calendar.ViewModels/CalendarAppShellViewModel.cs index 009eb9bb..c8486909 100644 --- a/Wino.Calendar.ViewModels/CalendarAppShellViewModel.cs +++ b/Wino.Calendar.ViewModels/CalendarAppShellViewModel.cs @@ -20,6 +20,7 @@ using Wino.Core.ViewModels; using Wino.Messaging.Client.Calendar; using Wino.Messaging.Client.Navigation; using Wino.Messaging.Server; +using Wino.Messaging.UI; namespace Wino.Calendar.ViewModels; @@ -27,7 +28,8 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel, IRecipient, IRecipient, IRecipient, - IRecipient + IRecipient, + IRecipient { public IPreferencesService PreferencesService { get; } public IStatePersistanceService StatePersistenceService { get; } @@ -110,7 +112,12 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel, { base.OnNavigatedTo(mode, parameters); - if (mode == NavigationMode.Back) return; + // Account list may have changed while this shell was inactive. + if (mode == NavigationMode.Back) + { + await InitializeAccountCalendarsAsync(); + return; + } UpdateDateNavigationHeaderItems(); @@ -300,6 +307,7 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel, Messenger.Register(this); Messenger.Register(this); Messenger.Register(this); + Messenger.Register(this); } protected override void UnregisterRecipients() @@ -310,6 +318,7 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel, Messenger.Unregister(this); Messenger.Unregister(this); Messenger.Unregister(this); + Messenger.Unregister(this); } public void Receive(VisibleDateRangeChangedMessage message) => HighlightedDateRange = message.DateRange; @@ -403,4 +412,7 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel, OnPropertyChanged(nameof(IsVerticalCalendar)); UpdateDateNavigationHeaderItems(); } + + public async void Receive(AccountRemovedMessage message) + => await InitializeAccountCalendarsAsync(); } diff --git a/Wino.Calendar.ViewModels/CalendarPageViewModel.cs b/Wino.Calendar.ViewModels/CalendarPageViewModel.cs index 5d613ecb..7ebc4ffc 100644 --- a/Wino.Calendar.ViewModels/CalendarPageViewModel.cs +++ b/Wino.Calendar.ViewModels/CalendarPageViewModel.cs @@ -22,6 +22,7 @@ using Wino.Core.Domain.Models.Calendar.CalendarTypeStrategies; using Wino.Core.Domain.Models.Navigation; using Wino.Core.ViewModels; using Wino.Messaging.Client.Calendar; +using Wino.Messaging.UI; namespace Wino.Calendar.ViewModels; @@ -31,7 +32,8 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel, IRecipient, IRecipient, IRecipient, - IRecipient + IRecipient, + IRecipient { #region Quick Event Creation @@ -177,6 +179,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel, Messenger.Register(this); Messenger.Register(this); Messenger.Register(this); + Messenger.Register(this); } protected override void UnregisterRecipients() { @@ -187,6 +190,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel, Messenger.Unregister(this); Messenger.Unregister(this); Messenger.Unregister(this); + Messenger.Unregister(this); } private void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e) @@ -928,6 +932,29 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel, public void Receive(CalendarItemRightTappedMessage message) { } + public async void Receive(AccountRemovedMessage message) + { + var removedAccountId = message.Account.Id; + + await ExecuteUIThread(() => + { + foreach (var dayRange in DayRanges) + { + foreach (var calendarDay in dayRange.CalendarDays) + { + calendarDay.EventsCollection.RemoveCalendarItems(item => item.AssignedCalendar?.AccountId == removedAccountId); + } + } + + if (DisplayDetailsCalendarItemViewModel?.AssignedCalendar?.AccountId == removedAccountId) + { + DisplayDetailsCalendarItemViewModel = null; + } + + SelectedQuickEventAccountCalendar = AccountCalendarStateService.ActiveCalendars.FirstOrDefault(a => a.IsPrimary); + }); + } + protected override async void OnCalendarItemDeleted(CalendarItem calendarItem) { base.OnCalendarItemDeleted(calendarItem); diff --git a/Wino.Core.Domain/Collections/CalendarEventCollection.cs b/Wino.Core.Domain/Collections/CalendarEventCollection.cs index 720a3899..1b070837 100644 --- a/Wino.Core.Domain/Collections/CalendarEventCollection.cs +++ b/Wino.Core.Domain/Collections/CalendarEventCollection.cs @@ -115,6 +115,18 @@ public class CalendarEventCollection } } + public void RemoveCalendarItems(Func predicate) + { + if (predicate == null) return; + + var itemsToRemove = _allItems.Where(predicate).ToList(); + + foreach (var item in itemsToRemove) + { + RemoveCalendarItem(item); + } + } + private void AddCalendarItemInternal(ObservableRangeCollection collection, ICalendarItem calendarItem, bool create = true) { if (calendarItem is not ICalendarItemViewModel viewModel) diff --git a/Wino.Mail.ViewModels/MailAppShellViewModel.cs b/Wino.Mail.ViewModels/MailAppShellViewModel.cs index f2d35f07..68d8226a 100644 --- a/Wino.Mail.ViewModels/MailAppShellViewModel.cs +++ b/Wino.Mail.ViewModels/MailAppShellViewModel.cs @@ -221,7 +221,13 @@ public partial class MailAppShellViewModel : MailBaseViewModel, { base.OnNavigatedTo(mode, parameters); - if (mode == NavigationMode.Back) return; + if (mode == NavigationMode.Back) + { + // Account list may have changed while this shell was inactive. + await RecreateMenuItemsAsync(); + await RestoreSelectedAccountAfterMenuRefreshAsync(false); + return; + } await CreateFooterItemsAsync(); @@ -904,22 +910,50 @@ public partial class MailAppShellViewModel : MailBaseViewModel, await LoadAccountsAsync(); } + private async Task RestoreSelectedAccountAfterMenuRefreshAsync(bool automaticallyNavigateFirstItem) + { + IAccountMenuItem validSelectedMenuItem = null; + bool hasPreviousSelection = latestSelectedAccountMenuItem != null; + + if (hasPreviousSelection) + { + var selectedEntityId = latestSelectedAccountMenuItem.EntityId.GetValueOrDefault(); + + if (selectedEntityId != Guid.Empty && + MenuItems.TryGetAccountMenuItem(selectedEntityId, out IAccountMenuItem foundSelectedMenuItem)) + { + validSelectedMenuItem = foundSelectedMenuItem; + } + else + { + latestSelectedAccountMenuItem = null; + } + } + + if (validSelectedMenuItem == null) + { + validSelectedMenuItem = MenuItems.FirstOrDefault(a => a is IAccountMenuItem) as IAccountMenuItem; + hasPreviousSelection = false; + } + + if (validSelectedMenuItem != null) + { + await ChangeLoadedAccountAsync(validSelectedMenuItem, hasPreviousSelection || automaticallyNavigateFirstItem); + } + else + { + await ExecuteUIThread(() => SelectedMenuItem = null); + NavigationService.Navigate(WinoPage.WelcomePage); + } + } + public async void Receive(RefreshUnreadCountsMessage message) => await UpdateUnreadItemCountAsync(); public async void Receive(AccountsMenuRefreshRequested message) { await RecreateMenuItemsAsync(); - - // Try to restore latest selected account. - if (latestSelectedAccountMenuItem != null) - { - await ChangeLoadedAccountAsync(latestSelectedAccountMenuItem, navigateInbox: true); - } - else if (MenuItems.FirstOrDefault(a => a is IAccountMenuItem) is IAccountMenuItem firstAccount) - { - await ChangeLoadedAccountAsync(firstAccount, message.AutomaticallyNavigateFirstItem); - } + await RestoreSelectedAccountAfterMenuRefreshAsync(message.AutomaticallyNavigateFirstItem); } public async void Receive(AccountFolderConfigurationUpdated message) @@ -949,8 +983,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel, { await CreateFooterItemsAsync(); await RecreateMenuItemsAsync(); - - await ChangeLoadedAccountAsync(latestSelectedAccountMenuItem, navigateInbox: false); + await RestoreSelectedAccountAfterMenuRefreshAsync(false); } private void ReorderAccountMenuItems(Dictionary newAccountOrder) @@ -1073,7 +1106,17 @@ public partial class MailAppShellViewModel : MailBaseViewModel, Messenger.Unregister(this); } - public void Receive(AccountRemovedMessage message) => Messenger.Send(new AccountsMenuRefreshRequested(false)); + public async void Receive(AccountRemovedMessage message) + { + if (latestSelectedAccountMenuItem?.HoldingAccounts?.Any(a => a.Id == message.Account.Id) == true) + { + latestSelectedAccountMenuItem = null; + await ExecuteUIThread(() => SelectedMenuItem = null); + } + + await RecreateMenuItemsAsync(); + await RestoreSelectedAccountAfterMenuRefreshAsync(false); + } public async void Receive(AccountCreatedMessage message) { diff --git a/Wino.Mail.WinUI/Services/AccountCalendarStateService.cs b/Wino.Mail.WinUI/Services/AccountCalendarStateService.cs index 456352e5..a8fbfd98 100644 --- a/Wino.Mail.WinUI/Services/AccountCalendarStateService.cs +++ b/Wino.Mail.WinUI/Services/AccountCalendarStateService.cs @@ -10,6 +10,7 @@ using Wino.Calendar.ViewModels.Interfaces; using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Interfaces; using Wino.Messaging.Client.Calendar; +using Wino.Messaging.UI; namespace Wino.Mail.WinUI.Services; @@ -21,7 +22,8 @@ public partial class AccountCalendarStateService : ObservableRecipient, IAccountCalendarStateService, IRecipient, IRecipient, - IRecipient + IRecipient, + IRecipient { public IDispatcher? Dispatcher { get; set; } @@ -71,6 +73,7 @@ public partial class AccountCalendarStateService : ObservableRecipient, Messenger.Register(this); Messenger.Register(this); Messenger.Register(this); + Messenger.Register(this); } private void SingleGroupCalendarCollectiveStateChanged(object? sender, EventArgs e) @@ -277,4 +280,31 @@ public partial class AccountCalendarStateService : ObservableRecipient, } } } + + public async void Receive(AccountRemovedMessage message) + { + var removedAccountId = message.Account.Id; + + if (Dispatcher != null) + { + await Dispatcher.ExecuteOnUIThread(() => + { + var groupedAccount = _internalGroupedAccountCalendars.FirstOrDefault(a => a.Account.Id == removedAccountId); + + if (groupedAccount != null) + { + RemoveGroupedAccountCalendar(groupedAccount); + } + }); + } + else + { + var groupedAccount = _internalGroupedAccountCalendars.FirstOrDefault(a => a.Account.Id == removedAccountId); + + if (groupedAccount != null) + { + RemoveGroupedAccountCalendar(groupedAccount); + } + } + } } diff --git a/Wino.Services/AccountService.cs b/Wino.Services/AccountService.cs index 50e65296..bce8d32e 100644 --- a/Wino.Services/AccountService.cs +++ b/Wino.Services/AccountService.cs @@ -12,6 +12,7 @@ using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Accounts; +using Wino.Messaging.Client.Calendar; using Wino.Messaging.Client.Accounts; using Wino.Messaging.UI; @@ -289,8 +290,40 @@ public class AccountService : BaseDatabaseService, IAccountService public async Task DeleteAccountAsync(MailAccount account) { + // Collect calendar entities before deletion so we can notify UI subscribers. + var accountCalendars = await Connection.Table() + .Where(a => a.AccountId == account.Id) + .ToListAsync() + .ConfigureAwait(false); + + var deletedCalendarItems = new List(); + foreach (var accountCalendar in accountCalendars) + { + var calendarItems = await Connection.Table() + .Where(a => a.CalendarId == accountCalendar.Id) + .ToListAsync() + .ConfigureAwait(false); + + deletedCalendarItems.AddRange(calendarItems); + } + await DeleteAccountMailCacheAsync(account.Id, AccountCacheResetReason.AccountRemoval); + // Delete calendar metadata and related records for this account. + foreach (var calendarItem in deletedCalendarItems) + { + await Connection.Table().DeleteAsync(a => a.CalendarItemId == calendarItem.Id).ConfigureAwait(false); + await Connection.Table().DeleteAsync(a => a.CalendarItemId == calendarItem.Id).ConfigureAwait(false); + await Connection.Table().DeleteAsync(a => a.CalendarItemId == calendarItem.Id).ConfigureAwait(false); + } + + foreach (var accountCalendar in accountCalendars) + { + await Connection.Table().DeleteAsync(a => a.CalendarId == accountCalendar.Id).ConfigureAwait(false); + } + + await Connection.Table().DeleteAsync(a => a.AccountId == account.Id).ConfigureAwait(false); + await Connection.Table().DeleteAsync(a => a.MailAccountId == account.Id); await Connection.Table().DeleteAsync(a => a.MailAccountId == account.Id); await Connection.Table().DeleteAsync(a => a.AccountId == account.Id); @@ -338,6 +371,16 @@ public class AccountService : BaseDatabaseService, IAccountService } } + foreach (var calendarItem in deletedCalendarItems) + { + WeakReferenceMessenger.Default.Send(new CalendarItemDeleted(calendarItem)); + } + + foreach (var accountCalendar in accountCalendars) + { + WeakReferenceMessenger.Default.Send(new CalendarListDeleted(accountCalendar)); + } + ReportUIChange(new AccountRemovedMessage(account)); }