mirror of
https://github.com/claunia/marechai.git
synced 2025-12-16 19:14:25 +00:00
Add carousel of logos on company detail.
This commit is contained in:
@@ -34,4 +34,14 @@
|
|||||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.11"/>
|
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.11"/>
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.11"/>
|
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.11"/>
|
||||||
</ItemGroup>
|
</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>
|
</Project>
|
||||||
@@ -80,4 +80,14 @@
|
|||||||
</Compile>
|
</Compile>
|
||||||
</ItemGroup>
|
</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>
|
||||||
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ public partial class CompanyDetailViewModel : ObservableObject
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private int _companyId;
|
private int _companyId;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private ObservableCollection<CompanyLogoItem> _companyLogos = [];
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ObservableCollection<CompanyDetailMachine> _computers = [];
|
private ObservableCollection<CompanyDetailMachine> _computers = [];
|
||||||
|
|
||||||
@@ -106,6 +109,11 @@ public partial class CompanyDetailViewModel : ObservableObject
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasLogoContent => LogoImageSource != null;
|
public bool HasLogoContent => LogoImageSource != null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether company has multiple logos
|
||||||
|
/// </summary>
|
||||||
|
public bool HasMultipleLogos => CompanyLogos.Count > 1;
|
||||||
|
|
||||||
public IAsyncRelayCommand LoadData { get; }
|
public IAsyncRelayCommand LoadData { get; }
|
||||||
public ICommand GoBackCommand { get; }
|
public ICommand GoBackCommand { get; }
|
||||||
public IAsyncRelayCommand<CompanyDetailMachine> NavigateToMachineCommand { get; }
|
public IAsyncRelayCommand<CompanyDetailMachine> NavigateToMachineCommand { get; }
|
||||||
@@ -130,6 +138,13 @@ public partial class CompanyDetailViewModel : ObservableObject
|
|||||||
OnPropertyChanged(nameof(HasLogoContent));
|
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)
|
partial void OnComputersFilterTextChanged(string value)
|
||||||
{
|
{
|
||||||
FilterComputers(value);
|
FilterComputers(value);
|
||||||
@@ -324,6 +339,7 @@ public partial class CompanyDetailViewModel : ObservableObject
|
|||||||
IsDataLoaded = false;
|
IsDataLoaded = false;
|
||||||
FlagImageSource = null;
|
FlagImageSource = null;
|
||||||
LogoImageSource = null;
|
LogoImageSource = null;
|
||||||
|
CompanyLogos.Clear();
|
||||||
|
|
||||||
if(CompanyId <= 0)
|
if(CompanyId <= 0)
|
||||||
{
|
{
|
||||||
@@ -379,7 +395,7 @@ public partial class CompanyDetailViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Stream? logoStream = await _logoCache.GetFlagAsync(Company.LastLogo.Value);
|
Stream? logoStream = await _logoCache.GetLogoAsync(Company.LastLogo.Value);
|
||||||
|
|
||||||
var logoSource = new SvgImageSource();
|
var logoSource = new SvgImageSource();
|
||||||
await logoSource.SetSourceAsync(logoStream.AsRandomAccessStream());
|
await logoSource.SetSourceAsync(logoStream.AsRandomAccessStream());
|
||||||
@@ -395,6 +411,58 @@ public partial class CompanyDetailViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
Year = logo.Year != null
|
||||||
|
? (int?)UntypedNodeExtractor.ExtractInt(logo.Year)
|
||||||
|
: null
|
||||||
|
})
|
||||||
|
.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
|
// Load computers and consoles made by this company
|
||||||
List<MachineDto> machines = await _companyDetailService.GetComputersByCompanyAsync(CompanyId);
|
List<MachineDto> machines = await _companyDetailService.GetComputersByCompanyAsync(CompanyId);
|
||||||
Computers.Clear();
|
Computers.Clear();
|
||||||
@@ -452,4 +520,14 @@ public class CompanyDetailMachine
|
|||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Name { get; set; } = string.Empty;
|
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; }
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:utu="using:Uno.Toolkit.UI"
|
xmlns:utu="using:Uno.Toolkit.UI"
|
||||||
|
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
|
||||||
NavigationCacheMode="Required"
|
NavigationCacheMode="Required"
|
||||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||||
|
|
||||||
@@ -201,6 +202,49 @@
|
|||||||
</HyperlinkButton>
|
</HyperlinkButton>
|
||||||
</StackPanel>
|
</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 -->
|
<!-- Computers Section -->
|
||||||
<StackPanel Visibility="{Binding Computers.Count, Converter={StaticResource ZeroToVisibilityConverter}}"
|
<StackPanel Visibility="{Binding Computers.Count, Converter={StaticResource ZeroToVisibilityConverter}}"
|
||||||
Spacing="8">
|
Spacing="8">
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Marechai.App.Services.Caching;
|
|||||||
public sealed class CompanyLogoCache
|
public sealed class CompanyLogoCache
|
||||||
{
|
{
|
||||||
readonly IConfiguration _configuration;
|
readonly IConfiguration _configuration;
|
||||||
StorageFolder _flagsFolder;
|
StorageFolder _logosFolder;
|
||||||
|
|
||||||
public CompanyLogoCache(IConfiguration configuration)
|
public CompanyLogoCache(IConfiguration configuration)
|
||||||
{
|
{
|
||||||
@@ -21,32 +21,32 @@ public sealed class CompanyLogoCache
|
|||||||
async Task EnsureFolderExistAsync()
|
async Task EnsureFolderExistAsync()
|
||||||
{
|
{
|
||||||
StorageFolder localFolder = ApplicationData.Current.LocalCacheFolder;
|
StorageFolder localFolder = ApplicationData.Current.LocalCacheFolder;
|
||||||
_flagsFolder = await localFolder.CreateFolderAsync("logos", CreationCollisionOption.OpenIfExists);
|
_logosFolder = await localFolder.CreateFolderAsync("logos", CreationCollisionOption.OpenIfExists);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Stream> GetFlagAsync(Guid companyLogoId)
|
public async Task<Stream> GetLogoAsync(Guid companyLogoId)
|
||||||
{
|
{
|
||||||
var filename = $"{companyLogoId}.svg";
|
var filename = $"{companyLogoId}.svg";
|
||||||
|
|
||||||
Stream retStream;
|
Stream retStream;
|
||||||
|
|
||||||
if(await _flagsFolder.TryGetItemAsync(filename) is StorageFile file)
|
if(await _logosFolder.TryGetItemAsync(filename) is StorageFile file)
|
||||||
{
|
{
|
||||||
retStream = await file.OpenStreamForReadAsync();
|
retStream = await file.OpenStreamForReadAsync();
|
||||||
|
|
||||||
return retStream;
|
return retStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
await CacheFlagAsync(companyLogoId);
|
await CacheLogoAsync(companyLogoId);
|
||||||
|
|
||||||
file = await _flagsFolder.GetFileAsync(filename);
|
file = await _logosFolder.GetFileAsync(filename);
|
||||||
|
|
||||||
retStream = await file.OpenStreamForReadAsync();
|
retStream = await file.OpenStreamForReadAsync();
|
||||||
|
|
||||||
return retStream;
|
return retStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task CacheFlagAsync(Guid companyLogoId)
|
async Task CacheLogoAsync(Guid companyLogoId)
|
||||||
{
|
{
|
||||||
var filename = $"{companyLogoId}.svg";
|
var filename = $"{companyLogoId}.svg";
|
||||||
string baseUrl = _configuration.GetSection("ApiClient:Url").Value;
|
string baseUrl = _configuration.GetSection("ApiClient:Url").Value;
|
||||||
@@ -56,7 +56,7 @@ public sealed class CompanyLogoCache
|
|||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
using Stream stream = await response.Content.ReadAsStreamAsync();
|
using Stream stream = await response.Content.ReadAsStreamAsync();
|
||||||
StorageFile file = await _flagsFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
|
StorageFile file = await _logosFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
|
||||||
|
|
||||||
using Stream fileStream = await file.OpenStreamForWriteAsync();
|
using Stream fileStream = await file.OpenStreamForWriteAsync();
|
||||||
await stream.CopyToAsync(fileStream);
|
await stream.CopyToAsync(fileStream);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Marechai.App.Models;
|
|
||||||
|
|
||||||
namespace Marechai.App.Services;
|
namespace Marechai.App.Services;
|
||||||
|
|
||||||
@@ -12,7 +11,7 @@ namespace Marechai.App.Services;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class CompanyDetailService
|
public class CompanyDetailService
|
||||||
{
|
{
|
||||||
private readonly ApiClient _apiClient;
|
private readonly ApiClient _apiClient;
|
||||||
private readonly ILogger<CompanyDetailService> _logger;
|
private readonly ILogger<CompanyDetailService> _logger;
|
||||||
|
|
||||||
public CompanyDetailService(ApiClient apiClient, ILogger<CompanyDetailService> logger)
|
public CompanyDetailService(ApiClient apiClient, ILogger<CompanyDetailService> logger)
|
||||||
@@ -64,7 +63,9 @@ public class CompanyDetailService
|
|||||||
|
|
||||||
if(machines == null) return [];
|
if(machines == null) return [];
|
||||||
|
|
||||||
_logger.LogInformation("Successfully fetched {Count} computers for company {CompanyId}", machines.Count, companyId);
|
_logger.LogInformation("Successfully fetched {Count} computers for company {CompanyId}",
|
||||||
|
machines.Count,
|
||||||
|
companyId);
|
||||||
|
|
||||||
return machines;
|
return machines;
|
||||||
}
|
}
|
||||||
@@ -98,4 +99,31 @@ public class CompanyDetailService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all logos for a company
|
||||||
|
/// </summary>
|
||||||
|
public async Task<List<CompanyLogoDto>> GetCompanyLogosAsync(int companyId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Fetching logos for company {CompanyId}", companyId);
|
||||||
|
|
||||||
|
List<CompanyLogoDto>? logos = await _apiClient.Companies[companyId].Logos.GetAsync();
|
||||||
|
|
||||||
|
if(logos == null) return [];
|
||||||
|
|
||||||
|
_logger.LogInformation("Successfully fetched {Count} logos for company {CompanyId}",
|
||||||
|
logos.Count,
|
||||||
|
companyId);
|
||||||
|
|
||||||
|
return logos;
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error fetching logos for company {CompanyId}", companyId);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user