Add: Added checkbox to disable non-drm streams

Chg: Show additional info for downloads (slected dubs and subs)
Chg: When pressing update it now shows the progress
Chg: Changed how the error window looks
Chg: Additional checks for premium episodes to make sure it is possible the logged in user can download the episode
This commit is contained in:
Elwador 2024-05-25 00:46:11 +02:00
parent d769e5e75e
commit f0346fd4c8
21 changed files with 301 additions and 148 deletions

View File

@ -219,6 +219,12 @@ public class CrEpisode(Crunchyroll crunInstance){
Time = 0,
DownloadSpeed = 0
};
epMeta.AvailableSubs = item.SubtitleLocales;
if (episode.Langs.Count > 0){
epMeta.SelectedDubs = dubLang
.Where(language => episode.Langs.Any(epLang => epLang.CrLocale == language))
.ToList();
}
var epMetaData = epMeta.Data[0];
if (!string.IsNullOrEmpty(item.StreamsLink)){

View File

@ -83,7 +83,12 @@ public class CrSeries(Crunchyroll crunInstance){
Time = 0,
DownloadSpeed = 0
};
epMeta.AvailableSubs = item.SubtitleLocales;
if (episode.Langs.Count > 0){
epMeta.SelectedDubs = dubLang
.Where(language => episode.Langs.Any(epLang => epLang.CrLocale == language))
.ToList();
}
var epMetaData = epMeta.Data[0];

View File

@ -150,7 +150,7 @@ public class Crunchyroll{
CrunOptions.History = true;
CfgManager.UpdateSettingsFromFile();
if (CrunOptions.History){
if (File.Exists(CfgManager.PathCrHistory)){
HistoryList = JsonConvert.DeserializeObject<ObservableCollection<HistorySeries>>(File.ReadAllText(CfgManager.PathCrHistory)) ??[];
@ -281,8 +281,8 @@ public class Crunchyroll{
if (episodeL != null){
if (episodeL.Value.Data != null && episodeL.Value.Data.First().IsPremiumOnly && Profile.Username == "???"){
MessageBus.Current.SendMessage(new ToastMessage($"Episode is a premium episode - try to login first", ToastType.Error, 3));
if (episodeL.Value.Data != null && episodeL.Value.Data.First().IsPremiumOnly && (episodeL.Value.Data.First().StreamsLink == null || Profile.Username == "???")){
MessageBus.Current.SendMessage(new ToastMessage($"Episode is a premium episode make sure that you are signed in with an account that has an active premium subscription", ToastType.Error, 3));
return;
}
@ -292,6 +292,7 @@ public class Crunchyroll{
foreach (var crunchyEpMeta in metas){
Queue.Add(crunchyEpMeta);
}
Console.WriteLine("Added Episode to Queue");
MessageBus.Current.SendMessage(new ToastMessage($"Added episode to the queue", ToastType.Information, 1));
}
@ -300,8 +301,20 @@ public class Crunchyroll{
public void AddSeriesToQueue(CrunchySeriesList list, CrunchyMultiDownload data){
var selected = CrSeries.ItemSelectMultiDub(list.Data, data.DubLang, data.But, data.AllEpisodes, data.E);
bool failed = false;
foreach (var crunchyEpMeta in selected.Values.ToList()){
Queue.Add(crunchyEpMeta);
if (crunchyEpMeta.Data?.First().Playback != null){
Queue.Add(crunchyEpMeta);
} else{
failed = true;
}
}
if (failed){
MainWindow.Instance.ShowError("Not all episodes could be added make sure that you are signed in with an account that has an active premium subscription?");
} else{
MessageBus.Current.SendMessage(new ToastMessage($"Added episodes to the queue", ToastType.Information, 1));
}
}
@ -471,7 +484,7 @@ public class Crunchyroll{
private async Task<DownloadResponse> DownloadMediaList(CrunchyEpMeta data, CrDownloadOptions options){
if (CmsToken?.Cms == null){
Console.WriteLine("Missing CMS Token");
MainWindow.ShowError("Missing CMS Token - are you signed in?");
MainWindow.Instance.ShowError("Missing CMS Token - are you signed in?");
return new DownloadResponse{
Data = new List<DownloadedMedia>(),
Error = true,
@ -480,7 +493,7 @@ public class Crunchyroll{
}
if (Profile.Username == "???"){
MainWindow.ShowError("User Account not recognized - are you signed in?");
MainWindow.Instance.ShowError("User Account not recognized - are you signed in?");
return new DownloadResponse{
Data = new List<DownloadedMedia>(),
Error = true,
@ -490,7 +503,17 @@ public class Crunchyroll{
if (!File.Exists(CfgManager.PathFFMPEG)){
Console.Error.WriteLine("Missing ffmpeg");
MainWindow.ShowError("FFmpeg not found");
MainWindow.Instance.ShowError("FFmpeg not found");
return new DownloadResponse{
Data = new List<DownloadedMedia>(),
Error = true,
FileName = "./unknown"
};
}
if (!options.UseNonDrmStreams && !_widevine.canDecrypt){
Console.Error.WriteLine("Only searching for drm streams but widevine can't decrypt");
MainWindow.Instance.ShowError("Settings set to not search for DRM streams - but can't find CDM files in widevine folder ");
return new DownloadResponse{
Data = new List<DownloadedMedia>(),
Error = true,
@ -506,7 +529,7 @@ public class Crunchyroll{
if (data.Data != null && data.Data.All(a => a.Playback == null)){
Console.WriteLine("Video not available!");
MainWindow.ShowError("No Video Data found");
MainWindow.Instance.ShowError("No Video Data found - Are you trying to download a premium episode without havíng a premium account?");
return new DownloadResponse{
Data = files,
Error = true,
@ -544,7 +567,7 @@ public class Crunchyroll{
if (currentVersion.MediaGuid == null){
Console.WriteLine("Selected language not found in versions.");
MainWindow.ShowError("Selected language not found");
MainWindow.Instance.ShowError("Selected language not found");
continue;
}
@ -583,7 +606,7 @@ public class Crunchyroll{
var fetchPlaybackData = await FetchPlaybackData(mediaId, epMeta);
if (!fetchPlaybackData.IsOk){
MainWindow.ShowError("Couldn't get Playback Data");
MainWindow.Instance.ShowError("Couldn't get Playback Data");
return new DownloadResponse{
Data = new List<DownloadedMedia>(),
Error = true,
@ -641,7 +664,7 @@ public class Crunchyroll{
if (streams.Count < 1){
Console.WriteLine("No full streams found!");
MainWindow.ShowError("No streams found");
MainWindow.Instance.ShowError("No streams found");
return new DownloadResponse{
Data = new List<DownloadedMedia>(),
Error = true,
@ -700,7 +723,6 @@ public class Crunchyroll{
StreamDetailsPop? curStream = null;
if (!dlFailed){
options.Kstream = options.Kstream >= 1 && options.Kstream <= streams.Count
? options.Kstream
: 1;
@ -726,7 +748,7 @@ public class Crunchyroll{
if (!streamPlaylistsReqResponse.IsOk){
dlFailed = true;
}
if (dlFailed){
Console.WriteLine($"CAN\'T FETCH VIDEO PLAYLISTS!");
} else{
@ -847,7 +869,7 @@ public class Crunchyroll{
LanguageItem? lang = Languages.languages.FirstOrDefault(a => a.Code == curStream.AudioLang);
if (lang == null){
Console.Error.WriteLine($"Unable to find language for code {curStream.AudioLang}");
MainWindow.ShowError($"Unable to find language for code {curStream.AudioLang}");
MainWindow.Instance.ShowError($"Unable to find language for code {curStream.AudioLang}");
return new DownloadResponse{
Data = new List<DownloadedMedia>(),
Error = true,
@ -869,7 +891,7 @@ public class Crunchyroll{
if (options.DlVideoOnce && dlVideoOnce){
Console.WriteLine("Already downloaded video, skipping video download...");
}else if (options.Novids){
} else if (options.Novids){
Console.WriteLine("Skipping video download...");
} else{
var videoDownloadResult = await DownloadVideo(chosenVideoSegments, options, outFile, tsFile, tempTsFile, data);
@ -1059,7 +1081,7 @@ public class Crunchyroll{
}
} else{
Console.WriteLine("mp4decrypt not found, files need decryption. Decryption Keys: ");
MainWindow.ShowError($"mp4decrypt not found, files need decryption");
MainWindow.Instance.ShowError($"mp4decrypt not found, files need decryption");
}
} else{
if (videoDownloaded){
@ -1082,7 +1104,7 @@ public class Crunchyroll{
}
} else if (!options.Novids){
//TODO
MainWindow.ShowError("Requested Video with the current settings not implemented");
MainWindow.Instance.ShowError("Requested Video with the current settings not implemented");
} else if (options.Novids){
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers, options.Override).ToArray());
Console.WriteLine("Downloading skipped!");
@ -1395,15 +1417,14 @@ public class Crunchyroll{
playbackRequestNonDrm.Headers.UserAgent.ParseAdd("Crunchyroll/1.8.0 Nintendo Switch/12.3.12.0 UE4/4.27");
var playbackRequestNonDrmResponse = await HttpClientReq.Instance.SendHttpRequest(playbackRequestNonDrm);
if (playbackRequestNonDrmResponse.IsOk && playbackRequestNonDrmResponse.ResponseContent != string.Empty){
CrunchyNoDrmStream? playStream = JsonConvert.DeserializeObject<CrunchyNoDrmStream>(playbackRequestNonDrmResponse.ResponseContent, SettingsJsonSerializerSettings);
CrunchyStreams derivedPlayCrunchyStreams = new CrunchyStreams();
if (playStream != null){
var deauthVideoToken = HttpClientReq.CreateRequestMessage($"https://cr-play-service.prd.crunchyrollsvc.com/v1/token/{currentMediaId}/{playStream.Token}/inactive", HttpMethod.Patch, true, false, null);
var deauthVideoTokenResponse = await HttpClientReq.Instance.SendHttpRequest(deauthVideoToken);
if (playStream.HardSubs != null)
foreach (var hardsub in playStream.HardSubs){
var stream = hardsub.Value;

View File

@ -141,6 +141,7 @@ public class CfgManager{
Crunchyroll.Instance.CrunOptions.Theme = loadedOptions.Theme;
Crunchyroll.Instance.CrunOptions.AccentColor = loadedOptions.AccentColor;
Crunchyroll.Instance.CrunOptions.History = loadedOptions.History;
Crunchyroll.Instance.CrunOptions.UseNonDrmStreams = loadedOptions.UseNonDrmStreams;
}
private static object fileLock = new object();

View File

@ -111,4 +111,8 @@ public class CrDownloadOptions{
[YamlMember(Alias = "history", ApplyNamingConventions = false)]
public bool History{ get; set; }
[YamlMember(Alias = "user_non_drm_streams", ApplyNamingConventions = false)]
public bool UseNonDrmStreams{ get; set; }
}

View File

@ -187,6 +187,10 @@ public class CrunchyEpMeta{
public string? Image{ get; set; }
public bool Paused{ get; set; }
public DownloadProgress? DownloadProgress{ get; set; }
public List<string>? SelectedDubs{ get; set; }
public List<string>? AvailableSubs{ get; set; }
}
public class DownloadProgress{

View File

@ -1,20 +1,27 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using CRD.ViewModels;
using CRD.Views.Utils;
using FluentAvalonia.UI.Controls;
using Newtonsoft.Json;
namespace CRD.Utils.Updater;
public class Updater{
public class Updater : INotifyPropertyChanged{
#region Singelton
private static Updater? _instance;
private static readonly object Padlock = new();
public double progress = 0;
public static Updater Instance{
get{
if (_instance == null){
@ -31,6 +38,12 @@ public class Updater{
#endregion
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null){
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string downloadUrl = "";
private readonly string tempPath = Path.Combine(Path.GetTempPath(), "Update.zip");
private readonly string extractPath = Path.Combine(Path.GetTempPath(), "ExtractedUpdate");
@ -65,22 +78,52 @@ public class Updater{
}
}
public async Task DownloadAndUpdateAsync(){
try{
using (var client = new HttpClient()){
// Download the zip file
var response = await client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead);
using (var stream = await response.Content.ReadAsStreamAsync())
using (var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None)){
await stream.CopyToAsync(fileStream);
if (response.IsSuccessStatusCode){
var totalBytes = response.Content.Headers.ContentLength ?? -1L;
var totalBytesRead = 0L;
var buffer = new byte[8192];
var isMoreToRead = true;
using (var stream = await response.Content.ReadAsStreamAsync())
using (var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None)){
do{
var bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
if (bytesRead == 0){
isMoreToRead = false;
progress = 100;
OnPropertyChanged(nameof(progress));
continue;
}
await fileStream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
if (totalBytes != -1){
progress = (double)totalBytesRead / totalBytes * 100;
OnPropertyChanged(nameof(progress));
}
} while (isMoreToRead);
}
ZipFile.ExtractToDirectory(tempPath, extractPath, true);
ApplyUpdate(extractPath);
} else{
Console.WriteLine("Failed to get Update");
}
ZipFile.ExtractToDirectory(tempPath, extractPath, true);
ApplyUpdate(extractPath);
}
} catch (Exception e){
Console.WriteLine("Failed to get Update");
Console.WriteLine($"Failed to get Update: {e.Message}");
}
}

View File

@ -80,7 +80,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
if (currentSeriesList != null){
Crunchyroll.Instance.AddSeriesToQueue(currentSeriesList.Value, new CrunchyMultiDownload(Crunchyroll.Instance.CrunOptions.DubLang, AddAllEpisodes, false, selectedEpisodes));
MessageBus.Current.SendMessage(new ToastMessage($"Added episodes to the queue", ToastType.Information, 1));
}

View File

@ -0,0 +1,40 @@
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;
public partial class ContentDialogUpdateViewModel : ViewModelBase{
private readonly ContentDialog dialog;
[ObservableProperty]
private double _progress;
private AccountPageViewModel accountPageViewModel;
public ContentDialogUpdateViewModel(ContentDialog dialog){
if (dialog is null){
throw new ArgumentNullException(nameof(dialog));
}
this.dialog = dialog;
dialog.Closed += DialogOnClosed;
Updater.Instance.PropertyChanged += Progress_PropertyChanged;
}
private void Progress_PropertyChanged(object? sender, PropertyChangedEventArgs e){
if (e.PropertyName == nameof(Updater.Instance.progress)){
Progress = Updater.Instance.progress;
}
}
private void DialogOnClosed(ContentDialog sender, ContentDialogClosedEventArgs args){
dialog.Closed -= DialogOnClosed;
}
}

View File

@ -116,10 +116,26 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
Paused = epMeta.Paused || !isDownloading && !epMeta.Paused;
DoingWhat = epMeta.Paused ? "Paused" : Done ? "Done" : epMeta.DownloadProgress.Doing != string.Empty ? epMeta.DownloadProgress.Doing : "Waiting";
if (epMeta.Data != null) InfoText = "Dub: " + epMeta.Data.First().Lang?.CrLocale + " - " + GetSubtitleString();
if (epMeta.Data != null) InfoText = GetDubString() + " - " + GetSubtitleString();
Error = epMeta.DownloadProgress.Error;
}
private string GetDubString(){
var hardSubs = Crunchyroll.Instance.CrunOptions.Hslang != "none" ? "Hardsub: " + Crunchyroll.Instance.CrunOptions.Hslang : "";
if (hardSubs != string.Empty){
return hardSubs;
}
var dubs = "Dub: ";
if (epMeta.SelectedDubs != null)
foreach (var crunOptionsDlDub in epMeta.SelectedDubs){
dubs += crunOptionsDlDub + " ";
}
return dubs;
}
private string GetSubtitleString(){
var hardSubs = Crunchyroll.Instance.CrunOptions.Hslang != "none" ? "Hardsub: " + Crunchyroll.Instance.CrunOptions.Hslang : "";
@ -129,8 +145,11 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
var softSubs = "Softsub: ";
foreach (var crunOptionsDlSub in Crunchyroll.Instance.CrunOptions.DlSubs){
softSubs += crunOptionsDlSub + " ";
if (epMeta.AvailableSubs != null && epMeta.AvailableSubs.Contains(crunOptionsDlSub)){
softSubs += crunOptionsDlSub + " ";
}
}
return softSubs;
@ -146,6 +165,8 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
Paused = epMeta.Paused || !isDownloading && !epMeta.Paused;
DoingWhat = epMeta.Paused ? "Paused" : Done ? "Done" : epMeta.DownloadProgress.Doing != string.Empty ? epMeta.DownloadProgress.Doing : "Waiting";
if (epMeta.Data != null) InfoText = GetDubString() + " - " + GetSubtitleString();
Error = epMeta.DownloadProgress.Error;
if (PropertyChanged != null){
@ -155,6 +176,7 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DownloadSpeed)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DoingWhat)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Error)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(InfoText)));
}
}

View File

@ -31,16 +31,19 @@ public partial class SettingsPageViewModel : ViewModelBase{
private bool _downloadChapters = true;
[ObservableProperty]
private bool _muxToMp4 = false;
private bool _muxToMp4;
[ObservableProperty]
private bool _history = false;
private bool _history;
[ObservableProperty]
private bool _useNonDrmEndpoint = true;
[ObservableProperty]
private int _leadingNumbers = 0;
private int _leadingNumbers;
[ObservableProperty]
private int _simultaneousDownloads = 0;
private int _simultaneousDownloads;
[ObservableProperty]
private string _fileName = "";
@ -76,7 +79,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
private ObservableCollection<ListBoxItem> _selectedSubLang = new();
[ObservableProperty]
private bool _useCustomAccent = false;
private bool _useCustomAccent;
[ObservableProperty]
private Color _listBoxColor;
@ -174,7 +177,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
private readonly FluentAvaloniaTheme _faTheme;
private bool settingsLoaded = false;
private bool settingsLoaded;
public SettingsPageViewModel(){
var version = Assembly.GetExecutingAssembly().GetName().Version;
@ -190,7 +193,10 @@ public partial class SettingsPageViewModel : ViewModelBase{
}
CrDownloadOptions options = Crunchyroll.Instance.CrunOptions;
ComboBoxItem? hsLang = HardSubLangList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.Hslang) ?? null;
SelectedHSLang = hsLang ?? HardSubLangList[0];
var softSubLang = SubLangList.Where(a => options.DlSubs.Contains(a.Content)).ToList();
SelectedSubLang.Clear();
@ -198,21 +204,6 @@ public partial class SettingsPageViewModel : ViewModelBase{
SelectedSubLang.Add(listBoxItem);
}
if (SelectedSubLang.Count == 0){
SelectedSubs = "none";
} else{
SelectedSubs = SelectedSubLang[0].Content.ToString();
for (var i = 1; i < SelectedSubLang.Count; i++){
SelectedSubs += "," + SelectedSubLang[i].Content;
}
}
ComboBoxItem? hsLang = HardSubLangList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.Hslang) ?? null;
SelectedHSLang = hsLang ?? HardSubLangList[0];
// ComboBoxItem? dubLang = DubLangList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.DubLang[0]) ?? null;
// SelectedDubLang = dubLang ?? DubLangList[0];
var dubLang = DubLangList.Where(a => options.DubLang.Contains(a.Content)).ToList();
SelectedDubLang.Clear();
@ -220,16 +211,10 @@ public partial class SettingsPageViewModel : ViewModelBase{
SelectedDubLang.Add(listBoxItem);
}
if (SelectedDubLang.Count == 0){
SelectedDubs = "none";
} else{
SelectedDubs = SelectedDubLang[0].Content.ToString();
for (var i = 1; i < SelectedDubLang.Count; i++){
SelectedDubs += "," + SelectedDubLang[i].Content;
}
}
UpdateSubAndDubString();
UseNonDrmEndpoint = options.UseNonDrmStreams;
DownloadVideo = !options.Novids;
DownloadAudio = !options.Noaudio;
DownloadChapters = options.Chapters;
@ -266,23 +251,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
return;
}
if (SelectedSubLang.Count == 0){
SelectedSubs = "none";
} else{
SelectedSubs = SelectedSubLang[0].Content.ToString();
for (var i = 1; i < SelectedSubLang.Count; i++){
SelectedSubs += "," + SelectedSubLang[i].Content;
}
}
if (SelectedDubLang.Count == 0){
SelectedDubs = "none";
} else{
SelectedDubs = SelectedDubLang[0].Content.ToString();
for (var i = 1; i < SelectedDubLang.Count; i++){
SelectedDubs += "," + SelectedDubLang[i].Content;
}
}
UpdateSubAndDubString();
Crunchyroll.Instance.CrunOptions.Novids = !DownloadVideo;
Crunchyroll.Instance.CrunOptions.Noaudio = !DownloadAudio;
@ -314,7 +283,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
Crunchyroll.Instance.CrunOptions.SimultaneousDownloads = SimultaneousDownloads;
Crunchyroll.Instance.CrunOptions.UseNonDrmStreams = UseNonDrmEndpoint;
Crunchyroll.Instance.CrunOptions.QualityAudio = SelectedAudioQuality?.Content + "";
Crunchyroll.Instance.CrunOptions.QualityVideo = SelectedVideoQuality?.Content + "";
Crunchyroll.Instance.CrunOptions.Theme = CurrentAppTheme?.Content + "";
@ -330,6 +299,26 @@ public partial class SettingsPageViewModel : ViewModelBase{
// Console.WriteLine("Updated Settings");
}
private void UpdateSubAndDubString(){
if (SelectedSubLang.Count == 0){
SelectedSubs = "none";
} else{
SelectedSubs = SelectedSubLang[0].Content.ToString();
for (var i = 1; i < SelectedSubLang.Count; i++){
SelectedSubs += "," + SelectedSubLang[i].Content;
}
}
if (SelectedDubLang.Count == 0){
SelectedDubs = "none";
} else{
SelectedDubs = SelectedDubLang[0].Content.ToString();
for (var i = 1; i < SelectedDubLang.Count; i++){
SelectedDubs += "," + SelectedDubLang[i].Content;
}
}
}
partial void OnCurrentAppThemeChanged(ComboBoxItem? value){
if (value?.Content?.ToString() == "System"){
_faTheme.PreferSystemTheme = true;

View File

@ -58,8 +58,8 @@
<TextBlock Grid.Row="0" Grid.Column="0" MaxHeight="117" Text="{Binding Title}" FontWeight="Bold" FontSize="16"
TextWrapping="Wrap" VerticalAlignment="Top" />
<!-- <TextBlock Grid.Row="1" Grid.Column="0" MaxHeight="117" Text="{Binding InfoText}" Opacity="0.8" -->
<!-- TextWrapping="Wrap" VerticalAlignment="Center" /> -->
<TextBlock Grid.Row="1" Grid.Column="0" MaxHeight="117" Text="{Binding InfoText}" Opacity="0.8"
TextWrapping="Wrap" VerticalAlignment="Center" />
<Button Grid.Row="0" Grid.Column="1" Margin="0 0 5 0" IsVisible="{Binding !Error}" Command="{Binding ToggleIsDownloading}" FontStyle="Italic"
HorizontalAlignment="Right" VerticalAlignment="Top">

View File

@ -14,7 +14,7 @@
<Design.DataContext>
<vm:MainWindowViewModel />
</Design.DataContext>
<Grid>
<Grid Name="mainGrid">
<ContentControl x:Name="MainContent">
<Grid RowDefinitions="Auto, *">
@ -76,8 +76,9 @@
</Grid>
</ContentControl>
<!-- Your main window content -->
<!-- Tost Message -->
<views:ToastNotification x:Name="Toast" IsVisible="False" />
</Grid>

View File

@ -1,22 +1,13 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Reactive.Disposables;
using System.Reflection;
using System.Threading.Tasks;
using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using CRD.Downloader;
using Avalonia.Markup.Xaml;
using CRD.Utils.Updater;
using CRD.ViewModels;
using CRD.Views.Utils;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
using FluentAvalonia.UI.Windowing;
using Newtonsoft.Json;
using ReactiveUI;
namespace CRD.Views;
@ -25,9 +16,32 @@ public partial class MainWindow : AppWindow{
private Stack<object> navigationStack = new Stack<object>();
#region Singelton
private static MainWindow? _instance;
private static readonly object Padlock = new();
public static MainWindow Instance{
get{
if (_instance == null){
lock (Padlock){
if (_instance == null){
_instance = new MainWindow();
}
}
}
return _instance;
}
}
#endregion
private object selectedNavVieItem;
public MainWindow(){
AvaloniaXamlLoader.Load(this);
InitializeComponent();
TitleBar.ExtendsContentIntoTitleBar = true;
@ -38,7 +52,7 @@ public partial class MainWindow : AppWindow{
var nv = this.FindControl<NavigationView>("NavView");
nv.SelectedItem = nv.MenuItems.ElementAt(0);
selectedNavVieItem = nv.SelectedItem;
MessageBus.Current.Listen<NavigationMessage>()
.Subscribe(message => {
if (message.Refresh){
@ -61,23 +75,26 @@ public partial class MainWindow : AppWindow{
.Subscribe(message => ShowToast(message.Message, message.Type, message.Seconds));
}
public async void ShowError(string message){
var dialog = new ContentDialog(){
Title = "Error",
Content = message,
CloseButtonText = "Close"
};
public static void ShowError(string message){
var window = new ErrorWindow();
window.SetErrorMessage(message);
window.Show(); // 'this' is a reference to the parent window, if applicable
_ = await dialog.ShowAsync();
}
public void ShowToast(string message, ToastType type, int durationInSeconds = 5){
this.FindControl<ToastNotification>("Toast").Show(message, type, durationInSeconds);
}
private void NavView_SelectionChanged(object? sender, NavigationViewSelectionChangedEventArgs e){
if (sender is NavigationView navView){
var selectedItem = navView.SelectedItem as NavigationViewItem;
if (selectedItem != null){
switch (selectedItem.Tag){
case "DownloadQueue":
navView.Content = Activator.CreateInstance(typeof(DownloadsPageViewModel));
@ -107,6 +124,7 @@ public partial class MainWindow : AppWindow{
break;
case "UpdateAvailable":
Updater.Instance.DownloadAndUpdateAsync();
ShowUpdateDialog();
break;
default:
// (sender as NavigationView).Content = Activator.CreateInstance(typeof(DownloadsPageViewModel));
@ -116,7 +134,19 @@ public partial class MainWindow : AppWindow{
}
}
public async void ShowUpdateDialog(){
var dialog = new ContentDialog(){
Title = "Updating",
// CloseButtonText = "Close"
};
var viewModel = new ContentDialogUpdateViewModel(dialog);
dialog.Content = new ContentDialogUpdateView(){
DataContext = viewModel
};
_ = await dialog.ShowAsync();
}
}
public class ToastMessage(string message, ToastType type, int i){

View File

@ -166,6 +166,12 @@
<CheckBox IsChecked="{Binding DownloadChapters}"> </CheckBox>
</controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem>
<controls:SettingsExpanderItem Content="Check for Non-DRM streams">
<controls:SettingsExpanderItem.Footer>
<CheckBox IsChecked="{Binding UseNonDrmEndpoint}"> </CheckBox>
</controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem>
<controls:SettingsExpander.Footer>

View File

@ -0,0 +1,15 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="CRD.Views.Utils.ContentDialogUpdateView">
<StackPanel Spacing="10" MinWidth="400">
<ProgressBar Minimum="0" Maximum="100" Value="{Binding Progress}" HorizontalAlignment="Center" VerticalAlignment="Center" Width="350"/>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,11 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace CRD.Views.Utils;
public partial class ContentDialogUpdateView : UserControl{
public ContentDialogUpdateView(){
InitializeComponent();
}
}

View File

@ -1,22 +0,0 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="200"
x:Class="CRD.Views.Utils.ErrorWindow"
Icon="/Assets/app_icon.ico"
Title="Error" Width="300" Height="150">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Center" TextAlignment="Center" Margin="10" x:Name="ErrorMessage" Text="Error goes here" TextWrapping="Wrap" />
<Button Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Center" Content="Close" Click="Close_Click"/>
</Grid>
</Window>

View File

@ -1,23 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace CRD.Views.Utils;
public partial class ErrorWindow : Window{
public ErrorWindow(){
InitializeComponent();
}
private void Close_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e){
Close();
}
private void InitializeComponent(){
AvaloniaXamlLoader.Load(this);
}
public void SetErrorMessage(string message){
this.FindControl<TextBlock>("ErrorMessage").Text = message;
}
}