218 lines
6.6 KiB
C#
218 lines
6.6 KiB
C#
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using MangaReader.Core.Metadata;
|
|
using MangaReader.Core.Pipeline;
|
|
using MangaReader.Core.Search;
|
|
using Microsoft.UI.Dispatching;
|
|
using Microsoft.UI.Xaml.Media.Imaging;
|
|
using SixLabors.ImageSharp;
|
|
using SixLabors.ImageSharp.PixelFormats;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.IO;
|
|
using System.Net.Http;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Input;
|
|
|
|
namespace MangaReader.WinUI.ViewModels;
|
|
|
|
public partial class SearchViewModel(
|
|
IMangaSearchCoordinator searchCoordinator,
|
|
IMangaMetadataCoordinator metadataCoordinator,
|
|
IMangaPipeline pipeline) : ViewModelBase
|
|
{
|
|
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
|
|
|
private CancellationTokenSource? _cancellationTokenSource;
|
|
|
|
private string? _keyword;
|
|
|
|
public string? Keyword
|
|
{
|
|
get
|
|
{
|
|
return _keyword;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _keyword, value);
|
|
}
|
|
}
|
|
|
|
private ObservableCollection<MangaSearchResult> _searchResults = [];
|
|
|
|
public ObservableCollection<MangaSearchResult> SearchResults
|
|
{
|
|
get
|
|
{
|
|
return _searchResults;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _searchResults, value);
|
|
}
|
|
}
|
|
|
|
private ObservableCollection<ObservableMangaSearchResult> _searchResults2 = [];
|
|
|
|
public ObservableCollection<ObservableMangaSearchResult> SearchResults2
|
|
{
|
|
get
|
|
{
|
|
return _searchResults2;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _searchResults2, value);
|
|
}
|
|
}
|
|
|
|
public ICommand SearchCommand => new AsyncRelayCommand(SearchAsync);
|
|
//public ICommand ImportCommand => new AsyncRelayCommand(ImportAsync);
|
|
|
|
public async Task SearchAsync()
|
|
{
|
|
if (string.IsNullOrWhiteSpace(Keyword))
|
|
return;
|
|
|
|
_cancellationTokenSource?.Cancel();
|
|
_cancellationTokenSource = new();
|
|
|
|
Dictionary<string, MangaSearchResult[]> result = await searchCoordinator.SearchAsync(Keyword, _cancellationTokenSource.Token);
|
|
|
|
List<MangaSearchResult> searchResults = [];
|
|
List<ObservableMangaSearchResult> mangaSearchResults = [];
|
|
|
|
foreach (var item in result)
|
|
{
|
|
foreach (MangaSearchResult searchResult in item.Value)
|
|
{
|
|
//searchResults.Add(searchResult);
|
|
|
|
ObservableMangaSearchResult mangaSearchResult = new()
|
|
{
|
|
Source = searchResult.Source,
|
|
Url = searchResult.Url,
|
|
Title = searchResult.Title,
|
|
Thumbnail = searchResult.Thumbnail,
|
|
Description = searchResult.Description,
|
|
Genres = searchResult.Genres
|
|
};
|
|
|
|
Task.Run(() => mangaSearchResult.LoadThumbnailAsync(_dispatcherQueue)); // or defer this if you want lazy loading
|
|
|
|
searchResults.Add(searchResult);
|
|
mangaSearchResults.Add(mangaSearchResult);
|
|
}
|
|
}
|
|
|
|
SearchResults = new(searchResults);
|
|
SearchResults2 = new(mangaSearchResults);
|
|
}
|
|
|
|
public static async Task<BitmapImage?> LoadWebpAsBitmapImageAsync(string? url)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(url))
|
|
return null;
|
|
|
|
using var httpClient = new HttpClient();
|
|
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0");
|
|
using var webpStream = await httpClient.GetStreamAsync(url);
|
|
|
|
using var image = await Image.LoadAsync(webpStream); // from SixLabors.ImageSharp
|
|
using var ms = new MemoryStream();
|
|
//await image.SaveAsPngAsync(ms); // Convert to PNG in memory
|
|
await image.SaveAsJpegAsync(ms);
|
|
ms.Position = 0;
|
|
|
|
var bitmap = new BitmapImage();
|
|
await bitmap.SetSourceAsync(ms.AsRandomAccessStream());
|
|
|
|
return bitmap;
|
|
}
|
|
|
|
public async Task ImportAsync(ObservableMangaSearchResult searchResult, CancellationToken cancellationToken)
|
|
{
|
|
IMangaMetadataProvider metadataProvider = metadataCoordinator.GetProvider(searchResult.Source);
|
|
SourceManga? sourceManga = await metadataProvider.GetMangaAsync(searchResult.Url, cancellationToken);
|
|
|
|
if (sourceManga == null)
|
|
return;
|
|
|
|
MangaMetadataPipelineRequest request = new()
|
|
{
|
|
SourceName = searchResult.Source,
|
|
SourceUrl = searchResult.Url,
|
|
SourceManga = sourceManga,
|
|
};
|
|
|
|
await pipeline.RunMetadataAsync(request, cancellationToken);
|
|
}
|
|
}
|
|
|
|
public partial class ObservableMangaSearchResult : ObservableObject
|
|
{
|
|
public required string Source { get; init; }
|
|
public required string Url { get; init; }
|
|
public string? Title { get; init; }
|
|
public string? Description { get; init; }
|
|
public string? Thumbnail { get; init; }
|
|
public string[] Genres { get; init; } = [];
|
|
|
|
private BitmapImage? _thumbnailBitmap;
|
|
|
|
public BitmapImage? ThumbnailBitmap
|
|
{
|
|
get
|
|
{
|
|
return _thumbnailBitmap;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _thumbnailBitmap, value);
|
|
}
|
|
}
|
|
|
|
public async Task LoadThumbnailAsync(DispatcherQueue dispatchQueue)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(Thumbnail))
|
|
return;
|
|
|
|
try
|
|
{
|
|
using var httpClient = new HttpClient();
|
|
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0");
|
|
|
|
using var stream = await httpClient.GetStreamAsync(Thumbnail);
|
|
using var image = await Image.LoadAsync(stream); // Important: use a pixel type
|
|
|
|
using var ms = new MemoryStream();
|
|
await image.SaveAsJpegAsync(ms); // or SaveAsPngAsync
|
|
ms.Position = 0;
|
|
|
|
TaskCompletionSource taskCompletionSource = new();
|
|
|
|
dispatchQueue.TryEnqueue(async () => {
|
|
var bitmap = new BitmapImage();
|
|
await bitmap.SetSourceAsync(ms.AsRandomAccessStream());
|
|
|
|
ThumbnailBitmap = bitmap;
|
|
|
|
taskCompletionSource.SetResult();
|
|
});
|
|
|
|
taskCompletionSource.Task.GetAwaiter().GetResult();
|
|
|
|
//var bitmap = new BitmapImage();
|
|
//await bitmap.SetSourceAsync(ms.AsRandomAccessStream());
|
|
|
|
//ThumbnailBitmap = bitmap;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[Thumbnail Load Failed] {ex.Message}");
|
|
}
|
|
}
|
|
} |