mirror of
https://github.com/claunia/marechai.git
synced 2025-12-16 19:14:25 +00:00
Add missing computers list page.
This commit is contained in:
261
Marechai.App/Presentation/ComputersListPage.xaml
Normal file
261
Marechai.App/Presentation/ComputersListPage.xaml
Normal file
@@ -0,0 +1,261 @@
|
||||
<?xml version="1.0"
|
||||
encoding="utf-8"?>
|
||||
|
||||
<Page x:Class="Marechai.App.Presentation.ComputersListPage"
|
||||
x:Name="PageRoot"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
NavigationCacheMode="Required"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid RowSpacing="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header with Back Button and Title -->
|
||||
<Grid Grid.Row="0"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
BorderThickness="0,0,0,1"
|
||||
Padding="16,12,16,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Back Button -->
|
||||
<Button Grid.Column="0"
|
||||
Command="{Binding GoBackCommand}"
|
||||
Style="{ThemeResource AlternateButtonStyle}"
|
||||
ToolTipService.ToolTip="Go back"
|
||||
Padding="8"
|
||||
MinWidth="44"
|
||||
MinHeight="44"
|
||||
VerticalAlignment="Center">
|
||||
<FontIcon Glyph=""
|
||||
FontSize="16" />
|
||||
</Button>
|
||||
|
||||
<!-- Page Title -->
|
||||
<StackPanel Grid.Column="1"
|
||||
Margin="16,0,0,0"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding PageTitle}"
|
||||
FontSize="20"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}" />
|
||||
<TextBlock Text="{Binding FilterDescription}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
Margin="0,4,0,0" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Main Content -->
|
||||
<Grid Grid.Row="1">
|
||||
|
||||
<!-- Loading State -->
|
||||
<StackPanel Visibility="{Binding IsLoading}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Padding="32"
|
||||
Spacing="16">
|
||||
<ProgressRing IsActive="True"
|
||||
IsIndeterminate="True"
|
||||
Height="64"
|
||||
Width="64"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
<TextBlock Text="Loading computers..."
|
||||
FontSize="14"
|
||||
TextAlignment="Center"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Error State -->
|
||||
<StackPanel Visibility="{Binding HasError}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Padding="24"
|
||||
Spacing="16"
|
||||
MaxWidth="400">
|
||||
<InfoBar IsOpen="True"
|
||||
Severity="Error"
|
||||
Title="Unable to Load Computers"
|
||||
Message="{Binding ErrorMessage}"
|
||||
IsClosable="False" />
|
||||
<Button Content="Retry"
|
||||
Command="{Binding LoadData}"
|
||||
HorizontalAlignment="Center"
|
||||
Style="{ThemeResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Computers List -->
|
||||
<Grid Visibility="{Binding IsDataLoaded}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
<Grid Padding="8">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Count Header -->
|
||||
<StackPanel Grid.Row="0"
|
||||
Padding="16,12"
|
||||
Orientation="Horizontal"
|
||||
Spacing="4">
|
||||
<TextBlock FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
Text="RESULTS:" />
|
||||
<TextBlock FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
Text="{Binding ComputersList.Count}" />
|
||||
<TextBlock FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
Text="computers" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Computers List -->
|
||||
<ItemsControl Grid.Row="1"
|
||||
ItemsSource="{Binding ComputersList}"
|
||||
Padding="0"
|
||||
Margin="0,8,0,0"
|
||||
HorizontalAlignment="Stretch">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="0"
|
||||
HorizontalAlignment="Stretch" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Padding="0"
|
||||
Margin="0,0,0,8"
|
||||
MinHeight="80"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding DataContext.NavigateToComputerCommand, ElementName=PageRoot}"
|
||||
CommandParameter="{Binding}"
|
||||
Background="Transparent"
|
||||
BorderThickness="0">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Grid MinHeight="80"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<!-- Shadow effect -->
|
||||
<Border x:Name="ShadowBorder"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Padding="16,12"
|
||||
Translation="0, 0, 4"
|
||||
VerticalAlignment="Stretch">
|
||||
<Border.Shadow>
|
||||
<ThemeShadow />
|
||||
</Border.Shadow>
|
||||
|
||||
<Grid ColumnSpacing="16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Computer Info -->
|
||||
<StackPanel Grid.Column="0"
|
||||
Spacing="8"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Stretch">
|
||||
<TextBlock Text="{Binding Name}"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
<Grid ColumnSpacing="16"
|
||||
Height="20"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6"
|
||||
VerticalAlignment="Center">
|
||||
<FontIcon Glyph=""
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
<TextBlock Text="{Binding Manufacturer}"
|
||||
FontSize="13"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6"
|
||||
VerticalAlignment="Center">
|
||||
<FontIcon Glyph=""
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
<TextBlock FontSize="13"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}">
|
||||
<Run Text="{Binding Year}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Navigation Arrow -->
|
||||
<StackPanel Grid.Column="1"
|
||||
VerticalAlignment="Center">
|
||||
<FontIcon Glyph=""
|
||||
FontSize="18"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="PointerOver">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ShadowBorder.Background"
|
||||
Value="{ThemeResource CardBackgroundFillColorSecondaryBrush}" />
|
||||
<Setter Target="ShadowBorder.Translation"
|
||||
Value="0, -2, 8" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ShadowBorder.Background"
|
||||
Value="{ThemeResource CardBackgroundFillColorTertiaryBrush}" />
|
||||
<Setter Target="ShadowBorder.Translation"
|
||||
Value="0, 0, 2" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Page>
|
||||
36
Marechai.App/Presentation/ComputersListPage.xaml.cs
Normal file
36
Marechai.App/Presentation/ComputersListPage.xaml.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Marechai.App.Presentation;
|
||||
|
||||
/// <summary>
|
||||
/// Professional list view for displaying computers filtered by letter, year, or all.
|
||||
/// Features responsive layout, modern styling, and smooth navigation.
|
||||
/// </summary>
|
||||
public sealed partial class ComputersListPage : Page
|
||||
{
|
||||
public ComputersListPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += ComputersListPage_Loaded;
|
||||
DataContextChanged += ComputersListPage_DataContextChanged;
|
||||
}
|
||||
|
||||
private void ComputersListPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
|
||||
{
|
||||
if(DataContext is ComputersListViewModel vm)
|
||||
{
|
||||
// Load data when DataContext is set
|
||||
vm.LoadData.Execute(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void ComputersListPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(DataContext is ComputersListViewModel vm)
|
||||
{
|
||||
// Load data when page is loaded (fallback)
|
||||
vm.LoadData.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
249
Marechai.App/Presentation/ComputersListViewModel.cs
Normal file
249
Marechai.App/Presentation/ComputersListViewModel.cs
Normal 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.Services;
|
||||
using Uno.Extensions.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation;
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel for displaying a filtered list of computers
|
||||
/// </summary>
|
||||
public partial class ComputersListViewModel : ObservableObject
|
||||
{
|
||||
private readonly ComputersService _computersService;
|
||||
private readonly IComputersListFilterContext _filterContext;
|
||||
private readonly IStringLocalizer _localizer;
|
||||
private readonly ILogger<ComputersListViewModel> _logger;
|
||||
private readonly INavigator _navigator;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<ComputerListItem> computersList = [];
|
||||
|
||||
[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 ComputersListViewModel(ComputersService computersService, IStringLocalizer localizer,
|
||||
ILogger<ComputersListViewModel> logger, INavigator navigator,
|
||||
IComputersListFilterContext filterContext)
|
||||
{
|
||||
_computersService = computersService;
|
||||
_localizer = localizer;
|
||||
_logger = logger;
|
||||
_navigator = navigator;
|
||||
_filterContext = filterContext;
|
||||
LoadData = new AsyncRelayCommand(LoadDataAsync);
|
||||
GoBackCommand = new AsyncRelayCommand(GoBackAsync);
|
||||
NavigateToComputerCommand = new AsyncRelayCommand<ComputerListItem>(NavigateToComputerAsync);
|
||||
}
|
||||
|
||||
public IAsyncRelayCommand LoadData { get; }
|
||||
public ICommand GoBackCommand { get; }
|
||||
public IAsyncRelayCommand<ComputerListItem> NavigateToComputerCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the filter type
|
||||
/// </summary>
|
||||
public ComputerListFilterType 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 computers based on the current filter
|
||||
/// </summary>
|
||||
private async Task LoadDataAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
ErrorMessage = string.Empty;
|
||||
HasError = false;
|
||||
IsDataLoaded = false;
|
||||
ComputersList.Clear();
|
||||
|
||||
_logger.LogInformation("LoadDataAsync called. FilterType={FilterType}, FilterValue={FilterValue}",
|
||||
FilterType,
|
||||
FilterValue);
|
||||
|
||||
// Update title and filter description based on filter type
|
||||
UpdateFilterDescription();
|
||||
|
||||
// Load computers from the API based on the current filter
|
||||
await LoadComputersFromApiAsync();
|
||||
|
||||
_logger.LogInformation("LoadComputersFromApiAsync completed. ComputersList.Count={Count}",
|
||||
ComputersList.Count);
|
||||
|
||||
if(ComputersList.Count == 0)
|
||||
{
|
||||
ErrorMessage = _localizer["No computers found for this filter"].Value;
|
||||
HasError = true;
|
||||
|
||||
_logger.LogWarning("No computers found for filter: {FilterType} {FilterValue}",
|
||||
FilterType,
|
||||
FilterValue);
|
||||
}
|
||||
else
|
||||
IsDataLoaded = true;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading computers: {Exception}", ex.Message);
|
||||
ErrorMessage = _localizer["Failed to load computers. 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 ComputerListFilterType.All:
|
||||
PageTitle = _localizer["All Computers"];
|
||||
FilterDescription = _localizer["Browsing all computers in the database"];
|
||||
|
||||
break;
|
||||
|
||||
case ComputerListFilterType.Letter:
|
||||
if(!string.IsNullOrEmpty(FilterValue) && FilterValue.Length == 1)
|
||||
{
|
||||
PageTitle = $"{_localizer["Computers Starting with"]} {FilterValue}";
|
||||
FilterDescription = $"{_localizer["Showing computers that start with"]} {FilterValue}";
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ComputerListFilterType.Year:
|
||||
if(!string.IsNullOrEmpty(FilterValue) && int.TryParse(FilterValue, out int year))
|
||||
{
|
||||
PageTitle = $"{_localizer["Computers from"]} {year}";
|
||||
FilterDescription = $"{_localizer["Showing computers released in"]} {year}";
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads computers from the API based on the current filter
|
||||
/// </summary>
|
||||
private async Task LoadComputersFromApiAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
List<MachineDto> computers = FilterType switch
|
||||
{
|
||||
ComputerListFilterType.Letter when FilterValue.Length == 1 =>
|
||||
await _computersService.GetComputersByLetterAsync(FilterValue[0]),
|
||||
|
||||
ComputerListFilterType.Year when int.TryParse(FilterValue, out int year) =>
|
||||
await _computersService.GetComputersByYearAsync(year),
|
||||
|
||||
_ => await _computersService.GetAllComputersAsync()
|
||||
};
|
||||
|
||||
// Add computers to the list sorted by name
|
||||
foreach(MachineDto computer in computers.OrderBy(c => c.Name))
|
||||
{
|
||||
int year = computer.Introduced?.Year ?? 0;
|
||||
int id = UntypedNodeExtractor.ExtractInt(computer.Id);
|
||||
|
||||
_logger.LogInformation("Computer: {Name}, Introduced: {Introduced}, Year: {Year}, Company: {Company}, ID: {Id}",
|
||||
computer.Name,
|
||||
computer.Introduced,
|
||||
year,
|
||||
computer.Company,
|
||||
id);
|
||||
|
||||
ComputersList.Add(new ComputerListItem
|
||||
{
|
||||
Id = id,
|
||||
Name = computer.Name ?? string.Empty,
|
||||
Year = year,
|
||||
Manufacturer = computer.Company ?? string.Empty
|
||||
});
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading computers from API");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates back to the computers main view
|
||||
/// </summary>
|
||||
private async Task GoBackAsync()
|
||||
{
|
||||
await _navigator.NavigateViewModelAsync<ComputersViewModel>(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to the computer detail view
|
||||
/// </summary>
|
||||
private async Task NavigateToComputerAsync(ComputerListItem? computer)
|
||||
{
|
||||
if(computer is null) return;
|
||||
|
||||
_logger.LogInformation("Navigating to computer detail: {ComputerName} (ID: {ComputerId})",
|
||||
computer.Name,
|
||||
computer.Id);
|
||||
|
||||
var navParam = new MachineViewNavigationParameter
|
||||
{
|
||||
MachineId = computer.Id,
|
||||
NavigationSource = this
|
||||
};
|
||||
|
||||
await _navigator.NavigateViewModelAsync<MachineViewViewModel>(this, data: navParam);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data model for a computer in the list
|
||||
/// </summary>
|
||||
public class ComputerListItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public int Year { get; set; }
|
||||
public string Manufacturer { get; set; } = string.Empty;
|
||||
}
|
||||
Reference in New Issue
Block a user