Add consoles pages.

This commit is contained in:
2025-11-15 04:13:24 +00:00
parent e9221ac130
commit 7ee042bdec
9 changed files with 1285 additions and 4 deletions

View File

@@ -155,7 +155,7 @@ public partial class ComputersViewModel : ObservableObject
_logger.LogInformation("Navigating to computers by letter: {Letter}", letter);
_filterContext.FilterType = ComputerListFilterType.Letter;
_filterContext.FilterValue = letter.ToString();
await _navigator.NavigateViewModelAsync<ComputersListViewModel>(this);
await _navigator.NavigateRouteAsync(this, "list-computers");
}
catch(Exception ex)
{
@@ -175,7 +175,7 @@ public partial class ComputersViewModel : ObservableObject
_logger.LogInformation("Navigating to computers by year: {Year}", year);
_filterContext.FilterType = ComputerListFilterType.Year;
_filterContext.FilterValue = year.ToString();
await _navigator.NavigateViewModelAsync<ComputersListViewModel>(this);
await _navigator.NavigateRouteAsync(this, "list-computers");
}
catch(Exception ex)
{
@@ -195,7 +195,7 @@ public partial class ComputersViewModel : ObservableObject
_logger.LogInformation("Navigating to all computers");
_filterContext.FilterType = ComputerListFilterType.All;
_filterContext.FilterValue = string.Empty;
await _navigator.NavigateViewModelAsync<ComputersListViewModel>(this);
await _navigator.NavigateRouteAsync(this, "list-computers");
}
catch(Exception ex)
{

View File

@@ -0,0 +1,249 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Marechai.App.Helpers;
using Marechai.App.Presentation.Models;
using Marechai.App.Services;
using Uno.Extensions.Navigation;
namespace Marechai.App.Presentation.ViewModels;
/// <summary>
/// ViewModel for displaying a filtered list of consoles
/// </summary>
public partial class ConsolesListViewModel : ObservableObject
{
private readonly ConsolesService _consolesService;
private readonly IConsolesListFilterContext _filterContext;
private readonly IStringLocalizer _localizer;
private readonly ILogger<ConsolesListViewModel> _logger;
private readonly INavigator _navigator;
[ObservableProperty]
private ObservableCollection<ConsoleListItem> _consolesList = [];
[ObservableProperty]
private string _errorMessage = string.Empty;
[ObservableProperty]
private string _filterDescription = string.Empty;
[ObservableProperty]
private bool _hasError;
[ObservableProperty]
private bool _isDataLoaded;
[ObservableProperty]
private bool _isLoading;
[ObservableProperty]
private string _pageTitle = string.Empty;
public ConsolesListViewModel(ConsolesService consolesService, IStringLocalizer localizer,
ILogger<ConsolesListViewModel> logger, INavigator navigator,
IConsolesListFilterContext filterContext)
{
_consolesService = consolesService;
_localizer = localizer;
_logger = logger;
_navigator = navigator;
_filterContext = filterContext;
LoadData = new AsyncRelayCommand(LoadDataAsync);
GoBackCommand = new AsyncRelayCommand(GoBackAsync);
NavigateToConsoleCommand = new AsyncRelayCommand<ConsoleListItem>(NavigateToConsoleAsync);
}
public IAsyncRelayCommand LoadData { get; }
public ICommand GoBackCommand { get; }
public IAsyncRelayCommand<ConsoleListItem> NavigateToConsoleCommand { get; }
/// <summary>
/// Gets or sets the filter type
/// </summary>
public ConsoleListFilterType FilterType
{
get => _filterContext.FilterType;
set => _filterContext.FilterType = value;
}
/// <summary>
/// Gets or sets the filter value
/// </summary>
public string FilterValue
{
get => _filterContext.FilterValue;
set => _filterContext.FilterValue = value;
}
/// <summary>
/// Loads consoles based on the current filter
/// </summary>
private async Task LoadDataAsync()
{
try
{
IsLoading = true;
ErrorMessage = string.Empty;
HasError = false;
IsDataLoaded = false;
ConsolesList.Clear();
_logger.LogInformation("LoadDataAsync called. FilterType={FilterType}, FilterValue={FilterValue}",
FilterType,
FilterValue);
// Update title and filter description based on filter type
UpdateFilterDescription();
// Load consoles from the API based on the current filter
await LoadConsolesFromApiAsync();
_logger.LogInformation("LoadConsolesFromApiAsync completed. ConsolesList.Count={Count}",
ConsolesList.Count);
if(ConsolesList.Count == 0)
{
ErrorMessage = _localizer["No consoles found for this filter"].Value;
HasError = true;
_logger.LogWarning("No consoles found for filter: {FilterType} {FilterValue}", FilterType, FilterValue);
}
else
IsDataLoaded = true;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error loading consoles: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to load consoles. Please try again later."].Value;
HasError = true;
}
finally
{
IsLoading = false;
}
}
/// <summary>
/// Updates the title and filter description based on the current filter
/// </summary>
private void UpdateFilterDescription()
{
switch(FilterType)
{
case ConsoleListFilterType.All:
PageTitle = _localizer["All Consoles"];
FilterDescription = _localizer["Browsing all consoles in the database"];
break;
case ConsoleListFilterType.Letter:
if(!string.IsNullOrEmpty(FilterValue) && FilterValue.Length == 1)
{
PageTitle = $"{_localizer["Consoles Starting with"]} {FilterValue}";
FilterDescription = $"{_localizer["Showing consoles that start with"]} {FilterValue}";
}
break;
case ConsoleListFilterType.Year:
if(!string.IsNullOrEmpty(FilterValue) && int.TryParse(FilterValue, out int year))
{
PageTitle = $"{_localizer["Consoles from"]} {year}";
FilterDescription = $"{_localizer["Showing consoles released in"]} {year}";
}
break;
}
}
/// <summary>
/// Loads consoles from the API based on the current filter
/// </summary>
private async Task LoadConsolesFromApiAsync()
{
try
{
List<MachineDto> consoles = FilterType switch
{
ConsoleListFilterType.Letter when FilterValue.Length == 1 =>
await _consolesService.GetConsolesByLetterAsync(FilterValue[0]),
ConsoleListFilterType.Year when int.TryParse(FilterValue, out int year) =>
await _consolesService.GetConsolesByYearAsync(year),
_ => await _consolesService.GetAllConsolesAsync()
};
// Add consoles to the list sorted by name
foreach(MachineDto console in consoles.OrderBy(c => c.Name))
{
int year = console.Introduced?.Year ?? 0;
int id = UntypedNodeExtractor.ExtractInt(console.Id);
_logger.LogInformation("Console: {Name}, Introduced: {Introduced}, Year: {Year}, Company: {Company}, ID: {Id}",
console.Name,
console.Introduced,
year,
console.Company,
id);
ConsolesList.Add(new ConsoleListItem
{
Id = id,
Name = console.Name ?? string.Empty,
Year = year,
Manufacturer = console.Company ?? string.Empty
});
}
}
catch(Exception ex)
{
_logger.LogError(ex, "Error loading consoles from API");
}
}
/// <summary>
/// Navigates back to the consoles main view
/// </summary>
private async Task GoBackAsync()
{
await _navigator.NavigateViewModelAsync<ConsolesViewModel>(this);
}
/// <summary>
/// Navigates to the console detail view
/// </summary>
private async Task NavigateToConsoleAsync(ConsoleListItem? console)
{
if(console is null) return;
_logger.LogInformation("Navigating to console detail: {ConsoleName} (ID: {ConsoleId})",
console.Name,
console.Id);
var navParam = new MachineViewNavigationParameter
{
MachineId = console.Id,
NavigationSource = this
};
await _navigator.NavigateViewModelAsync<MachineViewViewModel>(this, data: navParam);
}
}
/// <summary>
/// Data model for a console in the list
/// </summary>
public class ConsoleListItem
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public int Year { get; set; }
public string Manufacturer { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,207 @@
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows.Input;
using Marechai.App.Services;
using Uno.Extensions.Navigation;
namespace Marechai.App.Presentation.ViewModels;
public partial class ConsolesViewModel : ObservableObject
{
private readonly ConsolesService _consolesService;
private readonly IConsolesListFilterContext _filterContext;
private readonly IStringLocalizer _localizer;
private readonly ILogger<ConsolesViewModel> _logger;
private readonly INavigator _navigator;
[ObservableProperty]
private int _consoleCount;
[ObservableProperty]
private string _consoleCountText = string.Empty;
[ObservableProperty]
private string _errorMessage = string.Empty;
[ObservableProperty]
private bool _hasError;
[ObservableProperty]
private bool _isDataLoaded;
[ObservableProperty]
private bool _isLoading;
[ObservableProperty]
private ObservableCollection<char> _lettersList = [];
[ObservableProperty]
private int _maximumYear;
[ObservableProperty]
private int _minimumYear;
[ObservableProperty]
private string _yearsGridTitle = string.Empty;
[ObservableProperty]
private ObservableCollection<int> _yearsList = [];
public ConsolesViewModel(ConsolesService consolesService, IStringLocalizer localizer,
ILogger<ConsolesViewModel> logger, INavigator navigator,
IConsolesListFilterContext filterContext)
{
_consolesService = consolesService;
_localizer = localizer;
_logger = logger;
_navigator = navigator;
_filterContext = filterContext;
LoadData = new AsyncRelayCommand(LoadDataAsync);
GoBackCommand = new AsyncRelayCommand(GoBackAsync);
NavigateByLetterCommand = new AsyncRelayCommand<char>(NavigateByLetterAsync);
NavigateByYearCommand = new AsyncRelayCommand<int>(NavigateByYearAsync);
NavigateAllConsolesCommand = new AsyncRelayCommand(NavigateAllConsolesAsync);
InitializeLetters();
}
public IAsyncRelayCommand LoadData { get; }
public ICommand GoBackCommand { get; }
public IAsyncRelayCommand<char> NavigateByLetterCommand { get; }
public IAsyncRelayCommand<int> NavigateByYearCommand { get; }
public IAsyncRelayCommand NavigateAllConsolesCommand { get; }
public string Title { get; } = "Consoles";
/// <summary>
/// Initializes the alphabet list (A-Z)
/// </summary>
private void InitializeLetters()
{
LettersList.Clear();
for(var c = 'A'; c <= 'Z'; c++) LettersList.Add(c);
}
/// <summary>
/// Loads consoles count, minimum and maximum years from the API
/// </summary>
private async Task LoadDataAsync()
{
try
{
IsLoading = true;
ErrorMessage = string.Empty;
HasError = false;
IsDataLoaded = false;
YearsList.Clear();
// Load all data in parallel for better performance
Task<int> countTask = _consolesService.GetConsolesCountAsync();
Task<int> minYearTask = _consolesService.GetMinimumYearAsync();
Task<int> maxYearTask = _consolesService.GetMaximumYearAsync();
await Task.WhenAll(countTask, minYearTask, maxYearTask);
ConsoleCount = countTask.Result;
MinimumYear = minYearTask.Result;
MaximumYear = maxYearTask.Result;
// Update display text
ConsoleCountText = _localizer["Consoles in the database"];
// Generate years list
if(MinimumYear > 0 && MaximumYear > 0)
{
for(int year = MinimumYear; year <= MaximumYear; year++) YearsList.Add(year);
YearsGridTitle = $"Browse by Year ({MinimumYear} - {MaximumYear})";
}
if(ConsoleCount == 0)
{
ErrorMessage = _localizer["No consoles found"].Value;
HasError = true;
}
else
IsDataLoaded = true;
}
catch(Exception ex)
{
_logger.LogError("Error loading consoles data: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to load consoles data. Please try again later."].Value;
HasError = true;
}
finally
{
IsLoading = false;
}
}
/// <summary>
/// Handles back navigation
/// </summary>
private async Task GoBackAsync()
{
await _navigator.NavigateViewModelAsync<MainViewModel>(this);
}
/// <summary>
/// Navigates to consoles filtered by letter
/// </summary>
private async Task NavigateByLetterAsync(char letter)
{
try
{
_logger.LogInformation("Navigating to consoles by letter: {Letter}", letter);
_filterContext.FilterType = ConsoleListFilterType.Letter;
_filterContext.FilterValue = letter.ToString();
await _navigator.NavigateRouteAsync(this, "list-consoles");
}
catch(Exception ex)
{
_logger.LogError("Error navigating to letter consoles: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to navigate. Please try again."].Value;
HasError = true;
}
}
/// <summary>
/// Navigates to consoles filtered by year
/// </summary>
private async Task NavigateByYearAsync(int year)
{
try
{
_logger.LogInformation("Navigating to consoles by year: {Year}", year);
_filterContext.FilterType = ConsoleListFilterType.Year;
_filterContext.FilterValue = year.ToString();
await _navigator.NavigateRouteAsync(this, "list-consoles");
}
catch(Exception ex)
{
_logger.LogError("Error navigating to year consoles: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to navigate. Please try again."].Value;
HasError = true;
}
}
/// <summary>
/// Navigates to all consoles view
/// </summary>
private async Task NavigateAllConsolesAsync()
{
try
{
_logger.LogInformation("Navigating to all consoles");
_filterContext.FilterType = ConsoleListFilterType.All;
_filterContext.FilterValue = string.Empty;
await _navigator.NavigateRouteAsync(this, "list-consoles");
}
catch(Exception ex)
{
_logger.LogError("Error navigating to all consoles: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to navigate. Please try again."].Value;
HasError = true;
}
}
}

View File

@@ -127,6 +127,22 @@ public partial class MachineViewViewModel : ObservableObject
return;
}
// If we came from ConsolesListViewModel, navigate back to consoles list
if(_navigationSource is ConsolesListViewModel)
{
await _navigator.NavigateViewModelAsync<ConsolesListViewModel>(this);
return;
}
// If we came from ComputersListViewModel, navigate back to computers list
if(_navigationSource is ComputersListViewModel)
{
await _navigator.NavigateViewModelAsync<ComputersListViewModel>(this);
return;
}
// Otherwise, try to go back in the navigation stack
await _navigator.GoBack(this);
}