1+ using System . Collections . Concurrent ;
12using System . Collections . ObjectModel ;
23using System . ComponentModel ;
4+ using System . Net . Http ;
35using Avalonia . Collections ;
6+ using Avalonia . Media . Imaging ;
7+ using Avalonia . Threading ;
48using UniGetUI . Avalonia . ViewModels . Pages ;
9+ using UniGetUI . Core . SettingsEngine ;
10+ using UniGetUI . Core . Tools ;
511using UniGetUI . Interface . Enums ;
612using UniGetUI . PackageEngine . Interfaces ;
713
@@ -13,6 +19,12 @@ namespace UniGetUI.PackageEngine.PackageClasses;
1319/// </summary>
1420public sealed class PackageWrapper : INotifyPropertyChanged , IDisposable
1521{
22+ private static readonly HttpClient _iconHttpClient = new ( CoreTools . GenericHttpClientParameters )
23+ {
24+ Timeout = TimeSpan . FromSeconds ( 8 ) ,
25+ } ;
26+ private static readonly ConcurrentDictionary < long , Bitmap ? > _iconCache = new ( ) ;
27+
1628 public IPackage Package { get ; }
1729 public PackageWrapper Self => this ;
1830 public int Index { get ; set ; }
@@ -21,6 +33,19 @@ public sealed class PackageWrapper : INotifyPropertyChanged, IDisposable
2133
2234 private readonly PackagesPageViewModel _page ;
2335
36+ private Bitmap ? _iconBitmap ;
37+ public Bitmap ? IconBitmap
38+ {
39+ get => _iconBitmap ;
40+ private set
41+ {
42+ _iconBitmap = value ;
43+ PropertyChanged ? . Invoke ( this , new PropertyChangedEventArgs ( nameof ( IconBitmap ) ) ) ;
44+ PropertyChanged ? . Invoke ( this , new PropertyChangedEventArgs ( nameof ( HasCustomIcon ) ) ) ;
45+ }
46+ }
47+ public bool HasCustomIcon => _iconBitmap is not null ;
48+
2449 public bool IsChecked
2550 {
2651 get => Package . IsChecked ;
@@ -35,6 +60,8 @@ public bool IsChecked
3560 public string VersionComboString { get ; }
3661 public string ListedNameTooltip { get ; private set ; } = "" ;
3762 public float ListedOpacity { get ; private set ; } = 1.0f ;
63+ public string TagIconPath { get ; private set ; } = "" ;
64+ public bool TagIconVisible { get ; private set ; }
3865
3966 public string SourceIconPath => IconTypeToSvgPath ( Package . Source . IconId ) ;
4067
@@ -63,6 +90,43 @@ public PackageWrapper(IPackage package, PackagesPageViewModel page)
6390
6491 Package . PropertyChanged += Package_PropertyChanged ;
6592 UpdateDisplayState ( ) ;
93+
94+ if ( ! Settings . Get ( Settings . K . DisableIconsOnPackageLists ) )
95+ _ = LoadIconAsync ( ) ;
96+ }
97+
98+ private async Task LoadIconAsync ( )
99+ {
100+ long hash = Package . GetHash ( ) ;
101+ if ( _iconCache . TryGetValue ( hash , out Bitmap ? cached ) )
102+ {
103+ if ( cached is not null )
104+ IconBitmap = cached ;
105+ return ;
106+ }
107+
108+ try
109+ {
110+ var uri = Package . GetIconUrlIfAny ( ) ;
111+ if ( uri is null ) { _iconCache [ hash ] = null ; return ; }
112+
113+ Bitmap bitmap ;
114+ if ( uri . IsFile )
115+ {
116+ bitmap = new Bitmap ( uri . LocalPath ) ;
117+ }
118+ else if ( uri . Scheme is "http" or "https" )
119+ {
120+ var bytes = await _iconHttpClient . GetByteArrayAsync ( uri ) ;
121+ using var ms = new MemoryStream ( bytes ) ;
122+ bitmap = new Bitmap ( ms ) ;
123+ }
124+ else { _iconCache [ hash ] = null ; return ; }
125+
126+ _iconCache [ hash ] = bitmap ;
127+ await Dispatcher . UIThread . InvokeAsync ( ( ) => IconBitmap = bitmap ) ;
128+ }
129+ catch { _iconCache [ hash ] = null ; }
66130 }
67131
68132 private void Package_PropertyChanged ( object ? sender , PropertyChangedEventArgs e )
@@ -72,6 +136,8 @@ private void Package_PropertyChanged(object? sender, PropertyChangedEventArgs e)
72136 UpdateDisplayState ( ) ;
73137 PropertyChanged ? . Invoke ( this , new PropertyChangedEventArgs ( nameof ( ListedOpacity ) ) ) ;
74138 PropertyChanged ? . Invoke ( this , new PropertyChangedEventArgs ( nameof ( ListedNameTooltip ) ) ) ;
139+ PropertyChanged ? . Invoke ( this , new PropertyChangedEventArgs ( nameof ( TagIconPath ) ) ) ;
140+ PropertyChanged ? . Invoke ( this , new PropertyChangedEventArgs ( nameof ( TagIconVisible ) ) ) ;
75141 }
76142 else if ( e . PropertyName == nameof ( Package . IsChecked ) )
77143 {
@@ -91,6 +157,21 @@ private void UpdateDisplayState()
91157 _ => 1.0f ,
92158 } ;
93159 ListedNameTooltip = Package . Name ;
160+
161+ string tagName = Package . Tag switch
162+ {
163+ PackageTag . AlreadyInstalled => "installed_filled" ,
164+ PackageTag . IsUpgradable => "upgradable_filled" ,
165+ PackageTag . Pinned => "pin_filled" ,
166+ PackageTag . OnQueue => "sandclock" ,
167+ PackageTag . BeingProcessed => "loading_filled" ,
168+ PackageTag . Failed => "warning_filled" ,
169+ _ => "" ,
170+ } ;
171+ TagIconVisible = tagName . Length > 0 ;
172+ TagIconPath = TagIconVisible
173+ ? $ "avares://UniGetUI.Avalonia/Assets/Symbols/{ tagName } .svg"
174+ : "" ;
94175 }
95176
96177 public void Dispose ( )
0 commit comments