mirror of
https://github.com/Jellyfin2Samsung/Samsung-Jellyfin-Installer.git
synced 2026-03-01 11:21:12 +03:00
App preview added
This commit is contained in:
@@ -82,7 +82,7 @@ namespace Jellyfin2Samsung.Helpers
|
||||
// ----- Application-scoped settings (readonly at runtime) -----
|
||||
public string ReleasesUrl { get; set; } = "https://api.github.com/repos/jeppevinkel/jellyfin-tizen-builds/releases";
|
||||
public string AuthorEndpoint { get; set; } = "https://dev.tizen.samsung.com/apis/v2/authors";
|
||||
public string AppVersion { get; set; } = "v2.2.0.0";
|
||||
public string AppVersion { get; set; } = "v2.2.0.1";
|
||||
public string TizenSdb { get; set; } = "https://api.github.com/repos/PatrickSt1991/tizen-sdb/releases";
|
||||
public string JellyfinAvRelease { get; set; } = "https://api.github.com/repos/PatrickSt1991/tizen-jellyfin-avplay/releases";
|
||||
public string JellyfinAvReleaseFork { get; set; } = "https://api.github.com/repos/asamahy/tizen-jellyfin-avplay/releases";
|
||||
|
||||
@@ -17,6 +17,20 @@ namespace Jellyfin2Samsung.Helpers.Core
|
||||
public const string CustomWgtFile = "Custom WGT File";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// preview image URLS for different apps.
|
||||
/// </summary>
|
||||
public static class PreviewImages
|
||||
{
|
||||
public const string Jellyfin = "https://jellyfin.org/assets/images/10.8-home-4a73a92bf90d1eeffa5081201ca9c7bb.png";
|
||||
public const string Moonfin = "https://iili.io/fs8W4Re.png";
|
||||
public const string Moonlight = "https://iili.io/fsvn6mJ.png";
|
||||
public const string Fireplace = "https://raw.githubusercontent.com/thonythony/fireplace/refs/heads/master/icon.jpg";
|
||||
public const string TVApp = "https://iili.io/fsvaHsn.png";
|
||||
public const string Twitch = "https://iili.io/fsvUNu2.md.gif";
|
||||
public const string ClubInfoBoard = "https://iili.io/fsviHQV.png";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tizen installation error codes returned by the SDB tool.
|
||||
/// </summary>
|
||||
|
||||
@@ -6,5 +6,6 @@ namespace Jellyfin2Samsung.Models
|
||||
{
|
||||
[ObservableProperty] private string fileName = string.Empty;
|
||||
[ObservableProperty] private string description = string.Empty;
|
||||
[ObservableProperty] private string repoUrl = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
9
Jellyfin2Samsung-CrossOS/Models/ProviderOption.cs
Normal file
9
Jellyfin2Samsung-CrossOS/Models/ProviderOption.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace Jellyfin2Samsung.Models;
|
||||
|
||||
public sealed class ProviderOption
|
||||
{
|
||||
public string DisplayName { get; init; } = "";
|
||||
public IImage? PreviewImage { get; init; } // can be a Bitmap later
|
||||
}
|
||||
@@ -1,12 +1,18 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Jellyfin2Samsung.Helpers;
|
||||
using Jellyfin2Samsung.Helpers.Core;
|
||||
using Jellyfin2Samsung.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Jellyfin2Samsung.ViewModels
|
||||
@@ -16,21 +22,53 @@ namespace Jellyfin2Samsung.ViewModels
|
||||
public ObservableCollection<BuildVersion> JellyfinVersions { get; } = new();
|
||||
public ObservableCollection<BuildVersion> CommunityApps { get; } = new();
|
||||
|
||||
public ObservableCollection<ProviderOption> ProviderOptions { get; } = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private ProviderOption? selectedProviderOption;
|
||||
|
||||
private static readonly HttpClient _http = new();
|
||||
private readonly Dictionary<string, Bitmap?> _bitmapCache = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private bool _isLoading;
|
||||
private int _rebuildVersion;
|
||||
|
||||
public BuildInfoViewModel()
|
||||
{
|
||||
CommunityApps.CollectionChanged += (_, __) =>
|
||||
{
|
||||
if (_isLoading) return;
|
||||
QueueRebuild();
|
||||
};
|
||||
|
||||
JellyfinVersions.CollectionChanged += (_, __) =>
|
||||
{
|
||||
if (_isLoading) return;
|
||||
QueueRebuild();
|
||||
};
|
||||
|
||||
_ = LoadAsync();
|
||||
}
|
||||
|
||||
private void QueueRebuild()
|
||||
{
|
||||
// Start a rebuild, but only the latest one is allowed to apply results
|
||||
var version = Interlocked.Increment(ref _rebuildVersion);
|
||||
_ = RebuildProviderOptionsAsync(version);
|
||||
}
|
||||
|
||||
public async Task LoadAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var client = new HttpClient();
|
||||
_isLoading = true;
|
||||
|
||||
var jellyfinMd = await client.GetStringAsync(AppSettings.Default.ReleaseInfo);
|
||||
var communityMd = await client.GetStringAsync(AppSettings.Default.CommunityInfo);
|
||||
var jellyfinMd = await _http.GetStringAsync(AppSettings.Default.ReleaseInfo);
|
||||
var communityMd = await _http.GetStringAsync(AppSettings.Default.CommunityInfo);
|
||||
|
||||
JellyfinVersions.Clear();
|
||||
CommunityApps.Clear();
|
||||
|
||||
// Parse Jellyfin table
|
||||
ParseVersionsTable(jellyfinMd, JellyfinVersions);
|
||||
|
||||
JellyfinVersions.Add(new BuildVersion
|
||||
@@ -39,7 +77,6 @@ namespace Jellyfin2Samsung.ViewModels
|
||||
Description = "Moonfin is optimized for the viewing experience on Samsung Smart TVs."
|
||||
});
|
||||
|
||||
// Static Jellyfin entries
|
||||
JellyfinVersions.Add(new BuildVersion
|
||||
{
|
||||
FileName = "Legacy",
|
||||
@@ -55,19 +92,141 @@ namespace Jellyfin2Samsung.ViewModels
|
||||
JellyfinVersions.Add(new BuildVersion
|
||||
{
|
||||
FileName = "AVPlay 10.10.z - SmartHub",
|
||||
Description = "Includes AVPlay video player patches for better Samsung TV compatibility for10.10.z SmartHub variant"
|
||||
Description = "Includes AVPlay video player patches for better Samsung TV compatibility for 10.10.z SmartHub variant"
|
||||
});
|
||||
|
||||
// Parse community apps
|
||||
ParseApplicationsTable(communityMd, CommunityApps);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.WriteLine($"Failed to load build info: {ex}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoading = false;
|
||||
}
|
||||
|
||||
// Do a single authoritative rebuild after load finishes
|
||||
var version = Interlocked.Increment(ref _rebuildVersion);
|
||||
await RebuildProviderOptionsAsync(version);
|
||||
|
||||
if (SelectedProviderOption is null && ProviderOptions.Count > 0)
|
||||
SelectedProviderOption = ProviderOptions[0];
|
||||
}
|
||||
|
||||
// Remove markdown formatting like **bold**, emoji, etc.
|
||||
private async Task RebuildProviderOptionsAsync(int version)
|
||||
{
|
||||
// If a newer rebuild was queued, abandon this one
|
||||
if (version != Volatile.Read(ref _rebuildVersion))
|
||||
return;
|
||||
|
||||
// Map ONLY the apps that truly need unique thumbnails
|
||||
var communityPreviewUrls = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "Moonlight", Constants.PreviewImages.Moonlight },
|
||||
{ "Fireplace", Constants.PreviewImages.Fireplace },
|
||||
{ "TVApp", Constants.PreviewImages.TVApp },
|
||||
{ "Twitch", Constants.PreviewImages.Twitch },
|
||||
{ "Club Info Board", Constants.PreviewImages.ClubInfoBoard }
|
||||
};
|
||||
|
||||
// Exceptions inside JellyfinVersions that should NOT use Jellyfin image
|
||||
var jellyfinOverrides = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "Moonfin", Constants.PreviewImages.Moonfin }
|
||||
};
|
||||
|
||||
var jellyfinBitmap = await LoadBitmapAsync(Constants.PreviewImages.Jellyfin);
|
||||
|
||||
// Build locally (don’t mutate ObservableCollection from background thread)
|
||||
var built = new List<ProviderOption>();
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
void AddIfNew(string name, Bitmap? bmp)
|
||||
{
|
||||
name = (name ?? string.Empty).Trim();
|
||||
if (name.Length == 0) return;
|
||||
if (!seen.Add(name)) return;
|
||||
|
||||
built.Add(new ProviderOption
|
||||
{
|
||||
DisplayName = name,
|
||||
PreviewImage = bmp
|
||||
});
|
||||
}
|
||||
|
||||
// 1) Jellyfin top entry
|
||||
AddIfNew("Jellyfin", jellyfinBitmap);
|
||||
|
||||
// 2) Community apps
|
||||
foreach (var app in CommunityApps)
|
||||
{
|
||||
var name = (app.FileName ?? string.Empty).Trim();
|
||||
if (name.Length == 0) continue;
|
||||
|
||||
var url = communityPreviewUrls.FirstOrDefault(kvp =>
|
||||
name.Contains(kvp.Key, StringComparison.OrdinalIgnoreCase)).Value;
|
||||
|
||||
var bmp = url is not null ? await LoadBitmapAsync(url) : null;
|
||||
|
||||
AddIfNew(name, bmp);
|
||||
}
|
||||
|
||||
// 3) Jellyfin builds (default Jellyfin image, override forks like Moonfin)
|
||||
foreach (var build in JellyfinVersions)
|
||||
{
|
||||
var name = (build.FileName ?? string.Empty).Trim();
|
||||
if (name.Length == 0) continue;
|
||||
|
||||
Bitmap? bmp = jellyfinBitmap;
|
||||
|
||||
var overrideUrl = jellyfinOverrides.FirstOrDefault(kvp =>
|
||||
name.Contains(kvp.Key, StringComparison.OrdinalIgnoreCase)).Value;
|
||||
|
||||
if (overrideUrl is not null)
|
||||
bmp = await LoadBitmapAsync(overrideUrl);
|
||||
|
||||
AddIfNew(name, bmp);
|
||||
}
|
||||
|
||||
// If a newer rebuild was queued while we were downloading images, abandon this one
|
||||
if (version != Volatile.Read(ref _rebuildVersion))
|
||||
return;
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
ProviderOptions.Clear();
|
||||
foreach (var opt in built)
|
||||
ProviderOptions.Add(opt);
|
||||
|
||||
if (SelectedProviderOption is null && ProviderOptions.Count > 0)
|
||||
SelectedProviderOption = ProviderOptions[0];
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<Bitmap?> LoadBitmapAsync(string url)
|
||||
{
|
||||
if (_bitmapCache.TryGetValue(url, out var cached))
|
||||
return cached;
|
||||
|
||||
try
|
||||
{
|
||||
var bytes = await _http.GetByteArrayAsync(url);
|
||||
await using var ms = new MemoryStream(bytes);
|
||||
var bmp = new Bitmap(ms);
|
||||
_bitmapCache[url] = bmp;
|
||||
return bmp;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.WriteLine($"Failed to load image '{url}': {ex.Message}");
|
||||
_bitmapCache[url] = null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// -------- your existing parsing methods (unchanged) --------
|
||||
|
||||
private static string CleanText(string input)
|
||||
{
|
||||
var text = RegexPatterns.BuildInfo.MarkdownBold.Replace(input, "$1");
|
||||
@@ -78,22 +237,19 @@ namespace Jellyfin2Samsung.ViewModels
|
||||
private void ParseVersionsTable(string md, ObservableCollection<BuildVersion> target)
|
||||
{
|
||||
var match = RegexPatterns.BuildInfo.VersionsTable.Match(md);
|
||||
|
||||
if (!match.Success) return;
|
||||
|
||||
var table = match.Groups["table"].Value;
|
||||
|
||||
var rows = RegexPatterns.BuildInfo.TableRow2Columns.Matches(table);
|
||||
|
||||
bool headerSkipped = false;
|
||||
|
||||
foreach (Match row in rows)
|
||||
foreach (System.Text.RegularExpressions.Match row in rows)
|
||||
{
|
||||
var col1 = row.Groups[1].Value.Trim();
|
||||
var col2 = row.Groups[2].Value.Trim();
|
||||
|
||||
if (!headerSkipped &&
|
||||
col1.Equals("File name", StringComparison.OrdinalIgnoreCase))
|
||||
if (!headerSkipped && col1.Equals("File name", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
headerSkipped = true;
|
||||
continue;
|
||||
@@ -113,22 +269,22 @@ namespace Jellyfin2Samsung.ViewModels
|
||||
private void ParseApplicationsTable(string md, ObservableCollection<BuildVersion> target)
|
||||
{
|
||||
var match = RegexPatterns.BuildInfo.ApplicationsTable.Match(md);
|
||||
|
||||
if (!match.Success) return;
|
||||
|
||||
var table = match.Groups["table"].Value;
|
||||
|
||||
var rows = RegexPatterns.BuildInfo.TableRow3Columns.Matches(table);
|
||||
|
||||
bool headerSkipped = false;
|
||||
|
||||
foreach (Match row in rows)
|
||||
foreach (System.Text.RegularExpressions.Match row in rows)
|
||||
{
|
||||
var col1 = row.Groups[1].Value.Trim();
|
||||
var col2 = row.Groups[2].Value.Trim();
|
||||
var col3 = row.Groups[3].Value.Trim();
|
||||
|
||||
if (!headerSkipped &&
|
||||
col1.Contains("Application", StringComparison.OrdinalIgnoreCase))
|
||||
Debug.WriteLine(col3);
|
||||
|
||||
if (!headerSkipped && col1.Contains("Application", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
headerSkipped = true;
|
||||
continue;
|
||||
@@ -153,4 +309,4 @@ namespace Jellyfin2Samsung.ViewModels
|
||||
|
||||
public event Action? OnRequestClose;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
xmlns:viewModels="clr-namespace:Jellyfin2Samsung.ViewModels"
|
||||
x:DataType="viewModels:BuildInfoViewModel"
|
||||
SizeToContent="WidthAndHeight"
|
||||
MinWidth="650"
|
||||
MaxWidth="900"
|
||||
MinHeight="500"
|
||||
MinWidth="900"
|
||||
MaxWidth="1200"
|
||||
MinHeight="520"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Title="Jellyfin Tizen Versions">
|
||||
|
||||
@@ -15,112 +15,175 @@
|
||||
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
|
||||
</Window.Styles>
|
||||
|
||||
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
|
||||
Padding="16"
|
||||
CornerRadius="8"
|
||||
Margin="12"
|
||||
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1">
|
||||
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
|
||||
Padding="16"
|
||||
CornerRadius="8"
|
||||
Margin="12"
|
||||
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1">
|
||||
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="15"
|
||||
OffsetX="2"
|
||||
OffsetY="2"
|
||||
Color="#00000020"
|
||||
Opacity="0.15"/>
|
||||
OffsetX="2"
|
||||
OffsetY="2"
|
||||
Color="#00000020"
|
||||
Opacity="0.15"/>
|
||||
</Border.Effect>
|
||||
|
||||
<StackPanel Spacing="20">
|
||||
<StackPanel Spacing="16">
|
||||
|
||||
<!-- Header -->
|
||||
<Border Background="#2C3E50"
|
||||
Padding="14,10"
|
||||
CornerRadius="6">
|
||||
Padding="14,10"
|
||||
CornerRadius="6">
|
||||
<TextBlock Text="Jellyfin Tizen Build Versions"
|
||||
Foreground="White"
|
||||
FontWeight="SemiBold"
|
||||
FontSize="18"
|
||||
HorizontalAlignment="Center"/>
|
||||
Foreground="White"
|
||||
FontWeight="SemiBold"
|
||||
FontSize="18"
|
||||
HorizontalAlignment="Center"/>
|
||||
</Border>
|
||||
|
||||
<!-- Two-column layout -->
|
||||
<Grid ColumnDefinitions="*,340"
|
||||
ColumnSpacing="18">
|
||||
|
||||
<Border Background="#34495E"
|
||||
Padding="10"
|
||||
CornerRadius="6">
|
||||
<TextBlock Text="Official Jellyfin Builds"
|
||||
Foreground="White"
|
||||
FontWeight="Medium"
|
||||
FontSize="15"/>
|
||||
</Border>
|
||||
<!-- ================= LEFT COLUMN ================= -->
|
||||
<StackPanel Grid.Column="0" Spacing="16">
|
||||
|
||||
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4"
|
||||
Height="200">
|
||||
<DataGrid ItemsSource="{Binding JellyfinVersions}"
|
||||
AutoGenerateColumns="False"
|
||||
HeadersVisibility="Column"
|
||||
GridLinesVisibility="All"
|
||||
IsReadOnly="True"
|
||||
ColumnWidth="*">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="File name" Width="180"
|
||||
Binding="{Binding FileName}" />
|
||||
<DataGridTemplateColumn Header="Description" Width="*">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Description}"
|
||||
TextWrapping="Wrap"
|
||||
Padding="4"
|
||||
VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Border>
|
||||
<Border Background="#34495E"
|
||||
Padding="10"
|
||||
CornerRadius="6">
|
||||
<TextBlock Text="Jellyfin Builds"
|
||||
Foreground="White"
|
||||
FontWeight="Medium"
|
||||
FontSize="15"/>
|
||||
</Border>
|
||||
|
||||
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4"
|
||||
Height="220">
|
||||
<DataGrid ItemsSource="{Binding JellyfinVersions}"
|
||||
AutoGenerateColumns="False"
|
||||
HeadersVisibility="Column"
|
||||
GridLinesVisibility="All"
|
||||
IsReadOnly="True"
|
||||
ColumnWidth="*">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="File name" Width="180"
|
||||
Binding="{Binding FileName}" />
|
||||
<DataGridTemplateColumn Header="Description" Width="*">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Description}"
|
||||
TextWrapping="Wrap"
|
||||
Padding="4"
|
||||
VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Border>
|
||||
|
||||
<Border Background="#34495E"
|
||||
Padding="10"
|
||||
CornerRadius="6"
|
||||
Margin="0,6,0,0">
|
||||
<TextBlock Text="Community Applications"
|
||||
Foreground="White"
|
||||
FontWeight="Medium"
|
||||
FontSize="15"/>
|
||||
</Border>
|
||||
|
||||
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4"
|
||||
Height="280">
|
||||
<DataGrid ItemsSource="{Binding CommunityApps}"
|
||||
AutoGenerateColumns="False"
|
||||
HeadersVisibility="Column"
|
||||
GridLinesVisibility="All"
|
||||
RowHeight="32"
|
||||
IsReadOnly="True"
|
||||
ColumnWidth="*">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Application" Width="180"
|
||||
Binding="{Binding FileName}" />
|
||||
<DataGridTextColumn Header="Description" Width="*"
|
||||
Binding="{Binding Description}" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Border>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<!-- ================= RIGHT COLUMN ================= -->
|
||||
<!-- CompileBindings off so you can iterate on option types freely -->
|
||||
<StackPanel Grid.Column="1"
|
||||
Spacing="10"
|
||||
x:CompileBindings="False">
|
||||
|
||||
<Border Background="#34495E"
|
||||
Padding="10"
|
||||
CornerRadius="6">
|
||||
<TextBlock Text="App Preview"
|
||||
Foreground="White"
|
||||
FontWeight="Medium"
|
||||
FontSize="15"/>
|
||||
</Border>
|
||||
|
||||
<!-- Dropdown: TEXT ONLY (no preview image here) -->
|
||||
<ComboBox ItemsSource="{Binding ProviderOptions}"
|
||||
SelectedItem="{Binding SelectedProviderOption, Mode=TwoWay}"
|
||||
MinWidth="320"
|
||||
HorizontalAlignment="Stretch"
|
||||
DisplayMemberBinding="{Binding DisplayName}"/>
|
||||
|
||||
<!-- Preview card: image shows ONLY here -->
|
||||
<Border Height="320"
|
||||
CornerRadius="12"
|
||||
ClipToBounds="True"
|
||||
Background="#22000000">
|
||||
<Grid>
|
||||
<!-- fallback text behind -->
|
||||
<StackPanel VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Spacing="6"
|
||||
Margin="12">
|
||||
<TextBlock Text="No preview available"
|
||||
FontWeight="SemiBold"
|
||||
FontSize="14"
|
||||
HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="This app has no thumbnail."
|
||||
Opacity="0.7"
|
||||
FontSize="12"
|
||||
HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- image on top (if null, it simply won't render) -->
|
||||
<Image Source="{Binding SelectedProviderOption.PreviewImage}"
|
||||
Stretch="Uniform"
|
||||
StretchDirection="DownOnly"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
|
||||
<Border Background="#34495E"
|
||||
Padding="10"
|
||||
CornerRadius="6"
|
||||
Margin="0,10,0,0">
|
||||
<TextBlock Text="Community Applications"
|
||||
Foreground="White"
|
||||
FontWeight="Medium"
|
||||
FontSize="15"/>
|
||||
</Border>
|
||||
|
||||
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4"
|
||||
Height="260">
|
||||
<DataGrid ItemsSource="{Binding CommunityApps}"
|
||||
AutoGenerateColumns="False"
|
||||
HeadersVisibility="Column"
|
||||
GridLinesVisibility="All"
|
||||
RowHeight="32"
|
||||
IsReadOnly="True"
|
||||
ColumnWidth="*">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Application" Width="180"
|
||||
Binding="{Binding FileName}" />
|
||||
<DataGridTextColumn Header="Description" Width="*"
|
||||
Binding="{Binding Description}" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- Footer -->
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0,10,0,0">
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0,6,0,0">
|
||||
<Button Content="Close"
|
||||
Width="100"
|
||||
Command="{Binding CloseCommand}"/>
|
||||
Width="100"
|
||||
Command="{Binding CloseCommand}"/>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</Border>
|
||||
|
||||
</Window>
|
||||
</Window>
|
||||
|
||||
Reference in New Issue
Block a user