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.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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user