mirror of
https://github.com/claunia/marechai.git
synced 2025-12-16 19:14:25 +00:00
Show machine photo thumbnails in machine details.
This commit is contained in:
@@ -32,7 +32,7 @@ public partial class App : Application
|
||||
}
|
||||
|
||||
protected Window? MainWindow { get; private set; }
|
||||
protected IHost? Host { get; private set; }
|
||||
public IHost? Host { get; private set; }
|
||||
|
||||
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
@@ -109,6 +109,7 @@ public partial class App : Application
|
||||
// Register application services
|
||||
services.AddSingleton<FlagCache>();
|
||||
services.AddSingleton<CompanyLogoCache>();
|
||||
services.AddSingleton<MachinePhotoCache>();
|
||||
services.AddSingleton<NewsService>();
|
||||
services.AddSingleton<NewsViewModel>();
|
||||
services.AddSingleton<ComputersService>();
|
||||
|
||||
@@ -28,13 +28,18 @@
|
||||
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.Helpers;
|
||||
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;
|
||||
@@ -44,6 +49,7 @@ 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;
|
||||
@@ -94,6 +100,9 @@ public partial class MachineViewViewModel : ObservableObject
|
||||
[ObservableProperty]
|
||||
private Visibility _showModel = Visibility.Collapsed;
|
||||
|
||||
[ObservableProperty]
|
||||
private Visibility _showPhotos = Visibility.Collapsed;
|
||||
|
||||
[ObservableProperty]
|
||||
private Visibility _showProcessors = Visibility.Collapsed;
|
||||
|
||||
@@ -104,11 +113,12 @@ public partial class MachineViewViewModel : ObservableObject
|
||||
private Visibility _showStorage = Visibility.Collapsed;
|
||||
|
||||
public MachineViewViewModel(ILogger<MachineViewViewModel> logger, INavigator navigator,
|
||||
ComputersService computersService)
|
||||
ComputersService computersService, MachinePhotoCache photoCache)
|
||||
{
|
||||
_logger = logger;
|
||||
_navigator = navigator;
|
||||
_computersService = computersService;
|
||||
_photoCache = photoCache;
|
||||
}
|
||||
|
||||
public ObservableCollection<ProcessorDisplayItem> Processors { get; } = [];
|
||||
@@ -116,6 +126,7 @@ public partial class MachineViewViewModel : ObservableObject
|
||||
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()
|
||||
@@ -192,6 +203,7 @@ public partial class MachineViewViewModel : ObservableObject
|
||||
Gpus.Clear();
|
||||
SoundSynthesizers.Clear();
|
||||
Storage.Clear();
|
||||
Photos.Clear();
|
||||
|
||||
_logger.LogInformation("Loading machine {MachineId}", machineId);
|
||||
|
||||
@@ -324,6 +336,25 @@ public partial class MachineViewViewModel : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -354,6 +385,28 @@ public partial class MachineViewViewModel : ObservableObject
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,3 +458,14 @@ 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; }
|
||||
}
|
||||
@@ -86,7 +86,14 @@
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<ItemsRepeater ItemsSource="{Binding LettersList}"
|
||||
Layout="{StaticResource LettersGridLayout}">
|
||||
<ItemsRepeater.ItemTemplate></ItemsRepeater.ItemTemplate>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Content="{Binding}"
|
||||
Command="{Binding DataContext.NavigateByLetterCommand, ElementName=PageRoot}"
|
||||
CommandParameter="{Binding}"
|
||||
Style="{StaticResource KeyboardKeyButtonStyle}" />
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</StackPanel>
|
||||
|
||||
@@ -98,7 +105,14 @@
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<ItemsRepeater ItemsSource="{Binding YearsList}"
|
||||
Layout="{StaticResource YearsGridLayout}">
|
||||
<ItemsRepeater.ItemTemplate></ItemsRepeater.ItemTemplate>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Content="{Binding}"
|
||||
Command="{Binding DataContext.NavigateByYearCommand, ElementName=PageRoot}"
|
||||
CommandParameter="{Binding}"
|
||||
Style="{StaticResource KeyboardKeyButtonStyle}" />
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
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}">
|
||||
|
||||
@@ -372,6 +373,37 @@
|
||||
</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>
|
||||
<Grid Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<Image Source="{Binding ThumbnailImageSource}"
|
||||
Stretch="Uniform"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
MaxWidth="256"
|
||||
MaxHeight="256" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</wctui:Carousel.ItemTemplate>
|
||||
</wctui:Carousel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Bottom Spacing -->
|
||||
<Border Height="24" />
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ public sealed class MachinePhotoCache
|
||||
|
||||
public async Task<Stream> GetThumbnailAsync(Guid photoId)
|
||||
{
|
||||
var filename = $"{photoId}.svg";
|
||||
var filename = $"{photoId}.webp";
|
||||
|
||||
Stream retStream;
|
||||
|
||||
@@ -53,7 +53,7 @@ public sealed class MachinePhotoCache
|
||||
|
||||
public async Task<Stream> GetPhotoAsync(Guid photoId)
|
||||
{
|
||||
var filename = $"{photoId}.svg";
|
||||
var filename = $"{photoId}.webp";
|
||||
|
||||
Stream retStream;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Marechai.App.Helpers;
|
||||
using Microsoft.Kiota.Abstractions.Serialization;
|
||||
@@ -205,4 +206,39 @@ public class ComputersService
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the list of photo GUIDs for a machine from the API
|
||||
/// </summary>
|
||||
public async Task<List<Guid>> GetMachinePhotosAsync(int machineId)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Fetching photos for machine {MachineId} from API", machineId);
|
||||
|
||||
List<Guid?>? photos = await _apiClient.Machines[machineId].Photos.GetAsync();
|
||||
|
||||
if(photos == null || photos.Count == 0)
|
||||
{
|
||||
_logger.LogInformation("No photos found for machine {MachineId}", machineId);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
// Filter out null values
|
||||
var validPhotos = photos.Where(p => p.HasValue).Select(p => p!.Value).ToList();
|
||||
|
||||
_logger.LogInformation("Successfully fetched {Count} photos for machine {MachineId}",
|
||||
validPhotos.Count,
|
||||
machineId);
|
||||
|
||||
return validPhotos;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching photos for machine {MachineId} from API", machineId);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user