Add carousel of logos on company detail.

This commit is contained in:
2025-11-15 18:09:54 +00:00
parent 4d30530ef0
commit fe2c3a082d
6 changed files with 183 additions and 13 deletions

View File

@@ -34,4 +34,14 @@
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.11"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.11"/>
</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>

View File

@@ -80,4 +80,14 @@
</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>
</Project>

View File

@@ -32,6 +32,9 @@ public partial class CompanyDetailViewModel : ObservableObject
[ObservableProperty]
private int _companyId;
[ObservableProperty]
private ObservableCollection<CompanyLogoItem> _companyLogos = [];
[ObservableProperty]
private ObservableCollection<CompanyDetailMachine> _computers = [];
@@ -106,6 +109,11 @@ public partial class CompanyDetailViewModel : ObservableObject
/// </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; }
@@ -130,6 +138,13 @@ public partial class CompanyDetailViewModel : ObservableObject
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);
@@ -324,6 +339,7 @@ public partial class CompanyDetailViewModel : ObservableObject
IsDataLoaded = false;
FlagImageSource = null;
LogoImageSource = null;
CompanyLogos.Clear();
if(CompanyId <= 0)
{
@@ -379,7 +395,7 @@ public partial class CompanyDetailViewModel : ObservableObject
{
try
{
Stream? logoStream = await _logoCache.GetFlagAsync(Company.LastLogo.Value);
Stream? logoStream = await _logoCache.GetLogoAsync(Company.LastLogo.Value);
var logoSource = new SvgImageSource();
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
List<MachineDto> machines = await _companyDetailService.GetComputersByCompanyAsync(CompanyId);
Computers.Clear();
@@ -453,3 +521,13 @@ 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; }
}

View File

@@ -4,6 +4,7 @@
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}">
@@ -201,6 +202,49 @@
</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">

View File

@@ -10,7 +10,7 @@ namespace Marechai.App.Services.Caching;
public sealed class CompanyLogoCache
{
readonly IConfiguration _configuration;
StorageFolder _flagsFolder;
StorageFolder _logosFolder;
public CompanyLogoCache(IConfiguration configuration)
{
@@ -21,32 +21,32 @@ public sealed class CompanyLogoCache
async Task EnsureFolderExistAsync()
{
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";
Stream retStream;
if(await _flagsFolder.TryGetItemAsync(filename) is StorageFile file)
if(await _logosFolder.TryGetItemAsync(filename) is StorageFile file)
{
retStream = await file.OpenStreamForReadAsync();
return retStream;
}
await CacheFlagAsync(companyLogoId);
await CacheLogoAsync(companyLogoId);
file = await _flagsFolder.GetFileAsync(filename);
file = await _logosFolder.GetFileAsync(filename);
retStream = await file.OpenStreamForReadAsync();
return retStream;
}
async Task CacheFlagAsync(Guid companyLogoId)
async Task CacheLogoAsync(Guid companyLogoId)
{
var filename = $"{companyLogoId}.svg";
string baseUrl = _configuration.GetSection("ApiClient:Url").Value;
@@ -56,7 +56,7 @@ public sealed class CompanyLogoCache
response.EnsureSuccessStatusCode();
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();
await stream.CopyToAsync(fileStream);

View File

@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Marechai.App.Models;
namespace Marechai.App.Services;
@@ -64,7 +63,9 @@ public class CompanyDetailService
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;
}
@@ -98,4 +99,31 @@ public class CompanyDetailService
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 [];
}
}
}