Online Search (#576)
* Very basic online search for gmail. * Server side of handling offline search and listing part in listing page. * Default search mode implementation and search UI improvements. * Online search for Outlook. * Very basic online search for gmail. * Server side of handling offline search and listing part in listing page. * Default search mode implementation and search UI improvements. * Online search for Outlook. * Online search for imap without downloading the messages yet. TODO * Completing imap search.
This commit is contained in:
@@ -18,6 +18,10 @@ public partial class AppPreferencesPageViewModel : MailBaseViewModel
|
||||
[ObservableProperty]
|
||||
private List<string> _appTerminationBehavior;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
public partial List<string> SearchModes { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsStartupBehaviorDisabled))]
|
||||
[NotifyPropertyChangedFor(nameof(IsStartupBehaviorEnabled))]
|
||||
@@ -38,6 +42,18 @@ public partial class AppPreferencesPageViewModel : MailBaseViewModel
|
||||
}
|
||||
}
|
||||
|
||||
private string _selectedDefaultSearchMode;
|
||||
public string SelectedDefaultSearchMode
|
||||
{
|
||||
get => _selectedDefaultSearchMode;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _selectedDefaultSearchMode, value);
|
||||
|
||||
PreferencesService.DefaultSearchMode = (SearchMode)SearchModes.IndexOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly IMailDialogService _dialogService;
|
||||
private readonly IWinoServerConnectionManager _winoServerConnectionManager;
|
||||
private readonly IStartupBehaviorService _startupBehaviorService;
|
||||
@@ -61,7 +77,14 @@ public partial class AppPreferencesPageViewModel : MailBaseViewModel
|
||||
Translator.SettingsAppPreferences_ServerBackgroundingMode_Terminate_Title // "Terminate"
|
||||
];
|
||||
|
||||
SearchModes =
|
||||
[
|
||||
Translator.SettingsAppPreferences_SearchMode_Local,
|
||||
Translator.SettingsAppPreferences_SearchMode_Online
|
||||
];
|
||||
|
||||
SelectedAppTerminationBehavior = _appTerminationBehavior[(int)PreferencesService.ServerTerminationBehavior];
|
||||
SelectedDefaultSearchMode = SearchModes[(int)PreferencesService.DefaultSearchMode];
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -22,6 +22,7 @@ using Wino.Core.Domain.Models.Folders;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Menus;
|
||||
using Wino.Core.Domain.Models.Reader;
|
||||
using Wino.Core.Domain.Models.Server;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Mail.ViewModels.Collections;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
@@ -71,12 +72,14 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
public IThemeService ThemeService { get; }
|
||||
|
||||
private readonly IAccountService _accountService;
|
||||
private readonly IMailDialogService _mailDialogService;
|
||||
private readonly IMailService _mailService;
|
||||
private readonly IFolderService _folderService;
|
||||
private readonly IThreadingStrategyProvider _threadingStrategyProvider;
|
||||
private readonly IContextMenuItemService _contextMenuItemService;
|
||||
private readonly IWinoRequestDelegator _winoRequestDelegator;
|
||||
private readonly IKeyPressService _keyPressService;
|
||||
private readonly IWinoLogger _winoLogger;
|
||||
private readonly IWinoServerConnectionManager _winoServerConnectionManager;
|
||||
private MailItemViewModel _activeMailItem;
|
||||
|
||||
@@ -100,7 +103,7 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
private bool isMultiSelectionModeEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
private string searchQuery;
|
||||
public partial string SearchQuery { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
private FilterOption _selectedFilterOption;
|
||||
@@ -109,7 +112,6 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
// Indicates state when folder is initializing. It can happen after folder navigation, search or filter change applied or loading more items.
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsEmpty))]
|
||||
[NotifyPropertyChangedFor(nameof(IsCriteriaFailed))]
|
||||
[NotifyPropertyChangedFor(nameof(IsFolderEmpty))]
|
||||
[NotifyPropertyChangedFor(nameof(IsProgressRing))]
|
||||
private bool isInitializingFolder;
|
||||
@@ -147,6 +149,7 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
public MailListPageViewModel(IMailDialogService dialogService,
|
||||
INavigationService navigationService,
|
||||
IAccountService accountService,
|
||||
IMailDialogService mailDialogService,
|
||||
IMailService mailService,
|
||||
IStatePersistanceService statePersistenceService,
|
||||
IFolderService folderService,
|
||||
@@ -156,14 +159,17 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
IKeyPressService keyPressService,
|
||||
IPreferencesService preferencesService,
|
||||
IThemeService themeService,
|
||||
IWinoLogger winoLogger,
|
||||
IWinoServerConnectionManager winoServerConnectionManager)
|
||||
{
|
||||
PreferencesService = preferencesService;
|
||||
ThemeService = themeService;
|
||||
_winoLogger = winoLogger;
|
||||
_winoServerConnectionManager = winoServerConnectionManager;
|
||||
StatePersistenceService = statePersistenceService;
|
||||
NavigationService = navigationService;
|
||||
_accountService = accountService;
|
||||
_mailDialogService = mailDialogService;
|
||||
_mailService = mailService;
|
||||
_folderService = folderService;
|
||||
_threadingStrategyProvider = threadingStrategyProvider;
|
||||
@@ -254,6 +260,7 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
public bool IsArchiveSpecialFolder => ActiveFolder?.SpecialFolderType == SpecialFolderType.Archive;
|
||||
|
||||
public string SelectedMessageText => HasSelectedItems ? string.Format(Translator.MailsSelected, SelectedItemCount) : Translator.NoMailSelected;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates current state of the mail list. Doesn't matter it's loading or no.
|
||||
/// </summary>
|
||||
@@ -263,11 +270,21 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
/// Progress ring only should be visible when the folder is initializing and there are no items. We don't need to show it when there are items.
|
||||
/// </summary>
|
||||
public bool IsProgressRing => IsInitializingFolder && IsEmpty;
|
||||
private bool isFilters => IsInSearchMode || SelectedFilterOption.Type != FilterOptionType.All;
|
||||
public bool IsCriteriaFailed => !IsInitializingFolder && IsEmpty && isFilters;
|
||||
public bool IsFolderEmpty => !IsInitializingFolder && IsEmpty && !isFilters;
|
||||
public bool IsFolderEmpty => !IsInitializingFolder && IsEmpty;
|
||||
|
||||
public bool IsInSearchMode { get; set; }
|
||||
public bool HasNoOnlineSearchResult { get; private set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsInSearchMode { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsOnlineSearchButtonVisible { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsOnlineSearchEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool AreSearchResultsOnline { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -333,7 +350,6 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
private void NotifyItemFoundState()
|
||||
{
|
||||
OnPropertyChanged(nameof(IsEmpty));
|
||||
OnPropertyChanged(nameof(IsCriteriaFailed));
|
||||
OnPropertyChanged(nameof(IsFolderEmpty));
|
||||
}
|
||||
|
||||
@@ -378,41 +394,52 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
|
||||
private async Task UpdateFolderPivotsAsync()
|
||||
{
|
||||
if (ActiveFolder == null) return;
|
||||
|
||||
PivotFolders.Clear();
|
||||
SelectedFolderPivot = null;
|
||||
|
||||
if (ActiveFolder == null) return;
|
||||
|
||||
// Merged folders don't support focused feature.
|
||||
|
||||
if (ActiveFolder is IMergedAccountFolderMenuItem)
|
||||
if (IsInSearchMode)
|
||||
{
|
||||
PivotFolders.Add(new FolderPivotViewModel(ActiveFolder.FolderName, null));
|
||||
var isFocused = SelectedFolderPivot?.IsFocused;
|
||||
|
||||
PivotFolders.Add(new FolderPivotViewModel(Translator.SearchPivotName, isFocused));
|
||||
}
|
||||
else if (ActiveFolder is IFolderMenuItem singleFolderMenuItem)
|
||||
else
|
||||
{
|
||||
var parentAccount = singleFolderMenuItem.ParentAccount;
|
||||
// Merged folders don't support focused feature.
|
||||
|
||||
bool isFocusedInboxEnabled = await _accountService.IsAccountFocusedEnabledAsync(parentAccount.Id);
|
||||
bool isInboxFolder = ActiveFolder.SpecialFolderType == SpecialFolderType.Inbox;
|
||||
|
||||
// Folder supports Focused - Other
|
||||
if (isInboxFolder && isFocusedInboxEnabled)
|
||||
if (ActiveFolder is IMergedAccountFolderMenuItem)
|
||||
{
|
||||
// Can be passed as empty string. Focused - Other will be used regardless.
|
||||
var focusedItem = new FolderPivotViewModel(string.Empty, true);
|
||||
var otherItem = new FolderPivotViewModel(string.Empty, false);
|
||||
|
||||
PivotFolders.Add(focusedItem);
|
||||
PivotFolders.Add(otherItem);
|
||||
PivotFolders.Add(new FolderPivotViewModel(ActiveFolder.FolderName, null));
|
||||
}
|
||||
else
|
||||
else if (ActiveFolder is IFolderMenuItem singleFolderMenuItem)
|
||||
{
|
||||
// If the account and folder doesn't support focused feature, just add itself.
|
||||
PivotFolders.Add(new FolderPivotViewModel(singleFolderMenuItem.FolderName, null));
|
||||
var parentAccount = singleFolderMenuItem.ParentAccount;
|
||||
|
||||
bool isFocusedInboxEnabled = await _accountService.IsAccountFocusedEnabledAsync(parentAccount.Id);
|
||||
bool isInboxFolder = ActiveFolder.SpecialFolderType == SpecialFolderType.Inbox;
|
||||
|
||||
// Folder supports Focused - Other
|
||||
if (isInboxFolder && isFocusedInboxEnabled)
|
||||
{
|
||||
// Can be passed as empty string. Focused - Other will be used regardless.
|
||||
var focusedItem = new FolderPivotViewModel(string.Empty, true);
|
||||
var otherItem = new FolderPivotViewModel(string.Empty, false);
|
||||
|
||||
PivotFolders.Add(focusedItem);
|
||||
PivotFolders.Add(otherItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the account and folder doesn't support focused feature, just add itself.
|
||||
PivotFolders.Add(new FolderPivotViewModel(singleFolderMenuItem.FolderName, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// This will trigger refresh.
|
||||
SelectedFolderPivot = PivotFolders.FirstOrDefault();
|
||||
}
|
||||
@@ -512,33 +539,16 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
[RelayCommand]
|
||||
public async Task PerformSearchAsync()
|
||||
{
|
||||
if (string.IsNullOrEmpty(SearchQuery) && IsInSearchMode)
|
||||
IsOnlineSearchEnabled = false;
|
||||
AreSearchResultsOnline = false;
|
||||
IsInSearchMode = !string.IsNullOrEmpty(SearchQuery);
|
||||
|
||||
if (IsInSearchMode)
|
||||
{
|
||||
await UpdateFolderPivotsAsync();
|
||||
IsInSearchMode = false;
|
||||
await InitializeFolderAsync();
|
||||
IsOnlineSearchButtonVisible = false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(SearchQuery))
|
||||
{
|
||||
|
||||
IsInSearchMode = true;
|
||||
CreateSearchPivot();
|
||||
}
|
||||
|
||||
void CreateSearchPivot()
|
||||
{
|
||||
PivotFolders.Clear();
|
||||
var isFocused = SelectedFolderPivot?.IsFocused;
|
||||
SelectedFolderPivot = null;
|
||||
|
||||
if (ActiveFolder == null) return;
|
||||
|
||||
PivotFolders.Add(new FolderPivotViewModel(Translator.SearchPivotName, isFocused));
|
||||
|
||||
// This will trigger refresh.
|
||||
SelectedFolderPivot = PivotFolders.FirstOrDefault();
|
||||
}
|
||||
await UpdateFolderPivotsAsync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -555,7 +565,7 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
[RelayCommand]
|
||||
private async Task LoadMoreItemsAsync()
|
||||
{
|
||||
if (IsInitializingFolder) return;
|
||||
if (IsInitializingFolder || IsOnlineSearchEnabled) return;
|
||||
|
||||
await ExecuteUIThread(() => { IsInitializingFolder = true; });
|
||||
|
||||
@@ -763,6 +773,15 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task PerformOnlineSearchAsync()
|
||||
{
|
||||
IsOnlineSearchButtonVisible = false;
|
||||
IsOnlineSearchEnabled = true;
|
||||
|
||||
await InitializeFolderAsync();
|
||||
}
|
||||
|
||||
private async Task InitializeFolderAsync()
|
||||
{
|
||||
if (SelectedFilterOption == null || SelectedFolderPivot == null || SelectedSortingOption == null)
|
||||
@@ -783,14 +802,6 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
// Folder is changed during initialization.
|
||||
// Just cancel the existing one and wait for new initialization.
|
||||
|
||||
//if (listManipulationSemepahore.CurrentCount == 0)
|
||||
//{
|
||||
// Debug.WriteLine("Canceling initialization of mails.");
|
||||
|
||||
// listManipulationCancellationTokenSource.Cancel();
|
||||
// listManipulationCancellationTokenSource.Token.ThrowIfCancellationRequested();
|
||||
//}
|
||||
|
||||
if (!listManipulationCancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
listManipulationCancellationTokenSource.Cancel();
|
||||
@@ -812,15 +823,77 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
|
||||
// Here items are sorted and filtered.
|
||||
|
||||
List<IMailItem> items = null;
|
||||
List<MailCopy> onlineSearchItems = null;
|
||||
|
||||
bool isDoingSearch = !string.IsNullOrEmpty(SearchQuery);
|
||||
bool isDoingOnlineSearch = false;
|
||||
|
||||
if (isDoingSearch)
|
||||
{
|
||||
isDoingOnlineSearch = PreferencesService.DefaultSearchMode == SearchMode.Online || IsOnlineSearchEnabled;
|
||||
|
||||
// Perform online search.
|
||||
if (isDoingOnlineSearch)
|
||||
{
|
||||
WinoServerResponse<OnlineSearchResult> onlineSearchResult = null;
|
||||
string onlineSearchFailedMessage = null;
|
||||
|
||||
try
|
||||
{
|
||||
var accountIds = ActiveFolder.HandlingFolders.Select(a => a.MailAccountId).ToList();
|
||||
var folders = ActiveFolder.HandlingFolders.ToList();
|
||||
var searchRequest = new OnlineSearchRequested(accountIds, SearchQuery, folders);
|
||||
|
||||
onlineSearchResult = await _winoServerConnectionManager.GetResponseAsync<OnlineSearchResult, OnlineSearchRequested>(searchRequest, cancellationToken);
|
||||
|
||||
if (onlineSearchResult.IsSuccess)
|
||||
{
|
||||
await ExecuteUIThread(() => { AreSearchResultsOnline = true; });
|
||||
|
||||
onlineSearchItems = onlineSearchResult.Data.SearchResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
onlineSearchFailedMessage = onlineSearchResult.Message;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Failed to perform online search.");
|
||||
onlineSearchFailedMessage = ex.Message;
|
||||
}
|
||||
|
||||
if (onlineSearchResult != null && !onlineSearchResult.IsSuccess)
|
||||
{
|
||||
// Query or server error.
|
||||
var serverErrorMessage = string.Format(Translator.OnlineSearchFailed_Message, onlineSearchResult.Message);
|
||||
_mailDialogService.InfoBarMessage(Translator.GeneralTitle_Error, serverErrorMessage, InfoBarMessageType.Warning);
|
||||
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(onlineSearchFailedMessage))
|
||||
{
|
||||
// Fatal error.
|
||||
var serverErrorMessage = string.Format(Translator.OnlineSearchFailed_Message, onlineSearchFailedMessage);
|
||||
_mailDialogService.InfoBarMessage(Translator.GeneralTitle_Error, serverErrorMessage, InfoBarMessageType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var initializationOptions = new MailListInitializationOptions(ActiveFolder.HandlingFolders,
|
||||
SelectedFilterOption.Type,
|
||||
SelectedSortingOption.Type,
|
||||
PreferencesService.IsThreadingEnabled,
|
||||
SelectedFolderPivot.IsFocused,
|
||||
SearchQuery,
|
||||
MailCollection.MailCopyIdHashSet);
|
||||
MailCollection.MailCopyIdHashSet,
|
||||
onlineSearchItems);
|
||||
|
||||
var items = await _mailService.FetchMailsAsync(initializationOptions, cancellationToken).ConfigureAwait(false);
|
||||
items = await _mailService.FetchMailsAsync(initializationOptions, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!listManipulationCancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
@@ -830,7 +903,15 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
|
||||
var viewModels = PrepareMailViewModels(items);
|
||||
|
||||
await ExecuteUIThread(() => { MailCollection.AddRange(viewModels, true); });
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
MailCollection.AddRange(viewModels, true);
|
||||
|
||||
if (isDoingSearch && !isDoingOnlineSearch)
|
||||
{
|
||||
IsOnlineSearchButtonVisible = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -887,6 +968,10 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
// Notify change for archive-unarchive app bar button.
|
||||
OnPropertyChanged(nameof(IsArchiveSpecialFolder));
|
||||
|
||||
IsInSearchMode = false;
|
||||
IsOnlineSearchButtonVisible = false;
|
||||
AreSearchResultsOnline = false;
|
||||
|
||||
// Prepare Focused - Other or folder name tabs.
|
||||
await UpdateFolderPivotsAsync();
|
||||
|
||||
@@ -918,6 +1003,7 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
SelectedSortingOption = SortingOptions[0];
|
||||
SearchQuery = string.Empty;
|
||||
IsInSearchMode = false;
|
||||
IsOnlineSearchEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user