Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/JellyBox/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,22 @@ public App()

InitializeComponent();

UnhandledException += OnUnhandledException;

Current.RequiresPointerMode = ApplicationRequiresPointerMode.WhenRequested;

Suspending += OnSuspending;
}

private void OnUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e)
{
System.Diagnostics.Debug.WriteLine($"Unhandled UI exception: {e.Exception}");
if (e.Exception.InnerException is not null)
{
System.Diagnostics.Debug.WriteLine($"Inner exception: {e.Exception.InnerException}");
}
}

/// <inheritdoc/>
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
Expand Down
2 changes: 2 additions & 0 deletions src/JellyBox/AppServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ private AppServices()
serviceCollection.AddTransient<LibraryViewModel>();
serviceCollection.AddTransient<LoginViewModel>();
serviceCollection.AddTransient<MainPageViewModel>();
serviceCollection.AddTransient<SearchViewModel>();
serviceCollection.AddTransient<ShellSearchViewModel>();
serviceCollection.AddTransient<ServerSelectionViewModel>();
serviceCollection.AddTransient<VideoViewModel>();
serviceCollection.AddTransient<WebVideoViewModel>();
Expand Down
4 changes: 3 additions & 1 deletion src/JellyBox/Behaviors/FocusFirstItemBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ namespace JellyBox.Behaviors;
/// <summary>
/// Automatically focuses the first item in a list once items are loaded and containers are realized.
/// </summary>
#pragma warning disable CA1812 // Instantiated via XAML Interaction.Behaviors.
internal sealed class FocusFirstItemBehavior : Behavior<ListViewBase>
#pragma warning restore CA1812
{
private bool _hasFocused;

Expand Down Expand Up @@ -41,4 +43,4 @@ private async void OnLayoutUpdated(object? sender, object e)
AssociatedObject.LayoutUpdated -= OnLayoutUpdated;
await FocusManager.TryFocusAsync(firstItem, FocusState.Programmatic);
}
}
}
2 changes: 2 additions & 0 deletions src/JellyBox/Behaviors/FocusOnLoadBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

namespace JellyBox.Behaviors;

#pragma warning disable CA1812 // Instantiated via XAML Interaction.Behaviors.
internal sealed class FocusOnLoadBehavior : Behavior<Control>
#pragma warning restore CA1812
{
protected override void OnAttached()
{
Expand Down
2 changes: 2 additions & 0 deletions src/JellyBox/Behaviors/ListViewBaseCommandBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ namespace JellyBox.Behaviors;
/// <summary>
/// Invokes the NavigateCommand on INavigable items when they are clicked in a ListViewBase control.
/// </summary>
#pragma warning disable CA1812 // Instantiated via XAML Interaction.Behaviors.
internal sealed class ListViewBaseCommandBehavior : Behavior<ListViewBase>
#pragma warning restore CA1812
{
protected override void OnAttached()
{
Expand Down
2 changes: 2 additions & 0 deletions src/JellyBox/Behaviors/ScrollOnFocusBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ namespace JellyBox.Behaviors;
/// Adjusts scroll position to keep focused items within the TV-safe zone.
/// Supports both horizontal carousels and full grid layouts.
/// </summary>
#pragma warning disable CA1812 // Instantiated via XAML Interaction.Behaviors.
internal sealed class ScrollOnFocusBehavior : Behavior<ListViewBase>
#pragma warning restore CA1812
{
private ScrollViewer? _scrollViewer;

Expand Down
7 changes: 6 additions & 1 deletion src/JellyBox/Behaviors/SectionNavigationBehavior.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using JellyBox;
using Microsoft.Xaml.Interactivity;
using Windows.Foundation;
using Windows.UI.Core;
Expand All @@ -11,9 +12,12 @@ namespace JellyBox.Behaviors;
/// Handles vertical navigation between horizontal list rows within a sections container.
/// Maintains horizontal position when moving between rows and handles edge trapping.
/// </summary>
#pragma warning disable CA1812 // Instantiated via XAML Interaction.Behaviors.
internal sealed class SectionNavigationBehavior : Behavior<ItemsControl>
#pragma warning restore CA1812
{
private ScrollViewer? _scrollViewer;
private MainPage? _mainPage;

/// <summary>
/// The ScrollViewer to use for bringing items into view.
Expand Down Expand Up @@ -55,6 +59,7 @@ protected override void OnDetaching()
private void OnLoaded(object sender, RoutedEventArgs e)
{
_scrollViewer = ScrollViewer ?? AssociatedObject.FindAncestor<ScrollViewer>();
_mainPage ??= AssociatedObject.FindAncestor<MainPage>();
}

private void OnGotFocus(object sender, RoutedEventArgs e)
Expand Down Expand Up @@ -100,7 +105,7 @@ private void OnLosingFocus(UIElement sender, LosingFocusEventArgs e)

if (targetIndex < 0)
{
if (TrapAtTop)
if (TrapAtTop && _mainPage?.TryRedirectFocusToSearch(e) != true)
{
e.TryCancel();
}
Expand Down
12 changes: 12 additions & 0 deletions src/JellyBox/CancellableLoad.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ internal sealed class CancellableLoad
{
private CancellationTokenSource? _cts;

/// <summary>
/// Cancels any in-flight load without starting a new one.
/// </summary>
public async Task CancelAsync()
{
CancellationTokenSource? previous = Interlocked.Exchange(ref _cts, null);
if (previous is not null)
{
await previous.CancelAsync();
}
}

/// <summary>
/// Cancels any prior in-flight load, then runs <paramref name="operation"/> with a fresh
/// cancellation token. If this load is itself superseded by a later call, the resulting
Expand Down
1 change: 1 addition & 0 deletions src/JellyBox/Glyphs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ internal static class Glyphs
{
// Navigation
public const string Home = "\uE80F";
public const string Search = "\uE721";
public const string Library = "\uE8F1";
public const string Switch = "\uE895";
public const string SignOut = "\uE8BB";
Expand Down
61 changes: 60 additions & 1 deletion src/JellyBox/MainPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,75 @@
x:Class="JellyBox.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:models="using:JellyBox.Models"
xmlns:g="using:JellyBox"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{StaticResource BackgroundBase}">

<Grid XYFocusKeyboardNavigation="Enabled">
<Frame x:Name="ContentFrame" />
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<Grid
Grid.Row="0"
Padding="48,20,48,12"
Background="{StaticResource BackgroundBase}"
XYFocusKeyboardNavigation="Enabled">
<AutoSuggestBox
x:Name="SearchBox"
MaxWidth="720"
HorizontalAlignment="Stretch"
IsTabStop="False"
PlaceholderText="Search movies and TV shows"
Style="{StaticResource SearchAutoSuggestBox}"
UpdateTextOnSelect="False"
ItemsSource="{x:Bind ViewModel.Search.Suggestions, Mode=OneWay}"
TextChanged="SearchBox_TextChanged"
QuerySubmitted="SearchBox_QuerySubmitted"
LostFocus="SearchBox_LostFocus"
XYFocusKeyboardNavigation="Enabled">
<AutoSuggestBox.QueryIcon>
<SymbolIcon Symbol="Find" />
</AutoSuggestBox.QueryIcon>
<AutoSuggestBox.ItemTemplate>
<DataTemplate x:DataType="models:SearchSuggestion">
<StackPanel
Orientation="Horizontal"
Spacing="12"
Padding="4,10"
Tapped="SearchSuggestionItem_Tapped">
<FontIcon
FontFamily="{StaticResource SegoeIcons}"
Glyph="{x:Bind g:Glyphs.Search}"
FontSize="16"
Foreground="{StaticResource TextMuted}"
VerticalAlignment="Center" />
<StackPanel Spacing="2" VerticalAlignment="Center">
<TextBlock
Text="{x:Bind DisplayText}"
FontSize="{StaticResource FontS}"
Foreground="{StaticResource TextPrimary}" />
<TextBlock
Text="{x:Bind SecondaryText}"
FontSize="{StaticResource FontXS}"
Foreground="{StaticResource TextMuted}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</AutoSuggestBox.ItemTemplate>
</AutoSuggestBox>
</Grid>

<Frame x:Name="ContentFrame" Grid.Row="1" />

<Grid
x:Name="NavigationOverlay"
Grid.Row="0"
Grid.RowSpan="2"
Visibility="Collapsed"
XYFocusKeyboardNavigation="Enabled">

Expand Down
Loading