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

@@ -2,7 +2,8 @@
public enum Language public enum Language
{ {
Unknown,
Japanese, Japanese,
Romanji, Romaji,
English English
} }

View File

@@ -31,7 +31,7 @@ public static class ServiceCollectionExtensions
services.AddScoped<IMangaSearchProvider, MangaDexSearchProvider>(); services.AddScoped<IMangaSearchProvider, MangaDexSearchProvider>();
services.AddScoped<IMangaSearchCoordinator, MangaSearchCoordinator>(); services.AddScoped<IMangaSearchCoordinator, MangaSearchCoordinator>();
//services.AddScoped<IMangaMetadataProvider, NatoMangaWebCrawler>(); ///services.AddScoped<IMangaMetadataProvider, NatoMangaWebCrawler>();
services.AddScoped<IMangaMetadataProvider, MangaDexMetadataProvider>(); services.AddScoped<IMangaMetadataProvider, MangaDexMetadataProvider>();
return services; return services;

View File

@@ -1,16 +1,25 @@
namespace MangaReader.Core.HttpService; namespace MangaReader.Core.HttpService;
public class HttpService : IHttpService public class HttpService(HttpClient httpClient) : IHttpService
{ {
private readonly HttpClient _httpClient; public Task<string> GetStringAsync(string url, CancellationToken cancellationToken)
=> GetStringAsync(url, new Dictionary<string, string>(), cancellationToken);
public HttpService(HttpClient httpClient) public async Task<string> GetStringAsync(string url, IDictionary<string, string> headers, CancellationToken cancellationToken)
{ {
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0"); using HttpRequestMessage request = new(HttpMethod.Get, url);
_httpClient = httpClient; foreach (KeyValuePair<string, string> header in headers)
{
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
} }
public Task<string> GetStringAsync(string url, CancellationToken cancellationToken) //httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("MangaReader/1.0");
=> _httpClient.GetStringAsync(url, cancellationToken); httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0");
using HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync(cancellationToken);
}
} }

View File

@@ -3,4 +3,5 @@
public interface IHttpService public interface IHttpService
{ {
Task<string> GetStringAsync(string url, CancellationToken cancellationToken); Task<string> GetStringAsync(string url, CancellationToken cancellationToken);
Task<string> GetStringAsync(string url, IDictionary<string, string> headers, CancellationToken cancellationToken);
} }

View File

@@ -1,9 +1,9 @@
namespace MangaReader.Core.Metadata; namespace MangaReader.Core.Metadata;
public enum SourceMangaLanguage //public enum SourceMangaLanguage
{ //{
Unknown, // Unknown,
Japanese, // Japanese,
Romanji, // Romanji,
English // English
} //}

View File

@@ -1,7 +1,9 @@
namespace MangaReader.Core.Metadata; using MangaReader.Core.Common;
namespace MangaReader.Core.Metadata;
public class SourceMangaTitle public class SourceMangaTitle
{ {
public required string Title { get; set; } public required string Title { get; set; }
public SourceMangaLanguage Language { get; set; } public Language Language { get; set; }
} }

View File

@@ -10,11 +10,14 @@ public partial class MangaPipeline(MangaContext context) : IMangaPipeline
public async Task RunAsync(MangaPipelineRequest request) public async Task RunAsync(MangaPipelineRequest request)
{ {
string sourceName = request.SourceName; string sourceName = request.SourceName;
string sourceUrl = request.SourceUrl;
SourceManga sourceManga = request.SourceManga; SourceManga sourceManga = request.SourceManga;
Source source = await GetOrAddSourceAsync(sourceName); Source source = await GetOrAddSourceAsync(sourceName);
Manga manga = await GetOrAddMangaAsync(sourceManga); Manga manga = await GetOrAddMangaAsync(sourceManga);
await AddMangaSourceAsync(sourceUrl, manga, source);
foreach (SourceMangaTitle alternateTitle in sourceManga.AlternateTitles) foreach (SourceMangaTitle alternateTitle in sourceManga.AlternateTitles)
{ {
await AddTitleAsync(manga, alternateTitle); await AddTitleAsync(manga, alternateTitle);
@@ -83,6 +86,24 @@ public partial class MangaPipeline(MangaContext context) : IMangaPipeline
[GeneratedRegex(@"\s+")] [GeneratedRegex(@"\s+")]
private static partial Regex RemoveSpacesWithDashRegex(); private static partial Regex RemoveSpacesWithDashRegex();
private async Task AddMangaSourceAsync(string sourceUrl, Manga manga, Source source)
{
MangaSource? mangaSource = await context.MangaSources.FirstOrDefaultAsync(ms =>
ms.Manga == manga && ms.Source == source && ms.Url == sourceUrl);
if (mangaSource != null)
return;
mangaSource = new()
{
Manga = manga,
Source = source,
Url = sourceUrl
};
context.MangaSources.Add(mangaSource);
}
private async Task AddTitleAsync(Manga manga, SourceMangaTitle sourceMangaTitle) private async Task AddTitleAsync(Manga manga, SourceMangaTitle sourceMangaTitle)
{ {
MangaTitle? mangaTitle = await context.MangaTitles.FirstOrDefaultAsync(mt => MangaTitle? mangaTitle = await context.MangaTitles.FirstOrDefaultAsync(mt =>

View File

@@ -5,5 +5,6 @@ namespace MangaReader.Core.Pipeline;
public class MangaPipelineRequest public class MangaPipelineRequest
{ {
public required string SourceName { get; init; } public required string SourceName { get; init; }
public required string SourceUrl { get; init; }
public required SourceManga SourceManga { get; init; } public required SourceManga SourceManga { get; init; }
} }

View File

@@ -1,4 +1,5 @@
using MangaReader.Core.Metadata; using MangaReader.Core.Common;
using MangaReader.Core.Metadata;
using MangaReader.Core.Sources.MangaDex.Api; using MangaReader.Core.Sources.MangaDex.Api;
namespace MangaReader.Core.Sources.MangaDex.Metadata; namespace MangaReader.Core.Sources.MangaDex.Metadata;
@@ -62,11 +63,11 @@ public class MangaDexMetadataProvider(IMangaDexClient mangaDexClient) : IMangaMe
if (attributes.AltTitles == null || attributes.AltTitles.Count == 0) if (attributes.AltTitles == null || attributes.AltTitles.Count == 0)
return []; return [];
Dictionary<string, SourceMangaLanguage> languageIdMap = new() Dictionary<string, Language> languageIdMap = new()
{ {
{ "en", SourceMangaLanguage.English }, { "en", Language.English },
{ "ja", SourceMangaLanguage.Japanese }, { "ja", Language.Japanese },
{ "ja-ro", SourceMangaLanguage.Romanji }, { "ja-ro", Language.Romaji },
}; };
List<SourceMangaTitle> sourceMangaTitles = []; List<SourceMangaTitle> sourceMangaTitles = [];
@@ -75,7 +76,7 @@ public class MangaDexMetadataProvider(IMangaDexClient mangaDexClient) : IMangaMe
{ {
foreach (string alternateTitleKey in alternateTitle.Keys) foreach (string alternateTitleKey in alternateTitle.Keys)
{ {
if (languageIdMap.TryGetValue(alternateTitleKey, out SourceMangaLanguage language) == false) if (languageIdMap.TryGetValue(alternateTitleKey, out Language language) == false)
continue; continue;
SourceMangaTitle sourceMangaTitle = new() SourceMangaTitle sourceMangaTitle = new()

View File

@@ -45,7 +45,7 @@ public partial class MangaDexSearchProvider(IMangaDexClient mangaDexClient) : IM
if (thing.Count > 0) if (thing.Count > 0)
{ {
Guid[] mangaGuids = thing.Select(x => x.Key).ToArray(); Guid[] mangaGuids = [.. thing.Select(x => x.Key)];
var reults = await GetCoverArtFileNamesAsync(mangaGuids, cancellationToken); var reults = await GetCoverArtFileNamesAsync(mangaGuids, cancellationToken);
//var reults = await mangaDexClient.GetCoverArtAsync(mangaGuids, cancellationToken); //var reults = await mangaDexClient.GetCoverArtAsync(mangaGuids, cancellationToken);
} }

View File

@@ -1,4 +1,5 @@
using HtmlAgilityPack; using HtmlAgilityPack;
using MangaReader.Core.Common;
using MangaReader.Core.Metadata; using MangaReader.Core.Metadata;
using System.Text; using System.Text;
using System.Web; using System.Web;
@@ -46,7 +47,7 @@ public class MangaNatoWebCrawler : MangaWebCrawler
SourceMangaTitle sourceMangaTitle = new() SourceMangaTitle sourceMangaTitle = new()
{ {
Title = title, Title = title,
Language = SourceMangaLanguage.Unknown Language = Language.Unknown
}; };
sourceMangaTitles.Add(sourceMangaTitle); sourceMangaTitles.Add(sourceMangaTitle);

View File

@@ -23,12 +23,15 @@ public partial class NatoMangaClient(IHttpService httpService) : INatoMangaClien
{ {
string url = GetSearchUrl(searchWord); string url = GetSearchUrl(searchWord);
string response = await httpService.GetStringAsync(url, cancellationToken); Dictionary<string,string> requestHeader = [];
requestHeader.Add("Referer", "https://www.natomanga.com/");
string response = await httpService.GetStringAsync(url, requestHeader, cancellationToken);
return JsonSerializer.Deserialize<NatoMangaSearchResult[]>(response, _jsonSerializerOptions) ?? []; return JsonSerializer.Deserialize<NatoMangaSearchResult[]>(response, _jsonSerializerOptions) ?? [];
} }
protected string GetSearchUrl(string searchWord) protected static string GetSearchUrl(string searchWord)
{ {
string formattedSeachWord = GetFormattedSearchWord(searchWord); string formattedSeachWord = GetFormattedSearchWord(searchWord);

View File

@@ -1,7 +1,9 @@
using MangaReader.Core.Data; using MangaReader.Core.Common;
using MangaReader.Core.Data;
using MangaReader.Core.Metadata; using MangaReader.Core.Metadata;
using MangaReader.Core.Pipeline; using MangaReader.Core.Pipeline;
using MangaReader.Tests.Utilities; using MangaReader.Tests.Utilities;
using Shouldly;
namespace MangaReader.Tests.Pipeline; namespace MangaReader.Tests.Pipeline;
@@ -21,7 +23,7 @@ public class MangaPipelineTests(TestDbContextFactory factory) : IClassFixture<Te
new() new()
{ {
Title = "Hagane no Renkinjutsushi", Title = "Hagane no Renkinjutsushi",
Language = SourceMangaLanguage.Romanji Language = Language.Romaji
} }
], ],
Genres = ["Action", "Adventure"], Genres = ["Action", "Adventure"],
@@ -40,14 +42,15 @@ public class MangaPipelineTests(TestDbContextFactory factory) : IClassFixture<Te
MangaPipelineRequest request = new() MangaPipelineRequest request = new()
{ {
SourceName = "MySource", SourceName = "MySource",
SourceUrl = "https://wwww.mymangasource.org/my-manga",
SourceManga = sourceManga SourceManga = sourceManga
}; };
await pipeline.RunAsync(request); await pipeline.RunAsync(request);
Assert.Single(context.Mangas); context.Mangas.ShouldHaveSingleItem();
Assert.Single(context.MangaTitles); context.MangaTitles.ShouldHaveSingleItem();
Assert.Equal(2, context.Genres.Count()); context.Genres.Count().ShouldBe(2);
Assert.Single(context.MangaChapters); context.MangaChapters.ShouldHaveSingleItem();
} }
} }

View File

@@ -1,4 +1,5 @@
using MangaReader.Core.Metadata; using MangaReader.Core.Common;
using MangaReader.Core.Metadata;
using MangaReader.Core.Sources.MangaDex.Api; using MangaReader.Core.Sources.MangaDex.Api;
using MangaReader.Core.Sources.MangaDex.Metadata; using MangaReader.Core.Sources.MangaDex.Metadata;
using NSubstitute; using NSubstitute;
@@ -233,19 +234,19 @@ public class MangaDexMetadataTests
sourceManga.AlternateTitles.Count.ShouldBe(5); sourceManga.AlternateTitles.Count.ShouldBe(5);
sourceManga.AlternateTitles[0].Title.ShouldBe("オタクに優しいギャルはいない!?"); sourceManga.AlternateTitles[0].Title.ShouldBe("オタクに優しいギャルはいない!?");
sourceManga.AlternateTitles[0].Language.ShouldBe(SourceMangaLanguage.Japanese); sourceManga.AlternateTitles[0].Language.ShouldBe(Language.Japanese);
sourceManga.AlternateTitles[1].Title.ShouldBe("Otaku ni Yasashii Gal wa Inai!?"); sourceManga.AlternateTitles[1].Title.ShouldBe("Otaku ni Yasashii Gal wa Inai!?");
sourceManga.AlternateTitles[1].Language.ShouldBe(SourceMangaLanguage.Romanji); sourceManga.AlternateTitles[1].Language.ShouldBe(Language.Romaji);
sourceManga.AlternateTitles[2].Title.ShouldBe("Otaku ni Yasashii Gyaru ha Inai!?"); sourceManga.AlternateTitles[2].Title.ShouldBe("Otaku ni Yasashii Gyaru ha Inai!?");
sourceManga.AlternateTitles[2].Language.ShouldBe(SourceMangaLanguage.Romanji); sourceManga.AlternateTitles[2].Language.ShouldBe(Language.Romaji);
sourceManga.AlternateTitles[3].Title.ShouldBe("Gal Can't Be Kind to Otaku!?"); sourceManga.AlternateTitles[3].Title.ShouldBe("Gal Can't Be Kind to Otaku!?");
sourceManga.AlternateTitles[3].Language.ShouldBe(SourceMangaLanguage.English); sourceManga.AlternateTitles[3].Language.ShouldBe(Language.English);
sourceManga.AlternateTitles[4].Title.ShouldBe("Gals Can't Be Kind To A Geek!?"); sourceManga.AlternateTitles[4].Title.ShouldBe("Gals Can't Be Kind To A Geek!?");
sourceManga.AlternateTitles[4].Language.ShouldBe(SourceMangaLanguage.English); sourceManga.AlternateTitles[4].Language.ShouldBe(Language.English);
sourceManga.Genres.Count.ShouldBe(5); sourceManga.Genres.Count.ShouldBe(5);
sourceManga.Genres[0].ShouldBe("Romance"); sourceManga.Genres[0].ShouldBe("Romance");

View File

@@ -32,5 +32,7 @@ public class TestDbContextFactory : IDisposable
{ {
_connection.Close(); _connection.Close();
_connection.Dispose(); _connection.Dispose();
GC.SuppressFinalize(this);
} }
} }

View File

@@ -10,7 +10,7 @@ public partial class UppercaseConverter : IValueConverter
if (value == null) if (value == null)
return null; return null;
return value.ToString().ToUpperInvariant(); return value?.ToString()?.ToUpperInvariant();
} }
public object ConvertBack(object value, Type targetType, object parameter, string language) 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; using Microsoft.UI.Xaml;
namespace MangaReader.WinUI; namespace MangaReader.WinUI;
@@ -8,7 +6,7 @@ public sealed partial class MainWindow : Window
{ {
private const string ApplicationTitle = "Manga Reader"; private const string ApplicationTitle = "Manga Reader";
public MainWindow(IMangaSearchCoordinator mangaSearchCoordinator, IMangaDexClient mangaDexClient) public MainWindow()
{ {
InitializeComponent(); InitializeComponent();

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows10.0.19041.0</TargetFramework> <TargetFramework>net9.0-windows10.0.19041.0</TargetFramework>
@@ -51,6 +51,8 @@
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" /> <PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" /> <PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" /> <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>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MangaReader.Core\MangaReader.Core.csproj" /> <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 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.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO;
using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Input; using System.Windows.Input;
@@ -10,6 +18,8 @@ namespace MangaReader.WinUI.ViewModels;
public partial class SearchViewModel(IMangaSearchCoordinator searchCoordinator) : ViewModelBase public partial class SearchViewModel(IMangaSearchCoordinator searchCoordinator) : ViewModelBase
{ {
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
private CancellationTokenSource? _cancellationTokenSource; private CancellationTokenSource? _cancellationTokenSource;
private string? _keyword; 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 ICommand SearchCommand => new AsyncRelayCommand(SearchAsync);
public async Task 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); Dictionary<string, MangaSearchResult[]> result = await searchCoordinator.SearchAsync(Keyword, _cancellationTokenSource.Token);
List<MangaSearchResult> searchResults = []; List<MangaSearchResult> searchResults = [];
List<ObservableMangaSearchResult> mangaSearchResults = [];
foreach (var item in result) foreach (var item in result)
{ {
foreach (MangaSearchResult searchResult in item.Value) 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); searchResults.Add(searchResult);
mangaSearchResults.Add(mangaSearchResult);
} }
} }
SearchResults = new(searchResults); 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

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