#nullable enable using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; using Wino.Core.Domain; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Accounts; using Wino.Core.Domain.Models.Navigation; using Wino.Core.ViewModels.Data; using Wino.Mail.Api.Contracts.Common; using Wino.Messaging.UI; namespace Wino.Core.ViewModels; public partial class WinoAccountManagementPageViewModel : CoreBaseViewModel, IRecipient, IRecipient, IRecipient { private readonly IWinoAccountProfileService _profileService; private readonly IWinoAddOnService _addOnService; private readonly IMailDialogService _dialogService; private readonly INativeAppService _nativeAppService; public ObservableCollection AddOns { get; } = []; [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] [NotifyCanExecuteChangedFor(nameof(PurchaseAddOnCommand))] public partial bool IsCheckoutInProgress { get; set; } public bool IsSignedOut => !IsSignedIn; public WinoAccountManagementPageViewModel(IWinoAccountProfileService profileService, IWinoAddOnService addOnService, IMailDialogService dialogService, INativeAppService nativeAppService) { _profileService = profileService; _addOnService = addOnService; _dialogService = dialogService; _nativeAppService = nativeAppService; } public override void OnNavigatedTo(NavigationMode mode, object parameters) { base.OnNavigatedTo(mode, parameters); _ = InitializeAsync(); } [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); } [RelayCommand] private async Task ChangePasswordAsync() { var account = await _profileService.GetActiveAccountAsync().ConfigureAwait(false); if (account == null) { _dialogService.InfoBarMessage(Translator.GeneralTitle_Warning, Translator.WinoAccount_SignOut_NoAccountMessage, InfoBarMessageType.Warning); return; } var shouldContinue = await _dialogService.ShowConfirmationDialogAsync( string.Format(Translator.WinoAccount_ChangePassword_ConfirmationMessage, account.Email), Translator.WinoAccount_ChangePassword_Title, Translator.WinoAccount_ChangePassword_Action).ConfigureAwait(false); if (!shouldContinue) { return; } var response = await _profileService.ForgotPasswordAsync(account.Email).ConfigureAwait(false); if (!response.IsSuccess) { _dialogService.InfoBarMessage(Translator.GeneralTitle_Error, TranslateForgotPasswordError(response.ErrorCode), InfoBarMessageType.Error); return; } _dialogService.InfoBarMessage(Translator.GeneralTitle_Info, string.Format(Translator.WinoAccount_ForgotPasswordDialog_SuccessMessage, account.Email), InfoBarMessageType.Success); } private static string TranslateForgotPasswordError(string? errorCode) => errorCode switch { ApiErrorCodes.EmailNotRegistered => Translator.WinoAccount_Error_EmailNotRegistered, ApiErrorCodes.ValidationFailed => Translator.WinoAccount_Error_ValidationFailed, _ when string.IsNullOrWhiteSpace(errorCode) => Translator.GeneralTitle_Error, _ => errorCode! }; [RelayCommand(CanExecute = nameof(CanPurchaseAddOn))] private async Task PurchaseAddOnAsync(WinoAddOnItemViewModel? addOn) { if (addOn == null) { return; } var account = await _profileService.GetAuthenticatedAccountAsync().ConfigureAwait(false); if (account == null) { _dialogService.InfoBarMessage(Translator.GeneralTitle_Warning, Translator.WinoAccount_Management_PurchaseRequiresSignIn, InfoBarMessageType.Warning); return; } await ExecuteUIThread(() => { IsCheckoutInProgress = true; addOn.IsPurchaseInProgress = true; }); try { var checkoutSession = await _profileService.CreateCheckoutSessionAsync(addOn.ProductType).ConfigureAwait(false); if (!checkoutSession.IsSuccess || string.IsNullOrWhiteSpace(checkoutSession.Result?.Url)) { _dialogService.InfoBarMessage(Translator.GeneralTitle_Error, Translator.WinoAccount_Management_PurchaseStartFailed, InfoBarMessageType.Error); return; } var isLaunched = await _nativeAppService.LaunchUriAsync(new Uri(checkoutSession.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); } finally { await ExecuteUIThread(() => { IsCheckoutInProgress = false; addOn.IsPurchaseInProgress = false; }); } } private bool CanPurchaseAddOn(WinoAddOnItemViewModel? addOn) => addOn != null && !addOn.IsPurchased && !IsCheckoutInProgress; [RelayCommand] 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); } } [RelayCommand] private Task ExportSettingsAsync() => Task.CompletedTask; [RelayCommand] private Task ImportSettingsAsync() => Task.CompletedTask; protected override void RegisterRecipients() { base.RegisterRecipients(); Messenger.Register(this); Messenger.Register(this); Messenger.Register(this); } protected override void UnregisterRecipients() { base.UnregisterRecipients(); Messenger.Unregister(this); Messenger.Unregister(this); Messenger.Unregister(this); } public void Receive(WinoAccountProfileUpdatedMessage message) => _ = LoadAsync(); public void Receive(WinoAccountProfileDeletedMessage message) => _ = LoadAsync(); public void Receive(WinoAccountAddOnPurchasedMessage message) => _ = HandleAddOnPurchasedAsync(); private async Task InitializeAsync() { await LoadAsync().ConfigureAwait(false); } private async Task LoadAsync() { try { var cachedAccount = await _profileService.GetActiveAccountAsync().ConfigureAwait(false); var cachedAddOns = await _addOnService.GetAvailableAddOnsAsync(true).ConfigureAwait(false); if (cachedAccount != null) { await ApplyAccountStateAsync(cachedAccount).ConfigureAwait(false); } if (cachedAddOns.Count > 0) { await UpdateAddOnsAsync(cachedAddOns).ConfigureAwait(false); } await ExecuteUIThread(() => IsBusy = cachedAccount == null && cachedAddOns.Count == 0); var account = await _profileService.GetAuthenticatedAccountAsync().ConfigureAwait(false); var refreshedProfileResult = account == null ? null : await _profileService.RefreshProfileAsync().ConfigureAwait(false); var addOns = await _addOnService.GetAvailableAddOnsAsync().ConfigureAwait(false); var resolvedAccount = refreshedProfileResult?.IsSuccess == true && refreshedProfileResult.Account != null ? refreshedProfileResult.Account : account; await ApplyAccountStateAsync(resolvedAccount).ConfigureAwait(false); await UpdateAddOnsAsync(addOns).ConfigureAwait(false); } catch (Exception) { _dialogService.InfoBarMessage(Translator.GeneralTitle_Error, Translator.WinoAccount_Management_LoadFailed, InfoBarMessageType.Error); await ResetStateAsync().ConfigureAwait(false); } finally { await ExecuteUIThread(() => IsBusy = false); } } private async Task ApplyAccountStateAsync(Wino.Core.Domain.Entities.Shared.WinoAccount? account) { await ExecuteUIThread(() => { IsSignedIn = account != null; AccountEmail = account?.Email ?? string.Empty; AccountStatusText = account == null ? string.Empty : string.Format(Translator.WinoAccount_Management_StatusLabel, account.AccountStatus); }); } private async Task HandleAddOnPurchasedAsync() { await LoadAsync().ConfigureAwait(false); _dialogService.InfoBarMessage(Translator.Info_PurchaseThankYouTitle, Translator.Info_PurchaseThankYouMessage, InfoBarMessageType.Success); } private async Task ResetStateAsync() { await ExecuteUIThread(() => { IsSignedIn = false; AccountEmail = string.Empty; AccountStatusText = string.Empty; IsCheckoutInProgress = false; AddOns.Clear(); PurchaseAddOnCommand.NotifyCanExecuteChanged(); }); } private async Task UpdateAddOnsAsync(IReadOnlyList addOns) { var items = addOns.Select(CreateAddOnItem).ToList(); await ExecuteUIThread(() => { AddOns.Clear(); foreach (var item in items) { AddOns.Add(item); } PurchaseAddOnCommand.NotifyCanExecuteChanged(); }); } private WinoAddOnItemViewModel CreateAddOnItem(WinoAddOnInfo addOn) { 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); } return item; } }