mirror of
https://github.com/claunia/marechai.git
synced 2025-12-16 19:14:25 +00:00
Add computers page.
This commit is contained in:
@@ -100,6 +100,8 @@ public partial class App : Application
|
||||
// Register application services
|
||||
services.AddSingleton<NewsService>();
|
||||
services.AddSingleton<NewsViewModel>();
|
||||
services.AddSingleton<ComputersService>();
|
||||
services.AddSingleton<ComputersViewModel>();
|
||||
})
|
||||
.UseNavigation(RegisterRoutes));
|
||||
|
||||
@@ -117,14 +119,27 @@ public partial class App : Application
|
||||
{
|
||||
views.Register(new ViewMap(ViewModel: typeof(ShellViewModel)),
|
||||
new ViewMap<MainPage, MainViewModel>(),
|
||||
new ViewMap<NewsPage, NewsViewModel>(),
|
||||
new ViewMap<ComputersPage, ComputersViewModel>(),
|
||||
new DataViewMap<SecondPage, SecondViewModel, Entity>());
|
||||
|
||||
routes.Register(new RouteMap("",
|
||||
views.FindByViewModel<ShellViewModel>(),
|
||||
Nested:
|
||||
[
|
||||
new RouteMap("Main", views.FindByViewModel<MainViewModel>(), true),
|
||||
new RouteMap("Second", views.FindByViewModel<SecondViewModel>())
|
||||
new RouteMap("Main",
|
||||
views.FindByViewModel<MainViewModel>(),
|
||||
true,
|
||||
Nested:
|
||||
[
|
||||
new RouteMap("News",
|
||||
views.FindByViewModel<NewsViewModel>(),
|
||||
true),
|
||||
new RouteMap("computers",
|
||||
views.FindByViewModel<ComputersViewModel>()),
|
||||
new RouteMap("Second",
|
||||
views.FindByViewModel<SecondViewModel>())
|
||||
])
|
||||
]));
|
||||
}
|
||||
}
|
||||
297
Marechai.App/Presentation/ComputersPage.xaml
Normal file
297
Marechai.App/Presentation/ComputersPage.xaml
Normal file
@@ -0,0 +1,297 @@
|
||||
<?xml version="1.0"
|
||||
encoding="utf-8"?>
|
||||
|
||||
<Page x:Class="Marechai.App.Presentation.ComputersPage"
|
||||
x:Name="PageRoot"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:utu="using:Uno.Toolkit.UI"
|
||||
NavigationCacheMode="Required"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid utu:SafeArea.Insets="VisibleBounds">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header -->
|
||||
<utu:NavigationBar Grid.Row="0"
|
||||
Content="{Binding Path=Title}">
|
||||
<utu:NavigationBar.MainCommand>
|
||||
<AppBarButton Icon="Back"
|
||||
Label="Back"
|
||||
Command="{Binding GoBackCommand}"
|
||||
AutomationProperties.Name="Go back" />
|
||||
</utu:NavigationBar.MainCommand>
|
||||
</utu:NavigationBar>
|
||||
|
||||
<!-- Content -->
|
||||
<ScrollViewer Grid.Row="1">
|
||||
<StackPanel Padding="16"
|
||||
Spacing="24">
|
||||
|
||||
<!-- Computer Count Display -->
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
Spacing="8">
|
||||
<TextBlock Text="{Binding ComputerCountText}"
|
||||
TextAlignment="Center"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding ComputerCount}"
|
||||
TextAlignment="Center"
|
||||
FontSize="48"
|
||||
FontWeight="Bold"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Loading State -->
|
||||
<StackPanel Visibility="{Binding IsLoading}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Padding="32"
|
||||
Spacing="16">
|
||||
<ProgressRing IsActive="True"
|
||||
IsIndeterminate="True"
|
||||
Height="48"
|
||||
Width="48" />
|
||||
<TextBlock Text="Loading..."
|
||||
TextAlignment="Center"
|
||||
FontSize="14" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Error State -->
|
||||
<StackPanel Visibility="{Binding HasError}"
|
||||
Padding="16"
|
||||
Background="{ThemeResource SystemErrorBackgroundColor}"
|
||||
CornerRadius="8"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Error"
|
||||
FontWeight="Bold"
|
||||
Foreground="{ThemeResource SystemErrorTextForegroundColor}" />
|
||||
<TextBlock Text="{Binding ErrorMessage}"
|
||||
Foreground="{ThemeResource SystemErrorTextForegroundColor}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Main Content (visible when loaded and no error) -->
|
||||
<StackPanel Visibility="{Binding IsDataLoaded}"
|
||||
Spacing="24">
|
||||
|
||||
<!-- Letters Grid Section -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Browse by Letter"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<ItemsRepeater ItemsSource="{Binding LettersList}"
|
||||
Layout="{StaticResource LettersGridLayout}">
|
||||
<ItemsRepeater.ItemTemplate></ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Years Grid Section -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="{Binding YearsGridTitle}"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<ItemsRepeater ItemsSource="{Binding YearsList}"
|
||||
Layout="{StaticResource YearsGridLayout}">
|
||||
<ItemsRepeater.ItemTemplate></ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</StackPanel>
|
||||
|
||||
<!-- All Computers and Search Section -->
|
||||
<StackPanel Spacing="12">
|
||||
<Button Content="All Computers"
|
||||
Padding="16,12"
|
||||
HorizontalAlignment="Stretch"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Command="{Binding NavigateAllComputersCommand}"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
|
||||
<!-- Search Field (placeholder for future implementation) -->
|
||||
<TextBox PlaceholderText="Search computers..."
|
||||
Padding="12"
|
||||
IsEnabled="False"
|
||||
Opacity="0.5" />
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Page.Resources>
|
||||
<!-- Keyboard Key Button Style (revised: more padding, simplified borders to avoid clipping, darker scheme) -->
|
||||
<Style x:Key="KeyboardKeyButtonStyle"
|
||||
TargetType="Button">
|
||||
<!-- Base appearance -->
|
||||
<Setter Property="Foreground"
|
||||
Value="#1A1A1A" />
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<LinearGradientBrush StartPoint="0,0"
|
||||
EndPoint="0,1">
|
||||
<GradientStop Color="#D6D6D6"
|
||||
Offset="0" />
|
||||
<GradientStop Color="#C2C2C2"
|
||||
Offset="0.55" />
|
||||
<GradientStop Color="#B0B0B0"
|
||||
Offset="1" />
|
||||
</LinearGradientBrush>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="BorderBrush"
|
||||
Value="#7A7A7A" />
|
||||
<Setter Property="BorderThickness"
|
||||
Value="1" />
|
||||
<Setter Property="CornerRadius"
|
||||
Value="6" />
|
||||
<Setter Property="Padding"
|
||||
Value="14,12" /> <!-- Increased vertical padding to prevent cutoff -->
|
||||
<Setter Property="Margin"
|
||||
Value="4" />
|
||||
<Setter Property="FontFamily"
|
||||
Value="Segoe UI" />
|
||||
<Setter Property="FontWeight"
|
||||
Value="SemiBold" />
|
||||
<Setter Property="FontSize"
|
||||
Value="15" />
|
||||
<Setter Property="HorizontalAlignment"
|
||||
Value="Stretch" />
|
||||
<Setter Property="VerticalAlignment"
|
||||
Value="Stretch" />
|
||||
<Setter Property="HorizontalContentAlignment"
|
||||
Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment"
|
||||
Value="Center" />
|
||||
<Setter Property="MinWidth"
|
||||
Value="52" />
|
||||
<Setter Property="MinHeight"
|
||||
Value="52" /> <!-- Larger min height avoids clipping ascenders/descenders -->
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Grid>
|
||||
<!-- Shadow (simple) -->
|
||||
<Border x:Name="Shadow"
|
||||
CornerRadius="6"
|
||||
Background="#33000000"
|
||||
Margin="2,4,4,2" />
|
||||
<!-- Key surface -->
|
||||
<Border x:Name="KeyBorder"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<!-- Inner highlight & content -->
|
||||
<Grid>
|
||||
<Border CornerRadius="{TemplateBinding CornerRadius}"
|
||||
BorderBrush="#60FFFFFF"
|
||||
BorderThickness="1,1,0,0" />
|
||||
<Border CornerRadius="{TemplateBinding CornerRadius}"
|
||||
BorderBrush="#30000000"
|
||||
BorderThickness="0,0,1,1" />
|
||||
<ContentPresenter x:Name="ContentPresenter"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Margin="0"
|
||||
TextWrapping="NoWrap" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="PointerOver">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="KeyBorder.Background">
|
||||
<Setter.Value>
|
||||
<LinearGradientBrush StartPoint="0,0"
|
||||
EndPoint="0,1">
|
||||
<GradientStop Color="#E0E0E0"
|
||||
Offset="0" />
|
||||
<GradientStop Color="#CFCFCF"
|
||||
Offset="0.55" />
|
||||
<GradientStop Color="#BDBDBD"
|
||||
Offset="1" />
|
||||
</LinearGradientBrush>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Target="KeyBorder.BorderBrush"
|
||||
Value="#5F5F5F" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="KeyBorder.Background">
|
||||
<Setter.Value>
|
||||
<LinearGradientBrush StartPoint="0,0"
|
||||
EndPoint="0,1">
|
||||
<GradientStop Color="#9C9C9C"
|
||||
Offset="0" />
|
||||
<GradientStop Color="#A8A8A8"
|
||||
Offset="0.55" />
|
||||
<GradientStop Color="#B4B4B4"
|
||||
Offset="1" />
|
||||
</LinearGradientBrush>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Target="KeyBorder.BorderBrush"
|
||||
Value="#4A4A4A" />
|
||||
<Setter Target="KeyBorder.RenderTransform">
|
||||
<Setter.Value>
|
||||
<TranslateTransform Y="2" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Target="Shadow.Opacity"
|
||||
Value="0.15" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="KeyBorder.Opacity"
|
||||
Value="0.45" />
|
||||
<Setter Target="ContentPresenter.Foreground"
|
||||
Value="#777777" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="FocusStates">
|
||||
<VisualState x:Name="Focused">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="KeyBorder.BorderBrush"
|
||||
Value="#3A7AFE" />
|
||||
<Setter Target="KeyBorder.BorderThickness"
|
||||
Value="2" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Unfocused" />
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- Responsive Grid Layouts -->
|
||||
<UniformGridLayout x:Key="LettersGridLayout"
|
||||
ItemsStretch="Fill"
|
||||
MinItemWidth="44"
|
||||
MinItemHeight="44"
|
||||
MaximumRowsOrColumns="13" />
|
||||
|
||||
<UniformGridLayout x:Key="YearsGridLayout"
|
||||
ItemsStretch="Fill"
|
||||
MinItemWidth="54"
|
||||
MinItemHeight="44"
|
||||
MaximumRowsOrColumns="10" />
|
||||
|
||||
</Page.Resources>
|
||||
|
||||
</Page>
|
||||
43
Marechai.App/Presentation/ComputersPage.xaml.cs
Normal file
43
Marechai.App/Presentation/ComputersPage.xaml.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation;
|
||||
|
||||
public sealed partial class ComputersPage : Page
|
||||
{
|
||||
public ComputersPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContextChanged += ComputersPage_DataContextChanged;
|
||||
Loaded += ComputersPage_Loaded;
|
||||
}
|
||||
|
||||
private void ComputersPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(DataContext is not ComputersViewModel viewModel) return;
|
||||
|
||||
// Trigger data loading
|
||||
_ = viewModel.LoadData.ExecuteAsync(null);
|
||||
}
|
||||
|
||||
private void ComputersPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
|
||||
{
|
||||
if(args.NewValue is ComputersViewModel viewModel)
|
||||
{
|
||||
// Trigger data loading when data context changes
|
||||
_ = viewModel.LoadData.ExecuteAsync(null);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
if(DataContext is ComputersViewModel viewModel)
|
||||
{
|
||||
// Trigger data loading when navigating to the page
|
||||
_ = viewModel.LoadData.ExecuteAsync(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
177
Marechai.App/Presentation/ComputersViewModel.cs
Normal file
177
Marechai.App/Presentation/ComputersViewModel.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
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;
|
||||
|
||||
public partial class ComputersViewModel : ObservableObject
|
||||
{
|
||||
private readonly ComputersService _computersService;
|
||||
private readonly IStringLocalizer _localizer;
|
||||
private readonly ILogger<ComputersViewModel> _logger;
|
||||
private readonly INavigator _navigator;
|
||||
|
||||
[ObservableProperty]
|
||||
private int computerCount;
|
||||
|
||||
[ObservableProperty]
|
||||
private string computerCountText = 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 = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private int maximumYear;
|
||||
|
||||
[ObservableProperty]
|
||||
private int minimumYear;
|
||||
|
||||
[ObservableProperty]
|
||||
private string yearsGridTitle = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<int> yearsList = new();
|
||||
|
||||
public ComputersViewModel(ComputersService computersService, IStringLocalizer localizer,
|
||||
ILogger<ComputersViewModel> logger, INavigator navigator)
|
||||
{
|
||||
_computersService = computersService;
|
||||
_localizer = localizer;
|
||||
_logger = logger;
|
||||
_navigator = navigator;
|
||||
LoadData = new AsyncRelayCommand(LoadDataAsync);
|
||||
GoBackCommand = new AsyncRelayCommand(GoBackAsync);
|
||||
NavigateByLetterCommand = new AsyncRelayCommand<char>(NavigateByLetterAsync);
|
||||
NavigateByYearCommand = new AsyncRelayCommand<int>(NavigateByYearAsync);
|
||||
NavigateAllComputersCommand = new AsyncRelayCommand(NavigateAllComputersAsync);
|
||||
|
||||
InitializeLetters();
|
||||
}
|
||||
|
||||
public IAsyncRelayCommand LoadData { get; }
|
||||
public ICommand GoBackCommand { get; }
|
||||
public IAsyncRelayCommand<char> NavigateByLetterCommand { get; }
|
||||
public IAsyncRelayCommand<int> NavigateByYearCommand { get; }
|
||||
public IAsyncRelayCommand NavigateAllComputersCommand { get; }
|
||||
public string Title { get; } = "Computers";
|
||||
|
||||
/// <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 computers 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 = _computersService.GetComputersCountAsync();
|
||||
Task<int> minYearTask = _computersService.GetMinimumYearAsync();
|
||||
Task<int> maxYearTask = _computersService.GetMaximumYearAsync();
|
||||
await Task.WhenAll(countTask, minYearTask, maxYearTask);
|
||||
|
||||
ComputerCount = countTask.Result;
|
||||
MinimumYear = minYearTask.Result;
|
||||
MaximumYear = maxYearTask.Result;
|
||||
|
||||
// Update display text
|
||||
ComputerCountText = _localizer["Computers 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(ComputerCount == 0)
|
||||
{
|
||||
ErrorMessage = _localizer["No computers found"].Value;
|
||||
HasError = true;
|
||||
}
|
||||
else
|
||||
IsDataLoaded = true;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError("Error loading computers data: {Exception}", ex.Message);
|
||||
ErrorMessage = _localizer["Failed to load computers 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 computers filtered by letter
|
||||
/// </summary>
|
||||
private async Task NavigateByLetterAsync(char letter)
|
||||
{
|
||||
_logger.LogInformation("Navigating to computers by letter: {Letter}", letter);
|
||||
|
||||
// TODO: Implement navigation to letter-filtered view
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to computers filtered by year
|
||||
/// </summary>
|
||||
private async Task NavigateByYearAsync(int year)
|
||||
{
|
||||
_logger.LogInformation("Navigating to computers by year: {Year}", year);
|
||||
|
||||
// TODO: Implement navigation to year-filtered view
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to all computers view
|
||||
/// </summary>
|
||||
private async Task NavigateAllComputersAsync()
|
||||
{
|
||||
_logger.LogInformation("Navigating to all computers");
|
||||
|
||||
// TODO: Implement navigation to all computers view
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Marechai.App.Presentation"
|
||||
xmlns:utu="using:Uno.Toolkit.UI"
|
||||
xmlns:uen="using:Uno.Extensions.Navigation.UI"
|
||||
NavigationCacheMode="Required"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
@@ -43,120 +44,12 @@
|
||||
</utu:NavigationBar.MainCommand>
|
||||
</utu:NavigationBar>
|
||||
|
||||
<!-- Content -->
|
||||
<Grid Grid.Row="1"
|
||||
Grid.Column="1">
|
||||
<RefreshContainer x:Name="RefreshContainer"
|
||||
RefreshRequested="RefreshContainer_RefreshRequested">
|
||||
<ScrollViewer>
|
||||
<Grid Padding="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- News Title Section -->
|
||||
<StackPanel Grid.Row="0"
|
||||
Margin="0,0,0,16">
|
||||
<TextBlock Text="Latest News"
|
||||
FontSize="32"
|
||||
FontWeight="Bold"
|
||||
Foreground="{ThemeResource SystemAccentColor}"
|
||||
Margin="0,0,0,8" />
|
||||
<TextBlock Text="Stay updated with the latest additions to the database"
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Loading State -->
|
||||
<StackPanel Grid.Row="2"
|
||||
Visibility="{Binding NewsViewModel.IsLoading}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Padding="32"
|
||||
Spacing="16">
|
||||
<ProgressRing IsActive="True"
|
||||
IsIndeterminate="True"
|
||||
Height="48"
|
||||
Width="48"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
<TextBlock Text="Loading latest news..."
|
||||
FontSize="16"
|
||||
TextAlignment="Center"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Error State -->
|
||||
<StackPanel Grid.Row="2"
|
||||
Visibility="{Binding NewsViewModel.HasError}"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="16"
|
||||
Padding="32">
|
||||
<InfoBar IsOpen="True"
|
||||
Severity="Error"
|
||||
Title="Unable to Load News"
|
||||
Message="{Binding NewsViewModel.ErrorMessage}"
|
||||
IsClosable="False" />
|
||||
<Button Content="Retry"
|
||||
Command="{Binding NewsViewModel.LoadNews}"
|
||||
HorizontalAlignment="Center"
|
||||
Style="{ThemeResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- News Feed -->
|
||||
<ItemsControl Grid.Row="2"
|
||||
ItemsSource="{Binding NewsViewModel.NewsList}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Margin="0,0,0,12"
|
||||
CornerRadius="8"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
Padding="16">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Date -->
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{Binding News.Timestamp}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
Margin="0,0,0,12" />
|
||||
|
||||
<!-- News Title/Text (Localized) -->
|
||||
<TextBlock Grid.Row="1"
|
||||
Text="{Binding DisplayText}"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseHighColor}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="0,0,0,12" />
|
||||
|
||||
<!-- Item Name Link -->
|
||||
<HyperlinkButton Grid.Row="2"
|
||||
Content="{Binding News.ItemName}"
|
||||
FontSize="14"
|
||||
Padding="0,4,0,4"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="0" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</RefreshContainer>
|
||||
</Grid>
|
||||
<!-- Content Region for Navigation -->
|
||||
<ContentControl Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
uen:Region.Attached="True"
|
||||
uen:Region.Name="Main" />
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -1,15 +1,12 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Windows.Foundation;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation;
|
||||
|
||||
public sealed partial class MainPage : Page
|
||||
{
|
||||
private bool _initialNewsLoaded;
|
||||
private PropertyChangedEventHandler _sidebarPropertyChangedHandler;
|
||||
|
||||
public MainPage()
|
||||
@@ -96,44 +93,5 @@ public sealed partial class MainPage : Page
|
||||
|
||||
((INotifyPropertyChanged)vm).PropertyChanged += _sidebarPropertyChangedHandler;
|
||||
}
|
||||
|
||||
if(_initialNewsLoaded) return;
|
||||
|
||||
if(args.NewValue is MainViewModel viewModel && viewModel.NewsViewModel is not null)
|
||||
{
|
||||
_initialNewsLoaded = true;
|
||||
_ = viewModel.NewsViewModel.LoadNews.ExecuteAsync(null);
|
||||
DataContextChanged -= MainPage_DataContextChanged;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
if(_initialNewsLoaded) return;
|
||||
|
||||
if(DataContext is MainViewModel viewModel && viewModel.NewsViewModel is not null)
|
||||
{
|
||||
_initialNewsLoaded = true;
|
||||
_ = viewModel.NewsViewModel.LoadNews.ExecuteAsync(null);
|
||||
DataContextChanged -= MainPage_DataContextChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private async void RefreshContainer_RefreshRequested(RefreshContainer sender, RefreshRequestedEventArgs args)
|
||||
{
|
||||
// Handle pull-to-refresh
|
||||
using Deferral deferral = args.GetDeferral();
|
||||
|
||||
try
|
||||
{
|
||||
if(DataContext is MainViewModel viewModel && viewModel.NewsViewModel is not null)
|
||||
await viewModel.NewsViewModel.LoadNews.ExecuteAsync(null);
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
// Swallow to avoid process crash; NewsViewModel already logs errors.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
@@ -149,16 +150,23 @@ public partial class MainViewModel : ObservableObject
|
||||
|
||||
private async Task NavigateTo(string destination)
|
||||
{
|
||||
// TODO: Navigate to the specified destination
|
||||
// These routes will need to be registered in App.xaml.cs RegisterRoutes method
|
||||
// For now, placeholder implementation
|
||||
await Task.CompletedTask;
|
||||
try
|
||||
{
|
||||
// Navigate within the Main region using relative navigation
|
||||
// The "./" prefix means navigate within the current page's region
|
||||
await _navigator.NavigateRouteAsync(this, $"./{destination}");
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
// Navigation error - fail silently for now
|
||||
// TODO: Add error handling/logging
|
||||
}
|
||||
}
|
||||
|
||||
private async Task NavigateToMainAsync()
|
||||
{
|
||||
// Stay on main page
|
||||
await Task.CompletedTask;
|
||||
// Navigate to News page (the default/home page)
|
||||
await NavigateTo("News");
|
||||
}
|
||||
|
||||
private async Task GoToSecondView()
|
||||
|
||||
123
Marechai.App/Presentation/NewsPage.xaml
Normal file
123
Marechai.App/Presentation/NewsPage.xaml
Normal file
@@ -0,0 +1,123 @@
|
||||
<?xml version="1.0"
|
||||
encoding="utf-8"?>
|
||||
|
||||
<Page x:Class="Marechai.App.Presentation.NewsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:utu="using:Uno.Toolkit.UI"
|
||||
NavigationCacheMode="Required"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid utu:SafeArea.Insets="VisibleBounds">
|
||||
<RefreshContainer x:Name="RefreshContainer"
|
||||
RefreshRequested="RefreshContainer_RefreshRequested">
|
||||
<ScrollViewer>
|
||||
<Grid Padding="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- News Title Section -->
|
||||
<StackPanel Grid.Row="0"
|
||||
Margin="0,0,0,16">
|
||||
<TextBlock Text="Latest News"
|
||||
FontSize="32"
|
||||
FontWeight="Bold"
|
||||
Foreground="{ThemeResource SystemAccentColor}"
|
||||
Margin="0,0,0,8" />
|
||||
<TextBlock Text="Stay updated with the latest additions to the database"
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Loading State -->
|
||||
<StackPanel Grid.Row="2"
|
||||
Visibility="{Binding IsLoading}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Padding="32"
|
||||
Spacing="16">
|
||||
<ProgressRing IsActive="True"
|
||||
IsIndeterminate="True"
|
||||
Height="48"
|
||||
Width="48"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
<TextBlock Text="Loading latest news..."
|
||||
FontSize="16"
|
||||
TextAlignment="Center"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Error State -->
|
||||
<StackPanel Grid.Row="2"
|
||||
Visibility="{Binding HasError}"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="16"
|
||||
Padding="32">
|
||||
<InfoBar IsOpen="True"
|
||||
Severity="Error"
|
||||
Title="Unable to Load News"
|
||||
Message="{Binding ErrorMessage}"
|
||||
IsClosable="False" />
|
||||
<Button Content="Retry"
|
||||
Command="{Binding LoadNews}"
|
||||
HorizontalAlignment="Center"
|
||||
Style="{ThemeResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- News Feed -->
|
||||
<ItemsControl Grid.Row="2"
|
||||
ItemsSource="{Binding NewsList}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Margin="0,0,0,12"
|
||||
CornerRadius="8"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
Padding="16">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Date -->
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{Binding News.Timestamp}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
Margin="0,0,0,12" />
|
||||
|
||||
<!-- News Title/Text (Localized) -->
|
||||
<TextBlock Grid.Row="1"
|
||||
Text="{Binding DisplayText}"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseHighColor}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="0,0,0,12" />
|
||||
|
||||
<!-- Item Name Link -->
|
||||
<HyperlinkButton Grid.Row="2"
|
||||
Content="{Binding News.ItemName}"
|
||||
FontSize="14"
|
||||
Padding="0,4,0,4"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="0" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</RefreshContainer>
|
||||
</Grid>
|
||||
</Page>
|
||||
72
Marechai.App/Presentation/NewsPage.xaml.cs
Normal file
72
Marechai.App/Presentation/NewsPage.xaml.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using Windows.Foundation;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation;
|
||||
|
||||
public sealed partial class NewsPage : Page
|
||||
{
|
||||
private bool _initialNewsLoaded;
|
||||
|
||||
public NewsPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContextChanged += NewsPage_DataContextChanged;
|
||||
Loaded += NewsPage_Loaded;
|
||||
}
|
||||
|
||||
private void NewsPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(_initialNewsLoaded) return;
|
||||
|
||||
if(DataContext is NewsViewModel viewModel)
|
||||
{
|
||||
_initialNewsLoaded = true;
|
||||
_ = viewModel.LoadNews.ExecuteAsync(null);
|
||||
DataContextChanged -= NewsPage_DataContextChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void NewsPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
|
||||
{
|
||||
if(_initialNewsLoaded) return;
|
||||
|
||||
if(args.NewValue is NewsViewModel viewModel)
|
||||
{
|
||||
_initialNewsLoaded = true;
|
||||
_ = viewModel.LoadNews.ExecuteAsync(null);
|
||||
DataContextChanged -= NewsPage_DataContextChanged;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
if(_initialNewsLoaded) return;
|
||||
|
||||
if(DataContext is NewsViewModel viewModel)
|
||||
{
|
||||
_initialNewsLoaded = true;
|
||||
_ = viewModel.LoadNews.ExecuteAsync(null);
|
||||
DataContextChanged -= NewsPage_DataContextChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private async void RefreshContainer_RefreshRequested(RefreshContainer sender, RefreshRequestedEventArgs args)
|
||||
{
|
||||
// Handle pull-to-refresh
|
||||
using Deferral deferral = args.GetDeferral();
|
||||
|
||||
try
|
||||
{
|
||||
if(DataContext is NewsViewModel viewModel) await viewModel.LoadNews.ExecuteAsync(null);
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
// Swallow to avoid process crash; NewsViewModel already logs errors.
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user