mirror of
https://github.com/claunia/marechai.git
synced 2025-12-16 19:14:25 +00:00
Compare commits
64 Commits
9847c987b5
...
devel
| Author | SHA1 | Date | |
|---|---|---|---|
|
b06406e20f
|
|||
|
80ba603265
|
|||
|
db7aee369b
|
|||
|
5fe5c94c55
|
|||
|
5f66029528
|
|||
|
869e675ee3
|
|||
|
20216bc1d6
|
|||
|
22bcd4ede4
|
|||
|
7f080c8734
|
|||
|
6e4a4bc67b
|
|||
|
ac460cb050
|
|||
|
c1c6e427c2
|
|||
|
4c273ef661
|
|||
|
9567153378
|
|||
|
c475d0e6a4
|
|||
|
9f89186dde
|
|||
|
733dc59f7b
|
|||
|
5b709755c7
|
|||
|
959a48b36c
|
|||
|
4b02dd6d2c
|
|||
|
f308668f69
|
|||
|
e5f1d766b5
|
|||
|
cc2738e45d
|
|||
|
981cd3c27c
|
|||
|
5c64e59f8f
|
|||
|
01c24ae987
|
|||
|
a60fb39687
|
|||
|
497251be86
|
|||
|
195b23f755
|
|||
|
edc8d33bb2
|
|||
|
dbef655a3d
|
|||
|
4f59f6870d
|
|||
|
c3e75175f9
|
|||
|
1dcb062c35
|
|||
|
6a52c1f067
|
|||
|
c35fdbb0e4
|
|||
|
0368e12974
|
|||
|
fe2c3a082d
|
|||
|
4d30530ef0
|
|||
|
e0689684e1
|
|||
|
cfdef93787
|
|||
|
c6cac9e04a
|
|||
|
e2f86b76db
|
|||
|
80791a8cc9
|
|||
|
d5fbb55425
|
|||
|
7ee042bdec
|
|||
|
e9221ac130
|
|||
|
87291d9dd8
|
|||
|
ce1c089fb0
|
|||
|
5d249f435e
|
|||
|
3e4677b084
|
|||
|
b7c94312fc
|
|||
|
61ebf7b503
|
|||
|
b18396f8d8
|
|||
|
4f1aee302b
|
|||
|
7ede62514f
|
|||
|
955c2f9654
|
|||
|
4a5708b910
|
|||
|
5bffbc342e
|
|||
|
392c69350f
|
|||
|
2bb07845e1
|
|||
|
1053617622
|
|||
|
30b60c0e96
|
|||
|
14596c5499
|
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<TargetFramework Condition="'$(MSBuildProjectName)' != 'Marechai.App'">net10.0</TargetFramework>
|
||||
<Company>Canary Islands Computer Museum</Company>
|
||||
<Copyright>Copyright © 2003-2026 Natalia Portillo</Copyright>
|
||||
<Product>Canary Islands Computer Museum Website</Product>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- Duplicated packages (also in Directory.Build.props) -->
|
||||
<PackageVersion Include="Humanizer" Version="2.14.1"/>
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.11"/>
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.11"/>
|
||||
<!-- Unique to Marechai.csproj -->
|
||||
@@ -32,5 +33,16 @@
|
||||
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0"/>
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.11"/>
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.11"/>
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.2.1"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2"/>
|
||||
<!-- Add more community toolkit references here -->
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'windows'">
|
||||
<PackageVersion Include="Uno.CommunityToolkit.WinUI.UI.Controls" Version="7.1.200"/>
|
||||
<!-- Add more uno community toolkit references here -->
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,19 +1,29 @@
|
||||
<Application x:Class="Marechai.App.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Marechai.App.Presentation.Converters">
|
||||
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!-- Load WinUI resources -->
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<!-- Load Uno.UI.Toolkit resources -->
|
||||
<ToolkitResources xmlns="using:Uno.Toolkit.UI" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!-- Load WinUI resources -->
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
|
||||
<!-- Add resources here -->
|
||||
<!-- Load Uno.UI.Toolkit resources -->
|
||||
<ToolkitResources xmlns="using:Uno.Toolkit.UI" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
<!-- Add resources here -->
|
||||
<local:ObjectToVisibilityConverter x:Key="ObjectToVisibilityConverter" />
|
||||
<local:StringToVisibilityConverter x:Key="StringToVisibilityConverter" />
|
||||
<local:ZeroToVisibilityConverter x:Key="ZeroToVisibilityConverter" />
|
||||
<local:InvertBoolConverter x:Key="InvertBoolConverter" />
|
||||
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<local:InvertBoolToVisibilityConverter x:Key="InvertBoolToVisibilityConverter" />
|
||||
<local:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
||||
<local:RolesListConverter x:Key="RolesListConverter" />
|
||||
|
||||
</Application>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
</Application>
|
||||
@@ -1,88 +1,175 @@
|
||||
using System.Net.Http;
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Marechai.App.Presentation.Views;
|
||||
using Marechai.App.Services;
|
||||
using Marechai.App.Services.Authentication;
|
||||
using Marechai.App.Services.Caching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Uno.Extensions;
|
||||
using Uno.Extensions.Authentication;
|
||||
using Uno.Extensions.Configuration;
|
||||
using Uno.Extensions.Hosting;
|
||||
using Uno.Extensions.Http;
|
||||
using Uno.Extensions.Localization;
|
||||
using Uno.Extensions.Navigation;
|
||||
using Uno.Resizetizer;
|
||||
using Uno.UI;
|
||||
using CompanyDetailViewModel = Marechai.App.Presentation.ViewModels.CompanyDetailViewModel;
|
||||
using ComputersListViewModel = Marechai.App.Presentation.ViewModels.ComputersListViewModel;
|
||||
using ComputersViewModel = Marechai.App.Presentation.ViewModels.ComputersViewModel;
|
||||
using GpuDetailViewModel = Marechai.App.Presentation.ViewModels.GpuDetailViewModel;
|
||||
using GpuListViewModel = Marechai.App.Presentation.ViewModels.GpusListViewModel;
|
||||
using MachineViewViewModel = Marechai.App.Presentation.ViewModels.MachineViewViewModel;
|
||||
using MainViewModel = Marechai.App.Presentation.ViewModels.MainViewModel;
|
||||
using NewsViewModel = Marechai.App.Presentation.ViewModels.NewsViewModel;
|
||||
using PhotoDetailViewModel = Marechai.App.Presentation.ViewModels.PhotoDetailViewModel;
|
||||
using ProcessorDetailViewModel = Marechai.App.Presentation.ViewModels.ProcessorDetailViewModel;
|
||||
using ProcessorsListViewModel = Marechai.App.Presentation.ViewModels.ProcessorsListViewModel;
|
||||
using SettingsViewModel = Marechai.App.Presentation.ViewModels.SettingsViewModel;
|
||||
using SoundSynthDetailViewModel = Marechai.App.Presentation.ViewModels.SoundSynthDetailViewModel;
|
||||
using SoundSynthsListViewModel = Marechai.App.Presentation.ViewModels.SoundSynthsListViewModel;
|
||||
using LoginViewModel = Marechai.App.Presentation.ViewModels.LoginViewModel;
|
||||
|
||||
namespace Marechai.App;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected Window? MainWindow { get; private set; }
|
||||
protected IHost? Host { get; private set; }
|
||||
public IHost? Host { get; private set; }
|
||||
|
||||
protected async override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
var builder = this.CreateBuilder(args)
|
||||
// Add navigation support for toolkit controls such as TabBar and NavigationView
|
||||
.UseToolkitNavigation()
|
||||
.Configure(host => host
|
||||
#if DEBUG
|
||||
// Switch to Development environment when running in DEBUG
|
||||
.UseEnvironment(Environments.Development)
|
||||
#endif
|
||||
.UseLogging(configure: (context, logBuilder) =>
|
||||
{
|
||||
// Configure log levels for different categories of logging
|
||||
logBuilder
|
||||
.SetMinimumLevel(
|
||||
context.HostingEnvironment.IsDevelopment() ? LogLevel.Information : LogLevel.Warning)
|
||||
IApplicationBuilder builder = this.CreateBuilder(args)
|
||||
|
||||
// Default filters for core Uno Platform namespaces
|
||||
.CoreLogLevel(LogLevel.Warning);
|
||||
|
||||
// Uno Platform namespace filter groups
|
||||
// Uncomment individual methods to see more detailed logging
|
||||
//// Generic Xaml events
|
||||
//logBuilder.XamlLogLevel(LogLevel.Debug);
|
||||
//// Layout specific messages
|
||||
//logBuilder.XamlLayoutLogLevel(LogLevel.Debug);
|
||||
//// Storage messages
|
||||
//logBuilder.StorageLogLevel(LogLevel.Debug);
|
||||
//// Binding related messages
|
||||
//logBuilder.XamlBindingLogLevel(LogLevel.Debug);
|
||||
//// Binder memory references tracking
|
||||
//logBuilder.BinderMemoryReferenceLogLevel(LogLevel.Debug);
|
||||
//// DevServer and HotReload related
|
||||
//logBuilder.HotReloadCoreLogLevel(LogLevel.Information);
|
||||
//// Debug JS interop
|
||||
//logBuilder.WebAssemblyLogLevel(LogLevel.Debug);
|
||||
}, enableUnoLogging: true)
|
||||
.UseSerilog(consoleLoggingEnabled: true, fileLoggingEnabled: true)
|
||||
.UseConfiguration(configure: configBuilder =>
|
||||
configBuilder
|
||||
.EmbeddedSource<App>()
|
||||
.Section<AppConfig>()
|
||||
)
|
||||
// Enable localization (see appsettings.json for supported languages)
|
||||
.UseLocalization()
|
||||
.UseHttp((context, services) =>
|
||||
{
|
||||
// Add navigation support for toolkit controls such as TabBar and NavigationView
|
||||
.UseToolkitNavigation()
|
||||
.Configure(host => host
|
||||
#if DEBUG
|
||||
// DelegatingHandler will be automatically injected
|
||||
services.AddTransient<DelegatingHandler, DebugHttpHandler>();
|
||||
|
||||
// Switch to Development environment when running in DEBUG
|
||||
.UseEnvironment(Environments.Development)
|
||||
#endif
|
||||
})
|
||||
.ConfigureServices((context, services) =>
|
||||
{
|
||||
// TODO: Register your services
|
||||
//services.AddSingleton<IMyService, MyService>();
|
||||
})
|
||||
.UseNavigation(RegisterRoutes)
|
||||
);
|
||||
.UseLogging((context, logBuilder) =>
|
||||
{
|
||||
// Configure log levels for different categories of logging
|
||||
logBuilder
|
||||
.SetMinimumLevel(context
|
||||
.HostingEnvironment
|
||||
.IsDevelopment()
|
||||
? LogLevel.Information
|
||||
: LogLevel.Warning)
|
||||
|
||||
// Default filters for core Uno Platform namespaces
|
||||
.CoreLogLevel(LogLevel.Warning);
|
||||
|
||||
// Uno Platform namespace filter groups
|
||||
// Uncomment individual methods to see more detailed logging
|
||||
//// Generic Xaml events
|
||||
//logBuilder.XamlLogLevel(LogLevel.Debug);
|
||||
//// Layout specific messages
|
||||
//logBuilder.XamlLayoutLogLevel(LogLevel.Debug);
|
||||
//// Storage messages
|
||||
//logBuilder.StorageLogLevel(LogLevel.Debug);
|
||||
//// Binding related messages
|
||||
//logBuilder.XamlBindingLogLevel(LogLevel.Debug);
|
||||
//// Binder memory references tracking
|
||||
//logBuilder.BinderMemoryReferenceLogLevel(LogLevel.Debug);
|
||||
//// DevServer and HotReload related
|
||||
//logBuilder.HotReloadCoreLogLevel(LogLevel.Information);
|
||||
//// Debug JS interop
|
||||
//logBuilder.WebAssemblyLogLevel(LogLevel.Debug);
|
||||
},
|
||||
true)
|
||||
.UseSerilog(true, true)
|
||||
.UseConfiguration(configure: configBuilder =>
|
||||
configBuilder.EmbeddedSource<App>()
|
||||
.Section<AppConfig>())
|
||||
|
||||
// Enable localization (see appsettings.json for supported languages)
|
||||
.UseLocalization()
|
||||
.UseHttp((context, services) =>
|
||||
{
|
||||
services.AddTransient<DelegatingHandler,
|
||||
HttpAuthHandler>();
|
||||
#if DEBUG
|
||||
|
||||
// DelegatingHandler will be automatically injected
|
||||
services
|
||||
.AddTransient<DelegatingHandler,
|
||||
DebugHttpHandler>();
|
||||
#endif
|
||||
services.AddKiotaClientV2<ApiClient>(context,
|
||||
new EndpointOptions
|
||||
{
|
||||
Url = context.Configuration
|
||||
.GetSection("ApiClient:Url")
|
||||
.Value ??
|
||||
|
||||
// Fallback to a default URL if not configured
|
||||
"https://localhost:5023"
|
||||
});
|
||||
})
|
||||
.ConfigureServices((context, services) =>
|
||||
{
|
||||
// Register application services
|
||||
services
|
||||
.AddSingleton<IColorThemeService,
|
||||
ColorThemeService>();
|
||||
|
||||
services
|
||||
.AddSingleton<IAuthenticationService,
|
||||
AuthService>();
|
||||
|
||||
services.AddSingleton<ITokenService, TokenService>();
|
||||
services.AddSingleton<IJwtService, JwtService>();
|
||||
services.AddSingleton<FlagCache>();
|
||||
services.AddSingleton<CompanyLogoCache>();
|
||||
services.AddSingleton<MachinePhotoCache>();
|
||||
services.AddSingleton<NewsService>();
|
||||
services.AddSingleton<NewsViewModel>();
|
||||
services.AddSingleton<ComputersService>();
|
||||
services.AddSingleton<ComputersViewModel>();
|
||||
services.AddSingleton<ConsolesService>();
|
||||
services.AddSingleton<ConsolesViewModel>();
|
||||
services.AddSingleton<CompaniesService>();
|
||||
services.AddSingleton<CompaniesViewModel>();
|
||||
services.AddSingleton<CompanyDetailService>();
|
||||
services.AddSingleton<CompanyDetailViewModel>();
|
||||
services.AddSingleton<MachineViewViewModel>();
|
||||
services.AddSingleton<GpusService>();
|
||||
services.AddSingleton<ProcessorsService>();
|
||||
services.AddSingleton<SoundSynthsService>();
|
||||
services.AddTransient<PhotoDetailViewModel>();
|
||||
|
||||
services
|
||||
.AddSingleton<IComputersListFilterContext,
|
||||
ComputersListFilterContext>();
|
||||
|
||||
services
|
||||
.AddSingleton<IConsolesListFilterContext,
|
||||
ConsolesListFilterContext>();
|
||||
|
||||
services.AddTransient<ComputersListViewModel>();
|
||||
services.AddTransient<ConsolesListViewModel>();
|
||||
services.AddTransient<GpuListViewModel>();
|
||||
services.AddTransient<GpuDetailViewModel>();
|
||||
services.AddTransient<ProcessorsListViewModel>();
|
||||
services.AddTransient<ProcessorDetailViewModel>();
|
||||
services.AddTransient<SoundSynthsListViewModel>();
|
||||
services.AddTransient<SoundSynthDetailViewModel>();
|
||||
services.AddTransient<SettingsViewModel>();
|
||||
services.AddTransient<LoginViewModel>();
|
||||
})
|
||||
.UseNavigation(RegisterRoutes));
|
||||
|
||||
MainWindow = builder.Window;
|
||||
|
||||
#if DEBUG
|
||||
@@ -95,20 +182,107 @@ public partial class App : Application
|
||||
|
||||
private static void RegisterRoutes(IViewRegistry views, IRouteRegistry routes)
|
||||
{
|
||||
views.Register(
|
||||
new ViewMap(ViewModel: typeof(ShellViewModel)),
|
||||
new ViewMap<MainPage, MainViewModel>(),
|
||||
new DataViewMap<SecondPage, SecondViewModel, Entity>()
|
||||
);
|
||||
views.Register(new ViewMap(ViewModel: typeof(ShellViewModel)),
|
||||
new ViewMap<MainPage, MainViewModel>(),
|
||||
new ViewMap<LoginPage, LoginViewModel>(),
|
||||
new ViewMap<NewsPage, NewsViewModel>(),
|
||||
new ViewMap<ComputersPage, ComputersViewModel>(),
|
||||
new ViewMap<ComputersListPage, ComputersListViewModel>(),
|
||||
new ViewMap<ConsolesPage, ConsolesViewModel>(),
|
||||
new ViewMap<ConsolesListPage, ConsolesListViewModel>(),
|
||||
new ViewMap<CompaniesPage, CompaniesViewModel>(),
|
||||
new ViewMap<CompanyDetailPage, CompanyDetailViewModel>(),
|
||||
new ViewMap<MachineViewPage, MachineViewViewModel>(),
|
||||
new ViewMap<PhotoDetailPage, PhotoDetailViewModel>(),
|
||||
new ViewMap<GpuListPage, GpuListViewModel>(),
|
||||
new ViewMap<GpuDetailPage, GpuDetailViewModel>(),
|
||||
new ViewMap<ProcessorListPage, ProcessorsListViewModel>(),
|
||||
new ViewMap<ProcessorDetailPage, ProcessorDetailViewModel>(),
|
||||
new ViewMap<SoundSynthListPage, SoundSynthsListViewModel>(),
|
||||
new ViewMap<SoundSynthDetailPage, SoundSynthDetailViewModel>(),
|
||||
new ViewMap<SettingsPage, SettingsViewModel>(),
|
||||
new ViewMap<UsersPage, UsersViewModel>(),
|
||||
new DataViewMap<SecondPage, SecondViewModel, Entity>());
|
||||
|
||||
routes.Register(
|
||||
new RouteMap("", View: views.FindByViewModel<ShellViewModel>(),
|
||||
Nested:
|
||||
[
|
||||
new("Main", View: views.FindByViewModel<MainViewModel>(), IsDefault: true),
|
||||
new("Second", View: views.FindByViewModel<SecondViewModel>()),
|
||||
]
|
||||
)
|
||||
);
|
||||
routes.Register(new RouteMap("",
|
||||
views.FindByViewModel<ShellViewModel>(),
|
||||
Nested:
|
||||
[
|
||||
new RouteMap("Login", views.FindByViewModel<LoginViewModel>()),
|
||||
new RouteMap("Main",
|
||||
views.FindByViewModel<MainViewModel>(),
|
||||
true,
|
||||
Nested:
|
||||
[
|
||||
new RouteMap("News",
|
||||
views.FindByViewModel<NewsViewModel>(),
|
||||
true),
|
||||
new RouteMap("computers",
|
||||
views.FindByViewModel<ComputersViewModel>(),
|
||||
Nested
|
||||
:
|
||||
[
|
||||
new RouteMap("list-computers",
|
||||
views.FindByViewModel<
|
||||
ComputersListViewModel>()),
|
||||
new RouteMap("view",
|
||||
views.FindByViewModel<
|
||||
MachineViewViewModel>())
|
||||
]),
|
||||
new RouteMap("consoles",
|
||||
views.FindByViewModel<ConsolesViewModel>(),
|
||||
Nested
|
||||
:
|
||||
[
|
||||
new RouteMap("list-consoles",
|
||||
views.FindByViewModel<
|
||||
ConsolesListViewModel>())
|
||||
]),
|
||||
new RouteMap("companies",
|
||||
views.FindByViewModel<CompaniesViewModel>(),
|
||||
Nested
|
||||
:
|
||||
[
|
||||
new RouteMap("company-details",
|
||||
views.FindByViewModel<
|
||||
CompanyDetailViewModel>())
|
||||
]),
|
||||
new RouteMap("gpus",
|
||||
views.FindByViewModel<GpuListViewModel>(),
|
||||
Nested
|
||||
:
|
||||
[
|
||||
new RouteMap("gpu-details",
|
||||
views.FindByViewModel<
|
||||
GpuDetailViewModel>())
|
||||
]),
|
||||
new RouteMap("processors",
|
||||
views.FindByViewModel<ProcessorsListViewModel>(),
|
||||
Nested:
|
||||
[
|
||||
new RouteMap("processor-details",
|
||||
views.FindByViewModel<
|
||||
ProcessorDetailViewModel>())
|
||||
]),
|
||||
new RouteMap("sound-synths",
|
||||
views.FindByViewModel<
|
||||
SoundSynthsListViewModel>(),
|
||||
Nested:
|
||||
[
|
||||
new RouteMap("sound-synth-details",
|
||||
views.FindByViewModel<
|
||||
SoundSynthDetailViewModel>()),
|
||||
new RouteMap("machine-view",
|
||||
views.FindByViewModel<
|
||||
MachineViewViewModel>())
|
||||
]),
|
||||
new RouteMap("settings",
|
||||
views.FindByViewModel<SettingsViewModel>()),
|
||||
new RouteMap("users",
|
||||
views.FindByViewModel<UsersViewModel>()),
|
||||
new RouteMap("Second",
|
||||
views.FindByViewModel<SecondViewModel>())
|
||||
])
|
||||
]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,99 @@
|
||||
<Project Sdk="Uno.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net10.0-android;net10.0-browserwasm;net10.0-desktop</TargetFrameworks>
|
||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows')) Or $([MSBuild]::IsOSPlatform('macos'))">$(TargetFrameworks);net10.0-ios</TargetFrameworks>
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net10.0-android;net10.0-browserwasm;net10.0-desktop</TargetFrameworks>
|
||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows')) Or $([MSBuild]::IsOSPlatform('macos'))">$(TargetFrameworks);net10.0-ios</TargetFrameworks>
|
||||
|
||||
<OutputType>Exe</OutputType>
|
||||
<UnoSingleProject>true</UnoSingleProject>
|
||||
<OutputType>Exe</OutputType>
|
||||
<UnoSingleProject>true</UnoSingleProject>
|
||||
|
||||
<!-- Display name -->
|
||||
<ApplicationTitle>Marechai.App</ApplicationTitle>
|
||||
<!-- App Identifier -->
|
||||
<ApplicationId>net.marechai.app</ApplicationId>
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
<!-- Package Publisher -->
|
||||
<ApplicationPublisher>O=Marechai.App</ApplicationPublisher>
|
||||
<!-- Package Description -->
|
||||
<Description>Marechai.App powered by Uno Platform.</Description>
|
||||
<!-- Display name -->
|
||||
<ApplicationTitle>Marechai.App</ApplicationTitle>
|
||||
<!-- App Identifier -->
|
||||
<ApplicationId>net.marechai.app</ApplicationId>
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
<!-- Package Publisher -->
|
||||
<ApplicationPublisher>O=Marechai.App</ApplicationPublisher>
|
||||
<!-- Package Description -->
|
||||
<Description>Marechai.App powered by Uno Platform.</Description>
|
||||
|
||||
<!--
|
||||
UnoFeatures let's you quickly add and manage implicit package references based on the features you want to use.
|
||||
https://aka.platform.uno/singleproject-features
|
||||
-->
|
||||
<UnoFeatures>
|
||||
Lottie;
|
||||
Hosting;
|
||||
Toolkit;
|
||||
Logging;
|
||||
LoggingSerilog;
|
||||
Mvvm;
|
||||
Configuration;
|
||||
HttpKiota;
|
||||
Serialization;
|
||||
Localization;
|
||||
Navigation;
|
||||
ThemeService;
|
||||
Storage;
|
||||
SkiaRenderer;
|
||||
Svg;
|
||||
</UnoFeatures>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Marechai.Data\Marechai.Data.csproj"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Humanizer"/>
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Presentation\Views\Shell.xaml.cs">
|
||||
<DependentUpon>Shell.xaml</DependentUpon>
|
||||
<IsDefaultItem>true</IsDefaultItem>
|
||||
</Compile>
|
||||
<Compile Update="Presentation\Views\SecondPage.xaml.cs">
|
||||
<DependentUpon>SecondPage.xaml</DependentUpon>
|
||||
<IsDefaultItem>true</IsDefaultItem>
|
||||
</Compile>
|
||||
<Compile Update="Presentation\Views\ComputersPage.xaml.cs">
|
||||
<DependentUpon>ComputersPage.xaml</DependentUpon>
|
||||
<IsDefaultItem>true</IsDefaultItem>
|
||||
</Compile>
|
||||
<Compile Update="Presentation\Views\MainPage.xaml.cs">
|
||||
<DependentUpon>MainPage.xaml</DependentUpon>
|
||||
<IsDefaultItem>true</IsDefaultItem>
|
||||
</Compile>
|
||||
<Compile Update="Presentation\Views\ComputersListPage.xaml.cs">
|
||||
<DependentUpon>ComputersListPage.xaml</DependentUpon>
|
||||
<IsDefaultItem>true</IsDefaultItem>
|
||||
</Compile>
|
||||
<Compile Update="Presentation\Views\NewsPage.xaml.cs">
|
||||
<DependentUpon>NewsPage.xaml</DependentUpon>
|
||||
<IsDefaultItem>true</IsDefaultItem>
|
||||
</Compile>
|
||||
<Compile Update="Presentation\Views\MachineViewPage.xaml.cs">
|
||||
<DependentUpon>MachineViewPage.xaml</DependentUpon>
|
||||
<IsDefaultItem>true</IsDefaultItem>
|
||||
</Compile>
|
||||
<Compile Update="Presentation\Components\Sidebar.xaml.cs">
|
||||
<DependentUpon>Sidebar.xaml</DependentUpon>
|
||||
<IsDefaultItem>true</IsDefaultItem>
|
||||
</Compile>
|
||||
<Compile Update="Presentation\Views\UsersPage.xaml.cs">
|
||||
<DependentUpon>UsersPage.xaml</DependentUpon>
|
||||
<IsDefaultItem>true</IsDefaultItem>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">
|
||||
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls"/>
|
||||
<!-- Add more community toolkit references here -->
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'windows'">
|
||||
<PackageReference Include="Uno.CommunityToolkit.WinUI.UI.Controls"/>
|
||||
<!-- Add more uno community toolkit references here -->
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
UnoFeatures let's you quickly add and manage implicit package references based on the features you want to use.
|
||||
https://aka.platform.uno/singleproject-features
|
||||
-->
|
||||
<UnoFeatures>
|
||||
Lottie;
|
||||
Hosting;
|
||||
Toolkit;
|
||||
Logging;
|
||||
LoggingSerilog;
|
||||
Mvvm;
|
||||
Configuration;
|
||||
HttpKiota;
|
||||
Serialization;
|
||||
Localization;
|
||||
Navigation;
|
||||
ThemeService;
|
||||
SkiaRenderer;
|
||||
</UnoFeatures>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
273
Marechai.App/Presentation/Components/Sidebar.xaml
Normal file
273
Marechai.App/Presentation/Components/Sidebar.xaml
Normal file
@@ -0,0 +1,273 @@
|
||||
<?xml version="1.0"
|
||||
encoding="utf-8"?>
|
||||
|
||||
<UserControl x:Class="Marechai.App.Presentation.Components.Sidebar"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="using:Marechai.App.Presentation.Converters"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="600"
|
||||
d:DesignWidth="280"
|
||||
Background="{ThemeResource NavigationViewDefaultPaneBackground}">
|
||||
<UserControl.Resources>
|
||||
<local:CollapseExpandIconConverter x:Key="CollapseExpandIconConverter" />
|
||||
<local:CollapseExpandTooltipConverter x:Key="CollapseExpandTooltipConverter" />
|
||||
</UserControl.Resources>
|
||||
|
||||
<!-- Grid container - naturally responds to parent column width -->
|
||||
<Grid RowSpacing="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Sidebar Header with Collapse/Expand Button -->
|
||||
<Grid Grid.Row="0"
|
||||
Padding="8,8,8,8"
|
||||
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
BorderThickness="0,0,0,1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Sidebar Title - Hidden when collapsed -->
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="Navigation"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
VerticalAlignment="Center"
|
||||
Padding="4,0,0,0"
|
||||
Visibility="{Binding SidebarContentVisible}" />
|
||||
|
||||
<!-- Collapse/Expand Button - Always visible -->
|
||||
<Button Grid.Column="1"
|
||||
Content="{Binding IsSidebarOpen, Converter={StaticResource CollapseExpandIconConverter}}"
|
||||
Command="{Binding ToggleSidebarCommand}"
|
||||
Background="Transparent"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Padding="8,8,8,8"
|
||||
CornerRadius="4"
|
||||
ToolTipService.ToolTip="{Binding IsSidebarOpen, Converter={StaticResource CollapseExpandTooltipConverter}}"
|
||||
FontSize="14"
|
||||
MinWidth="40"
|
||||
MinHeight="40"
|
||||
HorizontalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
<!-- Scrollable Navigation Items - Hidden when collapsed -->
|
||||
<ScrollViewer Grid.Row="1"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
Padding="0"
|
||||
Visibility="{Binding SidebarContentVisible}">
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="0"
|
||||
Padding="0">
|
||||
<!-- News -->
|
||||
<Button Content="{Binding LocalizedStrings[News]}"
|
||||
Command="{Binding NavigateToNewsCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="16,10,16,10"
|
||||
FontSize="13"
|
||||
Background="Transparent"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0" />
|
||||
|
||||
<!-- Books -->
|
||||
<Button Content="{Binding LocalizedStrings[Books]}"
|
||||
Command="{Binding NavigateToBooksCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="16,10,16,10"
|
||||
FontSize="13"
|
||||
Background="Transparent"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0" />
|
||||
|
||||
<!-- Companies -->
|
||||
<Button Content="{Binding LocalizedStrings[Companies]}"
|
||||
Command="{Binding NavigateToCompaniesCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="16,10,16,10"
|
||||
FontSize="13"
|
||||
Background="Transparent"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0" />
|
||||
|
||||
<!-- Computers -->
|
||||
<Button Content="{Binding LocalizedStrings[Computers]}"
|
||||
Command="{Binding NavigateToComputersCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="16,10,16,10"
|
||||
FontSize="13"
|
||||
Background="Transparent"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0" />
|
||||
|
||||
<!-- Consoles -->
|
||||
<Button Content="{Binding LocalizedStrings[Consoles]}"
|
||||
Command="{Binding NavigateToConsolesCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="16,10,16,10"
|
||||
FontSize="13"
|
||||
Background="Transparent"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0" />
|
||||
|
||||
<!-- Documents -->
|
||||
<Button Content="{Binding LocalizedStrings[Documents]}"
|
||||
Command="{Binding NavigateToDocumentsCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="16,10,16,10"
|
||||
FontSize="13"
|
||||
Background="Transparent"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0" />
|
||||
|
||||
<!-- Dumps -->
|
||||
<Button Content="{Binding LocalizedStrings[Dumps]}"
|
||||
Command="{Binding NavigateToDumpsCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="16,10,16,10"
|
||||
FontSize="13"
|
||||
Background="Transparent"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0" />
|
||||
|
||||
<!-- Graphical Processing Units -->
|
||||
<Button Content="{Binding LocalizedStrings[GraphicalProcessingUnits]}"
|
||||
Command="{Binding NavigateToGraphicalProcessingUnitsCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="16,10,16,10"
|
||||
FontSize="13"
|
||||
Background="Transparent"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0" />
|
||||
|
||||
<!-- Magazines -->
|
||||
<Button Content="{Binding LocalizedStrings[Magazines]}"
|
||||
Command="{Binding NavigateToMagazinesCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="16,10,16,10"
|
||||
FontSize="13"
|
||||
Background="Transparent"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0" />
|
||||
|
||||
<!-- People -->
|
||||
<Button Content="{Binding LocalizedStrings[People]}"
|
||||
Command="{Binding NavigateToPeopleCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="16,10,16,10"
|
||||
FontSize="13"
|
||||
Background="Transparent"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0" />
|
||||
|
||||
<!-- Processors -->
|
||||
<Button Content="{Binding LocalizedStrings[Processors]}"
|
||||
Command="{Binding NavigateToProcessorsCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="16,10,16,10"
|
||||
FontSize="13"
|
||||
Background="Transparent"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0" />
|
||||
|
||||
<!-- Software -->
|
||||
<Button Content="{Binding LocalizedStrings[Software]}"
|
||||
Command="{Binding NavigateToSoftwareCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="16,10,16,10"
|
||||
FontSize="13"
|
||||
Background="Transparent"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0" />
|
||||
|
||||
<!-- Sound Synthesizers -->
|
||||
<Button Content="{Binding LocalizedStrings[SoundSynthesizers]}"
|
||||
Command="{Binding NavigateToSoundSynthesizersCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="16,10,16,10"
|
||||
FontSize="13"
|
||||
Background="Transparent"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- Bottom Fixed Items - Hidden when collapsed -->
|
||||
<Grid Grid.Row="2"
|
||||
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
BorderThickness="0,1,0,0"
|
||||
Padding="0"
|
||||
Visibility="{Binding IsSidebarOpen}">
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="0">
|
||||
<!-- Users (Uberadmin only) -->
|
||||
<Button Content="User Management"
|
||||
Command="{Binding NavigateToUsersCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="16,10,16,10"
|
||||
FontSize="13"
|
||||
Background="Transparent"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0"
|
||||
Visibility="{Binding IsUberadminUser, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
|
||||
<!-- Login/Logout -->
|
||||
<Button Content="{Binding LoginLogoutButtonText}"
|
||||
Command="{Binding LoginLogoutCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="16,10,16,10"
|
||||
FontSize="13"
|
||||
Background="Transparent"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0" />
|
||||
|
||||
<!-- Settings -->
|
||||
<Button Content="{Binding LocalizedStrings[Settings]}"
|
||||
Command="{Binding NavigateToSettingsCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="16,10,16,10"
|
||||
FontSize="13"
|
||||
Background="Transparent"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
11
Marechai.App/Presentation/Components/Sidebar.xaml.cs
Normal file
11
Marechai.App/Presentation/Components/Sidebar.xaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Marechai.App.Presentation.Components;
|
||||
|
||||
public sealed partial class Sidebar : UserControl
|
||||
{
|
||||
public Sidebar()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
51
Marechai.App/Presentation/Converters/CompanyConverters.cs
Normal file
51
Marechai.App/Presentation/Converters/CompanyConverters.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace Marechai.App.Presentation.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Converts null object to Collapsed visibility
|
||||
/// </summary>
|
||||
public class ObjectToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language) =>
|
||||
value != null ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts empty/null string to Collapsed visibility
|
||||
/// </summary>
|
||||
public class StringToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if(value is string str && !string.IsNullOrWhiteSpace(str)) return Visibility.Visible;
|
||||
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts zero count to Collapsed visibility, otherwise Visible
|
||||
/// </summary>
|
||||
public class ZeroToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if(value is int count && count > 0) return Visibility.Visible;
|
||||
|
||||
if(value is long longCount && longCount > 0) return Visibility.Visible;
|
||||
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace Marechai.App.Presentation.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Converts DateTime to formatted foundation date string, returns empty if null
|
||||
/// </summary>
|
||||
public class FoundationDateConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if(value is DateTime dateTime) return dateTime.ToString("MMMM d, yyyy");
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
59
Marechai.App/Presentation/Converters/SidebarConverters.cs
Normal file
59
Marechai.App/Presentation/Converters/SidebarConverters.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace Marechai.App.Presentation.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Converts boolean value to collapse/expand arrow icon
|
||||
/// </summary>
|
||||
public class CollapseExpandIconConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if(value is bool isOpen) return isOpen ? "◄" : "►";
|
||||
|
||||
return "►";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts boolean value to collapse/expand tooltip text
|
||||
/// </summary>
|
||||
public class CollapseExpandTooltipConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if(value is bool isOpen) return isOpen ? "Collapse Sidebar" : "Expand Sidebar";
|
||||
|
||||
return "Expand Sidebar";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts boolean value to GridLength for sidebar column width
|
||||
/// </summary>
|
||||
public class SidebarWidthConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if(value is bool isOpen)
|
||||
{
|
||||
// 280 when open, 60 when collapsed (to keep toggle button visible)
|
||||
double width = isOpen ? 280 : 60;
|
||||
|
||||
return new GridLength(width, GridUnitType.Pixel);
|
||||
}
|
||||
|
||||
return new GridLength(280, GridUnitType.Pixel);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<Page x:Class="Marechai.App.Presentation.MainPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Marechai.App.Presentation"
|
||||
xmlns:uen="using:Uno.Extensions.Navigation.UI"
|
||||
xmlns:utu="using:Uno.Toolkit.UI"
|
||||
NavigationCacheMode="Required"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<ScrollViewer IsTabStop="True">
|
||||
<Grid utu:SafeArea.Insets="VisibleBounds">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<utu:NavigationBar Content="{Binding Title}" />
|
||||
|
||||
<StackPanel Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="16">
|
||||
<TextBox Text="{Binding Name, Mode=TwoWay}"
|
||||
PlaceholderText="Enter your name:" />
|
||||
<Button Content="Go to Second Page"
|
||||
AutomationProperties.AutomationId="SecondPageButton"
|
||||
Command="{Binding GoToSecond}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Page>
|
||||
@@ -1,11 +0,0 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Marechai.App.Presentation;
|
||||
|
||||
public sealed partial class MainPage : Page
|
||||
{
|
||||
public MainPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Uno.Extensions.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation;
|
||||
|
||||
public partial class MainViewModel : ObservableObject
|
||||
{
|
||||
private INavigator _navigator;
|
||||
|
||||
[ObservableProperty] private string? name;
|
||||
|
||||
public MainViewModel(
|
||||
IStringLocalizer localizer,
|
||||
IOptions<AppConfig> appInfo,
|
||||
INavigator navigator)
|
||||
{
|
||||
_navigator = navigator;
|
||||
Title = "Main";
|
||||
Title += $" - {localizer["ApplicationName"]}";
|
||||
Title += $" - {appInfo?.Value?.Environment}";
|
||||
GoToSecond = new AsyncRelayCommand(GoToSecondView);
|
||||
}
|
||||
|
||||
public string? Title { get; }
|
||||
|
||||
public ICommand GoToSecond { get; }
|
||||
|
||||
private async Task GoToSecondView()
|
||||
{
|
||||
await _navigator.NavigateViewModelAsync<SecondViewModel>(this, data: new Entity(Name!));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/******************************************************************************
|
||||
// MARECHAI: Master repository of computing history artifacts information
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2003-2026 Natalia Portillo
|
||||
*******************************************************************************/
|
||||
|
||||
namespace Marechai.App.Presentation.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Navigation parameter for the CompanyDetailPage containing both the company ID and the navigation source.
|
||||
/// </summary>
|
||||
public class CompanyDetailNavigationParameter
|
||||
{
|
||||
public required int CompanyId { get; init; }
|
||||
public object? NavigationSource { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/******************************************************************************
|
||||
// MARECHAI: Master repository of computing history artifacts information
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2003-2026 Natalia Portillo
|
||||
*******************************************************************************/
|
||||
|
||||
namespace Marechai.App.Presentation.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Navigation parameter for the GpuDetailPage containing both the GPU ID and the navigation source.
|
||||
/// </summary>
|
||||
public class GpuDetailNavigationParameter
|
||||
{
|
||||
public required int GpuId { get; init; }
|
||||
public object? NavigationSource { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/******************************************************************************
|
||||
// MARECHAI: Master repository of computing history artifacts information
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2003-2026 Natalia Portillo
|
||||
*******************************************************************************/
|
||||
|
||||
namespace Marechai.App.Presentation.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Navigation parameter for the MachineViewPage containing both the machine ID and the navigation source.
|
||||
/// </summary>
|
||||
public class MachineViewNavigationParameter
|
||||
{
|
||||
public required int MachineId { get; init; }
|
||||
public object? NavigationSource { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/******************************************************************************
|
||||
// MARECHAI: Master repository of computing history artifacts information
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2003-2026 Natalia Portillo
|
||||
*******************************************************************************/
|
||||
|
||||
namespace Marechai.App.Presentation.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Navigation parameter for the ProcessorDetailPage containing both the processor ID and the navigation source.
|
||||
/// </summary>
|
||||
public class ProcessorDetailNavigationParameter
|
||||
{
|
||||
public required int ProcessorId { get; init; }
|
||||
public object? NavigationSource { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Marechai.App.Presentation.Models;
|
||||
|
||||
public class SoundSynthDetailNavigationParameter
|
||||
{
|
||||
public int SoundSynthId { get; set; }
|
||||
public object? NavigationSource { get; set; }
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<Page x:Class="Marechai.App.Presentation.SecondPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Marechai.App.Presentation"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:uen="using:Uno.Extensions.Navigation.UI"
|
||||
xmlns:utu="using:Uno.Toolkit.UI"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid utu:SafeArea.Insets="VisibleBounds">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<utu:NavigationBar Content="Second Page" />
|
||||
<StackPanel Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Entity.Name}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Margin="8" />
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -1,5 +0,0 @@
|
||||
namespace Marechai.App.Presentation;
|
||||
|
||||
public partial record SecondViewModel(Entity Entity)
|
||||
{
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<UserControl x:Class="Marechai.App.Presentation.Shell"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Marechai.App.Presentation"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:utu="using:Uno.Toolkit.UI"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="300"
|
||||
d:DesignWidth="400">
|
||||
<Border Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<utu:ExtendedSplashScreen x:Name="Splash"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch">
|
||||
<utu:ExtendedSplashScreen.LoadingContentTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="2*" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ProgressRing IsActive="True"
|
||||
Grid.Row="1"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Height="100"
|
||||
Width="100" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</utu:ExtendedSplashScreen.LoadingContentTemplate>
|
||||
</utu:ExtendedSplashScreen>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -1,15 +0,0 @@
|
||||
using Uno.Extensions.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation;
|
||||
|
||||
public class ShellViewModel
|
||||
{
|
||||
private readonly INavigator _navigator;
|
||||
|
||||
public ShellViewModel(
|
||||
INavigator navigator)
|
||||
{
|
||||
_navigator = navigator;
|
||||
// Add code here to initialize or attach event handlers to singleton services
|
||||
}
|
||||
}
|
||||
220
Marechai.App/Presentation/ViewModels/CompaniesViewModel.cs
Normal file
220
Marechai.App/Presentation/ViewModels/CompaniesViewModel.cs
Normal file
@@ -0,0 +1,220 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Marechai.App.Presentation.Models;
|
||||
using Marechai.App.Services;
|
||||
using Marechai.App.Services.Caching;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Uno.Extensions.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
public partial class CompaniesViewModel : ObservableObject
|
||||
{
|
||||
private readonly List<CompanyListItem> _allCompanies = [];
|
||||
private readonly CompaniesService _companiesService;
|
||||
private readonly IStringLocalizer _localizer;
|
||||
private readonly ILogger<CompaniesViewModel> _logger;
|
||||
private readonly CompanyLogoCache _logoCache;
|
||||
private readonly INavigator _navigator;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<CompanyListItem> _companiesList = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private int _companyCount;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _companyCountText = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _errorMessage = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasError;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isDataLoaded;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isLoading;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _searchQuery = string.Empty;
|
||||
|
||||
public CompaniesViewModel(CompaniesService companiesService, CompanyLogoCache logoCache, IStringLocalizer localizer,
|
||||
ILogger<CompaniesViewModel> logger, INavigator navigator)
|
||||
{
|
||||
_companiesService = companiesService;
|
||||
_logoCache = logoCache;
|
||||
_localizer = localizer;
|
||||
_logger = logger;
|
||||
_navigator = navigator;
|
||||
LoadData = new AsyncRelayCommand(LoadDataAsync);
|
||||
GoBackCommand = new AsyncRelayCommand(GoBackAsync);
|
||||
NavigateToCompanyCommand = new AsyncRelayCommand<CompanyListItem>(NavigateToCompanyAsync);
|
||||
}
|
||||
|
||||
public IAsyncRelayCommand LoadData { get; }
|
||||
public ICommand GoBackCommand { get; }
|
||||
public IAsyncRelayCommand<CompanyListItem> NavigateToCompanyCommand { get; }
|
||||
public string Title { get; } = "Companies";
|
||||
|
||||
partial void OnSearchQueryChanged(string value)
|
||||
{
|
||||
// Automatically filter when SearchQuery changes
|
||||
UpdateFilter(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads companies count and list from the API
|
||||
/// </summary>
|
||||
private async Task LoadDataAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
ErrorMessage = string.Empty;
|
||||
HasError = false;
|
||||
IsDataLoaded = false;
|
||||
CompaniesList.Clear();
|
||||
_allCompanies.Clear();
|
||||
|
||||
// Load companies
|
||||
List<CompanyDto> companies = await _companiesService.GetAllCompaniesAsync();
|
||||
|
||||
// Set count
|
||||
CompanyCount = companies.Count;
|
||||
CompanyCountText = _localizer["Companies in the database"];
|
||||
|
||||
// Build the full list in memory
|
||||
foreach(CompanyDto company in companies)
|
||||
{
|
||||
// Extract id from company
|
||||
int companyId = company.Id ?? 0;
|
||||
|
||||
// Convert DateTimeOffset? to DateTime?
|
||||
DateTime? foundedDate = company.Founded?.DateTime;
|
||||
|
||||
// Load logo if available
|
||||
SvgImageSource? logoSource = null;
|
||||
|
||||
if(company.LastLogo.HasValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
Stream? logoStream = await _logoCache.GetLogoAsync(company.LastLogo.Value);
|
||||
logoSource = new SvgImageSource();
|
||||
await logoSource.SetSourceAsync(logoStream.AsRandomAccessStream());
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogWarning("Failed to load logo for company {CompanyId}: {Exception}",
|
||||
companyId,
|
||||
ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
_allCompanies.Add(new CompanyListItem
|
||||
{
|
||||
Id = companyId,
|
||||
Name = company.Name ?? string.Empty,
|
||||
FoundationDate = foundedDate,
|
||||
LogoImageSource = logoSource
|
||||
});
|
||||
}
|
||||
|
||||
// Apply current filter (will show all if SearchQuery is empty)
|
||||
UpdateFilter(SearchQuery);
|
||||
|
||||
if(CompaniesList.Count == 0)
|
||||
{
|
||||
ErrorMessage = _localizer["No companies found"].Value;
|
||||
HasError = true;
|
||||
}
|
||||
else
|
||||
IsDataLoaded = true;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError("Error loading companies data: {Exception}", ex.Message);
|
||||
ErrorMessage = _localizer["Failed to load companies 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 company detail view
|
||||
/// </summary>
|
||||
private async Task NavigateToCompanyAsync(CompanyListItem? company)
|
||||
{
|
||||
if(company is null) return;
|
||||
|
||||
_logger.LogInformation("Navigating to company: {CompanyName} (ID: {CompanyId})", company.Name, company.Id);
|
||||
|
||||
// Navigate to company detail view with navigation parameter
|
||||
var navParam = new CompanyDetailNavigationParameter
|
||||
{
|
||||
CompanyId = company.Id,
|
||||
NavigationSource = this
|
||||
};
|
||||
|
||||
await _navigator.NavigateViewModelAsync<CompanyDetailViewModel>(this, data: navParam);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the filtered list based on search query
|
||||
/// </summary>
|
||||
private void UpdateFilter(string? query)
|
||||
{
|
||||
string lowerQuery = string.IsNullOrWhiteSpace(query) ? string.Empty : query.Trim().ToLowerInvariant();
|
||||
|
||||
CompaniesList.Clear();
|
||||
|
||||
if(string.IsNullOrEmpty(lowerQuery))
|
||||
{
|
||||
// No filter, show all companies
|
||||
foreach(CompanyListItem company in _allCompanies) CompaniesList.Add(company);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Filter companies by name (case-insensitive)
|
||||
var filtered = _allCompanies.Where(c => c.Name.Contains(lowerQuery, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
foreach(CompanyListItem company in filtered) CompaniesList.Add(company);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data model for a company in the list
|
||||
/// </summary>
|
||||
public class CompanyListItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public DateTime? FoundationDate { get; set; }
|
||||
public SvgImageSource? LogoImageSource { get; set; }
|
||||
|
||||
public string FoundationDateDisplay =>
|
||||
FoundationDate.HasValue ? FoundationDate.Value.ToString("MMMM d, yyyy") : string.Empty;
|
||||
}
|
||||
530
Marechai.App/Presentation/ViewModels/CompanyDetailViewModel.cs
Normal file
530
Marechai.App/Presentation/ViewModels/CompanyDetailViewModel.cs
Normal file
@@ -0,0 +1,530 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Marechai.App.Presentation.Models;
|
||||
using Marechai.App.Services;
|
||||
using Marechai.App.Services.Caching;
|
||||
using Marechai.Data;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Uno.Extensions.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
public partial class CompanyDetailViewModel : ObservableObject
|
||||
{
|
||||
private readonly CompanyDetailService _companyDetailService;
|
||||
private readonly FlagCache _flagCache;
|
||||
private readonly IStringLocalizer _localizer;
|
||||
private readonly ILogger<CompanyDetailViewModel> _logger;
|
||||
private readonly CompanyLogoCache _logoCache;
|
||||
private readonly INavigator _navigator;
|
||||
|
||||
[ObservableProperty]
|
||||
private CompanyDto? _company;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _companyId;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<CompanyLogoItem> _companyLogos = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<CompanyDetailMachine> _computers = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private string _computersFilterText = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _consoelsFilterText = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<CompanyDetailMachine> _consoles = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private string _errorMessage = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<CompanyDetailMachine> _filteredComputers = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<CompanyDetailMachine> _filteredConsoles = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private SvgImageSource? _flagImageSource;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasError;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isDataLoaded;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isLoading;
|
||||
|
||||
[ObservableProperty]
|
||||
private SvgImageSource? _logoImageSource;
|
||||
|
||||
[ObservableProperty]
|
||||
private CompanyDto? _soldToCompany;
|
||||
|
||||
public CompanyDetailViewModel(CompanyDetailService companyDetailService, FlagCache flagCache,
|
||||
CompanyLogoCache logoCache, IStringLocalizer localizer,
|
||||
ILogger<CompanyDetailViewModel> logger, INavigator navigator)
|
||||
{
|
||||
_companyDetailService = companyDetailService;
|
||||
_flagCache = flagCache;
|
||||
_logoCache = logoCache;
|
||||
_localizer = localizer;
|
||||
_logger = logger;
|
||||
_navigator = navigator;
|
||||
LoadData = new AsyncRelayCommand(LoadDataAsync);
|
||||
GoBackCommand = new AsyncRelayCommand(GoBackAsync);
|
||||
NavigateToMachineCommand = new AsyncRelayCommand<CompanyDetailMachine>(NavigateToMachineAsync);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display text for the company's status
|
||||
/// </summary>
|
||||
public string CompanyStatusDisplay => Company != null ? GetStatusMessage(Company) : string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display text for the company's founded date
|
||||
/// </summary>
|
||||
public string CompanyFoundedDateDisplay => Company != null ? GetFoundedDateDisplay(Company) : string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether flag content is available
|
||||
/// </summary>
|
||||
public bool HasFlagContent => FlagImageSource != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether logo content is available
|
||||
/// </summary>
|
||||
public bool HasLogoContent => LogoImageSource != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether company has multiple logos
|
||||
/// </summary>
|
||||
public bool HasMultipleLogos => CompanyLogos.Count > 1;
|
||||
|
||||
public IAsyncRelayCommand LoadData { get; }
|
||||
public ICommand GoBackCommand { get; }
|
||||
public IAsyncRelayCommand<CompanyDetailMachine> NavigateToMachineCommand { get; }
|
||||
public string Title { get; } = "Company Details";
|
||||
|
||||
partial void OnCompanyChanged(CompanyDto? oldValue, CompanyDto? newValue)
|
||||
{
|
||||
// Notify that computed properties have changed
|
||||
OnPropertyChanged(nameof(CompanyStatusDisplay));
|
||||
OnPropertyChanged(nameof(CompanyFoundedDateDisplay));
|
||||
}
|
||||
|
||||
partial void OnFlagImageSourceChanged(SvgImageSource? oldValue, SvgImageSource? newValue)
|
||||
{
|
||||
// Notify that HasFlagContent has changed
|
||||
OnPropertyChanged(nameof(HasFlagContent));
|
||||
}
|
||||
|
||||
partial void OnLogoImageSourceChanged(SvgImageSource? oldValue, SvgImageSource? newValue)
|
||||
{
|
||||
// Notify that HasLogoContent has changed
|
||||
OnPropertyChanged(nameof(HasLogoContent));
|
||||
}
|
||||
|
||||
partial void OnCompanyLogosChanged(ObservableCollection<CompanyLogoItem>? oldValue,
|
||||
ObservableCollection<CompanyLogoItem> newValue)
|
||||
{
|
||||
// Notify that HasMultipleLogos has changed
|
||||
OnPropertyChanged(nameof(HasMultipleLogos));
|
||||
}
|
||||
|
||||
partial void OnComputersFilterTextChanged(string value)
|
||||
{
|
||||
FilterComputers(value);
|
||||
}
|
||||
|
||||
partial void OnConsoelsFilterTextChanged(string value)
|
||||
{
|
||||
FilterConsoles(value);
|
||||
}
|
||||
|
||||
private void FilterComputers(string filterText)
|
||||
{
|
||||
ObservableCollection<CompanyDetailMachine> filtered = string.IsNullOrWhiteSpace(filterText)
|
||||
? new ObservableCollection<
|
||||
CompanyDetailMachine>(Computers)
|
||||
: new
|
||||
ObservableCollection<
|
||||
CompanyDetailMachine>(Computers.Where(c =>
|
||||
c.Name.Contains(filterText,
|
||||
StringComparison
|
||||
.OrdinalIgnoreCase)));
|
||||
|
||||
FilteredComputers = filtered;
|
||||
}
|
||||
|
||||
private void FilterConsoles(string filterText)
|
||||
{
|
||||
ObservableCollection<CompanyDetailMachine> filtered = string.IsNullOrWhiteSpace(filterText)
|
||||
? new ObservableCollection<
|
||||
CompanyDetailMachine>(Consoles)
|
||||
: new
|
||||
ObservableCollection<
|
||||
CompanyDetailMachine>(Consoles.Where(c =>
|
||||
c.Name.Contains(filterText,
|
||||
StringComparison
|
||||
.OrdinalIgnoreCase)));
|
||||
|
||||
FilteredConsoles = filtered;
|
||||
}
|
||||
|
||||
private async Task NavigateToMachineAsync(CompanyDetailMachine? machine)
|
||||
{
|
||||
if(machine == null) return;
|
||||
|
||||
var navParam = new MachineViewNavigationParameter
|
||||
{
|
||||
MachineId = machine.Id,
|
||||
NavigationSource = this
|
||||
};
|
||||
|
||||
await _navigator.NavigateViewModelAsync<MachineViewViewModel>(this, data: navParam);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the formatted founding date with unknown handling
|
||||
/// </summary>
|
||||
public string GetFoundedDateDisplay(CompanyDto company)
|
||||
{
|
||||
if(company.Founded is null) return string.Empty;
|
||||
|
||||
DateTime date = company.Founded.Value.DateTime;
|
||||
|
||||
if(company.FoundedMonthIsUnknown ?? false) return $"{date.Year}.";
|
||||
|
||||
if(company.FoundedDayIsUnknown ?? false) return $"{date:Y}.";
|
||||
|
||||
return $"{date:D}.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the formatted sold/event date with unknown handling
|
||||
/// </summary>
|
||||
public string GetEventDateDisplay(CompanyDto? company, bool monthUnknown = false, bool dayUnknown = false)
|
||||
{
|
||||
if(company?.Sold is null) return _localizer["unknown date"].Value;
|
||||
|
||||
DateTime date = company.Sold.Value.DateTime;
|
||||
|
||||
if(monthUnknown || (company.SoldMonthIsUnknown ?? false)) return $"{date.Year}";
|
||||
|
||||
if(dayUnknown || (company.SoldDayIsUnknown ?? false)) return $"{date:Y}";
|
||||
|
||||
return $"{date:D}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status message for the company
|
||||
/// </summary>
|
||||
public string GetStatusMessage(CompanyDto company)
|
||||
{
|
||||
return company.Status switch
|
||||
{
|
||||
1 => _localizer["Company is active."].Value,
|
||||
2 => GetSoldStatusMessage(company),
|
||||
3 => GetMergedStatusMessage(company),
|
||||
4 => GetBankruptcyMessage(company),
|
||||
5 => GetDefunctMessage(company),
|
||||
6 => GetRenamedStatusMessage(company),
|
||||
_ => _localizer["Current company status is unknown."].Value
|
||||
};
|
||||
}
|
||||
|
||||
private string GetSoldStatusMessage(CompanyDto company)
|
||||
{
|
||||
if(SoldToCompany != null)
|
||||
{
|
||||
return string.Format(_localizer["Company sold to {0} on {1}."].Value,
|
||||
SoldToCompany.Name,
|
||||
GetEventDateDisplay(company));
|
||||
}
|
||||
|
||||
if(company.Sold != null)
|
||||
{
|
||||
return string.Format(_localizer["Company sold on {0} to an unknown company."].Value,
|
||||
GetEventDateDisplay(company));
|
||||
}
|
||||
|
||||
return SoldToCompany != null
|
||||
? string.Format(_localizer["Company sold to {0} on an unknown date."].Value, SoldToCompany.Name)
|
||||
: _localizer["Company was sold to an unknown company on an unknown date."].Value;
|
||||
}
|
||||
|
||||
private string GetMergedStatusMessage(CompanyDto company)
|
||||
{
|
||||
if(SoldToCompany != null)
|
||||
{
|
||||
return string.Format(_localizer["Company merged on {0} to form {1}."].Value,
|
||||
GetEventDateDisplay(company),
|
||||
SoldToCompany.Name);
|
||||
}
|
||||
|
||||
if(company.Sold != null)
|
||||
{
|
||||
return string.Format(_localizer["Company merged on {0} to form an unknown company."].Value,
|
||||
GetEventDateDisplay(company));
|
||||
}
|
||||
|
||||
return SoldToCompany != null
|
||||
? string.Format(_localizer["Company merged on an unknown date to form {0}."].Value,
|
||||
SoldToCompany.Name)
|
||||
: _localizer["Company merged to form an unknown company on an unknown date."].Value;
|
||||
}
|
||||
|
||||
private string GetBankruptcyMessage(CompanyDto company) => company.Sold != null
|
||||
? string.Format(_localizer
|
||||
["Company declared bankruptcy on {0}."]
|
||||
.Value,
|
||||
GetEventDateDisplay(company))
|
||||
: _localizer
|
||||
["Company declared bankruptcy on an unknown date."]
|
||||
.Value;
|
||||
|
||||
private string GetDefunctMessage(CompanyDto company) => company.Sold != null
|
||||
? string.Format(_localizer
|
||||
["Company ceased operations on {0}."]
|
||||
.Value,
|
||||
GetEventDateDisplay(company))
|
||||
: _localizer
|
||||
["Company ceased operations on an unknown date."]
|
||||
.Value;
|
||||
|
||||
private string GetRenamedStatusMessage(CompanyDto company)
|
||||
{
|
||||
if(SoldToCompany != null)
|
||||
{
|
||||
return string.Format(_localizer["Company renamed to {0} on {1}."].Value,
|
||||
SoldToCompany.Name,
|
||||
GetEventDateDisplay(company));
|
||||
}
|
||||
|
||||
if(company.Sold != null)
|
||||
{
|
||||
return string.Format(_localizer["Company was renamed on {0} to an unknown name."].Value,
|
||||
GetEventDateDisplay(company));
|
||||
}
|
||||
|
||||
return SoldToCompany != null
|
||||
? string.Format(_localizer["Company renamed to {0} on an unknown date."].Value, SoldToCompany.Name)
|
||||
: _localizer["Company renamed to an unknown name on an unknown date."].Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads company details from the API
|
||||
/// </summary>
|
||||
private async Task LoadDataAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
ErrorMessage = string.Empty;
|
||||
HasError = false;
|
||||
IsDataLoaded = false;
|
||||
FlagImageSource = null;
|
||||
LogoImageSource = null;
|
||||
CompanyLogos.Clear();
|
||||
|
||||
if(CompanyId <= 0)
|
||||
{
|
||||
ErrorMessage = _localizer["Invalid company ID."].Value;
|
||||
HasError = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Load company details
|
||||
Company = await _companyDetailService.GetCompanyByIdAsync(CompanyId);
|
||||
|
||||
if(Company is null)
|
||||
{
|
||||
ErrorMessage = _localizer["Company not found."].Value;
|
||||
HasError = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Load flag if country is available
|
||||
if(Company.CountryId is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var countryCode = (short)(Company.CountryId ?? 0);
|
||||
Stream? flagStream = await _flagCache.GetFlagAsync(countryCode);
|
||||
|
||||
var flagSource = new SvgImageSource();
|
||||
await flagSource.SetSourceAsync(flagStream.AsRandomAccessStream());
|
||||
FlagImageSource = flagSource;
|
||||
|
||||
_logger.LogInformation("Successfully loaded flag for country code {CountryCode}", countryCode);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError("Failed to load flag for country {CountryId}: {Exception}",
|
||||
Company.CountryId,
|
||||
ex.Message);
|
||||
|
||||
// Continue without flag if loading fails
|
||||
}
|
||||
}
|
||||
|
||||
if(Company.SoldToId != null)
|
||||
{
|
||||
int soldToId = Company.SoldToId ?? 0;
|
||||
if(soldToId > 0) SoldToCompany = await _companyDetailService.GetSoldToCompanyAsync(soldToId);
|
||||
}
|
||||
|
||||
// Load logo if available
|
||||
if(Company.LastLogo.HasValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
Stream? logoStream = await _logoCache.GetLogoAsync(Company.LastLogo.Value);
|
||||
|
||||
var logoSource = new SvgImageSource();
|
||||
await logoSource.SetSourceAsync(logoStream.AsRandomAccessStream());
|
||||
LogoImageSource = logoSource;
|
||||
|
||||
_logger.LogInformation("Successfully loaded logo for company {CompanyId}", CompanyId);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError("Failed to load logo for company {CompanyId}: {Exception}", CompanyId, ex.Message);
|
||||
|
||||
// Continue without logo if loading fails
|
||||
}
|
||||
}
|
||||
|
||||
// Load all logos for carousel
|
||||
try
|
||||
{
|
||||
// Get all logos for this company
|
||||
List<CompanyLogoDto> logosList = await _companyDetailService.GetCompanyLogosAsync(CompanyId);
|
||||
|
||||
// Convert to list with extracted years for sorting
|
||||
var logosWithYears = logosList.Select(logo => new
|
||||
{
|
||||
Logo = logo,
|
||||
logo.Year
|
||||
})
|
||||
.OrderBy(l => l.Year)
|
||||
.ToList();
|
||||
|
||||
var loadedLogos = new ObservableCollection<CompanyLogoItem>();
|
||||
|
||||
foreach(var logoData in logosWithYears)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(logoData.Logo.Guid == null) continue;
|
||||
|
||||
Stream? logoStream = await _logoCache.GetLogoAsync(logoData.Logo.Guid.Value);
|
||||
var logoSource = new SvgImageSource();
|
||||
await logoSource.SetSourceAsync(logoStream.AsRandomAccessStream());
|
||||
|
||||
loadedLogos.Add(new CompanyLogoItem
|
||||
{
|
||||
LogoGuid = logoData.Logo.Guid.Value,
|
||||
LogoSource = logoSource,
|
||||
Year = logoData.Year
|
||||
});
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError("Failed to load carousel logo: {Exception}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
// Assign the new collection (this will trigger OnCompanyLogosChanged)
|
||||
CompanyLogos = loadedLogos;
|
||||
|
||||
_logger.LogInformation("Loaded {Count} logos for company {CompanyId}", CompanyLogos.Count, CompanyId);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError("Failed to load company logos for carousel: {Exception}", ex.Message);
|
||||
}
|
||||
|
||||
// Load computers and consoles made by this company
|
||||
List<MachineDto> machines = await _companyDetailService.GetComputersByCompanyAsync(CompanyId);
|
||||
Computers.Clear();
|
||||
Consoles.Clear();
|
||||
FilteredComputers.Clear();
|
||||
FilteredConsoles.Clear();
|
||||
|
||||
foreach(MachineDto machine in machines)
|
||||
{
|
||||
int machineId = machine.Id ?? 0;
|
||||
|
||||
var machineItem = new CompanyDetailMachine
|
||||
{
|
||||
Id = machineId,
|
||||
Name = machine.Name ?? string.Empty
|
||||
};
|
||||
|
||||
// Categorize by machine type enum
|
||||
if(machine.Type == (int)MachineType.Computer)
|
||||
Computers.Add(machineItem);
|
||||
else if(machine.Type == (int)MachineType.Console) Consoles.Add(machineItem);
|
||||
}
|
||||
|
||||
// Initialize filtered lists
|
||||
FilteredComputers = new ObservableCollection<CompanyDetailMachine>(Computers);
|
||||
FilteredConsoles = new ObservableCollection<CompanyDetailMachine>(Consoles);
|
||||
|
||||
IsDataLoaded = true;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError("Error loading company details: {Exception}", ex.Message);
|
||||
ErrorMessage = _localizer["Failed to load company details. Please try again later."].Value;
|
||||
HasError = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles back navigation
|
||||
/// </summary>
|
||||
private async Task GoBackAsync()
|
||||
{
|
||||
await _navigator.NavigateViewModelAsync<CompaniesViewModel>(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data model for a machine in the company detail view
|
||||
/// </summary>
|
||||
public class CompanyDetailMachine
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data model for a company logo in the carousel
|
||||
/// </summary>
|
||||
public class CompanyLogoItem
|
||||
{
|
||||
public Guid LogoGuid { get; set; }
|
||||
public SvgImageSource? LogoSource { get; set; }
|
||||
public int? Year { get; set; }
|
||||
}
|
||||
250
Marechai.App/Presentation/ViewModels/ComputersListViewModel.cs
Normal file
250
Marechai.App/Presentation/ViewModels/ComputersListViewModel.cs
Normal file
@@ -0,0 +1,250 @@
|
||||
#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.Presentation.Models;
|
||||
using Marechai.App.Services;
|
||||
using Uno.Extensions.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
/// <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 = computer.Id ?? 0;
|
||||
|
||||
_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;
|
||||
}
|
||||
207
Marechai.App/Presentation/ViewModels/ComputersViewModel.cs
Normal file
207
Marechai.App/Presentation/ViewModels/ComputersViewModel.cs
Normal 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 ComputersViewModel : ObservableObject
|
||||
{
|
||||
private readonly ComputersService _computersService;
|
||||
private readonly IComputersListFilterContext _filterContext;
|
||||
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 = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private int _maximumYear;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _minimumYear;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _yearsGridTitle = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<int> _yearsList = [];
|
||||
|
||||
public ComputersViewModel(ComputersService computersService, IStringLocalizer localizer,
|
||||
ILogger<ComputersViewModel> logger, INavigator navigator,
|
||||
IComputersListFilterContext filterContext)
|
||||
{
|
||||
_computersService = computersService;
|
||||
_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);
|
||||
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)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Navigating to computers by letter: {Letter}", letter);
|
||||
_filterContext.FilterType = ComputerListFilterType.Letter;
|
||||
_filterContext.FilterValue = letter.ToString();
|
||||
await _navigator.NavigateRouteAsync(this, "list-computers");
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError("Error navigating to letter computers: {Exception}", ex.Message);
|
||||
ErrorMessage = _localizer["Failed to navigate. Please try again."].Value;
|
||||
HasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to computers filtered by year
|
||||
/// </summary>
|
||||
private async Task NavigateByYearAsync(int year)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Navigating to computers by year: {Year}", year);
|
||||
_filterContext.FilterType = ComputerListFilterType.Year;
|
||||
_filterContext.FilterValue = year.ToString();
|
||||
await _navigator.NavigateRouteAsync(this, "list-computers");
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError("Error navigating to year computers: {Exception}", ex.Message);
|
||||
ErrorMessage = _localizer["Failed to navigate. Please try again."].Value;
|
||||
HasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to all computers view
|
||||
/// </summary>
|
||||
private async Task NavigateAllComputersAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Navigating to all computers");
|
||||
_filterContext.FilterType = ComputerListFilterType.All;
|
||||
_filterContext.FilterValue = string.Empty;
|
||||
await _navigator.NavigateRouteAsync(this, "list-computers");
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError("Error navigating to all computers: {Exception}", ex.Message);
|
||||
ErrorMessage = _localizer["Failed to navigate. Please try again."].Value;
|
||||
HasError = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
248
Marechai.App/Presentation/ViewModels/ConsolesListViewModel.cs
Normal file
248
Marechai.App/Presentation/ViewModels/ConsolesListViewModel.cs
Normal file
@@ -0,0 +1,248 @@
|
||||
#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.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 = console.Id ?? 0;
|
||||
|
||||
_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;
|
||||
}
|
||||
207
Marechai.App/Presentation/ViewModels/ConsolesViewModel.cs
Normal file
207
Marechai.App/Presentation/ViewModels/ConsolesViewModel.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
387
Marechai.App/Presentation/ViewModels/GpuDetailViewModel.cs
Normal file
387
Marechai.App/Presentation/ViewModels/GpuDetailViewModel.cs
Normal file
@@ -0,0 +1,387 @@
|
||||
#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.Presentation.Models;
|
||||
using Marechai.App.Services;
|
||||
using Uno.Extensions.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
public partial class GpuDetailViewModel : ObservableObject
|
||||
{
|
||||
private readonly CompaniesService _companiesService;
|
||||
private readonly GpusService _gpusService;
|
||||
private readonly IStringLocalizer _localizer;
|
||||
private readonly ILogger<GpuDetailViewModel> _logger;
|
||||
private readonly INavigator _navigator;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<MachineItem> _computers = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private string _computersFilterText = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _consoelsFilterText = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<MachineItem> _consoles = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private string _errorMessage = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<MachineItem> _filteredComputers = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<MachineItem> _filteredConsoles = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private GpuDto? _gpu;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _gpuId;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasComputers;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasConsoles;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasError;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isDataLoaded;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isLoading;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _manufacturerName = string.Empty;
|
||||
private object? _navigationSource;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<ResolutionItem> _resolutions = [];
|
||||
|
||||
public GpuDetailViewModel(GpusService gpusService, CompaniesService companiesService, IStringLocalizer localizer,
|
||||
ILogger<GpuDetailViewModel> logger, INavigator navigator)
|
||||
{
|
||||
_gpusService = gpusService;
|
||||
_companiesService = companiesService;
|
||||
_localizer = localizer;
|
||||
_logger = logger;
|
||||
_navigator = navigator;
|
||||
LoadData = new AsyncRelayCommand(LoadDataAsync);
|
||||
GoBackCommand = new AsyncRelayCommand(GoBackAsync);
|
||||
SelectMachineCommand = new AsyncRelayCommand<int>(SelectMachineAsync);
|
||||
ComputersFilterCommand = new RelayCommand(() => FilterComputers());
|
||||
ConsolesFilterCommand = new RelayCommand(() => FilterConsoles());
|
||||
}
|
||||
|
||||
public IAsyncRelayCommand LoadData { get; }
|
||||
public ICommand GoBackCommand { get; }
|
||||
public IAsyncRelayCommand SelectMachineCommand { get; }
|
||||
public ICommand ComputersFilterCommand { get; }
|
||||
public ICommand ConsolesFilterCommand { get; }
|
||||
|
||||
public string Title { get; } = "GPU Details";
|
||||
|
||||
/// <summary>
|
||||
/// Loads GPU details including resolutions, computers, and consoles
|
||||
/// </summary>
|
||||
private async Task LoadDataAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
ErrorMessage = string.Empty;
|
||||
HasError = false;
|
||||
IsDataLoaded = false;
|
||||
Resolutions.Clear();
|
||||
Computers.Clear();
|
||||
Consoles.Clear();
|
||||
|
||||
if(GpuId <= 0)
|
||||
{
|
||||
ErrorMessage = _localizer["Invalid GPU ID"].Value;
|
||||
HasError = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Loading GPU details for ID: {GpuId}", GpuId);
|
||||
|
||||
// Load GPU details
|
||||
Gpu = await _gpusService.GetGpuByIdAsync(GpuId);
|
||||
|
||||
if(Gpu is null)
|
||||
{
|
||||
ErrorMessage = _localizer["Graphics processing unit not found"].Value;
|
||||
HasError = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Set manufacturer name (from Company field or fetch by CompanyId if empty)
|
||||
ManufacturerName = Gpu.Company ?? string.Empty;
|
||||
|
||||
if(string.IsNullOrEmpty(ManufacturerName) && Gpu.CompanyId.HasValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
CompanyDto? company = await _companiesService.GetCompanyByIdAsync(Gpu.CompanyId.Value);
|
||||
if(company != null) ManufacturerName = company.Name ?? string.Empty;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to load company for GPU {GpuId}", GpuId);
|
||||
}
|
||||
}
|
||||
|
||||
// Format display name
|
||||
string displayName = Gpu.Name ?? string.Empty;
|
||||
|
||||
if(displayName == "DB_FRAMEBUFFER")
|
||||
displayName = "Framebuffer";
|
||||
else if(displayName == "DB_SOFTWARE")
|
||||
displayName = "Software";
|
||||
else if(displayName == "DB_NONE") displayName = "None";
|
||||
|
||||
_logger.LogInformation("GPU loaded: {Name}, Company: {Company}", displayName, ManufacturerName);
|
||||
|
||||
// Load resolutions
|
||||
try
|
||||
{
|
||||
List<ResolutionByGpuDto>? resolutions = await _gpusService.GetResolutionsByGpuAsync(GpuId);
|
||||
|
||||
if(resolutions != null && resolutions.Count > 0)
|
||||
{
|
||||
Resolutions.Clear();
|
||||
|
||||
foreach(ResolutionByGpuDto res in resolutions)
|
||||
{
|
||||
// Get the full resolution DTO using the resolution ID
|
||||
if(res.ResolutionId.HasValue)
|
||||
{
|
||||
ResolutionDto? resolutionDto =
|
||||
await _gpusService.GetResolutionByIdAsync(res.ResolutionId.Value);
|
||||
|
||||
if(resolutionDto != null)
|
||||
{
|
||||
Resolutions.Add(new ResolutionItem
|
||||
{
|
||||
Id = resolutionDto.Id ?? 0,
|
||||
Name = $"{resolutionDto.Width}x{resolutionDto.Height}",
|
||||
Width = resolutionDto.Width ?? 0,
|
||||
Height = resolutionDto.Height ?? 0,
|
||||
Colors = resolutionDto.Colors ?? 0,
|
||||
Palette = resolutionDto.Palette ?? 0,
|
||||
Chars = resolutionDto.Chars ?? false,
|
||||
Grayscale = resolutionDto.Grayscale ?? false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("Loaded {Count} resolutions for GPU {GpuId}", Resolutions.Count, GpuId);
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to load resolutions for GPU {GpuId}", GpuId);
|
||||
}
|
||||
|
||||
// Load machines and separate into computers and consoles
|
||||
try
|
||||
{
|
||||
List<MachineDto>? machines = await _gpusService.GetMachinesByGpuAsync(GpuId);
|
||||
|
||||
if(machines != null && machines.Count > 0)
|
||||
{
|
||||
Computers.Clear();
|
||||
Consoles.Clear();
|
||||
|
||||
foreach(MachineDto machine in machines)
|
||||
{
|
||||
var machineItem = new MachineItem
|
||||
{
|
||||
Id = machine.Id ?? 0,
|
||||
Name = machine.Name ?? string.Empty,
|
||||
Manufacturer = machine.Company ?? string.Empty,
|
||||
Year = machine.Introduced?.Year ?? 0
|
||||
};
|
||||
|
||||
// Distinguish between computers and consoles based on Type
|
||||
if(machine.Type == 2) // MachineType.Console
|
||||
Consoles.Add(machineItem);
|
||||
else // MachineType.Computer or Unknown
|
||||
Computers.Add(machineItem);
|
||||
}
|
||||
|
||||
HasComputers = Computers.Count > 0;
|
||||
HasConsoles = Consoles.Count > 0;
|
||||
|
||||
// Initialize filtered collections
|
||||
FilterComputers();
|
||||
FilterConsoles();
|
||||
|
||||
_logger.LogInformation("Loaded {ComputerCount} computers and {ConsoleCount} consoles for GPU {GpuId}",
|
||||
Computers.Count,
|
||||
Consoles.Count,
|
||||
GpuId);
|
||||
}
|
||||
else
|
||||
{
|
||||
HasComputers = false;
|
||||
HasConsoles = false;
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to load machines for GPU {GpuId}", GpuId);
|
||||
HasComputers = false;
|
||||
HasConsoles = false;
|
||||
}
|
||||
|
||||
IsDataLoaded = true;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading GPU details: {Exception}", ex.Message);
|
||||
ErrorMessage = _localizer["Failed to load graphics processing unit details. Please try again later."].Value;
|
||||
HasError = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters computers based on search text
|
||||
/// </summary>
|
||||
private void FilterComputers()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(ComputersFilterText))
|
||||
{
|
||||
FilteredComputers.Clear();
|
||||
foreach(MachineItem computer in Computers) FilteredComputers.Add(computer);
|
||||
}
|
||||
else
|
||||
{
|
||||
var filtered = Computers
|
||||
.Where(c => c.Name.Contains(ComputersFilterText, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
FilteredComputers.Clear();
|
||||
foreach(MachineItem computer in filtered) FilteredComputers.Add(computer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters consoles based on search text
|
||||
/// </summary>
|
||||
private void FilterConsoles()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(ConsoelsFilterText))
|
||||
{
|
||||
FilteredConsoles.Clear();
|
||||
foreach(MachineItem console in Consoles) FilteredConsoles.Add(console);
|
||||
}
|
||||
else
|
||||
{
|
||||
var filtered = Consoles.Where(c => c.Name.Contains(ConsoelsFilterText, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
FilteredConsoles.Clear();
|
||||
foreach(MachineItem console in filtered) FilteredConsoles.Add(console);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates back to the GPU list
|
||||
/// </summary>
|
||||
private async Task GoBackAsync()
|
||||
{
|
||||
// If we came from a machine view, go back to machine view
|
||||
if(_navigationSource is MachineViewViewModel machineVm)
|
||||
{
|
||||
await _navigator.NavigateViewModelAsync<MachineViewViewModel>(this);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Default: go back to GPU list
|
||||
await _navigator.NavigateViewModelAsync<GpusListViewModel>(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to machine detail view
|
||||
/// </summary>
|
||||
private async Task SelectMachineAsync(int machineId)
|
||||
{
|
||||
if(machineId <= 0) return;
|
||||
|
||||
var navParam = new MachineViewNavigationParameter
|
||||
{
|
||||
MachineId = machineId,
|
||||
NavigationSource = this
|
||||
};
|
||||
|
||||
await _navigator.NavigateViewModelAsync<MachineViewViewModel>(this, data: navParam);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the navigation source (where we came from).
|
||||
/// </summary>
|
||||
public void SetNavigationSource(object? source)
|
||||
{
|
||||
_navigationSource = source;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolution item for displaying GPU supported resolutions
|
||||
/// </summary>
|
||||
public class ResolutionItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
public long Colors { get; set; }
|
||||
public long Palette { get; set; }
|
||||
public bool Chars { get; set; }
|
||||
public bool Grayscale { get; set; }
|
||||
|
||||
public string Resolution => $"{Width}x{Height}";
|
||||
|
||||
public string ResolutionType => Chars ? "Text" : "Pixel";
|
||||
|
||||
public string ResolutionDisplay => Chars ? $"{Width}x{Height} characters" : $"{Width}x{Height}";
|
||||
|
||||
public string ColorDisplay => Grayscale
|
||||
? $"{Colors} grays"
|
||||
: Palette > 0
|
||||
? $"{Colors} colors from a palette of {Palette} colors"
|
||||
: $"{Colors} colors";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Machine item for displaying computers or consoles that use the GPU
|
||||
/// </summary>
|
||||
public class MachineItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Manufacturer { get; set; } = string.Empty;
|
||||
public int Year { get; set; }
|
||||
|
||||
public string YearDisplay => Year > 0 ? Year.ToString() : "Unknown";
|
||||
}
|
||||
211
Marechai.App/Presentation/ViewModels/GpusListViewModel.cs
Normal file
211
Marechai.App/Presentation/ViewModels/GpusListViewModel.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using Marechai.App.Presentation.Models;
|
||||
using Marechai.App.Services;
|
||||
using Uno.Extensions.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel for displaying a list of GPUs
|
||||
/// </summary>
|
||||
public partial class GpusListViewModel : ObservableObject
|
||||
{
|
||||
private readonly GpusService _gpusService;
|
||||
private readonly IStringLocalizer _localizer;
|
||||
private readonly ILogger<GpusListViewModel> _logger;
|
||||
private readonly INavigator _navigator;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _errorMessage = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<GpuListItem> _gpusList = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasError;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isDataLoaded;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isLoading;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _pageTitle = string.Empty;
|
||||
|
||||
public GpusListViewModel(GpusService gpusService, IStringLocalizer localizer, ILogger<GpusListViewModel> logger,
|
||||
INavigator navigator)
|
||||
{
|
||||
_gpusService = gpusService;
|
||||
_localizer = localizer;
|
||||
_logger = logger;
|
||||
_navigator = navigator;
|
||||
LoadData = new AsyncRelayCommand(LoadDataAsync);
|
||||
NavigateToGpuCommand = new AsyncRelayCommand<GpuListItem>(NavigateToGpuAsync);
|
||||
}
|
||||
|
||||
public IAsyncRelayCommand LoadData { get; }
|
||||
public IAsyncRelayCommand<GpuListItem> NavigateToGpuCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Loads all GPUs and sorts them with special handling for Framebuffer and Software
|
||||
/// </summary>
|
||||
private async Task LoadDataAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
ErrorMessage = string.Empty;
|
||||
HasError = false;
|
||||
IsDataLoaded = false;
|
||||
GpusList.Clear();
|
||||
|
||||
_logger.LogInformation("LoadDataAsync called for GPUs");
|
||||
|
||||
PageTitle = _localizer["GraphicalProcessingUnits"];
|
||||
|
||||
// Load GPUs from the API
|
||||
await LoadGpusFromApiAsync();
|
||||
|
||||
_logger.LogInformation("LoadGpusFromApiAsync completed. GpusList.Count={Count}", GpusList.Count);
|
||||
|
||||
if(GpusList.Count == 0)
|
||||
{
|
||||
ErrorMessage = _localizer["No graphics processing units found"].Value;
|
||||
HasError = true;
|
||||
|
||||
_logger.LogWarning("No GPUs found");
|
||||
}
|
||||
else
|
||||
IsDataLoaded = true;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading GPUs: {Exception}", ex.Message);
|
||||
ErrorMessage = _localizer["Failed to load graphics processing units. Please try again later."].Value;
|
||||
HasError = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads GPUs from the API and sorts them with special handling for Framebuffer and Software
|
||||
/// </summary>
|
||||
private async Task LoadGpusFromApiAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
List<GpuDto> gpus = await _gpusService.GetAllGpusAsync();
|
||||
|
||||
if(gpus == null || gpus.Count == 0)
|
||||
{
|
||||
_logger.LogInformation("No GPUs returned from API");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Separate special GPUs from regular ones
|
||||
var specialGpus = new List<GpuListItem>();
|
||||
var regularGpus = new List<GpuListItem>();
|
||||
|
||||
foreach(GpuDto gpu in gpus)
|
||||
{
|
||||
string displayName = gpu.Name ?? string.Empty;
|
||||
|
||||
// Replace special database names
|
||||
if(displayName == "DB_FRAMEBUFFER")
|
||||
displayName = "Framebuffer";
|
||||
else if(displayName == "DB_SOFTWARE")
|
||||
displayName = "Software";
|
||||
else if(displayName == "DB_NONE") displayName = "None";
|
||||
|
||||
var gpuItem = new GpuListItem
|
||||
{
|
||||
Id = gpu.Id ?? 0,
|
||||
Name = displayName,
|
||||
Company = gpu.Company ?? string.Empty,
|
||||
IsSpecial = gpu.Name is "DB_FRAMEBUFFER" or "DB_SOFTWARE" or "DB_NONE"
|
||||
};
|
||||
|
||||
if(gpuItem.IsSpecial)
|
||||
specialGpus.Add(gpuItem);
|
||||
else
|
||||
regularGpus.Add(gpuItem);
|
||||
|
||||
_logger.LogInformation("GPU: {Name}, Company: {Company}, ID: {Id}, IsSpecial: {IsSpecial}",
|
||||
displayName,
|
||||
gpu.Company,
|
||||
gpu.Id,
|
||||
gpuItem.IsSpecial);
|
||||
}
|
||||
|
||||
// Sort special GPUs: Framebuffer first, then Software, then None
|
||||
specialGpus.Sort((a, b) =>
|
||||
{
|
||||
int orderA = a.Name == "Framebuffer"
|
||||
? 0
|
||||
: a.Name == "Software"
|
||||
? 1
|
||||
: 2;
|
||||
|
||||
int orderB = b.Name == "Framebuffer"
|
||||
? 0
|
||||
: b.Name == "Software"
|
||||
? 1
|
||||
: 2;
|
||||
|
||||
return orderA.CompareTo(orderB);
|
||||
});
|
||||
|
||||
// Sort regular GPUs alphabetically
|
||||
regularGpus.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// Add special GPUs first, then regular GPUs
|
||||
foreach(GpuListItem gpu in specialGpus) GpusList.Add(gpu);
|
||||
|
||||
foreach(GpuListItem gpu in regularGpus) GpusList.Add(gpu);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading GPUs from API");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to the GPU detail view
|
||||
/// </summary>
|
||||
private async Task NavigateToGpuAsync(GpuListItem? gpu)
|
||||
{
|
||||
if(gpu is null) return;
|
||||
|
||||
_logger.LogInformation("Navigating to GPU detail: {GpuName} (ID: {GpuId})", gpu.Name, gpu.Id);
|
||||
|
||||
// Navigate to GPU detail view with navigation parameter
|
||||
var navParam = new GpuDetailNavigationParameter
|
||||
{
|
||||
GpuId = gpu.Id,
|
||||
NavigationSource = this
|
||||
};
|
||||
|
||||
await _navigator.NavigateViewModelAsync<GpuDetailViewModel>(this, data: navParam);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data model for a GPU in the list
|
||||
/// </summary>
|
||||
public class GpuListItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Company { get; set; } = string.Empty;
|
||||
public bool IsSpecial { get; set; }
|
||||
}
|
||||
93
Marechai.App/Presentation/ViewModels/LoginViewModel.cs
Normal file
93
Marechai.App/Presentation/ViewModels/LoginViewModel.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Uno.Extensions.Authentication;
|
||||
using Uno.Extensions.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
public partial class LoginViewModel : ObservableObject
|
||||
{
|
||||
private readonly IAuthenticationService _authService;
|
||||
private readonly INavigator _navigator;
|
||||
private readonly IStringLocalizer _stringLocalizer;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _email = string.Empty;
|
||||
[ObservableProperty]
|
||||
private string? _errorMessage;
|
||||
[ObservableProperty]
|
||||
private bool _isLoggingIn;
|
||||
[ObservableProperty]
|
||||
private string _password = string.Empty;
|
||||
|
||||
public LoginViewModel(INavigator navigator, IAuthenticationService authService, IStringLocalizer stringLocalizer)
|
||||
{
|
||||
_navigator = navigator;
|
||||
_authService = authService;
|
||||
_stringLocalizer = stringLocalizer;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task LoginAsync()
|
||||
{
|
||||
// Clear previous error
|
||||
ErrorMessage = null;
|
||||
|
||||
// Validate inputs
|
||||
if(string.IsNullOrWhiteSpace(Email))
|
||||
{
|
||||
ErrorMessage = _stringLocalizer["LoginPage.Error.EmailRequired"];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(Password))
|
||||
{
|
||||
ErrorMessage = _stringLocalizer["LoginPage.Error.PasswordRequired"];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
IsLoggingIn = true;
|
||||
|
||||
try
|
||||
{
|
||||
var credentials = new Dictionary<string, string>
|
||||
{
|
||||
["Email"] = Email,
|
||||
["Password"] = Password
|
||||
};
|
||||
|
||||
bool success = await _authService.LoginAsync(null, credentials, null, CancellationToken.None);
|
||||
|
||||
if(success)
|
||||
{
|
||||
// Navigate back to main page on successful login
|
||||
await _navigator.NavigateRouteAsync(this, "/Main");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check if there's an error message in credentials
|
||||
if(credentials.TryGetValue("error", out string? error))
|
||||
ErrorMessage = error;
|
||||
else
|
||||
ErrorMessage = _stringLocalizer["LoginPage.Error.LoginFailed"];
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
ErrorMessage = ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoggingIn = false;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ClearError() => ErrorMessage = null;
|
||||
}
|
||||
524
Marechai.App/Presentation/ViewModels/MachineViewViewModel.cs
Normal file
524
Marechai.App/Presentation/ViewModels/MachineViewViewModel.cs
Normal file
@@ -0,0 +1,524 @@
|
||||
/******************************************************************************
|
||||
// MARECHAI: Master repository of computing history artifacts information
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2003-2026 Natalia Portillo
|
||||
*******************************************************************************/
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage.Streams;
|
||||
using Humanizer;
|
||||
using Marechai.App.Presentation.Models;
|
||||
using Marechai.App.Services;
|
||||
using Marechai.App.Services.Caching;
|
||||
using Marechai.Data;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Uno.Extensions.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
public partial class MachineViewViewModel : ObservableObject
|
||||
{
|
||||
private readonly ComputersService _computersService;
|
||||
private readonly ILogger<MachineViewViewModel> _logger;
|
||||
private readonly INavigator _navigator;
|
||||
private readonly MachinePhotoCache _photoCache;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _companyName = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _errorMessage = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _familyName;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasError;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _introductionDateDisplay;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isDataLoaded;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isLoading;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isPrototype;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _machineName = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _modelName;
|
||||
private object? _navigationSource;
|
||||
|
||||
[ObservableProperty]
|
||||
private Visibility _showFamily = Visibility.Collapsed;
|
||||
|
||||
[ObservableProperty]
|
||||
private Visibility _showFamilyOrModel = Visibility.Collapsed;
|
||||
|
||||
[ObservableProperty]
|
||||
private Visibility _showGpus = Visibility.Collapsed;
|
||||
|
||||
[ObservableProperty]
|
||||
private Visibility _showIntroductionDate = Visibility.Collapsed;
|
||||
|
||||
[ObservableProperty]
|
||||
private Visibility _showMemory = Visibility.Collapsed;
|
||||
|
||||
[ObservableProperty]
|
||||
private Visibility _showModel = Visibility.Collapsed;
|
||||
|
||||
[ObservableProperty]
|
||||
private Visibility _showPhotos = Visibility.Collapsed;
|
||||
|
||||
[ObservableProperty]
|
||||
private Visibility _showProcessors = Visibility.Collapsed;
|
||||
|
||||
[ObservableProperty]
|
||||
private Visibility _showSoundSynthesizers = Visibility.Collapsed;
|
||||
|
||||
[ObservableProperty]
|
||||
private Visibility _showStorage = Visibility.Collapsed;
|
||||
|
||||
public MachineViewViewModel(ILogger<MachineViewViewModel> logger, INavigator navigator,
|
||||
ComputersService computersService, MachinePhotoCache photoCache)
|
||||
{
|
||||
_logger = logger;
|
||||
_navigator = navigator;
|
||||
_computersService = computersService;
|
||||
_photoCache = photoCache;
|
||||
}
|
||||
|
||||
public ObservableCollection<ProcessorDisplayItem> Processors { get; } = [];
|
||||
public ObservableCollection<MemoryDisplayItem> Memory { get; } = [];
|
||||
public ObservableCollection<GpuDisplayItem> Gpus { get; } = [];
|
||||
public ObservableCollection<SoundSynthesizerDisplayItem> SoundSynthesizers { get; } = [];
|
||||
public ObservableCollection<StorageDisplayItem> Storage { get; } = [];
|
||||
public ObservableCollection<PhotoCarouselDisplayItem> Photos { get; } = [];
|
||||
|
||||
[RelayCommand]
|
||||
public async Task GoBack()
|
||||
{
|
||||
// If we came from News, navigate back to News
|
||||
if(_navigationSource is NewsViewModel)
|
||||
{
|
||||
await _navigator.NavigateViewModelAsync<NewsViewModel>(this);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If we came from CompanyDetailViewModel, navigate back to company details
|
||||
if(_navigationSource is CompanyDetailViewModel companyVm)
|
||||
{
|
||||
var navParam = new CompanyDetailNavigationParameter
|
||||
{
|
||||
CompanyId = companyVm.CompanyId
|
||||
};
|
||||
|
||||
await _navigator.NavigateViewModelAsync<CompanyDetailViewModel>(this, data: navParam);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// If we came from GpuDetailViewModel, navigate back to GPU details
|
||||
if(_navigationSource is GpuDetailViewModel gpuDetailVm)
|
||||
{
|
||||
var navParam = new GpuDetailNavigationParameter
|
||||
{
|
||||
GpuId = gpuDetailVm.GpuId,
|
||||
NavigationSource = this
|
||||
};
|
||||
|
||||
await _navigator.NavigateViewModelAsync<GpuDetailViewModel>(this, data: navParam);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If we came from ProcessorDetailViewModel, navigate back to processor details
|
||||
if(_navigationSource is ProcessorDetailViewModel processorDetailVm)
|
||||
{
|
||||
var navParam = new ProcessorDetailNavigationParameter
|
||||
{
|
||||
ProcessorId = processorDetailVm.ProcessorId,
|
||||
NavigationSource = this
|
||||
};
|
||||
|
||||
await _navigator.NavigateViewModelAsync<ProcessorDetailViewModel>(this, data: navParam);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If we came from SoundSynthDetailViewModel, navigate back to sound synth details
|
||||
if(_navigationSource is SoundSynthDetailViewModel soundSynthDetailVm)
|
||||
{
|
||||
var navParam = new SoundSynthDetailNavigationParameter
|
||||
{
|
||||
SoundSynthId = soundSynthDetailVm.SoundSynthId,
|
||||
NavigationSource = this
|
||||
};
|
||||
|
||||
await _navigator.NavigateViewModelAsync<SoundSynthDetailViewModel>(this, data: navParam);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, try to go back in the navigation stack
|
||||
await _navigator.GoBack(this);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task ViewPhotoDetails(Guid photoId)
|
||||
{
|
||||
var navParam = new PhotoDetailNavigationParameter
|
||||
{
|
||||
PhotoId = photoId
|
||||
};
|
||||
|
||||
_logger.LogInformation("Navigating to photo details for {PhotoId}", photoId);
|
||||
await _navigator.NavigateViewModelAsync<PhotoDetailViewModel>(this, data: navParam);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the navigation source (where we came from).
|
||||
/// </summary>
|
||||
public void SetNavigationSource(object? source)
|
||||
{
|
||||
_navigationSource = source;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public Task LoadData()
|
||||
{
|
||||
// Placeholder for retry functionality
|
||||
HasError = false;
|
||||
ErrorMessage = string.Empty;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task LoadMachineAsync(int machineId)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
IsDataLoaded = false;
|
||||
HasError = false;
|
||||
ErrorMessage = string.Empty;
|
||||
Processors.Clear();
|
||||
Memory.Clear();
|
||||
Gpus.Clear();
|
||||
SoundSynthesizers.Clear();
|
||||
Storage.Clear();
|
||||
Photos.Clear();
|
||||
|
||||
_logger.LogInformation("Loading machine {MachineId}", machineId);
|
||||
|
||||
// Fetch machine data from API
|
||||
MachineDto? machine = await _computersService.GetMachineByIdAsync(machineId);
|
||||
|
||||
if(machine is null)
|
||||
{
|
||||
HasError = true;
|
||||
ErrorMessage = "Machine not found";
|
||||
IsLoading = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate basic information
|
||||
MachineName = machine.Name ?? string.Empty;
|
||||
CompanyName = machine.Company ?? string.Empty;
|
||||
FamilyName = machine.FamilyName;
|
||||
ModelName = machine.Model;
|
||||
|
||||
// Check if this is a prototype (year 1000 is used as placeholder for prototypes)
|
||||
IsPrototype = machine.Introduced?.Year == 1000;
|
||||
|
||||
// Set introduction date if available and not a prototype
|
||||
if(machine.Introduced.HasValue && machine.Introduced.Value.Year != 1000)
|
||||
IntroductionDateDisplay = machine.Introduced.Value.ToString("MMMM d, yyyy");
|
||||
|
||||
// Populate processors
|
||||
if(machine.Processors != null)
|
||||
{
|
||||
foreach(ProcessorDto processor in machine.Processors)
|
||||
{
|
||||
var details = new List<string>();
|
||||
var speed = (int)(processor.Speed ?? 0);
|
||||
int gprSize = processor.GprSize ?? 0;
|
||||
int cores = processor.Cores ?? 0;
|
||||
|
||||
if(speed > 0) details.Add($"{speed} MHz");
|
||||
if(gprSize > 0) details.Add($"{gprSize} bits");
|
||||
if(cores > 1) details.Add($"{cores} cores");
|
||||
|
||||
Processors.Add(new ProcessorDisplayItem
|
||||
{
|
||||
DisplayName = processor.Name ?? string.Empty,
|
||||
Manufacturer = processor.Company ?? string.Empty,
|
||||
HasDetails = details.Count > 0,
|
||||
DetailsText = string.Join(", ", details)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Populate memory
|
||||
if(machine.Memory != null)
|
||||
{
|
||||
foreach(MemoryDto mem in machine.Memory)
|
||||
{
|
||||
long size = mem.Size ?? 0;
|
||||
|
||||
string sizeStr = size > 0
|
||||
? size > 1024 ? $"{size} bytes ({size.Bytes().Humanize()})" : $"{size} bytes"
|
||||
: "Unknown";
|
||||
|
||||
// Get humanized memory usage description
|
||||
string usageDescription = mem.Usage.HasValue
|
||||
? ((MemoryUsage)mem.Usage.Value).Humanize()
|
||||
: "Unknown";
|
||||
|
||||
Memory.Add(new MemoryDisplayItem
|
||||
{
|
||||
SizeDisplay = sizeStr,
|
||||
TypeDisplay = usageDescription
|
||||
});
|
||||
}
|
||||
} // Populate GPUs
|
||||
|
||||
if(machine.Gpus != null)
|
||||
{
|
||||
foreach(GpuDto gpu in machine.Gpus)
|
||||
{
|
||||
Gpus.Add(new GpuDisplayItem
|
||||
{
|
||||
DisplayName = gpu.Name ?? string.Empty,
|
||||
Manufacturer = gpu.Company ?? string.Empty,
|
||||
HasManufacturer = !string.IsNullOrEmpty(gpu.Company)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Populate sound synthesizers
|
||||
if(machine.SoundSynthesizers != null)
|
||||
{
|
||||
foreach(SoundSynthDto synth in machine.SoundSynthesizers)
|
||||
{
|
||||
var details = new List<string>();
|
||||
int voices = synth.Voices ?? 0;
|
||||
|
||||
if(voices > 0) details.Add($"{voices} voices");
|
||||
|
||||
SoundSynthesizers.Add(new SoundSynthesizerDisplayItem
|
||||
{
|
||||
DisplayName = synth.Name ?? string.Empty,
|
||||
HasDetails = details.Count > 0,
|
||||
DetailsText = string.Join(", ", details)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Populate storage
|
||||
if(machine.Storage != null)
|
||||
{
|
||||
foreach(StorageDto storage in machine.Storage)
|
||||
{
|
||||
long capacity = storage.Capacity ?? 0;
|
||||
|
||||
string displayText = capacity > 0
|
||||
? capacity > 1024
|
||||
? $"{capacity} bytes ({capacity.Bytes().Humanize()})"
|
||||
: $"{capacity} bytes"
|
||||
: "Storage";
|
||||
|
||||
// Get humanized storage type description
|
||||
string typeNote = storage.Type.HasValue ? ((StorageType)storage.Type.Value).Humanize() : "Unknown";
|
||||
|
||||
Storage.Add(new StorageDisplayItem
|
||||
{
|
||||
DisplayText = displayText,
|
||||
TypeNote = typeNote
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Populate photos
|
||||
List<Guid> photoIds = await _computersService.GetMachinePhotosAsync(machineId);
|
||||
|
||||
if(photoIds.Count > 0)
|
||||
{
|
||||
foreach(Guid photoId in photoIds)
|
||||
{
|
||||
var photoItem = new PhotoCarouselDisplayItem
|
||||
{
|
||||
PhotoId = photoId
|
||||
};
|
||||
|
||||
// Load thumbnail image asynchronously
|
||||
_ = LoadPhotoThumbnailAsync(photoItem);
|
||||
|
||||
Photos.Add(photoItem);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateVisibilities();
|
||||
IsDataLoaded = true;
|
||||
IsLoading = false;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading machine {MachineId}", machineId);
|
||||
HasError = true;
|
||||
ErrorMessage = ex.Message;
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateVisibilities()
|
||||
{
|
||||
ShowIntroductionDate =
|
||||
!string.IsNullOrEmpty(IntroductionDateDisplay) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
ShowFamily = !string.IsNullOrEmpty(FamilyName) ? Visibility.Visible : Visibility.Collapsed;
|
||||
ShowModel = !string.IsNullOrEmpty(ModelName) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
ShowFamilyOrModel = ShowFamily == Visibility.Visible || ShowModel == Visibility.Visible
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
|
||||
ShowProcessors = Processors.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
ShowMemory = Memory.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
ShowGpus = Gpus.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
ShowSoundSynthesizers = SoundSynthesizers.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
ShowStorage = Storage.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
ShowPhotos = Photos.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private async Task LoadPhotoThumbnailAsync(PhotoCarouselDisplayItem photoItem)
|
||||
{
|
||||
try
|
||||
{
|
||||
Stream stream = await _photoCache.GetThumbnailAsync(photoItem.PhotoId);
|
||||
|
||||
var bitmap = new BitmapImage();
|
||||
|
||||
using(IRandomAccessStream randomStream = stream.AsRandomAccessStream())
|
||||
{
|
||||
await bitmap.SetSourceAsync(randomStream);
|
||||
}
|
||||
|
||||
photoItem.ThumbnailImageSource = bitmap;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading photo thumbnail {PhotoId}", photoItem.PhotoId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display item for processor information
|
||||
/// </summary>
|
||||
public class ProcessorDisplayItem
|
||||
{
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
public string Manufacturer { get; set; } = string.Empty;
|
||||
public bool HasDetails { get; set; }
|
||||
public string DetailsText { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display item for memory information
|
||||
/// </summary>
|
||||
public class MemoryDisplayItem
|
||||
{
|
||||
public string SizeDisplay { get; set; } = string.Empty;
|
||||
public string TypeDisplay { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display item for GPU information
|
||||
/// </summary>
|
||||
public class GpuDisplayItem
|
||||
{
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
public string Manufacturer { get; set; } = string.Empty;
|
||||
public bool HasManufacturer { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display item for sound synthesizer information
|
||||
/// </summary>
|
||||
public class SoundSynthesizerDisplayItem
|
||||
{
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
public bool HasDetails { get; set; }
|
||||
public string DetailsText { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display item for storage information
|
||||
/// </summary>
|
||||
public class StorageDisplayItem
|
||||
{
|
||||
public string DisplayText { get; set; } = string.Empty;
|
||||
public string TypeNote { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display item for photo carousel
|
||||
/// </summary>
|
||||
public class PhotoCarouselDisplayItem
|
||||
{
|
||||
// Thumbnail constraints
|
||||
public const int ThumbnailMaxSize = 256;
|
||||
public Guid PhotoId { get; set; }
|
||||
public ImageSource? ThumbnailImageSource { get; set; }
|
||||
}
|
||||
263
Marechai.App/Presentation/ViewModels/MainViewModel.cs
Normal file
263
Marechai.App/Presentation/ViewModels/MainViewModel.cs
Normal file
@@ -0,0 +1,263 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Marechai.App.Services;
|
||||
using Marechai.App.Services.Authentication;
|
||||
using Uno.Extensions.Authentication;
|
||||
using Uno.Extensions.Navigation;
|
||||
using Uno.Extensions.Toolkit;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
public partial class MainViewModel : ObservableObject
|
||||
{
|
||||
private readonly IAuthenticationService _authService;
|
||||
private readonly IJwtService _jwtService;
|
||||
private readonly IStringLocalizer _localizer;
|
||||
private readonly INavigator _navigator;
|
||||
private readonly ITokenService _tokenService;
|
||||
[ObservableProperty]
|
||||
private bool _isSidebarOpen = true;
|
||||
[ObservableProperty]
|
||||
private bool _isUberadminUser;
|
||||
[ObservableProperty]
|
||||
private Dictionary<string, string> _localizedStrings = new();
|
||||
[ObservableProperty]
|
||||
private string _loginLogoutButtonText = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _name;
|
||||
[ObservableProperty]
|
||||
private NewsViewModel? _newsViewModel;
|
||||
[ObservableProperty]
|
||||
private bool _sidebarContentVisible = true;
|
||||
|
||||
public MainViewModel(IStringLocalizer localizer, IOptions<AppConfig> appInfo, INavigator navigator,
|
||||
NewsViewModel newsViewModel, IColorThemeService colorThemeService, IThemeService themeService,
|
||||
IAuthenticationService authService, IJwtService jwtService, ITokenService tokenService)
|
||||
{
|
||||
_navigator = navigator;
|
||||
_localizer = localizer;
|
||||
_authService = authService;
|
||||
_jwtService = jwtService;
|
||||
_tokenService = tokenService;
|
||||
NewsViewModel = newsViewModel;
|
||||
Title = "Marechai";
|
||||
Title += $" - {localizer["ApplicationName"]}";
|
||||
if(appInfo?.Value?.Environment != null) Title += $" - {appInfo.Value.Environment}";
|
||||
|
||||
GoToSecond = new AsyncRelayCommand(GoToSecondView);
|
||||
|
||||
// Initialize color theme service with theme service
|
||||
_ = InitializeThemeServicesAsync(colorThemeService, themeService);
|
||||
|
||||
// Initialize localized strings
|
||||
InitializeLocalizedStrings();
|
||||
|
||||
// Initialize commands
|
||||
NavigateToNewsCommand = new AsyncRelayCommand(NavigateToMainAsync);
|
||||
NavigateToBooksCommand = new AsyncRelayCommand(() => NavigateTo("books"));
|
||||
NavigateToCompaniesCommand = new AsyncRelayCommand(() => NavigateTo("companies"));
|
||||
NavigateToComputersCommand = new AsyncRelayCommand(() => NavigateTo("computers"));
|
||||
NavigateToConsolesCommand = new AsyncRelayCommand(() => NavigateTo("consoles"));
|
||||
NavigateToDocumentsCommand = new AsyncRelayCommand(() => NavigateTo("documents"));
|
||||
NavigateToDumpsCommand = new AsyncRelayCommand(() => NavigateTo("dumps"));
|
||||
NavigateToGraphicalProcessingUnitsCommand = new AsyncRelayCommand(() => NavigateTo("gpus"));
|
||||
NavigateToMagazinesCommand = new AsyncRelayCommand(() => NavigateTo("magazines"));
|
||||
NavigateToPeopleCommand = new AsyncRelayCommand(() => NavigateTo("people"));
|
||||
NavigateToProcessorsCommand = new AsyncRelayCommand(() => NavigateTo("processors"));
|
||||
NavigateToSoftwareCommand = new AsyncRelayCommand(() => NavigateTo("software"));
|
||||
NavigateToSoundSynthesizersCommand = new AsyncRelayCommand(() => NavigateTo("sound-synths"));
|
||||
NavigateToUsersCommand = new AsyncRelayCommand(() => NavigateTo("users"));
|
||||
NavigateToSettingsCommand = new AsyncRelayCommand(() => NavigateTo("settings"));
|
||||
LoginLogoutCommand = new RelayCommand(HandleLoginLogout);
|
||||
ToggleSidebarCommand = new RelayCommand(() => IsSidebarOpen = !IsSidebarOpen);
|
||||
|
||||
// Subscribe to authentication events
|
||||
_authService.LoggedOut += OnLoggedOut;
|
||||
|
||||
UpdateLoginLogoutButtonText();
|
||||
UpdateUberadminStatus();
|
||||
}
|
||||
|
||||
public string? Title { get; }
|
||||
|
||||
public ICommand GoToSecond { get; }
|
||||
|
||||
public ICommand NavigateToNewsCommand { get; }
|
||||
public ICommand NavigateToBooksCommand { get; }
|
||||
public ICommand NavigateToCompaniesCommand { get; }
|
||||
public ICommand NavigateToComputersCommand { get; }
|
||||
public ICommand NavigateToConsolesCommand { get; }
|
||||
public ICommand NavigateToDocumentsCommand { get; }
|
||||
public ICommand NavigateToDumpsCommand { get; }
|
||||
public ICommand NavigateToGraphicalProcessingUnitsCommand { get; }
|
||||
public ICommand NavigateToMagazinesCommand { get; }
|
||||
public ICommand NavigateToPeopleCommand { get; }
|
||||
public ICommand NavigateToProcessorsCommand { get; }
|
||||
public ICommand NavigateToSoftwareCommand { get; }
|
||||
public ICommand NavigateToSoundSynthesizersCommand { get; }
|
||||
public ICommand NavigateToUsersCommand { get; }
|
||||
public ICommand NavigateToSettingsCommand { get; }
|
||||
public ICommand LoginLogoutCommand { get; }
|
||||
public ICommand ToggleSidebarCommand { get; }
|
||||
|
||||
private async Task InitializeThemeServicesAsync(IColorThemeService colorThemeService, IThemeService themeService)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Wait for theme service to be ready
|
||||
await themeService.InitializeAsync();
|
||||
|
||||
// Set the theme service reference and reapply the saved theme
|
||||
colorThemeService.SetThemeService(themeService);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Silently fail - theme will work but without refresh on startup
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeLocalizedStrings()
|
||||
{
|
||||
LocalizedStrings = new Dictionary<string, string>
|
||||
{
|
||||
{
|
||||
"News", _localizer["News"]
|
||||
},
|
||||
{
|
||||
"Books", _localizer["Books"]
|
||||
},
|
||||
{
|
||||
"Companies", _localizer["Companies"]
|
||||
},
|
||||
{
|
||||
"Computers", _localizer["Computers"]
|
||||
},
|
||||
{
|
||||
"Consoles", _localizer["Consoles"]
|
||||
},
|
||||
{
|
||||
"Documents", _localizer["Documents"]
|
||||
},
|
||||
{
|
||||
"Dumps", _localizer["Dumps"]
|
||||
},
|
||||
{
|
||||
"GraphicalProcessingUnits", _localizer["GraphicalProcessingUnits"]
|
||||
},
|
||||
{
|
||||
"Magazines", _localizer["Magazines"]
|
||||
},
|
||||
{
|
||||
"People", _localizer["People"]
|
||||
},
|
||||
{
|
||||
"Processors", _localizer["Processors"]
|
||||
},
|
||||
{
|
||||
"Software", _localizer["Software"]
|
||||
},
|
||||
{
|
||||
"SoundSynthesizers", _localizer["SoundSynthesizers"]
|
||||
},
|
||||
{
|
||||
"Settings", _localizer["Settings"]
|
||||
},
|
||||
{
|
||||
"Login", _localizer["Login"]
|
||||
},
|
||||
{
|
||||
"Logout", _localizer["Logout"]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async void UpdateLoginLogoutButtonText()
|
||||
{
|
||||
bool isAuthenticated = await _authService.IsAuthenticated(CancellationToken.None);
|
||||
LoginLogoutButtonText = isAuthenticated ? LocalizedStrings["Logout"] : LocalizedStrings["Login"];
|
||||
}
|
||||
|
||||
private void UpdateUberadminStatus()
|
||||
{
|
||||
try
|
||||
{
|
||||
string token = _tokenService.GetToken();
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(token))
|
||||
{
|
||||
IEnumerable<string> roles = _jwtService.GetRoles(token);
|
||||
IsUberadminUser = roles.Contains("Uberadmin", StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
else
|
||||
IsUberadminUser = false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
IsUberadminUser = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLoggedOut(object? sender, EventArgs e)
|
||||
{
|
||||
// Update button text when user logs out
|
||||
UpdateLoginLogoutButtonText();
|
||||
UpdateUberadminStatus();
|
||||
}
|
||||
|
||||
public void RefreshAuthenticationState()
|
||||
{
|
||||
// Public method to refresh authentication state (called after login)
|
||||
UpdateLoginLogoutButtonText();
|
||||
UpdateUberadminStatus();
|
||||
}
|
||||
|
||||
private async void HandleLoginLogout()
|
||||
{
|
||||
bool isAuthenticated = await _authService.IsAuthenticated(CancellationToken.None);
|
||||
|
||||
if(isAuthenticated)
|
||||
{
|
||||
// Logout
|
||||
await _authService.LogoutAsync(null, CancellationToken.None);
|
||||
UpdateLoginLogoutButtonText();
|
||||
UpdateUberadminStatus();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Navigate to login page - use absolute path starting from root
|
||||
await _navigator.NavigateRouteAsync(this, "/Login");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task NavigateTo(string destination)
|
||||
{
|
||||
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()
|
||||
{
|
||||
// Navigate to News page (the default/home page)
|
||||
await NavigateTo("News");
|
||||
}
|
||||
|
||||
private async Task GoToSecondView()
|
||||
{
|
||||
// Navigate to Second view model providing qualifier and data
|
||||
await _navigator.NavigateViewModelAsync<SecondViewModel>(this, "Second", new Entity(Name ?? ""));
|
||||
}
|
||||
}
|
||||
175
Marechai.App/Presentation/ViewModels/NewsViewModel.cs
Normal file
175
Marechai.App/Presentation/ViewModels/NewsViewModel.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using Marechai.App.Presentation.Models;
|
||||
using Marechai.App.Services;
|
||||
using Marechai.Data;
|
||||
using Uno.Extensions.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for NewsDto with generated display text
|
||||
/// </summary>
|
||||
public class NewsItemViewModel
|
||||
{
|
||||
public required NewsDto News { get; init; }
|
||||
public required string DisplayText { get; init; }
|
||||
public required IAsyncRelayCommand<NewsDto> NavigateToItemCommand { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if this news item can be navigated to (only computers and consoles)
|
||||
/// </summary>
|
||||
public bool CanNavigateToItem
|
||||
{
|
||||
get
|
||||
{
|
||||
if(News?.Type is null) return false;
|
||||
var type = (NewsType)News.Type.Value;
|
||||
|
||||
return type is NewsType.NewComputerInDb
|
||||
or NewsType.NewConsoleInDb
|
||||
or NewsType.UpdatedComputerInDb
|
||||
or NewsType.UpdatedConsoleInDb
|
||||
or NewsType.NewComputerInCollection
|
||||
or NewsType.NewConsoleInCollection
|
||||
or NewsType.UpdatedComputerInCollection
|
||||
or NewsType.UpdatedConsoleInCollection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class NewsViewModel : ObservableObject
|
||||
{
|
||||
private readonly IStringLocalizer _localizer;
|
||||
private readonly ILogger<NewsViewModel> _logger;
|
||||
private readonly INavigator _navigator;
|
||||
private readonly NewsService _newsService;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _errorMessage = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasError;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isLoading;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<NewsItemViewModel> _newsList = [];
|
||||
|
||||
public NewsViewModel(NewsService newsService, IStringLocalizer localizer, ILogger<NewsViewModel> logger,
|
||||
INavigator navigator)
|
||||
{
|
||||
_newsService = newsService;
|
||||
_localizer = localizer;
|
||||
_logger = logger;
|
||||
_navigator = navigator;
|
||||
LoadNews = new AsyncRelayCommand(LoadNewsAsync);
|
||||
}
|
||||
|
||||
public IAsyncRelayCommand LoadNews { get; }
|
||||
|
||||
[RelayCommand]
|
||||
private async Task NavigateToNewsItem(NewsDto news)
|
||||
{
|
||||
if(news?.Type is null) return;
|
||||
|
||||
var newsType = (NewsType)news.Type.Value;
|
||||
|
||||
// Only navigate for computer and console news items
|
||||
bool isComputerOrConsole = newsType is NewsType.NewComputerInDb
|
||||
or NewsType.NewConsoleInDb
|
||||
or NewsType.UpdatedComputerInDb
|
||||
or NewsType.UpdatedConsoleInDb
|
||||
or NewsType.NewComputerInCollection
|
||||
or NewsType.NewConsoleInCollection
|
||||
or NewsType.UpdatedComputerInCollection
|
||||
or NewsType.UpdatedConsoleInCollection;
|
||||
|
||||
if(!isComputerOrConsole) return;
|
||||
|
||||
// Extract the machine ID from AffectedId
|
||||
if(news.AffectedId is null) return;
|
||||
|
||||
int machineId = news.AffectedId ?? 0;
|
||||
|
||||
if(machineId <= 0) return;
|
||||
|
||||
// Navigate to machine view with source information
|
||||
var navParam = new MachineViewNavigationParameter
|
||||
{
|
||||
MachineId = machineId,
|
||||
NavigationSource = this
|
||||
};
|
||||
|
||||
await _navigator.NavigateViewModelAsync<MachineViewViewModel>(this, data: navParam);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to extract int from UntypedNode
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Generates localized text based on NewsType
|
||||
/// </summary>
|
||||
private string GetLocalizedTextForNewsType(NewsType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
NewsType.NewComputerInDb => _localizer["New computer in database"].Value,
|
||||
NewsType.NewConsoleInDb => _localizer["New console in database"].Value,
|
||||
NewsType.NewComputerInCollection => _localizer["New computer in collection"].Value,
|
||||
NewsType.NewConsoleInCollection => _localizer["New console in collection"].Value,
|
||||
NewsType.UpdatedComputerInDb => _localizer["Updated computer in database"].Value,
|
||||
NewsType.UpdatedConsoleInDb => _localizer["Updated console in database"].Value,
|
||||
NewsType.UpdatedComputerInCollection => _localizer["Updated computer in collection"].Value,
|
||||
NewsType.UpdatedConsoleInCollection => _localizer["Updated console in collection"].Value,
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the latest news from the API
|
||||
/// </summary>
|
||||
private async Task LoadNewsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
ErrorMessage = string.Empty;
|
||||
HasError = false;
|
||||
NewsList.Clear();
|
||||
|
||||
List<NewsDto> news = await _newsService.GetLatestNewsAsync();
|
||||
|
||||
if(news.Count == 0)
|
||||
{
|
||||
ErrorMessage = _localizer["No news available"].Value;
|
||||
HasError = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach(NewsDto item in news)
|
||||
{
|
||||
NewsList.Add(new NewsItemViewModel
|
||||
{
|
||||
News = item,
|
||||
DisplayText = GetLocalizedTextForNewsType((NewsType)(item.Type ?? 0)),
|
||||
NavigateToItemCommand = NavigateToNewsItemCommand
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError("Error loading news: {Exception}", ex.Message);
|
||||
ErrorMessage = _localizer["Failed to load news. Please try again later."].Value;
|
||||
HasError = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
425
Marechai.App/Presentation/ViewModels/PhotoDetailViewModel.cs
Normal file
425
Marechai.App/Presentation/ViewModels/PhotoDetailViewModel.cs
Normal file
@@ -0,0 +1,425 @@
|
||||
/******************************************************************************
|
||||
// MARECHAI: Master repository of computing history artifacts information
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2003-2026 Natalia Portillo
|
||||
*******************************************************************************/
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage.Streams;
|
||||
using Humanizer;
|
||||
using Marechai.App.Services;
|
||||
using Marechai.App.Services.Caching;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Uno.Extensions.Navigation;
|
||||
using ColorSpace = Marechai.Data.ColorSpace;
|
||||
using Contrast = Marechai.Data.Contrast;
|
||||
using ExposureMode = Marechai.Data.ExposureMode;
|
||||
using ExposureProgram = Marechai.Data.ExposureProgram;
|
||||
using Flash = Marechai.Data.Flash;
|
||||
using LightSource = Marechai.Data.LightSource;
|
||||
using MeteringMode = Marechai.Data.MeteringMode;
|
||||
using Orientation = Marechai.Data.Orientation;
|
||||
using ResolutionUnit = Marechai.Data.ResolutionUnit;
|
||||
using Saturation = Marechai.Data.Saturation;
|
||||
using SceneCaptureType = Marechai.Data.SceneCaptureType;
|
||||
using SensingMethod = Marechai.Data.SensingMethod;
|
||||
using Sharpness = Marechai.Data.Sharpness;
|
||||
using SubjectDistanceRange = Marechai.Data.SubjectDistanceRange;
|
||||
using WhiteBalance = Marechai.Data.WhiteBalance;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// Navigation parameter for photo detail page
|
||||
/// </summary>
|
||||
public class PhotoDetailNavigationParameter
|
||||
{
|
||||
public Guid PhotoId { get; set; }
|
||||
}
|
||||
|
||||
public partial class PhotoDetailViewModel : ObservableObject
|
||||
{
|
||||
private readonly ComputersService _computersService;
|
||||
private readonly ILogger<PhotoDetailViewModel> _logger;
|
||||
private readonly INavigator _navigator;
|
||||
private readonly MachinePhotoCache _photoCache;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _errorMessage = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _errorOccurred;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isLoading;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isPortrait = true;
|
||||
|
||||
// EXIF Camera Settings
|
||||
[ObservableProperty]
|
||||
private string _photoAperture = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoAuthor = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoCameraManufacturer = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoCameraModel = string.Empty;
|
||||
|
||||
// Photo Properties
|
||||
[ObservableProperty]
|
||||
private string _photoColorSpace = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoComments = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoContrast = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoCreationDate = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoDigitalZoomRatio = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoExifVersion = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoExposureMode = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoExposureProgram = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoExposureTime = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoFlash = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoFocalLength = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoFocalLengthEquivalent = string.Empty;
|
||||
|
||||
// Resolution and Other
|
||||
[ObservableProperty]
|
||||
private string _photoHorizontalResolution = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private BitmapImage? _photoImageSource;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoIsoRating = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoLensModel = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoLicenseName = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoLightSource = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoMachineCompany = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoMachineName = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoMeteringMode = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoOrientation = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoOriginalExtension = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoResolutionUnit = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoSaturation = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoSceneCaptureType = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoSensingMethod = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoSharpness = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoSoftwareUsed = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoSource = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoSubjectDistanceRange = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoUploadDate = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoVerticalResolution = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _photoWhiteBalance = string.Empty;
|
||||
|
||||
public PhotoDetailViewModel(ILogger<PhotoDetailViewModel> logger, INavigator navigator,
|
||||
ComputersService computersService, MachinePhotoCache photoCache)
|
||||
{
|
||||
_logger = logger;
|
||||
_navigator = navigator;
|
||||
_computersService = computersService;
|
||||
_photoCache = photoCache;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task GoBack()
|
||||
{
|
||||
await _navigator.GoBack(this);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task LoadPhoto(Guid photoId)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
ErrorOccurred = false;
|
||||
ErrorMessage = string.Empty;
|
||||
PhotoImageSource = null;
|
||||
|
||||
_logger.LogInformation("Loading photo details for {PhotoId}", photoId);
|
||||
|
||||
// Fetch photo details from API
|
||||
MachinePhotoDto? photo = await _computersService.GetMachinePhotoDetailsAsync(photoId);
|
||||
|
||||
if(photo is null)
|
||||
{
|
||||
ErrorOccurred = true;
|
||||
ErrorMessage = "Photo not found";
|
||||
IsLoading = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate photo information
|
||||
PhotoAuthor = photo.Author ?? string.Empty;
|
||||
PhotoCameraManufacturer = photo.CameraManufacturer ?? string.Empty;
|
||||
PhotoCameraModel = photo.CameraModel ?? string.Empty;
|
||||
PhotoComments = photo.Comments ?? string.Empty;
|
||||
PhotoLensModel = photo.Lens ?? string.Empty;
|
||||
PhotoLicenseName = photo.LicenseName ?? string.Empty;
|
||||
PhotoMachineCompany = photo.MachineCompanyName ?? string.Empty;
|
||||
PhotoMachineName = photo.MachineName ?? string.Empty;
|
||||
PhotoOriginalExtension = photo.OriginalExtension ?? string.Empty;
|
||||
|
||||
if(photo.CreationDate.HasValue)
|
||||
PhotoCreationDate = photo.CreationDate.Value.ToString("MMMM d, yyyy 'at' HH:mm");
|
||||
|
||||
// EXIF Camera Settings
|
||||
PhotoAperture = photo.Aperture != null ? $"f/{photo.Aperture}" : string.Empty;
|
||||
PhotoExposureTime = photo.Exposure != null ? $"{photo.Exposure}s" : string.Empty;
|
||||
|
||||
// Extract ExposureMode - simple nullable integer now
|
||||
PhotoExposureMode = photo.ExposureMethod.HasValue
|
||||
? ((ExposureMode)photo.ExposureMethod.Value).Humanize()
|
||||
: string.Empty;
|
||||
|
||||
// Extract ExposureProgram - simple nullable integer now
|
||||
PhotoExposureProgram = photo.ExposureProgram.HasValue
|
||||
? ((ExposureProgram)photo.ExposureProgram.Value).Humanize()
|
||||
: string.Empty;
|
||||
|
||||
PhotoFocalLength = photo.FocalLength != null ? $"{photo.FocalLength}mm" : string.Empty;
|
||||
PhotoFocalLengthEquivalent = photo.FocalEquivalent != null ? $"{photo.FocalEquivalent}mm" : string.Empty;
|
||||
PhotoIsoRating = photo.Iso != null ? photo.Iso.ToString() : string.Empty;
|
||||
|
||||
// Extract Flash - simple nullable integer now
|
||||
PhotoFlash = photo.Flash.HasValue ? ((Flash)photo.Flash.Value).Humanize() : string.Empty;
|
||||
|
||||
// Extract LightSource - simple nullable integer now
|
||||
PhotoLightSource = photo.LightSource.HasValue
|
||||
? ((LightSource)photo.LightSource.Value).Humanize()
|
||||
: string.Empty;
|
||||
|
||||
// Extract MeteringMode - simple nullable integer now
|
||||
PhotoMeteringMode = photo.MeteringMode.HasValue
|
||||
? ((MeteringMode)photo.MeteringMode.Value).Humanize()
|
||||
: string.Empty;
|
||||
|
||||
// Extract WhiteBalance - simple nullable integer now
|
||||
PhotoWhiteBalance = photo.WhiteBalance.HasValue
|
||||
? ((WhiteBalance)photo.WhiteBalance.Value).Humanize()
|
||||
: string.Empty;
|
||||
|
||||
// Photo Properties
|
||||
// Extract ColorSpace - simple nullable integer now
|
||||
PhotoColorSpace = photo.Colorspace.HasValue
|
||||
? ((ColorSpace)photo.Colorspace.Value).Humanize()
|
||||
: string.Empty;
|
||||
|
||||
// Extract Contrast - simple nullable integer now
|
||||
PhotoContrast = photo.Contrast.HasValue ? ((Contrast)photo.Contrast.Value).Humanize() : string.Empty;
|
||||
|
||||
// Extract Saturation - simple nullable integer now
|
||||
PhotoSaturation = photo.Saturation.HasValue
|
||||
? ((Saturation)photo.Saturation.Value).Humanize()
|
||||
: string.Empty;
|
||||
|
||||
// Extract Sharpness - simple nullable integer now
|
||||
PhotoSharpness = photo.Sharpness.HasValue ? ((Sharpness)photo.Sharpness.Value).Humanize() : string.Empty;
|
||||
|
||||
// Extract Orientation - simple nullable integer now
|
||||
PhotoOrientation = photo.Orientation.HasValue
|
||||
? ((Orientation)photo.Orientation.Value).Humanize()
|
||||
: string.Empty;
|
||||
|
||||
// Extract SceneCaptureType - simple nullable integer now
|
||||
PhotoSceneCaptureType = photo.SceneCaptureType.HasValue
|
||||
? ((SceneCaptureType)photo.SceneCaptureType.Value).Humanize()
|
||||
: string.Empty;
|
||||
|
||||
// Extract SensingMethod - simple nullable integer now
|
||||
PhotoSensingMethod = photo.SensingMethod.HasValue
|
||||
? ((SensingMethod)photo.SensingMethod.Value).Humanize()
|
||||
: string.Empty;
|
||||
|
||||
// Extract SubjectDistanceRange - simple nullable integer now
|
||||
PhotoSubjectDistanceRange = photo.SubjectDistanceRange.HasValue
|
||||
? ((SubjectDistanceRange)photo.SubjectDistanceRange.Value).Humanize()
|
||||
: string.Empty;
|
||||
|
||||
// Resolution and Other
|
||||
PhotoHorizontalResolution =
|
||||
photo.HorizontalResolution != null ? $"{photo.HorizontalResolution} DPI" : string.Empty;
|
||||
|
||||
PhotoVerticalResolution =
|
||||
photo.VerticalResolution != null ? $"{photo.VerticalResolution} DPI" : string.Empty;
|
||||
|
||||
// Extract ResolutionUnit - simple nullable integer now
|
||||
PhotoResolutionUnit = photo.ResolutionUnit.HasValue
|
||||
? ((ResolutionUnit)photo.ResolutionUnit.Value).Humanize()
|
||||
: string.Empty;
|
||||
|
||||
PhotoDigitalZoomRatio = photo.DigitalZoom != null ? $"{photo.DigitalZoom}x" : string.Empty;
|
||||
PhotoExifVersion = photo.ExifVersion ?? string.Empty;
|
||||
PhotoSoftwareUsed = photo.Software ?? string.Empty;
|
||||
|
||||
PhotoUploadDate = photo.UploadDate.HasValue
|
||||
? photo.UploadDate.Value.ToString("MMMM d, yyyy 'at' HH:mm")
|
||||
: string.Empty;
|
||||
|
||||
PhotoSource = photo.Source ?? string.Empty;
|
||||
|
||||
// Load the full photo image
|
||||
await LoadPhotoImageAsync(photoId);
|
||||
|
||||
IsLoading = false;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading photo details for {PhotoId}", photoId);
|
||||
ErrorOccurred = true;
|
||||
ErrorMessage = ex.Message;
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the portrait/landscape orientation flag
|
||||
/// </summary>
|
||||
public void UpdateOrientation(bool isPortrait)
|
||||
{
|
||||
IsPortrait = isPortrait;
|
||||
}
|
||||
|
||||
private async Task LoadPhotoImageAsync(Guid photoId)
|
||||
{
|
||||
try
|
||||
{
|
||||
Stream stream = await _photoCache.GetPhotoAsync(photoId);
|
||||
|
||||
var bitmap = new BitmapImage();
|
||||
|
||||
using(IRandomAccessStream randomStream = stream.AsRandomAccessStream())
|
||||
{
|
||||
await bitmap.SetSourceAsync(randomStream);
|
||||
}
|
||||
|
||||
PhotoImageSource = bitmap;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading photo image {PhotoId}", photoId);
|
||||
ErrorOccurred = true;
|
||||
ErrorMessage = "Failed to load photo image";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts an integer value from AdditionalData dictionary
|
||||
/// </summary>
|
||||
private int ExtractInt(IDictionary<string, object> additionalData)
|
||||
{
|
||||
if(additionalData == null || additionalData.Count == 0) return 0;
|
||||
|
||||
object? value = additionalData.Values.FirstOrDefault();
|
||||
|
||||
if(value is int intValue) return intValue;
|
||||
if(value is double dblValue) return (int)dblValue;
|
||||
if(int.TryParse(value?.ToString() ?? "", out int parsed)) return parsed;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Humanizes an enum value extracted from AdditionalData
|
||||
/// </summary>
|
||||
private string ExtractAndHumanizeEnum(IDictionary<string, object>? additionalData, Type enumType)
|
||||
{
|
||||
if(additionalData == null || additionalData.Count == 0) return string.Empty;
|
||||
|
||||
int intValue = ExtractInt(additionalData);
|
||||
|
||||
if(intValue == 0 && enumType != typeof(ExposureMode)) return string.Empty;
|
||||
|
||||
var enumValue = Enum.ToObject(enumType, intValue);
|
||||
|
||||
return ((Enum)enumValue).Humanize();
|
||||
}
|
||||
}
|
||||
294
Marechai.App/Presentation/ViewModels/ProcessorDetailViewModel.cs
Normal file
294
Marechai.App/Presentation/ViewModels/ProcessorDetailViewModel.cs
Normal file
@@ -0,0 +1,294 @@
|
||||
#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.Presentation.Models;
|
||||
using Marechai.App.Services;
|
||||
using Uno.Extensions.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
public partial class ProcessorDetailViewModel : ObservableObject
|
||||
{
|
||||
private readonly CompaniesService _companiesService;
|
||||
private readonly IStringLocalizer _localizer;
|
||||
private readonly ILogger<ProcessorDetailViewModel> _logger;
|
||||
private readonly INavigator _navigator;
|
||||
private readonly ProcessorsService _processorsService;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<MachineItem> _computers = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private string _computersFilterText = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _consoelsFilterText = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<MachineItem> _consoles = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private string _errorMessage = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<MachineItem> _filteredComputers = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<MachineItem> _filteredConsoles = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasComputers;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasConsoles;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasError;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isDataLoaded;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isLoading;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _manufacturerName = string.Empty;
|
||||
|
||||
private object? _navigationSource;
|
||||
|
||||
[ObservableProperty]
|
||||
private ProcessorDto? _processor;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _processorId;
|
||||
|
||||
public ProcessorDetailViewModel(ProcessorsService processorsService, CompaniesService companiesService,
|
||||
IStringLocalizer localizer, ILogger<ProcessorDetailViewModel> logger,
|
||||
INavigator navigator)
|
||||
{
|
||||
_processorsService = processorsService;
|
||||
_companiesService = companiesService;
|
||||
_localizer = localizer;
|
||||
_logger = logger;
|
||||
_navigator = navigator;
|
||||
LoadData = new AsyncRelayCommand(LoadDataAsync);
|
||||
GoBackCommand = new AsyncRelayCommand(GoBackAsync);
|
||||
SelectMachineCommand = new AsyncRelayCommand<int>(SelectMachineAsync);
|
||||
ComputersFilterCommand = new RelayCommand(() => FilterComputers());
|
||||
ConsolesFilterCommand = new RelayCommand(() => FilterConsoles());
|
||||
}
|
||||
|
||||
public IAsyncRelayCommand LoadData { get; }
|
||||
public ICommand GoBackCommand { get; }
|
||||
public IAsyncRelayCommand SelectMachineCommand { get; }
|
||||
public ICommand ComputersFilterCommand { get; }
|
||||
public ICommand ConsolesFilterCommand { get; }
|
||||
|
||||
public string Title { get; } = "Processor Details";
|
||||
|
||||
/// <summary>
|
||||
/// Loads Processor details
|
||||
/// </summary>
|
||||
private async Task LoadDataAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
ErrorMessage = string.Empty;
|
||||
HasError = false;
|
||||
IsDataLoaded = false;
|
||||
Computers.Clear();
|
||||
Consoles.Clear();
|
||||
|
||||
if(ProcessorId <= 0)
|
||||
{
|
||||
ErrorMessage = _localizer["Invalid Processor ID"].Value;
|
||||
HasError = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Loading Processor details for ID: {ProcessorId}", ProcessorId);
|
||||
|
||||
// Load Processor details
|
||||
Processor = await _processorsService.GetProcessorByIdAsync(ProcessorId);
|
||||
|
||||
if(Processor is null)
|
||||
{
|
||||
ErrorMessage = _localizer["Processor not found"].Value;
|
||||
HasError = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Set manufacturer name (from Company field or fetch by CompanyId if empty)
|
||||
ManufacturerName = Processor.Company ?? string.Empty;
|
||||
|
||||
if(string.IsNullOrEmpty(ManufacturerName) && Processor.CompanyId.HasValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
CompanyDto? company = await _companiesService.GetCompanyByIdAsync(Processor.CompanyId.Value);
|
||||
if(company != null) ManufacturerName = company.Name ?? string.Empty;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to load company for Processor {ProcessorId}", ProcessorId);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("Processor loaded: {Name}, Company: {Company}", Processor.Name, ManufacturerName);
|
||||
|
||||
// Load machines and separate into computers and consoles
|
||||
try
|
||||
{
|
||||
List<MachineDto>? machines = await _processorsService.GetMachinesByProcessorAsync(ProcessorId);
|
||||
|
||||
if(machines != null && machines.Count > 0)
|
||||
{
|
||||
Computers.Clear();
|
||||
Consoles.Clear();
|
||||
|
||||
foreach(MachineDto machine in machines)
|
||||
{
|
||||
var machineItem = new MachineItem
|
||||
{
|
||||
Id = machine.Id ?? 0,
|
||||
Name = machine.Name ?? string.Empty,
|
||||
Manufacturer = machine.Company ?? string.Empty,
|
||||
Year = machine.Introduced?.Year ?? 0
|
||||
};
|
||||
|
||||
// Distinguish between computers and consoles based on Type
|
||||
if(machine.Type == 2) // MachineType.Console
|
||||
Consoles.Add(machineItem);
|
||||
else // MachineType.Computer or Unknown
|
||||
Computers.Add(machineItem);
|
||||
}
|
||||
|
||||
HasComputers = Computers.Count > 0;
|
||||
HasConsoles = Consoles.Count > 0;
|
||||
|
||||
// Initialize filtered collections
|
||||
FilterComputers();
|
||||
FilterConsoles();
|
||||
|
||||
_logger.LogInformation("Loaded {ComputerCount} computers and {ConsoleCount} consoles for Processor {ProcessorId}",
|
||||
Computers.Count,
|
||||
Consoles.Count,
|
||||
ProcessorId);
|
||||
}
|
||||
else
|
||||
{
|
||||
HasComputers = false;
|
||||
HasConsoles = false;
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to load machines for Processor {ProcessorId}", ProcessorId);
|
||||
HasComputers = false;
|
||||
HasConsoles = false;
|
||||
}
|
||||
|
||||
IsDataLoaded = true;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading Processor details: {Exception}", ex.Message);
|
||||
ErrorMessage = _localizer["Failed to load processor details. Please try again later."].Value;
|
||||
HasError = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates back to the Processor list
|
||||
/// </summary>
|
||||
private async Task GoBackAsync()
|
||||
{
|
||||
// If we came from a machine view, go back to machine view
|
||||
if(_navigationSource is MachineViewViewModel machineVm)
|
||||
{
|
||||
await _navigator.NavigateViewModelAsync<MachineViewViewModel>(this);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Default: go back to Processor list
|
||||
await _navigator.NavigateViewModelAsync<ProcessorsListViewModel>(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters computers based on search text
|
||||
/// </summary>
|
||||
private void FilterComputers()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(ComputersFilterText))
|
||||
{
|
||||
FilteredComputers.Clear();
|
||||
foreach(MachineItem computer in Computers) FilteredComputers.Add(computer);
|
||||
}
|
||||
else
|
||||
{
|
||||
var filtered = Computers
|
||||
.Where(c => c.Name.Contains(ComputersFilterText, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
FilteredComputers.Clear();
|
||||
foreach(MachineItem computer in filtered) FilteredComputers.Add(computer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters consoles based on search text
|
||||
/// </summary>
|
||||
private void FilterConsoles()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(ConsoelsFilterText))
|
||||
{
|
||||
FilteredConsoles.Clear();
|
||||
foreach(MachineItem console in Consoles) FilteredConsoles.Add(console);
|
||||
}
|
||||
else
|
||||
{
|
||||
var filtered = Consoles.Where(c => c.Name.Contains(ConsoelsFilterText, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
FilteredConsoles.Clear();
|
||||
foreach(MachineItem console in filtered) FilteredConsoles.Add(console);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to machine detail view
|
||||
/// </summary>
|
||||
private async Task SelectMachineAsync(int machineId)
|
||||
{
|
||||
if(machineId <= 0) return;
|
||||
|
||||
var navParam = new MachineViewNavigationParameter
|
||||
{
|
||||
MachineId = machineId,
|
||||
NavigationSource = this
|
||||
};
|
||||
|
||||
await _navigator.NavigateViewModelAsync<MachineViewViewModel>(this, data: navParam);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the navigation source (where we came from).
|
||||
/// </summary>
|
||||
public void SetNavigationSource(object? source)
|
||||
{
|
||||
_navigationSource = source;
|
||||
}
|
||||
}
|
||||
177
Marechai.App/Presentation/ViewModels/ProcessorsListViewModel.cs
Normal file
177
Marechai.App/Presentation/ViewModels/ProcessorsListViewModel.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using Marechai.App.Presentation.Models;
|
||||
using Marechai.App.Services;
|
||||
using Uno.Extensions.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel for displaying a list of Processors
|
||||
/// </summary>
|
||||
public partial class ProcessorsListViewModel : ObservableObject
|
||||
{
|
||||
private readonly IStringLocalizer _localizer;
|
||||
private readonly ILogger<ProcessorsListViewModel> _logger;
|
||||
private readonly INavigator _navigator;
|
||||
private readonly ProcessorsService _processorsService;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _errorMessage = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasError;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isDataLoaded;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isLoading;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _pageTitle = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<ProcessorListItem> _processorsList = [];
|
||||
|
||||
public ProcessorsListViewModel(ProcessorsService processorsService, IStringLocalizer localizer,
|
||||
ILogger<ProcessorsListViewModel> logger, INavigator navigator)
|
||||
{
|
||||
_processorsService = processorsService;
|
||||
_localizer = localizer;
|
||||
_logger = logger;
|
||||
_navigator = navigator;
|
||||
LoadData = new AsyncRelayCommand(LoadDataAsync);
|
||||
NavigateToProcessorCommand = new AsyncRelayCommand<ProcessorListItem>(NavigateToProcessorAsync);
|
||||
}
|
||||
|
||||
public IAsyncRelayCommand LoadData { get; }
|
||||
public IAsyncRelayCommand<ProcessorListItem> NavigateToProcessorCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Loads all Processors and sorts them alphabetically
|
||||
/// </summary>
|
||||
private async Task LoadDataAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
ErrorMessage = string.Empty;
|
||||
HasError = false;
|
||||
IsDataLoaded = false;
|
||||
ProcessorsList.Clear();
|
||||
|
||||
_logger.LogInformation("LoadDataAsync called for Processors");
|
||||
|
||||
PageTitle = _localizer["Processors"];
|
||||
|
||||
// Load Processors from the API
|
||||
await LoadProcessorsFromApiAsync();
|
||||
|
||||
_logger.LogInformation("LoadProcessorsFromApiAsync completed. ProcessorsList.Count={Count}",
|
||||
ProcessorsList.Count);
|
||||
|
||||
if(ProcessorsList.Count == 0)
|
||||
{
|
||||
ErrorMessage = _localizer["No processors found"].Value;
|
||||
HasError = true;
|
||||
|
||||
_logger.LogWarning("No Processors found");
|
||||
}
|
||||
else
|
||||
IsDataLoaded = true;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading Processors: {Exception}", ex.Message);
|
||||
ErrorMessage = _localizer["Failed to load processors. Please try again later."].Value;
|
||||
HasError = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads Processors from the API and sorts them alphabetically
|
||||
/// </summary>
|
||||
private async Task LoadProcessorsFromApiAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
List<ProcessorDto> processors = await _processorsService.GetAllProcessorsAsync();
|
||||
|
||||
if(processors == null || processors.Count == 0)
|
||||
{
|
||||
_logger.LogInformation("No Processors returned from API");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var processorItems = new List<ProcessorListItem>();
|
||||
|
||||
foreach(ProcessorDto processor in processors)
|
||||
{
|
||||
var processorItem = new ProcessorListItem
|
||||
{
|
||||
Id = processor.Id ?? 0,
|
||||
Name = processor.Name ?? string.Empty,
|
||||
Company = processor.Company ?? string.Empty
|
||||
};
|
||||
|
||||
processorItems.Add(processorItem);
|
||||
|
||||
_logger.LogInformation("Processor: {Name}, Company: {Company}, ID: {Id}",
|
||||
processor.Name,
|
||||
processor.Company,
|
||||
processor.Id);
|
||||
}
|
||||
|
||||
// Sort processors alphabetically
|
||||
processorItems.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// Add all processors to the list
|
||||
foreach(ProcessorListItem processor in processorItems) ProcessorsList.Add(processor);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading Processors from API");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to the Processor detail view
|
||||
/// </summary>
|
||||
private async Task NavigateToProcessorAsync(ProcessorListItem? processor)
|
||||
{
|
||||
if(processor is null) return;
|
||||
|
||||
_logger.LogInformation("Navigating to Processor detail: {ProcessorName} (ID: {ProcessorId})",
|
||||
processor.Name,
|
||||
processor.Id);
|
||||
|
||||
// Navigate to Processor detail view with navigation parameter
|
||||
var navParam = new ProcessorDetailNavigationParameter
|
||||
{
|
||||
ProcessorId = processor.Id,
|
||||
NavigationSource = this
|
||||
};
|
||||
|
||||
await _navigator.NavigateViewModelAsync<ProcessorDetailViewModel>(this, data: navParam);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data model for a Processor in the list
|
||||
/// </summary>
|
||||
public class ProcessorListItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Company { get; set; } = string.Empty;
|
||||
}
|
||||
3
Marechai.App/Presentation/ViewModels/SecondViewModel.cs
Normal file
3
Marechai.App/Presentation/ViewModels/SecondViewModel.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
public record SecondViewModel(Entity Entity) {}
|
||||
190
Marechai.App/Presentation/ViewModels/SettingsViewModel.cs
Normal file
190
Marechai.App/Presentation/ViewModels/SettingsViewModel.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Marechai.App.Services;
|
||||
using Uno.Extensions.Toolkit;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
public partial class SettingsViewModel : ObservableObject
|
||||
{
|
||||
private readonly IColorThemeService _colorThemeService;
|
||||
private readonly IStringLocalizer _localizer;
|
||||
private readonly IThemeService _themeService;
|
||||
|
||||
[ObservableProperty]
|
||||
private List<ColorThemeOption> _availableColorThemes = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private List<ThemeOption> _availableThemes = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private ColorThemeOption _selectedColorTheme;
|
||||
|
||||
[ObservableProperty]
|
||||
private ThemeOption _selectedTheme;
|
||||
|
||||
public SettingsViewModel(IStringLocalizer localizer, IThemeService themeService,
|
||||
IColorThemeService colorThemeService)
|
||||
{
|
||||
_localizer = localizer;
|
||||
_themeService = themeService;
|
||||
_colorThemeService = colorThemeService;
|
||||
Title = _localizer["Settings"];
|
||||
|
||||
// Initialize immediately to ensure UI is populated
|
||||
InitializeOptions();
|
||||
|
||||
// Wait for theme service to initialize
|
||||
_ = InitializeThemeServiceAsync();
|
||||
}
|
||||
|
||||
public string Title { get; }
|
||||
|
||||
private async Task InitializeThemeServiceAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _themeService.InitializeAsync();
|
||||
|
||||
// Ensure the color theme service has a reference to the theme service
|
||||
_colorThemeService.SetThemeService(_themeService);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Theme service might already be initialized
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeOptions()
|
||||
{
|
||||
// Initialize Light/Dark/System Themes
|
||||
AvailableThemes = new List<ThemeOption>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Theme = AppTheme.Light,
|
||||
DisplayName = _localizer["LightTheme"]
|
||||
},
|
||||
new()
|
||||
{
|
||||
Theme = AppTheme.Dark,
|
||||
DisplayName = _localizer["DarkTheme"]
|
||||
},
|
||||
new()
|
||||
{
|
||||
Theme = AppTheme.System,
|
||||
DisplayName = _localizer["SystemTheme"]
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize Color Themes
|
||||
AvailableColorThemes = new List<ColorThemeOption>
|
||||
{
|
||||
new()
|
||||
{
|
||||
ThemeName = "Default",
|
||||
DisplayName = _localizer["DefaultColorTheme"]
|
||||
},
|
||||
new()
|
||||
{
|
||||
ThemeName = "Windows311",
|
||||
DisplayName = _localizer["Windows311Theme"]
|
||||
},
|
||||
new()
|
||||
{
|
||||
ThemeName = "MacOS9",
|
||||
DisplayName = _localizer["MacOS9Theme"]
|
||||
},
|
||||
new()
|
||||
{
|
||||
ThemeName = "DOS",
|
||||
DisplayName = _localizer["DOSTheme"]
|
||||
},
|
||||
new()
|
||||
{
|
||||
ThemeName = "Amiga",
|
||||
DisplayName = _localizer["AmigaTheme"]
|
||||
},
|
||||
new()
|
||||
{
|
||||
ThemeName = "CDE",
|
||||
DisplayName = _localizer["CDETheme"]
|
||||
}
|
||||
};
|
||||
|
||||
// Try to load saved preferences
|
||||
LoadSavedPreferences();
|
||||
}
|
||||
|
||||
private async void LoadSavedPreferences()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Load current theme from ThemeService
|
||||
AppTheme currentTheme = _themeService.Theme;
|
||||
|
||||
SelectedTheme = AvailableThemes.FirstOrDefault(t => t.Theme == currentTheme) ??
|
||||
AvailableThemes.FirstOrDefault(t => t.Theme == AppTheme.System) ?? AvailableThemes.First();
|
||||
|
||||
// Load current color theme
|
||||
string currentColorTheme = _colorThemeService.CurrentColorTheme;
|
||||
|
||||
SelectedColorTheme = AvailableColorThemes.FirstOrDefault(t => t.ThemeName == currentColorTheme) ??
|
||||
AvailableColorThemes.First();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If loading fails, use defaults
|
||||
SelectedTheme = AvailableThemes.FirstOrDefault(t => t.Theme == AppTheme.System) ?? AvailableThemes.First();
|
||||
SelectedColorTheme = AvailableColorThemes.First();
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedThemeChanged(ThemeOption value)
|
||||
{
|
||||
if(value != null) ApplyTheme(value);
|
||||
}
|
||||
|
||||
partial void OnSelectedColorThemeChanged(ColorThemeOption value)
|
||||
{
|
||||
if(value != null) ApplyColorTheme(value);
|
||||
}
|
||||
|
||||
private async void ApplyTheme(ThemeOption theme)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Apply theme immediately using ThemeService
|
||||
await _themeService.SetThemeAsync(theme.Theme);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Silently fail
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyColorTheme(ColorThemeOption colorTheme)
|
||||
{
|
||||
try
|
||||
{
|
||||
_colorThemeService.ApplyColorTheme(colorTheme.ThemeName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Silently fail
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ThemeOption
|
||||
{
|
||||
public AppTheme Theme { get; set; }
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class ColorThemeOption
|
||||
{
|
||||
public string ThemeName { get; set; } = string.Empty;
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
}
|
||||
13
Marechai.App/Presentation/ViewModels/ShellViewModel.cs
Normal file
13
Marechai.App/Presentation/ViewModels/ShellViewModel.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Uno.Extensions.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
public class ShellViewModel
|
||||
{
|
||||
private readonly INavigator _navigator;
|
||||
|
||||
public ShellViewModel(INavigator navigator) => _navigator = navigator;
|
||||
|
||||
// Users can browse the app without authentication
|
||||
// Login is available from the sidebar when needed
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
#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.Presentation.Models;
|
||||
using Marechai.App.Services;
|
||||
using Uno.Extensions.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
public partial class SoundSynthDetailViewModel : ObservableObject
|
||||
{
|
||||
private readonly CompaniesService _companiesService;
|
||||
private readonly IStringLocalizer _localizer;
|
||||
private readonly ILogger<SoundSynthDetailViewModel> _logger;
|
||||
private readonly INavigator _navigator;
|
||||
private readonly SoundSynthsService _soundSynthsService;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<MachineItem> _computers = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private string _computersFilterText = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _consoelsFilterText = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<MachineItem> _consoles = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private string _errorMessage = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<MachineItem> _filteredComputers = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<MachineItem> _filteredConsoles = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasComputers;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasConsoles;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasError;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isDataLoaded;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isLoading;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _manufacturerName = string.Empty;
|
||||
private object? _navigationSource;
|
||||
|
||||
[ObservableProperty]
|
||||
private SoundSynthDto? _soundSynth;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _soundSynthId;
|
||||
|
||||
public SoundSynthDetailViewModel(SoundSynthsService soundSynthsService, CompaniesService companiesService,
|
||||
IStringLocalizer localizer, ILogger<SoundSynthDetailViewModel> logger,
|
||||
INavigator navigator)
|
||||
{
|
||||
_soundSynthsService = soundSynthsService;
|
||||
_companiesService = companiesService;
|
||||
_localizer = localizer;
|
||||
_logger = logger;
|
||||
_navigator = navigator;
|
||||
LoadData = new AsyncRelayCommand(LoadDataAsync);
|
||||
GoBackCommand = new AsyncRelayCommand(GoBackAsync);
|
||||
SelectMachineCommand = new AsyncRelayCommand<int>(SelectMachineAsync);
|
||||
ComputersFilterCommand = new RelayCommand(() => FilterComputers());
|
||||
ConsolesFilterCommand = new RelayCommand(() => FilterConsoles());
|
||||
}
|
||||
|
||||
public IAsyncRelayCommand LoadData { get; }
|
||||
public ICommand GoBackCommand { get; }
|
||||
public IAsyncRelayCommand SelectMachineCommand { get; }
|
||||
public ICommand ComputersFilterCommand { get; }
|
||||
public ICommand ConsolesFilterCommand { get; }
|
||||
|
||||
public string Title { get; } = "Sound Synthesizer Details";
|
||||
|
||||
/// <summary>
|
||||
/// Loads Sound Synthesizer details including computers and consoles
|
||||
/// </summary>
|
||||
private async Task LoadDataAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
ErrorMessage = string.Empty;
|
||||
HasError = false;
|
||||
IsDataLoaded = false;
|
||||
Computers.Clear();
|
||||
Consoles.Clear();
|
||||
|
||||
if(SoundSynthId <= 0)
|
||||
{
|
||||
ErrorMessage = _localizer["Invalid Sound Synthesizer ID"].Value;
|
||||
HasError = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Loading Sound Synthesizer details for ID: {SoundSynthId}", SoundSynthId);
|
||||
|
||||
// Load Sound Synthesizer details
|
||||
SoundSynth = await _soundSynthsService.GetSoundSynthByIdAsync(SoundSynthId);
|
||||
|
||||
if(SoundSynth is null)
|
||||
{
|
||||
ErrorMessage = _localizer["Sound Synthesizer not found"].Value;
|
||||
HasError = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Set manufacturer name (from Company field or fetch by CompanyId if empty)
|
||||
ManufacturerName = SoundSynth.Company ?? string.Empty;
|
||||
|
||||
if(string.IsNullOrEmpty(ManufacturerName) && SoundSynth.CompanyId.HasValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
CompanyDto? company = await _companiesService.GetCompanyByIdAsync(SoundSynth.CompanyId.Value);
|
||||
if(company != null) ManufacturerName = company.Name ?? string.Empty;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to load company for Sound Synthesizer {SoundSynthId}", SoundSynthId);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("Sound Synthesizer loaded: {Name}, Company: {Company}",
|
||||
SoundSynth.Name,
|
||||
ManufacturerName);
|
||||
|
||||
// Load machines and separate into computers and consoles
|
||||
try
|
||||
{
|
||||
List<MachineDto>? machines = await _soundSynthsService.GetMachinesBySoundSynthAsync(SoundSynthId);
|
||||
|
||||
if(machines != null && machines.Count > 0)
|
||||
{
|
||||
Computers.Clear();
|
||||
Consoles.Clear();
|
||||
|
||||
foreach(MachineDto machine in machines)
|
||||
{
|
||||
var machineItem = new MachineItem
|
||||
{
|
||||
Id = machine.Id ?? 0,
|
||||
Name = machine.Name ?? string.Empty,
|
||||
Manufacturer = machine.Company ?? string.Empty,
|
||||
Year = machine.Introduced?.Year ?? 0
|
||||
};
|
||||
|
||||
// Distinguish between computers and consoles based on Type
|
||||
if(machine.Type == 2) // MachineType.Console
|
||||
Consoles.Add(machineItem);
|
||||
else // MachineType.Computer or Unknown
|
||||
Computers.Add(machineItem);
|
||||
}
|
||||
|
||||
HasComputers = Computers.Count > 0;
|
||||
HasConsoles = Consoles.Count > 0;
|
||||
|
||||
// Initialize filtered collections
|
||||
FilterComputers();
|
||||
FilterConsoles();
|
||||
|
||||
_logger.LogInformation("Loaded {ComputerCount} computers and {ConsoleCount} consoles for Sound Synthesizer {SoundSynthId}",
|
||||
Computers.Count,
|
||||
Consoles.Count,
|
||||
SoundSynthId);
|
||||
}
|
||||
else
|
||||
{
|
||||
HasComputers = false;
|
||||
HasConsoles = false;
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to load machines for Sound Synthesizer {SoundSynthId}", SoundSynthId);
|
||||
HasComputers = false;
|
||||
HasConsoles = false;
|
||||
}
|
||||
|
||||
IsDataLoaded = true;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading Sound Synthesizer details: {Exception}", ex.Message);
|
||||
ErrorMessage = _localizer["Failed to load Sound Synthesizer details. Please try again later."].Value;
|
||||
HasError = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters computers based on search text
|
||||
/// </summary>
|
||||
private void FilterComputers()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(ComputersFilterText))
|
||||
{
|
||||
FilteredComputers.Clear();
|
||||
foreach(MachineItem computer in Computers) FilteredComputers.Add(computer);
|
||||
}
|
||||
else
|
||||
{
|
||||
var filtered = Computers
|
||||
.Where(c => c.Name.Contains(ComputersFilterText, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
FilteredComputers.Clear();
|
||||
foreach(MachineItem computer in filtered) FilteredComputers.Add(computer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters consoles based on search text
|
||||
/// </summary>
|
||||
private void FilterConsoles()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(ConsoelsFilterText))
|
||||
{
|
||||
FilteredConsoles.Clear();
|
||||
foreach(MachineItem console in Consoles) FilteredConsoles.Add(console);
|
||||
}
|
||||
else
|
||||
{
|
||||
var filtered = Consoles.Where(c => c.Name.Contains(ConsoelsFilterText, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
FilteredConsoles.Clear();
|
||||
foreach(MachineItem console in filtered) FilteredConsoles.Add(console);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates back to the Sound Synthesizer list
|
||||
/// </summary>
|
||||
private async Task GoBackAsync()
|
||||
{
|
||||
// If we came from a machine view, go back to machine view
|
||||
if(_navigationSource is MachineViewViewModel machineVm)
|
||||
{
|
||||
await _navigator.NavigateViewModelAsync<MachineViewViewModel>(this);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Default: go back to Sound Synthesizer list
|
||||
await _navigator.NavigateViewModelAsync<SoundSynthsListViewModel>(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to machine detail view
|
||||
/// </summary>
|
||||
private async Task SelectMachineAsync(int machineId)
|
||||
{
|
||||
if(machineId <= 0) return;
|
||||
|
||||
var navParam = new MachineViewNavigationParameter
|
||||
{
|
||||
MachineId = machineId,
|
||||
NavigationSource = this
|
||||
};
|
||||
|
||||
await _navigator.NavigateViewModelAsync<MachineViewViewModel>(this, data: navParam);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the navigation source (where we came from).
|
||||
/// </summary>
|
||||
public void SetNavigationSource(object? source)
|
||||
{
|
||||
_navigationSource = source;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Machine item for displaying computers or consoles that use the Sound Synthesizer
|
||||
/// </summary>
|
||||
public class MachineItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Manufacturer { get; set; } = string.Empty;
|
||||
public int Year { get; set; }
|
||||
|
||||
public string YearDisplay => Year > 0 ? Year.ToString() : "Unknown";
|
||||
}
|
||||
}
|
||||
135
Marechai.App/Presentation/ViewModels/SoundSynthsListViewModel.cs
Normal file
135
Marechai.App/Presentation/ViewModels/SoundSynthsListViewModel.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using Marechai.App.Presentation.Models;
|
||||
using Marechai.App.Services;
|
||||
using Uno.Extensions.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
public partial class SoundSynthsListViewModel : ObservableObject
|
||||
{
|
||||
private readonly ILogger<SoundSynthsListViewModel> _logger;
|
||||
private readonly INavigator _navigator;
|
||||
private readonly SoundSynthsService _soundSynthsService;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _errorMessage = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasError;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isDataLoaded;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isLoading = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<SoundSynthListItem> _soundSynths = [];
|
||||
|
||||
public SoundSynthsListViewModel(SoundSynthsService soundSynthsService, INavigator navigator,
|
||||
ILogger<SoundSynthsListViewModel> logger)
|
||||
{
|
||||
_soundSynthsService = soundSynthsService;
|
||||
_navigator = navigator;
|
||||
_logger = logger;
|
||||
LoadData = new AsyncRelayCommand(LoadDataAsync);
|
||||
NavigateToSoundSynthCommand = new AsyncRelayCommand<SoundSynthListItem>(NavigateToSoundSynthAsync);
|
||||
}
|
||||
|
||||
public IAsyncRelayCommand LoadData { get; }
|
||||
public IAsyncRelayCommand<SoundSynthListItem> NavigateToSoundSynthCommand { get; }
|
||||
|
||||
private async Task LoadDataAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
IsDataLoaded = false;
|
||||
HasError = false;
|
||||
ErrorMessage = string.Empty;
|
||||
|
||||
List<SoundSynthDto> soundSynths = await _soundSynthsService.GetAllSoundSynthsAsync();
|
||||
|
||||
// Separate special sound synths from regular ones
|
||||
var specialSoundSynths = new List<SoundSynthListItem>();
|
||||
var regularSoundSynths = new List<SoundSynthListItem>();
|
||||
|
||||
foreach(SoundSynthDto ss in soundSynths)
|
||||
{
|
||||
string displayName = ss.Name ?? "Unknown";
|
||||
|
||||
// Replace special database name
|
||||
if(displayName == "DB_SOFTWARE") displayName = "Software";
|
||||
|
||||
var soundSynthItem = new SoundSynthListItem
|
||||
{
|
||||
Id = ss.Id ?? 0,
|
||||
Name = displayName,
|
||||
Company = ss.Company ?? "Unknown",
|
||||
IsSpecial = ss.Name == "DB_SOFTWARE"
|
||||
};
|
||||
|
||||
if(soundSynthItem.IsSpecial)
|
||||
specialSoundSynths.Add(soundSynthItem);
|
||||
else
|
||||
regularSoundSynths.Add(soundSynthItem);
|
||||
|
||||
_logger.LogInformation("Sound Synth: {Name}, Company: {Company}, ID: {Id}, IsSpecial: {IsSpecial}",
|
||||
displayName,
|
||||
ss.Company,
|
||||
ss.Id,
|
||||
soundSynthItem.IsSpecial);
|
||||
}
|
||||
|
||||
// Sort regular sound synths alphabetically by name
|
||||
regularSoundSynths.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// Add special sound synths first (Software), then regular sound synths
|
||||
SoundSynths.Clear();
|
||||
|
||||
foreach(SoundSynthListItem ss in specialSoundSynths) SoundSynths.Add(ss);
|
||||
|
||||
foreach(SoundSynthListItem ss in regularSoundSynths) SoundSynths.Add(ss);
|
||||
|
||||
_logger.LogInformation("Successfully loaded {Count} Sound Synthesizers", SoundSynths.Count);
|
||||
IsDataLoaded = true;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading Sound Synthesizers");
|
||||
ErrorMessage = "Failed to load Sound Synthesizers. Please try again later.";
|
||||
HasError = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task NavigateToSoundSynthAsync(SoundSynthListItem? item)
|
||||
{
|
||||
if(item == null) return;
|
||||
|
||||
_logger.LogInformation("Navigating to Sound Synthesizer {SoundSynthId}", item.Id);
|
||||
|
||||
await _navigator.NavigateViewModelAsync<SoundSynthDetailViewModel>(this,
|
||||
data: new SoundSynthDetailNavigationParameter
|
||||
{
|
||||
SoundSynthId = item.Id,
|
||||
NavigationSource = this
|
||||
});
|
||||
}
|
||||
|
||||
public class SoundSynthListItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Company { get; set; }
|
||||
public bool IsSpecial { get; set; }
|
||||
}
|
||||
}
|
||||
440
Marechai.App/Presentation/ViewModels/UsersViewModel.cs
Normal file
440
Marechai.App/Presentation/ViewModels/UsersViewModel.cs
Normal file
@@ -0,0 +1,440 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Marechai.App.Services.Authentication;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel for user management page (Uberadmin only)
|
||||
/// </summary>
|
||||
public partial class UsersViewModel : ObservableObject
|
||||
{
|
||||
private readonly ApiClient _apiClient;
|
||||
private readonly IJwtService _jwtService;
|
||||
private readonly IStringLocalizer _localizer;
|
||||
private readonly ILogger<UsersViewModel> _logger;
|
||||
private readonly ITokenService _tokenService;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<string> _availableRoles = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private string _confirmPassword = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _dialogTitle = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _dialogType = string.Empty;
|
||||
|
||||
private string? _editingUserId;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _email = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _errorMessage = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasError;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isDataLoaded;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isLoading;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _password = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _phoneNumber = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _selectedRole;
|
||||
|
||||
[ObservableProperty]
|
||||
private UserDto? _selectedUser;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _userName = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<string> _userRoles = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<UserDto> _users = [];
|
||||
|
||||
public UsersViewModel(ApiClient apiClient, IJwtService jwtService, ITokenService tokenService,
|
||||
ILogger<UsersViewModel> logger, IStringLocalizer localizer)
|
||||
{
|
||||
_apiClient = apiClient;
|
||||
_jwtService = jwtService;
|
||||
_tokenService = tokenService;
|
||||
_logger = logger;
|
||||
_localizer = localizer;
|
||||
|
||||
LoadUsersCommand = new AsyncRelayCommand(LoadUsersAsync);
|
||||
DeleteUserCommand = new AsyncRelayCommand<UserDto>(DeleteUserAsync);
|
||||
OpenAddUserDialogCommand = new AsyncRelayCommand(OpenAddUserDialogAsync);
|
||||
OpenEditUserDialogCommand = new AsyncRelayCommand<UserDto>(OpenEditUserDialogAsync);
|
||||
OpenChangePasswordDialogCommand = new AsyncRelayCommand<UserDto>(OpenChangePasswordDialogAsync);
|
||||
OpenManageRolesDialogCommand = new AsyncRelayCommand<UserDto>(OpenManageRolesDialogAsync);
|
||||
SaveUserCommand = new AsyncRelayCommand(SaveUserAsync);
|
||||
SavePasswordCommand = new AsyncRelayCommand(SavePasswordAsync);
|
||||
AddRoleCommand = new AsyncRelayCommand(AddRoleAsync);
|
||||
RemoveRoleCommand = new AsyncRelayCommand<string>(RemoveRoleAsync);
|
||||
CloseDialogCommand = new RelayCommand(CloseDialog);
|
||||
}
|
||||
|
||||
public IAsyncRelayCommand LoadUsersCommand { get; }
|
||||
public IAsyncRelayCommand<UserDto> DeleteUserCommand { get; }
|
||||
public IAsyncRelayCommand OpenAddUserDialogCommand { get; }
|
||||
public IAsyncRelayCommand<UserDto> OpenEditUserDialogCommand { get; }
|
||||
public IAsyncRelayCommand<UserDto> OpenChangePasswordDialogCommand { get; }
|
||||
public IAsyncRelayCommand<UserDto> OpenManageRolesDialogCommand { get; }
|
||||
public IAsyncRelayCommand SaveUserCommand { get; }
|
||||
public IAsyncRelayCommand SavePasswordCommand { get; }
|
||||
public IAsyncRelayCommand AddRoleCommand { get; }
|
||||
public IAsyncRelayCommand<string> RemoveRoleCommand { get; }
|
||||
public IRelayCommand CloseDialogCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the current user is Uberadmin
|
||||
/// </summary>
|
||||
public bool IsUberadmin
|
||||
{
|
||||
get
|
||||
{
|
||||
string? token = _tokenService.GetToken();
|
||||
IEnumerable<string>? roles = _jwtService.GetRoles(token);
|
||||
|
||||
return roles.Contains("Uberadmin", StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler<string>? ShowDialogRequested;
|
||||
|
||||
private async Task LoadUsersAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
HasError = false;
|
||||
ErrorMessage = string.Empty;
|
||||
Users.Clear();
|
||||
|
||||
List<UserDto>? usersResponse = await _apiClient.Users.GetAsync();
|
||||
|
||||
if(usersResponse != null)
|
||||
{
|
||||
foreach(UserDto user in usersResponse) Users.Add(user);
|
||||
|
||||
IsDataLoaded = true;
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading users");
|
||||
ErrorMessage = _localizer["Failed to load users. Please try again."];
|
||||
HasError = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteUserAsync(UserDto? user)
|
||||
{
|
||||
if(user?.Id == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
await _apiClient.Users[user.Id].DeleteAsync();
|
||||
await LoadUsersAsync();
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error deleting user");
|
||||
ErrorMessage = _localizer["Failed to delete user."];
|
||||
HasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OpenAddUserDialogAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_editingUserId = null;
|
||||
DialogTitle = _localizer["Add User"];
|
||||
DialogType = "AddEdit";
|
||||
Email = string.Empty;
|
||||
UserName = string.Empty;
|
||||
PhoneNumber = string.Empty;
|
||||
Password = string.Empty;
|
||||
ConfirmPassword = string.Empty;
|
||||
ShowDialogRequested?.Invoke(this, "AddEdit");
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error opening add user dialog");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OpenEditUserDialogAsync(UserDto? user)
|
||||
{
|
||||
if(user?.Id == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
_editingUserId = user.Id;
|
||||
DialogTitle = _localizer["Edit User"];
|
||||
DialogType = "AddEdit";
|
||||
Email = user.Email ?? string.Empty;
|
||||
UserName = user.UserName ?? string.Empty;
|
||||
PhoneNumber = user.PhoneNumber ?? string.Empty;
|
||||
Password = string.Empty;
|
||||
ConfirmPassword = string.Empty;
|
||||
ShowDialogRequested?.Invoke(this, "AddEdit");
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error opening edit user dialog");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OpenChangePasswordDialogAsync(UserDto? user)
|
||||
{
|
||||
if(user?.Id == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
_editingUserId = user.Id;
|
||||
DialogTitle = _localizer["Change Password"];
|
||||
DialogType = "Password";
|
||||
Password = string.Empty;
|
||||
ConfirmPassword = string.Empty;
|
||||
ShowDialogRequested?.Invoke(this, "Password");
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error opening change password dialog");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OpenManageRolesDialogAsync(UserDto? user)
|
||||
{
|
||||
if(user?.Id == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
_editingUserId = user.Id;
|
||||
DialogTitle = _localizer["Manage Roles"];
|
||||
DialogType = "Roles";
|
||||
|
||||
// Load available roles
|
||||
List<string>? rolesResponse = await _apiClient.Users.Roles.GetAsync();
|
||||
AvailableRoles.Clear();
|
||||
|
||||
if(rolesResponse != null)
|
||||
{
|
||||
foreach(string role in rolesResponse)
|
||||
if(!string.IsNullOrWhiteSpace(role))
|
||||
AvailableRoles.Add(role);
|
||||
}
|
||||
|
||||
_logger.LogInformation($"Loaded {AvailableRoles.Count} available roles");
|
||||
|
||||
// Load user's current roles
|
||||
UserRoles.Clear();
|
||||
|
||||
if(user.Roles != null)
|
||||
foreach(string role in user.Roles)
|
||||
UserRoles.Add(role);
|
||||
|
||||
ShowDialogRequested?.Invoke(this, "Roles");
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error opening manage roles dialog");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveUserAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(Email) || string.IsNullOrWhiteSpace(UserName))
|
||||
{
|
||||
ErrorMessage = _localizer["Email and username are required."];
|
||||
HasError = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(_editingUserId == null)
|
||||
{
|
||||
// Create new user
|
||||
if(string.IsNullOrWhiteSpace(Password))
|
||||
{
|
||||
ErrorMessage = _localizer["Password is required for new users."];
|
||||
HasError = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(Password != ConfirmPassword)
|
||||
{
|
||||
ErrorMessage = _localizer["Passwords do not match."];
|
||||
HasError = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var createRequest = new CreateUserRequest
|
||||
{
|
||||
Email = Email,
|
||||
UserName = UserName,
|
||||
PhoneNumber = PhoneNumber,
|
||||
Password = Password
|
||||
};
|
||||
|
||||
await _apiClient.Users.PostAsync(createRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update existing user
|
||||
var updateRequest = new UpdateUserRequest
|
||||
{
|
||||
Email = Email,
|
||||
UserName = UserName,
|
||||
PhoneNumber = PhoneNumber
|
||||
};
|
||||
|
||||
await _apiClient.Users[_editingUserId].PutAsync(updateRequest);
|
||||
}
|
||||
|
||||
CloseDialog();
|
||||
await LoadUsersAsync();
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error saving user");
|
||||
ErrorMessage = _localizer["Failed to save user."];
|
||||
HasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SavePasswordAsync()
|
||||
{
|
||||
if(_editingUserId == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(Password))
|
||||
{
|
||||
ErrorMessage = _localizer["Password is required."];
|
||||
HasError = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(Password != ConfirmPassword)
|
||||
{
|
||||
ErrorMessage = _localizer["Passwords do not match."];
|
||||
HasError = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var changePasswordRequest = new ChangePasswordRequest
|
||||
{
|
||||
NewPassword = Password
|
||||
};
|
||||
|
||||
await _apiClient.Users[_editingUserId].Password.PostAsync(changePasswordRequest);
|
||||
|
||||
CloseDialog();
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error changing password");
|
||||
ErrorMessage = _localizer["Failed to change password."];
|
||||
HasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddRoleAsync()
|
||||
{
|
||||
if(_editingUserId == null || string.IsNullOrWhiteSpace(SelectedRole)) return;
|
||||
|
||||
try
|
||||
{
|
||||
if(UserRoles.Contains(SelectedRole))
|
||||
{
|
||||
ErrorMessage = _localizer["User already has this role."];
|
||||
HasError = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var addRoleRequest = new UserRoleRequest
|
||||
{
|
||||
RoleName = SelectedRole
|
||||
};
|
||||
|
||||
await _apiClient.Users[_editingUserId].Roles.PostAsync(addRoleRequest);
|
||||
UserRoles.Add(SelectedRole);
|
||||
|
||||
// Reload users to refresh the list
|
||||
await LoadUsersAsync();
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error adding role");
|
||||
ErrorMessage = _localizer["Failed to add role."];
|
||||
HasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RemoveRoleAsync(string? role)
|
||||
{
|
||||
if(_editingUserId == null || string.IsNullOrWhiteSpace(role)) return;
|
||||
|
||||
try
|
||||
{
|
||||
await _apiClient.Users[_editingUserId].Roles[role].DeleteAsync();
|
||||
UserRoles.Remove(role);
|
||||
|
||||
// Reload users to refresh the list
|
||||
await LoadUsersAsync();
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error removing role");
|
||||
ErrorMessage = _localizer["Failed to remove role."];
|
||||
HasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseDialog()
|
||||
{
|
||||
DialogType = string.Empty;
|
||||
_editingUserId = null;
|
||||
Email = string.Empty;
|
||||
UserName = string.Empty;
|
||||
PhoneNumber = string.Empty;
|
||||
Password = string.Empty;
|
||||
ConfirmPassword = string.Empty;
|
||||
UserRoles.Clear();
|
||||
AvailableRoles.Clear();
|
||||
HasError = false;
|
||||
ErrorMessage = string.Empty;
|
||||
}
|
||||
}
|
||||
139
Marechai.App/Presentation/Views/CompaniesPage.xaml
Normal file
139
Marechai.App/Presentation/Views/CompaniesPage.xaml
Normal file
@@ -0,0 +1,139 @@
|
||||
<?xml version="1.0"
|
||||
encoding="utf-8"?>
|
||||
|
||||
<Page x:Class="Marechai.App.Presentation.Views.CompaniesPage"
|
||||
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="16">
|
||||
|
||||
<!-- Company Count Display -->
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
Spacing="8">
|
||||
<TextBlock Text="{Binding CompanyCountText}"
|
||||
TextAlignment="Center"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding CompanyCount}"
|
||||
TextAlignment="Center"
|
||||
FontSize="48"
|
||||
FontWeight="Bold"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Search Box -->
|
||||
<AutoSuggestBox x:Name="CompaniesSearchBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
PlaceholderText="Search companies..."
|
||||
Text="{Binding SearchQuery, Mode=TwoWay}"
|
||||
QuerySubmitted="OnSearchQuerySubmitted"
|
||||
TextChanged="OnSearchTextChanged">
|
||||
<AutoSuggestBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
</DataTemplate>
|
||||
</AutoSuggestBox.ItemTemplate>
|
||||
</AutoSuggestBox>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- Companies List -->
|
||||
<StackPanel Visibility="{Binding IsDataLoaded}"
|
||||
Spacing="8">
|
||||
<ItemsRepeater ItemsSource="{Binding CompaniesList}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button HorizontalAlignment="Stretch"
|
||||
Command="{Binding DataContext.NavigateToCompanyCommand, ElementName=PageRoot}"
|
||||
CommandParameter="{Binding}">
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Company Logo -->
|
||||
<Image Grid.Column="0"
|
||||
Width="48"
|
||||
Height="48"
|
||||
Stretch="Uniform"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Left"
|
||||
Source="{Binding LogoImageSource}"
|
||||
Visibility="{Binding LogoImageSource, Converter={StaticResource ObjectToVisibilityConverter}}" />
|
||||
|
||||
<!-- Company Details -->
|
||||
<StackPanel Grid.Column="1"
|
||||
Spacing="4"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Name}"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold" />
|
||||
<TextBlock Text="{Binding FoundationDateDisplay}"
|
||||
FontSize="12"
|
||||
Opacity="0.6" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Page>
|
||||
59
Marechai.App/Presentation/Views/CompaniesPage.xaml.cs
Normal file
59
Marechai.App/Presentation/Views/CompaniesPage.xaml.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
public sealed partial class CompaniesPage : Page
|
||||
{
|
||||
public CompaniesPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContextChanged += CompaniesPage_DataContextChanged;
|
||||
Loaded += CompaniesPage_Loaded;
|
||||
}
|
||||
|
||||
private void CompaniesPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(DataContext is not CompaniesViewModel viewModel) return;
|
||||
|
||||
// Trigger data loading
|
||||
_ = viewModel.LoadData.ExecuteAsync(null);
|
||||
}
|
||||
|
||||
private void CompaniesPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
|
||||
{
|
||||
if(args.NewValue is CompaniesViewModel viewModel)
|
||||
{
|
||||
// Trigger data loading when data context changes
|
||||
_ = viewModel.LoadData.ExecuteAsync(null);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
if(DataContext is CompaniesViewModel viewModel)
|
||||
{
|
||||
// Trigger data loading when navigating to the page
|
||||
_ = viewModel.LoadData.ExecuteAsync(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSearchTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
|
||||
{
|
||||
if(args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
|
||||
{
|
||||
// The two-way binding will automatically update SearchQuery in ViewModel,
|
||||
// which will trigger OnSearchQueryChanged and filter the list
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSearchQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
|
||||
{
|
||||
// The two-way binding will automatically update SearchQuery in ViewModel,
|
||||
// which will trigger OnSearchQueryChanged and filter the list
|
||||
}
|
||||
}
|
||||
351
Marechai.App/Presentation/Views/CompanyDetailPage.xaml
Normal file
351
Marechai.App/Presentation/Views/CompanyDetailPage.xaml
Normal file
@@ -0,0 +1,351 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Page x:Class="Marechai.App.Presentation.Views.CompanyDetailPage"
|
||||
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"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
|
||||
NavigationCacheMode="Required"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Page.Resources>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid utu:SafeArea.Insets="VisibleBounds">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header -->
|
||||
<utu:NavigationBar Grid.Row="0"
|
||||
Content="{Binding Path=Title}"
|
||||
MainCommandMode="Action">
|
||||
<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="16">
|
||||
|
||||
<!-- Logo Display (Top Center) -->
|
||||
<Image MaxHeight="96"
|
||||
Stretch="Uniform"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,0,0,8"
|
||||
Source="{Binding LogoImageSource}"
|
||||
Visibility="{Binding HasLogoContent, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- Company Details -->
|
||||
<StackPanel Visibility="{Binding IsDataLoaded}"
|
||||
Spacing="16">
|
||||
|
||||
<!-- Company Name -->
|
||||
<TextBlock Text="{Binding Company.Name}"
|
||||
FontSize="28"
|
||||
FontWeight="Bold"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<!-- Company Status -->
|
||||
<StackPanel Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Status"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding CompanyStatusDisplay}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Founded Date -->
|
||||
<StackPanel Visibility="{Binding Company.Founded, Converter={StaticResource ObjectToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Founded"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding CompanyFoundedDateDisplay}"
|
||||
FontSize="14" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Legal Name -->
|
||||
<StackPanel Visibility="{Binding Company.LegalName, Converter={StaticResource StringToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Legal Name"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Company.LegalName}"
|
||||
FontSize="14" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Country -->
|
||||
<StackPanel Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12">
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Country Name and Label -->
|
||||
<StackPanel Grid.Column="0"
|
||||
Spacing="4">
|
||||
<TextBlock Text="Country"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Company.Country}"
|
||||
FontSize="14"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Country Flag -->
|
||||
<Image Grid.Column="1"
|
||||
Width="48"
|
||||
Height="32"
|
||||
Stretch="UniformToFill"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Source="{Binding FlagImageSource}"
|
||||
Visibility="{Binding HasFlagContent, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Address -->
|
||||
<StackPanel Visibility="{Binding Company.Address, Converter={StaticResource StringToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Address"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
<Run Text="{Binding Company.Address}" />
|
||||
<Run Text="{Binding Company.City}" />
|
||||
<Run Text="{Binding Company.PostalCode}" />
|
||||
<Run Text="{Binding Company.Province}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Links Section -->
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="Links"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
|
||||
<!-- Website -->
|
||||
<HyperlinkButton Visibility="{Binding Company.Website, Converter={StaticResource StringToVisibilityConverter}}"
|
||||
NavigateUri="{Binding Company.Website}">
|
||||
<TextBlock Text="Website" />
|
||||
</HyperlinkButton>
|
||||
|
||||
<!-- Twitter -->
|
||||
<HyperlinkButton Visibility="{Binding Company.Twitter, Converter={StaticResource StringToVisibilityConverter}}"
|
||||
Click="OnTwitterClick">
|
||||
<TextBlock Text="Twitter" />
|
||||
</HyperlinkButton>
|
||||
|
||||
<!-- Facebook -->
|
||||
<HyperlinkButton Visibility="{Binding Company.Facebook, Converter={StaticResource StringToVisibilityConverter}}"
|
||||
Click="OnFacebookClick">
|
||||
<TextBlock Text="Facebook" />
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Logo Carousel Section -->
|
||||
<StackPanel Visibility="{Binding HasMultipleLogos, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Logo History"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
|
||||
<!-- Logo Carousel -->
|
||||
<controls:Carousel ItemsSource="{Binding CompanyLogos}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
ItemRotationY="45"
|
||||
TransitionDuration="400"
|
||||
Height="220">
|
||||
<controls:Carousel.EasingFunction>
|
||||
<CubicEase EasingMode="EaseOut" />
|
||||
</controls:Carousel.EasingFunction>
|
||||
<controls:Carousel.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Spacing="8"
|
||||
Padding="24">
|
||||
<!-- Logo Image -->
|
||||
<Image Source="{Binding LogoSource}"
|
||||
Width="120"
|
||||
Height="120"
|
||||
Stretch="Uniform"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<!-- Year Label -->
|
||||
<TextBlock Text="{Binding Year, FallbackValue='Year Unknown'}"
|
||||
FontSize="12"
|
||||
TextAlignment="Center"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</controls:Carousel.ItemTemplate>
|
||||
</controls:Carousel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Computers Section -->
|
||||
<StackPanel Visibility="{Binding Computers.Count, Converter={StaticResource ZeroToVisibilityConverter}}"
|
||||
Spacing="8">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Computers"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Computers.Count}"
|
||||
FontSize="14"
|
||||
FontWeight="Bold"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Filter Box -->
|
||||
<AutoSuggestBox PlaceholderText="Filter computers..."
|
||||
Text="{Binding ComputersFilterText, Mode=TwoWay}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{ThemeResource ControlElevationBorderBrush}" />
|
||||
|
||||
<!-- Scrollable Computers List -->
|
||||
<ScrollViewer Height="200"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{ThemeResource ControlElevationBorderBrush}"
|
||||
CornerRadius="8">
|
||||
<ItemsRepeater ItemsSource="{Binding FilteredComputers}"
|
||||
Margin="0">
|
||||
<ItemsRepeater.Layout>
|
||||
<StackLayout Spacing="4" />
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Command="{Binding DataContext.NavigateToMachineCommand, ElementName=PageRoot}"
|
||||
CommandParameter="{Binding}"
|
||||
Padding="12,8"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left">
|
||||
<TextBlock Text="{Binding Name}"
|
||||
FontSize="12"
|
||||
TextWrapping="Wrap" />
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Consoles Section -->
|
||||
<StackPanel Visibility="{Binding Consoles.Count, Converter={StaticResource ZeroToVisibilityConverter}}"
|
||||
Spacing="8">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Consoles"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Consoles.Count}"
|
||||
FontSize="14"
|
||||
FontWeight="Bold"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Filter Box -->
|
||||
<AutoSuggestBox PlaceholderText="Filter consoles..."
|
||||
Text="{Binding ConsoelsFilterText, Mode=TwoWay}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{ThemeResource ControlElevationBorderBrush}" />
|
||||
|
||||
<!-- Scrollable Consoles List -->
|
||||
<ScrollViewer Height="200"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{ThemeResource ControlElevationBorderBrush}"
|
||||
CornerRadius="8">
|
||||
<ItemsRepeater ItemsSource="{Binding FilteredConsoles}"
|
||||
Margin="0">
|
||||
<ItemsRepeater.Layout>
|
||||
<StackLayout Spacing="4" />
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Command="{Binding DataContext.NavigateToMachineCommand, ElementName=PageRoot}"
|
||||
CommandParameter="{Binding}"
|
||||
Padding="12,8"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left">
|
||||
<TextBlock Text="{Binding Name}"
|
||||
FontSize="12"
|
||||
TextWrapping="Wrap" />
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Page>
|
||||
77
Marechai.App/Presentation/Views/CompanyDetailPage.xaml.cs
Normal file
77
Marechai.App/Presentation/Views/CompanyDetailPage.xaml.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using Windows.System;
|
||||
using Marechai.App.Presentation.Models;
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
public sealed partial class CompanyDetailPage : Page
|
||||
{
|
||||
private object? _navigationSource;
|
||||
private int? _pendingCompanyId;
|
||||
|
||||
public CompanyDetailPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContextChanged += CompanyDetailPage_DataContextChanged;
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
int? companyId = null;
|
||||
|
||||
// Handle both int and CompanyDetailNavigationParameter
|
||||
if(e.Parameter is int intId)
|
||||
companyId = intId;
|
||||
else if(e.Parameter is CompanyDetailNavigationParameter navParam)
|
||||
{
|
||||
companyId = navParam.CompanyId;
|
||||
_navigationSource = navParam.NavigationSource;
|
||||
}
|
||||
|
||||
if(companyId.HasValue)
|
||||
{
|
||||
_pendingCompanyId = companyId;
|
||||
|
||||
if(DataContext is CompanyDetailViewModel viewModel)
|
||||
{
|
||||
viewModel.CompanyId = companyId.Value;
|
||||
_ = viewModel.LoadData.ExecuteAsync(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CompanyDetailPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
|
||||
{
|
||||
if(DataContext is CompanyDetailViewModel viewModel && _pendingCompanyId.HasValue)
|
||||
{
|
||||
viewModel.CompanyId = _pendingCompanyId.Value;
|
||||
_ = viewModel.LoadData.ExecuteAsync(null);
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnTwitterClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(DataContext is CompanyDetailViewModel viewModel && viewModel.Company?.Twitter is not null)
|
||||
{
|
||||
var uri = new Uri($"https://www.twitter.com/{viewModel.Company.Twitter}");
|
||||
await Launcher.LaunchUriAsync(uri);
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnFacebookClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(DataContext is CompanyDetailViewModel viewModel && viewModel.Company?.Facebook is not null)
|
||||
{
|
||||
var uri = new Uri($"https://www.facebook.com/{viewModel.Company.Facebook}");
|
||||
await Launcher.LaunchUriAsync(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
261
Marechai.App/Presentation/Views/ComputersListPage.xaml
Normal file
261
Marechai.App/Presentation/Views/ComputersListPage.xaml
Normal file
@@ -0,0 +1,261 @@
|
||||
<?xml version="1.0"
|
||||
encoding="utf-8"?>
|
||||
|
||||
<Page x:Class="Marechai.App.Presentation.Views.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>
|
||||
37
Marechai.App/Presentation/Views/ComputersListPage.xaml.cs
Normal file
37
Marechai.App/Presentation/Views/ComputersListPage.xaml.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
311
Marechai.App/Presentation/Views/ComputersPage.xaml
Normal file
311
Marechai.App/Presentation/Views/ComputersPage.xaml
Normal file
@@ -0,0 +1,311 @@
|
||||
<?xml version="1.0"
|
||||
encoding="utf-8"?>
|
||||
|
||||
<Page x:Class="Marechai.App.Presentation.Views.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>
|
||||
<DataTemplate>
|
||||
<Button Content="{Binding}"
|
||||
Command="{Binding DataContext.NavigateByLetterCommand, ElementName=PageRoot}"
|
||||
CommandParameter="{Binding}"
|
||||
Style="{StaticResource KeyboardKeyButtonStyle}" />
|
||||
</DataTemplate>
|
||||
</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>
|
||||
<DataTemplate>
|
||||
<Button Content="{Binding}"
|
||||
Command="{Binding DataContext.NavigateByYearCommand, ElementName=PageRoot}"
|
||||
CommandParameter="{Binding}"
|
||||
Style="{StaticResource KeyboardKeyButtonStyle}" />
|
||||
</DataTemplate>
|
||||
</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>
|
||||
44
Marechai.App/Presentation/Views/ComputersPage.xaml.cs
Normal file
44
Marechai.App/Presentation/Views/ComputersPage.xaml.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
261
Marechai.App/Presentation/Views/ConsolesListPage.xaml
Normal file
261
Marechai.App/Presentation/Views/ConsolesListPage.xaml
Normal file
@@ -0,0 +1,261 @@
|
||||
<?xml version="1.0"
|
||||
encoding="utf-8"?>
|
||||
|
||||
<Page x:Class="Marechai.App.Presentation.Views.ConsolesListPage"
|
||||
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 consoles..."
|
||||
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 Consoles"
|
||||
Message="{Binding ErrorMessage}"
|
||||
IsClosable="False" />
|
||||
<Button Content="Retry"
|
||||
Command="{Binding LoadData}"
|
||||
HorizontalAlignment="Center"
|
||||
Style="{ThemeResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Consoles 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 ConsolesList.Count}" />
|
||||
<TextBlock FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
Text="consoles" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Consoles List -->
|
||||
<ItemsControl Grid.Row="1"
|
||||
ItemsSource="{Binding ConsolesList}"
|
||||
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.NavigateToConsoleCommand, 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>
|
||||
|
||||
<!-- Console 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>
|
||||
37
Marechai.App/Presentation/Views/ConsolesListPage.xaml.cs
Normal file
37
Marechai.App/Presentation/Views/ConsolesListPage.xaml.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
/// <summary>
|
||||
/// Professional list view for displaying consoles filtered by letter, year, or all.
|
||||
/// Features responsive layout, modern styling, and smooth navigation.
|
||||
/// </summary>
|
||||
public sealed partial class ConsolesListPage : Page
|
||||
{
|
||||
public ConsolesListPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += ConsolesListPage_Loaded;
|
||||
DataContextChanged += ConsolesListPage_DataContextChanged;
|
||||
}
|
||||
|
||||
private void ConsolesListPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
|
||||
{
|
||||
if(DataContext is ConsolesListViewModel vm)
|
||||
{
|
||||
// Load data when DataContext is set
|
||||
vm.LoadData.Execute(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConsolesListPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(DataContext is ConsolesListViewModel vm)
|
||||
{
|
||||
// Load data when page is loaded (fallback)
|
||||
vm.LoadData.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
311
Marechai.App/Presentation/Views/ConsolesPage.xaml
Normal file
311
Marechai.App/Presentation/Views/ConsolesPage.xaml
Normal file
@@ -0,0 +1,311 @@
|
||||
<?xml version="1.0"
|
||||
encoding="utf-8"?>
|
||||
|
||||
<Page x:Class="Marechai.App.Presentation.Views.ConsolesPage"
|
||||
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">
|
||||
|
||||
<!-- Console Count Display -->
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
Spacing="8">
|
||||
<TextBlock Text="{Binding ConsoleCountText}"
|
||||
TextAlignment="Center"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding ConsoleCount}"
|
||||
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>
|
||||
<DataTemplate>
|
||||
<Button Content="{Binding}"
|
||||
Command="{Binding DataContext.NavigateByLetterCommand, ElementName=PageRoot}"
|
||||
CommandParameter="{Binding}"
|
||||
Style="{StaticResource KeyboardKeyButtonStyle}" />
|
||||
</DataTemplate>
|
||||
</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>
|
||||
<DataTemplate>
|
||||
<Button Content="{Binding}"
|
||||
Command="{Binding DataContext.NavigateByYearCommand, ElementName=PageRoot}"
|
||||
CommandParameter="{Binding}"
|
||||
Style="{StaticResource KeyboardKeyButtonStyle}" />
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</StackPanel>
|
||||
|
||||
<!-- All Consoles and Search Section -->
|
||||
<StackPanel Spacing="12">
|
||||
<Button Content="All Consoles"
|
||||
Padding="16,12"
|
||||
HorizontalAlignment="Stretch"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Command="{Binding NavigateAllConsolesCommand}"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
|
||||
<!-- Search Field (placeholder for future implementation) -->
|
||||
<TextBox PlaceholderText="Search consoles..."
|
||||
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>
|
||||
44
Marechai.App/Presentation/Views/ConsolesPage.xaml.cs
Normal file
44
Marechai.App/Presentation/Views/ConsolesPage.xaml.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
public sealed partial class ConsolesPage : Page
|
||||
{
|
||||
public ConsolesPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContextChanged += ConsolesPage_DataContextChanged;
|
||||
Loaded += ConsolesPage_Loaded;
|
||||
}
|
||||
|
||||
private void ConsolesPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(DataContext is not ConsolesViewModel viewModel) return;
|
||||
|
||||
// Trigger data loading
|
||||
_ = viewModel.LoadData.ExecuteAsync(null);
|
||||
}
|
||||
|
||||
private void ConsolesPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
|
||||
{
|
||||
if(args.NewValue is ConsolesViewModel viewModel)
|
||||
{
|
||||
// Trigger data loading when data context changes
|
||||
_ = viewModel.LoadData.ExecuteAsync(null);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
if(DataContext is ConsolesViewModel viewModel)
|
||||
{
|
||||
// Trigger data loading when navigating to the page
|
||||
_ = viewModel.LoadData.ExecuteAsync(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
374
Marechai.App/Presentation/Views/GpuDetailPage.xaml
Normal file
374
Marechai.App/Presentation/Views/GpuDetailPage.xaml
Normal file
@@ -0,0 +1,374 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Page x:Class="Marechai.App.Presentation.Views.GpuDetailPage"
|
||||
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"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
|
||||
NavigationCacheMode="Required"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Page.Resources>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid utu:SafeArea.Insets="VisibleBounds">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header -->
|
||||
<utu:NavigationBar Grid.Row="0"
|
||||
Content="{Binding Path=Title}"
|
||||
MainCommandMode="Action">
|
||||
<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="16">
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- GPU Details -->
|
||||
<StackPanel Visibility="{Binding IsDataLoaded}"
|
||||
Spacing="16">
|
||||
|
||||
<!-- GPU Name -->
|
||||
<TextBlock Text="{Binding Gpu.Name}"
|
||||
FontSize="28"
|
||||
FontWeight="Bold"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<!-- Company/Manufacturer -->
|
||||
<StackPanel Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Manufacturer"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding ManufacturerName}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Model Code -->
|
||||
<StackPanel Visibility="{Binding Gpu.ModelCode, Converter={StaticResource StringToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Model Code"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Gpu.ModelCode}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Introduced Date -->
|
||||
<StackPanel Visibility="{Binding Gpu.Introduced, Converter={StaticResource ObjectToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Introduced"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Gpu.Introduced}"
|
||||
FontSize="14" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Package -->
|
||||
<StackPanel Visibility="{Binding Gpu.Package, Converter={StaticResource StringToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Package"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Gpu.Package}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Process -->
|
||||
<StackPanel Visibility="{Binding Gpu.Process, Converter={StaticResource StringToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Process"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Gpu.Process}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Process (nm) -->
|
||||
<StackPanel Visibility="{Binding Gpu.ProcessNm, Converter={StaticResource StringToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Process (nm)"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Gpu.ProcessNm}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Die Size -->
|
||||
<StackPanel Visibility="{Binding Gpu.DieSize, Converter={StaticResource ObjectToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Die Size (mm²)"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Gpu.DieSize}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Transistors -->
|
||||
<StackPanel Visibility="{Binding Gpu.Transistors, Converter={StaticResource ObjectToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Transistors"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Gpu.Transistors}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Resolutions Section -->
|
||||
<StackPanel Visibility="{Binding Resolutions.Count, Converter={StaticResource ZeroToVisibilityConverter}}"
|
||||
Spacing="8">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Supported Resolutions"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Resolutions.Count}"
|
||||
FontSize="14"
|
||||
FontWeight="Bold"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Scrollable Resolutions List -->
|
||||
<ScrollViewer Height="200"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{ThemeResource ControlElevationBorderBrush}"
|
||||
CornerRadius="8">
|
||||
<ItemsRepeater ItemsSource="{Binding Resolutions}"
|
||||
Margin="0">
|
||||
<ItemsRepeater.Layout>
|
||||
<StackLayout Spacing="4" />
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Padding="12,8"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="4"
|
||||
Margin="0,4">
|
||||
<StackPanel Spacing="4">
|
||||
<!-- First line: Resolution dimensions or format -->
|
||||
<TextBlock Text="{Binding ResolutionDisplay}"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold" />
|
||||
<!-- Second line: Color/palette information -->
|
||||
<TextBlock Text="{Binding ColorDisplay}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Computers Section -->
|
||||
<StackPanel Visibility="{Binding HasComputers}"
|
||||
Spacing="8">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Computers"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Computers.Count}"
|
||||
FontSize="14"
|
||||
FontWeight="Bold"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Filter Box -->
|
||||
<AutoSuggestBox PlaceholderText="Filter computers..."
|
||||
Text="{Binding ComputersFilterText, Mode=TwoWay}"
|
||||
TextChanged="ComputersSearchBox_TextChanged"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{ThemeResource ControlElevationBorderBrush}" />
|
||||
|
||||
<!-- Scrollable Computers List -->
|
||||
<ScrollViewer Height="200"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{ThemeResource ControlElevationBorderBrush}"
|
||||
CornerRadius="8">
|
||||
<ItemsRepeater ItemsSource="{Binding FilteredComputers}"
|
||||
Margin="0">
|
||||
<ItemsRepeater.Layout>
|
||||
<StackLayout Spacing="4" />
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Click="Computer_Click"
|
||||
Tag="{Binding Id}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="12"
|
||||
Margin="0,4">
|
||||
<StackPanel Spacing="4"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock Text="{Binding Name}"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold" />
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<TextBlock Text="{Binding Manufacturer}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding YearDisplay}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Consoles Section -->
|
||||
<StackPanel Visibility="{Binding HasConsoles}"
|
||||
Spacing="8">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Consoles"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Consoles.Count}"
|
||||
FontSize="14"
|
||||
FontWeight="Bold"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Filter Box -->
|
||||
<AutoSuggestBox PlaceholderText="Filter consoles..."
|
||||
Text="{Binding ConsoelsFilterText, Mode=TwoWay}"
|
||||
TextChanged="ConsolesSearchBox_TextChanged"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{ThemeResource ControlElevationBorderBrush}" />
|
||||
|
||||
<!-- Scrollable Consoles List -->
|
||||
<ScrollViewer Height="200"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{ThemeResource ControlElevationBorderBrush}"
|
||||
CornerRadius="8">
|
||||
<ItemsRepeater ItemsSource="{Binding FilteredConsoles}"
|
||||
Margin="0">
|
||||
<ItemsRepeater.Layout>
|
||||
<StackLayout Spacing="4" />
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Click="Console_Click"
|
||||
Tag="{Binding Id}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="12"
|
||||
Margin="0,4">
|
||||
<StackPanel Spacing="4"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock Text="{Binding Name}"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold" />
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<TextBlock Text="{Binding Manufacturer}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding YearDisplay}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Page>
|
||||
98
Marechai.App/Presentation/Views/GpuDetailPage.xaml.cs
Normal file
98
Marechai.App/Presentation/Views/GpuDetailPage.xaml.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
#nullable enable
|
||||
|
||||
using Marechai.App.Presentation.Models;
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
/// <summary>
|
||||
/// GPU detail page showing all information, resolutions, computers, and consoles
|
||||
/// </summary>
|
||||
public sealed partial class GpuDetailPage : Page
|
||||
{
|
||||
private object? _navigationSource;
|
||||
private int? _pendingGpuId;
|
||||
|
||||
public GpuDetailPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContextChanged += GpuDetailPage_DataContextChanged;
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
int? gpuId = null;
|
||||
|
||||
// Handle both int and GpuDetailNavigationParameter
|
||||
if(e.Parameter is int intId)
|
||||
gpuId = intId;
|
||||
else if(e.Parameter is GpuDetailNavigationParameter navParam)
|
||||
{
|
||||
gpuId = navParam.GpuId;
|
||||
_navigationSource = navParam.NavigationSource;
|
||||
}
|
||||
|
||||
if(gpuId.HasValue)
|
||||
{
|
||||
_pendingGpuId = gpuId;
|
||||
|
||||
if(DataContext is GpuDetailViewModel viewModel)
|
||||
{
|
||||
viewModel.GpuId = gpuId.Value;
|
||||
if(_navigationSource != null) viewModel.SetNavigationSource(_navigationSource);
|
||||
_ = viewModel.LoadData.ExecuteAsync(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GpuDetailPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
|
||||
{
|
||||
if(DataContext is GpuDetailViewModel viewModel && _pendingGpuId.HasValue)
|
||||
{
|
||||
viewModel.GpuId = _pendingGpuId.Value;
|
||||
if(_navigationSource != null) viewModel.SetNavigationSource(_navigationSource);
|
||||
_ = viewModel.LoadData.ExecuteAsync(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void ComputersSearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
|
||||
{
|
||||
if(DataContext is GpuDetailViewModel vm) vm.ComputersFilterCommand.Execute(null);
|
||||
}
|
||||
|
||||
private void ComputersSearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
|
||||
{
|
||||
if(args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
|
||||
if(DataContext is GpuDetailViewModel vm)
|
||||
vm.ComputersFilterCommand.Execute(null);
|
||||
}
|
||||
|
||||
private void ConsolesSearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
|
||||
{
|
||||
if(DataContext is GpuDetailViewModel vm) vm.ConsolesFilterCommand.Execute(null);
|
||||
}
|
||||
|
||||
private void ConsolesSearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
|
||||
{
|
||||
if(args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
|
||||
if(DataContext is GpuDetailViewModel vm)
|
||||
vm.ConsolesFilterCommand.Execute(null);
|
||||
}
|
||||
|
||||
private void Computer_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(sender is Button button && button.Tag is int machineId && DataContext is GpuDetailViewModel vm)
|
||||
_ = vm.SelectMachineCommand.ExecuteAsync(machineId);
|
||||
}
|
||||
|
||||
private void Console_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(sender is Button button && button.Tag is int machineId && DataContext is GpuDetailViewModel vm)
|
||||
_ = vm.SelectMachineCommand.ExecuteAsync(machineId);
|
||||
}
|
||||
}
|
||||
207
Marechai.App/Presentation/Views/GpuListPage.xaml
Normal file
207
Marechai.App/Presentation/Views/GpuListPage.xaml
Normal file
@@ -0,0 +1,207 @@
|
||||
<?xml version="1.0"
|
||||
encoding="utf-8"?>
|
||||
|
||||
<Page x:Class="Marechai.App.Presentation.Views.GpuListPage"
|
||||
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="Graphics Processing Units">
|
||||
</utu:NavigationBar>
|
||||
|
||||
<!-- 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 graphics processing units..."
|
||||
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 Graphics Processing Units"
|
||||
Message="{Binding ErrorMessage}"
|
||||
IsClosable="False" />
|
||||
<Button Content="Retry"
|
||||
Command="{Binding LoadData}"
|
||||
HorizontalAlignment="Center"
|
||||
Style="{ThemeResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- GPUs 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 GpusList.Count}" />
|
||||
<TextBlock FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
Text="graphics processing units" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- GPUs List -->
|
||||
<ItemsControl Grid.Row="1"
|
||||
ItemsSource="{Binding GpusList}"
|
||||
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.NavigateToGpuCommand, 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>
|
||||
|
||||
<!-- GPU Info -->
|
||||
<StackPanel Grid.Column="0"
|
||||
Spacing="8"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Stretch">
|
||||
<TextBlock Text="{Binding Name}"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="6"
|
||||
VerticalAlignment="Center">
|
||||
<FontIcon Glyph=""
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
<TextBlock Text="{Binding Company}"
|
||||
FontSize="13"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
</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>
|
||||
37
Marechai.App/Presentation/Views/GpuListPage.xaml.cs
Normal file
37
Marechai.App/Presentation/Views/GpuListPage.xaml.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
/// <summary>
|
||||
/// Professional list view for displaying all graphics processing units.
|
||||
/// Features responsive layout, modern styling, and special handling for Framebuffer and Software entries.
|
||||
/// </summary>
|
||||
public sealed partial class GpuListPage : Page
|
||||
{
|
||||
public GpuListPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += GpuListPage_Loaded;
|
||||
DataContextChanged += GpuListPage_DataContextChanged;
|
||||
}
|
||||
|
||||
private void GpuListPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
|
||||
{
|
||||
if(DataContext is GpusListViewModel vm)
|
||||
{
|
||||
// Load data when DataContext is set
|
||||
vm.LoadData.Execute(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void GpuListPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(DataContext is GpusListViewModel vm)
|
||||
{
|
||||
// Load data when page is loaded (fallback)
|
||||
vm.LoadData.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
209
Marechai.App/Presentation/Views/LoginPage.xaml
Normal file
209
Marechai.App/Presentation/Views/LoginPage.xaml
Normal file
@@ -0,0 +1,209 @@
|
||||
<?xml version="1.0"
|
||||
encoding="utf-8"?>
|
||||
|
||||
<Page x:Class="Marechai.App.Presentation.Views.LoginPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:Marechai.App.Presentation.ViewModels"
|
||||
xmlns:utu="using:Uno.Toolkit.UI"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
d:DataContext="{d:DesignInstance vm:LoginViewModel}">
|
||||
|
||||
<Grid utu:SafeArea.Insets="VisibleBounds">
|
||||
<!-- Center content on screen -->
|
||||
<Grid HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
MaxWidth="480"
|
||||
Padding="32">
|
||||
|
||||
<!-- Login Card with Mac OS 9 styling -->
|
||||
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="2"
|
||||
CornerRadius="8"
|
||||
Padding="0"
|
||||
Translation="0,0,16">
|
||||
<Border.Shadow>
|
||||
<ThemeShadow />
|
||||
</Border.Shadow>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header with Mac OS 9 Title Bar Style -->
|
||||
<Border Grid.Row="0"
|
||||
Background="{ThemeResource AccentFillColorDefaultBrush}"
|
||||
Padding="16,12"
|
||||
CornerRadius="6,6,0,0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Icon -->
|
||||
<FontIcon Grid.Column="0"
|
||||
Glyph=""
|
||||
FontSize="24"
|
||||
Foreground="White"
|
||||
Margin="0,0,12,0"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock Grid.Column="1"
|
||||
x:Uid="LoginPage_Title"
|
||||
Text="Sign In to Marechai"
|
||||
Style="{StaticResource TitleTextBlockStyle}"
|
||||
Foreground="White"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Login Form Content -->
|
||||
<ScrollViewer Grid.Row="1"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Padding="32"
|
||||
Spacing="24">
|
||||
|
||||
<!-- Welcome Message -->
|
||||
<TextBlock x:Uid="LoginPage_WelcomeMessage"
|
||||
Text="Welcome back! Please sign in to continue."
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
TextWrapping="Wrap"
|
||||
HorizontalAlignment="Center"
|
||||
TextAlignment="Center" />
|
||||
|
||||
<!-- Email Input -->
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock x:Uid="LoginPage_EmailLabel"
|
||||
Text="Email Address"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
<TextBox x:Name="EmailTextBox"
|
||||
x:Uid="LoginPage_EmailTextBox"
|
||||
Text="{Binding Email, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
PlaceholderText="Enter your email"
|
||||
InputScope="EmailSmtpAddress"
|
||||
IsSpellCheckEnabled="False"
|
||||
AutomationProperties.Name="Email address"
|
||||
KeyDown="OnEmailKeyDown" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Password Input -->
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock x:Uid="LoginPage_PasswordLabel"
|
||||
Text="Password"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
<PasswordBox x:Name="PasswordBox"
|
||||
x:Uid="LoginPage_PasswordBox"
|
||||
Password="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
PlaceholderText="Enter your password"
|
||||
AutomationProperties.Name="Password"
|
||||
KeyDown="OnPasswordKeyDown" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Error Message -->
|
||||
<Border x:Name="ErrorBorder"
|
||||
Background="#FFF4E6"
|
||||
BorderBrush="#FFB84D"
|
||||
BorderThickness="2"
|
||||
CornerRadius="4"
|
||||
Padding="12"
|
||||
Visibility="{Binding ErrorMessage, Converter={StaticResource NullToVisibilityConverter}}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Warning Icon -->
|
||||
<FontIcon Grid.Column="0"
|
||||
Glyph=""
|
||||
Foreground="#D97706"
|
||||
FontSize="20"
|
||||
Margin="0,0,8,0"
|
||||
VerticalAlignment="Top" />
|
||||
|
||||
<!-- Error Text -->
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{Binding ErrorMessage}"
|
||||
Foreground="#92400E"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<!-- Close Button -->
|
||||
<Button Grid.Column="2"
|
||||
Command="{Binding ClearErrorCommand}"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Padding="8"
|
||||
Margin="8,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="Close error message">
|
||||
<FontIcon Glyph=""
|
||||
FontSize="12"
|
||||
Foreground="#92400E" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Login Button -->
|
||||
<Button x:Name="LoginButton"
|
||||
x:Uid="LoginPage_LoginButton"
|
||||
Command="{Binding LoginCommand}"
|
||||
IsEnabled="{Binding IsLoggingIn, Converter={StaticResource InvertBoolConverter}}"
|
||||
HorizontalAlignment="Stretch"
|
||||
Padding="16,12"
|
||||
Style="{StaticResource AccentButtonStyle}"
|
||||
AutomationProperties.Name="Sign in button">
|
||||
<Button.Content>
|
||||
<Grid>
|
||||
<TextBlock x:Uid="LoginPage_LoginButtonText"
|
||||
Text="Sign In"
|
||||
Visibility="{Binding IsLoggingIn, Converter={StaticResource InvertBoolToVisibilityConverter}}" />
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Center"
|
||||
Spacing="8"
|
||||
Visibility="{Binding IsLoggingIn, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<ProgressRing IsActive="True"
|
||||
Width="20"
|
||||
Height="20"
|
||||
Foreground="White" />
|
||||
<TextBlock x:Uid="LoginPage_SigningInText"
|
||||
Text="Signing in..."
|
||||
Foreground="White" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
|
||||
<!-- Additional Options -->
|
||||
<StackPanel Spacing="8"
|
||||
HorizontalAlignment="Center">
|
||||
<HyperlinkButton x:Uid="LoginPage_ForgotPasswordLink"
|
||||
Content="Forgot your password?"
|
||||
HorizontalAlignment="Center"
|
||||
Visibility="Collapsed" />
|
||||
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Center"
|
||||
Spacing="4"
|
||||
Visibility="Collapsed">
|
||||
<TextBlock x:Uid="LoginPage_NoAccountText"
|
||||
Text="Don't have an account?"
|
||||
VerticalAlignment="Center" />
|
||||
<HyperlinkButton x:Uid="LoginPage_SignUpLink"
|
||||
Content="Sign up"
|
||||
Padding="4,0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Page>
|
||||
33
Marechai.App/Presentation/Views/LoginPage.xaml.cs
Normal file
33
Marechai.App/Presentation/Views/LoginPage.xaml.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Windows.System;
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
public sealed partial class LoginPage : Page
|
||||
{
|
||||
public LoginPage() => InitializeComponent();
|
||||
|
||||
private void OnEmailKeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if(e.Key == VirtualKey.Enter)
|
||||
{
|
||||
// Move focus to password field
|
||||
PasswordBox.Focus(FocusState.Keyboard);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPasswordKeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if(e.Key == VirtualKey.Enter)
|
||||
{
|
||||
// Trigger login when Enter is pressed
|
||||
if(DataContext is LoginViewModel viewModel && LoginButton.IsEnabled) viewModel.LoginCommand.Execute(null);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
422
Marechai.App/Presentation/Views/MachineViewPage.xaml
Normal file
422
Marechai.App/Presentation/Views/MachineViewPage.xaml
Normal file
@@ -0,0 +1,422 @@
|
||||
<?xml version="1.0"
|
||||
encoding="utf-8"?>
|
||||
|
||||
<Page x:Class="Marechai.App.Presentation.Views.MachineViewPage"
|
||||
x:Name="PageRoot"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:wctui="using:CommunityToolkit.WinUI.UI.Controls"
|
||||
NavigationCacheMode="Disabled"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid RowSpacing="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header with Back Button -->
|
||||
<Grid Grid.Row="0"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
BorderThickness="0,0,0,1"
|
||||
Padding="12,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>
|
||||
|
||||
<!-- Title Section -->
|
||||
<StackPanel Grid.Column="1"
|
||||
Margin="12,0,0,0"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding MachineName}"
|
||||
FontSize="20"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock Text="{Binding CompanyName}"
|
||||
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 machine details..."
|
||||
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 Machine"
|
||||
Message="{Binding ErrorMessage}"
|
||||
IsClosable="False" />
|
||||
<Button Content="Retry"
|
||||
Command="{Binding LoadData}"
|
||||
HorizontalAlignment="Center"
|
||||
Style="{ThemeResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Machine Details -->
|
||||
<ScrollViewer Visibility="{Binding IsDataLoaded}"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Padding="16"
|
||||
Spacing="24">
|
||||
|
||||
<!-- Prototype Badge -->
|
||||
<StackPanel Visibility="{Binding IsPrototype}">
|
||||
<Border Background="{ThemeResource WarningFillColorTertiaryBrush}"
|
||||
BorderBrush="{ThemeResource WarningBorderColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Padding="16,12">
|
||||
<TextBlock Text="PROTOTYPE"
|
||||
FontSize="14"
|
||||
FontWeight="Bold"
|
||||
Foreground="{ThemeResource WarningForegroundColorDefaultBrush}"
|
||||
TextAlignment="Center" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Introduction Date -->
|
||||
<StackPanel Visibility="{Binding ShowIntroductionDate}"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Introduction Date"
|
||||
FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Padding="16,12">
|
||||
<TextBlock Text="{Binding IntroductionDateDisplay}"
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource TextControlForeground}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Family and Model -->
|
||||
<Grid Visibility="{Binding ShowFamilyOrModel}"
|
||||
ColumnSpacing="16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Family -->
|
||||
<StackPanel Grid.Column="0"
|
||||
Visibility="{Binding ShowFamily}"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Family"
|
||||
FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Padding="16,12">
|
||||
<TextBlock Text="{Binding FamilyName}"
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource TextControlForeground}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Model -->
|
||||
<StackPanel Grid.Column="1"
|
||||
Visibility="{Binding ShowModel}"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Model"
|
||||
FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Padding="16,12">
|
||||
<TextBlock Text="{Binding ModelName}"
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource TextControlForeground}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Processors Section -->
|
||||
<StackPanel Visibility="{Binding ShowProcessors}"
|
||||
Spacing="12">
|
||||
<TextBlock Text="Processors"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}" />
|
||||
<ItemsControl ItemsSource="{Binding Processors}"
|
||||
HorizontalAlignment="Stretch">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="8" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Padding="16,12">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="{Binding DisplayName}"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}" />
|
||||
<TextBlock Text="{Binding Manufacturer}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding DetailsText}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{Binding HasDetails}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Memory Section -->
|
||||
<StackPanel Visibility="{Binding ShowMemory}"
|
||||
Spacing="12">
|
||||
<TextBlock Text="Memory"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}" />
|
||||
<ItemsControl ItemsSource="{Binding Memory}"
|
||||
HorizontalAlignment="Stretch">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="8" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Padding="16,12">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock Text="{Binding SizeDisplay}"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}" />
|
||||
<TextBlock Text="{Binding TypeDisplay}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
|
||||
<!-- GPUs Section -->
|
||||
<StackPanel Visibility="{Binding ShowGpus}"
|
||||
Spacing="12">
|
||||
<TextBlock Text="Graphics Processing Units"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}" />
|
||||
<ItemsControl ItemsSource="{Binding Gpus}"
|
||||
HorizontalAlignment="Stretch">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="8" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Padding="16,12">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="{Binding DisplayName}"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}" />
|
||||
<TextBlock Text="{Binding Manufacturer}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
Visibility="{Binding HasManufacturer}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Sound Synthesizers Section -->
|
||||
<StackPanel Visibility="{Binding ShowSoundSynthesizers}"
|
||||
Spacing="12">
|
||||
<TextBlock Text="Sound Synthesizers"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}" />
|
||||
<ItemsControl ItemsSource="{Binding SoundSynthesizers}"
|
||||
HorizontalAlignment="Stretch">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="8" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Padding="16,12">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="{Binding DisplayName}"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}" />
|
||||
<TextBlock Text="{Binding DetailsText}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{Binding HasDetails}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Storage Section -->
|
||||
<StackPanel Visibility="{Binding ShowStorage}"
|
||||
Spacing="12">
|
||||
<TextBlock Text="Storage"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}" />
|
||||
<ItemsControl ItemsSource="{Binding Storage}"
|
||||
HorizontalAlignment="Stretch">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="8" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Padding="16,12">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock Text="{Binding DisplayText}"
|
||||
FontSize="13"
|
||||
Foreground="{ThemeResource TextControlForeground}"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock Text="{Binding TypeNote}"
|
||||
FontSize="11"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Photos Carousel (Last element before spacing) -->
|
||||
<StackPanel Visibility="{Binding ShowPhotos}"
|
||||
Spacing="12">
|
||||
<TextBlock Text="Photos"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}" />
|
||||
<wctui:Carousel ItemsSource="{Binding Photos}"
|
||||
Height="280"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
HorizontalAlignment="Stretch">
|
||||
<wctui:Carousel.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Command="{Binding DataContext.ViewPhotoDetailsCommand, ElementName=PageRoot}"
|
||||
CommandParameter="{Binding PhotoId}"
|
||||
Padding="0"
|
||||
Background="Transparent">
|
||||
<Grid Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<Image Source="{Binding ThumbnailImageSource}"
|
||||
Stretch="Uniform"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
MaxWidth="256"
|
||||
MaxHeight="256" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</wctui:Carousel.ItemTemplate>
|
||||
</wctui:Carousel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Bottom Spacing -->
|
||||
<Border Height="24" />
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Page>
|
||||
82
Marechai.App/Presentation/Views/MachineViewPage.xaml.cs
Normal file
82
Marechai.App/Presentation/Views/MachineViewPage.xaml.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
/******************************************************************************
|
||||
// MARECHAI: Master repository of computing history artifacts information
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2003-2026 Natalia Portillo
|
||||
*******************************************************************************/
|
||||
|
||||
#nullable enable
|
||||
|
||||
using Marechai.App.Presentation.Models;
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
public sealed partial class MachineViewPage : Page
|
||||
{
|
||||
private object? _navigationSource;
|
||||
private int? _pendingMachineId;
|
||||
|
||||
public MachineViewPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContextChanged += MachineViewPage_DataContextChanged;
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
int? machineId = null;
|
||||
|
||||
// Handle both int and MachineViewNavigationParameter
|
||||
if(e.Parameter is int intId)
|
||||
machineId = intId;
|
||||
else if(e.Parameter is MachineViewNavigationParameter navParam)
|
||||
{
|
||||
machineId = navParam.MachineId;
|
||||
_navigationSource = navParam.NavigationSource;
|
||||
}
|
||||
|
||||
if(machineId.HasValue)
|
||||
{
|
||||
_pendingMachineId = machineId;
|
||||
|
||||
if(DataContext is MachineViewViewModel viewModel)
|
||||
{
|
||||
viewModel.SetNavigationSource(_navigationSource);
|
||||
_ = viewModel.LoadMachineAsync(machineId.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MachineViewPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
|
||||
{
|
||||
if(DataContext is MachineViewViewModel viewModel && _pendingMachineId.HasValue)
|
||||
{
|
||||
viewModel.SetNavigationSource(_navigationSource);
|
||||
_ = viewModel.LoadMachineAsync(_pendingMachineId.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
55
Marechai.App/Presentation/Views/MainPage.xaml
Normal file
55
Marechai.App/Presentation/Views/MainPage.xaml
Normal file
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0"
|
||||
encoding="utf-8"?>
|
||||
|
||||
<Page x:Class="Marechai.App.Presentation.Views.MainPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:utu="using:Uno.Toolkit.UI"
|
||||
xmlns:uen="using:Uno.Extensions.Navigation.UI"
|
||||
xmlns:components="clr-namespace:Marechai.App.Presentation.Components"
|
||||
NavigationCacheMode="Required"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid utu:SafeArea.Insets="VisibleBounds">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition x:Name="SidebarColumn"
|
||||
Width="280" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<Grid x:Name="SidebarWrapper"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="0"
|
||||
Width="280"
|
||||
HorizontalAlignment="Left">
|
||||
<components:Sidebar x:Name="SidebarPanel"
|
||||
DataContext="{Binding}"
|
||||
VerticalAlignment="Stretch" />
|
||||
</Grid>
|
||||
|
||||
<!-- Header -->
|
||||
<utu:NavigationBar Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Content="{Binding Title}">
|
||||
<utu:NavigationBar.MainCommand>
|
||||
<AppBarButton Icon="GlobalNavigationButton"
|
||||
Command="{Binding ToggleSidebarCommand}"
|
||||
Label="Toggle Sidebar"
|
||||
AutomationProperties.Name="Toggle sidebar visibility" />
|
||||
</utu:NavigationBar.MainCommand>
|
||||
</utu:NavigationBar>
|
||||
|
||||
<!-- 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>
|
||||
98
Marechai.App/Presentation/Views/MainPage.xaml.cs
Normal file
98
Marechai.App/Presentation/Views/MainPage.xaml.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
public sealed partial class MainPage : Page
|
||||
{
|
||||
private PropertyChangedEventHandler _sidebarPropertyChangedHandler;
|
||||
|
||||
public MainPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContextChanged += MainPage_DataContextChanged;
|
||||
Loaded += MainPage_Loaded;
|
||||
}
|
||||
|
||||
private void MainPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(DataContext is not MainViewModel viewModel) return;
|
||||
|
||||
SidebarWrapper.Width = viewModel.IsSidebarOpen ? 280 : 60;
|
||||
|
||||
if(_sidebarPropertyChangedHandler != null) return;
|
||||
|
||||
_sidebarPropertyChangedHandler = (_, propArgs) =>
|
||||
{
|
||||
if(propArgs.PropertyName != nameof(MainViewModel.IsSidebarOpen)) return;
|
||||
|
||||
AnimateSidebarWidth(((MainViewModel)DataContext).IsSidebarOpen);
|
||||
};
|
||||
|
||||
((INotifyPropertyChanged)viewModel).PropertyChanged += _sidebarPropertyChangedHandler;
|
||||
}
|
||||
|
||||
void AnimateSidebarWidth(bool isOpen)
|
||||
{
|
||||
double start = SidebarColumn.Width.Value;
|
||||
double end = isOpen ? 280 : 60;
|
||||
|
||||
if(Math.Abs(start - end) < 0.1) return;
|
||||
|
||||
// If expanding, show content immediately
|
||||
if(isOpen && DataContext is MainViewModel vm) vm.SidebarContentVisible = true;
|
||||
|
||||
const int durationMs = 250;
|
||||
const int fps = 60;
|
||||
var steps = (int)(durationMs / (1000.0 / fps));
|
||||
var currentStep = 0;
|
||||
|
||||
var timer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromMilliseconds(1000.0 / fps)
|
||||
};
|
||||
|
||||
timer.Tick += (_, _) =>
|
||||
{
|
||||
currentStep++;
|
||||
double t = (double)currentStep / steps;
|
||||
|
||||
// Ease in-out cubic
|
||||
double eased = t < 0.5 ? 4 * t * t * t : 1 - Math.Pow(-2 * t + 2, 3) / 2;
|
||||
double value = start + (end - start) * eased;
|
||||
SidebarColumn.Width = new GridLength(value, GridUnitType.Pixel);
|
||||
SidebarWrapper.Width = value;
|
||||
|
||||
if(currentStep >= steps)
|
||||
{
|
||||
SidebarColumn.Width = new GridLength(end, GridUnitType.Pixel);
|
||||
SidebarWrapper.Width = end;
|
||||
timer.Stop();
|
||||
|
||||
// After collapse animation completes, hide sidebar content
|
||||
if(!isOpen && DataContext is MainViewModel vm) vm.SidebarContentVisible = false;
|
||||
}
|
||||
};
|
||||
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
private void MainPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
|
||||
{
|
||||
if(args.NewValue is MainViewModel vm && _sidebarPropertyChangedHandler == null)
|
||||
{
|
||||
SidebarWrapper.Width = vm.IsSidebarOpen ? 280 : 60;
|
||||
|
||||
_sidebarPropertyChangedHandler = (_, propArgs) =>
|
||||
{
|
||||
if(propArgs.PropertyName != nameof(MainViewModel.IsSidebarOpen)) return;
|
||||
AnimateSidebarWidth(vm.IsSidebarOpen);
|
||||
};
|
||||
|
||||
((INotifyPropertyChanged)vm).PropertyChanged += _sidebarPropertyChangedHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
130
Marechai.App/Presentation/Views/NewsPage.xaml
Normal file
130
Marechai.App/Presentation/Views/NewsPage.xaml
Normal file
@@ -0,0 +1,130 @@
|
||||
<?xml version="1.0"
|
||||
encoding="utf-8"?>
|
||||
|
||||
<Page x:Class="Marechai.App.Presentation.Views.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>
|
||||
<Button Margin="0,0,0,12"
|
||||
Padding="0"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Command="{Binding NavigateToItemCommand}"
|
||||
CommandParameter="{Binding News}">
|
||||
<Border 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 -->
|
||||
<TextBlock Grid.Row="2"
|
||||
Text="{Binding News.ItemName}"
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="0" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</RefreshContainer>
|
||||
</Grid>
|
||||
</Page>
|
||||
73
Marechai.App/Presentation/Views/NewsPage.xaml.cs
Normal file
73
Marechai.App/Presentation/Views/NewsPage.xaml.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using Windows.Foundation;
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
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.
|
||||
}
|
||||
}
|
||||
}
|
||||
556
Marechai.App/Presentation/Views/PhotoDetailPage.xaml
Normal file
556
Marechai.App/Presentation/Views/PhotoDetailPage.xaml
Normal file
@@ -0,0 +1,556 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Page x:Class="Marechai.App.Presentation.Views.PhotoDetailPage"
|
||||
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="Disabled"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid RowSpacing="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header with Back Button -->
|
||||
<Grid Grid.Row="0"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
BorderThickness="0,0,0,1"
|
||||
Padding="12,12,16,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<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>
|
||||
|
||||
<StackPanel Grid.Column="1"
|
||||
Margin="12,0,0,0"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="Photo Details"
|
||||
FontSize="20"
|
||||
FontWeight="SemiBold"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
</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" />
|
||||
<TextBlock Text="Loading photo..."
|
||||
FontSize="14"
|
||||
TextAlignment="Center" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Error State -->
|
||||
<StackPanel Visibility="{Binding ErrorOccurred}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Padding="24"
|
||||
Spacing="16"
|
||||
MaxWidth="400">
|
||||
<InfoBar IsOpen="True"
|
||||
Severity="Error"
|
||||
Title="Unable to Load Photo"
|
||||
Message="{Binding ErrorMessage}"
|
||||
IsClosable="False" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Responsive Layout -->
|
||||
<utu:ResponsiveView Visibility="{Binding PhotoImageSource, Converter={StaticResource ObjectToVisibilityConverter}}">
|
||||
|
||||
<!-- Narrow Template -->
|
||||
<utu:ResponsiveView.NarrowTemplate>
|
||||
<DataTemplate>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Padding="16" Spacing="24">
|
||||
|
||||
<!-- Photo -->
|
||||
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Padding="8"
|
||||
MaxHeight="400"
|
||||
MinHeight="250">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
ZoomMode="Enabled"
|
||||
MinZoomFactor="1.0"
|
||||
MaxZoomFactor="5.0">
|
||||
<Image Source="{Binding PhotoImageSource}"
|
||||
Stretch="Uniform"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
<!-- Metadata Sections -->
|
||||
<StackPanel Spacing="16">
|
||||
|
||||
<!-- Machine -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Machine" FontSize="14" FontWeight="SemiBold" />
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoMachineName, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Name" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoMachineName}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoMachineCompany, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Company" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoMachineCompany}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Camera -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Camera" FontSize="14" FontWeight="SemiBold" />
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoCameraManufacturer, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Manufacturer" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoCameraManufacturer}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoCameraModel, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Model" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoCameraModel}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoLensModel, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Lens" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoLensModel}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoSoftwareUsed, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Software" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoSoftwareUsed}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Exposure Settings -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Exposure Settings" FontSize="14" FontWeight="SemiBold" />
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoAperture, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Aperture" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoAperture}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoExposureTime, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Exposure Time" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoExposureTime}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoExposureMode, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Exposure Mode" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoExposureMode}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoExposureProgram, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Exposure Program" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoExposureProgram}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoIsoRating, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="ISO Rating" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoIsoRating}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Flash & Light -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Flash & Light" FontSize="14" FontWeight="SemiBold" />
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoFlash, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Flash" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoFlash}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoLightSource, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Light Source" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoLightSource}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoMeteringMode, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Metering Mode" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoMeteringMode}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoWhiteBalance, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="White Balance" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoWhiteBalance}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Focal Length -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Focal Length" FontSize="14" FontWeight="SemiBold" />
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoFocalLength, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Focal Length" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoFocalLength}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoFocalLengthEquivalent, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="35mm Equivalent" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoFocalLengthEquivalent}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoDigitalZoomRatio, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Digital Zoom" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoDigitalZoomRatio}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Image Properties -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Image Properties" FontSize="14" FontWeight="SemiBold" />
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoColorSpace, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Color Space" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoColorSpace}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoContrast, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Contrast" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoContrast}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoSaturation, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Saturation" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoSaturation}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoSharpness, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Sharpness" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoSharpness}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoOrientation, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Orientation" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoOrientation}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoSceneCaptureType, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Scene Capture Type" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoSceneCaptureType}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Resolution -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Resolution" FontSize="14" FontWeight="SemiBold" />
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoHorizontalResolution, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Horizontal Resolution" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoHorizontalResolution}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoVerticalResolution, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Vertical Resolution" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoVerticalResolution}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoResolutionUnit, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Resolution Unit" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoResolutionUnit}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- File & Metadata -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="File & Metadata" FontSize="14" FontWeight="SemiBold" />
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoOriginalExtension, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Original Format" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoOriginalExtension}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoCreationDate, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Date Taken" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoCreationDate}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoUploadDate, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Upload Date" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoUploadDate}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoExifVersion, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="EXIF Version" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoExifVersion}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoAuthor, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Author" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoAuthor}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoLicenseName, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="License" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoLicenseName}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoComments, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Comments" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoComments}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoSource, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Source" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoSource}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Additional Sensors -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Advanced" FontSize="14" FontWeight="SemiBold" />
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoSensingMethod, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Sensing Method" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoSensingMethod}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoSubjectDistanceRange, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Subject Distance Range" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoSubjectDistanceRange}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<Border Height="24" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</DataTemplate>
|
||||
</utu:ResponsiveView.NarrowTemplate>
|
||||
|
||||
<!-- Wide Template -->
|
||||
<utu:ResponsiveView.WideTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Padding="16" ColumnSpacing="24">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Photo on left -->
|
||||
<Border Grid.Column="0"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Padding="8"
|
||||
VerticalAlignment="Top"
|
||||
MaxHeight="500">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
ZoomMode="Enabled"
|
||||
MinZoomFactor="1.0"
|
||||
MaxZoomFactor="5.0">
|
||||
<Image Source="{Binding PhotoImageSource}"
|
||||
Stretch="Uniform"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
<!-- Info on right -->
|
||||
<ScrollViewer Grid.Column="1"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Spacing="16" Padding="8">
|
||||
<!-- Same content as narrow template -->
|
||||
<!-- Machine -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Machine" FontSize="14" FontWeight="SemiBold" />
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoMachineName, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Name" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoMachineName}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoMachineCompany, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Company" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoMachineCompany}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Camera -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Camera" FontSize="14" FontWeight="SemiBold" />
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoCameraManufacturer, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Manufacturer" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoCameraManufacturer}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoCameraModel, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Model" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoCameraModel}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoLensModel, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Lens" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoLensModel}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoSoftwareUsed, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Software" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoSoftwareUsed}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Exposure Settings -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Exposure Settings" FontSize="14" FontWeight="SemiBold" />
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoAperture, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Aperture" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoAperture}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoExposureTime, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Exposure Time" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoExposureTime}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoExposureMode, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Exposure Mode" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoExposureMode}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoExposureProgram, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Exposure Program" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoExposureProgram}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoIsoRating, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="ISO Rating" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoIsoRating}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Flash & Light -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Flash & Light" FontSize="14" FontWeight="SemiBold" />
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoFlash, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Flash" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoFlash}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoLightSource, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Light Source" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoLightSource}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoMeteringMode, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Metering Mode" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoMeteringMode}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoWhiteBalance, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="White Balance" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoWhiteBalance}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Focal Length -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Focal Length" FontSize="14" FontWeight="SemiBold" />
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoFocalLength, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Focal Length" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoFocalLength}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoFocalLengthEquivalent, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="35mm Equivalent" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoFocalLengthEquivalent}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoDigitalZoomRatio, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Digital Zoom" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoDigitalZoomRatio}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Image Properties -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Image Properties" FontSize="14" FontWeight="SemiBold" />
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoColorSpace, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Color Space" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoColorSpace}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoContrast, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Contrast" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoContrast}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoSaturation, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Saturation" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoSaturation}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoSharpness, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Sharpness" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoSharpness}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoOrientation, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Orientation" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoOrientation}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoSceneCaptureType, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Scene Capture Type" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoSceneCaptureType}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Resolution -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Resolution" FontSize="14" FontWeight="SemiBold" />
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoHorizontalResolution, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Horizontal Resolution" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoHorizontalResolution}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoVerticalResolution, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Vertical Resolution" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoVerticalResolution}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoResolutionUnit, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Resolution Unit" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoResolutionUnit}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- File & Metadata -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="File & Metadata" FontSize="14" FontWeight="SemiBold" />
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoOriginalExtension, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Original Format" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoOriginalExtension}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoCreationDate, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Date Taken" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoCreationDate}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoUploadDate, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Upload Date" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoUploadDate}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoExifVersion, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="EXIF Version" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoExifVersion}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoAuthor, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Author" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoAuthor}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoLicenseName, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="License" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoLicenseName}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoComments, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Comments" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoComments}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoSource, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Source" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoSource}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Advanced -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Advanced" FontSize="14" FontWeight="SemiBold" />
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoSensingMethod, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Sensing Method" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoSensingMethod}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" Visibility="{Binding PhotoSubjectDistanceRange, Converter={StaticResource StringToVisibilityConverter}}">
|
||||
<TextBlock Text="Subject Distance Range" FontSize="12" FontWeight="SemiBold" Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding PhotoSubjectDistanceRange}" FontSize="13" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<Border Height="24" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</utu:ResponsiveView.WideTemplate>
|
||||
|
||||
</utu:ResponsiveView>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Page>
|
||||
47
Marechai.App/Presentation/Views/PhotoDetailPage.xaml.cs
Normal file
47
Marechai.App/Presentation/Views/PhotoDetailPage.xaml.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
public sealed partial class PhotoDetailPage : Page
|
||||
{
|
||||
private Guid? _pendingPhotoId;
|
||||
|
||||
public PhotoDetailPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContextChanged += PhotoDetailPage_DataContextChanged;
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
Guid? photoId = null;
|
||||
|
||||
if(e.Parameter is PhotoDetailNavigationParameter param) photoId = param.PhotoId;
|
||||
|
||||
if(photoId.HasValue)
|
||||
{
|
||||
_pendingPhotoId = photoId;
|
||||
|
||||
if(DataContext is PhotoDetailViewModel viewModel)
|
||||
_ = viewModel.LoadPhotoCommand.ExecuteAsync(photoId.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void PhotoDetailPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
|
||||
{
|
||||
if(DataContext is PhotoDetailViewModel viewModel && _pendingPhotoId.HasValue)
|
||||
_ = viewModel.LoadPhotoCommand.ExecuteAsync(_pendingPhotoId.Value);
|
||||
}
|
||||
|
||||
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
|
||||
{
|
||||
base.OnNavigatingFrom(e);
|
||||
_pendingPhotoId = null;
|
||||
}
|
||||
}
|
||||
366
Marechai.App/Presentation/Views/ProcessorDetailPage.xaml
Normal file
366
Marechai.App/Presentation/Views/ProcessorDetailPage.xaml
Normal file
@@ -0,0 +1,366 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Page x:Class="Marechai.App.Presentation.Views.ProcessorDetailPage"
|
||||
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}"
|
||||
MainCommandMode="Action">
|
||||
<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="16">
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- Processor Details -->
|
||||
<StackPanel Visibility="{Binding IsDataLoaded}"
|
||||
Spacing="16">
|
||||
|
||||
<!-- Processor Name -->
|
||||
<TextBlock Text="{Binding Processor.Name}"
|
||||
FontSize="28"
|
||||
FontWeight="Bold"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<!-- Company/Manufacturer -->
|
||||
<StackPanel Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Manufacturer"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding ManufacturerName}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Model Code -->
|
||||
<StackPanel Visibility="{Binding Processor.ModelCode, Converter={StaticResource StringToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Model Code"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Processor.ModelCode}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Introduced Date -->
|
||||
<StackPanel Visibility="{Binding Processor.Introduced, Converter={StaticResource ObjectToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Introduced"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Processor.Introduced}"
|
||||
FontSize="14" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Speed -->
|
||||
<StackPanel Visibility="{Binding Processor.Speed, Converter={StaticResource ObjectToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Speed (MHz)"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Processor.Speed}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Package -->
|
||||
<StackPanel Visibility="{Binding Processor.Package, Converter={StaticResource StringToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Package"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Processor.Package}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Cores -->
|
||||
<StackPanel Visibility="{Binding Processor.Cores, Converter={StaticResource ObjectToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Cores"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Processor.Cores}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Threads Per Core -->
|
||||
<StackPanel Visibility="{Binding Processor.ThreadsPerCore, Converter={StaticResource ObjectToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Threads Per Core"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Processor.ThreadsPerCore}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Process -->
|
||||
<StackPanel Visibility="{Binding Processor.Process, Converter={StaticResource StringToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Process"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Processor.Process}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Process (nm) -->
|
||||
<StackPanel Visibility="{Binding Processor.ProcessNm, Converter={StaticResource StringToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Process (nm)"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Processor.ProcessNm}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Die Size -->
|
||||
<StackPanel Visibility="{Binding Processor.DieSize, Converter={StaticResource ObjectToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Die Size (mm²)"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Processor.DieSize}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Transistors -->
|
||||
<StackPanel Visibility="{Binding Processor.Transistors, Converter={StaticResource ObjectToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Transistors"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Processor.Transistors}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Computers Section -->
|
||||
<StackPanel Visibility="{Binding HasComputers}"
|
||||
Spacing="8">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Computers"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Computers.Count}"
|
||||
FontSize="14"
|
||||
FontWeight="Bold"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Filter Box -->
|
||||
<AutoSuggestBox PlaceholderText="Filter computers..."
|
||||
Text="{Binding ComputersFilterText, Mode=TwoWay}"
|
||||
TextChanged="ComputersSearchBox_TextChanged"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{ThemeResource ControlElevationBorderBrush}" />
|
||||
|
||||
<!-- Scrollable Computers List -->
|
||||
<ScrollViewer Height="200"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{ThemeResource ControlElevationBorderBrush}"
|
||||
CornerRadius="8">
|
||||
<ItemsRepeater ItemsSource="{Binding FilteredComputers}"
|
||||
Margin="0">
|
||||
<ItemsRepeater.Layout>
|
||||
<StackLayout Spacing="4" />
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Click="Computer_Click"
|
||||
Tag="{Binding Id}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="12"
|
||||
Margin="0,4">
|
||||
<StackPanel Spacing="4"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock Text="{Binding Name}"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold" />
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<TextBlock Text="{Binding Manufacturer}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding YearDisplay}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Consoles Section -->
|
||||
<StackPanel Visibility="{Binding HasConsoles}"
|
||||
Spacing="8">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Consoles"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Consoles.Count}"
|
||||
FontSize="14"
|
||||
FontWeight="Bold"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Filter Box -->
|
||||
<AutoSuggestBox PlaceholderText="Filter consoles..."
|
||||
Text="{Binding ConsoelsFilterText, Mode=TwoWay}"
|
||||
TextChanged="ConsolesSearchBox_TextChanged"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{ThemeResource ControlElevationBorderBrush}" />
|
||||
|
||||
<!-- Scrollable Consoles List -->
|
||||
<ScrollViewer Height="200"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{ThemeResource ControlElevationBorderBrush}"
|
||||
CornerRadius="8">
|
||||
<ItemsRepeater ItemsSource="{Binding FilteredConsoles}"
|
||||
Margin="0">
|
||||
<ItemsRepeater.Layout>
|
||||
<StackLayout Spacing="4" />
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Click="Console_Click"
|
||||
Tag="{Binding Id}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="12"
|
||||
Margin="0,4">
|
||||
<StackPanel Spacing="4"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock Text="{Binding Name}"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold" />
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<TextBlock Text="{Binding Manufacturer}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding YearDisplay}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Page>
|
||||
98
Marechai.App/Presentation/Views/ProcessorDetailPage.xaml.cs
Normal file
98
Marechai.App/Presentation/Views/ProcessorDetailPage.xaml.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
#nullable enable
|
||||
|
||||
using Marechai.App.Presentation.Models;
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
/// <summary>
|
||||
/// Processor detail page showing all information about a specific processor
|
||||
/// </summary>
|
||||
public sealed partial class ProcessorDetailPage : Page
|
||||
{
|
||||
private object? _navigationSource;
|
||||
private int? _pendingProcessorId;
|
||||
|
||||
public ProcessorDetailPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContextChanged += ProcessorDetailPage_DataContextChanged;
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
int? processorId = null;
|
||||
|
||||
// Handle both int and ProcessorDetailNavigationParameter
|
||||
if(e.Parameter is int intId)
|
||||
processorId = intId;
|
||||
else if(e.Parameter is ProcessorDetailNavigationParameter navParam)
|
||||
{
|
||||
processorId = navParam.ProcessorId;
|
||||
_navigationSource = navParam.NavigationSource;
|
||||
}
|
||||
|
||||
if(processorId.HasValue)
|
||||
{
|
||||
_pendingProcessorId = processorId;
|
||||
|
||||
if(DataContext is ProcessorDetailViewModel viewModel)
|
||||
{
|
||||
viewModel.ProcessorId = processorId.Value;
|
||||
if(_navigationSource != null) viewModel.SetNavigationSource(_navigationSource);
|
||||
_ = viewModel.LoadData.ExecuteAsync(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessorDetailPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
|
||||
{
|
||||
if(_pendingProcessorId.HasValue && DataContext is ProcessorDetailViewModel viewModel)
|
||||
{
|
||||
viewModel.ProcessorId = _pendingProcessorId.Value;
|
||||
if(_navigationSource != null) viewModel.SetNavigationSource(_navigationSource);
|
||||
_ = viewModel.LoadData.ExecuteAsync(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void ComputersSearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
|
||||
{
|
||||
if(DataContext is ProcessorDetailViewModel vm) vm.ComputersFilterCommand.Execute(null);
|
||||
}
|
||||
|
||||
private void ComputersSearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
|
||||
{
|
||||
if(args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
|
||||
if(DataContext is ProcessorDetailViewModel vm)
|
||||
vm.ComputersFilterCommand.Execute(null);
|
||||
}
|
||||
|
||||
private void ConsolesSearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
|
||||
{
|
||||
if(DataContext is ProcessorDetailViewModel vm) vm.ConsolesFilterCommand.Execute(null);
|
||||
}
|
||||
|
||||
private void ConsolesSearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
|
||||
{
|
||||
if(args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
|
||||
if(DataContext is ProcessorDetailViewModel vm)
|
||||
vm.ConsolesFilterCommand.Execute(null);
|
||||
}
|
||||
|
||||
private void Computer_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(sender is Button button && button.Tag is int machineId && DataContext is ProcessorDetailViewModel vm)
|
||||
_ = vm.SelectMachineCommand.ExecuteAsync(machineId);
|
||||
}
|
||||
|
||||
private void Console_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(sender is Button button && button.Tag is int machineId && DataContext is ProcessorDetailViewModel vm)
|
||||
_ = vm.SelectMachineCommand.ExecuteAsync(machineId);
|
||||
}
|
||||
}
|
||||
207
Marechai.App/Presentation/Views/ProcessorListPage.xaml
Normal file
207
Marechai.App/Presentation/Views/ProcessorListPage.xaml
Normal file
@@ -0,0 +1,207 @@
|
||||
<?xml version="1.0"
|
||||
encoding="utf-8"?>
|
||||
|
||||
<Page x:Class="Marechai.App.Presentation.Views.ProcessorListPage"
|
||||
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="Processors">
|
||||
</utu:NavigationBar>
|
||||
|
||||
<!-- 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 processors..."
|
||||
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 Processors"
|
||||
Message="{Binding ErrorMessage}"
|
||||
IsClosable="False" />
|
||||
<Button Content="Retry"
|
||||
Command="{Binding LoadData}"
|
||||
HorizontalAlignment="Center"
|
||||
Style="{ThemeResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Processors 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 ProcessorsList.Count}" />
|
||||
<TextBlock FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
Text="processors" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Processors List -->
|
||||
<ItemsControl Grid.Row="1"
|
||||
ItemsSource="{Binding ProcessorsList}"
|
||||
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.NavigateToProcessorCommand, 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>
|
||||
|
||||
<!-- Processor Info -->
|
||||
<StackPanel Grid.Column="0"
|
||||
Spacing="8"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Stretch">
|
||||
<TextBlock Text="{Binding Name}"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="6"
|
||||
VerticalAlignment="Center">
|
||||
<FontIcon Glyph=""
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
<TextBlock Text="{Binding Company}"
|
||||
FontSize="13"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
</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>
|
||||
37
Marechai.App/Presentation/Views/ProcessorListPage.xaml.cs
Normal file
37
Marechai.App/Presentation/Views/ProcessorListPage.xaml.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
/// <summary>
|
||||
/// Professional list view for displaying all processors.
|
||||
/// Features responsive layout and modern styling.
|
||||
/// </summary>
|
||||
public sealed partial class ProcessorListPage : Page
|
||||
{
|
||||
public ProcessorListPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += ProcessorListPage_Loaded;
|
||||
DataContextChanged += ProcessorListPage_DataContextChanged;
|
||||
}
|
||||
|
||||
private void ProcessorListPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
|
||||
{
|
||||
if(DataContext is ProcessorsListViewModel vm)
|
||||
{
|
||||
// Load data when DataContext is set
|
||||
vm.LoadData.Execute(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessorListPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(DataContext is ProcessorsListViewModel vm)
|
||||
{
|
||||
// Load data when page is loaded (fallback)
|
||||
vm.LoadData.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Marechai.App/Presentation/Views/SecondPage.xaml
Normal file
22
Marechai.App/Presentation/Views/SecondPage.xaml
Normal file
@@ -0,0 +1,22 @@
|
||||
<Page x:Class="Marechai.App.Presentation.Views.SecondPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:utu="using:Uno.Toolkit.UI"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid utu:SafeArea.Insets="VisibleBounds">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<utu:NavigationBar Content="Second Page" />
|
||||
<StackPanel Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Entity.Name}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Margin="8" />
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -1,11 +1,11 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Marechai.App.Presentation;
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
public sealed partial class SecondPage : Page
|
||||
{
|
||||
public SecondPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
89
Marechai.App/Presentation/Views/SettingsPage.xaml
Normal file
89
Marechai.App/Presentation/Views/SettingsPage.xaml
Normal file
@@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Page x:Class="Marechai.App.Presentation.Views.SettingsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:Marechai.App.Presentation.ViewModels"
|
||||
xmlns:utu="using:Uno.Toolkit.UI"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid utu:SafeArea.Insets="VisibleBounds" Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header -->
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{Binding Title}"
|
||||
Style="{ThemeResource TitleTextBlockStyle}"
|
||||
Margin="0,0,0,24"/>
|
||||
|
||||
<!-- Settings Content -->
|
||||
<ScrollViewer Grid.Row="1"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="24" MaxWidth="600" HorizontalAlignment="Left">
|
||||
|
||||
<!-- Theme Section -->
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock x:Uid="SettingsPage_ThemeSection_Header"
|
||||
Text="Appearance"
|
||||
Style="{ThemeResource SubtitleTextBlockStyle}"/>
|
||||
|
||||
<TextBlock x:Uid="SettingsPage_ThemeSection_Description"
|
||||
Text="Select your preferred color theme"
|
||||
Style="{ThemeResource BodyTextBlockStyle}"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="0,0,0,8"/>
|
||||
|
||||
<!-- Brightness Theme Selector (Light/Dark/System) -->
|
||||
<ComboBox x:Name="BrightnessThemeSelector"
|
||||
x:Uid="SettingsPage_BrightnessThemeSelector"
|
||||
Header="Brightness"
|
||||
ItemsSource="{Binding AvailableThemes}"
|
||||
SelectedItem="{Binding SelectedTheme, Mode=TwoWay}"
|
||||
HorizontalAlignment="Stretch"
|
||||
MinWidth="250"
|
||||
Margin="0,0,0,12">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding DisplayName}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<!-- Color Theme Selector (Default/Windows 3.11) -->
|
||||
<ComboBox x:Name="ColorThemeSelector"
|
||||
x:Uid="SettingsPage_ColorThemeSelector"
|
||||
Header="Color Theme"
|
||||
ItemsSource="{Binding AvailableColorThemes}"
|
||||
SelectedItem="{Binding SelectedColorTheme, Mode=TwoWay}"
|
||||
HorizontalAlignment="Stretch"
|
||||
MinWidth="250">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding DisplayName}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Placeholder for future settings sections -->
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock x:Uid="SettingsPage_AboutSection_Header"
|
||||
Text="About"
|
||||
Style="{ThemeResource SubtitleTextBlockStyle}"/>
|
||||
|
||||
<TextBlock x:Uid="SettingsPage_Version"
|
||||
Text="Version 1.0"
|
||||
Style="{ThemeResource BodyTextBlockStyle}"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"/>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Page>
|
||||
14
Marechai.App/Presentation/Views/SettingsPage.xaml.cs
Normal file
14
Marechai.App/Presentation/Views/SettingsPage.xaml.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
public sealed partial class SettingsPage : Page
|
||||
{
|
||||
public SettingsPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public SettingsViewModel? ViewModel => DataContext as SettingsViewModel;
|
||||
}
|
||||
34
Marechai.App/Presentation/Views/Shell.xaml
Normal file
34
Marechai.App/Presentation/Views/Shell.xaml
Normal file
@@ -0,0 +1,34 @@
|
||||
<UserControl x:Class="Marechai.App.Presentation.Views.Shell"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:utu="using:Uno.Toolkit.UI"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="300"
|
||||
d:DesignWidth="400">
|
||||
<Border Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<utu:ExtendedSplashScreen x:Name="Splash"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch">
|
||||
<utu:ExtendedSplashScreen.LoadingContentTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="2*" /> <RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ProgressRing IsActive="True"
|
||||
Grid.Row="1"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Height="100"
|
||||
Width="100" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</utu:ExtendedSplashScreen.LoadingContentTemplate>
|
||||
</utu:ExtendedSplashScreen>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -1,14 +1,14 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Uno.Extensions.Hosting;
|
||||
|
||||
namespace Marechai.App.Presentation;
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
public sealed partial class Shell : UserControl, IContentControlProvider
|
||||
{
|
||||
public Shell()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public ContentControl ContentControl => Splash;
|
||||
}
|
||||
}
|
||||
310
Marechai.App/Presentation/Views/SoundSynthDetailPage.xaml
Normal file
310
Marechai.App/Presentation/Views/SoundSynthDetailPage.xaml
Normal file
@@ -0,0 +1,310 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Page x:Class="Marechai.App.Presentation.Views.SoundSynthDetailPage"
|
||||
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"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
|
||||
NavigationCacheMode="Required"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Page.Resources>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid utu:SafeArea.Insets="VisibleBounds">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header -->
|
||||
<utu:NavigationBar Grid.Row="0"
|
||||
Content="{Binding Path=Title}"
|
||||
MainCommandMode="Action">
|
||||
<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="16">
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- Sound Synthesizer Details -->
|
||||
<StackPanel Visibility="{Binding IsDataLoaded}"
|
||||
Spacing="16">
|
||||
|
||||
<!-- Sound Synthesizer Name -->
|
||||
<TextBlock Text="{Binding SoundSynth.Name}"
|
||||
FontSize="28"
|
||||
FontWeight="Bold"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<!-- Company/Manufacturer -->
|
||||
<StackPanel Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Manufacturer"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding ManufacturerName}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Model Code -->
|
||||
<StackPanel Visibility="{Binding SoundSynth.ModelCode, Converter={StaticResource StringToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Model Code"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding SoundSynth.ModelCode}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Introduced Date -->
|
||||
<StackPanel Visibility="{Binding SoundSynth.Introduced, Converter={StaticResource ObjectToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Introduced"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding SoundSynth.Introduced}"
|
||||
FontSize="14" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Voices -->
|
||||
<StackPanel Visibility="{Binding SoundSynth.Voices, Converter={StaticResource ObjectToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Voices"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding SoundSynth.Voices}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Frequency -->
|
||||
<StackPanel Visibility="{Binding SoundSynth.Frequency, Converter={StaticResource ObjectToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Frequency (Hz)"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding SoundSynth.Frequency}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Depth -->
|
||||
<StackPanel Visibility="{Binding SoundSynth.Depth, Converter={StaticResource ObjectToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Depth (bit)"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding SoundSynth.Depth}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Type -->
|
||||
<StackPanel Visibility="{Binding SoundSynth.Type, Converter={StaticResource StringToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Type"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding SoundSynth.Type}"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Computers Section -->
|
||||
<StackPanel Visibility="{Binding HasComputers}"
|
||||
Spacing="8">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Computers"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Computers.Count}"
|
||||
FontSize="14"
|
||||
FontWeight="Bold"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Filter Box -->
|
||||
<AutoSuggestBox PlaceholderText="Filter computers..."
|
||||
Text="{Binding ComputersFilterText, Mode=TwoWay}"
|
||||
TextChanged="ComputersSearchBox_TextChanged"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{ThemeResource ControlElevationBorderBrush}" />
|
||||
|
||||
<!-- Scrollable Computers List -->
|
||||
<ScrollViewer Height="200"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{ThemeResource ControlElevationBorderBrush}"
|
||||
CornerRadius="8">
|
||||
<ItemsRepeater ItemsSource="{Binding FilteredComputers}"
|
||||
Margin="0">
|
||||
<ItemsRepeater.Layout>
|
||||
<StackLayout Spacing="4" />
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Click="Computer_Click"
|
||||
Tag="{Binding Id}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="12"
|
||||
Margin="0,4">
|
||||
<StackPanel Spacing="4"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock Text="{Binding Name}"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold" />
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<TextBlock Text="{Binding Manufacturer}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding YearDisplay}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Consoles Section -->
|
||||
<StackPanel Visibility="{Binding HasConsoles}"
|
||||
Spacing="8">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Consoles"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Consoles.Count}"
|
||||
FontSize="14"
|
||||
FontWeight="Bold"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Filter Box -->
|
||||
<AutoSuggestBox PlaceholderText="Filter consoles..."
|
||||
Text="{Binding ConsoelsFilterText, Mode=TwoWay}"
|
||||
TextChanged="ConsolesSearchBox_TextChanged"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{ThemeResource ControlElevationBorderBrush}" />
|
||||
|
||||
<!-- Scrollable Consoles List -->
|
||||
<ScrollViewer Height="200"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{ThemeResource ControlElevationBorderBrush}"
|
||||
CornerRadius="8">
|
||||
<ItemsRepeater ItemsSource="{Binding FilteredConsoles}"
|
||||
Margin="0">
|
||||
<ItemsRepeater.Layout>
|
||||
<StackLayout Spacing="4" />
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Click="Console_Click"
|
||||
Tag="{Binding Id}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="12"
|
||||
Margin="0,4">
|
||||
<StackPanel Spacing="4"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock Text="{Binding Name}"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold" />
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<TextBlock Text="{Binding Manufacturer}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding YearDisplay}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Page>
|
||||
100
Marechai.App/Presentation/Views/SoundSynthDetailPage.xaml.cs
Normal file
100
Marechai.App/Presentation/Views/SoundSynthDetailPage.xaml.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
#nullable enable
|
||||
|
||||
using Marechai.App.Presentation.Models;
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
/// <summary>
|
||||
/// Sound Synthesizer detail page showing all information, computers, and consoles
|
||||
/// </summary>
|
||||
public sealed partial class SoundSynthDetailPage : Page
|
||||
{
|
||||
private object? _navigationSource;
|
||||
private int? _pendingSoundSynthId;
|
||||
|
||||
public SoundSynthDetailPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContextChanged += SoundSynthDetailPage_DataContextChanged;
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
int? soundSynthId = null;
|
||||
|
||||
// Handle both int and SoundSynthDetailNavigationParameter
|
||||
if(e.Parameter is int intId)
|
||||
soundSynthId = intId;
|
||||
else if(e.Parameter is SoundSynthDetailNavigationParameter navParam)
|
||||
{
|
||||
soundSynthId = navParam.SoundSynthId;
|
||||
_navigationSource = navParam.NavigationSource;
|
||||
}
|
||||
|
||||
if(soundSynthId.HasValue)
|
||||
{
|
||||
_pendingSoundSynthId = soundSynthId;
|
||||
|
||||
if(DataContext is SoundSynthDetailViewModel viewModel)
|
||||
{
|
||||
viewModel.SoundSynthId = soundSynthId.Value;
|
||||
if(_navigationSource != null) viewModel.SetNavigationSource(_navigationSource);
|
||||
_ = viewModel.LoadData.ExecuteAsync(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SoundSynthDetailPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
|
||||
{
|
||||
if(DataContext is SoundSynthDetailViewModel viewModel && _pendingSoundSynthId.HasValue)
|
||||
{
|
||||
viewModel.SoundSynthId = _pendingSoundSynthId.Value;
|
||||
if(_navigationSource != null) viewModel.SetNavigationSource(_navigationSource);
|
||||
_ = viewModel.LoadData.ExecuteAsync(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void ComputersSearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
|
||||
{
|
||||
if(DataContext is SoundSynthDetailViewModel vm) vm.ComputersFilterCommand.Execute(null);
|
||||
}
|
||||
|
||||
private void ComputersSearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
|
||||
{
|
||||
if(args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
|
||||
{
|
||||
if(DataContext is SoundSynthDetailViewModel vm) vm.ComputersFilterCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConsolesSearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
|
||||
{
|
||||
if(DataContext is SoundSynthDetailViewModel vm) vm.ConsolesFilterCommand.Execute(null);
|
||||
}
|
||||
|
||||
private void ConsolesSearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
|
||||
{
|
||||
if(args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
|
||||
{
|
||||
if(DataContext is SoundSynthDetailViewModel vm) vm.ConsolesFilterCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void Computer_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(sender is Button button && button.Tag is int machineId && DataContext is SoundSynthDetailViewModel vm)
|
||||
_ = vm.SelectMachineCommand.ExecuteAsync(machineId);
|
||||
}
|
||||
|
||||
private void Console_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(sender is Button button && button.Tag is int machineId && DataContext is SoundSynthDetailViewModel vm)
|
||||
_ = vm.SelectMachineCommand.ExecuteAsync(machineId);
|
||||
}
|
||||
}
|
||||
207
Marechai.App/Presentation/Views/SoundSynthListPage.xaml
Normal file
207
Marechai.App/Presentation/Views/SoundSynthListPage.xaml
Normal file
@@ -0,0 +1,207 @@
|
||||
<?xml version="1.0"
|
||||
encoding="utf-8"?>
|
||||
|
||||
<Page x:Class="Marechai.App.Presentation.Views.SoundSynthListPage"
|
||||
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="Sound Synthesizers">
|
||||
</utu:NavigationBar>
|
||||
|
||||
<!-- 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 sound synthesizers..."
|
||||
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 Sound Synthesizers"
|
||||
Message="{Binding ErrorMessage}"
|
||||
IsClosable="False" />
|
||||
<Button Content="Retry"
|
||||
Command="{Binding LoadData}"
|
||||
HorizontalAlignment="Center"
|
||||
Style="{ThemeResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Sound Synthesizers 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 SoundSynths.Count}" />
|
||||
<TextBlock FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
Text="sound synthesizers" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Sound Synthesizers List -->
|
||||
<ItemsControl Grid.Row="1"
|
||||
ItemsSource="{Binding SoundSynths}"
|
||||
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.NavigateToSoundSynthCommand, 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>
|
||||
|
||||
<!-- Sound Synth Info -->
|
||||
<StackPanel Grid.Column="0"
|
||||
Spacing="8"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Stretch">
|
||||
<TextBlock Text="{Binding Name}"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="6"
|
||||
VerticalAlignment="Center">
|
||||
<FontIcon Glyph=""
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
<TextBlock Text="{Binding Company}"
|
||||
FontSize="13"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
</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>
|
||||
33
Marechai.App/Presentation/Views/SoundSynthListPage.xaml.cs
Normal file
33
Marechai.App/Presentation/Views/SoundSynthListPage.xaml.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
public sealed partial class SoundSynthListPage : Page
|
||||
{
|
||||
public SoundSynthListPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += SoundSynthListPage_Loaded;
|
||||
DataContextChanged += SoundSynthListPage_DataContextChanged;
|
||||
}
|
||||
|
||||
private void SoundSynthListPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
|
||||
{
|
||||
if(DataContext is SoundSynthsListViewModel vm)
|
||||
{
|
||||
// Load data when DataContext is set
|
||||
vm.LoadData.Execute(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void SoundSynthListPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(DataContext is SoundSynthsListViewModel vm)
|
||||
{
|
||||
// Load data when page is loaded (fallback)
|
||||
vm.LoadData.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
184
Marechai.App/Presentation/Views/UsersPage.xaml
Normal file
184
Marechai.App/Presentation/Views/UsersPage.xaml
Normal file
@@ -0,0 +1,184 @@
|
||||
<?xml version="1.0"
|
||||
encoding="utf-8"?>
|
||||
|
||||
<Page x:Class="Marechai.App.Presentation.Views.UsersPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
|
||||
x:Name="PageRoot"
|
||||
NavigationCacheMode="Required"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid RowSpacing="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header with Title -->
|
||||
<Grid Grid.Row="0"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
BorderThickness="0,0,0,1"
|
||||
Padding="16,12">
|
||||
|
||||
<!-- Page Title -->
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock Text="User Management"
|
||||
FontSize="20"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}" />
|
||||
<TextBlock Text="Manage users, roles, and permissions"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
Margin="0,4,0,0" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Main Content -->
|
||||
<Grid Grid.Row="1">
|
||||
|
||||
<!-- Access Denied Message -->
|
||||
<StackPanel Visibility="{Binding IsUberadmin, Converter={StaticResource InvertBoolToVisibilityConverter}}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Padding="24"
|
||||
Spacing="16"
|
||||
MaxWidth="400">
|
||||
<InfoBar IsOpen="True"
|
||||
Severity="Warning"
|
||||
Title="Access Denied"
|
||||
Message="You must be an Uberadmin to access user management."
|
||||
IsClosable="False" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Content (only visible to Uberadmin) -->
|
||||
<Grid Visibility="{Binding IsUberadmin, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
Canvas.ZIndex="0">
|
||||
|
||||
<!-- Loading State -->
|
||||
<StackPanel Visibility="{Binding IsLoading, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Padding="32"
|
||||
Spacing="16">
|
||||
<ProgressRing IsActive="True"
|
||||
IsIndeterminate="True"
|
||||
Height="64"
|
||||
Width="64"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
<TextBlock Text="Loading users..."
|
||||
FontSize="14"
|
||||
TextAlignment="Center"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Error State -->
|
||||
<StackPanel Visibility="{Binding HasError, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Padding="24"
|
||||
Spacing="16"
|
||||
MaxWidth="400"
|
||||
Canvas.ZIndex="100">
|
||||
<InfoBar IsOpen="True"
|
||||
Severity="Error"
|
||||
Title="Unable to Load Users"
|
||||
Message="{Binding ErrorMessage}"
|
||||
IsClosable="False" />
|
||||
<Button Content="Retry"
|
||||
Command="{Binding LoadUsersCommand}"
|
||||
HorizontalAlignment="Center"
|
||||
Style="{ThemeResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Users DataGrid -->
|
||||
<Grid Visibility="{Binding IsDataLoaded, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
Canvas.ZIndex="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Command Bar -->
|
||||
<CommandBar Grid.Row="0"
|
||||
DefaultLabelPosition="Right">
|
||||
<AppBarButton Icon="Add"
|
||||
Label="Add User"
|
||||
Command="{Binding OpenAddUserDialogCommand}" />
|
||||
<AppBarButton Icon="Refresh"
|
||||
Label="Refresh"
|
||||
Command="{Binding LoadUsersCommand}" />
|
||||
</CommandBar>
|
||||
|
||||
<!-- DataGrid -->
|
||||
<controls:DataGrid Grid.Row="1"
|
||||
Margin="16"
|
||||
ItemsSource="{Binding Users}"
|
||||
SelectedItem="{Binding SelectedUser, Mode=TwoWay}"
|
||||
AutoGenerateColumns="False"
|
||||
GridLinesVisibility="None"
|
||||
HeadersVisibility="Column"
|
||||
AlternatingRowBackground="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
CanUserReorderColumns="True"
|
||||
CanUserResizeColumns="True"
|
||||
CanUserSortColumns="True"
|
||||
IsReadOnly="True"
|
||||
SelectionMode="Single"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
|
||||
<controls:DataGrid.Columns>
|
||||
<controls:DataGridTextColumn Header="Email"
|
||||
Binding="{Binding Email}"
|
||||
Width="2*" />
|
||||
|
||||
<controls:DataGridTextColumn Header="Username"
|
||||
Binding="{Binding UserName}"
|
||||
Width="*" />
|
||||
|
||||
<controls:DataGridTextColumn Header="Phone"
|
||||
Binding="{Binding PhoneNumber}"
|
||||
Width="*" />
|
||||
|
||||
<controls:DataGridTextColumn Header="Roles"
|
||||
Width="2*"
|
||||
Binding="{Binding Roles, Converter={StaticResource RolesListConverter}}" />
|
||||
|
||||
<controls:DataGridTemplateColumn Header="Actions"
|
||||
Width="Auto">
|
||||
<controls:DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="4">
|
||||
<Button Content="Edit"
|
||||
Command="{Binding DataContext.OpenEditUserDialogCommand, ElementName=PageRoot}"
|
||||
CommandParameter="{Binding}"
|
||||
Style="{ThemeResource TextBlockButtonStyle}"
|
||||
Padding="8,4" />
|
||||
<Button Content="Password"
|
||||
Command="{Binding DataContext.OpenChangePasswordDialogCommand, ElementName=PageRoot}"
|
||||
CommandParameter="{Binding}"
|
||||
Style="{ThemeResource TextBlockButtonStyle}"
|
||||
Padding="8,4" />
|
||||
<Button Content="Roles"
|
||||
Command="{Binding DataContext.OpenManageRolesDialogCommand, ElementName=PageRoot}"
|
||||
CommandParameter="{Binding}"
|
||||
Style="{ThemeResource TextBlockButtonStyle}"
|
||||
Padding="8,4" />
|
||||
<Button Content="Delete"
|
||||
Command="{Binding DataContext.DeleteUserCommand, ElementName=PageRoot}"
|
||||
CommandParameter="{Binding}"
|
||||
Foreground="{ThemeResource SystemErrorTextColor}"
|
||||
Style="{ThemeResource TextBlockButtonStyle}"
|
||||
Padding="8,4" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</controls:DataGridTemplateColumn.CellTemplate>
|
||||
</controls:DataGridTemplateColumn>
|
||||
</controls:DataGrid.Columns>
|
||||
</controls:DataGrid>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Page>
|
||||
394
Marechai.App/Presentation/Views/UsersPage.xaml.cs
Normal file
394
Marechai.App/Presentation/Views/UsersPage.xaml.cs
Normal file
@@ -0,0 +1,394 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Text;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
/// <summary>
|
||||
/// User management page for Uberadmins
|
||||
/// </summary>
|
||||
public sealed partial class UsersPage : Page
|
||||
{
|
||||
private ContentDialog? _currentOpenDialog;
|
||||
private UsersViewModel? _currentViewModel;
|
||||
|
||||
public UsersPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += UsersPage_Loaded;
|
||||
DataContextChanged += UsersPage_DataContextChanged;
|
||||
}
|
||||
|
||||
private void UsersPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
|
||||
{
|
||||
// Unsubscribe from previous ViewModel
|
||||
if(_currentViewModel != null) _currentViewModel.ShowDialogRequested -= OnShowDialogRequested;
|
||||
|
||||
// Subscribe to new ViewModel
|
||||
if(DataContext is UsersViewModel vm)
|
||||
{
|
||||
_currentViewModel = vm;
|
||||
vm.ShowDialogRequested += OnShowDialogRequested;
|
||||
|
||||
if(vm.IsUberadmin)
|
||||
{
|
||||
// Load data when DataContext is set and user is Uberadmin
|
||||
vm.LoadUsersCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UsersPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(DataContext is UsersViewModel vm && vm.IsUberadmin)
|
||||
{
|
||||
// Load data when page is loaded (fallback)
|
||||
vm.LoadUsersCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnShowDialogRequested(object? sender, string dialogType)
|
||||
{
|
||||
// Close any currently open dialog first
|
||||
if(_currentOpenDialog != null)
|
||||
{
|
||||
_currentOpenDialog.Hide();
|
||||
_currentOpenDialog = null;
|
||||
}
|
||||
|
||||
if(DataContext is not UsersViewModel vm) return;
|
||||
|
||||
ContentDialog? dialog = null;
|
||||
|
||||
switch(dialogType)
|
||||
{
|
||||
case "AddEdit":
|
||||
{
|
||||
var emailBox = new TextBox
|
||||
{
|
||||
Header = "Email",
|
||||
Text = vm.Email
|
||||
};
|
||||
|
||||
emailBox.TextChanged += (s, e) => vm.Email = emailBox.Text;
|
||||
|
||||
var userNameBox = new TextBox
|
||||
{
|
||||
Header = "Username",
|
||||
Text = vm.UserName
|
||||
};
|
||||
|
||||
userNameBox.TextChanged += (s, e) => vm.UserName = userNameBox.Text;
|
||||
|
||||
var phoneBox = new TextBox
|
||||
{
|
||||
Header = "Phone Number",
|
||||
Text = vm.PhoneNumber
|
||||
};
|
||||
|
||||
phoneBox.TextChanged += (s, e) => vm.PhoneNumber = phoneBox.Text;
|
||||
|
||||
var passwordBox = new PasswordBox
|
||||
{
|
||||
Header = "Password",
|
||||
Password = vm.Password,
|
||||
PlaceholderText = "Leave blank to keep current (when editing)"
|
||||
};
|
||||
|
||||
passwordBox.PasswordChanged += (s, e) => vm.Password = passwordBox.Password;
|
||||
|
||||
var confirmPasswordBox = new PasswordBox
|
||||
{
|
||||
Header = "Confirm Password",
|
||||
Password = vm.ConfirmPassword
|
||||
};
|
||||
|
||||
confirmPasswordBox.PasswordChanged += (s, e) => vm.ConfirmPassword = confirmPasswordBox.Password;
|
||||
|
||||
dialog = new ContentDialog
|
||||
{
|
||||
XamlRoot = XamlRoot,
|
||||
Title = vm.DialogTitle,
|
||||
PrimaryButtonText = "Save",
|
||||
CloseButtonText = "Cancel",
|
||||
PrimaryButtonCommand = vm.SaveUserCommand,
|
||||
CloseButtonCommand = vm.CloseDialogCommand,
|
||||
DefaultButton = ContentDialogButton.Primary,
|
||||
Content = new StackPanel
|
||||
{
|
||||
Spacing = 12,
|
||||
MinWidth = 400,
|
||||
Children =
|
||||
{
|
||||
emailBox,
|
||||
userNameBox,
|
||||
phoneBox,
|
||||
passwordBox,
|
||||
confirmPasswordBox
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "Password":
|
||||
{
|
||||
var passwordBox = new PasswordBox
|
||||
{
|
||||
Header = "New Password",
|
||||
Password = vm.Password
|
||||
};
|
||||
|
||||
passwordBox.PasswordChanged += (s, e) => vm.Password = passwordBox.Password;
|
||||
|
||||
var confirmPasswordBox = new PasswordBox
|
||||
{
|
||||
Header = "Confirm Password",
|
||||
Password = vm.ConfirmPassword
|
||||
};
|
||||
|
||||
confirmPasswordBox.PasswordChanged += (s, e) => vm.ConfirmPassword = confirmPasswordBox.Password;
|
||||
|
||||
dialog = new ContentDialog
|
||||
{
|
||||
XamlRoot = XamlRoot,
|
||||
Title = vm.DialogTitle,
|
||||
PrimaryButtonText = "Change Password",
|
||||
CloseButtonText = "Cancel",
|
||||
PrimaryButtonCommand = vm.SavePasswordCommand,
|
||||
CloseButtonCommand = vm.CloseDialogCommand,
|
||||
DefaultButton = ContentDialogButton.Primary,
|
||||
Content = new StackPanel
|
||||
{
|
||||
Spacing = 12,
|
||||
MinWidth = 400,
|
||||
Children =
|
||||
{
|
||||
passwordBox,
|
||||
confirmPasswordBox
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "Roles":
|
||||
{
|
||||
Debug.WriteLine($"Creating Roles dialog. Available roles count: {vm.AvailableRoles.Count}");
|
||||
foreach(string role in vm.AvailableRoles) Debug.WriteLine($" - Role: {role}");
|
||||
|
||||
var rolesContent = new Grid
|
||||
{
|
||||
RowSpacing = 16,
|
||||
MinWidth = 450
|
||||
};
|
||||
|
||||
rolesContent.RowDefinitions.Add(new RowDefinition
|
||||
{
|
||||
Height = GridLength.Auto
|
||||
});
|
||||
|
||||
rolesContent.RowDefinitions.Add(new RowDefinition
|
||||
{
|
||||
Height = new GridLength(1, GridUnitType.Star)
|
||||
});
|
||||
|
||||
var addRolePanel = new StackPanel
|
||||
{
|
||||
Spacing = 8
|
||||
};
|
||||
|
||||
addRolePanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "Add Role",
|
||||
FontWeight = FontWeights.SemiBold
|
||||
});
|
||||
|
||||
var addRoleGrid = new Grid
|
||||
{
|
||||
ColumnSpacing = 8
|
||||
};
|
||||
|
||||
addRoleGrid.ColumnDefinitions.Add(new ColumnDefinition
|
||||
{
|
||||
Width = new GridLength(1, GridUnitType.Star)
|
||||
});
|
||||
|
||||
addRoleGrid.ColumnDefinitions.Add(new ColumnDefinition
|
||||
{
|
||||
Width = GridLength.Auto
|
||||
});
|
||||
|
||||
// Use ListView with SingleSelection instead of ComboBox - ComboBox has issues with programmatic creation
|
||||
var roleListView = new ListView
|
||||
{
|
||||
SelectionMode = ListViewSelectionMode.Single,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
MaxHeight = 200,
|
||||
MinWidth = 300
|
||||
};
|
||||
|
||||
Debug.WriteLine($"Creating ListView for role selection with {vm.AvailableRoles.Count} roles");
|
||||
|
||||
// Populate the ListView
|
||||
foreach(string role in vm.AvailableRoles)
|
||||
{
|
||||
var item = new ListViewItem
|
||||
{
|
||||
Content = role
|
||||
};
|
||||
|
||||
roleListView.Items.Add(item);
|
||||
Debug.WriteLine($" Added role to ListView: {role}");
|
||||
}
|
||||
|
||||
Debug.WriteLine($"ListView Items.Count: {roleListView.Items.Count}");
|
||||
|
||||
roleListView.SelectionChanged += (s, e) =>
|
||||
{
|
||||
if(roleListView.SelectedItem is ListViewItem selectedItem &&
|
||||
selectedItem.Content is string selectedRole)
|
||||
{
|
||||
vm.SelectedRole = selectedRole;
|
||||
Debug.WriteLine($"Selected role from ListView: {selectedRole}");
|
||||
}
|
||||
};
|
||||
|
||||
Grid.SetColumn(roleListView, 0);
|
||||
addRoleGrid.Children.Add(roleListView);
|
||||
|
||||
var addButton = new Button
|
||||
{
|
||||
Content = "Add",
|
||||
Command = vm.AddRoleCommand
|
||||
};
|
||||
|
||||
Grid.SetColumn(addButton, 1);
|
||||
addRoleGrid.Children.Add(addButton);
|
||||
|
||||
addRolePanel.Children.Add(addRoleGrid);
|
||||
Grid.SetRow(addRolePanel, 0);
|
||||
rolesContent.Children.Add(addRolePanel);
|
||||
|
||||
var currentRolesPanel = new StackPanel
|
||||
{
|
||||
Spacing = 8,
|
||||
Margin = new Thickness(0, 8, 0, 0)
|
||||
};
|
||||
|
||||
currentRolesPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "Current Roles",
|
||||
FontWeight = FontWeights.SemiBold
|
||||
});
|
||||
|
||||
// Create a StackPanel to display roles dynamically
|
||||
var rolesStack = new StackPanel
|
||||
{
|
||||
Spacing = 4
|
||||
};
|
||||
|
||||
// Handler to refresh the roles list
|
||||
void UpdateRolesList()
|
||||
{
|
||||
rolesStack.Children.Clear();
|
||||
|
||||
foreach(string role in vm.UserRoles)
|
||||
{
|
||||
var roleItem = new Grid
|
||||
{
|
||||
ColumnSpacing = 8,
|
||||
Margin = new Thickness(0, 4, 0, 4)
|
||||
};
|
||||
|
||||
roleItem.ColumnDefinitions.Add(new ColumnDefinition
|
||||
{
|
||||
Width = new GridLength(1, GridUnitType.Star)
|
||||
});
|
||||
|
||||
roleItem.ColumnDefinitions.Add(new ColumnDefinition
|
||||
{
|
||||
Width = GridLength.Auto
|
||||
});
|
||||
|
||||
var roleText = new TextBlock
|
||||
{
|
||||
Text = role,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
|
||||
Grid.SetColumn(roleText, 0);
|
||||
roleItem.Children.Add(roleText);
|
||||
|
||||
var removeButton = new Button
|
||||
{
|
||||
Content = "Remove",
|
||||
Foreground = new SolidColorBrush(Colors.Red),
|
||||
CommandParameter = role
|
||||
};
|
||||
|
||||
removeButton.Click += (s, e) =>
|
||||
{
|
||||
var btn = s as Button;
|
||||
var roleToRemove = btn?.CommandParameter as string;
|
||||
|
||||
if(roleToRemove != null && vm.RemoveRoleCommand.CanExecute(roleToRemove))
|
||||
{
|
||||
vm.RemoveRoleCommand.Execute(roleToRemove);
|
||||
UpdateRolesList();
|
||||
}
|
||||
};
|
||||
|
||||
Grid.SetColumn(removeButton, 1);
|
||||
roleItem.Children.Add(removeButton);
|
||||
|
||||
rolesStack.Children.Add(roleItem);
|
||||
}
|
||||
}
|
||||
|
||||
// Listen to collection changes
|
||||
vm.UserRoles.CollectionChanged += (s, e) => UpdateRolesList();
|
||||
|
||||
// Initial population
|
||||
UpdateRolesList();
|
||||
|
||||
var scrollViewer = new ScrollViewer
|
||||
{
|
||||
MaxHeight = 200,
|
||||
Content = rolesStack
|
||||
};
|
||||
|
||||
currentRolesPanel.Children.Add(scrollViewer);
|
||||
|
||||
Grid.SetRow(currentRolesPanel, 1);
|
||||
rolesContent.Children.Add(currentRolesPanel);
|
||||
|
||||
dialog = new ContentDialog
|
||||
{
|
||||
XamlRoot = XamlRoot,
|
||||
Title = vm.DialogTitle,
|
||||
CloseButtonText = "Close",
|
||||
CloseButtonCommand = vm.CloseDialogCommand,
|
||||
DefaultButton = ContentDialogButton.Close,
|
||||
Content = rolesContent
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if(dialog != null)
|
||||
{
|
||||
_currentOpenDialog = dialog;
|
||||
await dialog.ShowAsync();
|
||||
_currentOpenDialog = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
123
Marechai.App/Services/Authentication/AuthService.cs
Normal file
123
Marechai.App/Services/Authentication/AuthService.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Kiota.Abstractions;
|
||||
using Uno.Extensions;
|
||||
using Uno.Extensions.Authentication;
|
||||
|
||||
namespace Marechai.App.Services.Authentication;
|
||||
|
||||
public sealed class AuthService
|
||||
(ApiClient client, ITokenService tokenService, IStringLocalizer stringLocalizer) : IAuthenticationService
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<bool> LoginAsync(IDispatcher? dispatcher, IDictionary<string, string>? credentials = null,
|
||||
string? provider = null, CancellationToken? cancellationToken = null)
|
||||
{
|
||||
if(credentials is null) return false;
|
||||
|
||||
string? email =
|
||||
(credentials.FirstOrDefault(x => x.Key.Equals("Email", StringComparison.OrdinalIgnoreCase)).Value ??
|
||||
credentials.FirstOrDefault(x => x.Key.Equals("email", StringComparison.OrdinalIgnoreCase)).Value ??
|
||||
credentials.FirstOrDefault(x => x.Key.Equals("Username", StringComparison.OrdinalIgnoreCase)).Value)
|
||||
?.Trim();
|
||||
|
||||
string? password =
|
||||
(credentials.FirstOrDefault(x => x.Key.Equals("Password", StringComparison.OrdinalIgnoreCase)).Value ??
|
||||
credentials.FirstOrDefault(x => x.Key.Equals("password", StringComparison.OrdinalIgnoreCase)).Value)
|
||||
?.Trim();
|
||||
|
||||
if(string.IsNullOrWhiteSpace(email))
|
||||
{
|
||||
credentials["error"] = stringLocalizer["Auth.EmailIsRequired"];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
credentials["error"] = stringLocalizer["Auth.PasswordIsRequired"];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var loginModel = new AuthRequest
|
||||
{
|
||||
Email = email,
|
||||
Password = password
|
||||
};
|
||||
|
||||
AuthResponse? authResponse;
|
||||
|
||||
try
|
||||
{
|
||||
tokenService.RemoveToken();
|
||||
authResponse = await client.Auth.Login.PostAsync(loginModel);
|
||||
}
|
||||
catch(ProblemDetails ex)
|
||||
{
|
||||
if(ex.Status == 400)
|
||||
credentials["error"] = ex.Detail ?? ex.Title ?? stringLocalizer["Http.BadRequest"];
|
||||
else if(ex.Status == 401)
|
||||
credentials["error"] = stringLocalizer["Auth.InvalidCredentials"];
|
||||
else
|
||||
credentials["error"] = ex.Detail ?? ex.Title ?? stringLocalizer["Http.BadRequest"];
|
||||
|
||||
return false;
|
||||
}
|
||||
catch(ApiException ex)
|
||||
{
|
||||
if(ex.ResponseStatusCode == 401)
|
||||
credentials["error"] = stringLocalizer["Auth.InvalidCredentials"];
|
||||
else if(ex.ResponseStatusCode == 400)
|
||||
credentials["error"] = stringLocalizer["Http.BadRequest"];
|
||||
else
|
||||
credentials["error"] = ex.Message ?? stringLocalizer["Http.BadRequest"];
|
||||
|
||||
return false;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
#pragma warning disable EPC12
|
||||
credentials["error"] = ex.Message;
|
||||
#pragma warning restore EPC12
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(authResponse?.Token)) return false;
|
||||
|
||||
tokenService.SetToken(authResponse.Token);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<bool> RefreshAsync(CancellationToken? cancellationToken = null) =>
|
||||
IsAuthenticated(cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<bool> LogoutAsync(IDispatcher? dispatcher, CancellationToken? cancellationToken = null)
|
||||
{
|
||||
tokenService.RemoveToken();
|
||||
LoggedOut?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<bool> IsAuthenticated(CancellationToken? cancellationToken = null)
|
||||
{
|
||||
string token = tokenService.GetToken();
|
||||
|
||||
// TODO: Check token validity
|
||||
return !string.IsNullOrWhiteSpace(token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string[] Providers { get; } = [];
|
||||
/// <inheritdoc />
|
||||
public event EventHandler? LoggedOut;
|
||||
}
|
||||
112
Marechai.App/Services/Authentication/JwtService.cs
Normal file
112
Marechai.App/Services/Authentication/JwtService.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Marechai.App.Services.Authentication;
|
||||
|
||||
public interface IJwtService
|
||||
{
|
||||
IEnumerable<string> GetRoles(string token);
|
||||
string? GetUserId(string token);
|
||||
string? GetUserName(string token);
|
||||
string? GetEmail(string token);
|
||||
bool IsTokenValid(string token);
|
||||
}
|
||||
|
||||
public sealed class JwtService : IJwtService
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> GetRoles(string token)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(token)) return [];
|
||||
|
||||
try
|
||||
{
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
JwtSecurityToken jwtToken = handler.ReadJwtToken(token);
|
||||
|
||||
return jwtToken.Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToList();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? GetUserId(string token)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(token)) return null;
|
||||
|
||||
try
|
||||
{
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
JwtSecurityToken jwtToken = handler.ReadJwtToken(token);
|
||||
|
||||
return jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Sid)?.Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? GetUserName(string token)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(token)) return null;
|
||||
|
||||
try
|
||||
{
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
JwtSecurityToken jwtToken = handler.ReadJwtToken(token);
|
||||
|
||||
return jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? GetEmail(string token)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(token)) return null;
|
||||
|
||||
try
|
||||
{
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
JwtSecurityToken jwtToken = handler.ReadJwtToken(token);
|
||||
|
||||
return jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsTokenValid(string token)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(token)) return false;
|
||||
|
||||
try
|
||||
{
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
JwtSecurityToken jwtToken = handler.ReadJwtToken(token);
|
||||
|
||||
// Check if token has expired (if expiration is set)
|
||||
if(jwtToken.ValidTo != DateTime.MinValue) return jwtToken.ValidTo > DateTime.UtcNow;
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Marechai.App/Services/Authentication/TokenService.cs
Normal file
32
Marechai.App/Services/Authentication/TokenService.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Windows.Storage;
|
||||
|
||||
namespace Marechai.App.Services.Authentication;
|
||||
|
||||
public interface ITokenService
|
||||
{
|
||||
string GetToken();
|
||||
|
||||
void RemoveToken();
|
||||
|
||||
void SetToken(string token);
|
||||
}
|
||||
|
||||
public sealed class TokenService : ITokenService
|
||||
{
|
||||
readonly ApplicationDataContainer _settings = ApplicationData.Current.LocalSettings;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetToken() => (string)_settings.Values["token"];
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveToken()
|
||||
{
|
||||
_settings.Values.Remove("token");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetToken(string token)
|
||||
{
|
||||
_settings.Values["token"] = token;
|
||||
}
|
||||
}
|
||||
64
Marechai.App/Services/Caching/FlagCache.cs
Normal file
64
Marechai.App/Services/Caching/FlagCache.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Marechai.App.Services.Caching;
|
||||
|
||||
public sealed class FlagCache
|
||||
{
|
||||
readonly IConfiguration _configuration;
|
||||
StorageFolder _flagsFolder;
|
||||
|
||||
public FlagCache(IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_ = EnsureFolderExistAsync();
|
||||
}
|
||||
|
||||
async Task EnsureFolderExistAsync()
|
||||
{
|
||||
StorageFolder localFolder = ApplicationData.Current.LocalCacheFolder;
|
||||
_flagsFolder = await localFolder.CreateFolderAsync("flags", CreationCollisionOption.OpenIfExists);
|
||||
}
|
||||
|
||||
public async Task<Stream> GetFlagAsync(short countryCode)
|
||||
{
|
||||
var filename = $"{countryCode:D3}.svg";
|
||||
|
||||
Stream retStream;
|
||||
|
||||
if(await _flagsFolder.TryGetItemAsync(filename) is StorageFile file)
|
||||
{
|
||||
retStream = await file.OpenStreamForReadAsync();
|
||||
|
||||
return retStream;
|
||||
}
|
||||
|
||||
await CacheFlagAsync(countryCode);
|
||||
|
||||
file = await _flagsFolder.GetFileAsync(filename);
|
||||
|
||||
retStream = await file.OpenStreamForReadAsync();
|
||||
|
||||
return retStream;
|
||||
}
|
||||
|
||||
async Task CacheFlagAsync(short countryCode)
|
||||
{
|
||||
var filename = $"{countryCode:D3}.svg";
|
||||
string baseUrl = _configuration.GetSection("ApiClient:Url").Value;
|
||||
string flagUrl = baseUrl + $"/assets/flags/countries/{filename}";
|
||||
using var httpClient = new HttpClient();
|
||||
using HttpResponseMessage response = await httpClient.GetAsync(flagUrl);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
using Stream stream = await response.Content.ReadAsStreamAsync();
|
||||
StorageFile file = await _flagsFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
|
||||
|
||||
using Stream fileStream = await file.OpenStreamForWriteAsync();
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
}
|
||||
64
Marechai.App/Services/Caching/LogoCache.cs
Normal file
64
Marechai.App/Services/Caching/LogoCache.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Marechai.App.Services.Caching;
|
||||
|
||||
public sealed class CompanyLogoCache
|
||||
{
|
||||
readonly IConfiguration _configuration;
|
||||
StorageFolder _logosFolder;
|
||||
|
||||
public CompanyLogoCache(IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_ = EnsureFolderExistAsync();
|
||||
}
|
||||
|
||||
async Task EnsureFolderExistAsync()
|
||||
{
|
||||
StorageFolder localFolder = ApplicationData.Current.LocalCacheFolder;
|
||||
_logosFolder = await localFolder.CreateFolderAsync("logos", CreationCollisionOption.OpenIfExists);
|
||||
}
|
||||
|
||||
public async Task<Stream> GetLogoAsync(Guid companyLogoId)
|
||||
{
|
||||
var filename = $"{companyLogoId}.svg";
|
||||
|
||||
Stream retStream;
|
||||
|
||||
if(await _logosFolder.TryGetItemAsync(filename) is StorageFile file)
|
||||
{
|
||||
retStream = await file.OpenStreamForReadAsync();
|
||||
|
||||
return retStream;
|
||||
}
|
||||
|
||||
await CacheLogoAsync(companyLogoId);
|
||||
|
||||
file = await _logosFolder.GetFileAsync(filename);
|
||||
|
||||
retStream = await file.OpenStreamForReadAsync();
|
||||
|
||||
return retStream;
|
||||
}
|
||||
|
||||
async Task CacheLogoAsync(Guid companyLogoId)
|
||||
{
|
||||
var filename = $"{companyLogoId}.svg";
|
||||
string baseUrl = _configuration.GetSection("ApiClient:Url").Value;
|
||||
string flagUrl = baseUrl + $"/assets/logos/{filename}";
|
||||
using var httpClient = new HttpClient();
|
||||
using HttpResponseMessage response = await httpClient.GetAsync(flagUrl);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
using Stream stream = await response.Content.ReadAsStreamAsync();
|
||||
StorageFile file = await _logosFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
|
||||
|
||||
using Stream fileStream = await file.OpenStreamForWriteAsync();
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
}
|
||||
107
Marechai.App/Services/Caching/MachinePhotoCache.cs
Normal file
107
Marechai.App/Services/Caching/MachinePhotoCache.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Marechai.App.Services.Caching;
|
||||
|
||||
public sealed class MachinePhotoCache
|
||||
{
|
||||
readonly IConfiguration _configuration;
|
||||
StorageFolder _photosFolder;
|
||||
StorageFolder _thumbnailsFolder;
|
||||
|
||||
public MachinePhotoCache(IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_ = EnsureFolderExistAsync();
|
||||
}
|
||||
|
||||
async Task EnsureFolderExistAsync()
|
||||
{
|
||||
StorageFolder localFolder = ApplicationData.Current.LocalCacheFolder;
|
||||
|
||||
_thumbnailsFolder =
|
||||
await localFolder.CreateFolderAsync("machine_thumbnails", CreationCollisionOption.OpenIfExists);
|
||||
|
||||
_photosFolder = await localFolder.CreateFolderAsync("machine_photos", CreationCollisionOption.OpenIfExists);
|
||||
}
|
||||
|
||||
public async Task<Stream> GetThumbnailAsync(Guid photoId)
|
||||
{
|
||||
var filename = $"{photoId}.webp";
|
||||
|
||||
Stream retStream;
|
||||
|
||||
if(await _thumbnailsFolder.TryGetItemAsync(filename) is StorageFile file)
|
||||
{
|
||||
retStream = await file.OpenStreamForReadAsync();
|
||||
|
||||
return retStream;
|
||||
}
|
||||
|
||||
await CacheThumbnailAsync(photoId);
|
||||
|
||||
file = await _thumbnailsFolder.GetFileAsync(filename);
|
||||
|
||||
retStream = await file.OpenStreamForReadAsync();
|
||||
|
||||
return retStream;
|
||||
}
|
||||
|
||||
public async Task<Stream> GetPhotoAsync(Guid photoId)
|
||||
{
|
||||
var filename = $"{photoId}.webp";
|
||||
|
||||
Stream retStream;
|
||||
|
||||
if(await _photosFolder.TryGetItemAsync(filename) is StorageFile file)
|
||||
{
|
||||
retStream = await file.OpenStreamForReadAsync();
|
||||
|
||||
return retStream;
|
||||
}
|
||||
|
||||
await CachePhotoAsync(photoId);
|
||||
|
||||
file = await _photosFolder.GetFileAsync(filename);
|
||||
|
||||
retStream = await file.OpenStreamForReadAsync();
|
||||
|
||||
return retStream;
|
||||
}
|
||||
|
||||
async Task CacheThumbnailAsync(Guid photoId)
|
||||
{
|
||||
var filename = $"{photoId}.webp";
|
||||
string baseUrl = _configuration.GetSection("ApiClient:Url").Value;
|
||||
string flagUrl = baseUrl + $"/assets/photos/machines/thumbs/webp/4k/{filename}";
|
||||
using var httpClient = new HttpClient();
|
||||
using HttpResponseMessage response = await httpClient.GetAsync(flagUrl);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
using Stream stream = await response.Content.ReadAsStreamAsync();
|
||||
StorageFile file = await _thumbnailsFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
|
||||
|
||||
using Stream fileStream = await file.OpenStreamForWriteAsync();
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
async Task CachePhotoAsync(Guid photoId)
|
||||
{
|
||||
var filename = $"{photoId}.webp";
|
||||
string baseUrl = _configuration.GetSection("ApiClient:Url").Value;
|
||||
string flagUrl = baseUrl + $"/assets/photos/machines/webp/4k/{filename}";
|
||||
using var httpClient = new HttpClient();
|
||||
using HttpResponseMessage response = await httpClient.GetAsync(flagUrl);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
using Stream stream = await response.Content.ReadAsStreamAsync();
|
||||
StorageFile file = await _photosFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
|
||||
|
||||
using Stream fileStream = await file.OpenStreamForWriteAsync();
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
}
|
||||
294
Marechai.App/Services/Client/ApiClient.cs
Normal file
294
Marechai.App/Services/Client/ApiClient.cs
Normal file
@@ -0,0 +1,294 @@
|
||||
// <auto-generated/>
|
||||
#pragma warning disable CS0618
|
||||
using Marechai.App.Auth;
|
||||
using Marechai.App.Books;
|
||||
using Marechai.App.BrowserTests;
|
||||
using Marechai.App.Companies;
|
||||
using Marechai.App.Computers;
|
||||
using Marechai.App.Consoles;
|
||||
using Marechai.App.Countries;
|
||||
using Marechai.App.Currencies;
|
||||
using Marechai.App.Documents;
|
||||
using Marechai.App.Dumps;
|
||||
using Marechai.App.Gpus;
|
||||
using Marechai.App.InstructionSetExtensions;
|
||||
using Marechai.App.InstructionSetExtensionsByProcessor;
|
||||
using Marechai.App.InstructionSets;
|
||||
using Marechai.App.Iso31661Numeric;
|
||||
using Marechai.App.Iso4217;
|
||||
using Marechai.App.Licenses;
|
||||
using Marechai.App.MachineFamilies;
|
||||
using Marechai.App.Machines;
|
||||
using Marechai.App.Magazines;
|
||||
using Marechai.App.MagazinesByMachine;
|
||||
using Marechai.App.MagazinesByMachineFamily;
|
||||
using Marechai.App.Medias;
|
||||
using Marechai.App.MemoriesByMachine;
|
||||
using Marechai.App.News;
|
||||
using Marechai.App.People;
|
||||
using Marechai.App.PeopleByBook;
|
||||
using Marechai.App.PeopleByDocument;
|
||||
using Marechai.App.PeopleByMagazine;
|
||||
using Marechai.App.Processor;
|
||||
using Marechai.App.Processors;
|
||||
using Marechai.App.ProcessorsByMachine;
|
||||
using Marechai.App.Resolutions;
|
||||
using Marechai.App.ResolutionsByGpu;
|
||||
using Marechai.App.ResolutionsByScreen;
|
||||
using Marechai.App.Screens;
|
||||
using Marechai.App.ScreensByMachine;
|
||||
using Marechai.App.Software;
|
||||
using Marechai.App.SoundSynths;
|
||||
using Marechai.App.SoundSynthsByMachine;
|
||||
using Marechai.App.StorageByMachine;
|
||||
using Marechai.App.Users;
|
||||
using Microsoft.Kiota.Abstractions.Extensions;
|
||||
using Microsoft.Kiota.Abstractions;
|
||||
using Microsoft.Kiota.Serialization.Form;
|
||||
using Microsoft.Kiota.Serialization.Json;
|
||||
using Microsoft.Kiota.Serialization.Multipart;
|
||||
using Microsoft.Kiota.Serialization.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
namespace Marechai.App
|
||||
{
|
||||
/// <summary>
|
||||
/// The main entry point of the SDK, exposes the configuration and the fluent API.
|
||||
/// </summary>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class ApiClient : BaseRequestBuilder
|
||||
{
|
||||
/// <summary>The auth property</summary>
|
||||
public global::Marechai.App.Auth.AuthRequestBuilder Auth
|
||||
{
|
||||
get => new global::Marechai.App.Auth.AuthRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The books property</summary>
|
||||
public global::Marechai.App.Books.BooksRequestBuilder Books
|
||||
{
|
||||
get => new global::Marechai.App.Books.BooksRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The browserTests property</summary>
|
||||
public global::Marechai.App.BrowserTests.BrowserTestsRequestBuilder BrowserTests
|
||||
{
|
||||
get => new global::Marechai.App.BrowserTests.BrowserTestsRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The companies property</summary>
|
||||
public global::Marechai.App.Companies.CompaniesRequestBuilder Companies
|
||||
{
|
||||
get => new global::Marechai.App.Companies.CompaniesRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The computers property</summary>
|
||||
public global::Marechai.App.Computers.ComputersRequestBuilder Computers
|
||||
{
|
||||
get => new global::Marechai.App.Computers.ComputersRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The consoles property</summary>
|
||||
public global::Marechai.App.Consoles.ConsolesRequestBuilder Consoles
|
||||
{
|
||||
get => new global::Marechai.App.Consoles.ConsolesRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The countries property</summary>
|
||||
public global::Marechai.App.Countries.CountriesRequestBuilder Countries
|
||||
{
|
||||
get => new global::Marechai.App.Countries.CountriesRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The currencies property</summary>
|
||||
public global::Marechai.App.Currencies.CurrenciesRequestBuilder Currencies
|
||||
{
|
||||
get => new global::Marechai.App.Currencies.CurrenciesRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The documents property</summary>
|
||||
public global::Marechai.App.Documents.DocumentsRequestBuilder Documents
|
||||
{
|
||||
get => new global::Marechai.App.Documents.DocumentsRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The dumps property</summary>
|
||||
public global::Marechai.App.Dumps.DumpsRequestBuilder Dumps
|
||||
{
|
||||
get => new global::Marechai.App.Dumps.DumpsRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The gpus property</summary>
|
||||
public global::Marechai.App.Gpus.GpusRequestBuilder Gpus
|
||||
{
|
||||
get => new global::Marechai.App.Gpus.GpusRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The instructionSetExtensions property</summary>
|
||||
public global::Marechai.App.InstructionSetExtensions.InstructionSetExtensionsRequestBuilder InstructionSetExtensions
|
||||
{
|
||||
get => new global::Marechai.App.InstructionSetExtensions.InstructionSetExtensionsRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The instructionSetExtensionsByProcessor property</summary>
|
||||
public global::Marechai.App.InstructionSetExtensionsByProcessor.InstructionSetExtensionsByProcessorRequestBuilder InstructionSetExtensionsByProcessor
|
||||
{
|
||||
get => new global::Marechai.App.InstructionSetExtensionsByProcessor.InstructionSetExtensionsByProcessorRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The instructionSets property</summary>
|
||||
public global::Marechai.App.InstructionSets.InstructionSetsRequestBuilder InstructionSets
|
||||
{
|
||||
get => new global::Marechai.App.InstructionSets.InstructionSetsRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The iso31661Numeric property</summary>
|
||||
public global::Marechai.App.Iso31661Numeric.Iso31661NumericRequestBuilder Iso31661Numeric
|
||||
{
|
||||
get => new global::Marechai.App.Iso31661Numeric.Iso31661NumericRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The iso4217 property</summary>
|
||||
public global::Marechai.App.Iso4217.Iso4217RequestBuilder Iso4217
|
||||
{
|
||||
get => new global::Marechai.App.Iso4217.Iso4217RequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The licenses property</summary>
|
||||
public global::Marechai.App.Licenses.LicensesRequestBuilder Licenses
|
||||
{
|
||||
get => new global::Marechai.App.Licenses.LicensesRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The machineFamilies property</summary>
|
||||
public global::Marechai.App.MachineFamilies.MachineFamiliesRequestBuilder MachineFamilies
|
||||
{
|
||||
get => new global::Marechai.App.MachineFamilies.MachineFamiliesRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The machines property</summary>
|
||||
public global::Marechai.App.Machines.MachinesRequestBuilder Machines
|
||||
{
|
||||
get => new global::Marechai.App.Machines.MachinesRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The magazines property</summary>
|
||||
public global::Marechai.App.Magazines.MagazinesRequestBuilder Magazines
|
||||
{
|
||||
get => new global::Marechai.App.Magazines.MagazinesRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The magazinesByMachine property</summary>
|
||||
public global::Marechai.App.MagazinesByMachine.MagazinesByMachineRequestBuilder MagazinesByMachine
|
||||
{
|
||||
get => new global::Marechai.App.MagazinesByMachine.MagazinesByMachineRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The magazinesByMachineFamily property</summary>
|
||||
public global::Marechai.App.MagazinesByMachineFamily.MagazinesByMachineFamilyRequestBuilder MagazinesByMachineFamily
|
||||
{
|
||||
get => new global::Marechai.App.MagazinesByMachineFamily.MagazinesByMachineFamilyRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The medias property</summary>
|
||||
public global::Marechai.App.Medias.MediasRequestBuilder Medias
|
||||
{
|
||||
get => new global::Marechai.App.Medias.MediasRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The memoriesByMachine property</summary>
|
||||
public global::Marechai.App.MemoriesByMachine.MemoriesByMachineRequestBuilder MemoriesByMachine
|
||||
{
|
||||
get => new global::Marechai.App.MemoriesByMachine.MemoriesByMachineRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The news property</summary>
|
||||
public global::Marechai.App.News.NewsRequestBuilder News
|
||||
{
|
||||
get => new global::Marechai.App.News.NewsRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The people property</summary>
|
||||
public global::Marechai.App.People.PeopleRequestBuilder People
|
||||
{
|
||||
get => new global::Marechai.App.People.PeopleRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The peopleByBook property</summary>
|
||||
public global::Marechai.App.PeopleByBook.PeopleByBookRequestBuilder PeopleByBook
|
||||
{
|
||||
get => new global::Marechai.App.PeopleByBook.PeopleByBookRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The peopleByDocument property</summary>
|
||||
public global::Marechai.App.PeopleByDocument.PeopleByDocumentRequestBuilder PeopleByDocument
|
||||
{
|
||||
get => new global::Marechai.App.PeopleByDocument.PeopleByDocumentRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The peopleByMagazine property</summary>
|
||||
public global::Marechai.App.PeopleByMagazine.PeopleByMagazineRequestBuilder PeopleByMagazine
|
||||
{
|
||||
get => new global::Marechai.App.PeopleByMagazine.PeopleByMagazineRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The processor property</summary>
|
||||
public global::Marechai.App.Processor.ProcessorRequestBuilder Processor
|
||||
{
|
||||
get => new global::Marechai.App.Processor.ProcessorRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The processors property</summary>
|
||||
public global::Marechai.App.Processors.ProcessorsRequestBuilder Processors
|
||||
{
|
||||
get => new global::Marechai.App.Processors.ProcessorsRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The processorsByMachine property</summary>
|
||||
public global::Marechai.App.ProcessorsByMachine.ProcessorsByMachineRequestBuilder ProcessorsByMachine
|
||||
{
|
||||
get => new global::Marechai.App.ProcessorsByMachine.ProcessorsByMachineRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The resolutions property</summary>
|
||||
public global::Marechai.App.Resolutions.ResolutionsRequestBuilder Resolutions
|
||||
{
|
||||
get => new global::Marechai.App.Resolutions.ResolutionsRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The resolutionsByGpu property</summary>
|
||||
public global::Marechai.App.ResolutionsByGpu.ResolutionsByGpuRequestBuilder ResolutionsByGpu
|
||||
{
|
||||
get => new global::Marechai.App.ResolutionsByGpu.ResolutionsByGpuRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The resolutionsByScreen property</summary>
|
||||
public global::Marechai.App.ResolutionsByScreen.ResolutionsByScreenRequestBuilder ResolutionsByScreen
|
||||
{
|
||||
get => new global::Marechai.App.ResolutionsByScreen.ResolutionsByScreenRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The screens property</summary>
|
||||
public global::Marechai.App.Screens.ScreensRequestBuilder Screens
|
||||
{
|
||||
get => new global::Marechai.App.Screens.ScreensRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The screensByMachine property</summary>
|
||||
public global::Marechai.App.ScreensByMachine.ScreensByMachineRequestBuilder ScreensByMachine
|
||||
{
|
||||
get => new global::Marechai.App.ScreensByMachine.ScreensByMachineRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The software property</summary>
|
||||
public global::Marechai.App.Software.SoftwareRequestBuilder Software
|
||||
{
|
||||
get => new global::Marechai.App.Software.SoftwareRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The soundSynths property</summary>
|
||||
public global::Marechai.App.SoundSynths.SoundSynthsRequestBuilder SoundSynths
|
||||
{
|
||||
get => new global::Marechai.App.SoundSynths.SoundSynthsRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The soundSynthsByMachine property</summary>
|
||||
public global::Marechai.App.SoundSynthsByMachine.SoundSynthsByMachineRequestBuilder SoundSynthsByMachine
|
||||
{
|
||||
get => new global::Marechai.App.SoundSynthsByMachine.SoundSynthsByMachineRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The storageByMachine property</summary>
|
||||
public global::Marechai.App.StorageByMachine.StorageByMachineRequestBuilder StorageByMachine
|
||||
{
|
||||
get => new global::Marechai.App.StorageByMachine.StorageByMachineRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The users property</summary>
|
||||
public global::Marechai.App.Users.UsersRequestBuilder Users
|
||||
{
|
||||
get => new global::Marechai.App.Users.UsersRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.App.ApiClient"/> and sets the default values.
|
||||
/// </summary>
|
||||
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
|
||||
public ApiClient(IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}", new Dictionary<string, object>())
|
||||
{
|
||||
ApiClientBuilder.RegisterDefaultSerializer<JsonSerializationWriterFactory>();
|
||||
ApiClientBuilder.RegisterDefaultSerializer<TextSerializationWriterFactory>();
|
||||
ApiClientBuilder.RegisterDefaultSerializer<FormSerializationWriterFactory>();
|
||||
ApiClientBuilder.RegisterDefaultSerializer<MultipartSerializationWriterFactory>();
|
||||
ApiClientBuilder.RegisterDefaultDeserializer<JsonParseNodeFactory>();
|
||||
ApiClientBuilder.RegisterDefaultDeserializer<TextParseNodeFactory>();
|
||||
ApiClientBuilder.RegisterDefaultDeserializer<FormParseNodeFactory>();
|
||||
if (string.IsNullOrEmpty(RequestAdapter.BaseUrl))
|
||||
{
|
||||
RequestAdapter.BaseUrl = "http://localhost:5023";
|
||||
}
|
||||
PathParameters.TryAdd("baseurl", RequestAdapter.BaseUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
41
Marechai.App/Services/Client/Auth/AuthRequestBuilder.cs
Normal file
41
Marechai.App/Services/Client/Auth/AuthRequestBuilder.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// <auto-generated/>
|
||||
#pragma warning disable CS0618
|
||||
using Marechai.App.Auth.Login;
|
||||
using Microsoft.Kiota.Abstractions.Extensions;
|
||||
using Microsoft.Kiota.Abstractions;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
namespace Marechai.App.Auth
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds and executes requests for operations under \auth
|
||||
/// </summary>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class AuthRequestBuilder : BaseRequestBuilder
|
||||
{
|
||||
/// <summary>The login property</summary>
|
||||
public global::Marechai.App.Auth.Login.LoginRequestBuilder Login
|
||||
{
|
||||
get => new global::Marechai.App.Auth.Login.LoginRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.App.Auth.AuthRequestBuilder"/> and sets the default values.
|
||||
/// </summary>
|
||||
/// <param name="pathParameters">Path parameters for the request</param>
|
||||
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
|
||||
public AuthRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/auth", pathParameters)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.App.Auth.AuthRequestBuilder"/> and sets the default values.
|
||||
/// </summary>
|
||||
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
|
||||
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
|
||||
public AuthRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/auth", rawUrl)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
@@ -0,0 +1,98 @@
|
||||
// <auto-generated/>
|
||||
#pragma warning disable CS0618
|
||||
using Marechai.App.Models;
|
||||
using Microsoft.Kiota.Abstractions.Extensions;
|
||||
using Microsoft.Kiota.Abstractions.Serialization;
|
||||
using Microsoft.Kiota.Abstractions;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System;
|
||||
namespace Marechai.App.Auth.Login
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds and executes requests for operations under \auth\login
|
||||
/// </summary>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class LoginRequestBuilder : BaseRequestBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.App.Auth.Login.LoginRequestBuilder"/> and sets the default values.
|
||||
/// </summary>
|
||||
/// <param name="pathParameters">Path parameters for the request</param>
|
||||
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
|
||||
public LoginRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/auth/login", pathParameters)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.App.Auth.Login.LoginRequestBuilder"/> and sets the default values.
|
||||
/// </summary>
|
||||
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
|
||||
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
|
||||
public LoginRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/auth/login", rawUrl)
|
||||
{
|
||||
}
|
||||
/// <returns>A <see cref="global::Marechai.App.Models.AuthResponse"/></returns>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
|
||||
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 401 status code</exception>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public async Task<global::Marechai.App.Models.AuthResponse?> PostAsync(global::Marechai.App.Models.AuthRequest body, Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public async Task<global::Marechai.App.Models.AuthResponse> PostAsync(global::Marechai.App.Models.AuthRequest body, Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#endif
|
||||
if(ReferenceEquals(body, null)) throw new ArgumentNullException(nameof(body));
|
||||
var requestInfo = ToPostRequestInformation(body, requestConfiguration);
|
||||
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
|
||||
{
|
||||
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
|
||||
{ "401", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
|
||||
};
|
||||
return await RequestAdapter.SendAsync<global::Marechai.App.Models.AuthResponse>(requestInfo, global::Marechai.App.Models.AuthResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
/// <returns>A <see cref="RequestInformation"/></returns>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public RequestInformation ToPostRequestInformation(global::Marechai.App.Models.AuthRequest body, Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public RequestInformation ToPostRequestInformation(global::Marechai.App.Models.AuthRequest body, Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
|
||||
{
|
||||
#endif
|
||||
if(ReferenceEquals(body, null)) throw new ArgumentNullException(nameof(body));
|
||||
var requestInfo = new RequestInformation(Method.POST, UrlTemplate, PathParameters);
|
||||
requestInfo.Configure(requestConfiguration);
|
||||
requestInfo.Headers.TryAdd("Accept", "application/json");
|
||||
requestInfo.SetContentFromParsable(RequestAdapter, "application/json", body);
|
||||
return requestInfo;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="global::Marechai.App.Auth.Login.LoginRequestBuilder"/></returns>
|
||||
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
|
||||
public global::Marechai.App.Auth.Login.LoginRequestBuilder WithUrl(string rawUrl)
|
||||
{
|
||||
return new global::Marechai.App.Auth.Login.LoginRequestBuilder(rawUrl, RequestAdapter);
|
||||
}
|
||||
/// <summary>
|
||||
/// Configuration for the request such as headers, query parameters, and middleware options.
|
||||
/// </summary>
|
||||
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class LoginRequestBuilderPostRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
181
Marechai.App/Services/Client/Books/BooksRequestBuilder.cs
Normal file
181
Marechai.App/Services/Client/Books/BooksRequestBuilder.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
// <auto-generated/>
|
||||
#pragma warning disable CS0618
|
||||
using Marechai.App.Books.Companies;
|
||||
using Marechai.App.Books.Item;
|
||||
using Marechai.App.Books.Scans;
|
||||
using Marechai.App.Models;
|
||||
using Microsoft.Kiota.Abstractions.Extensions;
|
||||
using Microsoft.Kiota.Abstractions.Serialization;
|
||||
using Microsoft.Kiota.Abstractions;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System;
|
||||
namespace Marechai.App.Books
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds and executes requests for operations under \books
|
||||
/// </summary>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class BooksRequestBuilder : BaseRequestBuilder
|
||||
{
|
||||
/// <summary>The companies property</summary>
|
||||
public global::Marechai.App.Books.Companies.CompaniesRequestBuilder Companies
|
||||
{
|
||||
get => new global::Marechai.App.Books.Companies.CompaniesRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The scans property</summary>
|
||||
public global::Marechai.App.Books.Scans.ScansRequestBuilder Scans
|
||||
{
|
||||
get => new global::Marechai.App.Books.Scans.ScansRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>Gets an item from the Marechai.App.books.item collection</summary>
|
||||
/// <param name="position">Unique identifier of the item</param>
|
||||
/// <returns>A <see cref="global::Marechai.App.Books.Item.BookItemRequestBuilder"/></returns>
|
||||
public global::Marechai.App.Books.Item.BookItemRequestBuilder this[long position]
|
||||
{
|
||||
get
|
||||
{
|
||||
var urlTplParams = new Dictionary<string, object>(PathParameters);
|
||||
urlTplParams.Add("book%2Did", position);
|
||||
return new global::Marechai.App.Books.Item.BookItemRequestBuilder(urlTplParams, RequestAdapter);
|
||||
}
|
||||
}
|
||||
/// <summary>Gets an item from the Marechai.App.books.item collection</summary>
|
||||
/// <param name="position">Unique identifier of the item</param>
|
||||
/// <returns>A <see cref="global::Marechai.App.Books.Item.BookItemRequestBuilder"/></returns>
|
||||
[Obsolete("This indexer is deprecated and will be removed in the next major version. Use the one with the typed parameter instead.")]
|
||||
public global::Marechai.App.Books.Item.BookItemRequestBuilder this[string position]
|
||||
{
|
||||
get
|
||||
{
|
||||
var urlTplParams = new Dictionary<string, object>(PathParameters);
|
||||
if (!string.IsNullOrWhiteSpace(position)) urlTplParams.Add("book%2Did", position);
|
||||
return new global::Marechai.App.Books.Item.BookItemRequestBuilder(urlTplParams, RequestAdapter);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.App.Books.BooksRequestBuilder"/> and sets the default values.
|
||||
/// </summary>
|
||||
/// <param name="pathParameters">Path parameters for the request</param>
|
||||
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
|
||||
public BooksRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/books", pathParameters)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.App.Books.BooksRequestBuilder"/> and sets the default values.
|
||||
/// </summary>
|
||||
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
|
||||
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
|
||||
public BooksRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/books", rawUrl)
|
||||
{
|
||||
}
|
||||
/// <returns>A List<global::Marechai.App.Models.BookDto></returns>
|
||||
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public async Task<List<global::Marechai.App.Models.BookDto>?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public async Task<List<global::Marechai.App.Models.BookDto>> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#endif
|
||||
var requestInfo = ToGetRequestInformation(requestConfiguration);
|
||||
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
|
||||
{
|
||||
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
|
||||
};
|
||||
var collectionResult = await RequestAdapter.SendCollectionAsync<global::Marechai.App.Models.BookDto>(requestInfo, global::Marechai.App.Models.BookDto.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
|
||||
return collectionResult?.AsList();
|
||||
}
|
||||
/// <returns>A <see cref="long"/></returns>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
|
||||
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 401 status code</exception>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public async Task<long?> PostAsync(global::Marechai.App.Models.BookDto body, Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public async Task<long?> PostAsync(global::Marechai.App.Models.BookDto body, Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#endif
|
||||
if(ReferenceEquals(body, null)) throw new ArgumentNullException(nameof(body));
|
||||
var requestInfo = ToPostRequestInformation(body, requestConfiguration);
|
||||
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
|
||||
{
|
||||
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
|
||||
{ "401", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
|
||||
};
|
||||
return await RequestAdapter.SendPrimitiveAsync<long?>(requestInfo, errorMapping, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
/// <returns>A <see cref="RequestInformation"/></returns>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
|
||||
{
|
||||
#endif
|
||||
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
|
||||
requestInfo.Configure(requestConfiguration);
|
||||
requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9");
|
||||
return requestInfo;
|
||||
}
|
||||
/// <returns>A <see cref="RequestInformation"/></returns>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public RequestInformation ToPostRequestInformation(global::Marechai.App.Models.BookDto body, Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public RequestInformation ToPostRequestInformation(global::Marechai.App.Models.BookDto body, Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
|
||||
{
|
||||
#endif
|
||||
if(ReferenceEquals(body, null)) throw new ArgumentNullException(nameof(body));
|
||||
var requestInfo = new RequestInformation(Method.POST, UrlTemplate, PathParameters);
|
||||
requestInfo.Configure(requestConfiguration);
|
||||
requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9");
|
||||
requestInfo.SetContentFromParsable(RequestAdapter, "application/json", body);
|
||||
return requestInfo;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="global::Marechai.App.Books.BooksRequestBuilder"/></returns>
|
||||
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
|
||||
public global::Marechai.App.Books.BooksRequestBuilder WithUrl(string rawUrl)
|
||||
{
|
||||
return new global::Marechai.App.Books.BooksRequestBuilder(rawUrl, RequestAdapter);
|
||||
}
|
||||
/// <summary>
|
||||
/// Configuration for the request such as headers, query parameters, and middleware options.
|
||||
/// </summary>
|
||||
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class BooksRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Configuration for the request such as headers, query parameters, and middleware options.
|
||||
/// </summary>
|
||||
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class BooksRequestBuilderPostRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
@@ -0,0 +1,124 @@
|
||||
// <auto-generated/>
|
||||
#pragma warning disable CS0618
|
||||
using Marechai.App.Books.Companies.Item;
|
||||
using Marechai.App.Models;
|
||||
using Microsoft.Kiota.Abstractions.Extensions;
|
||||
using Microsoft.Kiota.Abstractions.Serialization;
|
||||
using Microsoft.Kiota.Abstractions;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System;
|
||||
namespace Marechai.App.Books.Companies
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds and executes requests for operations under \books\companies
|
||||
/// </summary>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class CompaniesRequestBuilder : BaseRequestBuilder
|
||||
{
|
||||
/// <summary>Gets an item from the Marechai.App.books.companies.item collection</summary>
|
||||
/// <param name="position">Unique identifier of the item</param>
|
||||
/// <returns>A <see cref="global::Marechai.App.Books.Companies.Item.CompaniesItemRequestBuilder"/></returns>
|
||||
public global::Marechai.App.Books.Companies.Item.CompaniesItemRequestBuilder this[long position]
|
||||
{
|
||||
get
|
||||
{
|
||||
var urlTplParams = new Dictionary<string, object>(PathParameters);
|
||||
urlTplParams.Add("id", position);
|
||||
return new global::Marechai.App.Books.Companies.Item.CompaniesItemRequestBuilder(urlTplParams, RequestAdapter);
|
||||
}
|
||||
}
|
||||
/// <summary>Gets an item from the Marechai.App.books.companies.item collection</summary>
|
||||
/// <param name="position">Unique identifier of the item</param>
|
||||
/// <returns>A <see cref="global::Marechai.App.Books.Companies.Item.CompaniesItemRequestBuilder"/></returns>
|
||||
[Obsolete("This indexer is deprecated and will be removed in the next major version. Use the one with the typed parameter instead.")]
|
||||
public global::Marechai.App.Books.Companies.Item.CompaniesItemRequestBuilder this[string position]
|
||||
{
|
||||
get
|
||||
{
|
||||
var urlTplParams = new Dictionary<string, object>(PathParameters);
|
||||
if (!string.IsNullOrWhiteSpace(position)) urlTplParams.Add("id", position);
|
||||
return new global::Marechai.App.Books.Companies.Item.CompaniesItemRequestBuilder(urlTplParams, RequestAdapter);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.App.Books.Companies.CompaniesRequestBuilder"/> and sets the default values.
|
||||
/// </summary>
|
||||
/// <param name="pathParameters">Path parameters for the request</param>
|
||||
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
|
||||
public CompaniesRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/books/companies", pathParameters)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.App.Books.Companies.CompaniesRequestBuilder"/> and sets the default values.
|
||||
/// </summary>
|
||||
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
|
||||
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
|
||||
public CompaniesRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/books/companies", rawUrl)
|
||||
{
|
||||
}
|
||||
/// <returns>A <see cref="long"/></returns>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
|
||||
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 401 status code</exception>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public async Task<long?> PostAsync(global::Marechai.App.Models.CompanyByBookDto body, Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public async Task<long?> PostAsync(global::Marechai.App.Models.CompanyByBookDto body, Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#endif
|
||||
if(ReferenceEquals(body, null)) throw new ArgumentNullException(nameof(body));
|
||||
var requestInfo = ToPostRequestInformation(body, requestConfiguration);
|
||||
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
|
||||
{
|
||||
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
|
||||
{ "401", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
|
||||
};
|
||||
return await RequestAdapter.SendPrimitiveAsync<long?>(requestInfo, errorMapping, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
/// <returns>A <see cref="RequestInformation"/></returns>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public RequestInformation ToPostRequestInformation(global::Marechai.App.Models.CompanyByBookDto body, Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public RequestInformation ToPostRequestInformation(global::Marechai.App.Models.CompanyByBookDto body, Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
|
||||
{
|
||||
#endif
|
||||
if(ReferenceEquals(body, null)) throw new ArgumentNullException(nameof(body));
|
||||
var requestInfo = new RequestInformation(Method.POST, UrlTemplate, PathParameters);
|
||||
requestInfo.Configure(requestConfiguration);
|
||||
requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9");
|
||||
requestInfo.SetContentFromParsable(RequestAdapter, "application/json", body);
|
||||
return requestInfo;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="global::Marechai.App.Books.Companies.CompaniesRequestBuilder"/></returns>
|
||||
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
|
||||
public global::Marechai.App.Books.Companies.CompaniesRequestBuilder WithUrl(string rawUrl)
|
||||
{
|
||||
return new global::Marechai.App.Books.Companies.CompaniesRequestBuilder(rawUrl, RequestAdapter);
|
||||
}
|
||||
/// <summary>
|
||||
/// Configuration for the request such as headers, query parameters, and middleware options.
|
||||
/// </summary>
|
||||
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class CompaniesRequestBuilderPostRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
@@ -0,0 +1,95 @@
|
||||
// <auto-generated/>
|
||||
#pragma warning disable CS0618
|
||||
using Marechai.App.Models;
|
||||
using Microsoft.Kiota.Abstractions.Extensions;
|
||||
using Microsoft.Kiota.Abstractions.Serialization;
|
||||
using Microsoft.Kiota.Abstractions;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System;
|
||||
namespace Marechai.App.Books.Companies.Item
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds and executes requests for operations under \books\companies\{id}
|
||||
/// </summary>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class CompaniesItemRequestBuilder : BaseRequestBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.App.Books.Companies.Item.CompaniesItemRequestBuilder"/> and sets the default values.
|
||||
/// </summary>
|
||||
/// <param name="pathParameters">Path parameters for the request</param>
|
||||
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
|
||||
public CompaniesItemRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/books/companies/{id}", pathParameters)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.App.Books.Companies.Item.CompaniesItemRequestBuilder"/> and sets the default values.
|
||||
/// </summary>
|
||||
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
|
||||
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
|
||||
public CompaniesItemRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/books/companies/{id}", rawUrl)
|
||||
{
|
||||
}
|
||||
/// <returns>A <see cref="string"/></returns>
|
||||
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
|
||||
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 401 status code</exception>
|
||||
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 404 status code</exception>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public async Task<string?> DeleteAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public async Task<string> DeleteAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#endif
|
||||
var requestInfo = ToDeleteRequestInformation(requestConfiguration);
|
||||
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
|
||||
{
|
||||
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
|
||||
{ "401", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
|
||||
{ "404", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
|
||||
};
|
||||
return await RequestAdapter.SendPrimitiveAsync<string>(requestInfo, errorMapping, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
/// <returns>A <see cref="RequestInformation"/></returns>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public RequestInformation ToDeleteRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public RequestInformation ToDeleteRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
|
||||
{
|
||||
#endif
|
||||
var requestInfo = new RequestInformation(Method.DELETE, UrlTemplate, PathParameters);
|
||||
requestInfo.Configure(requestConfiguration);
|
||||
requestInfo.Headers.TryAdd("Accept", "application/json, text/plain;q=0.9");
|
||||
return requestInfo;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="global::Marechai.App.Books.Companies.Item.CompaniesItemRequestBuilder"/></returns>
|
||||
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
|
||||
public global::Marechai.App.Books.Companies.Item.CompaniesItemRequestBuilder WithUrl(string rawUrl)
|
||||
{
|
||||
return new global::Marechai.App.Books.Companies.Item.CompaniesItemRequestBuilder(rawUrl, RequestAdapter);
|
||||
}
|
||||
/// <summary>
|
||||
/// Configuration for the request such as headers, query parameters, and middleware options.
|
||||
/// </summary>
|
||||
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class CompaniesItemRequestBuilderDeleteRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
@@ -0,0 +1,228 @@
|
||||
// <auto-generated/>
|
||||
#pragma warning disable CS0618
|
||||
using Marechai.App.Books.Item.Companies;
|
||||
using Marechai.App.Books.Item.MachineFamilies;
|
||||
using Marechai.App.Books.Item.Machines;
|
||||
using Marechai.App.Books.Item.People;
|
||||
using Marechai.App.Books.Item.Scans;
|
||||
using Marechai.App.Books.Item.Synopsis;
|
||||
using Marechai.App.Models;
|
||||
using Microsoft.Kiota.Abstractions.Extensions;
|
||||
using Microsoft.Kiota.Abstractions.Serialization;
|
||||
using Microsoft.Kiota.Abstractions;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System;
|
||||
namespace Marechai.App.Books.Item
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds and executes requests for operations under \books\{book-id}
|
||||
/// </summary>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class BookItemRequestBuilder : BaseRequestBuilder
|
||||
{
|
||||
/// <summary>The companies property</summary>
|
||||
public global::Marechai.App.Books.Item.Companies.CompaniesRequestBuilder Companies
|
||||
{
|
||||
get => new global::Marechai.App.Books.Item.Companies.CompaniesRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The machineFamilies property</summary>
|
||||
public global::Marechai.App.Books.Item.MachineFamilies.MachineFamiliesRequestBuilder MachineFamilies
|
||||
{
|
||||
get => new global::Marechai.App.Books.Item.MachineFamilies.MachineFamiliesRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The machines property</summary>
|
||||
public global::Marechai.App.Books.Item.Machines.MachinesRequestBuilder Machines
|
||||
{
|
||||
get => new global::Marechai.App.Books.Item.Machines.MachinesRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The people property</summary>
|
||||
public global::Marechai.App.Books.Item.People.PeopleRequestBuilder People
|
||||
{
|
||||
get => new global::Marechai.App.Books.Item.People.PeopleRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The scans property</summary>
|
||||
public global::Marechai.App.Books.Item.Scans.ScansRequestBuilder Scans
|
||||
{
|
||||
get => new global::Marechai.App.Books.Item.Scans.ScansRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The synopsis property</summary>
|
||||
public global::Marechai.App.Books.Item.Synopsis.SynopsisRequestBuilder Synopsis
|
||||
{
|
||||
get => new global::Marechai.App.Books.Item.Synopsis.SynopsisRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.App.Books.Item.BookItemRequestBuilder"/> and sets the default values.
|
||||
/// </summary>
|
||||
/// <param name="pathParameters">Path parameters for the request</param>
|
||||
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
|
||||
public BookItemRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/books/{book%2Did}", pathParameters)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.App.Books.Item.BookItemRequestBuilder"/> and sets the default values.
|
||||
/// </summary>
|
||||
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
|
||||
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
|
||||
public BookItemRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/books/{book%2Did}", rawUrl)
|
||||
{
|
||||
}
|
||||
/// <returns>A <see cref="string"/></returns>
|
||||
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
|
||||
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 401 status code</exception>
|
||||
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 404 status code</exception>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public async Task<string?> DeleteAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public async Task<string> DeleteAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#endif
|
||||
var requestInfo = ToDeleteRequestInformation(requestConfiguration);
|
||||
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
|
||||
{
|
||||
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
|
||||
{ "401", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
|
||||
{ "404", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
|
||||
};
|
||||
return await RequestAdapter.SendPrimitiveAsync<string>(requestInfo, errorMapping, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
/// <returns>A <see cref="global::Marechai.App.Models.BookDto"/></returns>
|
||||
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public async Task<global::Marechai.App.Models.BookDto?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public async Task<global::Marechai.App.Models.BookDto> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#endif
|
||||
var requestInfo = ToGetRequestInformation(requestConfiguration);
|
||||
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
|
||||
{
|
||||
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
|
||||
};
|
||||
return await RequestAdapter.SendAsync<global::Marechai.App.Models.BookDto>(requestInfo, global::Marechai.App.Models.BookDto.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
/// <returns>A <see cref="string"/></returns>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
|
||||
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 401 status code</exception>
|
||||
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 404 status code</exception>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public async Task<string?> PutAsync(global::Marechai.App.Models.BookDto body, Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public async Task<string> PutAsync(global::Marechai.App.Models.BookDto body, Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#endif
|
||||
if(ReferenceEquals(body, null)) throw new ArgumentNullException(nameof(body));
|
||||
var requestInfo = ToPutRequestInformation(body, requestConfiguration);
|
||||
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
|
||||
{
|
||||
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
|
||||
{ "401", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
|
||||
{ "404", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
|
||||
};
|
||||
return await RequestAdapter.SendPrimitiveAsync<string>(requestInfo, errorMapping, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
/// <returns>A <see cref="RequestInformation"/></returns>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public RequestInformation ToDeleteRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public RequestInformation ToDeleteRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
|
||||
{
|
||||
#endif
|
||||
var requestInfo = new RequestInformation(Method.DELETE, UrlTemplate, PathParameters);
|
||||
requestInfo.Configure(requestConfiguration);
|
||||
requestInfo.Headers.TryAdd("Accept", "application/json, text/plain;q=0.9");
|
||||
return requestInfo;
|
||||
}
|
||||
/// <returns>A <see cref="RequestInformation"/></returns>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
|
||||
{
|
||||
#endif
|
||||
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
|
||||
requestInfo.Configure(requestConfiguration);
|
||||
requestInfo.Headers.TryAdd("Accept", "application/json, text/plain;q=0.9");
|
||||
return requestInfo;
|
||||
}
|
||||
/// <returns>A <see cref="RequestInformation"/></returns>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public RequestInformation ToPutRequestInformation(global::Marechai.App.Models.BookDto body, Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public RequestInformation ToPutRequestInformation(global::Marechai.App.Models.BookDto body, Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
|
||||
{
|
||||
#endif
|
||||
if(ReferenceEquals(body, null)) throw new ArgumentNullException(nameof(body));
|
||||
var requestInfo = new RequestInformation(Method.PUT, UrlTemplate, PathParameters);
|
||||
requestInfo.Configure(requestConfiguration);
|
||||
requestInfo.Headers.TryAdd("Accept", "application/json, text/plain;q=0.9");
|
||||
requestInfo.SetContentFromParsable(RequestAdapter, "application/json", body);
|
||||
return requestInfo;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="global::Marechai.App.Books.Item.BookItemRequestBuilder"/></returns>
|
||||
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
|
||||
public global::Marechai.App.Books.Item.BookItemRequestBuilder WithUrl(string rawUrl)
|
||||
{
|
||||
return new global::Marechai.App.Books.Item.BookItemRequestBuilder(rawUrl, RequestAdapter);
|
||||
}
|
||||
/// <summary>
|
||||
/// Configuration for the request such as headers, query parameters, and middleware options.
|
||||
/// </summary>
|
||||
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class BookItemRequestBuilderDeleteRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Configuration for the request such as headers, query parameters, and middleware options.
|
||||
/// </summary>
|
||||
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class BookItemRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Configuration for the request such as headers, query parameters, and middleware options.
|
||||
/// </summary>
|
||||
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class BookItemRequestBuilderPutRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
@@ -0,0 +1,92 @@
|
||||
// <auto-generated/>
|
||||
#pragma warning disable CS0618
|
||||
using Marechai.App.Models;
|
||||
using Microsoft.Kiota.Abstractions.Extensions;
|
||||
using Microsoft.Kiota.Abstractions.Serialization;
|
||||
using Microsoft.Kiota.Abstractions;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System;
|
||||
namespace Marechai.App.Books.Item.Companies
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds and executes requests for operations under \books\{book-id}\companies
|
||||
/// </summary>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class CompaniesRequestBuilder : BaseRequestBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.App.Books.Item.Companies.CompaniesRequestBuilder"/> and sets the default values.
|
||||
/// </summary>
|
||||
/// <param name="pathParameters">Path parameters for the request</param>
|
||||
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
|
||||
public CompaniesRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/books/{book%2Did}/companies", pathParameters)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.App.Books.Item.Companies.CompaniesRequestBuilder"/> and sets the default values.
|
||||
/// </summary>
|
||||
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
|
||||
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
|
||||
public CompaniesRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/books/{book%2Did}/companies", rawUrl)
|
||||
{
|
||||
}
|
||||
/// <returns>A List<global::Marechai.App.Models.CompanyByBookDto></returns>
|
||||
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public async Task<List<global::Marechai.App.Models.CompanyByBookDto>?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public async Task<List<global::Marechai.App.Models.CompanyByBookDto>> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#endif
|
||||
var requestInfo = ToGetRequestInformation(requestConfiguration);
|
||||
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
|
||||
{
|
||||
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
|
||||
};
|
||||
var collectionResult = await RequestAdapter.SendCollectionAsync<global::Marechai.App.Models.CompanyByBookDto>(requestInfo, global::Marechai.App.Models.CompanyByBookDto.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
|
||||
return collectionResult?.AsList();
|
||||
}
|
||||
/// <returns>A <see cref="RequestInformation"/></returns>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
|
||||
{
|
||||
#endif
|
||||
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
|
||||
requestInfo.Configure(requestConfiguration);
|
||||
requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9");
|
||||
return requestInfo;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="global::Marechai.App.Books.Item.Companies.CompaniesRequestBuilder"/></returns>
|
||||
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
|
||||
public global::Marechai.App.Books.Item.Companies.CompaniesRequestBuilder WithUrl(string rawUrl)
|
||||
{
|
||||
return new global::Marechai.App.Books.Item.Companies.CompaniesRequestBuilder(rawUrl, RequestAdapter);
|
||||
}
|
||||
/// <summary>
|
||||
/// Configuration for the request such as headers, query parameters, and middleware options.
|
||||
/// </summary>
|
||||
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class CompaniesRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
@@ -0,0 +1,92 @@
|
||||
// <auto-generated/>
|
||||
#pragma warning disable CS0618
|
||||
using Marechai.App.Models;
|
||||
using Microsoft.Kiota.Abstractions.Extensions;
|
||||
using Microsoft.Kiota.Abstractions.Serialization;
|
||||
using Microsoft.Kiota.Abstractions;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System;
|
||||
namespace Marechai.App.Books.Item.MachineFamilies
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds and executes requests for operations under \books\{book-id}\machine-families
|
||||
/// </summary>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class MachineFamiliesRequestBuilder : BaseRequestBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.App.Books.Item.MachineFamilies.MachineFamiliesRequestBuilder"/> and sets the default values.
|
||||
/// </summary>
|
||||
/// <param name="pathParameters">Path parameters for the request</param>
|
||||
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
|
||||
public MachineFamiliesRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/books/{book%2Did}/machine-families", pathParameters)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.App.Books.Item.MachineFamilies.MachineFamiliesRequestBuilder"/> and sets the default values.
|
||||
/// </summary>
|
||||
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
|
||||
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
|
||||
public MachineFamiliesRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/books/{book%2Did}/machine-families", rawUrl)
|
||||
{
|
||||
}
|
||||
/// <returns>A List<global::Marechai.App.Models.BookByMachineFamilyDto></returns>
|
||||
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public async Task<List<global::Marechai.App.Models.BookByMachineFamilyDto>?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public async Task<List<global::Marechai.App.Models.BookByMachineFamilyDto>> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#endif
|
||||
var requestInfo = ToGetRequestInformation(requestConfiguration);
|
||||
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
|
||||
{
|
||||
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
|
||||
};
|
||||
var collectionResult = await RequestAdapter.SendCollectionAsync<global::Marechai.App.Models.BookByMachineFamilyDto>(requestInfo, global::Marechai.App.Models.BookByMachineFamilyDto.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
|
||||
return collectionResult?.AsList();
|
||||
}
|
||||
/// <returns>A <see cref="RequestInformation"/></returns>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
|
||||
{
|
||||
#endif
|
||||
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
|
||||
requestInfo.Configure(requestConfiguration);
|
||||
requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9");
|
||||
return requestInfo;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="global::Marechai.App.Books.Item.MachineFamilies.MachineFamiliesRequestBuilder"/></returns>
|
||||
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
|
||||
public global::Marechai.App.Books.Item.MachineFamilies.MachineFamiliesRequestBuilder WithUrl(string rawUrl)
|
||||
{
|
||||
return new global::Marechai.App.Books.Item.MachineFamilies.MachineFamiliesRequestBuilder(rawUrl, RequestAdapter);
|
||||
}
|
||||
/// <summary>
|
||||
/// Configuration for the request such as headers, query parameters, and middleware options.
|
||||
/// </summary>
|
||||
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class MachineFamiliesRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user