Fix search and global title bar issues.

This commit is contained in:
Burak Kaan Köse
2026-03-25 09:45:49 +01:00
parent 7aad6b0157
commit 0056f372b9
17 changed files with 417 additions and 168 deletions
@@ -633,6 +633,22 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
return ApplyDisplayRequestAsync(new CalendarDisplayRequest(CurrentVisibleRange.DisplayType, CurrentVisibleRange.AnchorDate), forceReload: true);
}
public async Task<IReadOnlyList<CalendarItem>> SearchCalendarItemsAsync(string queryText, int limit, CancellationToken cancellationToken)
{
var results = await _calendarService.SearchCalendarItemsAsync(queryText, limit, cancellationToken).ConfigureAwait(false);
var activeCalendarIds = AccountCalendarStateService.ActiveCalendars.Select(calendar => calendar.Id).ToHashSet();
return results
.Where(result => activeCalendarIds.Contains(result.CalendarId))
.ToList();
}
public void OpenCalendarSearchResult(CalendarItem calendarItem)
{
ArgumentNullException.ThrowIfNull(calendarItem);
NavigateEvent(new CalendarItemViewModel(calendarItem), CalendarEventTargetType.Single);
}
private async Task<List<CalendarItemViewModel>> LoadCalendarItemsAsync(DateRange loadedDateWindow, long lifetimeVersion)
{
var loadedItems = new Dictionary<Guid, CalendarItemViewModel>();
@@ -41,6 +41,7 @@ public interface ICalendarService
Task<List<CalendarEventAttendee>> GetAttendeesAsync(Guid calendarEventTrackingId);
Task<List<CalendarEventAttendee>> ManageEventAttendeesAsync(Guid calendarItemId, List<CalendarEventAttendee> allAttendees);
Task UpdateCalendarItemAsync(CalendarItem calendarItem, List<CalendarEventAttendee> attendees);
Task<List<CalendarItem>> SearchCalendarItemsAsync(string searchQuery, int limit, CancellationToken cancellationToken = default);
Task<List<Reminder>> GetRemindersAsync(Guid calendarItemId);
Task SaveRemindersAsync(Guid calendarItemId, List<Reminder> reminders);
Task SnoozeCalendarItemAsync(Guid calendarItemId, DateTime snoozedUntilLocal);
-8
View File
@@ -13,14 +13,6 @@ namespace Wino.Mail.WinUI;
public partial class BasePage : Page, IRecipient<LanguageChanged>
{
public UIElement ShellContent
{
get { return (UIElement)GetValue(ShellContentProperty); }
set { SetValue(ShellContentProperty, value); }
}
public static readonly DependencyProperty ShellContentProperty = DependencyProperty.Register(nameof(ShellContent), typeof(UIElement), typeof(BasePage), new PropertyMetadata(null));
public void Receive(LanguageChanged message)
{
OnLanguageChanged();
@@ -0,0 +1,16 @@
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Wino.Mail.WinUI.Models;
namespace Wino.Mail.WinUI.Interfaces;
public interface ITitleBarSearchHost
{
string SearchText { get; set; }
string SearchPlaceholderText { get; }
ObservableCollection<TitleBarSearchSuggestion> SearchSuggestions { get; }
Task OnTitleBarSearchTextChangedAsync();
void OnTitleBarSearchSuggestionChosen(TitleBarSearchSuggestion suggestion);
Task OnTitleBarSearchSubmittedAsync(string queryText, TitleBarSearchSuggestion? chosenSuggestion);
}
@@ -1,11 +1,10 @@
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Wino.Messaging.UI;
namespace Wino.Mail.WinUI.Interfaces;
public interface IWinoShellWindow : IRecipient<TitleBarShellContentUpdated>
public interface IWinoShellWindow
{
void HandleAppActivation(string? launchArguments, string? tileId = null, string? appId = null);
TitleBar GetTitleBar();
@@ -0,0 +1,8 @@
namespace Wino.Mail.WinUI.Models;
public sealed class TitleBarSearchSuggestion(string title, string subtitle = "", object? tag = null)
{
public string Title { get; } = title;
public string Subtitle { get; } = subtitle;
public object? Tag { get; } = tag;
}
+32 -2
View File
@@ -9,6 +9,7 @@
xmlns:helpers="using:Wino.Helpers"
xmlns:local="using:Wino.Mail.WinUI"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:searchModels="using:Wino.Mail.WinUI.Models"
xmlns:syncModels="using:Wino.Core.Domain.Models.Synchronization"
xmlns:winuiex="using:WinUIEx"
Title="ShellWindow"
@@ -34,10 +35,39 @@
IsPaneToggleButtonVisible="True"
PaneToggleRequested="PaneButtonClicked"
Subtitle="{x:Bind StatePersistanceService.CoreWindowTitle, Mode=OneWay}">
<TitleBar.Resources>
<!--<TitleBar.Resources>
<HorizontalAlignment x:Key="TitleBarContentHorizontalAlignment">Stretch</HorizontalAlignment>
<VerticalAlignment x:Key="TitleBarContentVerticalAlignment">Stretch</VerticalAlignment>
</TitleBar.Resources>
</TitleBar.Resources>-->
<TitleBar.Content>
<Grid
MinWidth="280"
MaxWidth="520"
Margin="12,0,16,0">
<AutoSuggestBox
x:Name="TitleBarSearchBox"
VerticalAlignment="Center"
BorderBrush="Transparent"
PlaceholderText="{x:Bind domain:Translator.SearchBarPlaceholder}"
QueryIcon="Find"
QuerySubmitted="TitleBarSearchQuerySubmitted"
SuggestionChosen="TitleBarSearchSuggestionChosen"
TextChanged="TitleBarSearchTextChanged">
<AutoSuggestBox.ItemTemplate>
<DataTemplate x:DataType="searchModels:TitleBarSearchSuggestion">
<StackPanel Padding="0,4">
<TextBlock Text="{x:Bind Title}" TextTrimming="CharacterEllipsis" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind Subtitle}"
TextTrimming="CharacterEllipsis" />
</StackPanel>
</DataTemplate>
</AutoSuggestBox.ItemTemplate>
</AutoSuggestBox>
</Grid>
</TitleBar.Content>
<TitleBar.RightHeader>
<StackPanel Orientation="Horizontal" Spacing="8">
<Button
+50 -117
View File
@@ -1,6 +1,5 @@
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
@@ -16,9 +15,9 @@ using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Extensions;
using Wino.Mail.WinUI.Activation;
using Wino.Mail.WinUI.Controls;
using Wino.Mail.WinUI.Extensions;
using Wino.Mail.WinUI.Interfaces;
using Wino.Mail.WinUI.Models;
using Wino.Mail.WinUI.Views;
using Wino.Messaging.Client.Shell;
using Wino.Messaging.UI;
@@ -43,10 +42,9 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
public ObservableCollection<SynchronizationActionItem> SyncActionItems { get; } = new();
private bool _calendarReminderServerStartAttempted;
private ICalendarShellClient? _activeCalendarClient;
private readonly CalendarTitleBarContent _calendarTitleBarContent = new();
private long _calendarTypeSelectorChangedToken;
private ITitleBarSearchHost? _activeTitleBarSearchHost;
private bool _isBackButtonVisibilityReady;
private bool _isSynchronizingTitleBarSearch;
public ShellWindow()
{
@@ -58,9 +56,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
MinWidth = 420;
MinHeight = 420;
ConfigureTitleBar();
_calendarTypeSelectorChangedToken = _calendarTitleBarContent.RegisterSelectedTypeChanged(CalendarTypeSelectorSelectedTypeChanged);
_calendarTitleBarContent.PreviousDateRequested += CalendarTitleBarContentPreviousDateRequested;
_calendarTitleBarContent.NextDateRequested += CalendarTitleBarContentNextDateRequested;
ApplyTitleBarSearchHost();
// Handle window closing event to minimize to tray instead of closing
Closed += OnWindowClosed;
@@ -141,7 +137,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
}
_isBackButtonVisibilityReady = true;
ApplyTitleBarContent();
ApplyTitleBarSearchHost();
RefreshBackButtonVisibility();
}
@@ -169,7 +165,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
public void Receive(TitleBarShellContentUpdated message)
{
ApplyTitleBarContent();
ApplyTitleBarSearchHost();
RefreshBackButtonVisibility();
}
@@ -266,40 +262,10 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
});
}
private void CalendarTypeSelectorSelectedTypeChanged(DependencyObject sender, DependencyProperty dp)
private void ApplyTitleBarSearchHost()
{
if (_activeCalendarClient == null)
return;
var selectedType = _calendarTitleBarContent.SelectedType;
if (_activeCalendarClient.StatePersistenceService.CalendarDisplayType != selectedType)
{
_activeCalendarClient.StatePersistenceService.CalendarDisplayType = selectedType;
}
}
private void ApplyTitleBarContent()
{
if (MainShellFrame.Content is not WinoAppShell shellPage)
{
AttachCalendarClient(null);
ShellTitleBar.Content = MainShellFrame.Content is BasePage basePage ? basePage.ShellContent : null;
RefreshBackButtonVisibility();
return;
}
AttachCalendarClient(shellPage.ViewModel.CalendarClient);
if (shellPage.ViewModel.IsCalendarMode && !shellPage.ViewModel.StatePersistenceService.IsEventDetailsVisible)
{
RefreshCalendarSelector();
ShellTitleBar.Content = _calendarTitleBarContent;
RefreshBackButtonVisibility();
return;
}
ShellTitleBar.Content = shellPage.GetShellFrame().Content is BasePage page ? page.ShellContent : null;
RefreshBackButtonVisibility();
_activeTitleBarSearchHost = ResolveActiveTitleBarSearchHost();
SynchronizeTitleBarSearchBox();
}
private void StatePersistenceServiceChanged(object? sender, string propertyName)
@@ -333,87 +299,58 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
ShellTitleBar.IsBackButtonVisible = NavigationService.CanGoBack();
}
private void AttachCalendarClient(ICalendarShellClient? calendarClient)
private ITitleBarSearchHost? ResolveActiveTitleBarSearchHost()
{
if (ReferenceEquals(_activeCalendarClient, calendarClient))
if (MainShellFrame.Content is WinoAppShell shellPage)
{
return shellPage.GetShellFrame().Content as ITitleBarSearchHost;
}
return MainShellFrame.Content as ITitleBarSearchHost;
}
private void SynchronizeTitleBarSearchBox()
{
_isSynchronizingTitleBarSearch = true;
try
{
TitleBarSearchBox.IsEnabled = _activeTitleBarSearchHost != null;
TitleBarSearchBox.PlaceholderText = _activeTitleBarSearchHost?.SearchPlaceholderText ?? Translator.SearchBarPlaceholder;
TitleBarSearchBox.ItemsSource = _activeTitleBarSearchHost?.SearchSuggestions;
TitleBarSearchBox.Text = _activeTitleBarSearchHost?.SearchText ?? string.Empty;
}
finally
{
_isSynchronizingTitleBarSearch = false;
}
}
private async void TitleBarSearchTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
if (_isSynchronizingTitleBarSearch || _activeTitleBarSearchHost == null)
return;
if (_activeCalendarClient != null)
{
_activeCalendarClient.PropertyChanged -= CalendarClientPropertyChanged;
_activeCalendarClient.StatePersistenceService.StatePropertyChanged -= CalendarStatePersistenceServiceChanged;
_activeTitleBarSearchHost.SearchText = sender.Text;
await _activeTitleBarSearchHost.OnTitleBarSearchTextChangedAsync();
}
_activeCalendarClient = calendarClient;
if (_activeCalendarClient != null)
private void TitleBarSearchSuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
{
_activeCalendarClient.PropertyChanged += CalendarClientPropertyChanged;
_activeCalendarClient.StatePersistenceService.StatePropertyChanged += CalendarStatePersistenceServiceChanged;
}
}
private void CalendarClientPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (!DispatcherQueue.HasThreadAccess)
{
var enqueued = DispatcherQueue.TryEnqueue(() => CalendarClientPropertyChanged(sender, e));
if (!enqueued)
throw new InvalidOperationException("Could not marshal calendar client changes onto the UI thread.");
return;
}
if (e.PropertyName == nameof(ICalendarShellClient.VisibleDateRangeText))
{
RefreshCalendarSelector();
}
}
private void CalendarStatePersistenceServiceChanged(object? sender, string propertyName)
{
if (!DispatcherQueue.HasThreadAccess)
{
var enqueued = DispatcherQueue.TryEnqueue(() => CalendarStatePersistenceServiceChanged(sender, propertyName));
if (!enqueued)
throw new InvalidOperationException("Could not marshal title bar state changes onto the UI thread.");
return;
}
if (propertyName == nameof(IStatePersistanceService.CalendarDisplayType) ||
propertyName == nameof(IStatePersistanceService.DayDisplayCount))
{
RefreshCalendarSelector();
return;
}
if (propertyName == nameof(IStatePersistanceService.IsEventDetailsVisible))
{
ApplyTitleBarContent();
}
}
private void RefreshCalendarSelector()
{
if (_activeCalendarClient == null)
if (_activeTitleBarSearchHost == null || args.SelectedItem is not TitleBarSearchSuggestion suggestion)
return;
_calendarTitleBarContent.VisibleDateRangeText = _activeCalendarClient.VisibleDateRangeText;
_calendarTitleBarContent.TodayClickedCommand = _activeCalendarClient.TodayClickedCommand;
_calendarTitleBarContent.DisplayDayCount = _activeCalendarClient.StatePersistenceService.DayDisplayCount;
_activeTitleBarSearchHost.OnTitleBarSearchSuggestionChosen(suggestion);
SynchronizeTitleBarSearchBox();
}
if (_calendarTitleBarContent.SelectedType != _activeCalendarClient.StatePersistenceService.CalendarDisplayType)
private async void TitleBarSearchQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
{
_calendarTitleBarContent.SelectedType = _activeCalendarClient.StatePersistenceService.CalendarDisplayType;
}
}
if (_activeTitleBarSearchHost == null)
return;
private void CalendarTitleBarContentPreviousDateRequested(object? sender, EventArgs e)
=> _activeCalendarClient?.PreviousDateRangeCommand.Execute(null);
private void CalendarTitleBarContentNextDateRequested(object? sender, EventArgs e)
=> _activeCalendarClient?.NextDateRangeCommand.Execute(null);
await _activeTitleBarSearchHost.OnTitleBarSearchSubmittedAsync(args.QueryText, args.ChosenSuggestion as TitleBarSearchSuggestion);
SynchronizeTitleBarSearchBox();
}
private void OnAppWindowClosing(object sender, Microsoft.UI.Windowing.AppWindowClosingEventArgs e)
{
@@ -428,11 +365,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
private void OnWindowClosed(object sender, WindowEventArgs e)
{
AppWindow.Closing -= OnAppWindowClosing;
AttachCalendarClient(null);
StatePersistanceService.StatePropertyChanged -= StatePersistenceServiceChanged;
_calendarTitleBarContent.UnregisterSelectedTypeChanged(_calendarTypeSelectorChangedToken);
_calendarTitleBarContent.PreviousDateRequested -= CalendarTitleBarContentPreviousDateRequested;
_calendarTitleBarContent.NextDateRequested -= CalendarTitleBarContentNextDateRequested;
UnregisterRecipients();
}
@@ -10,6 +10,7 @@
xmlns:domain="using:Wino.Core.Domain"
xmlns:helpers="using:Wino.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:winoControls="using:Wino.Mail.WinUI.Controls"
mc:Ignorable="d">
<Page.Resources>
@@ -20,7 +21,18 @@
</Page.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<winoControls:CalendarTitleBarContent
x:Name="CalendarToolbar"
Grid.Row="0"
Margin="4,0,7,8" />
<Border
Grid.Row="1"
Margin="4,0,7,7"
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
@@ -34,7 +46,10 @@
VisibleRange="{x:Bind ViewModel.CurrentVisibleRange, Mode=OneWay}" />
</Border>
<Canvas x:Name="CalendarOverlayCanvas" IsHitTestVisible="False">
<Canvas
x:Name="CalendarOverlayCanvas"
Grid.RowSpan="2"
IsHitTestVisible="False">
<Grid
x:Name="TeachingTipPositionerGrid"
Background="Transparent"
@@ -1,24 +1,53 @@
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Navigation;
using Windows.Foundation;
using Wino.Core.Domain;
using Wino.Calendar.Controls;
using Wino.Calendar.Views.Abstract;
using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Models.Calendar;
using Wino.Core.Domain.Interfaces;
using Wino.Mail.WinUI;
using Wino.Mail.WinUI.Interfaces;
using Wino.Mail.WinUI.Models;
using Wino.Messaging.Client.Calendar;
namespace Wino.Calendar.Views;
public sealed partial class CalendarPage : CalendarPageAbstract
public sealed partial class CalendarPage : CalendarPageAbstract, ITitleBarSearchHost
{
private const int PopupDialogOffset = 12;
private ICalendarShellClient CalendarShellClient { get; } = WinoApplication.Current.Services.GetRequiredService<ICalendarShellClient>();
private CancellationTokenSource? _searchCancellationTokenSource;
private long _calendarTypeSelectorChangedToken;
public ObservableCollection<TitleBarSearchSuggestion> SearchSuggestions { get; } = [];
public string SearchText { get; set; } = string.Empty;
public string SearchPlaceholderText => Translator.SearchBarPlaceholder;
public CalendarPage()
{
InitializeComponent();
_calendarTypeSelectorChangedToken = CalendarToolbar.RegisterSelectedTypeChanged(CalendarTypeSelectorSelectedTypeChanged);
CalendarToolbar.PreviousDateRequested += CalendarToolbarPreviousDateRequested;
CalendarToolbar.NextDateRequested += CalendarToolbarNextDateRequested;
ViewModel.PropertyChanged += ViewModelPropertyChanged;
CalendarShellClient.PropertyChanged += CalendarShellClientPropertyChanged;
CalendarShellClient.StatePersistenceService.StatePropertyChanged += CalendarStatePersistenceServiceChanged;
Unloaded += CalendarPageUnloaded;
RefreshCalendarToolbar();
}
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
@@ -29,6 +58,7 @@ public sealed partial class CalendarPage : CalendarPageAbstract
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
RefreshCalendarToolbar();
if (e.NavigationMode == NavigationMode.Back && ViewModel.RestoreVisibleState())
{
@@ -46,6 +76,67 @@ public sealed partial class CalendarPage : CalendarPageAbstract
WeakReferenceMessenger.Default.Send(new LoadCalendarMessage(request));
}
public async Task OnTitleBarSearchTextChangedAsync()
{
_searchCancellationTokenSource?.Cancel();
_searchCancellationTokenSource?.Dispose();
_searchCancellationTokenSource = null;
SearchSuggestions.Clear();
if (string.IsNullOrWhiteSpace(SearchText))
return;
_searchCancellationTokenSource = new CancellationTokenSource();
var cancellationToken = _searchCancellationTokenSource.Token;
try
{
await Task.Delay(150, cancellationToken);
var results = await ViewModel.SearchCalendarItemsAsync(SearchText, 6, cancellationToken);
if (cancellationToken.IsCancellationRequested)
return;
foreach (var result in results)
{
var subtitleParts = new[]
{
result.AssignedCalendar?.MailAccount?.Name,
result.AssignedCalendar?.Name,
result.LocalStartDate.ToString("g")
}.Where(part => !string.IsNullOrWhiteSpace(part));
SearchSuggestions.Add(new TitleBarSearchSuggestion(result.Title, string.Join(" • ", subtitleParts), result));
}
}
catch (OperationCanceledException)
{
}
}
public void OnTitleBarSearchSuggestionChosen(TitleBarSearchSuggestion suggestion)
{
SearchText = suggestion.Title;
}
public async Task OnTitleBarSearchSubmittedAsync(string queryText, TitleBarSearchSuggestion? chosenSuggestion)
{
SearchText = queryText;
if (chosenSuggestion?.Tag is CalendarItem selectedItem)
{
ViewModel.OpenCalendarSearchResult(selectedItem);
return;
}
var result = (await ViewModel.SearchCalendarItemsAsync(queryText, 1, CancellationToken.None)).FirstOrDefault();
if (result != null)
{
ViewModel.OpenCalendarSearchResult(result);
}
}
private void CalendarSurfaceEmptySlotTapped(object sender, CalendarEmptySlotTappedEventArgs e)
{
if (ViewModel.DisplayDetailsCalendarItemViewModel != null)
@@ -111,4 +202,69 @@ public sealed partial class CalendarPage : CalendarPageAbstract
private void EndTimeDurationSubmitted(ComboBox sender, ComboBoxTextSubmittedEventArgs args)
=> ViewModel.SelectedEndTimeString = args.Text;
private void CalendarTypeSelectorSelectedTypeChanged(DependencyObject sender, DependencyProperty dp)
{
var selectedType = CalendarToolbar.SelectedType;
if (CalendarShellClient.StatePersistenceService.CalendarDisplayType != selectedType)
{
CalendarShellClient.StatePersistenceService.CalendarDisplayType = selectedType;
}
}
private void CalendarToolbarPreviousDateRequested(object? sender, EventArgs e)
=> CalendarShellClient.PreviousDateRangeCommand.Execute(null);
private void CalendarToolbarNextDateRequested(object? sender, EventArgs e)
=> CalendarShellClient.NextDateRangeCommand.Execute(null);
private void ViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(ViewModel.VisibleDateRangeText))
{
RefreshCalendarToolbar();
}
}
private void CalendarShellClientPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(ICalendarShellClient.VisibleDateRangeText))
{
RefreshCalendarToolbar();
}
}
private void CalendarStatePersistenceServiceChanged(object? sender, string propertyName)
{
if (propertyName == nameof(IStatePersistanceService.CalendarDisplayType) ||
propertyName == nameof(IStatePersistanceService.DayDisplayCount))
{
RefreshCalendarToolbar();
}
}
private void RefreshCalendarToolbar()
{
CalendarToolbar.VisibleDateRangeText = CalendarShellClient.VisibleDateRangeText;
CalendarToolbar.TodayClickedCommand = CalendarShellClient.TodayClickedCommand;
CalendarToolbar.DisplayDayCount = CalendarShellClient.StatePersistenceService.DayDisplayCount;
if (CalendarToolbar.SelectedType != CalendarShellClient.StatePersistenceService.CalendarDisplayType)
{
CalendarToolbar.SelectedType = CalendarShellClient.StatePersistenceService.CalendarDisplayType;
}
}
private void CalendarPageUnloaded(object sender, RoutedEventArgs e)
{
CalendarToolbar.UnregisterSelectedTypeChanged(_calendarTypeSelectorChangedToken);
CalendarToolbar.PreviousDateRequested -= CalendarToolbarPreviousDateRequested;
CalendarToolbar.NextDateRequested -= CalendarToolbarNextDateRequested;
ViewModel.PropertyChanged -= ViewModelPropertyChanged;
CalendarShellClient.PropertyChanged -= CalendarShellClientPropertyChanged;
CalendarShellClient.StatePersistenceService.StatePropertyChanged -= CalendarStatePersistenceServiceChanged;
_searchCancellationTokenSource?.Cancel();
_searchCancellationTokenSource?.Dispose();
Unloaded -= CalendarPageUnloaded;
}
}
@@ -142,32 +142,6 @@
</DataTemplate>
</Page.Resources>
<wino:BasePage.ShellContent>
<Grid HorizontalAlignment="Stretch">
<!-- Hidden focus receiver... -->
<TextBox
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Opacity="0"
Visibility="Collapsed" />
<AutoSuggestBox
x:Name="SearchBar"
Width="400"
Margin="2,0,-2,0"
VerticalAlignment="Center"
BorderBrush="Transparent"
GotFocus="SearchBoxFocused"
LostFocus="SearchBarUnfocused"
PlaceholderText="{x:Bind domain:Translator.SearchBarPlaceholder}"
QueryIcon="Find"
QuerySubmitted="SearchbarQuerySubmitted"
Text="{x:Bind ViewModel.SearchQuery, Mode=TwoWay}"
TextChanged="SearchBar_TextChanged" />
</Grid>
</wino:BasePage.ShellContent>
<Grid x:Name="RootGrid" Padding="0,2,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="MailListColumn" Width="{x:Bind ViewModel.MailListLength, Mode=OneWay, Converter={StaticResource GridLengthConverter}}" />
+27 -15
View File
@@ -1,4 +1,5 @@
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -27,6 +28,8 @@ using Wino.Mail.ViewModels.Messages;
using Wino.Mail.WinUI;
using Wino.Mail.WinUI.Controls.ListView;
using Wino.Mail.WinUI.Extensions;
using Wino.Mail.WinUI.Interfaces;
using Wino.Mail.WinUI.Models;
using Wino.MenuFlyouts.Context;
using Wino.Messaging.Client.Mails;
using Wino.Views.Abstract;
@@ -44,7 +47,8 @@ public sealed partial class MailListPage : MailListPageAbstract,
IRecipient<ClearMailSelectionsRequested>,
IRecipient<ActiveMailItemChangedEvent>,
IRecipient<SelectMailItemContainerEvent>,
IRecipient<DisposeRenderingFrameRequested>
IRecipient<DisposeRenderingFrameRequested>,
ITitleBarSearchHost
{
private const double RENDERING_COLUMN_MIN_WIDTH = 375;
private const int IDLE_NAVIGATION_DELAY_MS = 120;
@@ -52,6 +56,15 @@ public sealed partial class MailListPage : MailListPageAbstract,
private IStatePersistanceService StatePersistenceService { get; } = WinoApplication.Current.Services.GetService<IStatePersistanceService>() ?? throw new Exception($"Can't resolve {nameof(KeyPressService)}");
private IKeyPressService KeyPressService { get; } = WinoApplication.Current.Services.GetService<IKeyPressService>() ?? throw new Exception($"Can't resolve {nameof(KeyPressService)}");
public ObservableCollection<TitleBarSearchSuggestion> SearchSuggestions { get; } = [];
public string SearchText
{
get => ViewModel.SearchQuery;
set => ViewModel.SearchQuery = value;
}
public string SearchPlaceholderText => Translator.SearchBarPlaceholder;
public MailListPage()
{
InitializeComponent();
@@ -460,16 +473,6 @@ public sealed partial class MailListPage : MailListPageAbstract,
});
}
private void SearchBoxFocused(object sender, RoutedEventArgs e)
{
SearchBar.PlaceholderText = string.Empty;
}
private void SearchBarUnfocused(object sender, RoutedEventArgs e)
{
SearchBar.PlaceholderText = Translator.SearchBarPlaceholder;
}
/// <summary>
/// Thread header is mail info display control and it can be dragged spearately out of ListView.
/// We need to prepare a drag package for it from the items inside.
@@ -569,10 +572,9 @@ public sealed partial class MailListPage : MailListPageAbstract,
}
}
private async void SearchBar_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
public async Task OnTitleBarSearchTextChangedAsync()
{
// User clicked 'x' button to clearout the search text.
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput && string.IsNullOrWhiteSpace(sender.Text))
if (string.IsNullOrWhiteSpace(SearchText))
{
ViewModel.IsOnlineSearchButtonVisible = false;
await ViewModel.PerformSearchAsync();
@@ -903,9 +905,19 @@ public sealed partial class MailListPage : MailListPageAbstract,
await WinoClickItemInternalAsync(e.ClickedItem);
}
private void SearchbarQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
public void OnTitleBarSearchSuggestionChosen(TitleBarSearchSuggestion suggestion)
{
}
public Task OnTitleBarSearchSubmittedAsync(string queryText, TitleBarSearchSuggestion? chosenSuggestion)
{
SearchText = queryText;
if (ViewModel.PerformSearchCommand.CanExecute(null))
{
ViewModel.PerformSearchCommand.Execute(null);
}
return Task.CompletedTask;
}
}
@@ -122,11 +122,6 @@
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<AutoSuggestBox
PlaceholderText="{x:Bind domain:Translator.ContactsPage_SearchPlaceholder, Mode=OneTime}"
QueryIcon="Find"
Text="{x:Bind ViewModel.SearchQuery, Mode=TwoWay}" />
<Button
Grid.Column="1"
Command="{x:Bind ViewModel.ReloadContactsCommand}"
@@ -1,15 +1,30 @@
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Controls;
using Wino.Core.Domain;
using Wino.Mail.ViewModels;
using Wino.Mail.ViewModels.Data;
using Wino.Mail.WinUI.Interfaces;
using Wino.Mail.WinUI.Models;
using Wino.Views.Abstract;
namespace Wino.Views.Settings;
public sealed partial class ContactsPage : ContactsPageAbstract
public sealed partial class ContactsPage : ContactsPageAbstract, ITitleBarSearchHost
{
public ObservableCollection<TitleBarSearchSuggestion> SearchSuggestions { get; } = [];
public string SearchText
{
get => ViewModel.SearchQuery;
set => ViewModel.SearchQuery = value;
}
public string SearchPlaceholderText => Translator.ContactsPage_SearchPlaceholder;
public ContactsPage()
{
InitializeComponent();
@@ -110,4 +125,22 @@ public sealed partial class ContactsPage : ContactsPageAbstract
ContactsListView.SelectionChanged += ContactsListView_SelectionChanged;
ViewModel.SelectedContacts.Clear();
}
public Task OnTitleBarSearchTextChangedAsync() => Task.CompletedTask;
public void OnTitleBarSearchSuggestionChosen(TitleBarSearchSuggestion suggestion)
{
}
public Task OnTitleBarSearchSubmittedAsync(string queryText, TitleBarSearchSuggestion? chosenSuggestion)
{
SearchText = queryText;
if (ViewModel.ReloadContactsCommand.CanExecute(null))
{
ViewModel.ReloadContactsCommand.Execute(null);
}
return Task.CompletedTask;
}
}
+39 -1
View File
@@ -9,6 +9,8 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Settings;
using Wino.Helpers;
using Wino.Mail.ViewModels.Data;
using Wino.Mail.WinUI.Interfaces;
using Wino.Mail.WinUI.Models;
using Wino.Messaging.Client.Navigation;
using Wino.Messaging.UI;
using Wino.Views.Abstract;
@@ -21,9 +23,13 @@ public sealed partial class SettingsPage : SettingsPageAbstract,
IRecipient<BackBreadcrumNavigationRequested>,
IRecipient<SettingsRootNavigationRequested>,
IRecipient<MergedInboxRenamed>,
IRecipient<AccountUpdatedMessage>
IRecipient<AccountUpdatedMessage>,
ITitleBarSearchHost
{
public ObservableCollection<BreadcrumbNavigationItemViewModel> PageHistory { get; set; } = [];
public ObservableCollection<TitleBarSearchSuggestion> SearchSuggestions { get; } = [];
public string SearchText { get; set; } = string.Empty;
public string SearchPlaceholderText => Translator.SettingsHome_SearchPlaceholder;
public SettingsPage()
{
@@ -242,4 +248,36 @@ public sealed partial class SettingsPage : SettingsPageAbstract,
? Translator.MenuSettings
: activeTitle;
}
public Task OnTitleBarSearchTextChangedAsync()
{
SearchSuggestions.Clear();
foreach (var item in SettingsNavigationInfoProvider.Search(SearchText, ViewModel.ManageAccountsDescription).Take(6))
{
SearchSuggestions.Add(new TitleBarSearchSuggestion(item.Title, item.Description, item));
}
return Task.CompletedTask;
}
public void OnTitleBarSearchSuggestionChosen(TitleBarSearchSuggestion suggestion)
{
SearchText = suggestion.Title;
}
public Task OnTitleBarSearchSubmittedAsync(string queryText, TitleBarSearchSuggestion? chosenSuggestion)
{
SearchText = queryText;
var selectedSetting = chosenSuggestion?.Tag as SettingsNavigationItemInfo
?? SettingsNavigationInfoProvider.Search(queryText, ViewModel.ManageAccountsDescription).FirstOrDefault();
if (selectedSetting?.PageType is WinoPage pageType)
{
Receive(new SettingsRootNavigationRequested(pageType));
}
return Task.CompletedTask;
}
}
-1
View File
@@ -659,7 +659,6 @@
<VisualState x:Name="DefaultShellContentState" />
<VisualState x:Name="EventDetailsContentState">
<VisualState.Setters>
<Setter Target="RangeSummaryCard.IsEnabled" Value="False" />
<Setter Target="CalendarHostListView.IsEnabled" Value="False" />
</VisualState.Setters>
</VisualState>
+32
View File
@@ -182,6 +182,38 @@ public class CalendarService : BaseDatabaseService, ICalendarService
}
}
public async Task<List<CalendarItem>> SearchCalendarItemsAsync(string searchQuery, int limit, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (string.IsNullOrWhiteSpace(searchQuery) || limit <= 0)
return [];
var pattern = $"%{searchQuery.Trim()}%";
var results = await Connection.QueryAsync<CalendarItem>(
"""
SELECT *
FROM CalendarItem
WHERE IsHidden = 0
AND (Recurrence IS NULL OR Recurrence = '' OR RecurringCalendarItemId IS NOT NULL)
AND (Title LIKE ? OR Description LIKE ? OR Location LIKE ?)
ORDER BY StartDate DESC
LIMIT ?
""",
pattern,
pattern,
pattern,
limit).ConfigureAwait(false);
foreach (var result in results)
{
cancellationToken.ThrowIfCancellationRequested();
await LoadAssignedCalendarAsync(result).ConfigureAwait(false);
}
return results;
}
/// <summary>
/// Retrieves calendar events for a given calendar within the specified time period.
/// Returns all events (single instances and recurring event occurrences) that overlap with the period.