Proper cleanup of account on the UI when its deleted.

This commit is contained in:
Burak Kaan Köse
2026-02-16 01:56:22 +01:00
parent 31a7faeef9
commit fec49ce6f8
6 changed files with 185 additions and 18 deletions
@@ -20,6 +20,7 @@ using Wino.Core.ViewModels;
using Wino.Messaging.Client.Calendar; using Wino.Messaging.Client.Calendar;
using Wino.Messaging.Client.Navigation; using Wino.Messaging.Client.Navigation;
using Wino.Messaging.Server; using Wino.Messaging.Server;
using Wino.Messaging.UI;
namespace Wino.Calendar.ViewModels; namespace Wino.Calendar.ViewModels;
@@ -27,7 +28,8 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
IRecipient<VisibleDateRangeChangedMessage>, IRecipient<VisibleDateRangeChangedMessage>,
IRecipient<CalendarEnableStatusChangedMessage>, IRecipient<CalendarEnableStatusChangedMessage>,
IRecipient<NavigateManageAccountsRequested>, IRecipient<NavigateManageAccountsRequested>,
IRecipient<CalendarDisplayTypeChangedMessage> IRecipient<CalendarDisplayTypeChangedMessage>,
IRecipient<AccountRemovedMessage>
{ {
public IPreferencesService PreferencesService { get; } public IPreferencesService PreferencesService { get; }
public IStatePersistanceService StatePersistenceService { get; } public IStatePersistanceService StatePersistenceService { get; }
@@ -110,7 +112,12 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
{ {
base.OnNavigatedTo(mode, parameters); 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(); UpdateDateNavigationHeaderItems();
@@ -300,6 +307,7 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
Messenger.Register<CalendarEnableStatusChangedMessage>(this); Messenger.Register<CalendarEnableStatusChangedMessage>(this);
Messenger.Register<NavigateManageAccountsRequested>(this); Messenger.Register<NavigateManageAccountsRequested>(this);
Messenger.Register<CalendarDisplayTypeChangedMessage>(this); Messenger.Register<CalendarDisplayTypeChangedMessage>(this);
Messenger.Register<AccountRemovedMessage>(this);
} }
protected override void UnregisterRecipients() protected override void UnregisterRecipients()
@@ -310,6 +318,7 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
Messenger.Unregister<CalendarEnableStatusChangedMessage>(this); Messenger.Unregister<CalendarEnableStatusChangedMessage>(this);
Messenger.Unregister<NavigateManageAccountsRequested>(this); Messenger.Unregister<NavigateManageAccountsRequested>(this);
Messenger.Unregister<CalendarDisplayTypeChangedMessage>(this); Messenger.Unregister<CalendarDisplayTypeChangedMessage>(this);
Messenger.Unregister<AccountRemovedMessage>(this);
} }
public void Receive(VisibleDateRangeChangedMessage message) => HighlightedDateRange = message.DateRange; public void Receive(VisibleDateRangeChangedMessage message) => HighlightedDateRange = message.DateRange;
@@ -403,4 +412,7 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
OnPropertyChanged(nameof(IsVerticalCalendar)); OnPropertyChanged(nameof(IsVerticalCalendar));
UpdateDateNavigationHeaderItems(); UpdateDateNavigationHeaderItems();
} }
public async void Receive(AccountRemovedMessage message)
=> await InitializeAccountCalendarsAsync();
} }
@@ -22,6 +22,7 @@ using Wino.Core.Domain.Models.Calendar.CalendarTypeStrategies;
using Wino.Core.Domain.Models.Navigation; using Wino.Core.Domain.Models.Navigation;
using Wino.Core.ViewModels; using Wino.Core.ViewModels;
using Wino.Messaging.Client.Calendar; using Wino.Messaging.Client.Calendar;
using Wino.Messaging.UI;
namespace Wino.Calendar.ViewModels; namespace Wino.Calendar.ViewModels;
@@ -31,7 +32,8 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
IRecipient<CalendarSettingsUpdatedMessage>, IRecipient<CalendarSettingsUpdatedMessage>,
IRecipient<CalendarItemTappedMessage>, IRecipient<CalendarItemTappedMessage>,
IRecipient<CalendarItemDoubleTappedMessage>, IRecipient<CalendarItemDoubleTappedMessage>,
IRecipient<CalendarItemRightTappedMessage> IRecipient<CalendarItemRightTappedMessage>,
IRecipient<AccountRemovedMessage>
{ {
#region Quick Event Creation #region Quick Event Creation
@@ -177,6 +179,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
Messenger.Register<CalendarItemTappedMessage>(this); Messenger.Register<CalendarItemTappedMessage>(this);
Messenger.Register<CalendarItemDoubleTappedMessage>(this); Messenger.Register<CalendarItemDoubleTappedMessage>(this);
Messenger.Register<CalendarItemRightTappedMessage>(this); Messenger.Register<CalendarItemRightTappedMessage>(this);
Messenger.Register<AccountRemovedMessage>(this);
} }
protected override void UnregisterRecipients() protected override void UnregisterRecipients()
{ {
@@ -187,6 +190,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
Messenger.Unregister<CalendarItemTappedMessage>(this); Messenger.Unregister<CalendarItemTappedMessage>(this);
Messenger.Unregister<CalendarItemDoubleTappedMessage>(this); Messenger.Unregister<CalendarItemDoubleTappedMessage>(this);
Messenger.Unregister<CalendarItemRightTappedMessage>(this); Messenger.Unregister<CalendarItemRightTappedMessage>(this);
Messenger.Unregister<AccountRemovedMessage>(this);
} }
private void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e) private void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e)
@@ -928,6 +932,29 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
public void Receive(CalendarItemRightTappedMessage message) { } 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) protected override async void OnCalendarItemDeleted(CalendarItem calendarItem)
{ {
base.OnCalendarItemDeleted(calendarItem); base.OnCalendarItemDeleted(calendarItem);
@@ -115,6 +115,18 @@ public class CalendarEventCollection
} }
} }
public void RemoveCalendarItems(Func<ICalendarItem, bool> predicate)
{
if (predicate == null) return;
var itemsToRemove = _allItems.Where(predicate).ToList();
foreach (var item in itemsToRemove)
{
RemoveCalendarItem(item);
}
}
private void AddCalendarItemInternal(ObservableRangeCollection<ICalendarItem> collection, ICalendarItem calendarItem, bool create = true) private void AddCalendarItemInternal(ObservableRangeCollection<ICalendarItem> collection, ICalendarItem calendarItem, bool create = true)
{ {
if (calendarItem is not ICalendarItemViewModel viewModel) if (calendarItem is not ICalendarItemViewModel viewModel)
+57 -14
View File
@@ -221,7 +221,13 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
{ {
base.OnNavigatedTo(mode, parameters); 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(); await CreateFooterItemsAsync();
@@ -904,22 +910,50 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
await LoadAccountsAsync(); 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) public async void Receive(RefreshUnreadCountsMessage message)
=> await UpdateUnreadItemCountAsync(); => await UpdateUnreadItemCountAsync();
public async void Receive(AccountsMenuRefreshRequested message) public async void Receive(AccountsMenuRefreshRequested message)
{ {
await RecreateMenuItemsAsync(); await RecreateMenuItemsAsync();
await RestoreSelectedAccountAfterMenuRefreshAsync(message.AutomaticallyNavigateFirstItem);
// 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);
}
} }
public async void Receive(AccountFolderConfigurationUpdated message) public async void Receive(AccountFolderConfigurationUpdated message)
@@ -949,8 +983,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
{ {
await CreateFooterItemsAsync(); await CreateFooterItemsAsync();
await RecreateMenuItemsAsync(); await RecreateMenuItemsAsync();
await RestoreSelectedAccountAfterMenuRefreshAsync(false);
await ChangeLoadedAccountAsync(latestSelectedAccountMenuItem, navigateInbox: false);
} }
private void ReorderAccountMenuItems(Dictionary<Guid, int> newAccountOrder) private void ReorderAccountMenuItems(Dictionary<Guid, int> newAccountOrder)
@@ -1073,7 +1106,17 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
Messenger.Unregister<AccountFolderConfigurationUpdated>(this); Messenger.Unregister<AccountFolderConfigurationUpdated>(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) public async void Receive(AccountCreatedMessage message)
{ {
@@ -10,6 +10,7 @@ using Wino.Calendar.ViewModels.Interfaces;
using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Messaging.Client.Calendar; using Wino.Messaging.Client.Calendar;
using Wino.Messaging.UI;
namespace Wino.Mail.WinUI.Services; namespace Wino.Mail.WinUI.Services;
@@ -21,7 +22,8 @@ public partial class AccountCalendarStateService : ObservableRecipient,
IAccountCalendarStateService, IAccountCalendarStateService,
IRecipient<CalendarListAdded>, IRecipient<CalendarListAdded>,
IRecipient<CalendarListUpdated>, IRecipient<CalendarListUpdated>,
IRecipient<CalendarListDeleted> IRecipient<CalendarListDeleted>,
IRecipient<AccountRemovedMessage>
{ {
public IDispatcher? Dispatcher { get; set; } public IDispatcher? Dispatcher { get; set; }
@@ -71,6 +73,7 @@ public partial class AccountCalendarStateService : ObservableRecipient,
Messenger.Register<CalendarListAdded>(this); Messenger.Register<CalendarListAdded>(this);
Messenger.Register<CalendarListUpdated>(this); Messenger.Register<CalendarListUpdated>(this);
Messenger.Register<CalendarListDeleted>(this); Messenger.Register<CalendarListDeleted>(this);
Messenger.Register<AccountRemovedMessage>(this);
} }
private void SingleGroupCalendarCollectiveStateChanged(object? sender, EventArgs e) 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);
}
}
}
} }
+43
View File
@@ -12,6 +12,7 @@ using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Accounts; using Wino.Core.Domain.Models.Accounts;
using Wino.Messaging.Client.Calendar;
using Wino.Messaging.Client.Accounts; using Wino.Messaging.Client.Accounts;
using Wino.Messaging.UI; using Wino.Messaging.UI;
@@ -289,8 +290,40 @@ public class AccountService : BaseDatabaseService, IAccountService
public async Task DeleteAccountAsync(MailAccount account) public async Task DeleteAccountAsync(MailAccount account)
{ {
// Collect calendar entities before deletion so we can notify UI subscribers.
var accountCalendars = await Connection.Table<AccountCalendar>()
.Where(a => a.AccountId == account.Id)
.ToListAsync()
.ConfigureAwait(false);
var deletedCalendarItems = new List<CalendarItem>();
foreach (var accountCalendar in accountCalendars)
{
var calendarItems = await Connection.Table<CalendarItem>()
.Where(a => a.CalendarId == accountCalendar.Id)
.ToListAsync()
.ConfigureAwait(false);
deletedCalendarItems.AddRange(calendarItems);
}
await DeleteAccountMailCacheAsync(account.Id, AccountCacheResetReason.AccountRemoval); await DeleteAccountMailCacheAsync(account.Id, AccountCacheResetReason.AccountRemoval);
// Delete calendar metadata and related records for this account.
foreach (var calendarItem in deletedCalendarItems)
{
await Connection.Table<CalendarEventAttendee>().DeleteAsync(a => a.CalendarItemId == calendarItem.Id).ConfigureAwait(false);
await Connection.Table<Reminder>().DeleteAsync(a => a.CalendarItemId == calendarItem.Id).ConfigureAwait(false);
await Connection.Table<CalendarAttachment>().DeleteAsync(a => a.CalendarItemId == calendarItem.Id).ConfigureAwait(false);
}
foreach (var accountCalendar in accountCalendars)
{
await Connection.Table<CalendarItem>().DeleteAsync(a => a.CalendarId == accountCalendar.Id).ConfigureAwait(false);
}
await Connection.Table<AccountCalendar>().DeleteAsync(a => a.AccountId == account.Id).ConfigureAwait(false);
await Connection.Table<MailItemFolder>().DeleteAsync(a => a.MailAccountId == account.Id); await Connection.Table<MailItemFolder>().DeleteAsync(a => a.MailAccountId == account.Id);
await Connection.Table<AccountSignature>().DeleteAsync(a => a.MailAccountId == account.Id); await Connection.Table<AccountSignature>().DeleteAsync(a => a.MailAccountId == account.Id);
await Connection.Table<MailAccountAlias>().DeleteAsync(a => a.AccountId == account.Id); await Connection.Table<MailAccountAlias>().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)); ReportUIChange(new AccountRemovedMessage(account));
} }