Add - Added a button to manually match Sonarr series
Add - Available dubs/subs overall to history series - not all dubs/subs are available for every season/episode Fix - Subscription end date was in UTC, causing the program to not recognize an active premium subscription https://github.com/Crunchy-DL/Crunchy-Downloader/issues/74
This commit is contained in:
parent
29376c59a6
commit
6aa10cb2c2
|
@ -10,6 +10,13 @@
|
|||
<crd:ViewLocator />
|
||||
</Application.DataTemplates>
|
||||
|
||||
<Application.Resources>
|
||||
<x:Double x:Key="ContentDialogMinWidth">500</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxWidth">1500</x:Double>
|
||||
<x:Double x:Key="ContentDialogMinHeight">150</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxHeight">700</x:Double>
|
||||
</Application.Resources>
|
||||
|
||||
<Application.Styles>
|
||||
<sty:FluentAvaloniaTheme PreferSystemTheme="True" PreferUserAccentColor="True"/>
|
||||
<StyleInclude Source="avares://CRD/Styling/ControlsGalleryStyles.axaml" />
|
||||
|
|
|
@ -153,7 +153,6 @@ public class CalendarManager{
|
|||
}
|
||||
|
||||
|
||||
|
||||
CalendarWeek week = new CalendarWeek();
|
||||
week.CalendarDays = new List<CalendarDay>();
|
||||
|
||||
|
@ -181,10 +180,6 @@ public class CalendarManager{
|
|||
foreach (var crBrowseEpisode in newEpisodes){
|
||||
var targetDate = CrunchyrollManager.Instance.CrunOptions.CalendarFilterByAirDate ? crBrowseEpisode.EpisodeMetadata.EpisodeAirDate : crBrowseEpisode.LastPublic;
|
||||
|
||||
if (targetDate.Kind == DateTimeKind.Utc){
|
||||
targetDate = targetDate.ToLocalTime();
|
||||
}
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.CalendarHideDubs && crBrowseEpisode.EpisodeMetadata.SeasonTitle != null &&
|
||||
(crBrowseEpisode.EpisodeMetadata.SeasonTitle.EndsWith("Dub)") || crBrowseEpisode.EpisodeMetadata.AudioLocale != Locale.JaJp)){
|
||||
continue;
|
||||
|
@ -218,6 +213,18 @@ public class CalendarManager{
|
|||
calendarDay.CalendarEpisodes?.Add(calEpisode);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var weekCalendarDay in week.CalendarDays){
|
||||
if (weekCalendarDay.CalendarEpisodes != null)
|
||||
weekCalendarDay.CalendarEpisodes = weekCalendarDay.CalendarEpisodes
|
||||
.OrderBy(e => e.DateTime)
|
||||
.ThenBy(e => e.SeasonName)
|
||||
.ThenBy(e => {
|
||||
double parsedNumber;
|
||||
return double.TryParse(e.EpisodeNumber, out parsedNumber) ? parsedNumber : double.MinValue;
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ public class CrAuth{
|
|||
}
|
||||
|
||||
private void JsonTokenToFileAndVariable(string content){
|
||||
crunInstance.Token = JsonConvert.DeserializeObject<CrToken>(content, crunInstance.SettingsJsonSerializerSettings);
|
||||
crunInstance.Token = Helpers.Deserialize<CrToken>(content, crunInstance.SettingsJsonSerializerSettings);
|
||||
|
||||
|
||||
if (crunInstance.Token != null && crunInstance.Token.expires_in != null){
|
||||
|
|
|
@ -166,7 +166,7 @@ public class CrunchyrollManager{
|
|||
if (File.Exists(CfgManager.PathCrHistory)){
|
||||
var decompressedJson = CfgManager.DecompressJsonFile(CfgManager.PathCrHistory);
|
||||
if (!string.IsNullOrEmpty(decompressedJson)){
|
||||
HistoryList = JsonConvert.DeserializeObject<ObservableCollection<HistorySeries>>(decompressedJson) ?? new ObservableCollection<HistorySeries>();
|
||||
HistoryList = Helpers.Deserialize<ObservableCollection<HistorySeries>>(decompressedJson,CrunchyrollManager.Instance.SettingsJsonSerializerSettings) ?? new ObservableCollection<HistorySeries>();
|
||||
|
||||
foreach (var historySeries in HistoryList){
|
||||
historySeries.Init();
|
||||
|
@ -1611,7 +1611,7 @@ public class CrunchyrollManager{
|
|||
Data = new List<Dictionary<string, Dictionary<string, StreamDetails>>>()
|
||||
};
|
||||
|
||||
var playStream = JsonConvert.DeserializeObject<CrunchyStreamData>(responseContent, SettingsJsonSerializerSettings);
|
||||
var playStream = Helpers.Deserialize<CrunchyStreamData>(responseContent, SettingsJsonSerializerSettings);
|
||||
if (playStream == null) return temppbData;
|
||||
|
||||
if (!string.IsNullOrEmpty(playStream.Token)){
|
||||
|
@ -1758,7 +1758,7 @@ public class CrunchyrollManager{
|
|||
showRequestResponse = await HttpClientReq.Instance.SendHttpRequest(showRequest);
|
||||
|
||||
if (showRequestResponse.IsOk){
|
||||
CrunchyOldChapter chapterData = JsonConvert.DeserializeObject<CrunchyOldChapter>(showRequestResponse.ResponseContent);
|
||||
CrunchyOldChapter chapterData = Helpers.Deserialize<CrunchyOldChapter>(showRequestResponse.ResponseContent,SettingsJsonSerializerSettings);
|
||||
|
||||
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
|
|
|
@ -379,12 +379,22 @@ public class History(){
|
|||
}
|
||||
}
|
||||
|
||||
private CrSeriesBase? cachedSeries;
|
||||
|
||||
private async Task RefreshSeriesData(string seriesId, HistorySeries historySeries){
|
||||
var series = await crunInstance.CrSeries.SeriesById(seriesId, string.IsNullOrEmpty(crunInstance.CrunOptions.HistoryLang) ? crunInstance.DefaultLocale : crunInstance.CrunOptions.HistoryLang, true);
|
||||
if (series?.Data != null){
|
||||
historySeries.SeriesDescription = series.Data.First().Description;
|
||||
historySeries.ThumbnailImageUrl = GetSeriesThumbnail(series);
|
||||
historySeries.SeriesTitle = series.Data.First().Title;
|
||||
if (cachedSeries == null || (cachedSeries.Data != null && cachedSeries.Data.First().Id != seriesId)){
|
||||
cachedSeries = await crunInstance.CrSeries.SeriesById(seriesId, string.IsNullOrEmpty(crunInstance.CrunOptions.HistoryLang) ? crunInstance.DefaultLocale : crunInstance.CrunOptions.HistoryLang, true);
|
||||
} else{
|
||||
return;
|
||||
}
|
||||
|
||||
if (cachedSeries?.Data != null){
|
||||
var series = cachedSeries.Data.First();
|
||||
historySeries.SeriesDescription = series.Description;
|
||||
historySeries.ThumbnailImageUrl = GetSeriesThumbnail(cachedSeries);
|
||||
historySeries.SeriesTitle = series.Title;
|
||||
historySeries.HistorySeriesAvailableDubLang = series.AudioLocales;
|
||||
historySeries.HistorySeriesAvailableSoftSubs = series.SubtitleLocales;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -758,13 +768,13 @@ public class History(){
|
|||
return highestSimilarity < 0.8 ? null : closestMatch;
|
||||
}
|
||||
|
||||
private double CalculateSimilarity(string source, string target){
|
||||
public double CalculateSimilarity(string source, string target){
|
||||
int distance = LevenshteinDistance(source, target);
|
||||
return 1.0 - (double)distance / Math.Max(source.Length, target.Length);
|
||||
}
|
||||
|
||||
|
||||
public int LevenshteinDistance(string source, string target){
|
||||
private int LevenshteinDistance(string source, string target){
|
||||
if (string.IsNullOrEmpty(source)){
|
||||
return string.IsNullOrEmpty(target) ? 0 : target.Length;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ using System.Security.Cryptography;
|
|||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Parser.Utils;
|
||||
using CRD.Utils.Structs;
|
||||
using Newtonsoft.Json;
|
||||
|
@ -62,7 +63,7 @@ public class HlsDownloader{
|
|||
try{
|
||||
Console.WriteLine("Resume data found! Trying to resume...");
|
||||
string resumeFileContent = File.ReadAllText($"{fn}.resume");
|
||||
var resumeData = JsonConvert.DeserializeObject<ResumeData>(resumeFileContent);
|
||||
var resumeData = Helpers.Deserialize<ResumeData>(resumeFileContent, null);
|
||||
|
||||
if (resumeData != null){
|
||||
if (resumeData.Total == _data.M3U8Json?.Segments.Count &&
|
||||
|
|
|
@ -9,6 +9,7 @@ using System.Text;
|
|||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Media.Imaging;
|
||||
using CRD.Utils.JsonConv;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.Crunchyroll.Music;
|
||||
using Newtonsoft.Json;
|
||||
|
@ -16,15 +17,11 @@ using Newtonsoft.Json;
|
|||
namespace CRD.Utils;
|
||||
|
||||
public class Helpers{
|
||||
/// <summary>
|
||||
/// Deserializes a JSON string into a specified .NET type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the object to deserialize to.</typeparam>
|
||||
/// <param name="json">The JSON string to deserialize.</param>
|
||||
/// <param name="serializerSettings">The settings for deserialization if null default settings will be used</param>
|
||||
/// <returns>The deserialized object of type T.</returns>
|
||||
public static T? Deserialize<T>(string json, JsonSerializerSettings? serializerSettings){
|
||||
try{
|
||||
serializerSettings ??= new JsonSerializerSettings();
|
||||
serializerSettings.Converters.Add(new UtcToLocalTimeConverter());
|
||||
|
||||
return JsonConvert.DeserializeObject<T>(json, serializerSettings);
|
||||
} catch (JsonException ex){
|
||||
Console.Error.WriteLine($"Error deserializing JSON: {ex.Message}");
|
||||
|
@ -55,26 +52,6 @@ public class Helpers{
|
|||
return dialogue;
|
||||
}
|
||||
|
||||
public static string ExtractDialogue(string[] lines, int startLine){
|
||||
var dialogueBuilder = new StringBuilder();
|
||||
|
||||
for (int i = startLine; i < lines.Length && !string.IsNullOrWhiteSpace(lines[i]); i++){
|
||||
if (!lines[i].Contains("-->") && !lines[i].StartsWith("STYLE")){
|
||||
string line = lines[i].Trim();
|
||||
// Remove HTML tags and keep the inner text
|
||||
line = Regex.Replace(line, @"<[^>]+>", "");
|
||||
dialogueBuilder.Append(line + "\\N");
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the last newline character
|
||||
if (dialogueBuilder.Length > 0){
|
||||
dialogueBuilder.Length -= 2; // Remove the last "\N"
|
||||
}
|
||||
|
||||
return dialogueBuilder.ToString();
|
||||
}
|
||||
|
||||
public static void OpenUrl(string url){
|
||||
try{
|
||||
Process.Start(new ProcessStartInfo{
|
||||
|
@ -423,4 +400,5 @@ public class Helpers{
|
|||
|
||||
return languageGroups;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.Utils.JsonConv;
|
||||
|
||||
public class UtcToLocalTimeConverter : JsonConverter<DateTime>{
|
||||
public override DateTime ReadJson(JsonReader reader, Type objectType, DateTime existingValue, bool hasExistingValue, JsonSerializer serializer){
|
||||
return reader.Value switch{
|
||||
null => DateTime.MinValue,
|
||||
DateTime dateTime when dateTime.Kind == DateTimeKind.Utc => dateTime.ToLocalTime(),
|
||||
DateTime dateTime => dateTime,
|
||||
_ => throw new JsonSerializationException($"Unexpected token parsing date. Expected DateTime, got {reader.Value.GetType()}.")
|
||||
};
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, DateTime value, JsonSerializer serializer){
|
||||
writer.WriteValue(value);
|
||||
}
|
||||
}
|
|
@ -129,7 +129,10 @@ public class SonarrSeries{
|
|||
/// The images.
|
||||
/// </value>
|
||||
[JsonProperty("images")]
|
||||
public List<SonarrImage> Images{ get; set; }
|
||||
public List<SonarrImage>? Images{ get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string ImageUrl{ get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the series.
|
||||
|
|
|
@ -109,7 +109,7 @@ public class SonarrClient{
|
|||
List<SonarrSeries> series = [];
|
||||
|
||||
try{
|
||||
series = JsonConvert.DeserializeObject<List<SonarrSeries>>(json) ?? [];
|
||||
series = Helpers.Deserialize<List<SonarrSeries>>(json,null) ?? [];
|
||||
} catch (Exception e){
|
||||
MainWindow.Instance.ShowError("Sonarr GetSeries error \n" + e);
|
||||
Console.Error.WriteLine("Sonarr GetSeries error \n" + e);
|
||||
|
@ -124,7 +124,7 @@ public class SonarrClient{
|
|||
List<SonarrEpisode> episodes = [];
|
||||
|
||||
try{
|
||||
episodes = JsonConvert.DeserializeObject<List<SonarrEpisode>>(json) ?? [];
|
||||
episodes = Helpers.Deserialize<List<SonarrEpisode>>(json,null) ?? [];
|
||||
} catch (Exception e){
|
||||
MainWindow.Instance.ShowError("Sonarr GetEpisodes error \n" + e);
|
||||
Console.Error.WriteLine("Sonarr GetEpisodes error \n" + e);
|
||||
|
@ -138,7 +138,7 @@ public class SonarrClient{
|
|||
var json = await GetJson($"/v3/episode/id={episodeId}");
|
||||
var episode = new SonarrEpisode();
|
||||
try{
|
||||
episode = JsonConvert.DeserializeObject<SonarrEpisode>(json) ?? new SonarrEpisode();
|
||||
episode = Helpers.Deserialize<SonarrEpisode>(json,null) ?? new SonarrEpisode();
|
||||
} catch (Exception e){
|
||||
MainWindow.Instance.ShowError("Sonarr GetEpisode error \n" + e);
|
||||
Console.Error.WriteLine("Sonarr GetEpisode error \n" + e);
|
||||
|
|
|
@ -14,7 +14,7 @@ public class StreamError{
|
|||
|
||||
public static StreamError? FromJson(string json){
|
||||
try{
|
||||
return JsonConvert.DeserializeObject<StreamError>(json);
|
||||
return Helpers.Deserialize<StreamError>(json,null);
|
||||
} catch (Exception e){
|
||||
Console.Error.WriteLine(e);
|
||||
return null;
|
||||
|
|
|
@ -52,6 +52,12 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
[JsonProperty("history_series_add_date")]
|
||||
public DateTime? HistorySeriesAddDate{ get; set; }
|
||||
|
||||
[JsonProperty("history_series_available_soft_subs")]
|
||||
public List<string> HistorySeriesAvailableSoftSubs{ get; set; } =[];
|
||||
|
||||
[JsonProperty("history_series_available_dub_lang")]
|
||||
public List<string> HistorySeriesAvailableDubLang{ get; set; } =[];
|
||||
|
||||
[JsonProperty("history_series_soft_subs_override")]
|
||||
public List<string> HistorySeriesSoftSubsOverride{ get; set; } =[];
|
||||
|
||||
|
|
|
@ -49,9 +49,9 @@ public class Updater : INotifyPropertyChanged{
|
|||
public async Task<bool> CheckForUpdatesAsync(){
|
||||
try{
|
||||
using (var client = new HttpClient()){
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "C# App"); // GitHub API requires a user agent
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "C# App");
|
||||
var response = await client.GetStringAsync(apiEndpoint);
|
||||
var releaseInfo = JsonConvert.DeserializeObject<dynamic>(response);
|
||||
var releaseInfo = Helpers.Deserialize<dynamic>(response,null);
|
||||
|
||||
var latestVersion = releaseInfo.tag_name;
|
||||
downloadUrl = releaseInfo.assets[0].browser_download_url;
|
||||
|
@ -63,11 +63,11 @@ public class Updater : INotifyPropertyChanged{
|
|||
if (latestVersion != currentVersion){
|
||||
Console.WriteLine("Update available: " + latestVersion + " - Current Version: " + currentVersion);
|
||||
return true;
|
||||
} else{
|
||||
}
|
||||
|
||||
Console.WriteLine("No updates available.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (Exception e){
|
||||
Console.Error.WriteLine("Failed to get Update information");
|
||||
return false;
|
||||
|
|
|
@ -111,7 +111,7 @@ public partial class AccountPageViewModel : ViewModelBase{
|
|||
CloseButtonText = "Close"
|
||||
};
|
||||
|
||||
var viewModel = new ContentDialogInputLoginViewModel(dialog, this);
|
||||
var viewModel = new Utils.ContentDialogInputLoginViewModel(dialog, this);
|
||||
dialog.Content = new ContentDialogInputLoginView(){
|
||||
DataContext = viewModel
|
||||
};
|
||||
|
|
|
@ -13,7 +13,10 @@ using CRD.Utils;
|
|||
using CRD.Utils.Sonarr;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
using CRD.ViewModels.Utils;
|
||||
using CRD.Views;
|
||||
using CRD.Views.Utils;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
|
@ -28,24 +31,38 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
[ObservableProperty]
|
||||
public static bool _sonarrAvailable;
|
||||
|
||||
[ObservableProperty]
|
||||
public static bool _sonarrConnected;
|
||||
|
||||
private IStorageProvider? _storageProvider;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _availableDubs;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _availableSubs;
|
||||
|
||||
public SeriesPageViewModel(){
|
||||
|
||||
|
||||
|
||||
_selectedSeries = CrunchyrollManager.Instance.SelectedSeries;
|
||||
|
||||
if (_selectedSeries.ThumbnailImage == null){
|
||||
_selectedSeries.LoadImage();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(SelectedSeries.SonarrSeriesId) && CrunchyrollManager.Instance.CrunOptions.SonarrProperties != null){
|
||||
SonarrAvailable = SelectedSeries.SonarrSeriesId.Length > 0 && CrunchyrollManager.Instance.CrunOptions.SonarrProperties.SonarrEnabled;
|
||||
if (CrunchyrollManager.Instance.CrunOptions.SonarrProperties != null){
|
||||
SonarrConnected = CrunchyrollManager.Instance.CrunOptions.SonarrProperties.SonarrEnabled;
|
||||
|
||||
if (!string.IsNullOrEmpty(SelectedSeries.SonarrSeriesId)){
|
||||
SonarrAvailable = SelectedSeries.SonarrSeriesId.Length > 0 && SonarrConnected;
|
||||
} else{
|
||||
SonarrAvailable = false;
|
||||
}
|
||||
} else{
|
||||
SonarrConnected = SonarrAvailable = false;
|
||||
}
|
||||
|
||||
AvailableDubs = "Available Dubs: " + string.Join(", ", SelectedSeries.HistorySeriesAvailableDubLang);
|
||||
AvailableSubs = "Available Subs: " + string.Join(", ", SelectedSeries.HistorySeriesAvailableSoftSubs);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
@ -79,6 +96,44 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
_storageProvider = storageProvider ?? throw new ArgumentNullException(nameof(storageProvider));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task MatchSonarrSeries_Button(){
|
||||
var dialog = new ContentDialog(){
|
||||
Title = "Sonarr Matching",
|
||||
PrimaryButtonText = "Save",
|
||||
CloseButtonText = "Close",
|
||||
FullSizeDesired = true
|
||||
};
|
||||
|
||||
var viewModel = new ContentDialogSonarrMatchViewModel(dialog, SelectedSeries.SonarrSeriesId,SelectedSeries.SeriesTitle);
|
||||
dialog.Content = new ContentDialogSonarrMatchView(){
|
||||
DataContext = viewModel
|
||||
};
|
||||
|
||||
var dialogResult = await dialog.ShowAsync();
|
||||
|
||||
if (dialogResult == ContentDialogResult.Primary){
|
||||
SelectedSeries.SonarrSeriesId = viewModel.CurrentSonarrSeries.Id.ToString();
|
||||
SelectedSeries.SonarrTvDbId = viewModel.CurrentSonarrSeries.TvdbId.ToString();
|
||||
SelectedSeries.SonarrSlugTitle = viewModel.CurrentSonarrSeries.TitleSlug;
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.SonarrProperties != null){
|
||||
SonarrConnected = CrunchyrollManager.Instance.CrunOptions.SonarrProperties.SonarrEnabled;
|
||||
|
||||
if (!string.IsNullOrEmpty(SelectedSeries.SonarrSeriesId)){
|
||||
SonarrAvailable = SelectedSeries.SonarrSeriesId.Length > 0 && SonarrConnected;
|
||||
} else{
|
||||
SonarrAvailable = false;
|
||||
}
|
||||
} else{
|
||||
SonarrConnected = SonarrAvailable = false;
|
||||
}
|
||||
|
||||
UpdateData("");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
public async Task DownloadSeasonAll(HistorySeason season){
|
||||
|
@ -112,6 +167,9 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
|
||||
SelectedSeries.Seasons.Refresh();
|
||||
|
||||
AvailableDubs = "Available Dubs: " + string.Join(", ", SelectedSeries.HistorySeriesAvailableDubLang);
|
||||
AvailableSubs = "Available Subs: " + string.Join(", ", SelectedSeries.HistorySeriesAvailableSoftSubs);
|
||||
|
||||
// MessageBus.Current.SendMessage(new NavigationMessage(typeof(SeriesPageViewModel), false, true));
|
||||
}
|
||||
|
||||
|
@ -122,6 +180,7 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
SelectedSeries.Seasons.Remove(objectToRemove);
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
|
||||
MessageBus.Current.SendMessage(new NavigationMessage(typeof(SeriesPageViewModel), false, true));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
using System;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Structs;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
namespace CRD.ViewModels.Utils;
|
||||
|
||||
public partial class ContentDialogInputLoginViewModel : ViewModelBase{
|
||||
private readonly ContentDialog dialog;
|
|
@ -0,0 +1,101 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Avalonia.Media.Imaging;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Sonarr;
|
||||
using CRD.Utils.Sonarr.Models;
|
||||
using DynamicData;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
|
||||
namespace CRD.ViewModels.Utils;
|
||||
|
||||
public partial class ContentDialogSonarrMatchViewModel : ViewModelBase{
|
||||
private readonly ContentDialog dialog;
|
||||
|
||||
[ObservableProperty]
|
||||
private SonarrSeries _currentSonarrSeries;
|
||||
|
||||
[ObservableProperty]
|
||||
private Bitmap? _currentSeriesImage;
|
||||
|
||||
[ObservableProperty]
|
||||
private SonarrSeries _selectedItem;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<SonarrSeries> _sonarrSeriesList = new();
|
||||
|
||||
public ContentDialogSonarrMatchViewModel(ContentDialog dialog, string? currentSonarrId, string? seriesTitle){
|
||||
if (dialog is null){
|
||||
throw new ArgumentNullException(nameof(dialog));
|
||||
}
|
||||
|
||||
this.dialog = dialog;
|
||||
dialog.Closed += DialogOnClosed;
|
||||
dialog.PrimaryButtonClick += SaveButton;
|
||||
|
||||
CurrentSonarrSeries = SonarrClient.Instance.SonarrSeries.Find(e => e.Id.ToString() == currentSonarrId) ?? new SonarrSeries(){ Title = "No series matched" };
|
||||
|
||||
SetImageUrl(CurrentSonarrSeries);
|
||||
|
||||
LoadList(seriesTitle);
|
||||
}
|
||||
|
||||
private void SaveButton(ContentDialog sender, ContentDialogButtonClickEventArgs args){
|
||||
dialog.PrimaryButtonClick -= SaveButton;
|
||||
|
||||
}
|
||||
|
||||
private void LoadList(string? title){
|
||||
var list = PopulateSeriesList(title);
|
||||
SonarrSeriesList.AddRange(list);
|
||||
}
|
||||
|
||||
private List<SonarrSeries> PopulateSeriesList(string? title){
|
||||
var seriesList = SonarrClient.Instance.SonarrSeries.ToList();
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(title)){
|
||||
seriesList.Sort((series1, series2) => {
|
||||
double similarity1 = Helpers.CalculateCosineSimilarity(series1.Title.ToLower(), title.ToLower());
|
||||
double similarity2 = Helpers.CalculateCosineSimilarity(series2.Title.ToLower(), title.ToLower());
|
||||
|
||||
return similarity2.CompareTo(similarity1);
|
||||
});
|
||||
} else{
|
||||
seriesList.Sort((series1, series2) => string.Compare(series1.Title, series2.Title, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
seriesList = seriesList.Take(20).ToList();
|
||||
|
||||
foreach (var sonarrSeries in seriesList){
|
||||
SetImageUrl(sonarrSeries);
|
||||
}
|
||||
|
||||
return seriesList;
|
||||
}
|
||||
|
||||
private void SetImageUrl(SonarrSeries sonarrSeries){
|
||||
var properties = CrunchyrollManager.Instance.CrunOptions.SonarrProperties;
|
||||
if (properties == null || sonarrSeries.Images == null){
|
||||
return;
|
||||
}
|
||||
|
||||
var baseUrl = "";
|
||||
baseUrl = $"http{(properties.UseSsl ? "s" : "")}://{(!string.IsNullOrEmpty(properties.Host) ? properties.Host : "localhost")}:{properties.Port}{(properties.UrlBase ?? "")}";
|
||||
|
||||
sonarrSeries.ImageUrl = baseUrl + sonarrSeries.Images.Find(e => e.CoverType == SonarrCoverType.Poster)?.Url;
|
||||
}
|
||||
|
||||
|
||||
partial void OnSelectedItemChanged(SonarrSeries value){
|
||||
CurrentSonarrSeries = value;
|
||||
}
|
||||
|
||||
private void DialogOnClosed(ContentDialog sender, ContentDialogClosedEventArgs args){
|
||||
dialog.Closed -= DialogOnClosed;
|
||||
}
|
||||
}
|
|
@ -1,12 +1,10 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CRD.Downloader;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Updater;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
namespace CRD.ViewModels.Utils;
|
||||
|
||||
public partial class ContentDialogUpdateViewModel : ViewModelBase{
|
||||
private readonly ContentDialog dialog;
|
|
@ -9,6 +9,7 @@ using FluentAvalonia.Core;
|
|||
using FluentAvalonia.UI.Controls;
|
||||
using FluentAvalonia.UI.Windowing;
|
||||
using ReactiveUI;
|
||||
using ContentDialogUpdateViewModel = CRD.ViewModels.Utils.ContentDialogUpdateViewModel;
|
||||
|
||||
namespace CRD.Views;
|
||||
|
||||
|
|
|
@ -27,23 +27,36 @@
|
|||
|
||||
<Button Grid.Row="0" Grid.Column="0" Command="{Binding NavBack}" Margin="0 0 0 10">Back</Button>
|
||||
|
||||
<Image Grid.Row="1" Grid.Column="0" Margin="10" Source="{Binding SelectedSeries.ThumbnailImage}" Width="240"
|
||||
<ScrollViewer Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
|
||||
|
||||
<StackPanel Orientation="Vertical">
|
||||
|
||||
<Grid Margin="10" VerticalAlignment="Top" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Grid.Column="0" Margin="10" Source="{Binding SelectedSeries.ThumbnailImage}" Width="240"
|
||||
Height="360">
|
||||
</Image>
|
||||
|
||||
|
||||
<Grid Grid.Row="1" Grid.Column="1">
|
||||
<Grid Grid.Column="1">
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" FontSize="50" Text="{Binding SelectedSeries.SeriesTitle}"></TextBlock>
|
||||
<TextBlock Grid.Row="1" FontSize="20" TextWrapping="Wrap" Text="{Binding SelectedSeries.SeriesDescription}"></TextBlock>
|
||||
<StackPanel Grid.Row="3" Orientation="Vertical">
|
||||
<TextBlock Grid.Row="3" FontSize="15" Opacity="0.8" TextWrapping="Wrap" Text="{Binding AvailableDubs}"></TextBlock>
|
||||
<TextBlock Grid.Row="4" FontSize="15" Opacity="0.8" TextWrapping="Wrap" Text="{Binding AvailableSubs}"></TextBlock>
|
||||
<StackPanel Grid.Row="5" Orientation="Vertical">
|
||||
|
||||
<StackPanel Orientation="Horizontal" Margin="0 10 10 10">
|
||||
|
||||
|
@ -190,6 +203,18 @@
|
|||
</Popup>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel IsVisible="{Binding EditMode}">
|
||||
<Button Width="30" Height="30" Margin="0 0 10 0"
|
||||
BorderThickness="0"
|
||||
IsVisible="{Binding SonarrConnected}"
|
||||
Command="{Binding MatchSonarrSeries_Button}">
|
||||
<Grid>
|
||||
<controls:ImageIcon Source="../Assets/sonarr.png" Width="25" Height="25" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
|
@ -197,9 +222,9 @@
|
|||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
|
||||
<ScrollViewer Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
|
||||
<ItemsControl ItemsSource="{Binding SelectedSeries.Seasons}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
|
@ -485,6 +510,11 @@
|
|||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:CRD.ViewModels"
|
||||
x:DataType="vm:ContentDialogInputLoginViewModel"
|
||||
xmlns:utils="clr-namespace:CRD.ViewModels.Utils"
|
||||
x:DataType="utils:ContentDialogInputLoginViewModel"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="CRD.Views.Utils.ContentDialogInputLoginView">
|
||||
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
<controls:ContentDialog xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="clr-namespace:CRD.ViewModels.Utils"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:models="clr-namespace:CRD.Utils.Sonarr.Models"
|
||||
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||
x:DataType="vm:ContentDialogSonarrMatchViewModel"
|
||||
x:Class="CRD.Views.Utils.ContentDialogSonarrMatchView">
|
||||
|
||||
<Grid HorizontalAlignment="Stretch" MaxHeight="500" >
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border Grid.Column="0" Grid.Row="1" CornerRadius="10" Background="{DynamicResource ButtonBackground}">
|
||||
<Grid Margin="10" VerticalAlignment="Top">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- Image -->
|
||||
<Image Grid.Column="0" Margin="10" asyncImageLoader:ImageLoader.Source="{Binding CurrentSonarrSeries.ImageUrl}" MaxWidth="120" MaxHeight="180"></Image>
|
||||
|
||||
<Grid Grid.Column="1" Margin="10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" /> <!-- Takes up most space for the title -->
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<!-- Takes up space as needed for the time -->
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding CurrentSonarrSeries.Title}" FontWeight="Bold"
|
||||
FontSize="16"
|
||||
TextWrapping="Wrap" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding CurrentSonarrSeries.Year}" FontStyle="Italic"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2"
|
||||
Text="{Binding CurrentSonarrSeries.Overview}"
|
||||
FontStyle="Italic" Opacity="0.8" TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" FontSize="1" Text=" " Width="1500" Opacity="0" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- <Rectangle Grid.Row="2" Width="1500" Height="0" Fill="Gray" Margin="10,0" /> -->
|
||||
<!-- <TextBlock Grid.Column="0" Grid.Row="2" Text="Series"></TextBlock> -->
|
||||
|
||||
<ListBox Grid.Row="3" SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding SonarrSeriesList}">
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type models:SonarrSeries}">
|
||||
<StackPanel>
|
||||
<Border Padding="10" Margin="5" BorderThickness="1">
|
||||
<Grid Margin="10" VerticalAlignment="Top">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- Image -->
|
||||
<asyncImageLoader:AdvancedImage Grid.Column="0" MaxWidth="120" MaxHeight="180" Source="{Binding ImageUrl}"
|
||||
Stretch="Fill" />
|
||||
|
||||
<!-- Text Content -->
|
||||
<Grid Grid.Column="1" Margin="10" VerticalAlignment="Top">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" /> <!-- Takes up most space for the title -->
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<!-- Takes up space as needed for the time -->
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="{Binding Title}" FontWeight="Bold"
|
||||
FontSize="16"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Year}" FontStyle="Italic"
|
||||
HorizontalAlignment="Right" TextWrapping="Wrap" />
|
||||
<TextBlock Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2"
|
||||
Text="{Binding Overview}"
|
||||
FontStyle="Italic" Opacity="0.8" TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Background="LightGray" Height="1" Margin="0,5" HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
|
||||
</ListBox>
|
||||
|
||||
</Grid>
|
||||
|
||||
</controls:ContentDialog>
|
|
@ -0,0 +1,11 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace CRD.Views.Utils;
|
||||
|
||||
public partial class ContentDialogSonarrMatchView : UserControl{
|
||||
public ContentDialogSonarrMatchView(){
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
|
@ -3,7 +3,8 @@
|
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:CRD.ViewModels"
|
||||
x:DataType="vm:ContentDialogUpdateViewModel"
|
||||
xmlns:utils="clr-namespace:CRD.ViewModels.Utils"
|
||||
x:DataType="utils:ContentDialogUpdateViewModel"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="CRD.Views.Utils.ContentDialogUpdateView">
|
||||
|
||||
|
|
Loading…
Reference in New Issue