Sfoglia il codice sorgente

updated libs, android performance fix, DI fix, ui changes

Veloe 11 mesi fa
parent
commit
d6fcd0d716
20 ha cambiato i file con 198 aggiunte e 137 eliminazioni
  1. 5 2
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp.Android/MainActivity.cs
  2. 4 4
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp.Android/VeloeAvaloniaKemonoPartyApp.Android.csproj
  3. 2 1
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp.Desktop/Services/DesktopStorageService.cs
  4. 4 4
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp.Desktop/VeloeAvaloniaKemonoPartyApp.Desktop.csproj
  5. 3 0
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/App.axaml.cs
  6. 4 4
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/Models/Creator.cs
  7. 17 34
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/Models/Post.cs
  8. 5 6
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/Services/Extensions.cs
  9. 71 21
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/Services/HttpClient.cs
  10. 0 9
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/Services/RegisteredServices.cs
  11. 7 6
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp.csproj
  12. 3 1
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/ViewModels/AttachmentViewModel.cs
  13. 4 3
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/ViewModels/CreatorPostsViewModel.cs
  14. 0 4
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/ViewModels/CreatorViewModel.cs
  15. 5 3
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/ViewModels/CreatorsViewModel.cs
  16. 21 21
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/ViewModels/PostImageViewModel.cs
  17. 2 0
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/ViewModels/PostViewModel.cs
  18. 9 8
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/Views/CreatorPostsView.axaml
  19. 10 5
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/Views/CreatorView.axaml
  20. 22 1
      VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/Views/CreatorsView.axaml

+ 5 - 2
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp.Android/MainActivity.cs

@@ -3,6 +3,7 @@ using Android.Content.PM;
 using Avalonia;
 using Avalonia.Android;
 using Avalonia.ReactiveUI;
+using Splat;
 using VeloeAvaloniaKemonoPartyApp.Android.Services;
 using VeloeAvaloniaKemonoPartyApp.Services;
 
@@ -18,11 +19,13 @@ namespace VeloeAvaloniaKemonoPartyApp.Android
     {
         protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
         {
-            RegisteredServices.OpenUrlService = new AndroidOpenUrlService();
-            RegisteredServices.StorageService = new AndroidStorageService(CacheDir.AbsolutePath);
+            Locator.CurrentMutable.RegisterLazySingleton<IOpenUrlService>(() =>  new AndroidOpenUrlService());
+            Locator.CurrentMutable.RegisterLazySingleton<IStorageService>(() => new AndroidStorageService(CacheDir.AbsolutePath));
             
             return base.CustomizeAppBuilder(builder)
                 .WithInterFont()
+                .UseSkia().With(new SkiaOptions() { MaxGpuResourceSizeBytes = 512 * 1024 * 1024 })
+                .With(new AndroidPlatformOptions() { RenderingMode = new[] { AndroidRenderingMode.Vulkan } })
                 .UseReactiveUI();
         }
     }

+ 4 - 4
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp.Android/VeloeAvaloniaKemonoPartyApp.Android.csproj

@@ -6,11 +6,11 @@
     <Nullable>enable</Nullable>
     <ApplicationId>com.Veloe.AvaloniaKemonoParty</ApplicationId>
     <ApplicationVersion>1</ApplicationVersion>
-    <ApplicationDisplayVersion>1.0.0.160</ApplicationDisplayVersion>
+    <ApplicationDisplayVersion>1.0.0.179</ApplicationDisplayVersion>
     <AndroidPackageFormat>apk</AndroidPackageFormat>
     <AndroidEnableProfiledAot>False</AndroidEnableProfiledAot>
-    <AssemblyVersion>1.0.0.160</AssemblyVersion>
-    <FileVersion>1.0.0.160</FileVersion>
+    <AssemblyVersion>1.0.0.182</AssemblyVersion>
+    <FileVersion>1.0.0.182</FileVersion>
   </PropertyGroup>
 
   <ItemGroup>
@@ -20,7 +20,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="Avalonia.Android" Version="11.1.3" />
+    <PackageReference Include="Avalonia.Android" Version="11.2.0-rc1" />
     <PackageReference Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1.12" />
     <PackageReference Include="Xamarin.Essentials" Version="1.8.1" />
   </ItemGroup>

+ 2 - 1
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp.Desktop/Services/DesktopStorageService.cs

@@ -1,4 +1,5 @@
 using Avalonia;
+using Splat;
 using System.IO;
 using System.Reflection;
 using System.Threading.Tasks;
@@ -27,7 +28,7 @@ namespace VeloeAvaloniaKemonoPartyApp.Desktop.Services
     {
         public static AppBuilder InitStorageService(this AppBuilder appBuilder)
         {
-            RegisteredServices.StorageService = new DesktopStorageService();
+            Locator.CurrentMutable.RegisterLazySingleton<IStorageService>(()=>new DesktopStorageService());
             return appBuilder;
         }
     }

+ 4 - 4
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp.Desktop/VeloeAvaloniaKemonoPartyApp.Desktop.csproj

@@ -10,14 +10,14 @@
 
   <PropertyGroup>
     <ApplicationManifest>app.manifest</ApplicationManifest>
-    <AssemblyVersion>1.0.0.135</AssemblyVersion>
-    <FileVersion>1.0.0.135</FileVersion>
+    <AssemblyVersion>1.0.0.175</AssemblyVersion>
+    <FileVersion>1.0.0.175</FileVersion>
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Avalonia.Desktop" Version="11.1.3" />
+    <PackageReference Include="Avalonia.Desktop" Version="11.2.0-rc1" />
     <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
-    <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.3" />
+    <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.0-rc1" />
   </ItemGroup>
 
   <ItemGroup>

+ 3 - 0
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/App.axaml.cs

@@ -6,6 +6,7 @@ using HanumanInstitute.MvvmDialogs.Avalonia;
 using HanumanInstitute.MvvmDialogs.Avalonia.MessageBox;
 using Splat;
 using VeloeAvaloniaKemonoPartyApp.ViewModels;
+using VeloeKemonoPartyApp.Services;
 
 namespace VeloeAvaloniaKemonoPartyApp
 {
@@ -21,6 +22,8 @@ namespace VeloeAvaloniaKemonoPartyApp
                     viewLocator: ApplicationLifetime is IClassicDesktopStyleApplicationLifetime ? new Dialogs.ViewLocatorDesktop() : new Dialogs.ViewLocatorSingle(),
                     dialogFactory: new DialogFactory().AddMessageBox(MessageBoxMode.Popup)),
                 viewModelFactory: x => Locator.Current.GetService(x)));
+            build.RegisterLazySingleton(() => new KemonoHttpClient());
+
 
             SplatRegistrations.Register<CreatorsViewModel>();
             SplatRegistrations.Register<CreatorPostsViewModel>();

+ 4 - 4
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/Models/Creator.cs

@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using Splat;
+using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Text.Json.Serialization;
@@ -29,13 +30,12 @@ namespace VeloeAvaloniaKemonoPartyApp.Models
         public static async Task<IEnumerable<Creator>> SearchAsync(string searchTerm, bool reloadSource)
         {
             if (Source.Count == 0 || reloadSource)
-                Source = await s_httpClient.GetCreatorsList(reloadSource);
+                Source = await Locator.Current.GetService<KemonoHttpClient>().GetCreatorsList(reloadSource);
               
 
             return Source.Where(x=>x.Name.StartsWith(searchTerm,System.StringComparison.OrdinalIgnoreCase)).Take(50);
         }
 
-        private static KemonoHttpClient s_httpClient = new();
         private string CachePath => $"./Cache/{Service} - {Id}";
 
         public async Task<Stream> LoadCoverBitmapAsync()
@@ -48,7 +48,7 @@ namespace VeloeAvaloniaKemonoPartyApp.Models
             {
                 try
                 {
-                    var data = await s_httpClient.httpClient.GetByteArrayAsync(Icon);
+                    var data = await Locator.Current.GetService<KemonoHttpClient>().httpClient.GetByteArrayAsync(Icon);
                     return new MemoryStream(data);
                 }
                 catch (System.Net.Http.HttpRequestException ex)

+ 17 - 34
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/Models/Post.cs

@@ -1,9 +1,9 @@
-using System;
+using Splat;
+using System;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
 using System.Linq;
-using System.Net.Mail;
 using System.Text.Json.Serialization;
 using System.Threading.Tasks;
 using VeloeAvaloniaKemonoPartyApp.Services;
@@ -41,31 +41,9 @@ namespace VeloeAvaloniaKemonoPartyApp.Models
 
         public static async Task<IEnumerable<Post>> LoadPostsAsync(Creator creator, int start, string search = "")
         {
-            return await s_httpClient.GetPostsList(creator.Id, creator.Service, start, search);
+            return await Locator.Current.GetService<KemonoHttpClient>().GetPostsList(creator.Id, creator.Service, start, search);
         }
 
-        private static KemonoHttpClient s_httpClient = new();
-        private string CachePath => $"./Cache/{User} - {Published}";
-
-        public async Task<Stream> LoadCoverBitmapAsync()
-        {
-            if (System.IO.File.Exists(CachePath + ".bmp"))
-            {
-                return System.IO.File.OpenRead(CachePath + ".bmp");
-            }
-            else
-            {
-                try
-                {
-                    var data = await s_httpClient.httpClient.GetByteArrayAsync(Attachments.FirstOrDefault()?.Link);
-                    return new MemoryStream(data);
-                }
-                catch (System.Net.Http.HttpRequestException ex)
-                {
-                    return new MemoryStream();
-                }
-            }
-        }
     }
 
     public class Attachment
@@ -79,17 +57,17 @@ namespace VeloeAvaloniaKemonoPartyApp.Models
 
         public bool IsImage => Path.EndsWith(".jpg") || Path.EndsWith(".png") || Path.EndsWith(".jpeg");
 
-        private static KemonoHttpClient s_httpClient = new();
-
-        private static StorageService s_storage = new();
 
         protected virtual string FileName => Path.Split('/').Last();
 
+        public string User { get; internal set; }
+        public string Service { get; internal set; }
+
         public async Task<Stream?> LoadCoverBitmapAsync()
         {
-            if (System.IO.File.Exists(System.IO.Path.Combine(await RegisteredServices.StorageService.GetCacheFolderAsync(),  FileName + ".bmp")))
+            if (System.IO.File.Exists(await GetPath()))
             {
-                var stream = System.IO.File.OpenRead(System.IO.Path.Combine(await RegisteredServices.StorageService.GetCacheFolderAsync(), FileName + ".bmp"));
+                var stream = System.IO.File.OpenRead(await GetPath());
 
                 byte[] hash = System.Security.Cryptography.SHA256.Create().ComputeHash(stream);
 
@@ -101,12 +79,15 @@ namespace VeloeAvaloniaKemonoPartyApp.Models
                 }
                 stream.Close();
             }
-            
+
             try
             {
-                var data = await s_httpClient.httpClient.GetByteArrayAsync(Link);
+                var data = await Locator.Current.GetService<KemonoHttpClient>().httpClient.GetByteArrayAsync(Link);
 
-                using (var stream = System.IO.File.OpenWrite(System.IO.Path.Combine(await RegisteredServices.StorageService.GetCacheFolderAsync(), FileName + ".bmp")))
+                if (!Directory.Exists(await GetFolderPath()))
+                    Directory.CreateDirectory(await GetFolderPath());
+
+                using (var stream = System.IO.File.OpenWrite(await GetPath()))
                 {
                     await stream.WriteAsync(data);
                 }
@@ -118,9 +99,11 @@ namespace VeloeAvaloniaKemonoPartyApp.Models
                 Debug.WriteLine(ex);
                 return null;
             }
-            
+
         }
 
+        private async Task<string> GetPath() => System.IO.Path.Combine(await Locator.Current.GetService<IStorageService>().GetCacheFolderAsync(), $"{Service}{User}/{FileName}.bmp");
+        private async Task<string> GetFolderPath() => System.IO.Path.Combine(await Locator.Current.GetService<IStorageService>().GetCacheFolderAsync(), $"{Service}{User}");
     }
 
     public class ExternalAttachment : Attachment

+ 5 - 6
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/Services/Extensions.cs

@@ -1,12 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using Avalonia.Data.Converters;
+using System;
 
 namespace VeloeAvaloniaKemonoPartyApp.Services
 {
-    internal static class Extensions
+    public static class Extensions
     {
         public static string CreateMD5(string input)
         {
@@ -27,5 +24,7 @@ namespace VeloeAvaloniaKemonoPartyApp.Services
                 // return sb.ToString();
             }
         }
+
+        public static FuncValueConverter<double, DateTime> DoubleToTimeConverter { get; } = new FuncValueConverter<double,DateTime>( (x) => DateTimeOffset.FromUnixTimeSeconds((int)x).LocalDateTime); 
     }
 }

+ 71 - 21
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/Services/HttpClient.cs

@@ -5,6 +5,8 @@ using System.Threading.Tasks;
 using System.Text.Json;
 using VeloeAvaloniaKemonoPartyApp.Models;
 using VeloeAvaloniaKemonoPartyApp.Services;
+using System.Linq;
+using Splat;
 
 namespace VeloeKemonoPartyApp.Services
 {
@@ -12,7 +14,10 @@ namespace VeloeKemonoPartyApp.Services
     {
         public HttpClient httpClient;
 
-        public List<Post> posts;
+        private string _postsIds = string.Empty;
+        private Queue<Post> _posts;
+        private HashSet<string> _ids;
+        private const int _postsPerLoad = 10;
         public KemonoHttpClient() 
         {
             httpClient = new HttpClient()
@@ -20,23 +25,25 @@ namespace VeloeKemonoPartyApp.Services
                 BaseAddress = new Uri("https://kemono.su"),
                 
             };
-            posts = new List<Post>();
+            _posts = new Queue<Post>(50);
+            _ids = new HashSet<string>();
         }
 
         public async Task<List<Creator>> GetCreatorsList(bool reloadSource)
         {
-            if (System.IO.File.Exists(await RegisteredServices.StorageService.GetCacheFolderAsync() + "/creators.json") && new System.IO.FileInfo(await RegisteredServices.StorageService.GetCacheFolderAsync() + "/creators.json").CreationTime > DateTime.Now.AddDays(-7) && !reloadSource)
+            if (System.IO.File.Exists(await Locator.Current.GetService<IStorageService>().GetCacheFolderAsync() + "/creators.json") && new System.IO.FileInfo(await Locator.Current.GetService<IStorageService>().GetCacheFolderAsync() + "/creators.json").CreationTime > DateTime.Now.AddDays(-7) && !reloadSource)
             {
                 try
                 {
-                    using (var stream = System.IO.File.OpenRead(await RegisteredServices.StorageService.GetCacheFolderAsync() + "/creators.json"))
+                    using (var stream = System.IO.File.OpenRead(await Locator.Current.GetService<IStorageService>().GetCacheFolderAsync() + "/creators.json"))
                     {
                         return await JsonSerializer.DeserializeAsync<List<Creator>>(stream);
                     }
                 }
                 catch 
                 {
-                    System.IO.File.Delete(await RegisteredServices.StorageService.GetCacheFolderAsync() + "/creators.json");
+                    System.IO.File.Delete(await Locator.Current.GetService<IStorageService>().GetCacheFolderAsync() + "/creators.json");
+                    throw;
                 }
             }
 
@@ -45,12 +52,13 @@ namespace VeloeKemonoPartyApp.Services
                 using (HttpResponseMessage response = await httpClient.GetAsync("https://kemono.su/api/v1/creators.txt"))
                 using (HttpContent content = response.Content)
                 {
-                    using (var stream = System.IO.File.OpenWrite(await RegisteredServices.StorageService.GetCacheFolderAsync() + "/creators.json"))
+                    var result = await JsonSerializer.DeserializeAsync<List<Creator>>(await response.Content.ReadAsStreamAsync());
+
+                    using (var stream = System.IO.File.Create(await Locator.Current.GetService<IStorageService>().GetCacheFolderAsync() + "/creators.json"))
                     {
                         stream.Write(await response.Content.ReadAsByteArrayAsync());
                     }
-
-                    return await JsonSerializer.DeserializeAsync<List<Creator>>(await response.Content.ReadAsStreamAsync());
+                    return result;
                 }
             }
             catch (Exception)
@@ -59,25 +67,67 @@ namespace VeloeKemonoPartyApp.Services
             }
         }
 
-        public async Task<List<Post>> GetPostsList(string id, string service, int start, string search = "")
+        public async Task<IEnumerable<Post>> GetPostsList(string id, string service, int start, string search = "")
         {
-            try
+            if (_posts.Count > 0 && _postsIds.Equals(service+id, StringComparison.Ordinal))
             {
-                using (HttpResponseMessage response = await httpClient.GetAsync($"https://kemono.su/api/v1/{service}/user/{id}?o={start}" + (!string.IsNullOrEmpty(search) ? $"&q={search}" : string.Empty)))
-                using (HttpContent content = response.Content)
+                var result = new List<Post>();
+                for (int i = 0; i < _postsPerLoad && _posts.Count > 0; i++)
                 {
-                    if (response.IsSuccessStatusCode)
-                    {
-                        string jsonString = await response.Content.ReadAsStringAsync();
-                        // ... Read the string.
-                        return JsonSerializer.Deserialize<List<Post>>(jsonString);
-                    }
-                    return new List<Post>();
+                    var post = _posts.Dequeue();
+                    _ids.Remove(post.Id);
+                    result.Add(post);
                 }
+                return result;
             }
-            catch (Exception)
+            else
             {
-                throw;
+                try
+                {
+                    using (HttpResponseMessage response = await httpClient.GetAsync($"https://kemono.su/api/v1/{service}/user/{id}?o={start}" + (!string.IsNullOrEmpty(search) ? $"&q={search}" : string.Empty)))
+                    using (HttpContent content = response.Content)
+                    {
+                        if (response.IsSuccessStatusCode)
+                        {
+                            string jsonString = await response.Content.ReadAsStringAsync();
+                            // ... Read the string.
+                            var resultList = JsonSerializer.Deserialize<List<Post>>(jsonString);
+
+                            var i = 0;
+
+                            if (!_postsIds.Equals(service + id, StringComparison.Ordinal))
+                            {
+                                _posts.Clear();
+                                _ids.Clear();
+                                _postsIds = service + id;
+                            }
+
+                            foreach (var post in resultList)
+                            {
+                                foreach (var item in post.Attachments)
+                                {
+                                    item.Service = post.Service;
+                                    item.User = post.User;
+                                }
+
+                                if (i >= _postsPerLoad && !_ids.Contains(post.Id))
+                                {
+                                    _ids.Add(post.Id);
+                                    _posts.Enqueue(post);
+                                }
+                                i++;
+                            }
+
+                            return resultList.Take(_postsPerLoad);
+                        }
+                         return Enumerable.Empty<Post>();
+                    }
+                
+                }
+                catch (Exception)
+                {
+                    throw;
+                }
             }
         }
 

+ 0 - 9
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/Services/RegisteredServices.cs

@@ -1,9 +0,0 @@
-namespace VeloeAvaloniaKemonoPartyApp.Services
-{
-    public static class RegisteredServices
-    {
-        public static IOpenUrlService? OpenUrlService { get; set; }
-
-        public static IStorageService StorageService { get; set; } = new StorageService();
-    }
-}

+ 7 - 6
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp.csproj

@@ -4,8 +4,8 @@
     <Nullable>enable</Nullable>
     <LangVersion>latest</LangVersion>
     <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
-    <AssemblyVersion>1.0.0.350</AssemblyVersion>
-    <FileVersion>1.0.0.350</FileVersion>
+    <AssemblyVersion>1.0.0.396</AssemblyVersion>
+    <FileVersion>1.0.0.396</FileVersion>
   </PropertyGroup>
 
   
@@ -15,12 +15,13 @@
 
   <ItemGroup>
     <PackageReference Include="AngleSharp" Version="1.1.2" />
-    <PackageReference Include="Avalonia" Version="11.1.3" />
+    <PackageReference Include="Avalonia" Version="11.2.0-rc1" />
+    <PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.4" />
     <PackageReference Include="Avalonia.Controls.PanAndZoom" Version="11.1.0.1" />
     <PackageReference Include="Avalonia.HtmlRenderer" Version="11.0.0" />
-    <PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.3" />
-    <PackageReference Include="Avalonia.Fonts.Inter" Version="11.1.3" />
-    <PackageReference Include="Avalonia.ReactiveUI" Version="11.1.3" />
+    <PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.0-rc1" />
+    <PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.0-rc1" />
+    <PackageReference Include="Avalonia.ReactiveUI" Version="11.2.0-rc1" />
     <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
     <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.3" />
     <PackageReference Include="HanumanInstitute.MvvmDialogs.Avalonia" Version="2.1.0" />

+ 3 - 1
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/ViewModels/AttachmentViewModel.cs

@@ -3,6 +3,8 @@ using VeloeAvaloniaKemonoPartyApp.Models;
 using System.Diagnostics;
 using System.Runtime.InteropServices;
 using System;
+using Splat;
+using VeloeAvaloniaKemonoPartyApp.Services;
 
 namespace VeloeAvaloniaKemonoPartyApp.ViewModels
 {
@@ -39,7 +41,7 @@ namespace VeloeAvaloniaKemonoPartyApp.ViewModels
                 return;
             }
 
-            Services.RegisteredServices.OpenUrlService?.OpenUrl(_attachment.Link);
+            Locator.Current.GetService<IOpenUrlService>()?.OpenUrl(_attachment.Link);
         }
     }
 }

+ 4 - 3
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/ViewModels/CreatorPostsViewModel.cs

@@ -1,4 +1,5 @@
 using Avalonia.Notification;
+using DynamicData.Binding;
 using HanumanInstitute.MvvmDialogs;
 using ReactiveUI;
 using System;
@@ -25,9 +26,9 @@ namespace VeloeAvaloniaKemonoPartyApp.ViewModels
         {
             _dialogService = dialogService;
             Close = ReactiveCommand.Create(CloseImpl);
-
+            
             this.WhenAnyValue(x => x.SearchText)
-                .Throttle(TimeSpan.FromMilliseconds(400))
+                .Throttle(TimeSpan.FromMilliseconds(800))
                 .ObserveOn(RxApp.MainThreadScheduler)
                 .Subscribe(InitPosts!);
         }
@@ -57,7 +58,7 @@ namespace VeloeAvaloniaKemonoPartyApp.ViewModels
             set 
             {
                 _creator = value;
-                InitPosts();
+                //InitPosts();
             } 
         }
 

+ 0 - 4
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/ViewModels/CreatorViewModel.cs

@@ -15,10 +15,6 @@ namespace VeloeAvaloniaKemonoPartyApp.ViewModels
             _creator = creator;
         }
 
-        public string Artist => _creator.Name;
-
-        public string Title => _creator.Service;
-
         public Creator Creator => _creator;
 
         private Bitmap? _cover;

+ 5 - 3
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/ViewModels/CreatorsViewModel.cs

@@ -28,7 +28,7 @@ namespace VeloeAvaloniaKemonoPartyApp.ViewModels
                 .ObserveOn(RxApp.MainThreadScheduler)
                 .Subscribe(DoSearch!);
 
-            this.WhenPropertyChanged(x => x.SelectedAlbum).Subscribe(async x => 
+            this.WhenPropertyChanged(x => x.SelectedCreator).Subscribe(async x => 
             {
                 if (x.Value is null) return;
                 try
@@ -52,9 +52,11 @@ namespace VeloeAvaloniaKemonoPartyApp.ViewModels
                 }
                 finally 
                 {
-                    Dispatcher.UIThread.Post(() => { SelectedAlbum = null; });
+                    Dispatcher.UIThread.Post(() => { SelectedCreator = null; });
                 }
             });
+
+            SearchText = "Rukis";
         }
 
         private string _searchText = string.Empty;
@@ -78,7 +80,7 @@ namespace VeloeAvaloniaKemonoPartyApp.ViewModels
 
         public ObservableCollection<CreatorViewModel> SearchResults { get; } = new();
 
-        public CreatorViewModel? SelectedAlbum
+        public CreatorViewModel? SelectedCreator
         {
             get => _selectedAlbum;
             set => this.RaiseAndSetIfChanged(ref _selectedAlbum, value);

+ 21 - 21
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/ViewModels/PostImageViewModel.cs

@@ -22,7 +22,7 @@ namespace VeloeAvaloniaKemonoPartyApp.ViewModels
             _dialogService = dialogService;
             _parent = parent;
         }
-
+        
         private Bitmap? _cover;
 
         public Bitmap? Cover
@@ -33,33 +33,33 @@ namespace VeloeAvaloniaKemonoPartyApp.ViewModels
 
         public async Task LoadAvatar()
         {
-            await using (var imageStream = await _attachment.LoadCoverBitmapAsync())
+            Cover = await Task.Run(async() =>
             {
-                if (imageStream is null) return;
+                await using (var imageStream = await _attachment.LoadCoverBitmapAsync())
+                {
+                if (imageStream is null) return null;
 
-                Cover = await Task.Run(() =>
+                Bitmap? bitmap;
+                try
                 {
-                    Bitmap? bitmap;
-                    try
+                    bitmap = new Bitmap(imageStream);
+                    if (App.Current.ApplicationLifetime is ISingleViewApplicationLifetime lifetime)
                     {
-                        bitmap = new Bitmap(imageStream);
-                        if (App.Current.ApplicationLifetime is ISingleViewApplicationLifetime lifetime)
-                        {
-                            var topLevel = TopLevel.GetTopLevel(lifetime.MainView);
+                        var topLevel = TopLevel.GetTopLevel(lifetime.MainView);
 
-                            if (bitmap.Size.Width > Math.Ceiling(topLevel.ClientSize.Width * topLevel.RenderScaling))
-                                bitmap = bitmap.CreateScaledBitmap(new Avalonia.PixelSize((int)Math.Ceiling(topLevel.ClientSize.Width*topLevel.RenderScaling), (int)Math.Ceiling(topLevel.ClientSize.Width*topLevel.RenderScaling / bitmap.Size.AspectRatio)));
-                        }
-                    }
-                    catch (Exception ex)
-                    {
-                        bitmap = null;
+                        if (bitmap.Size.Width > Math.Ceiling(topLevel.ClientSize.Width * topLevel.RenderScaling))
+                            bitmap = bitmap.CreateScaledBitmap(new Avalonia.PixelSize((int)Math.Ceiling(topLevel.ClientSize.Width*topLevel.RenderScaling), (int)Math.Ceiling(topLevel.ClientSize.Width*topLevel.RenderScaling / bitmap.Size.AspectRatio)));
                     }
-                    return bitmap;
-                });
-            }
+                }
+                catch (Exception ex)
+                {
+                    bitmap = null;
+                }
+                return bitmap;
+                }
+            });
         }
-
+        
         public async Task OnImageClick(object sender, RoutedEventArgs e)
         {
             var vm = new ImageZoomViewModel(_attachment);

+ 2 - 0
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/ViewModels/PostViewModel.cs

@@ -69,6 +69,8 @@ namespace VeloeAvaloniaKemonoPartyApp.ViewModels
                 var attachment = new ExternalAttachment() { Url = element.Attributes.First(x => x.Name == "src").Value };
                 attachment.Path = Path.GetFileName(attachment.Url);
                 attachment.Name = Path.GetFileNameWithoutExtension(attachment.Path);
+                attachment.User = _post.User;
+                attachment.Service = _post.Service;
 
                 vm = new PostImageViewModel(_dialogService, _parent, attachment);
                 added = true;

+ 9 - 8
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/Views/CreatorPostsView.axaml

@@ -4,6 +4,7 @@
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 			 xmlns:m="using:VeloeAvaloniaKemonoPartyApp.Models"
 			 xmlns:vm="clr-namespace:VeloeAvaloniaKemonoPartyApp.ViewModels"
+			 xmlns:views="using:VeloeAvaloniaKemonoPartyApp.Views"
 			 xmlns:notif="using:Avalonia.Notification.Controls"
              mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="854"
              x:Class="VeloeAvaloniaKemonoPartyApp.Views.CreatorPostsView"
@@ -14,7 +15,7 @@
          to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
 		<vm:CreatorPostsViewModel />
 	</Design.DataContext>
-	<Panel>
+	<Panel HorizontalAlignment="Stretch">
 		<notif:NotificationMessageContainer VerticalAlignment="Top" HorizontalAlignment="Stretch" Manager="{Binding Manager}" ZIndex="999"/>
 		<ScrollViewer VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
 			<StackPanel>
@@ -22,15 +23,15 @@
 					<TextBox Watermark="Search in posts..." Text="{Binding SearchText}"/>
 					<ProgressBar IsIndeterminate="True" IsVisible="{Binding IsBusy}"/>
 				</StackPanel>
-				<ItemsControl 
+				<ItemsRepeater 
 					ItemsSource="{Binding Posts}"
 					Background="Transparent">
-					<ItemsControl.ItemsPanel>
-						<ItemsPanelTemplate>
-							<VirtualizingStackPanel />					
-						</ItemsPanelTemplate>
-					</ItemsControl.ItemsPanel>
-				</ItemsControl>
+					<ItemsRepeater.ItemTemplate>
+						<DataTemplate>
+							<views:PostView/>
+						</DataTemplate>
+					</ItemsRepeater.ItemTemplate>
+				</ItemsRepeater>
 				<Button 
 					Command="{Binding LoadMorePosts}"
 					HorizontalContentAlignment="Center"

File diff suppressed because it is too large
+ 10 - 5
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/Views/CreatorView.axaml


+ 22 - 1
VeloeAvaloniaKemonoPartyApp/VeloeAvaloniaKemonoPartyApp/Views/CreatorsView.axaml

@@ -20,7 +20,7 @@
 				<TextBox Watermark="Search for creators..." Text="{Binding SearchText}"/>
 				<ProgressBar IsIndeterminate="True" IsVisible="{Binding IsBusy}"/>
 			</StackPanel>
-			<ListBox ItemsSource="{Binding SearchResults}" SelectedItem="{Binding SelectedAlbum}" SelectionMode="Single"
+			<ListBox ItemsSource="{Binding SearchResults}" SelectedItem="{Binding SelectedCreator}" SelectionMode="Single"
 						Background="Transparent" Margin="0 10" Gestures.PullGestureEnded="ReloadCreatorsSource">
 				<ListBox.ItemsPanel>
 					<ItemsPanelTemplate>
@@ -32,5 +32,26 @@
 				</ListBox.GestureRecognizers>
 			</ListBox>		
 		</DockPanel>
+		<Button 
+			VerticalAlignment="Bottom" 
+			HorizontalAlignment="Right" 
+			Margin="0 0 35 35" 
+			HorizontalContentAlignment="Center" 
+			VerticalContentAlignment="Center" 
+			Content="⚙️">
+			<Button.Styles>
+				<Style Selector="Button">
+					<Setter Property="Margin" Value="9,3,9,3"/>
+					<Setter Property="Width" Value="40"/>
+					<Setter Property="Height" Value="40"/>
+					<Setter Property="BorderThickness" Value="0"/>
+					<Setter Property="Padding" Value="6"/>
+					<Setter Property="CornerRadius" Value="20"/>
+				</Style>
+				<Style Selector="Button:pointerover /template/ ContentPresenter">
+					<Setter Property="Background" Value="#80e0e0e0"/>
+				</Style>
+			</Button.Styles>
+		</Button>
 	</Panel>
 </UserControl>

Some files were not shown because too many files changed in this diff