LANCommander/LANCommander.Packager/Views/MetadataSearchDialog.axaml.cs
2026-06-04 01:29:31 -05:00

250 lines
7.2 KiB
C#

using System.Collections.ObjectModel;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using LANCommander.SDK.Models;
using LANCommander.SDK.Services;
using Game = LANCommander.SDK.Models.Manifest.Game;
using Key = Avalonia.Input.Key;
namespace LANCommander.Packager.Views;
public partial class MetadataSearchDialog : Window
{
private readonly MetadataClient _metadataClient;
private readonly ObservableCollection<SearchResultItem> _results = new();
private readonly List<MetadataSearchResult> _rawResults = new();
private string? _selectedProvider;
private int _offset;
private bool _hasMore;
public Game? SelectedGame { get; private set; }
public MetadataSearchDialog()
{
InitializeComponent();
}
public MetadataSearchDialog(MetadataClient metadataClient, string? defaultSearch = null)
{
_metadataClient = metadataClient;
InitializeComponent();
ResultsList.ItemsSource = _results;
ResultsList.SelectionChanged += (_, _) =>
SelectButton.IsEnabled = ResultsList.SelectedItem != null;
SearchButton.Click += async (_, _) => await SearchAsync();
SelectButton.Click += OnSelectClick;
CancelDialogButton.Click += (_, _) => Close(null);
MoreButton.Click += async (_, _) => await LoadMoreAsync();
ProviderCombo.SelectionChanged += async (_, _) =>
{
_selectedProvider = ProviderCombo.SelectedItem as string;
if (_selectedProvider != null)
await LoadSubProvidersAsync(_selectedProvider);
};
if (!string.IsNullOrWhiteSpace(defaultSearch))
SearchField.Text = defaultSearch;
_ = LoadProvidersAsync();
}
private async Task LoadProvidersAsync()
{
try
{
var providers = await _metadataClient.GetProvidersAsync();
var list = providers.ToList();
ProviderCombo.ItemsSource = list;
if (list.Count > 0)
ProviderCombo.SelectedIndex = 0;
}
catch (Exception ex)
{
EmptyLabel.Text = $"Failed to load providers: {ex.Message}";
}
}
private async Task LoadSubProvidersAsync(string provider)
{
try
{
var subProviders = await _metadataClient.GetSubProvidersAsync(provider);
var list = subProviders?.ToList();
if (list != null && list.Count > 0)
{
SubProviderCombo.ItemsSource = list.Select(sp => sp.Name).ToList();
SubProviderCombo.IsVisible = true;
SubProviderCombo.SelectedIndex = 0;
}
else
{
SubProviderCombo.IsVisible = false;
SubProviderCombo.SelectedItem = null;
}
}
catch
{
SubProviderCombo.IsVisible = false;
}
}
private void OnSearchKeyDown(object? sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
_ = SearchAsync();
}
private async Task SearchAsync()
{
var query = SearchField.Text?.Trim();
if (string.IsNullOrWhiteSpace(query) || _selectedProvider == null)
return;
_offset = 0;
_results.Clear();
_rawResults.Clear();
LoadingPanel.IsVisible = true;
EmptyLabel.IsVisible = false;
SearchButton.IsEnabled = false;
try
{
var subProvider = SubProviderCombo.IsVisible
? SubProviderCombo.SelectedItem as string
: null;
var results = await _metadataClient.SearchAsync(_selectedProvider, query, subProvider, 10, 0);
if (results?.Results != null)
{
foreach (var result in results.Results)
{
_rawResults.Add(result);
_results.Add(new SearchResultItem(result.Data));
}
_hasMore = results.More;
_offset = results.Offset + results.Limit;
MoreButton.IsVisible = _hasMore;
}
if (_results.Count == 0)
EmptyLabel.Text = "No results found.";
EmptyLabel.IsVisible = _results.Count == 0;
}
catch (Exception ex)
{
EmptyLabel.Text = $"Search failed: {ex.Message}";
EmptyLabel.IsVisible = true;
}
finally
{
LoadingPanel.IsVisible = false;
SearchButton.IsEnabled = true;
}
}
private async Task LoadMoreAsync()
{
if (_selectedProvider == null)
return;
var query = SearchField.Text?.Trim();
if (string.IsNullOrWhiteSpace(query))
return;
MoreButton.IsEnabled = false;
try
{
var subProvider = SubProviderCombo.IsVisible
? SubProviderCombo.SelectedItem as string
: null;
var results = await _metadataClient.SearchAsync(_selectedProvider, query, subProvider, 10, _offset);
if (results?.Results != null)
{
foreach (var result in results.Results)
{
_rawResults.Add(result);
_results.Add(new SearchResultItem(result.Data));
}
_hasMore = results.More;
_offset = results.Offset + results.Limit;
}
MoreButton.IsVisible = _hasMore;
}
catch
{
MoreButton.IsVisible = false;
}
finally
{
MoreButton.IsEnabled = true;
}
}
private async void OnSelectClick(object? sender, RoutedEventArgs e)
{
var selectedIndex = ResultsList.SelectedIndex;
if (selectedIndex < 0 || selectedIndex >= _rawResults.Count || _selectedProvider == null)
return;
SelectButton.IsEnabled = false;
SelectButton.Content = "Loading...";
try
{
var rawResult = _rawResults[selectedIndex];
var fullGame = await _metadataClient.GetGameAsync(_selectedProvider, rawResult.Id);
SelectedGame = fullGame;
Close(fullGame);
}
catch (Exception ex)
{
EmptyLabel.Text = $"Failed to load game details: {ex.Message}";
EmptyLabel.IsVisible = true;
SelectButton.IsEnabled = true;
SelectButton.Content = "Select";
}
}
}
internal class SearchResultItem
{
public string Title { get; }
public string? ReleasedOn { get; }
public bool HasReleasedOn { get; }
public string? Platforms { get; }
public bool HasPlatforms { get; }
public SearchResultItem(Game game)
{
Title = game.Title ?? string.Empty;
HasReleasedOn = game.ReleasedOn != default;
ReleasedOn = HasReleasedOn ? game.ReleasedOn.ToString("yyyy-MM-dd") : null;
var platformNames = game.Platforms?
.Select(p => p.Name)
.Where(n => !string.IsNullOrWhiteSpace(n))
.ToList();
HasPlatforms = platformNames != null && platformNames.Count > 0;
Platforms = HasPlatforms ? string.Join(", ", platformNames!) : null;
}
}