Loads the user's library from the /Library/Games endpoint and displays it immediately. This route returns a full data set for the user's games. Images are rendered directly from the server. The import still runs in the background, preserving any offline functionality.
357 lines
20 KiB
XML
357 lines
20 KiB
XML
<UserControl xmlns="https://github.com/avaloniaui"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
|
|
xmlns:vm="using:LANCommander.Launcher.ViewModels"
|
|
xmlns:vmComponents="using:LANCommander.Launcher.ViewModels.Components"
|
|
xmlns:components="using:LANCommander.Launcher.Views.Components"
|
|
xmlns:controls="using:LANCommander.Launcher.Controls"
|
|
x:Class="LANCommander.Launcher.Views.GameDetailView"
|
|
x:DataType="vm:GameDetailViewModel"
|
|
>
|
|
|
|
<UserControl.Styles>
|
|
<Style Selector="Panel:focus-visible > Border#FocusBorder">
|
|
<Setter Property="IsVisible" Value="True" />
|
|
</Style>
|
|
</UserControl.Styles>
|
|
|
|
<ScrollViewer Classes="TitlebarOffset"
|
|
HorizontalScrollBarVisibility="Disabled"
|
|
GotFocus="OnScrollViewerGotFocus">
|
|
<Grid RowDefinitions="Auto,*">
|
|
|
|
<!-- ══ Hero Section (extends into title bar) ═══════════════════════ -->
|
|
<Border Grid.Row="0"
|
|
Height="340"
|
|
Background="Black">
|
|
<Panel>
|
|
<!-- Blurred backdrop strip behind titlebar -->
|
|
<Border VerticalAlignment="Top"
|
|
Height="60"
|
|
ClipToBounds="True"
|
|
IsHitTestVisible="False"
|
|
IsVisible="{Binding BackgroundPath, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
|
<Panel>
|
|
<Image controls:AsyncImage.Source="{Binding BackgroundPath}"
|
|
controls:AsyncImage.DecodeWidth="1600"
|
|
Stretch="UniformToFill">
|
|
<Image.Effect>
|
|
<BlurEffect Radius="24" />
|
|
</Image.Effect>
|
|
</Image>
|
|
<Border Background="#33000000" />
|
|
</Panel>
|
|
</Border>
|
|
|
|
<!-- Background image -->
|
|
<Image controls:AsyncImage.Source="{Binding BackgroundPath}"
|
|
controls:AsyncImage.DecodeWidth="1600"
|
|
Stretch="UniformToFill"
|
|
IsVisible="{Binding BackgroundPath, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
|
|
|
<!-- Top fade gradient -->
|
|
<Border VerticalAlignment="Top"
|
|
Height="120"
|
|
IsHitTestVisible="False">
|
|
<Border.Background>
|
|
<LinearGradientBrush StartPoint="0%,0%" EndPoint="0%,100%">
|
|
<GradientStop Color="#55000000" Offset="0" />
|
|
<GradientStop Color="#00000000" Offset="1" />
|
|
</LinearGradientBrush>
|
|
</Border.Background>
|
|
</Border>
|
|
|
|
<!-- Bottom fade gradient -->
|
|
<Border VerticalAlignment="Bottom"
|
|
Height="128"
|
|
IsHitTestVisible="False">
|
|
<Border.Background>
|
|
<LinearGradientBrush StartPoint="0%,0%" EndPoint="0%,100%">
|
|
<GradientStop Color="#00000000" Offset="0" />
|
|
<GradientStop Color="#FF000000" Offset="1" />
|
|
</LinearGradientBrush>
|
|
</Border.Background>
|
|
</Border>
|
|
|
|
<!-- Back button (overlaid at top-left, below title bar) -->
|
|
<IconButton Command="{Binding GoBackCommand}"
|
|
Text="{Binding BackLabel}"
|
|
Classes="Glass Dark"
|
|
HorizontalAlignment="Left"
|
|
VerticalAlignment="Top"
|
|
Margin="16, 48, 0, 0"
|
|
IconType="Regular"
|
|
IconValue="CaretLeft"
|
|
Size="16"
|
|
ZIndex="2"
|
|
IsHitTestVisible="True" />
|
|
|
|
<!-- Logo (bottom-left) -->
|
|
<Border HorizontalAlignment="Stretch"
|
|
VerticalAlignment="Stretch"
|
|
Margin="48,0,0,64"
|
|
ClipToBounds="True">
|
|
<Viewbox Stretch="Uniform"
|
|
HorizontalAlignment="Left"
|
|
VerticalAlignment="Bottom"
|
|
MaxWidth="{Binding Bounds.Width, RelativeSource={RelativeSource AncestorType=Border}, Converter={StaticResource MultiplyConverter}, ConverterParameter=0.33}"
|
|
MaxHeight="{Binding Bounds.Height, RelativeSource={RelativeSource AncestorType=Border}, Converter={StaticResource MultiplyConverter}, ConverterParameter=0.5}">
|
|
<Image controls:AsyncImage.Source="{Binding LogoPath}"
|
|
Stretch="Uniform"
|
|
HorizontalAlignment="Left"
|
|
VerticalAlignment="Bottom" />
|
|
</Viewbox>
|
|
</Border>
|
|
|
|
<!-- Title fallback when no logo (bottom-left) -->
|
|
<TextBlock HorizontalAlignment="Left"
|
|
VerticalAlignment="Bottom"
|
|
Margin="24,0,0,28"
|
|
Text="{Binding Title}"
|
|
FontSize="32"
|
|
FontWeight="Bold"
|
|
Foreground="White"
|
|
IsVisible="{Binding LogoPath, Converter={x:Static StringConverters.IsNullOrEmpty}}" />
|
|
</Panel>
|
|
</Border>
|
|
|
|
<!-- ══ Game Details Content ══════════════════════════════════════════ -->
|
|
<Grid Grid.Row="1" ColumnDefinitions="*,Auto">
|
|
|
|
<!-- Left: Action Bar, Metadata -->
|
|
<StackPanel x:Name="LeftContent" Grid.Column="0" Margin="24,24,0,24" Spacing="16">
|
|
|
|
<!-- Game Action Bar Component -->
|
|
<components:GameActionBarView DataContext="{Binding ActionBar}" />
|
|
|
|
<!-- Description -->
|
|
<controls:MarkdownTextBlock Text="{Binding Description}"
|
|
IsVisible="{Binding Description, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
|
|
|
<!-- Screenshots / Videos carousel -->
|
|
<StackPanel x:Name="MediaSection" Spacing="8" IsVisible="{Binding HasMedia}">
|
|
<TextBlock Text="Media" FontWeight="SemiBold" FontSize="14" Opacity="0.7" />
|
|
<CarouselControl ItemsSource="{Binding MediaItems}"
|
|
ItemWidth="384"
|
|
VisibleItems="2"
|
|
Gap="12">
|
|
<CarouselControl.ItemTemplate>
|
|
<DataTemplate x:DataType="vmComponents:GameMediaItemViewModel">
|
|
<Panel Width="384" Height="216"
|
|
Focusable="True"
|
|
Tapped="MediaItem_Tapped"
|
|
KeyDown="MediaItem_KeyDown"
|
|
Cursor="Hand">
|
|
<!-- Skeleton placeholder -->
|
|
<Border CornerRadius="4"
|
|
Background="#15FFFFFF"
|
|
IsVisible="{Binding IsSkeleton}" />
|
|
<!-- Screenshot image -->
|
|
<Border CornerRadius="4" ClipToBounds="True"
|
|
IsVisible="{Binding !IsVideo}">
|
|
<Image Source="{Binding ImageSource}"
|
|
Stretch="UniformToFill" />
|
|
</Border>
|
|
<!-- Video: muted autoplay, click to open lightbox.
|
|
A ContentControl whose Content is the video path (null
|
|
for screenshots) only realizes its template when non-null,
|
|
so screenshots never instantiate an InlineVideoPlayer/libvlc. -->
|
|
<ContentControl Content="{Binding VideoPath}">
|
|
<ContentControl.ContentTemplate>
|
|
<DataTemplate x:DataType="sys:String">
|
|
<Border CornerRadius="4" ClipToBounds="True"
|
|
Background="Black">
|
|
<InlineVideoPlayer VideoPath="{Binding}" />
|
|
</Border>
|
|
</DataTemplate>
|
|
</ContentControl.ContentTemplate>
|
|
</ContentControl>
|
|
<!-- Focus indicator -->
|
|
<Border Name="FocusBorder"
|
|
CornerRadius="4"
|
|
BorderThickness="2"
|
|
BorderBrush="#994096FF"
|
|
IsVisible="False" />
|
|
</Panel>
|
|
</DataTemplate>
|
|
</CarouselControl.ItemTemplate>
|
|
</CarouselControl>
|
|
</StackPanel>
|
|
|
|
</StackPanel>
|
|
|
|
<!-- Right: Cover Image + Metadata -->
|
|
<StackPanel x:Name="MetadataPanel" Grid.Column="1"
|
|
Width="280"
|
|
Margin="16,-100,24,24"
|
|
Spacing="16"
|
|
VerticalAlignment="Top"
|
|
ZIndex="1">
|
|
|
|
<!-- Cover Image -->
|
|
<components:Cover Source="{Binding CoverPath}"
|
|
MimeType="{Binding CoverMimeType}"
|
|
Title="{Binding Title}"
|
|
AlwaysAnimate="True" />
|
|
|
|
<!-- Players -->
|
|
<StackPanel Classes="Badges"
|
|
IsVisible="{Binding HasPlayerInfo}">
|
|
<TextBlock Text="Players" Classes="Header" />
|
|
<WrapPanel Orientation="Horizontal">
|
|
<Border Margin="0,0,6,6" Padding="10,5" CornerRadius="4" Background="#22FFFFFF"
|
|
IsVisible="{Binding Singleplayer}">
|
|
<TextBlock Text="Singleplayer" FontSize="12" />
|
|
</Border>
|
|
<ItemsControl ItemsSource="{Binding MultiplayerModeDetails}">
|
|
<ItemsControl.ItemsPanel>
|
|
<ItemsPanelTemplate>
|
|
<WrapPanel Orientation="Horizontal" />
|
|
</ItemsPanelTemplate>
|
|
</ItemsControl.ItemsPanel>
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate x:DataType="sys:String">
|
|
<Border Margin="0,0,6,6"
|
|
Padding="10,5"
|
|
CornerRadius="4"
|
|
Background="#22FFFFFF">
|
|
<TextBlock Text="{Binding}" FontSize="12" />
|
|
</Border>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
</WrapPanel>
|
|
</StackPanel>
|
|
|
|
<!-- Developers -->
|
|
<StackPanel Classes="Badges"
|
|
IsVisible="{Binding Developers, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
|
<TextBlock Text="Developers" Classes="Header" />
|
|
<ItemsControl ItemsSource="{Binding DeveloperList}">
|
|
<ItemsControl.ItemsPanel>
|
|
<ItemsPanelTemplate>
|
|
<WrapPanel Orientation="Horizontal" />
|
|
</ItemsPanelTemplate>
|
|
</ItemsControl.ItemsPanel>
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate x:DataType="sys:String">
|
|
<Button Classes="Badge"
|
|
Command="{Binding $parent[UserControl].((vm:GameDetailViewModel)DataContext).SearchForCommand}"
|
|
CommandParameter="{Binding}">
|
|
<TextBlock Text="{Binding}" />
|
|
</Button>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
</StackPanel>
|
|
|
|
<!-- Publishers -->
|
|
<StackPanel Classes="Badges"
|
|
IsVisible="{Binding Publishers, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
|
<TextBlock Text="Publishers" Classes="Header" />
|
|
<ItemsControl ItemsSource="{Binding PublisherList}">
|
|
<ItemsControl.ItemsPanel>
|
|
<ItemsPanelTemplate>
|
|
<WrapPanel Orientation="Horizontal" />
|
|
</ItemsPanelTemplate>
|
|
</ItemsControl.ItemsPanel>
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate x:DataType="sys:String">
|
|
<Button Classes="Badge"
|
|
Command="{Binding $parent[UserControl].((vm:GameDetailViewModel)DataContext).SearchForCommand}"
|
|
CommandParameter="{Binding}">
|
|
<TextBlock Text="{Binding}" />
|
|
</Button>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
</StackPanel>
|
|
|
|
<!-- Genres -->
|
|
<StackPanel Classes="Badges"
|
|
IsVisible="{Binding Genres, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
|
<TextBlock Text="Genres" Classes="Header" />
|
|
<ItemsControl ItemsSource="{Binding GenreList}">
|
|
<ItemsControl.ItemsPanel>
|
|
<ItemsPanelTemplate>
|
|
<WrapPanel Orientation="Horizontal" />
|
|
</ItemsPanelTemplate>
|
|
</ItemsControl.ItemsPanel>
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate x:DataType="sys:String">
|
|
<Button Classes="Badge"
|
|
Command="{Binding $parent[UserControl].((vm:GameDetailViewModel)DataContext).SearchForCommand}"
|
|
CommandParameter="{Binding}">
|
|
<TextBlock Text="{Binding}" />
|
|
</Button>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
</StackPanel>
|
|
|
|
<!-- Tags — limited to 5, expandable -->
|
|
<StackPanel Classes="Badges"
|
|
IsVisible="{Binding Tags, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
|
<TextBlock Text="Tags" Classes="Header" />
|
|
<WrapPanel Orientation="Horizontal">
|
|
<ItemsControl ItemsSource="{Binding VisibleTagList}">
|
|
<ItemsControl.ItemsPanel>
|
|
<ItemsPanelTemplate>
|
|
<WrapPanel Orientation="Horizontal" />
|
|
</ItemsPanelTemplate>
|
|
</ItemsControl.ItemsPanel>
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate x:DataType="sys:String">
|
|
<Button Classes="Badge"
|
|
Command="{Binding $parent[UserControl].((vm:GameDetailViewModel)DataContext).SearchForCommand}"
|
|
CommandParameter="{Binding}">
|
|
<TextBlock Text="{Binding}" />
|
|
</Button>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
|
|
<Button Classes="Badge Primary"
|
|
IsVisible="{Binding HasMoreTags}"
|
|
Command="{Binding ToggleTagsExpandedCommand}">
|
|
<TextBlock Text="{Binding ShowMoreTagsLabel}" />
|
|
</Button>
|
|
</WrapPanel>
|
|
</StackPanel>
|
|
|
|
<!-- Tools -->
|
|
<StackPanel Classes="Badges"
|
|
IsVisible="{Binding HasTools}">
|
|
<TextBlock Text="Tools" Classes="Header" />
|
|
<ItemsControl ItemsSource="{Binding Tools}">
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate x:DataType="vm:ToolItemViewModel">
|
|
<Border Margin="0,0,0,6"
|
|
Padding="10,5"
|
|
CornerRadius="4"
|
|
Background="#22FFFFFF">
|
|
<Grid ColumnDefinitions="*,Auto">
|
|
<TextBlock Grid.Column="0"
|
|
Text="{Binding Name}"
|
|
FontSize="12"
|
|
VerticalAlignment="Center" />
|
|
<TextBlock Grid.Column="1"
|
|
Text="Installed"
|
|
FontSize="11"
|
|
Opacity="0.6"
|
|
VerticalAlignment="Center"
|
|
IsVisible="{Binding IsInstalled}" />
|
|
</Grid>
|
|
</Border>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
</StackPanel>
|
|
|
|
</StackPanel>
|
|
|
|
</Grid>
|
|
|
|
</Grid>
|
|
</ScrollViewer>
|
|
</UserControl>
|