diff --git a/Wino.Mail.ViewModels/MailListPageViewModel.cs b/Wino.Mail.ViewModels/MailListPageViewModel.cs index 6e9d4ae0..8848e11b 100644 --- a/Wino.Mail.ViewModels/MailListPageViewModel.cs +++ b/Wino.Mail.ViewModels/MailListPageViewModel.cs @@ -51,7 +51,7 @@ namespace Wino.Mail.ViewModels * to prevent them from being removed from the list when they are marked as read. */ - private HashSet gmailUnreadFolderMarkedAsReadUniqueIds = new HashSet(); + private readonly HashSet gmailUnreadFolderMarkedAsReadUniqueIds = []; private IObservable> selectionChangedObservable = null; @@ -68,7 +68,6 @@ namespace Wino.Mail.ViewModels public IPreferencesService PreferencesService { get; } private readonly IMailService _mailService; - private readonly INotificationBuilder _notificationBuilder; private readonly IFolderService _folderService; private readonly IWinoSynchronizerFactory _winoSynchronizerFactory; private readonly IThreadingStrategyProvider _threadingStrategyProvider; @@ -100,12 +99,15 @@ namespace Wino.Mail.ViewModels private FilterOption _selectedFilterOption; private SortingOption _selectedSortingOption; + // Indicates state when folder is initializing. It can happen after folder navigation, search or filter change applied. [ObservableProperty] [NotifyPropertyChangedFor(nameof(IsEmpty))] [NotifyPropertyChangedFor(nameof(IsCriteriaFailed))] [NotifyPropertyChangedFor(nameof(IsFolderEmpty))] private bool isInitializingFolder; + private bool isLoadMoreItemsLoading; + [ObservableProperty] private InfoBarMessageType barSeverity; @@ -118,10 +120,21 @@ namespace Wino.Mail.ViewModels [ObservableProperty] private bool isBarOpen; + /// + /// Current folder that is being represented from the menu. + /// + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(CanSynchronize))] + [NotifyPropertyChangedFor(nameof(IsFolderSynchronizationEnabled))] + private IBaseFolderMenuItem activeFolder; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(CanSynchronize))] + private bool isAccountSynchronizerInSynchronization; + public MailListPageViewModel(IDialogService dialogService, IWinoNavigationService navigationService, IMailService mailService, - INotificationBuilder notificationBuilder, IStatePersistanceService statePersistanceService, IFolderService folderService, IWinoSynchronizerFactory winoSynchronizerFactory, @@ -136,7 +149,6 @@ namespace Wino.Mail.ViewModels NavigationService = navigationService; _mailService = mailService; - _notificationBuilder = notificationBuilder; _folderService = folderService; _winoSynchronizerFactory = winoSynchronizerFactory; _threadingStrategyProvider = threadingStrategyProvider; @@ -156,53 +168,6 @@ namespace Wino.Mail.ViewModels }); } - /// - /// Executes the requested mail operation for currently selected items. - /// - /// Action to execute for selected items. - [RelayCommand] - private async Task MailOperationAsync(int mailOperationIndex) - { - if (!SelectedItems.Any()) return; - - // Commands don't like enums. So it has to be int. - var operation = (MailOperation)mailOperationIndex; - - var package = new MailOperationPreperationRequest(operation, SelectedItems.Select(a => a.MailCopy)); - - await ExecuteMailOperationAsync(package); - } - - /// - /// Sens a new message to synchronize current folder. - /// - [RelayCommand] - private void SyncFolder() - { - if (!CanSynchronize) return; - - // Only synchronize listed folders. - - // When doing linked inbox sync, we need to save the sync id to report progress back only once. - // Otherwise, we will report progress for each folder and that's what we don't want. - - trackingSynchronizationId = Guid.NewGuid(); - completedTrackingSynchronizationCount = 0; - - foreach (var folder in ActiveFolder.HandlingFolders) - { - var options = new SynchronizationOptions() - { - AccountId = folder.MailAccountId, - Type = SynchronizationType.Custom, - SynchronizationFolderIds = [folder.Id], - GroupedSynchronizationTrackingId = trackingSynchronizationId - }; - - Messenger.Send(new NewSynchronizationRequested(options)); - } - } - private async void ActiveMailItemChanged(MailItemViewModel selectedMailItemViewModel) { if (_activeMailItem == selectedMailItemViewModel) return; @@ -278,34 +243,18 @@ namespace Wino.Mail.ViewModels } } - /// - /// Current folder that is being represented from the menu. - /// - [ObservableProperty] - [NotifyPropertyChangedFor(nameof(CanSynchronize))] - [NotifyPropertyChangedFor(nameof(IsFolderSynchronizationEnabled))] - private IBaseFolderMenuItem activeFolder; - - [ObservableProperty] - [NotifyPropertyChangedFor(nameof(CanSynchronize))] - private bool isAccountSynchronizerInSynchronization; - - public bool CanSynchronize => !IsAccountSynchronizerInSynchronization && IsFolderSynchronizationEnabled; - - public bool IsFolderSynchronizationEnabled => ActiveFolder?.IsSynchronizationEnabled ?? false; - #region Properties - + public bool CanSynchronize => !IsAccountSynchronizerInSynchronization && IsFolderSynchronizationEnabled; + public bool IsFolderSynchronizationEnabled => ActiveFolder?.IsSynchronizationEnabled ?? false; public int SelectedItemCount => SelectedItems.Count; public bool HasMultipleItemSelections => SelectedItemCount > 1; public bool HasSelectedItems => SelectedItems.Any(); public bool IsArchiveSpecialFolder => ActiveFolder?.SpecialFolderType == SpecialFolderType.Archive; - public bool IsEmpty => !IsPerformingSearch && MailCollection.Count == 0; + public bool IsEmpty => !IsInitializingFolder && !IsPerformingSearch && MailCollection.Count == 0; public bool IsCriteriaFailed => IsEmpty && IsInSearchMode; public bool IsFolderEmpty => !IsInitializingFolder && IsEmpty && !IsInSearchMode; private bool _isPerformingSearch; - public bool IsPerformingSearch { get => _isPerformingSearch; @@ -318,7 +267,7 @@ namespace Wino.Mail.ViewModels } } - public bool IsInSearchMode => !string.IsNullOrEmpty(SearchQuery); + public bool IsInSearchMode { get; set; } #endregion @@ -444,6 +393,55 @@ namespace Wino.Mail.ViewModels SelectedFolderPivot = PivotFolders.FirstOrDefault(); } + #region Commands + + /// + /// Executes the requested mail operation for currently selected items. + /// + /// Action to execute for selected items. + [RelayCommand] + private async Task MailOperationAsync(int mailOperationIndex) + { + if (!SelectedItems.Any()) return; + + // Commands don't like enums. So it has to be int. + var operation = (MailOperation)mailOperationIndex; + + var package = new MailOperationPreperationRequest(operation, SelectedItems.Select(a => a.MailCopy)); + + await ExecuteMailOperationAsync(package); + } + + /// + /// Sens a new message to synchronize current folder. + /// + [RelayCommand] + private void SyncFolder() + { + if (!CanSynchronize) return; + + // Only synchronize listed folders. + + // When doing linked inbox sync, we need to save the sync id to report progress back only once. + // Otherwise, we will report progress for each folder and that's what we don't want. + + trackingSynchronizationId = Guid.NewGuid(); + completedTrackingSynchronizationCount = 0; + + foreach (var folder in ActiveFolder.HandlingFolders) + { + var options = new SynchronizationOptions() + { + AccountId = folder.MailAccountId, + Type = SynchronizationType.Custom, + SynchronizationFolderIds = [folder.Id], + GroupedSynchronizationTrackingId = trackingSynchronizationId + }; + + Messenger.Send(new NewSynchronizationRequested(options)); + } + } + [RelayCommand] private async Task SelectedPivotChanged() { @@ -472,6 +470,74 @@ namespace Wino.Mail.ViewModels await InitializeFolderAsync(); } + [RelayCommand] + public async Task PerformSearchAsync() + { + try + { + if (string.IsNullOrEmpty(SearchQuery) && IsInSearchMode) + { + IsInSearchMode = false; + await InitializeFolderAsync(); + } + + if (!string.IsNullOrEmpty(SearchQuery)) + { + IsInSearchMode = true; + IsPerformingSearch = true; + await InitializeFolderAsync(); + } + } + finally + { + IsPerformingSearch = false; + } + } + + [RelayCommand] + private async Task EnableFolderSynchronizationAsync() + { + if (ActiveFolder == null) return; + + foreach (var folder in ActiveFolder.HandlingFolders) + { + await _folderService.ChangeFolderSynchronizationStateAsync(folder.Id, true); + } + + // TODO + //ActiveFolder.IsSynchronizationEnabled = true; + + //OnPropertyChanged(nameof(IsFolderSynchronizationEnabled)); + //OnPropertyChanged(nameof(CanSynchronize)); + + //SyncFolderCommand?.Execute(null); + } + + [RelayCommand] + private async Task LoadMoreItemsAsync() + { + if (IsInitializingFolder) return; + + await ExecuteUIThread(() => { IsInitializingFolder = true; }); + + var initializationOptions = new MailListInitializationOptions(ActiveFolder.HandlingFolders, + SelectedFilterOption.Type, + SelectedSortingOption.Type, + PreferencesService.IsThreadingEnabled, + SelectedFolderPivot.IsFocused, + IsInSearchMode ? SearchQuery : string.Empty, + MailCollection.MailCopyIdHashSet); + + var items = await _mailService.FetchMailsAsync(initializationOptions).ConfigureAwait(false); + + var viewModels = PrepareMailViewModels(items); + + await ExecuteUIThread(() => { MailCollection.AddRange(viewModels, clearIdCache: false); }); + await ExecuteUIThread(() => { IsInitializingFolder = false; }); + } + + #endregion + public IEnumerable GetTargetMailItemViewModels(IMailItem clickedItem) { // Threat threads as a whole and include everything in the group. Except single selections outside of the thread. @@ -496,7 +562,7 @@ namespace Wino.Mail.ViewModels if (includedInSelectedItems) contextMailItems = SelectedItems; else - contextMailItems = new List() { clickedMailItemViewModel }; + contextMailItems = [clickedMailItemViewModel]; } return contextMailItems; @@ -506,7 +572,7 @@ namespace Wino.Mail.ViewModels => _contextMenuItemService.GetMailItemContextMenuActions(contextMailItems); public void ChangeCustomFocusedState(IEnumerable mailItems, bool isFocused) - => mailItems.Where(a => a is MailItemViewModel).Cast().ForEach(a => a.IsCustomFocused = isFocused); + => mailItems.OfType().ForEach(a => a.IsCustomFocused = isFocused); private bool ShouldPreventItemAdd(IMailItem mailItem) { @@ -650,29 +716,6 @@ namespace Wino.Mail.ViewModels } } - [RelayCommand] - private async Task LoadMoreItemsAsync() - { - if (IsInitializingFolder) return; - - await ExecuteUIThread(() => { IsInitializingFolder = true; }); - - var initializationOptions = new MailListInitializationOptions(ActiveFolder.HandlingFolders, - SelectedFilterOption.Type, - SelectedSortingOption.Type, - PreferencesService.IsThreadingEnabled, - SelectedFolderPivot.IsFocused, - SearchQuery, - MailCollection.MailCopyIdHashSet); - - var items = await _mailService.FetchMailsAsync(initializationOptions).ConfigureAwait(false); - - var viewModels = PrepareMailViewModels(items); - - await ExecuteUIThread(() => { MailCollection.AddRange(viewModels, clearIdCache: false); }); - await ExecuteUIThread(() => { IsInitializingFolder = false; }); - } - private async Task InitializeFolderAsync() { if (SelectedFilterOption == null || SelectedFolderPivot == null || SelectedSortingOption == null) @@ -680,10 +723,6 @@ namespace Wino.Mail.ViewModels try { - // Clear search query if not performing search. - if (!IsPerformingSearch) - SearchQuery = string.Empty; - MailCollection.Clear(); MailCollection.MailCopyIdHashSet.Clear(); @@ -768,53 +807,12 @@ namespace Wino.Mail.ViewModels } } - [RelayCommand] - private async Task EnableFolderSynchronizationAsync() - { - if (ActiveFolder == null) return; + #region Receivers + void IRecipient.Receive(MailItemSelectedEvent message) + => SelectedItems.Add(message.SelectedMailItem); - foreach (var folder in ActiveFolder.HandlingFolders) - { - await _folderService.ChangeFolderSynchronizationStateAsync(folder.Id, true); - } - - // TODO - //ActiveFolder.IsSynchronizationEnabled = true; - - //OnPropertyChanged(nameof(IsFolderSynchronizationEnabled)); - //OnPropertyChanged(nameof(CanSynchronize)); - - //SyncFolderCommand?.Execute(null); - } - - void IRecipient.Receive(MailItemNavigationRequested message) - { - // Find mail item and add to selected items. - - MailItemViewModel navigatingMailItem = null; - ThreadMailItemViewModel threadMailItemViewModel = null; - - for (int i = 0; i < 3; i++) - { - var mailContainer = MailCollection.GetMailItemContainer(message.UniqueMailId); - - if (mailContainer != null) - { - navigatingMailItem = mailContainer.ItemViewModel; - threadMailItemViewModel = mailContainer.ThreadViewModel; - - break; - } - } - - if (threadMailItemViewModel != null) - threadMailItemViewModel.IsThreadExpanded = true; - - if (navigatingMailItem != null) - WeakReferenceMessenger.Default.Send(new SelectMailItemContainerEvent(navigatingMailItem, message.ScrollToItem)); - else - Debugger.Break(); - } + void IRecipient.Receive(MailItemSelectionRemovedEvent message) + => SelectedItems.Remove(message.RemovedMailItem); async void IRecipient.Receive(ActiveMailFolderChangedEvent message) { @@ -835,6 +833,9 @@ namespace Wino.Mail.ViewModels // Prepare Focused - Other or folder name tabs. UpdateFolderPivots(); + // Reset filters and sorting options. + ResetFilters(); + await InitializeFolderAsync(); // TODO: This should be done in a better way. @@ -847,14 +848,15 @@ namespace Wino.Mail.ViewModels message.FolderInitLoadAwaitTask?.TrySetResult(true); isChangingFolder = false; + + void ResetFilters() + { + SelectedFilterOption = FilterOptions[0]; + SelectedSortingOption = SortingOptions[0]; + SearchQuery = string.Empty; + } } - void IRecipient.Receive(MailItemSelectedEvent message) - => SelectedItems.Add(message.SelectedMailItem); - - void IRecipient.Receive(MailItemSelectionRemovedEvent message) - => SelectedItems.Remove(message.RemovedMailItem); - public void Receive(AccountSynchronizationCompleted message) { if (ActiveFolder == null) return; @@ -889,24 +891,40 @@ namespace Wino.Mail.ViewModels } } + void IRecipient.Receive(MailItemNavigationRequested message) + { + // Find mail item and add to selected items. + + MailItemViewModel navigatingMailItem = null; + ThreadMailItemViewModel threadMailItemViewModel = null; + + for (int i = 0; i < 3; i++) + { + var mailContainer = MailCollection.GetMailItemContainer(message.UniqueMailId); + + if (mailContainer != null) + { + navigatingMailItem = mailContainer.ItemViewModel; + threadMailItemViewModel = mailContainer.ThreadViewModel; + + break; + } + } + + if (threadMailItemViewModel != null) + threadMailItemViewModel.IsThreadExpanded = true; + + if (navigatingMailItem != null) + WeakReferenceMessenger.Default.Send(new SelectMailItemContainerEvent(navigatingMailItem, message.ScrollToItem)); + else + Debugger.Break(); + } + + #endregion + public async void Receive(NewSynchronizationRequested message) => await ExecuteUIThread(() => { OnPropertyChanged(nameof(CanSynchronize)); }); - [RelayCommand] - public async Task PerformSearchAsync() - { - try - { - IsPerformingSearch = !string.IsNullOrEmpty(SearchQuery); - - await InitializeFolderAsync(); - } - finally - { - IsPerformingSearch = false; - } - } - public async void Receive(AccountSynchronizerStateChanged message) => await CheckIfAccountIsSynchronizingAsync(); diff --git a/Wino.Mail/Controls/Advanced/WinoListView.cs b/Wino.Mail/Controls/Advanced/WinoListView.cs index a6382321..a1f7ece3 100644 --- a/Wino.Mail/Controls/Advanced/WinoListView.cs +++ b/Wino.Mail/Controls/Advanced/WinoListView.cs @@ -205,7 +205,7 @@ namespace Wino.Controls.Advanced { bool found = false; - Items.Where(a => a is ThreadMailItemViewModel).Cast().ForEach(c => + Items.OfType().ForEach(c => { if (!found) { diff --git a/Wino.Mail/Views/MailListPage.xaml b/Wino.Mail/Views/MailListPage.xaml index bab8c1bb..d231ef21 100644 --- a/Wino.Mail/Views/MailListPage.xaml +++ b/Wino.Mail/Views/MailListPage.xaml @@ -391,6 +391,7 @@ PlaceholderText="{x:Bind domain:Translator.SearchBarPlaceholder}" QueryIcon="Find" TabIndex="1000" + TextChanged="SearchBar_TextChanged" Text="{x:Bind ViewModel.SearchQuery, Mode=TwoWay}"> @@ -590,36 +591,6 @@ x:Load="{x:Bind ViewModel.IsInitializingFolder, Mode=OneWay}" /> - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - +