Crunchy-Downloader/CRD/ViewModels/AccountPageViewModel.cs
Elwador ff3e28093e - Added notification service for webhooks
- Added retry delay for rate limit handling
- Added toggle to control whether auto refresh also adds missing episodes to the queue
- Added configurable delay after each dub download
- Changed encoding preset dialog to show a preview of the FFmpeg command
- Changed play sound on queue empty and execute file on completion to be handled by the notification service
- Changed shutdown PC option to disable once triggered
- Fixed crash with queue persistence
- Fixed crash with audio player
- Fixed subscription countdown on the account page
2026-05-14 21:49:57 +02:00

210 lines
7.6 KiB
C#

using System;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CRD.Downloader.Crunchyroll;
using CRD.Utils;
using CRD.Utils.Structs;
using CRD.Utils.UI;
using CRD.ViewModels.Utils;
using CRD.Views.Utils;
using FluentAvalonia.UI.Controls;
namespace CRD.ViewModels;
public partial class AccountPageViewModel : ViewModelBase, IDisposable{
[ObservableProperty]
private Bitmap? _profileImage;
[ObservableProperty]
private string _profileName = "";
[ObservableProperty]
private string _loginLogoutText = "";
[ObservableProperty]
private bool _hasMultiProfile;
[ObservableProperty]
private string _remainingTime = "";
private static AccountPageViewModel? _activeInstance;
private readonly DispatcherTimer _timer;
private DateTime _targetTime;
private bool IsCancelled;
private bool UnknownEndDate;
private bool EndedButMaybeActive;
public AccountPageViewModel(){
_activeInstance?.StopSubscriptionTimer();
_activeInstance = this;
_timer = new DispatcherTimer{
Interval = TimeSpan.FromSeconds(1)
};
_timer.Tick += Timer_Tick;
UpdatetProfile();
}
private void Timer_Tick(object? sender, EventArgs e){
var remaining = _targetTime - DateTime.Now;
if (remaining <= TimeSpan.Zero){
RemainingTime = "No active Subscription";
_timer.Stop();
if (UnknownEndDate){
RemainingTime = "Unknown Subscription end date";
}
if (EndedButMaybeActive){
RemainingTime = "Subscription maybe ended";
}
} else{
RemainingTime = $"{(IsCancelled ? "Subscription ending in: " : "Subscription refreshing in: ")}{remaining:dd\\:hh\\:mm\\:ss}";
}
}
public void UpdatetProfile(){
StopSubscriptionTimer();
IsCancelled = false;
UnknownEndDate = false;
EndedButMaybeActive = false;
RemainingTime = "No active Subscription";
var firstEndpoint = CrunchyrollManager.Instance.CrAuthEndpoint1;
var firstEndpointProfile = firstEndpoint.Profile;
var isLoggedIn = firstEndpointProfile.Username != "???";
HasMultiProfile = isLoggedIn && firstEndpoint.MultiProfile.Profiles.Count > 1;
ProfileName = firstEndpointProfile.ProfileName ?? firstEndpointProfile.Username ?? "???"; // Default or fetched user name
LoginLogoutText = isLoggedIn ? "Logout" : "Login"; // Default state
LoadProfileImage("https://static.crunchyroll.com/assets/avatar/170x170/" +
(string.IsNullOrEmpty(firstEndpointProfile.Avatar) ? "crbrand_avatars_logo_marks_mangagirl_taupe.png" : firstEndpointProfile.Avatar));
var subscriptions = CrunchyrollManager.Instance.CrAuthEndpoint1.Subscription;
if (subscriptions != null && HasSubscriptionData(subscriptions)){
if (subscriptions.SubscriptionProducts is{ Count: >= 1 }){
var sub = subscriptions.SubscriptionProducts.First();
IsCancelled = sub.IsCancelled;
EndedButMaybeActive = !subscriptions.IsActive;
} else if (subscriptions.ThirdPartySubscriptionProducts is{ Count: >= 1 }){
var sub = subscriptions.ThirdPartySubscriptionProducts.First();
IsCancelled = !sub.AutoRenew;
EndedButMaybeActive = !subscriptions.IsActive;
} else if (subscriptions.NonrecurringSubscriptionProducts is{ Count: >= 1 }){
IsCancelled = true;
EndedButMaybeActive = !subscriptions.IsActive;
} else if (subscriptions.FunimationSubscriptions is{ Count: >= 1 }){
IsCancelled = true;
UnknownEndDate = true;
}
if (!UnknownEndDate){
_targetTime = subscriptions.NextRenewalDate;
_timer.Start();
Timer_Tick(null, EventArgs.Empty);
}
}
if (UnknownEndDate){
RemainingTime = "Unknown Subscription end date";
}
if (EndedButMaybeActive){
RemainingTime = "Subscription maybe ended";
}
}
private static bool HasSubscriptionData(CRD.Utils.Structs.Crunchyroll.Subscription subscriptions){
return subscriptions.SubscriptionProducts is{ Count: > 0 } ||
subscriptions.ThirdPartySubscriptionProducts is{ Count: > 0 } ||
subscriptions.NonrecurringSubscriptionProducts is{ Count: > 0 } ||
subscriptions.FunimationSubscriptions is{ Count: > 0 };
}
private void StopSubscriptionTimer(){
_timer.Stop();
}
public void Dispose(){
_timer.Tick -= Timer_Tick;
StopSubscriptionTimer();
if (ReferenceEquals(_activeInstance, this)){
_activeInstance = null;
}
}
[RelayCommand]
public async Task Button_Press(){
if (LoginLogoutText == "Login"){
var dialog = new ContentDialog(){
Title = "Login",
PrimaryButtonText = "Login",
CloseButtonText = "Close"
};
var viewModel = new Utils.ContentDialogInputLoginViewModel(dialog, this);
dialog.Content = new ContentDialogInputLoginView(){
DataContext = viewModel
};
_ = await dialog.ShowAsync();
} else{
await CrunchyrollManager.Instance.CrAuthEndpoint1.AuthAnonymous();
await CrunchyrollManager.Instance.CrAuthEndpoint2.AuthAnonymous();
UpdatetProfile();
}
}
[RelayCommand]
public async Task OpenMultiProfileDialog(){
var multiProfile = CrunchyrollManager.Instance.CrAuthEndpoint1.MultiProfile;
var profiels = multiProfile.Profiles.Select(multiProfileProfile => new AccountProfile{
AvatarUrl = string.IsNullOrEmpty(multiProfileProfile.Avatar) ? "" : ("https://static.crunchyroll.com/assets/avatar/170x170/" + multiProfileProfile.Avatar),
ProfileName = multiProfileProfile.Username ?? multiProfileProfile.ProfileName ?? "???", CanBeSelected = multiProfileProfile is{ IsSelected: false, CanSwitch: true, IsPinProtected: false },
ProfileId = multiProfileProfile.ProfileId,
}).ToList();
var dialog = new CustomContentDialog(){
Name = "CRD Select Profile",
Title = "Select Profile",
IsPrimaryButtonEnabled = false,
CloseButtonText = "Close",
FullSizeDesired = true,
};
var viewModel = new ContentDialogMultiProfileSelectViewModel(dialog, profiels);
dialog.Content = new ContentDialogMultiProfileSelectView(){
DataContext = viewModel
};
var dialogResult = await dialog.ShowAsync();
if (dialogResult == ContentDialogResult.Primary){
var selectedProfile = viewModel.SelectedItem;
await CrunchyrollManager.Instance.CrAuthEndpoint1.ChangeProfile(selectedProfile.ProfileId ?? string.Empty);
await CrunchyrollManager.Instance.CrAuthEndpoint2.ChangeProfile(selectedProfile.ProfileId ?? string.Empty);
UpdatetProfile();
}
}
public async void LoadProfileImage(string imageUrl){
try{
ProfileImage = await Helpers.LoadImage(imageUrl);
} catch (Exception ex){
// Handle exceptions
Console.Error.WriteLine("Failed to load image: " + ex.Message);
}
}
}