Added common language enum. Fixed "romaji" spelling. More UI updates.

This commit is contained in:
2025-06-08 11:13:52 -04:00
parent 70513559cb
commit b5d22c3c7e
20 changed files with 224 additions and 55 deletions

View File

@@ -10,7 +10,7 @@ public partial class UppercaseConverter : IValueConverter
if (value == null)
return null;
return value.ToString().ToUpperInvariant();
return value?.ToString()?.ToUpperInvariant();
}
public object ConvertBack(object value, Type targetType, object parameter, string language)

View File

@@ -1,5 +1,3 @@
using MangaReader.Core.Search;
using MangaReader.Core.Sources.MangaDex.Api;
using Microsoft.UI.Xaml;
namespace MangaReader.WinUI;
@@ -8,7 +6,7 @@ public sealed partial class MainWindow : Window
{
private const string ApplicationTitle = "Manga Reader";
public MainWindow(IMangaSearchCoordinator mangaSearchCoordinator, IMangaDexClient mangaDexClient)
public MainWindow()
{
InitializeComponent();

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows10.0.19041.0</TargetFramework>
@@ -51,6 +51,8 @@
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.9" />
<PackageReference Include="SkiaSharp" Version="3.119.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MangaReader.Core\MangaReader.Core.csproj" />

View File

@@ -1,7 +1,15 @@
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
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;
@@ -10,6 +18,8 @@ namespace MangaReader.WinUI.ViewModels;
public partial class SearchViewModel(IMangaSearchCoordinator searchCoordinator) : ViewModelBase
{
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
private CancellationTokenSource? _cancellationTokenSource;
private string? _keyword;
@@ -40,6 +50,20 @@ public partial class SearchViewModel(IMangaSearchCoordinator searchCoordinator)
}
}
private ObservableCollection<ObservableMangaSearchResult> _searchResults2 = [];
public ObservableCollection<ObservableMangaSearchResult> SearchResults2
{
get
{
return _searchResults2;
}
set
{
SetProperty(ref _searchResults2, value);
}
}
public ICommand SearchCommand => new AsyncRelayCommand(SearchAsync);
public async Task SearchAsync()
@@ -53,15 +77,114 @@ public partial class SearchViewModel(IMangaSearchCoordinator searchCoordinator)
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()
{
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 partial class ObservableMangaSearchResult : ObservableObject
{
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}");
}
}
}

View File

@@ -16,16 +16,16 @@
mc:Ignorable="d">
<UserControl.Resources>
<Media:AttachedCardShadow x:Key="CommonShadow" Offset="5" BlurRadius="10" Opacity=".4" />
<DataTemplate x:Key="MangaSearchResultTemplate" x:DataType="search:MangaSearchResult">
<Grid Padding="20" ColumnSpacing="20" MaxHeight="400" VerticalAlignment="Stretch" Background="{StaticResource CardBackgroundFillColorDefault}" CornerRadius="8">
<DataTemplate x:Key="MangaSearchResultTemplate" x:DataType="vm:ObservableMangaSearchResult">
<Grid Padding="20" ColumnSpacing="20" Height="400" VerticalAlignment="Stretch" Background="{StaticResource CardBackgroundFillColorDefault}" CornerRadius="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" MaxWidth="300" UI:Effects.Shadow="{StaticResource CommonShadow}" VerticalAlignment="Top" HorizontalAlignment="Left">
<Grid VerticalAlignment="Top" HorizontalAlignment="Left" CornerRadius="8">
<Image Source="{x:Bind Thumbnail, Mode=OneWay}" MaxWidth="300"></Image>
<Image Source="{x:Bind ThumbnailBitmap, Mode=OneWay}" MaxWidth="300"></Image>
<Canvas Background="#19000000"></Canvas>
</Grid>
</Border>
@@ -35,8 +35,8 @@
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{x:Bind Title}" FontSize="24" FontFamily="{StaticResource PoppinsSemiBold}" TextWrapping="Wrap"></TextBlock>
<ItemsControl Grid.Row="1" ItemsSource="{x:Bind Genres, Mode=OneWay}" ItemTemplate="{StaticResource GenreTemplate}">
<TextBlock Grid.Row="0" Text="{x:Bind Title, Mode=OneTime}" FontSize="24" FontFamily="{StaticResource PoppinsSemiBold}" TextWrapping="Wrap"></TextBlock>
<ItemsControl Grid.Row="1" ItemsSource="{x:Bind Genres, Mode=OneTime}" ItemTemplate="{StaticResource GenreTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Controls:WrapPanel Orientation="Horizontal" HorizontalSpacing="10" VerticalSpacing="10" />
@@ -53,7 +53,7 @@
<DataTemplate x:Key="GenreTemplate" x:DataType="x:String">
<Border>
<TextBlock FontSize="12" Foreground="{StaticResource TextFillColorTertiary}" Text="{x:Bind Mode=OneWay, Converter={StaticResource UppercaseConverter}}" FontFamily="{StaticResource PoppinsSemiBold}"></TextBlock>
<TextBlock FontSize="12" Foreground="{StaticResource TextFillColorTertiary}" Text="{x:Bind Mode=OneTime, Converter={StaticResource UppercaseConverter}}" FontFamily="{StaticResource PoppinsSemiBold}"></TextBlock>
</Border>
</DataTemplate>
@@ -74,7 +74,7 @@
<ScrollViewer.RenderTransform>
<ScaleTransform ScaleX="1" ScaleY="1" />
</ScrollViewer.RenderTransform>
<ItemsRepeater ItemsSource="{Binding SearchResults, Mode=OneWay}" ItemTemplate="{StaticResource MangaSearchResultTemplate}">
<ItemsRepeater ItemsSource="{Binding SearchResults2, Mode=OneWay}" ItemTemplate="{StaticResource MangaSearchResultTemplate}">
<ItemsRepeater.Layout>
<UniformGridLayout MinRowSpacing="50" MinColumnSpacing="50" ItemsStretch="Fill" MinItemWidth="800"></UniformGridLayout>
</ItemsRepeater.Layout>