moved favourites into submenue

This commit is contained in:
Ebbe Baß
2026-10-13 07:56:46 +02:00
parent 52ca49ac85
commit ce9fdd7ec6
4 changed files with 165 additions and 134 deletions
+2 -5
View File
@@ -22,11 +22,8 @@ public sealed class AppSettings
public bool PlayGifPreviews { get; set; } public bool PlayGifPreviews { get; set; }
/// <summary> /// <summary>
/// Which tab the picker reopens on. Persisted so the choice survives an app restart, not /// Which source tab the picker reopens on. Persisted so the choice survives an app restart,
/// just a hide/show. Defaults to the favourites view, since that is where the memes you /// not just a hide/show.
/// actually reach for most often live.
/// </summary> /// </summary>
public MemeSource LastSource { get; set; } = MemeSource.Local; public MemeSource LastSource { get; set; } = MemeSource.Local;
public bool LastShowingFavorites { get; set; } = true;
} }
@@ -1,6 +1,7 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.IO; using System.IO;
using System.Windows.Data;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using EbbesMemeClipboard.Models; using EbbesMemeClipboard.Models;
@@ -35,6 +36,21 @@ public partial class PickerViewModel : ObservableObject
public ObservableCollection<MemeTileViewModel> Items { get; } = new(); public ObservableCollection<MemeTileViewModel> Items { get; } = new();
/// <summary>Favourites for the active source, shown in the favourites submenu.</summary>
public ObservableCollection<MemeTileViewModel> Favorites { get; } = new();
/// <summary>
/// Holds at most one navigation tile ("Favourites" or "Back") that is rendered as the first
/// cell of the grid, ahead of the actual memes.
/// </summary>
public ObservableCollection<FolderTileViewModel> HeaderTiles { get; } = new();
private readonly CompositeCollection _mainGrid = new();
private readonly CompositeCollection _favoritesGrid = new();
/// <summary>What the grid binds to: the nav tile followed by either the results or the favourites.</summary>
public CompositeCollection GridSource => IsInFavoritesView ? _favoritesGrid : _mainGrid;
[ObservableProperty] [ObservableProperty]
private string _searchText = string.Empty; private string _searchText = string.Empty;
@@ -47,12 +63,11 @@ public partial class PickerViewModel : ObservableObject
[ObservableProperty] [ObservableProperty]
private bool _isDialogOpen; private bool _isDialogOpen;
/// <summary>
/// Favourites act as a filtered view of the active source rather than an inline row, so they
/// get the full grid and the search box narrows within them.
/// </summary>
[ObservableProperty] [ObservableProperty]
private bool _showingFavorites; private bool _hasFavorites;
[ObservableProperty]
private bool _isInFavoritesView;
[ObservableProperty] [ObservableProperty]
private MemeSource _activeSource = MemeSource.Local; private MemeSource _activeSource = MemeSource.Local;
@@ -60,20 +75,13 @@ public partial class PickerViewModel : ObservableObject
public bool IsLocalSource => ActiveSource == MemeSource.Local; public bool IsLocalSource => ActiveSource == MemeSource.Local;
public bool IsGiphySource => ActiveSource == MemeSource.Giphy; public bool IsGiphySource => ActiveSource == MemeSource.Giphy;
/// <summary>Importing only applies to the local library, and not while browsing favourites.</summary> public string SearchPlaceholder => IsLocalSource
public bool CanImport => IsLocalSource && !ShowingFavorites;
public string SearchPlaceholder => ShowingFavorites
? "Search favourites..."
: IsLocalSource
? "Search your memes..." ? "Search your memes..."
: ActiveProvider?.SearchPlaceholder ?? "Search..."; : ActiveProvider?.SearchPlaceholder ?? "Search...";
public bool ShowEmptyState => Items.Count == 0 && !IsBusy && (ShowingFavorites || IsLocalSource); public bool ShowEmptyState => Items.Count == 0 && !IsBusy && IsLocalSource && !IsInFavoritesView;
public string EmptyStateText => ShowingFavorites public string EmptyStateText => "No memes yet. Click +, drag files in, or paste with Ctrl+V.";
? "No favourites here yet. Right-click any meme to add one."
: "No memes yet. Click +, drag files in, or paste with Ctrl+V.";
private IGifProvider? ActiveProvider => _providers.FirstOrDefault(p => p.Source == ActiveSource); private IGifProvider? ActiveProvider => _providers.FirstOrDefault(p => p.Source == ActiveSource);
@@ -100,33 +108,55 @@ public partial class PickerViewModel : ObservableObject
Items.CollectionChanged += OnItemsChanged; Items.CollectionChanged += OnItemsChanged;
// Restore the tab the picker was last left on. Assigned to the backing fields directly // Restore persisted view state. Assigned to the backing fields directly so restoring
// so restoring doesn't count as a user change and immediately re-save. // doesn't count as a user change and immediately re-save.
_activeSource = _settings.Current.LastSource; _activeSource = _settings.Current.LastSource;
_showingFavorites = _settings.Current.LastShowingFavorites;
// Seed the grid for the restored view. Remote sources are skipped here because that _mainGrid.Add(new CollectionContainer { Collection = HeaderTiles });
// needs an async call - OnShown covers it when the window is actually opened. _mainGrid.Add(new CollectionContainer { Collection = Items });
if (_showingFavorites) RefreshFavoriteItems(); _favoritesGrid.Add(new CollectionContainer { Collection = HeaderTiles });
else if (IsLocalSource) RefreshLocalItems(); _favoritesGrid.Add(new CollectionContainer { Collection = Favorites });
// Seed the grid. Remote sources are skipped here because that needs an async call -
// OnShown covers it when the window is actually opened.
if (IsLocalSource) RefreshLocalItems();
RefreshFavorites();
} }
private void OnItemsChanged(object? sender, NotifyCollectionChangedEventArgs e) => private void OnItemsChanged(object? sender, NotifyCollectionChangedEventArgs e) =>
OnPropertyChanged(nameof(ShowEmptyState)); OnPropertyChanged(nameof(ShowEmptyState));
partial void OnSearchTextChanged(string value) => _ = RefreshAsync(); partial void OnSearchTextChanged(string value)
{
// A query is a request to search, so it always leaves the favourites submenu.
if (!string.IsNullOrWhiteSpace(value)) IsInFavoritesView = false;
UpdateHeaderTile();
_ = RefreshAsync();
}
partial void OnHasFavoritesChanged(bool value) => UpdateHeaderTile();
partial void OnIsBusyChanged(bool value) => OnPropertyChanged(nameof(ShowEmptyState)); partial void OnIsBusyChanged(bool value) => OnPropertyChanged(nameof(ShowEmptyState));
partial void OnShowingFavoritesChanged(bool value) partial void OnIsInFavoritesViewChanged(bool value)
{ {
OnPropertyChanged(nameof(SearchPlaceholder)); OnPropertyChanged(nameof(GridSource));
OnPropertyChanged(nameof(CanImport));
OnPropertyChanged(nameof(EmptyStateText));
OnPropertyChanged(nameof(ShowEmptyState)); OnPropertyChanged(nameof(ShowEmptyState));
SearchText = string.Empty; UpdateHeaderTile();
PersistViewState(); }
_ = RefreshAsync();
private void UpdateHeaderTile()
{
HeaderTiles.Clear();
if (IsInFavoritesView)
{
HeaderTiles.Add(new FolderTileViewModel("\u25C4", "Back"));
}
else if (HasFavorites && string.IsNullOrWhiteSpace(SearchText))
{
HeaderTiles.Add(new FolderTileViewModel("\u2605", "Favourites"));
}
} }
partial void OnActiveSourceChanged(MemeSource value) partial void OnActiveSourceChanged(MemeSource value)
@@ -134,27 +164,24 @@ public partial class PickerViewModel : ObservableObject
OnPropertyChanged(nameof(IsLocalSource)); OnPropertyChanged(nameof(IsLocalSource));
OnPropertyChanged(nameof(IsGiphySource)); OnPropertyChanged(nameof(IsGiphySource));
OnPropertyChanged(nameof(SearchPlaceholder)); OnPropertyChanged(nameof(SearchPlaceholder));
OnPropertyChanged(nameof(CanImport));
OnPropertyChanged(nameof(ShowEmptyState)); OnPropertyChanged(nameof(ShowEmptyState));
_settings.Current.LastSource = value;
_ = _settings.SaveAsync();
IsInFavoritesView = false;
SearchText = string.Empty; SearchText = string.Empty;
PersistViewState(); RefreshFavorites();
// Setting SearchText above only triggers a refresh if the value actually changed, so // Setting SearchText above only triggers a refresh if the value actually changed, so
// refresh explicitly here to cover switching tabs with an already-empty box. // refresh explicitly here to cover switching tabs with an already-empty box.
_ = RefreshAsync(); _ = RefreshAsync();
} }
private void PersistViewState()
{
_settings.Current.LastSource = ActiveSource;
_settings.Current.LastShowingFavorites = ShowingFavorites;
_ = _settings.SaveAsync();
}
[RelayCommand] [RelayCommand]
private void SelectSource(MemeSource source) => ActiveSource = source; private void SelectSource(MemeSource source) => ActiveSource = source;
[RelayCommand] [RelayCommand]
private void ToggleFavoritesView() => ShowingFavorites = !ShowingFavorites; private void ToggleFavoritesView() => IsInFavoritesView = !IsInFavoritesView;
[RelayCommand] [RelayCommand]
private async Task SelectItemAsync(MemeTileViewModel? tile) private async Task SelectItemAsync(MemeTileViewModel? tile)
@@ -198,11 +225,7 @@ public partial class PickerViewModel : ObservableObject
if (_favorites.IsFavorite(tile.Source, tile.FavoriteKey)) if (_favorites.IsFavorite(tile.Source, tile.FavoriteKey))
{ {
await _favorites.RemoveAsync(tile.Source, tile.FavoriteKey); await _favorites.RemoveAsync(tile.Source, tile.FavoriteKey);
tile.IsFavorite = false;
StatusMessage = $"Removed {tile.DisplayName} from favourites"; StatusMessage = $"Removed {tile.DisplayName} from favourites";
// In the favourites view an unfavourited tile no longer belongs on screen.
if (ShowingFavorites) Items.Remove(tile);
} }
else else
{ {
@@ -215,9 +238,11 @@ public partial class PickerViewModel : ObservableObject
FullUrl = tile.RemoteResult?.FullUrl, FullUrl = tile.RemoteResult?.FullUrl,
DateAdded = DateTimeOffset.Now, DateAdded = DateTimeOffset.Now,
}); });
tile.IsFavorite = true;
StatusMessage = $"Added {tile.DisplayName} to favourites"; StatusMessage = $"Added {tile.DisplayName} to favourites";
} }
RefreshFavorites();
SyncFavoriteFlags();
} }
[RelayCommand] [RelayCommand]
@@ -230,7 +255,8 @@ public partial class PickerViewModel : ObservableObject
// Drop the matching favourite too, otherwise it lingers pointing at a deleted file. // Drop the matching favourite too, otherwise it lingers pointing at a deleted file.
await _favorites.RemoveAsync(MemeSource.Local, tile.Record.Id); await _favorites.RemoveAsync(MemeSource.Local, tile.Record.Id);
await RefreshAsync(); RefreshLocalItems();
RefreshFavorites();
StatusMessage = $"Removed {name}"; StatusMessage = $"Removed {name}";
} }
@@ -296,7 +322,6 @@ public partial class PickerViewModel : ObservableObject
return; return;
} }
ShowingFavorites = false;
ActiveSource = MemeSource.Local; ActiveSource = MemeSource.Local;
RefreshLocalItems(); RefreshLocalItems();
StatusMessage = $"Pasted {name}"; StatusMessage = $"Pasted {name}";
@@ -320,7 +345,6 @@ public partial class PickerViewModel : ObservableObject
} }
var imported = await _library.ImportAsync(candidates); var imported = await _library.ImportAsync(candidates);
ShowingFavorites = false;
ActiveSource = MemeSource.Local; ActiveSource = MemeSource.Local;
RefreshLocalItems(); RefreshLocalItems();
StatusMessage = $"Added {imported.Count} meme(s)."; StatusMessage = $"Added {imported.Count} meme(s).";
@@ -328,7 +352,9 @@ public partial class PickerViewModel : ObservableObject
public void OnShown() public void OnShown()
{ {
IsInFavoritesView = false;
SearchText = string.Empty; SearchText = string.Empty;
RefreshFavorites();
// Rebuild rather than just reset: picks up memes added since last time, and lets a // Rebuild rather than just reset: picks up memes added since last time, and lets a
// changed "animate GIF previews" setting take effect. // changed "animate GIF previews" setting take effect.
_ = RefreshAsync(); _ = RefreshAsync();
@@ -340,7 +366,7 @@ public partial class PickerViewModel : ObservableObject
/// </summary> /// </summary>
public async Task LoadMoreAsync() public async Task LoadMoreAsync()
{ {
if (IsLocalSource || ShowingFavorites || _showingResolvedLink || !_remoteHasMore || _isLoadingMore) return; if (IsLocalSource || _showingResolvedLink || !_remoteHasMore || _isLoadingMore) return;
var provider = ActiveProvider; var provider = ActiveProvider;
if (provider is null || !provider.IsConfigured) return; if (provider is null || !provider.IsConfigured) return;
@@ -377,7 +403,7 @@ public partial class PickerViewModel : ObservableObject
private async Task RefreshAsync() private async Task RefreshAsync()
{ {
// Any pending remote search is stale the moment the query, tab or view changes. // Any pending remote search is stale the moment the query or tab changes.
_searchCts?.Cancel(); _searchCts?.Cancel();
_searchCts?.Dispose(); _searchCts?.Dispose();
_searchCts = null; _searchCts = null;
@@ -386,12 +412,6 @@ public partial class PickerViewModel : ObservableObject
_remoteHasMore = false; _remoteHasMore = false;
_showingResolvedLink = false; _showingResolvedLink = false;
if (ShowingFavorites)
{
RefreshFavoriteItems();
return;
}
if (IsLocalSource) if (IsLocalSource)
{ {
RefreshLocalItems(); RefreshLocalItems();
@@ -487,16 +507,6 @@ public partial class PickerViewModel : ObservableObject
_ = tile.LoadThumbnailAsync(ct); _ = tile.LoadThumbnailAsync(ct);
} }
private void AddLocalTile(LocalMemeRecord record)
{
var tile = new MemeTileViewModel(record, _library.GetFullPath(record), AnimatePreviews)
{
IsFavorite = _favorites.IsFavorite(MemeSource.Local, record.Id),
};
Items.Add(tile);
_ = tile.LoadThumbnailAsync();
}
private void RefreshLocalItems() private void RefreshLocalItems()
{ {
var records = string.IsNullOrWhiteSpace(SearchText) var records = string.IsNullOrWhiteSpace(SearchText)
@@ -506,48 +516,68 @@ public partial class PickerViewModel : ObservableObject
Items.Clear(); Items.Clear();
foreach (var record in records) foreach (var record in records)
{ {
AddLocalTile(record); var tile = new MemeTileViewModel(record, _library.GetFullPath(record), AnimatePreviews)
{
IsFavorite = _favorites.IsFavorite(MemeSource.Local, record.Id),
};
Items.Add(tile);
_ = tile.LoadThumbnailAsync();
} }
StatusMessage = string.Empty; StatusMessage = string.Empty;
} }
/// <summary>Favourites for the active source, narrowed by the search box like any other view.</summary> private void RefreshFavorites()
private void RefreshFavoriteItems()
{ {
Items.Clear(); Favorites.Clear();
var query = SearchText;
foreach (var fav in _favorites.GetForSource(ActiveSource)) foreach (var fav in _favorites.GetForSource(ActiveSource))
{ {
MemeTileViewModel tile;
if (fav.Source == MemeSource.Local) if (fav.Source == MemeSource.Local)
{ {
var record = _library.GetAll().FirstOrDefault(r => r.Id == fav.Key); var record = _library.GetAll().FirstOrDefault(r => r.Id == fav.Key);
// Favourite pointing at a meme that has since been deleted - skip it rather // Favourite pointing at a meme that has since been deleted - skip it rather
// than rendering a broken tile. // than rendering a broken tile.
if (record is null) continue; if (record is null) continue;
if (!Matches(record.OriginalFileName, query)) continue; tile = new MemeTileViewModel(record, _library.GetFullPath(record), AnimatePreviews);
AddLocalTile(record);
} }
else else
{ {
if (fav.PreviewUrl is null || fav.FullUrl is null) continue; if (fav.PreviewUrl is null || fav.FullUrl is null) continue;
if (!Matches(fav.Title ?? string.Empty, query)) continue; tile = new MemeTileViewModel(
new GifSearchResult
AddRemoteTile(new GifSearchResult
{ {
Id = fav.Key, Id = fav.Key,
Title = fav.Title ?? "Favourite", Title = fav.Title ?? "Favourite",
PreviewUrl = fav.PreviewUrl, PreviewUrl = fav.PreviewUrl,
FullUrl = fav.FullUrl, FullUrl = fav.FullUrl,
Source = fav.Source, Source = fav.Source,
}); },
_gifCache,
AnimatePreviews);
}
tile.IsFavorite = true;
Favorites.Add(tile);
_ = tile.LoadThumbnailAsync();
}
HasFavorites = Favorites.Count > 0;
// Last favourite removed while inside the submenu: nothing left to show, so step out.
if (!HasFavorites) IsInFavoritesView = false;
}
/// <summary>Keeps the star state on the main grid in step with the favourites store.</summary>
private void SyncFavoriteFlags()
{
foreach (var tile in Items)
{
tile.IsFavorite = _favorites.IsFavorite(tile.Source, tile.FavoriteKey);
}
} }
} }
StatusMessage = Items.Count == 0 ? string.Empty : $"{Items.Count} favourite(s)."; /// <summary>A navigation tile occupying the first grid cell (opens or leaves the favourites submenu).</summary>
} public sealed record FolderTileViewModel(string Glyph, string Label);
private static bool Matches(string text, string query) =>
string.IsNullOrWhiteSpace(query) || text.Contains(query, StringComparison.OrdinalIgnoreCase);
}
+19 -15
View File
@@ -79,7 +79,7 @@
<!-- Shared by the favourites row and the main grid so both behave identically. <!-- Shared by the favourites row and the main grid so both behave identically.
No fixed Width: the tile stretches to fill its UniformGrid cell, so a row always No fixed Width: the tile stretches to fill its UniformGrid cell, so a row always
spans the full panel width instead of leaving a ragged gap on the right. --> spans the full panel width instead of leaving a ragged gap on the right. -->
<DataTemplate x:Key="MemeTileTemplate" DataType="{x:Type vm:MemeTileViewModel}"> <DataTemplate DataType="{x:Type vm:MemeTileViewModel}">
<Grid Height="118" Margin="4"> <Grid Height="118" Margin="4">
<Button Padding="0" <Button Padding="0"
Style="{StaticResource FlatButtonStyle}" Style="{StaticResource FlatButtonStyle}"
@@ -115,6 +115,19 @@
Visibility="{Binding IsFavorite, Converter={StaticResource BoolToVisibility}}"/> Visibility="{Binding IsFavorite, Converter={StaticResource BoolToVisibility}}"/>
</Grid> </Grid>
</DataTemplate> </DataTemplate>
<!-- Navigation tile in the first grid cell: opens / leaves the favourites submenu. -->
<DataTemplate DataType="{x:Type vm:FolderTileViewModel}">
<Button Height="118" Margin="4"
Style="{StaticResource FlatButtonStyle}"
Command="{Binding DataContext.ToggleFavoritesViewCommand, ElementName=RootWindow}">
<StackPanel>
<TextBlock Text="{Binding Glyph}" FontSize="34" Foreground="#FFC72C"
HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Label}" FontSize="12" Foreground="#C4C6CC"
FontWeight="SemiBold" HorizontalAlignment="Center" Margin="0,4,0,0"/>
</StackPanel>
</Button>
</DataTemplate>
</Window.Resources> </Window.Resources>
<Border Background="#F0202225" CornerRadius="10" BorderBrush="#3A3B3E" BorderThickness="1"> <Border Background="#F0202225" CornerRadius="10" BorderBrush="#3A3B3E" BorderThickness="1">
@@ -140,8 +153,7 @@
</Grid> </Grid>
</Border> </Border>
<Grid Grid.Row="1" Margin="0,4,0,8"> <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,4,0,8">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<Button Content="Local" <Button Content="Local"
Style="{StaticResource TabButtonStyle}" Style="{StaticResource TabButtonStyle}"
Tag="{Binding IsLocalSource}" Tag="{Binding IsLocalSource}"
@@ -153,15 +165,6 @@
Command="{Binding SelectSourceCommand}" Command="{Binding SelectSourceCommand}"
CommandParameter="{x:Static models:MemeSource.Giphy}"/> CommandParameter="{x:Static models:MemeSource.Giphy}"/>
</StackPanel> </StackPanel>
<!-- Right-aligned and separated from the source tabs on purpose: it filters
the active source rather than being a source of its own. -->
<Button Content="&#9733; Favourites" HorizontalAlignment="Right"
Style="{StaticResource TabButtonStyle}"
Tag="{Binding ShowingFavorites}"
Command="{Binding ToggleFavoritesViewCommand}"
ToolTip="Show only your favourites from this source"/>
</Grid>
<Grid Grid.Row="2" Margin="0,0,0,10"> <Grid Grid.Row="2" Margin="0,0,0,10">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
@@ -198,13 +201,14 @@
<Button Grid.Column="1" Content="+" Width="36" Height="36" Margin="8,0,0,0" <Button Grid.Column="1" Content="+" Width="36" Height="36" Margin="8,0,0,0"
FontSize="18" Style="{StaticResource FlatButtonStyle}" FontSize="18" Style="{StaticResource FlatButtonStyle}"
Command="{Binding ImportFilesCommand}" Command="{Binding ImportFilesCommand}"
Visibility="{Binding CanImport, Converter={StaticResource BoolToVisibility}}" Visibility="{Binding IsLocalSource, Converter={StaticResource BoolToVisibility}}"
ToolTip="Add memes"/> ToolTip="Add memes"/>
</Grid> </Grid>
<ScrollViewer x:Name="ResultsScrollViewer" <ScrollViewer x:Name="ResultsScrollViewer"
Grid.Row="3" VerticalScrollBarVisibility="Auto" Padding="0,0,4,0" Grid.Row="3" VerticalScrollBarVisibility="Auto" Padding="0,0,4,0"
ScrollChanged="ResultsScrollViewer_OnScrollChanged"> ScrollChanged="ResultsScrollViewer_OnScrollChanged">
<StackPanel>
<Grid> <Grid>
<TextBlock Text="{Binding EmptyStateText}" <TextBlock Text="{Binding EmptyStateText}"
Foreground="#7B7D85" FontSize="13" Foreground="#7B7D85" FontSize="13"
@@ -213,8 +217,7 @@
Margin="0,40,0,0" Margin="0,40,0,0"
Visibility="{Binding ShowEmptyState, Converter={StaticResource BoolToVisibility}}"/> Visibility="{Binding ShowEmptyState, Converter={StaticResource BoolToVisibility}}"/>
<ItemsControl ItemsSource="{Binding Items}" <ItemsControl ItemsSource="{Binding GridSource}">
ItemTemplate="{StaticResource MemeTileTemplate}">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<UniformGrid Columns="3"/> <UniformGrid Columns="3"/>
@@ -222,6 +225,7 @@
</ItemsControl.ItemsPanel> </ItemsControl.ItemsPanel>
</ItemsControl> </ItemsControl>
</Grid> </Grid>
</StackPanel>
</ScrollViewer> </ScrollViewer>
<Grid Grid.Row="4" Margin="2,8,0,0"> <Grid Grid.Row="4" Margin="2,8,0,0">
@@ -94,8 +94,8 @@ public partial class PickerWindow : Window
// Switching tab/view or typing replaces the whole grid, so a retained scroll offset // Switching tab/view or typing replaces the whole grid, so a retained scroll offset
// would leave the user part-way down a completely different set of results. // would leave the user part-way down a completely different set of results.
if (e.PropertyName is nameof(PickerViewModel.ActiveSource) if (e.PropertyName is nameof(PickerViewModel.ActiveSource)
or nameof(PickerViewModel.ShowingFavorites) or nameof(PickerViewModel.SearchText)
or nameof(PickerViewModel.SearchText)) or nameof(PickerViewModel.IsInFavoritesView))
{ {
ResultsScrollViewer.ScrollToTop(); ResultsScrollViewer.ScrollToTop();
} }