Files
Wino-Mail/Wino.Core.ViewModels/WinoAccountManagementPageViewModel.cs
T

344 lines
12 KiB
C#
Raw Normal View History

2026-03-18 09:00:26 +01:00
#nullable enable
using System;
2026-03-19 01:50:14 +01:00
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
2026-03-18 09:00:26 +01:00
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
2026-03-18 17:43:56 +01:00
using CommunityToolkit.Mvvm.Messaging;
2026-03-18 09:00:26 +01:00
using Wino.Core.Domain;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
2026-03-19 01:50:14 +01:00
using Wino.Core.Domain.Models.Accounts;
2026-03-18 09:00:26 +01:00
using Wino.Core.Domain.Models.Navigation;
2026-03-19 01:50:14 +01:00
using Wino.Core.ViewModels.Data;
2026-03-18 17:43:56 +01:00
using Wino.Messaging.UI;
2026-03-18 09:00:26 +01:00
namespace Wino.Core.ViewModels;
2026-03-18 17:43:56 +01:00
public partial class WinoAccountManagementPageViewModel : CoreBaseViewModel,
2026-03-19 10:26:17 +01:00
IRecipient<WinoAccountProfileUpdatedMessage>,
IRecipient<WinoAccountProfileDeletedMessage>,
IRecipient<WinoAccountAddOnPurchasedMessage>
2026-03-18 09:00:26 +01:00
{
private readonly IWinoAccountProfileService _profileService;
2026-03-19 01:50:14 +01:00
private readonly IWinoAddOnService _addOnService;
2026-03-18 09:00:26 +01:00
private readonly IMailDialogService _dialogService;
private readonly INativeAppService _nativeAppService;
2026-03-19 01:50:14 +01:00
public ObservableCollection<WinoAddOnItemViewModel> AddOns { get; } = [];
2026-03-18 09:00:26 +01:00
[ObservableProperty]
public partial bool IsBusy { get; set; }
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsSignedOut))]
public partial bool IsSignedIn { get; set; }
[ObservableProperty]
public partial string AccountEmail { get; set; } = string.Empty;
[ObservableProperty]
public partial string AccountStatusText { get; set; } = string.Empty;
[ObservableProperty]
2026-03-19 01:50:14 +01:00
[NotifyCanExecuteChangedFor(nameof(PurchaseAddOnCommand))]
public partial bool IsCheckoutInProgress { get; set; }
2026-03-18 17:43:56 +01:00
2026-03-18 09:00:26 +01:00
public bool IsSignedOut => !IsSignedIn;
public WinoAccountManagementPageViewModel(IWinoAccountProfileService profileService,
2026-03-19 01:50:14 +01:00
IWinoAddOnService addOnService,
2026-03-18 09:00:26 +01:00
IMailDialogService dialogService,
INativeAppService nativeAppService)
{
_profileService = profileService;
2026-03-19 01:50:14 +01:00
_addOnService = addOnService;
2026-03-18 09:00:26 +01:00
_dialogService = dialogService;
_nativeAppService = nativeAppService;
}
public override void OnNavigatedTo(NavigationMode mode, object parameters)
{
base.OnNavigatedTo(mode, parameters);
2026-03-19 10:26:17 +01:00
_ = InitializeAsync();
2026-03-18 09:00:26 +01:00
}
[RelayCommand]
private async Task RegisterAsync()
{
var account = await _dialogService.ShowWinoAccountRegistrationDialogAsync();
if (account == null)
{
return;
}
_dialogService.InfoBarMessage(Translator.GeneralTitle_Info,
string.Format(Translator.WinoAccount_RegisterSuccessMessage, account.Email),
InfoBarMessageType.Success);
}
[RelayCommand]
private async Task SignInAsync()
{
var account = await _dialogService.ShowWinoAccountLoginDialogAsync();
if (account == null)
{
return;
}
_dialogService.InfoBarMessage(Translator.GeneralTitle_Info,
string.Format(Translator.WinoAccount_LoginSuccessMessage, account.Email),
InfoBarMessageType.Success);
}
[RelayCommand]
private async Task SignOutAsync()
{
var account = await _profileService.GetActiveAccountAsync().ConfigureAwait(false);
if (account == null)
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Warning,
Translator.WinoAccount_SignOut_NoAccountMessage,
InfoBarMessageType.Warning);
return;
}
await _profileService.SignOutAsync().ConfigureAwait(false);
_dialogService.InfoBarMessage(Translator.GeneralTitle_Info,
string.Format(Translator.WinoAccount_SignOut_SuccessMessage, account.Email),
InfoBarMessageType.Success);
}
2026-03-19 01:50:14 +01:00
[RelayCommand(CanExecute = nameof(CanPurchaseAddOn))]
private async Task PurchaseAddOnAsync(WinoAddOnItemViewModel? addOn)
2026-03-18 09:00:26 +01:00
{
2026-03-19 01:50:14 +01:00
if (addOn == null)
2026-03-18 09:00:26 +01:00
{
2026-03-18 17:43:56 +01:00
return;
}
2026-03-18 09:00:26 +01:00
2026-03-18 17:43:56 +01:00
var account = await _profileService.GetAuthenticatedAccountAsync().ConfigureAwait(false);
if (account == null)
2026-03-18 09:00:26 +01:00
{
2026-03-18 17:43:56 +01:00
_dialogService.InfoBarMessage(Translator.GeneralTitle_Warning,
Translator.WinoAccount_Management_PurchaseRequiresSignIn,
InfoBarMessageType.Warning);
2026-03-18 09:00:26 +01:00
return;
}
2026-03-19 01:50:14 +01:00
await ExecuteUIThread(() =>
{
IsCheckoutInProgress = true;
addOn.IsPurchaseInProgress = true;
});
2026-03-18 17:43:56 +01:00
2026-03-18 09:00:26 +01:00
try
{
2026-03-19 01:50:14 +01:00
var checkoutSession = await _profileService.CreateCheckoutSessionAsync(addOn.ProductType).ConfigureAwait(false);
2026-03-18 17:43:56 +01:00
2026-03-19 01:50:14 +01:00
if (!checkoutSession.IsSuccess || string.IsNullOrWhiteSpace(checkoutSession.Result?.Url))
2026-03-18 09:00:26 +01:00
{
2026-03-18 17:43:56 +01:00
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error,
Translator.WinoAccount_Management_PurchaseStartFailed,
InfoBarMessageType.Error);
2026-03-18 09:00:26 +01:00
return;
}
2026-03-19 01:50:14 +01:00
var isLaunched = await _nativeAppService.LaunchUriAsync(new Uri(checkoutSession.Result.Url)).ConfigureAwait(false);
2026-03-18 17:43:56 +01:00
if (!isLaunched)
2026-03-18 09:00:26 +01:00
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error,
2026-03-18 17:43:56 +01:00
Translator.WinoAccount_Management_PurchaseStartFailed,
2026-03-18 09:00:26 +01:00
InfoBarMessageType.Error);
}
2026-03-18 17:43:56 +01:00
}
catch (OperationCanceledException)
{
2026-03-18 09:00:26 +01:00
}
catch (Exception)
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error,
2026-03-18 17:43:56 +01:00
Translator.WinoAccount_Management_PurchaseStartFailed,
2026-03-18 09:00:26 +01:00
InfoBarMessageType.Error);
}
2026-03-18 17:43:56 +01:00
finally
{
2026-03-19 01:50:14 +01:00
await ExecuteUIThread(() =>
{
IsCheckoutInProgress = false;
addOn.IsPurchaseInProgress = false;
});
2026-03-18 17:43:56 +01:00
}
2026-03-18 09:00:26 +01:00
}
2026-03-19 01:50:14 +01:00
private bool CanPurchaseAddOn(WinoAddOnItemViewModel? addOn)
=> addOn != null && !addOn.IsPurchased && !IsCheckoutInProgress;
2026-03-18 09:00:26 +01:00
[RelayCommand]
2026-03-19 01:50:14 +01:00
private async Task ManageAiPackAsync()
{
try
{
var portalSession = await _profileService.CreateCustomerPortalSessionAsync().ConfigureAwait(false);
if (!portalSession.IsSuccess || string.IsNullOrWhiteSpace(portalSession.Result?.Url))
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error,
Translator.WinoAccount_Management_PurchaseStartFailed,
InfoBarMessageType.Error);
return;
}
var isLaunched = await _nativeAppService.LaunchUriAsync(new Uri(portalSession.Result.Url)).ConfigureAwait(false);
if (!isLaunched)
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error,
Translator.WinoAccount_Management_PurchaseStartFailed,
InfoBarMessageType.Error);
}
}
catch (OperationCanceledException)
{
}
catch (Exception)
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error,
Translator.WinoAccount_Management_PurchaseStartFailed,
InfoBarMessageType.Error);
}
}
2026-03-18 09:00:26 +01:00
2026-03-18 17:43:56 +01:00
[RelayCommand]
private Task ExportSettingsAsync() => Task.CompletedTask;
2026-03-18 09:00:26 +01:00
2026-03-18 17:43:56 +01:00
[RelayCommand]
private Task ImportSettingsAsync() => Task.CompletedTask;
2026-03-18 09:00:26 +01:00
2026-03-18 17:43:56 +01:00
protected override void RegisterRecipients()
{
base.RegisterRecipients();
2026-03-18 09:00:26 +01:00
2026-03-19 10:26:17 +01:00
Messenger.Register<WinoAccountProfileUpdatedMessage>(this);
Messenger.Register<WinoAccountProfileDeletedMessage>(this);
Messenger.Register<WinoAccountAddOnPurchasedMessage>(this);
2026-03-18 17:43:56 +01:00
}
2026-03-18 09:00:26 +01:00
2026-03-18 17:43:56 +01:00
protected override void UnregisterRecipients()
{
base.UnregisterRecipients();
2026-03-18 09:00:26 +01:00
2026-03-19 10:26:17 +01:00
Messenger.Unregister<WinoAccountProfileUpdatedMessage>(this);
Messenger.Unregister<WinoAccountProfileDeletedMessage>(this);
Messenger.Unregister<WinoAccountAddOnPurchasedMessage>(this);
2026-03-18 09:00:26 +01:00
}
2026-03-19 10:26:17 +01:00
public void Receive(WinoAccountProfileUpdatedMessage message)
2026-03-18 17:43:56 +01:00
=> _ = LoadAsync();
2026-03-19 10:26:17 +01:00
public void Receive(WinoAccountProfileDeletedMessage message)
2026-03-19 01:50:14 +01:00
=> _ = LoadAsync();
2026-03-18 17:43:56 +01:00
2026-03-19 10:26:17 +01:00
public void Receive(WinoAccountAddOnPurchasedMessage message)
=> _ = HandleAddOnPurchasedAsync();
private async Task InitializeAsync()
{
await LoadAsync().ConfigureAwait(false);
}
2026-03-18 09:00:26 +01:00
private async Task LoadAsync()
{
await ExecuteUIThread(() => IsBusy = true);
try
{
2026-03-18 17:43:56 +01:00
var account = await _profileService.GetAuthenticatedAccountAsync().ConfigureAwait(false);
2026-03-19 01:50:14 +01:00
var addOns = await _addOnService.GetAvailableAddOnsAsync().ConfigureAwait(false);
2026-03-18 09:00:26 +01:00
await ExecuteUIThread(() =>
{
2026-03-19 01:50:14 +01:00
IsSignedIn = account != null;
AccountEmail = account?.Email ?? string.Empty;
AccountStatusText = account == null
? string.Empty
: string.Format(Translator.WinoAccount_Management_StatusLabel, account.AccountStatus);
2026-03-18 09:00:26 +01:00
});
2026-03-19 01:50:14 +01:00
await UpdateAddOnsAsync(addOns).ConfigureAwait(false);
2026-03-18 09:00:26 +01:00
}
catch (Exception)
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error,
Translator.WinoAccount_Management_LoadFailed,
InfoBarMessageType.Error);
2026-03-19 01:50:14 +01:00
await ResetStateAsync().ConfigureAwait(false);
2026-03-18 09:00:26 +01:00
}
finally
{
await ExecuteUIThread(() => IsBusy = false);
}
}
2026-03-19 10:26:17 +01:00
private async Task HandleAddOnPurchasedAsync()
{
await LoadAsync().ConfigureAwait(false);
_dialogService.InfoBarMessage(Translator.Info_PurchaseThankYouTitle,
Translator.Info_PurchaseThankYouMessage,
InfoBarMessageType.Success);
}
2026-03-19 01:50:14 +01:00
private async Task ResetStateAsync()
2026-03-18 09:00:26 +01:00
{
await ExecuteUIThread(() =>
{
IsSignedIn = false;
AccountEmail = string.Empty;
AccountStatusText = string.Empty;
2026-03-19 01:50:14 +01:00
IsCheckoutInProgress = false;
AddOns.Clear();
PurchaseAddOnCommand.NotifyCanExecuteChanged();
2026-03-18 09:00:26 +01:00
});
}
2026-03-19 01:50:14 +01:00
private async Task UpdateAddOnsAsync(IReadOnlyList<WinoAddOnInfo> addOns)
2026-03-18 09:00:26 +01:00
{
2026-03-19 01:50:14 +01:00
var items = addOns.Select(CreateAddOnItem).ToList();
2026-03-18 09:00:26 +01:00
2026-03-19 01:50:14 +01:00
await ExecuteUIThread(() =>
2026-03-18 09:00:26 +01:00
{
2026-03-19 01:50:14 +01:00
AddOns.Clear();
2026-03-18 09:00:26 +01:00
2026-03-19 01:50:14 +01:00
foreach (var item in items)
{
AddOns.Add(item);
}
PurchaseAddOnCommand.NotifyCanExecuteChanged();
2026-03-18 09:00:26 +01:00
});
}
2026-03-19 01:50:14 +01:00
private WinoAddOnItemViewModel CreateAddOnItem(WinoAddOnInfo addOn)
2026-03-18 10:25:07 +01:00
{
2026-03-19 01:50:14 +01:00
var item = new WinoAddOnItemViewModel(addOn.ProductType)
{
IsPurchased = addOn.IsPurchased,
PurchaseCommand = PurchaseAddOnCommand,
ManageCommand = ManageAiPackCommand,
UsageCount = addOn.UsageCount ?? 0,
UsageLimit = addOn.UsageLimit is > 0 ? addOn.UsageLimit.Value : 1,
UsagePercentage = addOn.UsagePercentage
};
if (addOn.RenewalDateUtc is DateTimeOffset renewalDateUtc)
{
item.RenewalText = string.Format(Translator.WinoAccount_Management_AiPackRenews, renewalDateUtc.LocalDateTime);
item.UsageResetText = string.Format(Translator.WinoAccount_Management_AiPackResets, renewalDateUtc.LocalDateTime);
}
2026-03-18 10:25:07 +01:00
2026-03-19 01:50:14 +01:00
return item;
2026-03-18 10:25:07 +01:00
}
2026-03-18 09:00:26 +01:00
}