feat: create cloud-free Wino Mail fork
This commit is contained in:
@@ -41,7 +41,6 @@ public enum WinoPage
|
||||
EmailTemplatesPage,
|
||||
CreateEmailTemplatePage,
|
||||
StoragePage,
|
||||
WinoAccountManagementPage,
|
||||
WelcomePageV2,
|
||||
WelcomeHostPage,
|
||||
ProviderSelectionPage,
|
||||
|
||||
@@ -87,9 +87,4 @@ public interface IMailDialogService : IDialogServiceBase
|
||||
/// <returns>Contact information. Null if canceled.</returns>
|
||||
Task<AccountContact?> ShowEditContactDialogAsync(AccountContact? contact = null);
|
||||
|
||||
Task<WinoAccount?> ShowWinoAccountRegistrationDialogAsync();
|
||||
|
||||
Task<WinoAccount?> ShowWinoAccountLoginDialogAsync();
|
||||
|
||||
Task<WinoAccountSyncExportResult?> ShowWinoAccountExportDialogAsync();
|
||||
}
|
||||
|
||||
@@ -70,12 +70,10 @@ public interface IPreferencesService : INotifyPropertyChanged
|
||||
/// <summary>
|
||||
/// Setting: Whether the Wino account profile button in the shell title bar should be hidden.
|
||||
/// </summary>
|
||||
bool IsWinoAccountButtonHidden { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Setting: Whether AI actions panels and their toggle buttons should be hidden.
|
||||
/// </summary>
|
||||
bool IsAiActionsPanelHidden { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Setting: Default target language code used for AI translation actions.
|
||||
|
||||
@@ -36,11 +36,6 @@ public static class SettingsNavigationInfoProvider
|
||||
manageAccountsDescription,
|
||||
"\uE77B",
|
||||
searchKeywords: Translator.SettingsSearch_ManageAccounts_Keywords),
|
||||
new(WinoPage.WinoAccountManagementPage,
|
||||
Translator.WinoAccount_SettingsSection_Title,
|
||||
Translator.WinoAccount_SettingsSection_Description,
|
||||
"\uE77B",
|
||||
searchKeywords: string.Empty),
|
||||
new(null, Translator.SettingsOptions_GeneralSection, string.Empty, "\uE713", isSeparator: true),
|
||||
new(WinoPage.AppPreferencesPage,
|
||||
Translator.SettingsAppPreferences_Title,
|
||||
@@ -152,7 +147,6 @@ public static class SettingsNavigationInfoProvider
|
||||
WinoPage.SettingOptionsPage => Translator.MenuSettings,
|
||||
WinoPage.ManageAccountsPage => Translator.SettingsManageAccountSettings_Title,
|
||||
WinoPage.AccountManagementPage => Translator.SettingsManageAccountSettings_Title,
|
||||
WinoPage.WinoAccountManagementPage => Translator.WinoAccount_SettingsSection_Title,
|
||||
WinoPage.PersonalizationPage => Translator.SettingsPersonalization_Title,
|
||||
WinoPage.AboutPage => Translator.SettingsAbout_Title,
|
||||
WinoPage.MessageListPage => Translator.SettingsMessageList_Title,
|
||||
|
||||
@@ -1,632 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
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<WinoAccountProfileUpdatedMessage>,
|
||||
IRecipient<WinoAccountProfileDeletedMessage>,
|
||||
IRecipient<WinoAccountAddOnPurchasedMessage>
|
||||
{
|
||||
private readonly IWinoAccountProfileService _profileService;
|
||||
private readonly IWinoAccountDataSyncService _syncService;
|
||||
private readonly IMailDialogService _dialogService;
|
||||
private readonly IStoreManagementService _storeManagementService;
|
||||
private readonly WinoAddOnItemViewModel _aiPackAddOn;
|
||||
private readonly WinoAddOnItemViewModel _unlimitedAccountsAddOn;
|
||||
|
||||
public ObservableCollection<WinoAddOnItemViewModel> 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,
|
||||
IWinoAccountDataSyncService syncService,
|
||||
IMailDialogService dialogService,
|
||||
IStoreManagementService storeManagementService)
|
||||
{
|
||||
_profileService = profileService;
|
||||
_syncService = syncService;
|
||||
_dialogService = dialogService;
|
||||
_storeManagementService = storeManagementService;
|
||||
|
||||
_aiPackAddOn = CreateAddOnItem(WinoAddOnProductType.AI_PACK);
|
||||
_unlimitedAccountsAddOn = CreateAddOnItem(WinoAddOnProductType.UNLIMITED_ACCOUNTS);
|
||||
AddOns.Add(_aiPackAddOn);
|
||||
AddOns.Add(_unlimitedAccountsAddOn);
|
||||
}
|
||||
|
||||
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();
|
||||
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);
|
||||
|
||||
if (!shouldContinue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var response = await _profileService.ForgotPasswordAsync(account.Email);
|
||||
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;
|
||||
}
|
||||
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
IsCheckoutInProgress = true;
|
||||
addOn.IsPurchaseInProgress = true;
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
var purchaseResult = await _storeManagementService.PurchaseAsync(addOn.ProductType);
|
||||
|
||||
if (purchaseResult == StorePurchaseResult.NotPurchased)
|
||||
{
|
||||
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error,
|
||||
Translator.WinoAccount_Management_PurchaseStartFailed,
|
||||
InfoBarMessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var syncResult = await _profileService.SyncStoreEntitlementsAsync().ConfigureAwait(false);
|
||||
if (!syncResult.IsSuccess && !string.Equals(syncResult.ErrorCode, "MissingAccessToken", StringComparison.Ordinal))
|
||||
{
|
||||
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error,
|
||||
TranslateStoreSyncError(syncResult.ErrorCode),
|
||||
InfoBarMessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
await HandleAddOnPurchasedAsync().ConfigureAwait(false);
|
||||
}
|
||||
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 && !addOn.IsLoading && !IsCheckoutInProgress;
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ExportSettingsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _dialogService.ShowWinoAccountExportDialogAsync().ConfigureAwait(false);
|
||||
if (result == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_dialogService.InfoBarMessage(
|
||||
Translator.GeneralTitle_Info,
|
||||
BuildExportSuccessMessage(result),
|
||||
InfoBarMessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_dialogService.InfoBarMessage(
|
||||
Translator.GeneralTitle_Error,
|
||||
ex.Message,
|
||||
InfoBarMessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ImportSettingsAsync()
|
||||
{
|
||||
await ExecuteUIThread(() => IsBusy = true);
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _syncService.ImportAsync(new WinoAccountSyncSelection());
|
||||
|
||||
if (!result.HasAnyRemoteData)
|
||||
{
|
||||
_dialogService.InfoBarMessage(
|
||||
Translator.GeneralTitle_Info,
|
||||
Translator.WinoAccount_Management_NoRemoteSettings,
|
||||
InfoBarMessageType.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
var messageType = result.FailedPreferenceCount > 0
|
||||
? InfoBarMessageType.Warning
|
||||
: InfoBarMessageType.Success;
|
||||
|
||||
_dialogService.InfoBarMessage(
|
||||
result.FailedPreferenceCount > 0 ? Translator.GeneralTitle_Warning : Translator.GeneralTitle_Info,
|
||||
BuildImportMessage(result),
|
||||
messageType);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_dialogService.InfoBarMessage(
|
||||
Translator.GeneralTitle_Error,
|
||||
ex.Message,
|
||||
InfoBarMessageType.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await ExecuteUIThread(() => IsBusy = false);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RegisterRecipients()
|
||||
{
|
||||
base.RegisterRecipients();
|
||||
|
||||
Messenger.Register<WinoAccountProfileUpdatedMessage>(this);
|
||||
Messenger.Register<WinoAccountProfileDeletedMessage>(this);
|
||||
Messenger.Register<WinoAccountAddOnPurchasedMessage>(this);
|
||||
}
|
||||
|
||||
protected override void UnregisterRecipients()
|
||||
{
|
||||
base.UnregisterRecipients();
|
||||
|
||||
Messenger.Unregister<WinoAccountProfileUpdatedMessage>(this);
|
||||
Messenger.Unregister<WinoAccountProfileDeletedMessage>(this);
|
||||
Messenger.Unregister<WinoAccountAddOnPurchasedMessage>(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()
|
||||
{
|
||||
WinoAccount? cachedAccount = null;
|
||||
|
||||
try
|
||||
{
|
||||
cachedAccount = await _profileService.GetActiveAccountAsync().ConfigureAwait(false);
|
||||
|
||||
if (cachedAccount != null)
|
||||
{
|
||||
await ApplyAccountStateAsync(cachedAccount).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await ExecuteUIThread(() => IsBusy = true);
|
||||
await ResetAddOnStatesAsync().ConfigureAwait(false);
|
||||
var loadAiPackTask = LoadAiPackAddOnAsync();
|
||||
var loadUnlimitedAccountsTask = LoadUnlimitedAccountsAddOnAsync();
|
||||
|
||||
var resolvedAccount = cachedAccount;
|
||||
|
||||
if (cachedAccount == null || IsAccessTokenExpired(cachedAccount))
|
||||
{
|
||||
try
|
||||
{
|
||||
var account = await _profileService.GetAuthenticatedAccountAsync().ConfigureAwait(false);
|
||||
if (account != null)
|
||||
{
|
||||
resolvedAccount = account;
|
||||
|
||||
var refreshedProfileResult = await _profileService.RefreshProfileAsync().ConfigureAwait(false);
|
||||
if (refreshedProfileResult.IsSuccess && refreshedProfileResult.Account != null)
|
||||
{
|
||||
resolvedAccount = refreshedProfileResult.Account;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
resolvedAccount ??= cachedAccount;
|
||||
}
|
||||
}
|
||||
|
||||
await ApplyAccountStateAsync(resolvedAccount).ConfigureAwait(false);
|
||||
await Task.WhenAll(loadAiPackTask, loadUnlimitedAccountsTask).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (cachedAccount == null)
|
||||
{
|
||||
_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;
|
||||
PurchaseAddOnCommand.NotifyCanExecuteChanged();
|
||||
});
|
||||
|
||||
await ResetAddOnStatesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private WinoAddOnItemViewModel CreateAddOnItem(WinoAddOnProductType productType)
|
||||
{
|
||||
return new WinoAddOnItemViewModel(productType)
|
||||
{
|
||||
PurchaseCommand = PurchaseAddOnCommand,
|
||||
UsageLimit = 1
|
||||
};
|
||||
}
|
||||
|
||||
private async Task ResetAddOnStatesAsync()
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
ResetAddOnItem(_aiPackAddOn);
|
||||
ResetAddOnItem(_unlimitedAccountsAddOn);
|
||||
PurchaseAddOnCommand.NotifyCanExecuteChanged();
|
||||
});
|
||||
}
|
||||
|
||||
private static void ResetAddOnItem(WinoAddOnItemViewModel addOn)
|
||||
{
|
||||
addOn.IsLoading = true;
|
||||
addOn.IsPurchased = false;
|
||||
addOn.IsPurchaseInProgress = false;
|
||||
addOn.HasUsageData = false;
|
||||
addOn.ErrorText = string.Empty;
|
||||
addOn.UsageCount = 0;
|
||||
addOn.UsageLimit = 1;
|
||||
addOn.UsagePercentage = 0;
|
||||
addOn.RenewalText = string.Empty;
|
||||
addOn.UsageResetText = string.Empty;
|
||||
}
|
||||
|
||||
private static string TranslateStoreSyncError(string? errorCode)
|
||||
=> errorCode switch
|
||||
{
|
||||
_ => Translator.WinoAccount_Management_StoreSyncFailed
|
||||
};
|
||||
|
||||
private static string BuildExportSuccessMessage(WinoAccountSyncExportResult result)
|
||||
{
|
||||
var parts = new Collection<string>();
|
||||
|
||||
if (result.IncludedPreferences)
|
||||
{
|
||||
parts.Add(Translator.WinoAccount_Management_ExportPreferencesSucceeded);
|
||||
}
|
||||
|
||||
if (result.IncludedAccounts)
|
||||
{
|
||||
parts.Add(string.Format(Translator.WinoAccount_Management_ExportAccountsSucceeded, result.ExportedMailboxCount));
|
||||
}
|
||||
|
||||
if (parts.Count == 0)
|
||||
{
|
||||
parts.Add(Translator.WinoAccount_Management_ExportSucceeded);
|
||||
}
|
||||
|
||||
return string.Join(" ", parts);
|
||||
}
|
||||
|
||||
private static string BuildImportMessage(WinoAccountSyncImportResult result)
|
||||
{
|
||||
var parts = new Collection<string>();
|
||||
|
||||
if (result.HadRemotePreferences)
|
||||
{
|
||||
parts.Add(result.FailedPreferenceCount > 0
|
||||
? string.Format(Translator.WinoAccount_Management_ImportPartial, result.AppliedPreferenceCount, result.FailedPreferenceCount)
|
||||
: string.Format(Translator.WinoAccount_Management_ImportPreferencesSucceeded, result.AppliedPreferenceCount));
|
||||
}
|
||||
|
||||
if (result.ImportedMailboxCount > 0)
|
||||
{
|
||||
parts.Add(string.Format(Translator.WinoAccount_Management_ImportAccountsSucceeded, result.ImportedMailboxCount));
|
||||
}
|
||||
|
||||
if (result.SkippedDuplicateMailboxCount > 0)
|
||||
{
|
||||
parts.Add(string.Format(Translator.WinoAccount_Management_ImportDuplicateAccountsSkipped, result.SkippedDuplicateMailboxCount));
|
||||
}
|
||||
|
||||
if (parts.Count == 0)
|
||||
{
|
||||
parts.Add(Translator.WinoAccount_Management_ImportEmpty);
|
||||
}
|
||||
|
||||
if (result.ImportedMailboxCount > 0)
|
||||
{
|
||||
parts.Add(Translator.WinoAccount_Management_ImportReloginReminder);
|
||||
}
|
||||
|
||||
return string.Join(" ", parts);
|
||||
}
|
||||
|
||||
private static bool IsAccessTokenExpired(WinoAccount account)
|
||||
=> string.IsNullOrWhiteSpace(account.AccessToken) || account.AccessTokenExpiresAtUtc <= DateTime.UtcNow;
|
||||
|
||||
private async Task LoadUnlimitedAccountsAddOnAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var hasUnlimitedAccounts = await _storeManagementService.HasProductAsync(WinoAddOnProductType.UNLIMITED_ACCOUNTS).ConfigureAwait(false);
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
_unlimitedAccountsAddOn.IsPurchased = hasUnlimitedAccounts;
|
||||
_unlimitedAccountsAddOn.ErrorText = string.Empty;
|
||||
});
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
_unlimitedAccountsAddOn.ErrorText = Translator.WinoAccount_Management_AddOnLoadFailed;
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
_unlimitedAccountsAddOn.IsLoading = false;
|
||||
PurchaseAddOnCommand.NotifyCanExecuteChanged();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadAiPackAddOnAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var hasAiPack = await _storeManagementService.HasProductAsync(WinoAddOnProductType.AI_PACK).ConfigureAwait(false);
|
||||
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
_aiPackAddOn.IsPurchased = hasAiPack;
|
||||
_aiPackAddOn.ErrorText = string.Empty;
|
||||
});
|
||||
|
||||
if (!hasAiPack)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var aiStatusResponse = await _profileService.GetAiStatusAsync().ConfigureAwait(false);
|
||||
if (!aiStatusResponse.IsSuccess || aiStatusResponse.Result == null)
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
_aiPackAddOn.HasUsageData = false;
|
||||
_aiPackAddOn.ErrorText = Translator.WinoAccount_Management_AiPackUsageLoadFailed;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var aiStatus = aiStatusResponse.Result;
|
||||
if (IsExpiredAiEntitlement(aiStatus.EntitlementStatus))
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
_aiPackAddOn.IsPurchased = false;
|
||||
_aiPackAddOn.HasUsageData = false;
|
||||
_aiPackAddOn.ErrorText = string.Empty;
|
||||
_aiPackAddOn.RenewalText = string.Empty;
|
||||
_aiPackAddOn.UsageResetText = string.Empty;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (aiStatus.MonthlyLimit is not int usageLimit || usageLimit <= 0 || aiStatus.Used is not int usageCount)
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
_aiPackAddOn.HasUsageData = false;
|
||||
_aiPackAddOn.ErrorText = Translator.WinoAccount_Management_AiPackUsageLoadFailed;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
_aiPackAddOn.HasUsageData = true;
|
||||
_aiPackAddOn.ErrorText = string.Empty;
|
||||
_aiPackAddOn.UsageCount = usageCount;
|
||||
_aiPackAddOn.UsageLimit = usageLimit;
|
||||
_aiPackAddOn.UsagePercentage = usageLimit > 0 ? (double)usageCount / usageLimit * 100 : 0;
|
||||
_aiPackAddOn.RenewalText = aiStatus.CurrentPeriodEndUtc is DateTimeOffset renewalDateUtc
|
||||
? string.Format(Translator.WinoAccount_Management_AiPackRenews, renewalDateUtc.LocalDateTime)
|
||||
: string.Empty;
|
||||
_aiPackAddOn.UsageResetText = aiStatus.CurrentPeriodEndUtc is DateTimeOffset resetDateUtc
|
||||
? string.Format(Translator.WinoAccount_Management_AiPackResets, resetDateUtc.LocalDateTime)
|
||||
: string.Empty;
|
||||
});
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
_aiPackAddOn.HasUsageData = false;
|
||||
_aiPackAddOn.ErrorText = Translator.WinoAccount_Management_AddOnLoadFailed;
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
_aiPackAddOn.IsLoading = false;
|
||||
PurchaseAddOnCommand.NotifyCanExecuteChanged();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsExpiredAiEntitlement(string? entitlementStatus)
|
||||
=> string.Equals(entitlementStatus, "Expired", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
@@ -73,40 +73,6 @@ public partial class WelcomePageV2ViewModel : MailBaseViewModel
|
||||
ProviderSelectionNavigationContext.CreateForWizard()));
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanOpenWelcomeActions))]
|
||||
private async Task ImportFromWinoAccountAsync()
|
||||
{
|
||||
await ExecuteUIThread(() => ImportStatusMessage = string.Empty);
|
||||
|
||||
try
|
||||
{
|
||||
var account = await _dialogService.ShowWinoAccountLoginDialogAsync().ConfigureAwait(false);
|
||||
if (account == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteUIThread(() => IsImportInProgress = true);
|
||||
|
||||
var result = await _syncService.ImportAsync(new WinoAccountSyncSelection()).ConfigureAwait(false);
|
||||
if (result.ImportedMailboxCount > 0)
|
||||
{
|
||||
ReportUIChange(new WelcomeImportCompletedMessage(result.ImportedMailboxCount));
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteUIThread(() => ImportStatusMessage = BuildInlineImportMessage(result));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _dialogService.ShowMessageAsync(ex.Message, Translator.GeneralTitle_Error, WinoCustomMessageDialogIcon.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await ExecuteUIThread(() => IsImportInProgress = false);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanOpenWelcomeActions))]
|
||||
private async Task ImportFromJsonAsync()
|
||||
{
|
||||
|
||||
@@ -413,7 +413,6 @@ public partial class App : WinoApplication,
|
||||
services.AddTransient(typeof(MergedAccountDetailsPageViewModel));
|
||||
services.AddTransient(typeof(AppPreferencesPageViewModel));
|
||||
services.AddTransient(typeof(StoragePageViewModel));
|
||||
services.AddTransient(typeof(WinoAccountManagementPageViewModel));
|
||||
services.AddTransient(typeof(AliasManagementPageViewModel));
|
||||
services.AddTransient(typeof(MailCategoryManagementPageViewModel));
|
||||
services.AddTransient(typeof(ContactsPageViewModel));
|
||||
@@ -1656,15 +1655,9 @@ public partial class App : WinoApplication,
|
||||
_autoSynchronizationLoopCts = null;
|
||||
}
|
||||
|
||||
private async Task LoadInitialWinoAccountAsync()
|
||||
private Task LoadInitialWinoAccountAsync()
|
||||
{
|
||||
var winoAccountProfileService = Services.GetRequiredService<IWinoAccountProfileService>();
|
||||
var winoAccount = await winoAccountProfileService.GetActiveAccountAsync();
|
||||
|
||||
if (winoAccount != null)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new WinoAccountProfileUpdatedMessage(winoAccount));
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task RunAutoSynchronizationLoopAsync(TimeSpan interval, CancellationToken cancellationToken)
|
||||
|
||||
@@ -1,328 +0,0 @@
|
||||
<UserControl
|
||||
x:Class="Wino.Mail.WinUI.Controls.AiActionsPanel"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="using:Wino.Core.Domain.Models.Ai"
|
||||
x:Name="root"
|
||||
Loaded="OnLoaded"
|
||||
Unloaded="OnUnloaded"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Border
|
||||
Padding="14"
|
||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="10">
|
||||
<Grid>
|
||||
<StackPanel Spacing="14">
|
||||
<ProgressBar
|
||||
x:Name="BusyProgressBar"
|
||||
IsIndeterminate="True"
|
||||
Visibility="Collapsed" />
|
||||
|
||||
<StackPanel x:Name="LoadingPanel" Spacing="12">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<ProgressRing
|
||||
Width="20"
|
||||
Height="20"
|
||||
IsActive="True" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.AiActions_CheckingStatus}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
x:Name="SignedOutPanel"
|
||||
Spacing="14"
|
||||
Visibility="Collapsed">
|
||||
<Border
|
||||
Height="120"
|
||||
Padding="18"
|
||||
CornerRadius="12">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Offset="0" Color="#1A6EE7B7" />
|
||||
<GradientStop Offset="0.55" Color="#2038BDF8" />
|
||||
<GradientStop Offset="1" Color="#1A818CF8" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<Grid ColumnSpacing="14">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border
|
||||
Width="56"
|
||||
Height="56"
|
||||
VerticalAlignment="Top"
|
||||
Background="#22FFFFFF"
|
||||
CornerRadius="28">
|
||||
<FontIcon
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
||||
FontSize="26"
|
||||
Foreground="White"
|
||||
Glyph="" />
|
||||
</Border>
|
||||
|
||||
<StackPanel Grid.Column="1" Spacing="6">
|
||||
<TextBlock
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.AiActions_SignedOutTitle}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.AiActions_SignedOutDescription}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button
|
||||
Click="SignInButton_Click"
|
||||
Content="{x:Bind domain:Translator.Buttons_SignIn}"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
<Button
|
||||
Grid.Column="1"
|
||||
Click="CreateAccountButton_Click"
|
||||
Content="{x:Bind domain:Translator.Buttons_CreateAccount}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
x:Name="PurchasePanel"
|
||||
Spacing="14"
|
||||
Visibility="Collapsed">
|
||||
<Border
|
||||
Padding="16"
|
||||
Background="{ThemeResource CardBackgroundFillColorTertiaryBrush}"
|
||||
CornerRadius="12">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Style="{StaticResource BodyStrongTextBlockStyle}" Text="{x:Bind domain:Translator.AiActions_NoPackTitle}" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.AiActions_NoPackDescription}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Border
|
||||
Padding="8,2"
|
||||
Background="{ThemeResource AccentFillColorDefaultBrush}"
|
||||
CornerRadius="8">
|
||||
<TextBlock Foreground="White" Text="{x:Bind domain:Translator.WinoAccount_Management_AiPackPromoPrice}" />
|
||||
</Border>
|
||||
<Border
|
||||
Padding="8,2"
|
||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
CornerRadius="8">
|
||||
<TextBlock Text="{x:Bind domain:Translator.WinoAccount_Management_AiPackPromoRequests}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Button
|
||||
HorizontalAlignment="Left"
|
||||
Click="PurchaseButton_Click"
|
||||
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackGetButton}"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
<Grid
|
||||
x:Name="ReadyPanel"
|
||||
ColumnSpacing="12"
|
||||
Visibility="Collapsed">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- Column 1: Action tabs -->
|
||||
<controls:Segmented
|
||||
x:Name="ActionSelector"
|
||||
Height="30"
|
||||
VerticalAlignment="Center"
|
||||
SelectionChanged="ActionSelector_SelectionChanged"
|
||||
Style="{StaticResource ButtonSegmentedStyle}">
|
||||
<controls:SegmentedItem
|
||||
x:Name="TranslateSegment"
|
||||
Padding="12,6"
|
||||
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureTranslate}">
|
||||
<controls:SegmentedItem.Icon>
|
||||
<SymbolIcon Symbol="Switch" />
|
||||
</controls:SegmentedItem.Icon>
|
||||
</controls:SegmentedItem>
|
||||
<controls:SegmentedItem
|
||||
x:Name="RewriteSegment"
|
||||
Padding="12,6"
|
||||
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureRewrite}">
|
||||
<controls:SegmentedItem.Icon>
|
||||
<SymbolIcon Symbol="Edit" />
|
||||
</controls:SegmentedItem.Icon>
|
||||
</controls:SegmentedItem>
|
||||
<controls:SegmentedItem
|
||||
x:Name="SummarizeSegment"
|
||||
Padding="12,6"
|
||||
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureSummarize}">
|
||||
<controls:SegmentedItem.Icon>
|
||||
<SymbolIcon Symbol="Bullets" />
|
||||
</controls:SegmentedItem.Icon>
|
||||
</controls:SegmentedItem>
|
||||
</controls:Segmented>
|
||||
|
||||
<!-- Column 2: Action-specific options -->
|
||||
<Grid Grid.Column="1" ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Translate options -->
|
||||
<StackPanel
|
||||
x:Name="TranslateOptionsPanel"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8"
|
||||
Visibility="Collapsed">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.Composer_AiTranslateLanguage}" />
|
||||
<ComboBox
|
||||
x:Name="TranslateLanguageComboBox"
|
||||
MinWidth="120"
|
||||
SelectionChanged="TranslateLanguageComboBox_SelectionChanged">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:AiTranslateLanguageOption">
|
||||
<TextBlock Text="{x:Bind Label}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<Button
|
||||
x:Name="RunTranslateButton"
|
||||
Click="RunTranslateButton_Click"
|
||||
Content="{x:Bind domain:Translator.Composer_AiTranslateApply}"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Rewrite options -->
|
||||
<StackPanel
|
||||
x:Name="RewriteOptionsPanel"
|
||||
Spacing="8"
|
||||
Visibility="Collapsed">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.Composer_AiRewriteMode}" />
|
||||
<ComboBox
|
||||
x:Name="RewriteModeComboBox"
|
||||
MinWidth="140"
|
||||
SelectionChanged="RewriteModeComboBox_SelectionChanged">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:AiRewriteModeOption">
|
||||
<TextBlock Text="{x:Bind Label}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<Button
|
||||
x:Name="RunRewriteButton"
|
||||
Click="RunRewriteButton_Click"
|
||||
Content="{x:Bind domain:Translator.Composer_AiRewriteApply}"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
<TextBlock
|
||||
x:Name="RewriteDescriptionTextBlock"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<TextBox
|
||||
x:Name="CustomRewriteTextBox"
|
||||
PlaceholderText="{x:Bind domain:Translator.Composer_AiRewriteCustomPlaceholder}"
|
||||
Visibility="Collapsed" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Summarize options -->
|
||||
<StackPanel
|
||||
x:Name="SummarizeOptionsPanel"
|
||||
Spacing="8"
|
||||
Visibility="Collapsed">
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.Composer_AiTranslateLanguage}" />
|
||||
<ComboBox
|
||||
x:Name="SummarizeLanguageComboBox"
|
||||
MinWidth="120"
|
||||
SelectionChanged="SummarizeLanguageComboBox_SelectionChanged">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:AiTranslateLanguageOption">
|
||||
<TextBlock Text="{x:Bind Label}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<Button
|
||||
x:Name="RunSummarizeButton"
|
||||
HorizontalAlignment="Left"
|
||||
Click="RunSummarizeButton_Click"
|
||||
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureSummarize}"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
<FontIcon
|
||||
x:Name="SummarizeCachedIndicator"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
||||
FontSize="16"
|
||||
Foreground="#2AA84A"
|
||||
Glyph=""
|
||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Composer_AiSummarize}"
|
||||
Visibility="Collapsed" />
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- Column 3: Progress -->
|
||||
<StackPanel
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<ProgressBar
|
||||
x:Name="UsageProgressBar"
|
||||
Width="100"
|
||||
VerticalAlignment="Center"
|
||||
Maximum="1000"
|
||||
Value="0" />
|
||||
<TextBlock
|
||||
x:Name="UsageSummaryTextBlock"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -1,760 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Ai;
|
||||
using Wino.Mail.Api.Contracts.Ai;
|
||||
using Wino.Mail.Api.Contracts.Common;
|
||||
using Wino.Mail.WinUI.Services;
|
||||
|
||||
namespace Wino.Mail.WinUI.Controls;
|
||||
|
||||
public sealed partial class AiActionsPanel : UserControl, IDisposable
|
||||
{
|
||||
public event EventHandler? CloseRequested;
|
||||
private readonly IWinoAccountProfileService _profileService = App.Current.Services.GetRequiredService<IWinoAccountProfileService>();
|
||||
private readonly IStoreManagementService _storeManagementService = App.Current.Services.GetRequiredService<IStoreManagementService>();
|
||||
private readonly IMailDialogService _dialogService = App.Current.Services.GetRequiredService<IMailDialogService>();
|
||||
private readonly IAiActionOptionsService _optionsService = App.Current.Services.GetRequiredService<IAiActionOptionsService>();
|
||||
private readonly IPreferencesService _preferencesService = App.Current.Services.GetRequiredService<IPreferencesService>();
|
||||
|
||||
private bool _disposedValue;
|
||||
private bool _isRefreshing;
|
||||
private bool _isBusy;
|
||||
private AiActionType _lastConfigurableAction = AiActionType.Translate;
|
||||
private bool _hasCachedSummary;
|
||||
private CancellationTokenSource? _actionCancellationTokenSource;
|
||||
private IReadOnlyList<AiTranslateLanguageOption> _translateOptions = Array.Empty<AiTranslateLanguageOption>();
|
||||
private IReadOnlyList<AiRewriteModeOption> _rewriteOptions = Array.Empty<AiRewriteModeOption>();
|
||||
|
||||
[GeneratedDependencyProperty(DefaultValue = AiActionType.None)]
|
||||
public partial AiActionType AvailableActions { get; set; }
|
||||
|
||||
[GeneratedDependencyProperty]
|
||||
public partial IAiHtmlActionHost? HtmlHost { get; set; }
|
||||
|
||||
public AiTranslateLanguageOption? SelectedTranslateLanguageOption { get; set; }
|
||||
|
||||
public AiTranslateLanguageOption? SelectedSummarizeLanguageOption { get; set; }
|
||||
|
||||
public AiRewriteModeOption? SelectedRewriteModeOption { get; set; }
|
||||
|
||||
public AiActionsPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void CancelPendingOperation()
|
||||
{
|
||||
_actionCancellationTokenSource?.Cancel();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposedValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
CancelAndDisposeActionCancellationToken();
|
||||
}
|
||||
|
||||
partial void OnAvailableActionsChanged(AiActionType newValue)
|
||||
{
|
||||
UpdateActionAvailability();
|
||||
ApplySelectedAction(SelectDefaultAction());
|
||||
_ = RefreshCachedSummaryStateAsync();
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
LoadOptions();
|
||||
UpdateActionAvailability();
|
||||
ApplySelectedAction(SelectDefaultAction());
|
||||
_ = RefreshAvailabilityAsync();
|
||||
}
|
||||
|
||||
private void OnUnloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CancelPendingOperation();
|
||||
}
|
||||
|
||||
private void LoadOptions()
|
||||
{
|
||||
// Save current selections before replacing ItemsSource (which clears SelectedItem).
|
||||
var previousTranslateCode = SelectedTranslateLanguageOption?.Code;
|
||||
var previousSummarizeCode = SelectedSummarizeLanguageOption?.Code;
|
||||
var previousRewriteMode = SelectedRewriteModeOption?.Mode;
|
||||
var preferredTranslateCode = string.IsNullOrWhiteSpace(previousTranslateCode)
|
||||
? _preferencesService.AiDefaultTranslationLanguageCode
|
||||
: previousTranslateCode;
|
||||
var preferredSummarizeCode = string.IsNullOrWhiteSpace(previousSummarizeCode)
|
||||
? _preferencesService.AiSummarizeLanguageCode
|
||||
: previousSummarizeCode;
|
||||
|
||||
_translateOptions = _optionsService.GetTranslateLanguageOptions();
|
||||
_rewriteOptions = _optionsService.GetRewriteModeOptions();
|
||||
|
||||
TranslateLanguageComboBox.ItemsSource = _translateOptions;
|
||||
SummarizeLanguageComboBox.ItemsSource = _translateOptions;
|
||||
RewriteModeComboBox.ItemsSource = _rewriteOptions;
|
||||
|
||||
// Restore selection by matching on value, falling back to first item.
|
||||
SelectedTranslateLanguageOption = FindOption(_translateOptions, o => o.Code == preferredTranslateCode)
|
||||
?? FindOption(_translateOptions, o => o.Code == "en-US")
|
||||
?? (_translateOptions.Count > 0 ? _translateOptions[0] : null);
|
||||
SelectedSummarizeLanguageOption = FindOption(_translateOptions, o => o.Code == preferredSummarizeCode)
|
||||
?? FindOption(_translateOptions, o => o.Code == "en-US")
|
||||
?? (_translateOptions.Count > 0 ? _translateOptions[0] : null);
|
||||
SelectedRewriteModeOption = FindOption(_rewriteOptions, o => o.Mode == previousRewriteMode) ?? (_rewriteOptions.Count > 0 ? _rewriteOptions[0] : null);
|
||||
|
||||
TranslateLanguageComboBox.SelectedItem = SelectedTranslateLanguageOption;
|
||||
SummarizeLanguageComboBox.SelectedItem = SelectedSummarizeLanguageOption;
|
||||
RewriteModeComboBox.SelectedItem = SelectedRewriteModeOption;
|
||||
UpdateRewriteOptionState();
|
||||
}
|
||||
|
||||
private static T? FindOption<T>(IReadOnlyList<T> options, Func<T, bool> predicate) where T : class
|
||||
{
|
||||
foreach (var option in options)
|
||||
{
|
||||
if (predicate(option))
|
||||
{
|
||||
return option;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void UpdateActionAvailability()
|
||||
{
|
||||
TranslateSegment.Visibility = HasAction(AiActionType.Translate) ? Visibility.Visible : Visibility.Collapsed;
|
||||
RewriteSegment.Visibility = HasAction(AiActionType.Rewrite) ? Visibility.Visible : Visibility.Collapsed;
|
||||
SummarizeSegment.Visibility = HasAction(AiActionType.Summarize) ? Visibility.Visible : Visibility.Collapsed;
|
||||
SummarizeCachedIndicator.Visibility = HasAction(AiActionType.Summarize) && _hasCachedSummary ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private bool HasAction(AiActionType action) => (AvailableActions & action) == action;
|
||||
|
||||
private AiActionType SelectDefaultAction()
|
||||
{
|
||||
if (HasAction(AiActionType.Translate))
|
||||
{
|
||||
return AiActionType.Translate;
|
||||
}
|
||||
|
||||
if (HasAction(AiActionType.Rewrite))
|
||||
{
|
||||
return AiActionType.Rewrite;
|
||||
}
|
||||
|
||||
if (HasAction(AiActionType.Summarize))
|
||||
{
|
||||
return AiActionType.Summarize;
|
||||
}
|
||||
|
||||
return AiActionType.None;
|
||||
}
|
||||
|
||||
private void ApplySelectedAction(AiActionType action)
|
||||
{
|
||||
if (action is AiActionType.Translate or AiActionType.Rewrite)
|
||||
{
|
||||
_lastConfigurableAction = action;
|
||||
}
|
||||
|
||||
ActionSelector.SelectedItem = action switch
|
||||
{
|
||||
AiActionType.Translate => TranslateSegment,
|
||||
AiActionType.Rewrite => RewriteSegment,
|
||||
AiActionType.Summarize => SummarizeSegment,
|
||||
_ => null
|
||||
};
|
||||
|
||||
TranslateOptionsPanel.Visibility = action == AiActionType.Translate ? Visibility.Visible : Visibility.Collapsed;
|
||||
RewriteOptionsPanel.Visibility = action == AiActionType.Rewrite ? Visibility.Visible : Visibility.Collapsed;
|
||||
SummarizeOptionsPanel.Visibility = action == AiActionType.Summarize ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public async Task RefreshAvailabilityAsync()
|
||||
{
|
||||
if (_isRefreshing || _disposedValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isRefreshing = true;
|
||||
SetBusyUi(isBusy: false, showLoading: true);
|
||||
|
||||
try
|
||||
{
|
||||
var account = await _profileService.GetAuthenticatedAccountAsync().ConfigureAwait(true);
|
||||
if (account == null)
|
||||
{
|
||||
UpdateUsageSummary(string.Empty);
|
||||
UpdatePanelState(showSignedOut: true);
|
||||
return;
|
||||
}
|
||||
|
||||
var hasAiPack = await _storeManagementService.HasProductAsync(WinoAddOnProductType.AI_PACK).ConfigureAwait(true);
|
||||
if (!hasAiPack)
|
||||
{
|
||||
UpdateUsageSummary(string.Empty);
|
||||
UpdatePanelState(showPurchase: true);
|
||||
return;
|
||||
}
|
||||
|
||||
var aiStatusResponse = await _profileService.GetAiStatusAsync().ConfigureAwait(true);
|
||||
if (aiStatusResponse.IsSuccess && aiStatusResponse.Result != null)
|
||||
{
|
||||
UpdateUsageSummary(
|
||||
CreateUsageSummary(aiStatusResponse.Result),
|
||||
GetUsedCount(aiStatusResponse.Result),
|
||||
GetUsageLimit(aiStatusResponse.Result));
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateUsageSummary(Translator.WinoAccount_Management_AiPackUsageLoadFailed);
|
||||
}
|
||||
|
||||
await RefreshCachedSummaryStateAsync().ConfigureAwait(true);
|
||||
ApplySelectedAction(SelectDefaultAction());
|
||||
UpdatePanelState(showReady: true);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
UpdateUsageSummary(Translator.WinoAccount_Management_AiPackUsageLoadFailed);
|
||||
UpdatePanelState(showReady: true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isRefreshing = false;
|
||||
SetBusyUi(_isBusy, showLoading: false);
|
||||
}
|
||||
}
|
||||
|
||||
private static string CreateUsageSummary(AiStatusResultDto aiStatus)
|
||||
{
|
||||
if (aiStatus.Used is int used && aiStatus.MonthlyLimit is int limit && limit > 0)
|
||||
{
|
||||
return string.Format(Translator.AiActions_UsageSummary, used, limit);
|
||||
}
|
||||
|
||||
return Translator.WinoAccount_Management_AiPackUsageLoadFailed;
|
||||
}
|
||||
|
||||
private static int GetUsedCount(AiStatusResultDto aiStatus)
|
||||
=> aiStatus.Used is int used ? used : 0;
|
||||
|
||||
private static string CreateUsageSummary(QuotaInfoDto quotaInfo)
|
||||
{
|
||||
if (quotaInfo.Used is int used && quotaInfo.MonthlyLimit is int limit && limit > 0)
|
||||
{
|
||||
return string.Format(Translator.AiActions_UsageSummary, used, limit);
|
||||
}
|
||||
|
||||
return Translator.WinoAccount_Management_AiPackUsageLoadFailed;
|
||||
}
|
||||
|
||||
private static int GetUsedCount(QuotaInfoDto quotaInfo)
|
||||
=> quotaInfo.Used is int used ? used : 0;
|
||||
|
||||
private static int GetUsageLimit(QuotaInfoDto quotaInfo)
|
||||
=> quotaInfo.MonthlyLimit is int limit && limit > 0 ? limit : 1000;
|
||||
|
||||
private static int GetUsageLimit(AiStatusResultDto aiStatus)
|
||||
=> aiStatus.MonthlyLimit is int limit && limit > 0 ? limit : 1000;
|
||||
|
||||
|
||||
private void UpdatePanelState(bool showLoading = false, bool showSignedOut = false, bool showPurchase = false, bool showReady = false)
|
||||
{
|
||||
LoadingPanel.Visibility = showLoading ? Visibility.Visible : Visibility.Collapsed;
|
||||
SignedOutPanel.Visibility = showSignedOut ? Visibility.Visible : Visibility.Collapsed;
|
||||
PurchasePanel.Visibility = showPurchase ? Visibility.Visible : Visibility.Collapsed;
|
||||
ReadyPanel.Visibility = showReady ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void UpdateUsageSummary(string usageText, int usedCount = 0)
|
||||
{
|
||||
UsageSummaryTextBlock.Text = usageText;
|
||||
UsageProgressBar.Maximum = 1000;
|
||||
UsageProgressBar.Value = Math.Min(usedCount, 1000);
|
||||
}
|
||||
|
||||
private void UpdateUsageSummary(string usageText, int usedCount, int usageLimit)
|
||||
{
|
||||
var normalizedLimit = usageLimit > 0 ? usageLimit : 1000;
|
||||
UsageSummaryTextBlock.Text = usageText;
|
||||
UsageProgressBar.Maximum = normalizedLimit;
|
||||
UsageProgressBar.Value = Math.Min(usedCount, normalizedLimit);
|
||||
}
|
||||
|
||||
private void SetBusyUi(bool isBusy, bool showLoading)
|
||||
{
|
||||
_isBusy = isBusy;
|
||||
|
||||
BusyProgressBar.Visibility = isBusy ? Visibility.Visible : Visibility.Collapsed;
|
||||
ActionSelector.IsEnabled = !isBusy;
|
||||
TranslateLanguageComboBox.IsEnabled = !isBusy;
|
||||
SummarizeLanguageComboBox.IsEnabled = !isBusy;
|
||||
RewriteModeComboBox.IsEnabled = !isBusy;
|
||||
CustomRewriteTextBox.IsEnabled = !isBusy;
|
||||
RunTranslateButton.IsEnabled = !isBusy;
|
||||
RunRewriteButton.IsEnabled = !isBusy;
|
||||
RunSummarizeButton.IsEnabled = !isBusy;
|
||||
SignedOutPanel.IsHitTestVisible = !isBusy;
|
||||
PurchasePanel.IsHitTestVisible = !isBusy;
|
||||
|
||||
if (showLoading)
|
||||
{
|
||||
UpdatePanelState(showLoading: true);
|
||||
}
|
||||
else if (ReadyPanel.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdatePanelState(showReady: true);
|
||||
}
|
||||
else if (SignedOutPanel.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdatePanelState(showSignedOut: true);
|
||||
}
|
||||
else if (PurchasePanel.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdatePanelState(showPurchase: true);
|
||||
}
|
||||
}
|
||||
|
||||
private async void SignInButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var account = await _dialogService.ShowWinoAccountLoginDialogAsync();
|
||||
if (account != null)
|
||||
{
|
||||
_dialogService.InfoBarMessage(Translator.GeneralTitle_Info, string.Format(Translator.WinoAccount_LoginSuccessMessage, account.Email), InfoBarMessageType.Success);
|
||||
}
|
||||
|
||||
await RefreshAvailabilityAsync();
|
||||
}
|
||||
|
||||
private async void CreateAccountButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var account = await _dialogService.ShowWinoAccountRegistrationDialogAsync();
|
||||
if (account != null)
|
||||
{
|
||||
_dialogService.InfoBarMessage(Translator.GeneralTitle_Info, string.Format(Translator.WinoAccount_RegisterSuccessMessage, account.Email), InfoBarMessageType.Success);
|
||||
}
|
||||
|
||||
await RefreshAvailabilityAsync();
|
||||
}
|
||||
|
||||
private async void PurchaseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetBusyUi(isBusy: true, showLoading: false);
|
||||
|
||||
try
|
||||
{
|
||||
var purchaseResult = await _storeManagementService.PurchaseAsync(WinoAddOnProductType.AI_PACK).ConfigureAwait(true);
|
||||
|
||||
if (purchaseResult == StorePurchaseResult.NotPurchased)
|
||||
{
|
||||
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error, Translator.WinoAccount_Management_PurchaseStartFailed, InfoBarMessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var syncResult = await _profileService.SyncStoreEntitlementsAsync().ConfigureAwait(true);
|
||||
if (!syncResult.IsSuccess && !string.Equals(syncResult.ErrorCode, "MissingAccessToken", StringComparison.Ordinal))
|
||||
{
|
||||
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error, Translator.WinoAccount_Management_StoreSyncFailed, InfoBarMessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (purchaseResult == StorePurchaseResult.AlreadyPurchased)
|
||||
{
|
||||
_dialogService.InfoBarMessage(Translator.Info_PurchaseExistsTitle, Translator.Info_PurchaseExistsMessage, InfoBarMessageType.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
_dialogService.InfoBarMessage(Translator.Info_PurchaseThankYouTitle, Translator.Info_PurchaseThankYouMessage, InfoBarMessageType.Success);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error, Translator.WinoAccount_Management_PurchaseStartFailed, InfoBarMessageType.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SetBusyUi(isBusy: false, showLoading: false);
|
||||
await RefreshAvailabilityAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void ActionSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (ReferenceEquals(ActionSelector.SelectedItem, TranslateSegment))
|
||||
{
|
||||
ApplySelectedAction(AiActionType.Translate);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(ActionSelector.SelectedItem, RewriteSegment))
|
||||
{
|
||||
ApplySelectedAction(AiActionType.Rewrite);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(ActionSelector.SelectedItem, SummarizeSegment))
|
||||
{
|
||||
ApplySelectedAction(AiActionType.Summarize);
|
||||
_ = RefreshCachedSummaryStateAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void TranslateLanguageComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (TranslateLanguageComboBox.SelectedItem is AiTranslateLanguageOption option)
|
||||
{
|
||||
SelectedTranslateLanguageOption = option;
|
||||
_preferencesService.AiDefaultTranslationLanguageCode = option.Code;
|
||||
}
|
||||
}
|
||||
|
||||
private void RewriteModeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (RewriteModeComboBox.SelectedItem is AiRewriteModeOption option)
|
||||
{
|
||||
SelectedRewriteModeOption = option;
|
||||
UpdateRewriteOptionState();
|
||||
}
|
||||
}
|
||||
|
||||
private void SummarizeLanguageComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (SummarizeLanguageComboBox.SelectedItem is AiTranslateLanguageOption option)
|
||||
{
|
||||
SelectedSummarizeLanguageOption = option;
|
||||
_preferencesService.AiSummarizeLanguageCode = option.Code;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateRewriteOptionState()
|
||||
{
|
||||
var isCustom = SelectedRewriteModeOption?.IsCustom ?? false;
|
||||
RewriteDescriptionTextBlock.Text = SelectedRewriteModeOption?.Description ?? string.Empty;
|
||||
RewriteDescriptionTextBlock.Visibility = string.IsNullOrWhiteSpace(RewriteDescriptionTextBlock.Text) ? Visibility.Collapsed : Visibility.Visible;
|
||||
CustomRewriteTextBox.Visibility = isCustom ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private async void RunTranslateButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await ExecuteAiActionAsync(AiActionType.Translate);
|
||||
}
|
||||
|
||||
private async void RunRewriteButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await ExecuteAiActionAsync(AiActionType.Rewrite);
|
||||
}
|
||||
|
||||
private async void RunSummarizeButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await ExecuteAiActionAsync(AiActionType.Summarize);
|
||||
}
|
||||
|
||||
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CancelPendingOperation();
|
||||
CloseRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private async Task ExecuteAiActionAsync(AiActionType action)
|
||||
{
|
||||
if (_isBusy)
|
||||
{
|
||||
_dialogService.InfoBarMessage(Translator.Composer_AiBusyTitle, Translator.Composer_AiBusyMessage, InfoBarMessageType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (HtmlHost == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CancelAndDisposeActionCancellationToken();
|
||||
_actionCancellationTokenSource = new CancellationTokenSource();
|
||||
var cancellationToken = _actionCancellationTokenSource.Token;
|
||||
|
||||
SetBusyUi(isBusy: true, showLoading: false);
|
||||
|
||||
try
|
||||
{
|
||||
if (action == AiActionType.Translate && SelectedTranslateLanguageOption == null)
|
||||
{
|
||||
_dialogService.InfoBarMessage(Translator.Composer_AiErrorTitle, Translator.WinoAccount_Error_ValidationFailed, InfoBarMessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (action == AiActionType.Rewrite && string.IsNullOrWhiteSpace(ResolveRewriteMode()))
|
||||
{
|
||||
_dialogService.InfoBarMessage(Translator.Composer_AiErrorTitle, Translator.WinoAccount_Error_ValidationFailed, InfoBarMessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (action == AiActionType.Summarize && SelectedSummarizeLanguageOption == null)
|
||||
{
|
||||
_dialogService.InfoBarMessage(Translator.Composer_AiErrorTitle, Translator.WinoAccount_Error_ValidationFailed, InfoBarMessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (action == AiActionType.Translate)
|
||||
{
|
||||
var cachedTranslation = await HtmlHost.TryGetCachedTranslationHtmlAsync(SelectedTranslateLanguageOption?.Code ?? string.Empty, cancellationToken).ConfigureAwait(true);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(cachedTranslation))
|
||||
{
|
||||
await HtmlHost.ApplyHtmlResultAsync(cachedTranslation, cancellationToken).ConfigureAwait(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (action == AiActionType.Summarize)
|
||||
{
|
||||
var cachedSummary = await HtmlHost.TryGetCachedSummaryTextAsync(cancellationToken).ConfigureAwait(true);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(cachedSummary))
|
||||
{
|
||||
_hasCachedSummary = true;
|
||||
UpdateActionAvailability();
|
||||
await ShowSummaryDialogAsync(cachedSummary).ConfigureAwait(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var html = await HtmlHost.GetCurrentHtmlAsync(cancellationToken).ConfigureAwait(true);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(html))
|
||||
{
|
||||
_dialogService.InfoBarMessage(Translator.Composer_AiErrorTitle, Translator.WinoAccount_Error_AiHtmlEmpty, InfoBarMessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var response = action switch
|
||||
{
|
||||
AiActionType.Translate => await _profileService.TranslateAsync(html, SelectedTranslateLanguageOption?.Code ?? string.Empty, cancellationToken).ConfigureAwait(true),
|
||||
AiActionType.Rewrite => await _profileService.RewriteAsync(html, ResolveRewriteMode(), cancellationToken).ConfigureAwait(true),
|
||||
AiActionType.Summarize => await _profileService.SummarizeAsync(html, SelectedSummarizeLanguageOption?.Code ?? string.Empty, cancellationToken).ConfigureAwait(true),
|
||||
_ => ApiEnvelope<AiTextResultDto>.Failure(ApiErrorCodes.ValidationFailed)
|
||||
};
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (!response.IsSuccess || response.Result == null || string.IsNullOrWhiteSpace(response.Result.Html))
|
||||
{
|
||||
_dialogService.InfoBarMessage(Translator.Composer_AiErrorTitle, WinoAccountAiErrorTranslator.Format(response.ErrorCode, null), InfoBarMessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.Quota != null)
|
||||
{
|
||||
UpdateUsageSummary(
|
||||
CreateUsageSummary(response.Quota),
|
||||
GetUsedCount(response.Quota),
|
||||
GetUsageLimit(response.Quota));
|
||||
}
|
||||
|
||||
if (action == AiActionType.Translate)
|
||||
{
|
||||
await HtmlHost.SaveCachedTranslationHtmlAsync(SelectedTranslateLanguageOption?.Code ?? string.Empty, response.Result.Html, cancellationToken).ConfigureAwait(true);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await HtmlHost.ApplyHtmlResultAsync(response.Result.Html, cancellationToken).ConfigureAwait(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (action == AiActionType.Summarize)
|
||||
{
|
||||
await HtmlHost.SaveCachedSummaryTextAsync(response.Result.Html, cancellationToken).ConfigureAwait(true);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_hasCachedSummary = true;
|
||||
UpdateActionAvailability();
|
||||
|
||||
var savedSummary = await HtmlHost.TryGetCachedSummaryTextAsync(cancellationToken).ConfigureAwait(true);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await ShowSummaryDialogAsync(string.IsNullOrWhiteSpace(savedSummary) ? response.Result.Html : savedSummary).ConfigureAwait(true);
|
||||
return;
|
||||
}
|
||||
|
||||
await HtmlHost.ApplyHtmlResultAsync(response.Result.Html, cancellationToken).ConfigureAwait(true);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_dialogService.InfoBarMessage(Translator.Composer_AiErrorTitle, WinoAccountAiErrorTranslator.Format(null, ex.Message), InfoBarMessageType.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SetBusyUi(isBusy: false, showLoading: false);
|
||||
|
||||
if (_actionCancellationTokenSource != null)
|
||||
{
|
||||
_actionCancellationTokenSource.Dispose();
|
||||
_actionCancellationTokenSource = null;
|
||||
}
|
||||
|
||||
// Summarize no longer auto-switches back; the user explicitly selected the tab.
|
||||
}
|
||||
}
|
||||
|
||||
private string ResolveRewriteMode()
|
||||
{
|
||||
if (SelectedRewriteModeOption == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (!SelectedRewriteModeOption.IsCustom)
|
||||
{
|
||||
return SelectedRewriteModeOption.Mode;
|
||||
}
|
||||
|
||||
return CustomRewriteTextBox.Text?.Trim() ?? string.Empty;
|
||||
}
|
||||
|
||||
private async Task RefreshCachedSummaryStateAsync()
|
||||
{
|
||||
if (HtmlHost == null || !HasAction(AiActionType.Summarize))
|
||||
{
|
||||
_hasCachedSummary = false;
|
||||
UpdateActionAvailability();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var cachedSummary = await HtmlHost.TryGetCachedSummaryTextAsync(CancellationToken.None).ConfigureAwait(true);
|
||||
_hasCachedSummary = !string.IsNullOrWhiteSpace(cachedSummary);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_hasCachedSummary = false;
|
||||
}
|
||||
|
||||
UpdateActionAvailability();
|
||||
}
|
||||
|
||||
private async Task ShowSummaryDialogAsync(string summary)
|
||||
{
|
||||
if (HtmlHost == null)
|
||||
{
|
||||
await _dialogService.ShowMessageAsync(summary, Translator.Composer_AiSummarize, WinoCustomMessageDialogIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
var summaryTextBox = new TextBox
|
||||
{
|
||||
Text = summary,
|
||||
IsReadOnly = true,
|
||||
AcceptsReturn = true,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
MinHeight = 240,
|
||||
MaxHeight = 420,
|
||||
BorderThickness = new Thickness(0),
|
||||
Background = new SolidColorBrush(Windows.UI.Color.FromArgb(0, 0, 0, 0))
|
||||
};
|
||||
|
||||
var dialog = new ContentDialog
|
||||
{
|
||||
XamlRoot = XamlRoot,
|
||||
RequestedTheme = ActualTheme,
|
||||
Title = Translator.Composer_AiSummarize,
|
||||
PrimaryButtonText = Translator.Buttons_Save,
|
||||
SecondaryButtonText = Translator.Buttons_Close,
|
||||
DefaultButton = ContentDialogButton.Secondary,
|
||||
Content = new ScrollViewer
|
||||
{
|
||||
Content = summaryTextBox,
|
||||
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
|
||||
HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled
|
||||
}
|
||||
};
|
||||
|
||||
dialog.PrimaryButtonClick += async (sender, args) =>
|
||||
{
|
||||
var deferral = args.GetDeferral();
|
||||
|
||||
try
|
||||
{
|
||||
var configuredSummarySavePath = _preferencesService.AiSummarySavePath;
|
||||
var path = !string.IsNullOrWhiteSpace(configuredSummarySavePath) && Directory.Exists(configuredSummarySavePath)
|
||||
? Path.Combine(configuredSummarySavePath, HtmlHost.GetSuggestedSummaryFileName())
|
||||
: await _dialogService.PickFilePathAsync(HtmlHost.GetSuggestedSummaryFileName());
|
||||
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
args.Cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
await File.WriteAllTextAsync(path, summary);
|
||||
_dialogService.InfoBarMessage(Translator.GeneralTitle_Info, string.Format(Translator.ClipboardTextCopied_Message, Path.GetFileName(path)), InfoBarMessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
args.Cancel = true;
|
||||
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error, ex.Message, InfoBarMessageType.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
deferral.Complete();
|
||||
}
|
||||
};
|
||||
|
||||
await dialog.ShowAsync();
|
||||
}
|
||||
|
||||
private void CancelAndDisposeActionCancellationToken()
|
||||
{
|
||||
if (_actionCancellationTokenSource == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_actionCancellationTokenSource.Cancel();
|
||||
_actionCancellationTokenSource.Dispose();
|
||||
_actionCancellationTokenSource = null;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
<ContentDialog
|
||||
x:Class="Wino.Dialogs.WinoAccountEmailConfirmationRequiredDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="{x:Bind domain:Translator.WinoAccount_EmailConfirmationPendingDialog_Title}"
|
||||
PrimaryButtonClick="ResendClicked"
|
||||
PrimaryButtonStyle="{ThemeResource AccentButtonStyle}"
|
||||
PrimaryButtonText="{x:Bind domain:Translator.WinoAccount_EmailConfirmationPendingDialog_ResendButton}"
|
||||
SecondaryButtonText="{x:Bind domain:Translator.Buttons_Close}"
|
||||
Style="{StaticResource WinoDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<ContentDialog.Resources>
|
||||
<x:Double x:Key="ContentDialogMinWidth">520</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxWidth">520</x:Double>
|
||||
</ContentDialog.Resources>
|
||||
|
||||
<StackPanel Spacing="16">
|
||||
<Border
|
||||
Padding="14"
|
||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
CornerRadius="12">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock
|
||||
x:Name="MessageTextBlock"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<TextBlock
|
||||
x:Name="CountdownTextBlock"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<ProgressRing
|
||||
x:Name="BusyRing"
|
||||
Width="20"
|
||||
Height="20"
|
||||
HorizontalAlignment="Left"
|
||||
IsActive="False"
|
||||
Visibility="Collapsed" />
|
||||
|
||||
<TextBlock
|
||||
x:Name="ErrorTextBlock"
|
||||
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
|
||||
TextWrapping="WrapWholeWords"
|
||||
Visibility="Collapsed" />
|
||||
</StackPanel>
|
||||
</ContentDialog>
|
||||
@@ -1,124 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Mail.Api.Contracts.Auth;
|
||||
using Wino.Mail.WinUI.Services;
|
||||
|
||||
namespace Wino.Dialogs;
|
||||
|
||||
public sealed partial class WinoAccountEmailConfirmationRequiredDialog : ContentDialog
|
||||
{
|
||||
private readonly IWinoAccountProfileService _profileService;
|
||||
private readonly DispatcherTimer _countdownTimer;
|
||||
private readonly string _email;
|
||||
private readonly string _endpoint;
|
||||
private readonly string _ticket;
|
||||
private DateTimeOffset _resendAvailableAtUtc;
|
||||
|
||||
public WinoAccountEmailConfirmationRequiredDialog(IWinoAccountProfileService profileService, string email, EmailConfirmationRequiredDetailsDto details)
|
||||
{
|
||||
_profileService = profileService;
|
||||
_email = email;
|
||||
_endpoint = details.ResendConfirmationEndpoint;
|
||||
_ticket = details.ResendConfirmationTicket;
|
||||
_resendAvailableAtUtc = details.ResendAvailableAtUtc;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
MessageTextBlock.Text = string.Format(Translator.WinoAccount_EmailConfirmationPendingDialog_Message, email);
|
||||
|
||||
_countdownTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromSeconds(1)
|
||||
};
|
||||
_countdownTimer.Tick += CountdownTimer_Tick;
|
||||
|
||||
Closing += DialogClosing;
|
||||
|
||||
UpdateCountdown();
|
||||
_countdownTimer.Start();
|
||||
}
|
||||
|
||||
public bool ResendSucceeded { get; private set; }
|
||||
|
||||
private async void ResendClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
args.Cancel = true;
|
||||
|
||||
if (DateTimeOffset.UtcNow < _resendAvailableAtUtc)
|
||||
{
|
||||
UpdateCountdown();
|
||||
return;
|
||||
}
|
||||
|
||||
var deferral = args.GetDeferral();
|
||||
|
||||
try
|
||||
{
|
||||
SetBusyState(true);
|
||||
HideError();
|
||||
|
||||
var response = await _profileService.ResendEmailConfirmationAsync(_endpoint, _ticket);
|
||||
if (!response.IsSuccess)
|
||||
{
|
||||
ShowError(WinoAccountAuthErrorTranslator.Translate(response.ErrorCode));
|
||||
return;
|
||||
}
|
||||
|
||||
ResendSucceeded = true;
|
||||
Hide();
|
||||
}
|
||||
finally
|
||||
{
|
||||
SetBusyState(false);
|
||||
deferral.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
private void CountdownTimer_Tick(object? sender, object e) => UpdateCountdown();
|
||||
|
||||
private void UpdateCountdown()
|
||||
{
|
||||
var remaining = _resendAvailableAtUtc - DateTimeOffset.UtcNow;
|
||||
if (remaining <= TimeSpan.Zero)
|
||||
{
|
||||
IsPrimaryButtonEnabled = true;
|
||||
CountdownTextBlock.Text = Translator.WinoAccount_EmailConfirmationPendingDialog_ReadyToResend;
|
||||
return;
|
||||
}
|
||||
|
||||
IsPrimaryButtonEnabled = false;
|
||||
CountdownTextBlock.Text = string.Format(
|
||||
Translator.WinoAccount_EmailConfirmationPendingDialog_Countdown,
|
||||
$"{Math.Max(0, (int)remaining.TotalMinutes):00}:{Math.Max(0, remaining.Seconds):00}");
|
||||
}
|
||||
|
||||
private void SetBusyState(bool isBusy)
|
||||
{
|
||||
IsPrimaryButtonEnabled = !isBusy && DateTimeOffset.UtcNow >= _resendAvailableAtUtc;
|
||||
IsSecondaryButtonEnabled = !isBusy;
|
||||
BusyRing.IsActive = isBusy;
|
||||
BusyRing.Visibility = isBusy ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void ShowError(string message)
|
||||
{
|
||||
ErrorTextBlock.Text = message;
|
||||
ErrorTextBlock.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void HideError()
|
||||
{
|
||||
ErrorTextBlock.Text = string.Empty;
|
||||
ErrorTextBlock.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void DialogClosing(ContentDialog sender, ContentDialogClosingEventArgs args)
|
||||
{
|
||||
_countdownTimer.Stop();
|
||||
_countdownTimer.Tick -= CountdownTimer_Tick;
|
||||
Closing -= DialogClosing;
|
||||
}
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
<ContentDialog
|
||||
x:Class="Wino.Dialogs.WinoAccountLoginDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="{x:Bind domain:Translator.WinoAccount_LoginDialog_Title}"
|
||||
PrimaryButtonClick="LoginClicked"
|
||||
PrimaryButtonStyle="{ThemeResource AccentButtonStyle}"
|
||||
PrimaryButtonText="{x:Bind domain:Translator.Buttons_SignIn}"
|
||||
SecondaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}"
|
||||
Style="{StaticResource WinoDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<ContentDialog.Resources>
|
||||
<x:Double x:Key="ContentDialogMinWidth">520</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxWidth">520</x:Double>
|
||||
</ContentDialog.Resources>
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="20">
|
||||
|
||||
<!-- Hero illustration area -->
|
||||
<Border
|
||||
Height="140"
|
||||
CornerRadius="12">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Offset="0" Color="#1A6EE7B7" />
|
||||
<GradientStop Offset="0.5" Color="#2038BDF8" />
|
||||
<GradientStop Offset="1" Color="#1A818CF8" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<Canvas>
|
||||
<!-- Background decorative circles -->
|
||||
<Ellipse
|
||||
Canvas.Left="340"
|
||||
Canvas.Top="-20"
|
||||
Width="100"
|
||||
Height="100"
|
||||
Opacity="0.15">
|
||||
<Ellipse.Fill>
|
||||
<RadialGradientBrush>
|
||||
<GradientStop Offset="0" Color="#38BDF8" />
|
||||
<GradientStop Offset="1" Color="Transparent" />
|
||||
</RadialGradientBrush>
|
||||
</Ellipse.Fill>
|
||||
</Ellipse>
|
||||
<Ellipse
|
||||
Canvas.Left="20"
|
||||
Canvas.Top="80"
|
||||
Width="80"
|
||||
Height="80"
|
||||
Opacity="0.1">
|
||||
<Ellipse.Fill>
|
||||
<RadialGradientBrush>
|
||||
<GradientStop Offset="0" Color="#6EE7B7" />
|
||||
<GradientStop Offset="1" Color="Transparent" />
|
||||
</RadialGradientBrush>
|
||||
</Ellipse.Fill>
|
||||
</Ellipse>
|
||||
|
||||
<!-- Shield with lock illustration -->
|
||||
<Canvas Canvas.Left="190" Canvas.Top="16">
|
||||
<!-- Shield shape -->
|
||||
<Path
|
||||
Data="M46 4 L82 18 L82 48 C82 66 65 80 46 84 C27 80 10 66 10 48 L10 18 Z"
|
||||
Opacity="0.9">
|
||||
<Path.Fill>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Offset="0" Color="#6EE7B7" />
|
||||
<GradientStop Offset="1" Color="#059669" />
|
||||
</LinearGradientBrush>
|
||||
</Path.Fill>
|
||||
</Path>
|
||||
<!-- Inner shield -->
|
||||
<Path
|
||||
Data="M46 12 L76 24 L76 48 C76 64 62 76 46 80 C30 76 16 64 16 48 L16 24 Z"
|
||||
Opacity="0.5">
|
||||
<Path.Fill>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
|
||||
<GradientStop Offset="0" Color="#A7F3D0" />
|
||||
<GradientStop Offset="1" Color="#34D399" />
|
||||
</LinearGradientBrush>
|
||||
</Path.Fill>
|
||||
</Path>
|
||||
<!-- Lock body -->
|
||||
<Rectangle
|
||||
Canvas.Left="32"
|
||||
Canvas.Top="44"
|
||||
Width="28"
|
||||
Height="20"
|
||||
Fill="White"
|
||||
RadiusX="5"
|
||||
RadiusY="5" />
|
||||
<!-- Lock shackle -->
|
||||
<Path
|
||||
Data="M38 44 L38 38 A8 8 0 0 1 54 38 L54 44"
|
||||
Stroke="White"
|
||||
StrokeEndLineCap="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeThickness="4"
|
||||
Fill="Transparent" />
|
||||
<!-- Keyhole -->
|
||||
<Ellipse
|
||||
Canvas.Left="42"
|
||||
Canvas.Top="49"
|
||||
Width="8"
|
||||
Height="8"
|
||||
Fill="#059669" />
|
||||
<Rectangle
|
||||
Canvas.Left="44"
|
||||
Canvas.Top="55"
|
||||
Width="4"
|
||||
Height="6"
|
||||
Fill="#059669"
|
||||
RadiusX="2"
|
||||
RadiusY="2" />
|
||||
</Canvas>
|
||||
|
||||
<!-- Decorative sparkles -->
|
||||
<Ellipse
|
||||
Canvas.Left="150"
|
||||
Canvas.Top="30"
|
||||
Width="6"
|
||||
Height="6"
|
||||
Fill="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Opacity="0.5" />
|
||||
<Ellipse
|
||||
Canvas.Left="320"
|
||||
Canvas.Top="50"
|
||||
Width="5"
|
||||
Height="5"
|
||||
Fill="#6EE7B7"
|
||||
Opacity="0.6" />
|
||||
<Ellipse
|
||||
Canvas.Left="130"
|
||||
Canvas.Top="100"
|
||||
Width="4"
|
||||
Height="4"
|
||||
Fill="#38BDF8"
|
||||
Opacity="0.4" />
|
||||
<Ellipse
|
||||
Canvas.Left="360"
|
||||
Canvas.Top="100"
|
||||
Width="4"
|
||||
Height="4"
|
||||
Fill="#A78BFA"
|
||||
Opacity="0.5" />
|
||||
</Canvas>
|
||||
</Border>
|
||||
|
||||
<!-- Benefits cards -->
|
||||
<StackPanel
|
||||
x:Name="BenefitsPanel"
|
||||
Spacing="8">
|
||||
<Border
|
||||
Padding="14"
|
||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
CornerRadius="12">
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border
|
||||
Width="36"
|
||||
Height="36"
|
||||
Background="{ThemeResource AccentFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Opacity="0.15" />
|
||||
<FontIcon
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Glyph="" />
|
||||
<StackPanel Grid.Column="1" Spacing="2">
|
||||
<TextBlock Style="{StaticResource BodyStrongTextBlockStyle}" Text="{x:Bind domain:Translator.WinoAccount_LoginDialog_BenefitsTitle}" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_LoginDialog_BenefitsDescription}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border
|
||||
Padding="14"
|
||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
CornerRadius="12">
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border
|
||||
Width="36"
|
||||
Height="36"
|
||||
Background="{ThemeResource AccentFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Opacity="0.15" />
|
||||
<FontIcon
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Glyph="" />
|
||||
<StackPanel Grid.Column="1" Spacing="2">
|
||||
<TextBlock Style="{StaticResource BodyStrongTextBlockStyle}" Text="{x:Bind domain:Translator.WinoAccount_LoginDialog_DifferenceTitle}" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_LoginDialog_DifferenceDescription}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Input fields -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBox
|
||||
x:Name="EmailTextBox"
|
||||
Header="{x:Bind domain:Translator.WinoAccount_EmailLabel}"
|
||||
KeyDown="EmailTextBox_KeyDown"
|
||||
PlaceholderText="{x:Bind domain:Translator.WinoAccount_EmailPlaceholder}"
|
||||
TextChanging="InputChanged" />
|
||||
|
||||
<StackPanel x:Name="PasswordPanel">
|
||||
<PasswordBox
|
||||
x:Name="PasswordBox"
|
||||
Header="{x:Bind domain:Translator.WinoAccount_PasswordLabel}"
|
||||
KeyDown="PasswordBox_KeyDown"
|
||||
PasswordChanged="InputChanged" />
|
||||
</StackPanel>
|
||||
|
||||
<Border
|
||||
x:Name="ForgotPasswordInfoPanel"
|
||||
Padding="14"
|
||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
CornerRadius="12"
|
||||
Visibility="Collapsed">
|
||||
<TextBlock
|
||||
Text="{x:Bind domain:Translator.WinoAccount_ForgotPasswordDialog_Description}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<HyperlinkButton
|
||||
x:Name="ModeToggleButton"
|
||||
HorizontalAlignment="Left"
|
||||
Click="ModeToggleButton_Click"
|
||||
Content="{x:Bind domain:Translator.WinoAccount_LoginDialog_ForgotPasswordLink}" />
|
||||
|
||||
<ProgressRing
|
||||
x:Name="BusyRing"
|
||||
Width="20"
|
||||
Height="20"
|
||||
HorizontalAlignment="Left"
|
||||
IsActive="False"
|
||||
Visibility="Collapsed" />
|
||||
|
||||
<TextBlock
|
||||
x:Name="ErrorTextBlock"
|
||||
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
|
||||
TextWrapping="WrapWholeWords"
|
||||
Visibility="Collapsed" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</ContentDialog>
|
||||
@@ -1,201 +0,0 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Windows.System;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Mail.Api.Contracts.Auth;
|
||||
using Wino.Mail.WinUI.Services;
|
||||
|
||||
namespace Wino.Dialogs;
|
||||
|
||||
public sealed partial class WinoAccountLoginDialog : ContentDialog
|
||||
{
|
||||
private readonly IWinoAccountProfileService _profileService;
|
||||
private bool _isForgotPasswordMode;
|
||||
|
||||
public WinoAccountLoginDialog(IWinoAccountProfileService profileService)
|
||||
{
|
||||
_profileService = profileService;
|
||||
InitializeComponent();
|
||||
UpdateMode();
|
||||
}
|
||||
|
||||
public WinoAccount? Result { get; private set; }
|
||||
public string? PendingConfirmationEmailAddress { get; private set; }
|
||||
public EmailConfirmationRequiredDetailsDto? EmailConfirmationRequiredDetails { get; private set; }
|
||||
public string? PasswordResetEmailAddress { get; private set; }
|
||||
|
||||
private async void LoginClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
args.Cancel = true;
|
||||
|
||||
var validationError = ValidateInput();
|
||||
if (!string.IsNullOrWhiteSpace(validationError))
|
||||
{
|
||||
ShowError(validationError);
|
||||
return;
|
||||
}
|
||||
|
||||
var deferral = args.GetDeferral();
|
||||
|
||||
try
|
||||
{
|
||||
await PerformPrimaryActionAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
deferral.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task PerformPrimaryActionAsync()
|
||||
{
|
||||
var validationError = ValidateInput();
|
||||
if (!string.IsNullOrWhiteSpace(validationError))
|
||||
{
|
||||
ShowError(validationError);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
SetBusyState(true);
|
||||
HideError();
|
||||
|
||||
if (_isForgotPasswordMode)
|
||||
{
|
||||
var forgotPasswordResponse = await _profileService.ForgotPasswordAsync(EmailTextBox.Text.Trim());
|
||||
if (!forgotPasswordResponse.IsSuccess)
|
||||
{
|
||||
ShowError(WinoAccountAuthErrorTranslator.Translate(forgotPasswordResponse.ErrorCode));
|
||||
return;
|
||||
}
|
||||
|
||||
PasswordResetEmailAddress = EmailTextBox.Text.Trim();
|
||||
Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _profileService.LoginAsync(EmailTextBox.Text.Trim(), PasswordBox.Password);
|
||||
|
||||
if (!result.IsSuccess || result.Account == null)
|
||||
{
|
||||
var confirmationDetails = WinoAccountEmailConfirmationHelper.Parse(result.ErrorDetails);
|
||||
if (WinoAccountEmailConfirmationHelper.IsEmailConfirmationRequiredError(result.ErrorCode) && confirmationDetails != null)
|
||||
{
|
||||
PendingConfirmationEmailAddress = EmailTextBox.Text.Trim();
|
||||
EmailConfirmationRequiredDetails = confirmationDetails;
|
||||
Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
ShowError(WinoAccountAuthErrorTranslator.Format(result.ErrorCode, result.ErrorMessage));
|
||||
return;
|
||||
}
|
||||
|
||||
Result = result.Account;
|
||||
Hide();
|
||||
}
|
||||
finally
|
||||
{
|
||||
SetBusyState(false);
|
||||
}
|
||||
}
|
||||
|
||||
private string ValidateInput()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(EmailTextBox.Text))
|
||||
{
|
||||
return Translator.WinoAccount_Validation_EmailRequired;
|
||||
}
|
||||
|
||||
if (_isForgotPasswordMode)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(PasswordBox.Password))
|
||||
{
|
||||
return Translator.WinoAccount_Validation_PasswordRequired;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private async void EmailTextBox_KeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key == VirtualKey.Enter)
|
||||
{
|
||||
if (_isForgotPasswordMode)
|
||||
{
|
||||
e.Handled = true;
|
||||
await PerformPrimaryActionAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
PasswordBox.Focus(FocusState.Programmatic);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async void PasswordBox_KeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key == VirtualKey.Enter)
|
||||
{
|
||||
e.Handled = true;
|
||||
await PerformPrimaryActionAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void ModeToggleButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_isForgotPasswordMode = !_isForgotPasswordMode;
|
||||
PasswordBox.Password = string.Empty;
|
||||
HideError();
|
||||
UpdateMode();
|
||||
}
|
||||
|
||||
private void InputChanged(TextBox sender, TextBoxTextChangingEventArgs args) => HideError();
|
||||
|
||||
private void InputChanged(object sender, RoutedEventArgs e) => HideError();
|
||||
|
||||
private void SetBusyState(bool isBusy)
|
||||
{
|
||||
IsPrimaryButtonEnabled = !isBusy;
|
||||
IsSecondaryButtonEnabled = !isBusy;
|
||||
BusyRing.IsActive = isBusy;
|
||||
BusyRing.Visibility = isBusy ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void ShowError(string message)
|
||||
{
|
||||
ErrorTextBlock.Text = message;
|
||||
ErrorTextBlock.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void HideError()
|
||||
{
|
||||
ErrorTextBlock.Text = string.Empty;
|
||||
ErrorTextBlock.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void UpdateMode()
|
||||
{
|
||||
Title = _isForgotPasswordMode
|
||||
? Translator.WinoAccount_ForgotPasswordDialog_Title
|
||||
: Translator.WinoAccount_LoginDialog_Title;
|
||||
|
||||
PrimaryButtonText = _isForgotPasswordMode
|
||||
? Translator.WinoAccount_ForgotPasswordDialog_PrimaryButton
|
||||
: Translator.Buttons_SignIn;
|
||||
|
||||
BenefitsPanel.Visibility = _isForgotPasswordMode ? Visibility.Collapsed : Visibility.Visible;
|
||||
PasswordPanel.Visibility = _isForgotPasswordMode ? Visibility.Collapsed : Visibility.Visible;
|
||||
ForgotPasswordInfoPanel.Visibility = _isForgotPasswordMode ? Visibility.Visible : Visibility.Collapsed;
|
||||
ModeToggleButton.Content = _isForgotPasswordMode
|
||||
? Translator.WinoAccount_ForgotPasswordDialog_BackToSignIn
|
||||
: Translator.WinoAccount_LoginDialog_ForgotPasswordLink;
|
||||
}
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
<ContentDialog
|
||||
x:Class="Wino.Dialogs.WinoAccountRegistrationDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="{x:Bind domain:Translator.WinoAccount_RegisterDialog_Title}"
|
||||
FullSizeDesired="True"
|
||||
PrimaryButtonClick="RegisterClicked"
|
||||
PrimaryButtonStyle="{ThemeResource AccentButtonStyle}"
|
||||
PrimaryButtonText="{x:Bind domain:Translator.WinoAccount_RegisterDialog_PrimaryButton}"
|
||||
SecondaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}"
|
||||
Style="{StaticResource WinoDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<ContentDialog.Resources>
|
||||
<x:Double x:Key="ContentDialogMinWidth">560</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxWidth">560</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxHeight">900</x:Double>
|
||||
</ContentDialog.Resources>
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="20">
|
||||
|
||||
<!-- Hero illustration area -->
|
||||
<Border Height="140" CornerRadius="12">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Offset="0" Color="#1A818CF8" />
|
||||
<GradientStop Offset="0.5" Color="#20A78BFA" />
|
||||
<GradientStop Offset="1" Color="#1AE879F9" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<Canvas>
|
||||
<!-- Background decorative circles -->
|
||||
<Ellipse
|
||||
Canvas.Left="370"
|
||||
Canvas.Top="-10"
|
||||
Width="90"
|
||||
Height="90"
|
||||
Opacity="0.12">
|
||||
<Ellipse.Fill>
|
||||
<RadialGradientBrush>
|
||||
<GradientStop Offset="0" Color="#A78BFA" />
|
||||
<GradientStop Offset="1" Color="Transparent" />
|
||||
</RadialGradientBrush>
|
||||
</Ellipse.Fill>
|
||||
</Ellipse>
|
||||
<Ellipse
|
||||
Canvas.Left="30"
|
||||
Canvas.Top="90"
|
||||
Width="70"
|
||||
Height="70"
|
||||
Opacity="0.1">
|
||||
<Ellipse.Fill>
|
||||
<RadialGradientBrush>
|
||||
<GradientStop Offset="0" Color="#38BDF8" />
|
||||
<GradientStop Offset="1" Color="Transparent" />
|
||||
</RadialGradientBrush>
|
||||
</Ellipse.Fill>
|
||||
</Ellipse>
|
||||
|
||||
<!-- Person/account illustration -->
|
||||
<Canvas Canvas.Left="200" Canvas.Top="12">
|
||||
<!-- Circular badge background -->
|
||||
<Ellipse
|
||||
Canvas.Left="8"
|
||||
Canvas.Top="8"
|
||||
Width="80"
|
||||
Height="80"
|
||||
Opacity="0.9">
|
||||
<Ellipse.Fill>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Offset="0" Color="#818CF8" />
|
||||
<GradientStop Offset="1" Color="#6366F1" />
|
||||
</LinearGradientBrush>
|
||||
</Ellipse.Fill>
|
||||
</Ellipse>
|
||||
<!-- Inner circle -->
|
||||
<Ellipse
|
||||
Canvas.Left="14"
|
||||
Canvas.Top="14"
|
||||
Width="68"
|
||||
Height="68"
|
||||
Opacity="0.35">
|
||||
<Ellipse.Fill>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
|
||||
<GradientStop Offset="0" Color="#C7D2FE" />
|
||||
<GradientStop Offset="1" Color="#A5B4FC" />
|
||||
</LinearGradientBrush>
|
||||
</Ellipse.Fill>
|
||||
</Ellipse>
|
||||
<!-- Person head -->
|
||||
<Ellipse
|
||||
Canvas.Left="34"
|
||||
Canvas.Top="24"
|
||||
Width="28"
|
||||
Height="28"
|
||||
Fill="White" />
|
||||
<!-- Person body -->
|
||||
<Path Data="M28 68 A20 16 0 0 1 68 68" Fill="White" />
|
||||
|
||||
<!-- Plus badge -->
|
||||
<Ellipse
|
||||
Canvas.Left="62"
|
||||
Canvas.Top="62"
|
||||
Width="28"
|
||||
Height="28">
|
||||
<Ellipse.Fill>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Offset="0" Color="#4ADE80" />
|
||||
<GradientStop Offset="1" Color="#16A34A" />
|
||||
</LinearGradientBrush>
|
||||
</Ellipse.Fill>
|
||||
</Ellipse>
|
||||
<!-- Plus sign -->
|
||||
<Rectangle
|
||||
Canvas.Left="73"
|
||||
Canvas.Top="70"
|
||||
Width="4"
|
||||
Height="16"
|
||||
Fill="White"
|
||||
RadiusX="2"
|
||||
RadiusY="2" />
|
||||
<Rectangle
|
||||
Canvas.Left="69"
|
||||
Canvas.Top="74"
|
||||
Width="12"
|
||||
Height="4"
|
||||
Fill="White"
|
||||
RadiusX="2"
|
||||
RadiusY="2" />
|
||||
</Canvas>
|
||||
|
||||
<!-- Decorative sparkles -->
|
||||
<Ellipse
|
||||
Canvas.Left="140"
|
||||
Canvas.Top="35"
|
||||
Width="6"
|
||||
Height="6"
|
||||
Fill="#A78BFA"
|
||||
Opacity="0.5" />
|
||||
<Ellipse
|
||||
Canvas.Left="350"
|
||||
Canvas.Top="55"
|
||||
Width="5"
|
||||
Height="5"
|
||||
Fill="#38BDF8"
|
||||
Opacity="0.6" />
|
||||
<Ellipse
|
||||
Canvas.Left="120"
|
||||
Canvas.Top="95"
|
||||
Width="4"
|
||||
Height="4"
|
||||
Fill="#6EE7B7"
|
||||
Opacity="0.4" />
|
||||
<Ellipse
|
||||
Canvas.Left="380"
|
||||
Canvas.Top="105"
|
||||
Width="4"
|
||||
Height="4"
|
||||
Fill="#F472B6"
|
||||
Opacity="0.5" />
|
||||
</Canvas>
|
||||
</Border>
|
||||
|
||||
<!-- Benefits section -->
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock
|
||||
Margin="0,0,0,4"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_RegisterDialog_BenefitsTitle}" />
|
||||
|
||||
<Border
|
||||
Padding="14"
|
||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
CornerRadius="12">
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border
|
||||
Width="36"
|
||||
Height="36"
|
||||
Background="{ThemeResource AccentFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Opacity="0.15" />
|
||||
<FontIcon
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Glyph="" />
|
||||
<StackPanel Grid.Column="1" Spacing="2">
|
||||
<TextBlock Style="{StaticResource BodyStrongTextBlockStyle}" Text="{x:Bind domain:Translator.WinoAccount_RegisterDialog_BenefitSyncTitle}" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_RegisterDialog_BenefitSyncDescription}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border
|
||||
Padding="14"
|
||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
CornerRadius="12">
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border
|
||||
Width="36"
|
||||
Height="36"
|
||||
Background="{ThemeResource AccentFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Opacity="0.15" />
|
||||
<FontIcon
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Glyph="" />
|
||||
<StackPanel Grid.Column="1" Spacing="2">
|
||||
<TextBlock Style="{StaticResource BodyStrongTextBlockStyle}" Text="{x:Bind domain:Translator.WinoAccount_RegisterDialog_BenefitAiTitle}" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_RegisterDialog_BenefitAiDescription}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
x:Name="ErrorTextBlock"
|
||||
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
|
||||
TextWrapping="WrapWholeWords"
|
||||
Visibility="Collapsed" />
|
||||
|
||||
<!-- Input fields -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBox
|
||||
x:Name="EmailTextBox"
|
||||
Header="{x:Bind domain:Translator.WinoAccount_EmailLabel}"
|
||||
KeyDown="EmailTextBox_KeyDown"
|
||||
PlaceholderText="{x:Bind domain:Translator.WinoAccount_EmailPlaceholder}"
|
||||
TextChanging="InputChanged" />
|
||||
|
||||
<PasswordBox
|
||||
x:Name="PasswordBox"
|
||||
Header="{x:Bind domain:Translator.WinoAccount_PasswordLabel}"
|
||||
KeyDown="PasswordBox_KeyDown"
|
||||
PasswordChanged="InputChanged" />
|
||||
|
||||
<PasswordBox
|
||||
x:Name="ConfirmPasswordBox"
|
||||
Header="{x:Bind domain:Translator.WinoAccount_ConfirmPasswordLabel}"
|
||||
KeyDown="ConfirmPasswordBox_KeyDown"
|
||||
PasswordChanged="InputChanged" />
|
||||
</StackPanel>
|
||||
|
||||
<Border
|
||||
Padding="14"
|
||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
CornerRadius="12">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock Style="{StaticResource BodyStrongTextBlockStyle}" Text="{x:Bind domain:Translator.WinoAccount_RegisterDialog_PrivacyTitle}" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_RegisterDialog_PrivacyDescription}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<HyperlinkButton
|
||||
HorizontalAlignment="Left"
|
||||
Click="PrivacyPolicyLink_Click"
|
||||
Content="{x:Bind domain:Translator.WinoAccount_RegisterDialog_PrivacyLinkText}" />
|
||||
<CheckBox
|
||||
x:Name="PrivacyPolicyCheckBox"
|
||||
Checked="InputChanged"
|
||||
Content="{x:Bind domain:Translator.WinoAccount_RegisterDialog_PrivacyCheckbox}"
|
||||
Unchecked="InputChanged" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<ProgressRing
|
||||
x:Name="BusyRing"
|
||||
Width="20"
|
||||
Height="20"
|
||||
HorizontalAlignment="Left"
|
||||
IsActive="False"
|
||||
Visibility="Collapsed" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</ContentDialog>
|
||||
@@ -1,161 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Windows.System;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Mail.WinUI.Services;
|
||||
|
||||
namespace Wino.Dialogs;
|
||||
|
||||
public sealed partial class WinoAccountRegistrationDialog : ContentDialog
|
||||
{
|
||||
private const string PrivacyPolicyUrl = "https://www.winomail.app/accounts_policy.html";
|
||||
private readonly IWinoAccountProfileService _profileService;
|
||||
|
||||
public WinoAccountRegistrationDialog(IWinoAccountProfileService profileService)
|
||||
{
|
||||
_profileService = profileService;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public WinoAccount? Result { get; private set; }
|
||||
public string? ConfirmationEmailAddress { get; private set; }
|
||||
|
||||
private async void RegisterClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
args.Cancel = true;
|
||||
|
||||
var validationError = ValidateInput();
|
||||
if (!string.IsNullOrWhiteSpace(validationError))
|
||||
{
|
||||
ShowError(validationError);
|
||||
return;
|
||||
}
|
||||
|
||||
var deferral = args.GetDeferral();
|
||||
|
||||
try
|
||||
{
|
||||
await PerformRegistrationAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
deferral.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task PerformRegistrationAsync()
|
||||
{
|
||||
var validationError = ValidateInput();
|
||||
if (!string.IsNullOrWhiteSpace(validationError))
|
||||
{
|
||||
ShowError(validationError);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
SetBusyState(true);
|
||||
HideError();
|
||||
|
||||
var result = await _profileService.RegisterAsync(EmailTextBox.Text.Trim(), PasswordBox.Password);
|
||||
|
||||
if (!result.IsSuccess || result.Account == null)
|
||||
{
|
||||
ShowError(WinoAccountAuthErrorTranslator.Format(result.ErrorCode, result.ErrorMessage));
|
||||
return;
|
||||
}
|
||||
|
||||
ConfirmationEmailAddress = result.Account.Email;
|
||||
Hide();
|
||||
}
|
||||
finally
|
||||
{
|
||||
SetBusyState(false);
|
||||
}
|
||||
}
|
||||
|
||||
private string ValidateInput()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(EmailTextBox.Text))
|
||||
{
|
||||
return Translator.WinoAccount_Validation_EmailRequired;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(PasswordBox.Password))
|
||||
{
|
||||
return Translator.WinoAccount_Validation_PasswordRequired;
|
||||
}
|
||||
|
||||
if (!string.Equals(PasswordBox.Password, ConfirmPasswordBox.Password, StringComparison.Ordinal))
|
||||
{
|
||||
return Translator.WinoAccount_Validation_PasswordMismatch;
|
||||
}
|
||||
|
||||
if (PrivacyPolicyCheckBox.IsChecked != true)
|
||||
{
|
||||
return Translator.WinoAccount_Validation_PrivacyConsentRequired;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private void EmailTextBox_KeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key == VirtualKey.Enter)
|
||||
{
|
||||
PasswordBox.Focus(FocusState.Programmatic);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void PasswordBox_KeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key == VirtualKey.Enter)
|
||||
{
|
||||
ConfirmPasswordBox.Focus(FocusState.Programmatic);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async void ConfirmPasswordBox_KeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key == VirtualKey.Enter)
|
||||
{
|
||||
e.Handled = true;
|
||||
await PerformRegistrationAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async void PrivacyPolicyLink_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await Launcher.LaunchUriAsync(new Uri(PrivacyPolicyUrl));
|
||||
}
|
||||
|
||||
private void InputChanged(TextBox sender, TextBoxTextChangingEventArgs args) => HideError();
|
||||
|
||||
private void InputChanged(object sender, RoutedEventArgs e) => HideError();
|
||||
|
||||
private void SetBusyState(bool isBusy)
|
||||
{
|
||||
IsPrimaryButtonEnabled = !isBusy;
|
||||
IsSecondaryButtonEnabled = !isBusy;
|
||||
BusyRing.IsActive = isBusy;
|
||||
BusyRing.Visibility = isBusy ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void ShowError(string message)
|
||||
{
|
||||
ErrorTextBlock.Text = message;
|
||||
ErrorTextBlock.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void HideError()
|
||||
{
|
||||
ErrorTextBlock.Text = string.Empty;
|
||||
ErrorTextBlock.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
<ContentDialog
|
||||
x:Class="Wino.Dialogs.WinoAccountSyncExportDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
DefaultButton="Primary"
|
||||
PrimaryButtonClick="ExportClicked"
|
||||
PrimaryButtonStyle="{ThemeResource AccentButtonStyle}"
|
||||
PrimaryButtonText="{x:Bind domain:Translator.Buttons_Export, Mode=OneTime}"
|
||||
SecondaryButtonText="{x:Bind domain:Translator.Buttons_Close, Mode=OneTime}"
|
||||
Style="{StaticResource WinoDialogStyle}"
|
||||
Title="{x:Bind domain:Translator.WinoAccount_Management_ExportDialog_Title, Mode=OneTime}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<ContentDialog.Resources>
|
||||
<x:Double x:Key="ContentDialogMinWidth">520</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxWidth">520</x:Double>
|
||||
</ContentDialog.Resources>
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="16">
|
||||
<TextBlock
|
||||
Text="{x:Bind domain:Translator.WinoAccount_Management_ExportDialog_Description, Mode=OneTime}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
|
||||
<StackPanel Spacing="12">
|
||||
<CheckBox
|
||||
x:Name="PreferencesCheckBox"
|
||||
Checked="SelectionChanged"
|
||||
Content="{x:Bind domain:Translator.WinoAccount_Management_ExportDialog_IncludePreferences, Mode=OneTime}"
|
||||
IsChecked="True"
|
||||
Unchecked="SelectionChanged" />
|
||||
|
||||
<StackPanel Spacing="8">
|
||||
<CheckBox
|
||||
x:Name="AccountsCheckBox"
|
||||
Checked="SelectionChanged"
|
||||
Content="{x:Bind domain:Translator.WinoAccount_Management_ExportDialog_IncludeAccounts, Mode=OneTime}"
|
||||
IsChecked="True"
|
||||
Unchecked="SelectionChanged" />
|
||||
|
||||
<Border
|
||||
Padding="12"
|
||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
CornerRadius="10">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_Management_ExportDialog_AccountsDisclaimer, Mode=OneTime}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_Management_ExportDialog_AccountsRelogin, Mode=OneTime}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
x:Name="ProgressPanel"
|
||||
Spacing="8"
|
||||
Visibility="Collapsed">
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_Management_ExportDialog_InProgress, Mode=OneTime}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<ProgressBar IsIndeterminate="True" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</ContentDialog>
|
||||
@@ -1,73 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
|
||||
namespace Wino.Dialogs;
|
||||
|
||||
public sealed partial class WinoAccountSyncExportDialog : ContentDialog
|
||||
{
|
||||
private readonly IWinoAccountDataSyncService _syncService;
|
||||
private bool _isBusy;
|
||||
|
||||
public WinoAccountSyncExportDialog(IWinoAccountDataSyncService syncService)
|
||||
{
|
||||
_syncService = syncService;
|
||||
InitializeComponent();
|
||||
UpdateButtonState();
|
||||
}
|
||||
|
||||
public WinoAccountSyncExportResult? Result { get; private set; }
|
||||
|
||||
public Exception? FailureException { get; private set; }
|
||||
|
||||
private async void ExportClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
args.Cancel = true;
|
||||
|
||||
if (!HasSelection())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var deferral = args.GetDeferral();
|
||||
|
||||
try
|
||||
{
|
||||
SetBusyState(true);
|
||||
FailureException = null;
|
||||
Result = await _syncService.ExportAsync(new WinoAccountSyncSelection(
|
||||
PreferencesCheckBox.IsChecked == true,
|
||||
AccountsCheckBox.IsChecked == true));
|
||||
Hide();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
FailureException = ex;
|
||||
Hide();
|
||||
}
|
||||
finally
|
||||
{
|
||||
SetBusyState(false);
|
||||
deferral.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectionChanged(object sender, RoutedEventArgs e)
|
||||
=> UpdateButtonState();
|
||||
|
||||
private void SetBusyState(bool isBusy)
|
||||
{
|
||||
_isBusy = isBusy;
|
||||
ProgressPanel.Visibility = isBusy ? Visibility.Visible : Visibility.Collapsed;
|
||||
IsSecondaryButtonEnabled = !isBusy;
|
||||
UpdateButtonState();
|
||||
}
|
||||
|
||||
private void UpdateButtonState()
|
||||
=> IsPrimaryButtonEnabled = !_isBusy && HasSelection();
|
||||
|
||||
private bool HasSelection()
|
||||
=> PreferencesCheckBox.IsChecked == true || AccountsCheckBox.IsChecked == true;
|
||||
}
|
||||
@@ -17,7 +17,6 @@ public partial class NavigationMenuTemplateSelector : DataTemplateSelector
|
||||
public DataTemplate SettingsItemTemplate { get; set; } = null!;
|
||||
public DataTemplate SettingsShellPageItemTemplate { get; set; } = null!;
|
||||
public DataTemplate SettingsShellSectionItemTemplate { get; set; } = null!;
|
||||
public DataTemplate WinoAccountSettingsShellPageItemTemplate { get; set; } = null!;
|
||||
public DataTemplate StoreUpdateItemTemplate { get; set; } = null!;
|
||||
public DataTemplate MoreItemsFolderTemplate { get; set; } = null!;
|
||||
public DataTemplate RatingItemTemplate { get; set; } = null!;
|
||||
@@ -40,10 +39,8 @@ public partial class NavigationMenuTemplateSelector : DataTemplateSelector
|
||||
return ContactsMenuItemTemplate;
|
||||
else if (item is SettingsItem)
|
||||
return SettingsItemTemplate;
|
||||
else if (item is SettingsShellPageMenuItem settingsShellPageMenuItem)
|
||||
return string.Equals(settingsShellPageMenuItem.Title, Translator.WinoAccount_SettingsSection_Title, System.StringComparison.Ordinal)
|
||||
? WinoAccountSettingsShellPageItemTemplate
|
||||
: SettingsShellPageItemTemplate;
|
||||
else if (item is SettingsShellPageMenuItem)
|
||||
return SettingsShellPageItemTemplate;
|
||||
else if (item is SettingsShellSectionMenuItem)
|
||||
return SettingsShellSectionItemTemplate;
|
||||
else if (item is StoreUpdateMenuItem)
|
||||
|
||||
@@ -27,17 +27,10 @@ namespace Wino.Services;
|
||||
|
||||
public class DialogService : DialogServiceBase, IMailDialogService
|
||||
{
|
||||
private readonly IWinoAccountProfileService _winoAccountProfileService;
|
||||
private readonly IWinoAccountDataSyncService _winoAccountDataSyncService;
|
||||
|
||||
public DialogService(INewThemeService themeService,
|
||||
IConfigurationService configurationService,
|
||||
IApplicationResourceManager<ResourceDictionary> applicationResourceManager,
|
||||
IWinoAccountProfileService winoAccountProfileService,
|
||||
IWinoAccountDataSyncService winoAccountDataSyncService) : base(themeService, configurationService, applicationResourceManager)
|
||||
IApplicationResourceManager<ResourceDictionary> applicationResourceManager) : base(themeService, configurationService, applicationResourceManager)
|
||||
{
|
||||
_winoAccountProfileService = winoAccountProfileService;
|
||||
_winoAccountDataSyncService = winoAccountDataSyncService;
|
||||
}
|
||||
|
||||
public void ShowReadOnlyCalendarMessage()
|
||||
@@ -274,82 +267,4 @@ public class DialogService : DialogServiceBase, IMailDialogService
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<WinoAccount?> ShowWinoAccountRegistrationDialogAsync()
|
||||
{
|
||||
var dialog = new WinoAccountRegistrationDialog(_winoAccountProfileService)
|
||||
{
|
||||
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
|
||||
};
|
||||
|
||||
await HandleDialogPresentationAsync(dialog);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(dialog.ConfirmationEmailAddress))
|
||||
{
|
||||
await ShowMessageAsync(
|
||||
string.Format(Translator.WinoAccount_EmailConfirmationSentDialog_Message, dialog.ConfirmationEmailAddress),
|
||||
Translator.WinoAccount_EmailConfirmationSentDialog_Title);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<WinoAccount?> ShowWinoAccountLoginDialogAsync()
|
||||
{
|
||||
var dialog = new WinoAccountLoginDialog(_winoAccountProfileService)
|
||||
{
|
||||
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
|
||||
};
|
||||
|
||||
var result = await HandleDialogPresentationAsync(dialog);
|
||||
|
||||
if (dialog.EmailConfirmationRequiredDetails != null && !string.IsNullOrWhiteSpace(dialog.PendingConfirmationEmailAddress))
|
||||
{
|
||||
var confirmationDialog = new WinoAccountEmailConfirmationRequiredDialog(
|
||||
_winoAccountProfileService,
|
||||
dialog.PendingConfirmationEmailAddress,
|
||||
dialog.EmailConfirmationRequiredDetails)
|
||||
{
|
||||
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
|
||||
};
|
||||
|
||||
await HandleDialogPresentationAsync(confirmationDialog);
|
||||
|
||||
if (confirmationDialog.ResendSucceeded)
|
||||
{
|
||||
await ShowMessageAsync(
|
||||
string.Format(Translator.WinoAccount_EmailConfirmationResentDialog_Message, dialog.PendingConfirmationEmailAddress),
|
||||
Translator.WinoAccount_EmailConfirmationResentDialog_Title);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(dialog.PasswordResetEmailAddress))
|
||||
{
|
||||
await ShowMessageAsync(
|
||||
string.Format(Translator.WinoAccount_ForgotPasswordDialog_SuccessMessage, dialog.PasswordResetEmailAddress),
|
||||
Translator.WinoAccount_ForgotPasswordDialog_SuccessTitle);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return dialog.Result;
|
||||
}
|
||||
|
||||
public async Task<WinoAccountSyncExportResult?> ShowWinoAccountExportDialogAsync()
|
||||
{
|
||||
var dialog = new WinoAccountSyncExportDialog(_winoAccountDataSyncService)
|
||||
{
|
||||
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
|
||||
};
|
||||
|
||||
await HandleDialogPresentationAsync(dialog);
|
||||
|
||||
if (dialog.FailureException != null)
|
||||
{
|
||||
throw dialog.FailureException;
|
||||
}
|
||||
|
||||
return dialog.Result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,6 @@ public class NavigationService : NavigationServiceBase, INavigationService
|
||||
WinoPage.EmailTemplatesPage,
|
||||
WinoPage.CreateEmailTemplatePage,
|
||||
WinoPage.StoragePage,
|
||||
WinoPage.WinoAccountManagementPage,
|
||||
WinoPage.CalendarSettingsPage,
|
||||
WinoPage.CalendarRenderingSettingsPage,
|
||||
WinoPage.CalendarNotificationSettingsPage,
|
||||
@@ -163,7 +162,6 @@ public class NavigationService : NavigationServiceBase, INavigationService
|
||||
WinoPage.EmailTemplatesPage => typeof(EmailTemplatesPage),
|
||||
WinoPage.CreateEmailTemplatePage => typeof(CreateEmailTemplatePage),
|
||||
WinoPage.StoragePage => typeof(StoragePage),
|
||||
WinoPage.WinoAccountManagementPage => typeof(WinoAccountManagementPage),
|
||||
WinoPage.WelcomeHostPage => typeof(WelcomeHostPage),
|
||||
WinoPage.ProviderSelectionPage => typeof(ProviderSelectionPage),
|
||||
WinoPage.AccountSetupProgressPage => typeof(AccountSetupProgressPage),
|
||||
|
||||
@@ -405,17 +405,6 @@ public class PreferencesService(IConfigurationService configurationService) : Ob
|
||||
set => SetPropertyAndSave(nameof(IsSystemTrayIconEnabled), value);
|
||||
}
|
||||
|
||||
public bool IsWinoAccountButtonHidden
|
||||
{
|
||||
get => _configurationService.Get(nameof(IsWinoAccountButtonHidden), false);
|
||||
set => SetPropertyAndSave(nameof(IsWinoAccountButtonHidden), value);
|
||||
}
|
||||
|
||||
public bool IsAiActionsPanelHidden
|
||||
{
|
||||
get => _configurationService.Get(nameof(IsAiActionsPanelHidden), false);
|
||||
set => SetPropertyAndSave(nameof(IsAiActionsPanelHidden), value);
|
||||
}
|
||||
|
||||
public string AiDefaultTranslationLanguageCode
|
||||
{
|
||||
|
||||
@@ -119,194 +119,6 @@
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
<Button
|
||||
x:Name="WinoAccountButton"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
Visibility="{x:Bind helpers:XamlHelpers.ReverseBoolToVisibilityConverter(PreferencesService.IsWinoAccountButtonHidden), Mode=OneWay}">
|
||||
<Button.Flyout>
|
||||
<Flyout x:Name="WinoAccountFlyout" Placement="Bottom">
|
||||
<Grid MinWidth="320" MaxWidth="360">
|
||||
<!-- Signed Out View -->
|
||||
<StackPanel x:Name="WinoAccountSignedOutView" Spacing="16">
|
||||
|
||||
<!-- Hero header with gradient and icon -->
|
||||
<Border
|
||||
Margin="-16,-16,-16,0"
|
||||
Padding="20"
|
||||
CornerRadius="8,8,0,0">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Offset="0" Color="#1A6EE7B7" />
|
||||
<GradientStop Offset="0.5" Color="#2038BDF8" />
|
||||
<GradientStop Offset="1" Color="#1A818CF8" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock
|
||||
Margin="0,8,0,0"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_Titlebar_SignedOutTitle}" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_Titlebar_SignedOutDescription}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Benefit cards -->
|
||||
<StackPanel Spacing="8">
|
||||
<Border
|
||||
Padding="12"
|
||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
CornerRadius="8">
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border
|
||||
Width="32"
|
||||
Height="32"
|
||||
Background="{ThemeResource AccentFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Opacity="0.15" />
|
||||
<FontIcon
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Glyph="" />
|
||||
<StackPanel Grid.Column="1" Spacing="2">
|
||||
<TextBlock
|
||||
FontWeight="SemiBold"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_Titlebar_SyncBenefitTitle}" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_Titlebar_SyncBenefitDescription}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border
|
||||
Padding="12"
|
||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
CornerRadius="8">
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border
|
||||
Width="32"
|
||||
Height="32"
|
||||
Background="{ThemeResource AccentFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Opacity="0.15" />
|
||||
<FontIcon
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Glyph="" />
|
||||
<StackPanel Grid.Column="1" Spacing="2">
|
||||
<TextBlock
|
||||
FontWeight="SemiBold"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_Titlebar_AddonsBenefitTitle}" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_Titlebar_AddonsBenefitDescription}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Click="LoginWinoAccountClicked"
|
||||
Content="{x:Bind domain:Translator.WinoAccount_LoginButton_Title}"
|
||||
Style="{ThemeResource AccentButtonStyle}" />
|
||||
<Button
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Click="RegisterWinoAccountClicked"
|
||||
Content="{x:Bind domain:Translator.WinoAccount_RegisterButton_Title}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Signed In View -->
|
||||
<StackPanel
|
||||
x:Name="WinoAccountSignedInView"
|
||||
Spacing="16"
|
||||
Visibility="Collapsed">
|
||||
|
||||
<!-- Profile header -->
|
||||
<Border
|
||||
Margin="-16,-16,-16,0"
|
||||
Padding="20"
|
||||
CornerRadius="8,8,0,0">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Offset="0" Color="#1A6EE7B7" />
|
||||
<GradientStop Offset="0.5" Color="#2038BDF8" />
|
||||
<GradientStop Offset="1" Color="#1A818CF8" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<StackPanel Orientation="Horizontal" Spacing="14">
|
||||
<PersonPicture
|
||||
x:Name="WinoAccountFlyoutPicture"
|
||||
Width="48"
|
||||
Height="48"
|
||||
Initials="W" />
|
||||
<StackPanel VerticalAlignment="Center" Spacing="2">
|
||||
<TextBlock
|
||||
x:Name="WinoAccountFlyoutEmailText"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<TextBlock
|
||||
x:Name="WinoAccountFlyoutStatusText"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Sign out button -->
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Click="SignOutWinoAccountClicked"
|
||||
Content="{x:Bind domain:Translator.WinoAccount_SignOutButton_Action}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
<Grid>
|
||||
<PersonPicture
|
||||
x:Name="WinoAccountButtonPicture"
|
||||
Width="30"
|
||||
Height="30"
|
||||
Initials="W"
|
||||
Visibility="Collapsed" />
|
||||
<Image
|
||||
x:Name="WinoAccountSignedOutIcon"
|
||||
Width="25"
|
||||
Height="30"
|
||||
Source="/Assets/AppEntries/MailAssets/Square150x150Logo.png" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</TitleBar.RightHeader>
|
||||
</TitleBar>
|
||||
|
||||
@@ -34,16 +34,12 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
|
||||
IRecipient<InfoBarMessageRequested>,
|
||||
IRecipient<TitleBarShellContentUpdated>,
|
||||
IRecipient<SynchronizationActionsAdded>,
|
||||
IRecipient<SynchronizationActionsCompleted>,
|
||||
IRecipient<WinoAccountProfileUpdatedMessage>,
|
||||
IRecipient<WinoAccountProfileDeletedMessage>
|
||||
IRecipient<SynchronizationActionsCompleted>
|
||||
{
|
||||
private bool _allowClose;
|
||||
public IStatePersistanceService StatePersistanceService { get; } = WinoApplication.Current.Services.GetService<IStatePersistanceService>() ?? throw new Exception("StatePersistanceService not registered in DI container.");
|
||||
public IPreferencesService PreferencesService { get; } = WinoApplication.Current.Services.GetService<IPreferencesService>() ?? throw new Exception("PreferencesService not registered in DI container.");
|
||||
public INavigationService NavigationService { get; } = WinoApplication.Current.Services.GetService<INavigationService>() ?? throw new Exception("NavigationService not registered in DI container.");
|
||||
private IMailDialogService MailDialogService { get; } = WinoApplication.Current.Services.GetRequiredService<IMailDialogService>();
|
||||
private IWinoAccountProfileService WinoAccountProfileService { get; } = WinoApplication.Current.Services.GetRequiredService<IWinoAccountProfileService>();
|
||||
|
||||
public ObservableCollection<SynchronizationActionItem> SyncActionItems { get; } = new();
|
||||
private bool _calendarReminderServerStartAttempted;
|
||||
@@ -210,15 +206,6 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
|
||||
});
|
||||
}
|
||||
|
||||
public void Receive(WinoAccountProfileUpdatedMessage message)
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(() => UpdateWinoAccountState(message.Account));
|
||||
}
|
||||
|
||||
public void Receive(WinoAccountProfileDeletedMessage message)
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(() => UpdateWinoAccountState(null));
|
||||
}
|
||||
|
||||
private void UpdateSyncStatusVisibility()
|
||||
{
|
||||
@@ -426,8 +413,6 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
|
||||
WeakReferenceMessenger.Default.Register<InfoBarMessageRequested>(this);
|
||||
WeakReferenceMessenger.Default.Register<SynchronizationActionsAdded>(this);
|
||||
WeakReferenceMessenger.Default.Register<SynchronizationActionsCompleted>(this);
|
||||
WeakReferenceMessenger.Default.Register<WinoAccountProfileUpdatedMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<WinoAccountProfileDeletedMessage>(this);
|
||||
}
|
||||
|
||||
private void UnregisterRecipients()
|
||||
@@ -437,8 +422,6 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
|
||||
WeakReferenceMessenger.Default.Unregister<InfoBarMessageRequested>(this);
|
||||
WeakReferenceMessenger.Default.Unregister<SynchronizationActionsAdded>(this);
|
||||
WeakReferenceMessenger.Default.Unregister<SynchronizationActionsCompleted>(this);
|
||||
WeakReferenceMessenger.Default.Unregister<WinoAccountProfileUpdatedMessage>(this);
|
||||
WeakReferenceMessenger.Default.Unregister<WinoAccountProfileDeletedMessage>(this);
|
||||
}
|
||||
|
||||
private void ShowInfoBarMessage(InfoBarMessageRequested message)
|
||||
@@ -465,29 +448,6 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateWinoAccountState(WinoAccount? account)
|
||||
{
|
||||
var isSignedIn = account != null;
|
||||
|
||||
WinoAccountSignedOutView.Visibility = isSignedIn ? Visibility.Collapsed : Visibility.Visible;
|
||||
WinoAccountSignedInView.Visibility = isSignedIn ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
WinoAccountButtonPicture.Visibility = isSignedIn ? Visibility.Visible : Visibility.Collapsed;
|
||||
WinoAccountSignedOutIcon.Visibility = isSignedIn ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
var initials = GetInitials(account?.Email);
|
||||
|
||||
WinoAccountButtonPicture.Initials = initials;
|
||||
WinoAccountFlyoutPicture.Initials = initials;
|
||||
WinoAccountButtonPicture.DisplayName = account?.Email ?? Translator.WinoAccount_Titlebar_SignedOutTitle;
|
||||
WinoAccountFlyoutPicture.DisplayName = account?.Email ?? Translator.WinoAccount_Titlebar_SignedOutTitle;
|
||||
|
||||
WinoAccountFlyoutEmailText.Text = account?.Email ?? string.Empty;
|
||||
WinoAccountFlyoutStatusText.Text = account == null
|
||||
? string.Empty
|
||||
: string.Format(Translator.WinoAccount_Titlebar_SignedInStatus, account.AccountStatus);
|
||||
}
|
||||
|
||||
private static string GetInitials(string? email)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(email))
|
||||
@@ -510,50 +470,5 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
|
||||
return string.Concat(segments.Select(segment => char.ToUpperInvariant(segment[0])));
|
||||
}
|
||||
|
||||
private async void RegisterWinoAccountClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WinoAccountFlyout.Hide();
|
||||
var account = await MailDialogService.ShowWinoAccountRegistrationDialogAsync();
|
||||
if (account != null)
|
||||
{
|
||||
ShowInfoBarMessage(new InfoBarMessageRequested(
|
||||
InfoBarMessageType.Success,
|
||||
Translator.GeneralTitle_Info,
|
||||
string.Format(Translator.WinoAccount_RegisterSuccessMessage, account.Email)));
|
||||
}
|
||||
}
|
||||
|
||||
private async void LoginWinoAccountClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WinoAccountFlyout.Hide();
|
||||
var account = await MailDialogService.ShowWinoAccountLoginDialogAsync();
|
||||
if (account != null)
|
||||
{
|
||||
ShowInfoBarMessage(new InfoBarMessageRequested(
|
||||
InfoBarMessageType.Success,
|
||||
Translator.GeneralTitle_Info,
|
||||
string.Format(Translator.WinoAccount_LoginSuccessMessage, account.Email)));
|
||||
}
|
||||
}
|
||||
|
||||
private async void SignOutWinoAccountClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var activeAccount = await WinoAccountProfileService.GetActiveAccountAsync();
|
||||
if (activeAccount == null)
|
||||
{
|
||||
ShowInfoBarMessage(new InfoBarMessageRequested(
|
||||
InfoBarMessageType.Warning,
|
||||
Translator.GeneralTitle_Info,
|
||||
Translator.WinoAccount_SignOut_NoAccountMessage));
|
||||
return;
|
||||
}
|
||||
|
||||
await WinoAccountProfileService.SignOutAsync();
|
||||
|
||||
ShowInfoBarMessage(new InfoBarMessageRequested(
|
||||
InfoBarMessageType.Success,
|
||||
Translator.GeneralTitle_Info,
|
||||
string.Format(Translator.WinoAccount_SignOut_SuccessMessage, activeAccount.Email)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
using Wino.Core.ViewModels;
|
||||
|
||||
namespace Wino.Views.Abstract;
|
||||
|
||||
public abstract class WinoAccountManagementPageAbstract : SettingsPageBase<WinoAccountManagementPageViewModel>
|
||||
{
|
||||
}
|
||||
@@ -160,18 +160,6 @@
|
||||
Visibility="{x:Bind ViewModel.IsDraftBusy, Mode=OneWay}">
|
||||
<ProgressRing IsActive="True" />
|
||||
</AppBarButton>
|
||||
<AppBarToggleButton
|
||||
x:Name="ComposeAiActionsToggleButton"
|
||||
MinWidth="40"
|
||||
HorizontalContentAlignment="Center"
|
||||
Checked="ComposeAiActionsToggleButton_Checked"
|
||||
LabelPosition="Collapsed"
|
||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Composer_AiActions}"
|
||||
Visibility="{x:Bind GetAiActionsToggleVisibility(ViewModel.PreferencesService.IsAiActionsPanelHidden), Mode=OneWay}">
|
||||
<AppBarToggleButton.Icon>
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
<AppBarButton
|
||||
Click="PopOutButton_Click"
|
||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Buttons_PopOut}"
|
||||
@@ -490,14 +478,6 @@
|
||||
BorderThickness="0"
|
||||
Text="{x:Bind ViewModel.Subject, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
|
||||
<coreControls:AiActionsPanel
|
||||
x:Name="ComposeAiActionsPanel"
|
||||
Grid.Row="5"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,8,0,0"
|
||||
AvailableActions="Rewrite"
|
||||
HtmlHost="{x:Bind}"
|
||||
Visibility="{x:Bind GetAiActionsPanelVisibility(ComposeAiActionsToggleButton.IsChecked, ViewModel.PreferencesService.IsAiActionsPanelHidden), Mode=OneWay}" />
|
||||
|
||||
<!-- Attachments -->
|
||||
<ListView
|
||||
|
||||
@@ -48,12 +48,8 @@ public sealed partial class ComposePage : ComposePageAbstract,
|
||||
|
||||
public WebView2 GetWebView() => WebViewEditor.GetUnderlyingWebView();
|
||||
|
||||
public Visibility GetAiActionsToggleVisibility(bool isHidden) => isHidden ? Visibility.Collapsed : Visibility.Visible;
|
||||
public Visibility GetPopOutButtonVisibility() => SupportsPopOut ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public Visibility GetAiActionsPanelVisibility(bool? isChecked, bool isHidden)
|
||||
=> !isHidden && isChecked == true ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
private readonly List<IDisposable> _disposables = [];
|
||||
|
||||
public ComposePage()
|
||||
@@ -343,11 +339,6 @@ public sealed partial class ComposePage : ComposePageAbstract,
|
||||
HostActionRequested?.Invoke(this, new PopoutHostActionRequestedEventArgs(PopoutHostActionKind.CloseHostedInstance));
|
||||
}
|
||||
|
||||
private async void ComposeAiActionsToggleButton_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await ComposeAiActionsPanel.RefreshAvailabilityAsync();
|
||||
}
|
||||
|
||||
private async void TokenItemAdding(TokenizingTextBox sender, TokenItemAddingEventArgs args)
|
||||
{
|
||||
var deferral = args.GetDeferral();
|
||||
@@ -504,7 +495,6 @@ public sealed partial class ComposePage : ComposePageAbstract,
|
||||
base.OnNavigatingFrom(e);
|
||||
|
||||
FocusManager.GotFocus -= GlobalFocusManagerGotFocus;
|
||||
ComposeAiActionsPanel.CancelPendingOperation();
|
||||
await ViewModel.UpdateMimeChangesAsync();
|
||||
ViewModel.SaveHTMLasPDFFunc = null;
|
||||
ViewModel.RenderHtmlBodyAsyncFunc = null;
|
||||
|
||||
@@ -269,7 +269,7 @@
|
||||
Grid.Row="1"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
DefaultLabelPosition="Right"
|
||||
IsAIActionsPaneToggleVisible="{x:Bind GetAiActionsToggleVisible(ViewModel.PreferencesService.IsAiActionsPanelHidden), Mode=OneWay}"
|
||||
IsAIActionsPaneToggleVisible="False"
|
||||
IsEditorThemeDark="{x:Bind ViewModel.IsDarkWebviewRenderer, Mode=TwoWay}"
|
||||
IsEditorThemeToggleVisible="True"
|
||||
IsPopOutButtonVisible="{x:Bind SupportsPopOut, Mode=OneWay}"
|
||||
@@ -411,13 +411,6 @@
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
<coreControls:AiActionsPanel
|
||||
x:Name="ReaderAiActionsPanel"
|
||||
Grid.Row="3"
|
||||
Margin="0,8,0,0"
|
||||
AvailableActions="Translate, Summarize"
|
||||
HtmlHost="{x:Bind}"
|
||||
Visibility="{x:Bind GetAiActionsPanelVisibility(RendererCommandBar.IsAIActionsEnabled, ViewModel.PreferencesService.IsAiActionsPanelHidden), Mode=OneWay}" />
|
||||
|
||||
<!-- Attachments -->
|
||||
<Grid Grid.Row="4">
|
||||
|
||||
@@ -49,9 +49,6 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
||||
public event EventHandler<PopoutHostActionRequestedEventArgs>? HostActionRequested;
|
||||
|
||||
public WebView2 GetWebView() => Chromium;
|
||||
public bool GetAiActionsToggleVisible(bool isHidden) => !isHidden;
|
||||
public Visibility GetAiActionsPanelVisibility(bool isEnabled, bool isHidden)
|
||||
=> !isHidden && isEnabled ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public MailRenderingPage()
|
||||
{
|
||||
@@ -179,7 +176,6 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
||||
RendererCommandBar.AIActionsEnabledChanged -= RendererCommandBar_AIActionsEnabledChanged;
|
||||
RendererCommandBar.PopOutClicked -= RendererCommandBar_PopOutClicked;
|
||||
RendererCommandBar.IsAIActionsEnabled = false;
|
||||
ReaderAiActionsPanel.CancelPendingOperation();
|
||||
|
||||
DisposeWebView2();
|
||||
}
|
||||
@@ -206,7 +202,6 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
||||
{
|
||||
if (isEnabled)
|
||||
{
|
||||
await ReaderAiActionsPanel.RefreshAvailabilityAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,19 +137,6 @@
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard Description="{x:Bind domain:Translator.SettingsAppPreferences_HideWinoAccountButton_Description}" Header="{x:Bind domain:Translator.SettingsAppPreferences_HideWinoAccountButton_Title}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.PreferencesService.IsWinoAccountButtonHidden, Mode=TwoWay}" />
|
||||
<!--<controls:SettingsCard.HeaderIcon>
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</controls:SettingsCard.HeaderIcon>-->
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard Description="{x:Bind domain:Translator.SettingsAppPreferences_HideAiActionsPanel_Description}" Header="{x:Bind domain:Translator.SettingsAppPreferences_HideAiActionsPanel_Title}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.PreferencesService.IsAiActionsPanelHidden, Mode=TwoWay}" />
|
||||
<!--<controls:SettingsCard.HeaderIcon>
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</controls:SettingsCard.HeaderIcon>-->
|
||||
</controls:SettingsCard>
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
</StackPanel>
|
||||
|
||||
@@ -1,398 +0,0 @@
|
||||
<abstract:WinoAccountManagementPageAbstract
|
||||
x:Class="Wino.Views.Settings.WinoAccountManagementPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:abstract="using:Wino.Views.Abstract"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:selectors="using:Wino.Selectors"
|
||||
xmlns:viewModelData="using:Wino.Core.ViewModels.Data"
|
||||
x:Name="root"
|
||||
Title="{x:Bind domain:Translator.WinoAccount_SettingsSection_Title}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
<DataTemplate x:Key="AddOnNotPurchasedTemplate" x:DataType="viewModelData:WinoAddOnItemViewModel">
|
||||
<controls:SettingsCard ActionIcon="Add" IsActionIconVisible="True">
|
||||
<controls:SettingsCard.HeaderIcon>
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="{x:Bind IconGlyph}" />
|
||||
</controls:SettingsCard.HeaderIcon>
|
||||
<controls:SettingsCard.Header>
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind domain:Translator.GetTranslatedString(NameKey)}" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.GetTranslatedString(DescriptionKey)}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.GetTranslatedString(KeywordsKey)}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="8"
|
||||
Visibility="{x:Bind ShowLoadingState, Mode=OneWay}">
|
||||
<ProgressRing
|
||||
Width="18"
|
||||
Height="18"
|
||||
IsActive="{x:Bind ShowLoadingState, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.Busy}" />
|
||||
</StackPanel>
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource SystemFillColorCautionBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind ErrorText, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords"
|
||||
Visibility="{x:Bind ShowErrorState, Mode=OneWay}" />
|
||||
<StackPanel
|
||||
Margin="0,4,0,0"
|
||||
Orientation="Horizontal"
|
||||
Spacing="12">
|
||||
<Button
|
||||
Command="{x:Bind PurchaseCommand}"
|
||||
CommandParameter="{x:Bind}"
|
||||
Content="{x:Bind domain:Translator.Buttons_Purchase}"
|
||||
Style="{StaticResource AccentButtonStyle}"
|
||||
Visibility="{x:Bind ShowPurchaseState, Mode=OneWay}" />
|
||||
<ProgressRing
|
||||
Width="18"
|
||||
Height="18"
|
||||
IsActive="{x:Bind IsPurchaseInProgress, Mode=OneWay}"
|
||||
Visibility="{x:Bind IsPurchaseInProgress, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</controls:SettingsCard.Header>
|
||||
</controls:SettingsCard>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="AiPackPurchasedTemplate" x:DataType="viewModelData:WinoAddOnItemViewModel">
|
||||
<controls:SettingsExpander IsExpanded="True">
|
||||
<controls:SettingsExpander.HeaderIcon>
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="{x:Bind IconGlyph}" />
|
||||
</controls:SettingsExpander.HeaderIcon>
|
||||
<controls:SettingsExpander.Header>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind domain:Translator.GetTranslatedString(NameKey)}" />
|
||||
<Border
|
||||
Padding="8,2"
|
||||
Background="{ThemeResource SystemAccentColor}"
|
||||
CornerRadius="4">
|
||||
<TextBlock
|
||||
FontSize="10"
|
||||
FontWeight="Bold"
|
||||
Foreground="White"
|
||||
Text="PRO" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</controls:SettingsExpander.Header>
|
||||
<controls:SettingsExpander.Description>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_Management_AiPackSubscriptionActive}" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text=" · " />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind RenewalText, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</controls:SettingsExpander.Description>
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard HorizontalContentAlignment="Stretch">
|
||||
<controls:SettingsCard.Header>
|
||||
<StackPanel Spacing="8">
|
||||
<StackPanel
|
||||
MinWidth="400"
|
||||
Spacing="8"
|
||||
Visibility="{x:Bind ShowUsageSummary, Mode=OneWay}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<TextBlock
|
||||
FontSize="24"
|
||||
FontWeight="Bold"
|
||||
Text="{x:Bind UsageCount, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
Margin="0,0,0,2"
|
||||
VerticalAlignment="Bottom"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}">
|
||||
<Run Text="/ " />
|
||||
<Run Text="{x:Bind UsageLimit, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<ProgressBar
|
||||
Height="8"
|
||||
Maximum="100"
|
||||
Value="{x:Bind UsagePercentage, Mode=OneWay}" />
|
||||
<Grid>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Left"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_Management_AiPackRequestsUsed}" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Right"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind UsageResetText, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="8"
|
||||
Visibility="{x:Bind ShowLoadingState, Mode=OneWay}">
|
||||
<ProgressRing
|
||||
Width="18"
|
||||
Height="18"
|
||||
IsActive="{x:Bind ShowLoadingState, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.Busy}" />
|
||||
</StackPanel>
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource SystemFillColorCautionBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind ErrorText, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords"
|
||||
Visibility="{x:Bind ShowErrorState, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard.Header>
|
||||
</controls:SettingsCard>
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="UnlimitedAccountsPurchasedTemplate" x:DataType="viewModelData:WinoAddOnItemViewModel">
|
||||
<controls:SettingsCard>
|
||||
<controls:SettingsCard.HeaderIcon>
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="{x:Bind IconGlyph}" />
|
||||
</controls:SettingsCard.HeaderIcon>
|
||||
<controls:SettingsCard.Header>
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind domain:Translator.GetTranslatedString(NameKey)}" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.GetTranslatedString(DescriptionKey)}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.GetTranslatedString(KeywordsKey)}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="8"
|
||||
Visibility="{x:Bind ShowLoadingState, Mode=OneWay}">
|
||||
<ProgressRing
|
||||
Width="18"
|
||||
Height="18"
|
||||
IsActive="{x:Bind ShowLoadingState, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.Busy}" />
|
||||
</StackPanel>
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource SystemFillColorCautionBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind ErrorText, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords"
|
||||
Visibility="{x:Bind ShowErrorState, Mode=OneWay}" />
|
||||
<Border
|
||||
Padding="12,4"
|
||||
HorizontalAlignment="Left"
|
||||
Background="{ThemeResource SystemFillColorSuccessBackgroundBrush}"
|
||||
CornerRadius="12"
|
||||
Visibility="{x:Bind ShowPurchaseState, Mode=OneWay}">
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource SystemFillColorSuccessBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.Purchased}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</controls:SettingsCard.Header>
|
||||
</controls:SettingsCard>
|
||||
</DataTemplate>
|
||||
|
||||
<selectors:WinoAddOnTemplateSelector
|
||||
x:Key="WinoAddOnTemplateSelector"
|
||||
AiPackPurchasedTemplate="{StaticResource AiPackPurchasedTemplate}"
|
||||
NotPurchasedTemplate="{StaticResource AddOnNotPurchasedTemplate}"
|
||||
UnlimitedAccountsPurchasedTemplate="{StaticResource UnlimitedAccountsPurchasedTemplate}" />
|
||||
</Page.Resources>
|
||||
|
||||
<ScrollViewer>
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
|
||||
<StackPanel
|
||||
x:Name="SignedOutPanel"
|
||||
HorizontalAlignment="Stretch"
|
||||
x:Load="{x:Bind ViewModel.IsSignedOut, Mode=OneWay}"
|
||||
Spacing="{StaticResource SettingsCardSpacing}">
|
||||
|
||||
<StackPanel
|
||||
Padding="0,40,0,40"
|
||||
HorizontalAlignment="Stretch"
|
||||
Spacing="16">
|
||||
|
||||
<Image
|
||||
Width="64"
|
||||
Height="64"
|
||||
HorizontalAlignment="Center"
|
||||
Source="ms-appx:///Assets/AppEntries/MailAssets/Square150x150Logo.png" />
|
||||
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="20"
|
||||
FontWeight="SemiBold"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_Management_SignedOutTitle}" />
|
||||
|
||||
<TextBlock
|
||||
MaxWidth="360"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_Management_SignedOutDescription}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="12">
|
||||
<Button
|
||||
Command="{x:Bind ViewModel.SignInCommand}"
|
||||
Content="{x:Bind domain:Translator.Buttons_SignIn}"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
<Button Command="{x:Bind ViewModel.RegisterCommand}" Content="{x:Bind domain:Translator.Buttons_CreateAccount}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
Margin="0,16,0,4"
|
||||
HorizontalAlignment="Center"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_Management_AddOnsSectionHeader}" />
|
||||
|
||||
<ListView
|
||||
x:Name="SignedOutAddOnsList"
|
||||
ItemContainerStyle="{StaticResource StretchedItemContainerStyle}"
|
||||
ItemTemplateSelector="{StaticResource WinoAddOnTemplateSelector}"
|
||||
ItemsSource="{x:Bind ViewModel.AddOns, Mode=OneWay}"
|
||||
SelectionMode="None" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
x:Name="SignedInPanel"
|
||||
x:Load="{x:Bind ViewModel.IsSignedIn, Mode=OneWay}"
|
||||
Spacing="{StaticResource SettingsCardSpacing}">
|
||||
|
||||
<TextBlock
|
||||
Margin="0,0,0,4"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_Management_ProfileSectionHeader}" />
|
||||
|
||||
<controls:SettingsCard Header="{x:Bind ViewModel.AccountEmail, Mode=OneWay}">
|
||||
<controls:SettingsCard.HeaderIcon>
|
||||
<PathIcon
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Data="F1 M 3.75 5 L 3.75 4.902344 C 3.75 4.225262 3.885091 3.588867 4.155273 2.993164 C 4.425456 2.397461 4.790039 1.878256 5.249023 1.435547 C 5.708008 0.99284 6.238606 0.642904 6.84082 0.385742 C 7.443034 0.128582 8.079427 0 8.75 0 C 9.440104 0 10.089518 0.130209 10.698242 0.390625 C 11.306966 0.651043 11.837564 1.007488 12.290039 1.459961 C 12.742513 1.912436 13.098958 2.443035 13.359375 3.051758 C 13.619791 3.660482 13.75 4.309896 13.75 5 C 13.75 5.690104 13.619791 6.339519 13.359375 6.948242 C 13.098958 7.556967 12.742513 8.087565 12.290039 8.540039 C 11.837564 8.992514 11.306966 9.348959 10.698242 9.609375 C 10.089518 9.869792 9.440104 10 8.75 10 C 8.059896 10 7.410481 9.869792 6.801758 9.609375 C 6.193034 9.348959 5.662435 8.992514 5.209961 8.540039 C 4.757487 8.087565 4.401042 7.556967 4.140625 6.948242 C 3.880208 6.339519 3.75 5.690104 3.75 5 Z M 5 5 L 5 5.078125 C 5 5.585938 5.100911 6.062826 5.302734 6.508789 C 5.504557 6.954753 5.777995 7.34375 6.123047 7.675781 C 6.468099 8.007812 6.866862 8.269857 7.319336 8.461914 C 7.77181 8.653972 8.248697 8.75 8.75 8.75 C 9.270833 8.75 9.759114 8.652344 10.214844 8.457031 C 10.670572 8.261719 11.067708 7.994792 11.40625 7.65625 C 11.744791 7.317709 12.011719 6.920573 12.207031 6.464844 C 12.402344 6.009115 12.5 5.520834 12.5 5 C 12.5 4.479167 12.402344 3.990887 12.207031 3.535156 C 12.011719 3.079428 11.744791 2.682293 11.40625 2.34375 C 11.067708 2.005209 10.670572 1.738281 10.214844 1.542969 C 9.759114 1.347656 9.270833 1.25 8.75 1.25 C 8.229166 1.25 7.740885 1.347656 7.285156 1.542969 C 6.829427 1.738281 6.432292 2.005209 6.09375 2.34375 C 5.755208 2.682293 5.488281 3.079428 5.292969 3.535156 C 5.097656 3.990887 5 4.479167 5 5 Z M 20 14.375 L 20 18.125 C 20 18.385416 19.951172 18.629557 19.853516 18.857422 C 19.755859 19.085287 19.622395 19.283854 19.453125 19.453125 C 19.283854 19.622396 19.085285 19.755859 18.857422 19.853516 C 18.629557 19.951172 18.385416 20 18.125 20 L 11.875 20 C 11.614583 20 11.370442 19.951172 11.142578 19.853516 C 10.914713 19.755859 10.716146 19.622396 10.546875 19.453125 C 10.377604 19.283854 10.244141 19.085287 10.146484 18.857422 C 10.048828 18.629557 10 18.385416 10 18.125 L 10 14.375 C 10 14.114584 10.048828 13.870443 10.146484 13.642578 C 10.244141 13.414714 10.377604 13.216146 10.546875 13.046875 C 10.716146 12.877604 10.914713 12.744141 11.142578 12.646484 C 11.370442 12.548828 11.614583 12.5 11.875 12.5 L 12.5 12.5 L 12.5 11.25 C 12.5 11.080729 12.532552 10.921225 12.597656 10.771484 C 12.66276 10.621745 12.753906 10.488281 12.871094 10.371094 C 13.118488 10.123698 13.411457 10 13.75 10 L 16.25 10 C 16.41927 10 16.578775 10.032553 16.728516 10.097656 C 16.878254 10.162761 17.011719 10.253906 17.128906 10.371094 C 17.376301 10.61849 17.5 10.911459 17.5 11.25 L 17.5 12.5 L 18.125 12.5 C 18.385416 12.5 18.629557 12.548828 18.857422 12.646484 C 19.085285 12.744141 19.283854 12.877604 19.453125 13.046875 C 19.622395 13.216146 19.755859 13.414714 19.853516 13.642578 C 19.951172 13.870443 20 14.114584 20 14.375 Z M 11.25 11.25 C 10.800781 11.25 10.382486 11.360678 9.995117 11.582031 C 9.607747 11.803386 9.303385 12.109375 9.082031 12.5 L 2.5 12.5 C 2.324219 12.5 2.159831 12.532553 2.006836 12.597656 C 1.853841 12.662761 1.722005 12.750651 1.611328 12.861328 C 1.500651 12.972006 1.41276 13.103842 1.347656 13.256836 C 1.282552 13.409831 1.25 13.574219 1.25 13.75 C 1.25 14.420573 1.360677 15.008139 1.582031 15.512695 C 1.803385 16.017252 2.102865 16.455078 2.480469 16.826172 C 2.858073 17.197266 3.297526 17.50651 3.798828 17.753906 C 4.30013 18.001303 4.827474 18.198242 5.380859 18.344727 C 5.934244 18.491211 6.50065 18.595377 7.080078 18.657227 C 7.659505 18.719076 8.216146 18.75 8.75 18.75 C 8.75 19.205729 8.860677 19.622396 9.082031 20 L 8.75 20 C 8.190104 20 7.618814 19.973959 7.036133 19.921875 C 6.45345 19.869791 5.878906 19.775391 5.3125 19.638672 C 4.746094 19.501953 4.197591 19.319662 3.666992 19.091797 C 3.136393 18.863932 2.646484 18.574219 2.197266 18.222656 C 1.474609 17.66276 0.927734 17.005209 0.556641 16.25 C 0.185547 15.494792 0 14.661458 0 13.75 C 0 13.404948 0.065104 13.081055 0.195312 12.77832 C 0.325521 12.475586 0.504557 12.210287 0.732422 11.982422 C 0.960286 11.754558 1.225586 11.575521 1.52832 11.445312 C 1.831055 11.315104 2.154948 11.25 2.5 11.25 Z M 16.25 11.25 L 13.75 11.25 L 13.75 12.5 L 16.25 12.5 Z " />
|
||||
</controls:SettingsCard.HeaderIcon>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Border
|
||||
Padding="12,4"
|
||||
Background="{ThemeResource SystemFillColorSuccessBackgroundBrush}"
|
||||
CornerRadius="12">
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource SystemFillColorSuccessBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind ViewModel.AccountStatusText, Mode=OneWay}" />
|
||||
</Border>
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="6"
|
||||
Visibility="{x:Bind ViewModel.IsBusy, Mode=OneWay}">
|
||||
<ProgressRing
|
||||
Width="16"
|
||||
Height="16"
|
||||
VerticalAlignment="Center"
|
||||
IsActive="{x:Bind ViewModel.IsBusy, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.Busy}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard
|
||||
Command="{x:Bind ViewModel.ChangePasswordCommand}"
|
||||
Description="{x:Bind domain:Translator.WinoAccount_ChangePassword_Description}"
|
||||
Header="{x:Bind domain:Translator.WinoAccount_ChangePassword_Title}"
|
||||
IsClickEnabled="True">
|
||||
<controls:SettingsCard.HeaderIcon>
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</controls:SettingsCard.HeaderIcon>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard
|
||||
Command="{x:Bind ViewModel.SignOutCommand}"
|
||||
Description="{x:Bind domain:Translator.WinoAccount_Management_SignOutDescription}"
|
||||
Header="{x:Bind domain:Translator.WinoAccount_Management_SignOutTitle}"
|
||||
IsClickEnabled="True">
|
||||
<controls:SettingsCard.HeaderIcon>
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</controls:SettingsCard.HeaderIcon>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<TextBlock
|
||||
Margin="0,12,0,4"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_Management_AddOnsSectionHeader}" />
|
||||
|
||||
<ListView
|
||||
x:Name="SignedInAddOnsList"
|
||||
ItemContainerStyle="{StaticResource StretchedItemContainerStyle}"
|
||||
ItemTemplateSelector="{StaticResource WinoAddOnTemplateSelector}"
|
||||
ItemsSource="{x:Bind ViewModel.AddOns, Mode=OneWay}"
|
||||
SelectionMode="None" />
|
||||
|
||||
<TextBlock
|
||||
Margin="0,12,0,4"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.WinoAccount_Management_DataSectionHeader}" />
|
||||
|
||||
<controls:SettingsCard Description="{x:Bind domain:Translator.WinoAccount_Management_SyncPreferencesDescription}" Header="{x:Bind domain:Translator.WinoAccount_Management_SyncPreferencesTitle}">
|
||||
<controls:SettingsCard.HeaderIcon>
|
||||
<PathIcon
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Data="F1 M 18.330078 3.330078 L 18.330078 7.5 C 18.330078 7.727865 18.248697 7.923178 18.085938 8.085938 C 17.923176 8.248698 17.727863 8.330078 17.5 8.330078 L 13.330078 8.330078 C 13.102213 8.330078 12.9069 8.248698 12.744141 8.085938 C 12.58138 7.923178 12.5 7.727865 12.5 7.5 C 12.5 7.272136 12.58138 7.076823 12.744141 6.914062 C 12.9069 6.751303 13.102213 6.669923 13.330078 6.669922 L 15.78125 6.669922 C 15.481771 6.149089 15.120442 5.681967 14.697266 5.268555 C 14.274088 4.855145 13.808593 4.505209 13.300781 4.21875 C 12.792968 3.932293 12.25423 3.712566 11.68457 3.55957 C 11.114908 3.406576 10.530599 3.330078 9.931641 3.330078 C 9.384766 3.330078 8.846028 3.401693 8.31543 3.544922 C 7.784831 3.688152 7.280273 3.891602 6.801758 4.155273 C 6.323242 4.418945 5.878906 4.737956 5.46875 5.112305 C 5.058594 5.486654 4.703776 5.901693 4.404297 6.357422 C 4.254557 6.585287 4.127604 6.816407 4.023438 7.050781 C 3.919271 7.285157 3.815104 7.526043 3.710938 7.773438 C 3.639323 7.942709 3.538411 8.0778 3.408203 8.178711 C 3.277995 8.279623 3.11849 8.330078 2.929688 8.330078 C 2.701823 8.330078 2.504883 8.250326 2.338867 8.09082 C 2.172852 7.931315 2.089844 7.734375 2.089844 7.5 C 2.089844 7.415365 2.10612 7.32422 2.138672 7.226562 C 2.41862 6.412761 2.81901 5.66569 3.339844 4.985352 C 3.860677 4.305014 4.464518 3.719076 5.151367 3.227539 C 5.838216 2.736004 6.588541 2.353516 7.402344 2.080078 C 8.216146 1.806641 9.052734 1.669922 9.912109 1.669922 C 10.576172 1.669922 11.227213 1.743164 11.865234 1.889648 C 12.503255 2.036133 13.111979 2.250977 13.691406 2.53418 C 14.270832 2.817383 14.811197 3.167318 15.3125 3.583984 C 15.813802 4.000652 16.266275 4.475912 16.669922 5.009766 L 16.669922 3.330078 C 16.669922 3.102215 16.751301 2.906902 16.914062 2.744141 C 17.076822 2.581381 17.272135 2.5 17.5 2.5 C 17.727863 2.5 17.923176 2.581381 18.085938 2.744141 C 18.248697 2.906902 18.330078 3.102215 18.330078 3.330078 Z M 17.861328 12.5 C 17.861328 12.584636 17.845051 12.675781 17.8125 12.773438 C 17.532551 13.58724 17.13216 14.334311 16.611328 15.014648 C 16.090494 15.694987 15.486653 16.280924 14.799805 16.772461 C 14.112955 17.263998 13.362629 17.646484 12.548828 17.919922 C 11.735025 18.193359 10.898438 18.330078 10.039062 18.330078 C 9.375 18.330078 8.728841 18.258463 8.100586 18.115234 C 7.472331 17.972006 6.871745 17.762045 6.298828 17.485352 C 5.725911 17.208658 5.188802 16.865234 4.6875 16.455078 C 4.186198 16.044922 3.733724 15.576172 3.330078 15.048828 L 3.330078 16.669922 C 3.330078 16.897787 3.248698 17.0931 3.085938 17.255859 C 2.923177 17.418619 2.727865 17.5 2.5 17.5 C 2.272135 17.5 2.076823 17.418619 1.914062 17.255859 C 1.751302 17.0931 1.669922 16.897787 1.669922 16.669922 L 1.669922 12.5 C 1.669922 12.272136 1.751302 12.076823 1.914062 11.914062 C 2.076823 11.751303 2.272135 11.669922 2.5 11.669922 L 6.669922 11.669922 C 6.897786 11.669922 7.093099 11.751303 7.255859 11.914062 C 7.418619 12.076823 7.5 12.272136 7.5 12.5 C 7.5 12.727865 7.418619 12.923178 7.255859 13.085938 C 7.093099 13.248698 6.897786 13.330078 6.669922 13.330078 L 4.179688 13.330078 C 4.479167 13.850912 4.840495 14.318034 5.263672 14.731445 C 5.686849 15.144857 6.150716 15.494792 6.655273 15.78125 C 7.15983 16.067709 7.696939 16.287436 8.266602 16.44043 C 8.836263 16.593424 9.420572 16.669922 10.019531 16.669922 C 10.566406 16.669922 11.106771 16.598307 11.640625 16.455078 C 12.174479 16.31185 12.680664 16.108398 13.15918 15.844727 C 13.637695 15.581055 14.080403 15.262045 14.487305 14.887695 C 14.894205 14.513347 15.247396 14.095053 15.546875 13.632812 C 15.696614 13.404948 15.821939 13.173828 15.922852 12.939453 C 16.023762 12.705078 16.129557 12.464193 16.240234 12.216797 C 16.311848 12.047526 16.414387 11.912436 16.547852 11.811523 C 16.681314 11.710612 16.842447 11.660156 17.03125 11.660156 C 17.148438 11.660156 17.257486 11.682943 17.358398 11.728516 C 17.459309 11.774089 17.547199 11.834311 17.62207 11.90918 C 17.696939 11.98405 17.755533 12.07194 17.797852 12.172852 C 17.840168 12.273764 17.861328 12.382812 17.861328 12.5 Z " />
|
||||
</controls:SettingsCard.HeaderIcon>
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Command="{x:Bind ViewModel.ExportSettingsCommand}" Content="{x:Bind domain:Translator.Buttons_Export}" />
|
||||
<Button Command="{x:Bind ViewModel.ImportSettingsCommand}" Content="{x:Bind domain:Translator.Buttons_Import}" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</abstract:WinoAccountManagementPageAbstract>
|
||||
@@ -1,11 +0,0 @@
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views.Settings;
|
||||
|
||||
public sealed partial class WinoAccountManagementPage : WinoAccountManagementPageAbstract
|
||||
{
|
||||
public WinoAccountManagementPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -68,13 +68,6 @@ public sealed partial class SettingsPage : SettingsPageAbstract,
|
||||
manageAccountsEntry.Title = Translator.SettingsManageAccountSettings_Title;
|
||||
}
|
||||
|
||||
var winoAccountEntry = PageHistory.FirstOrDefault(a => a.Request.PageType == WinoPage.WinoAccountManagementPage);
|
||||
|
||||
if (winoAccountEntry != null)
|
||||
{
|
||||
winoAccountEntry.Title = Translator.WinoAccount_SettingsSection_Title;
|
||||
}
|
||||
|
||||
_ = RefreshCurrentPageStateAsync();
|
||||
UpdateWindowTitle();
|
||||
}
|
||||
|
||||
@@ -127,11 +127,6 @@
|
||||
Grid.Row="3"
|
||||
MaxWidth="600"
|
||||
HorizontalAlignment="Center">
|
||||
<HyperlinkButton
|
||||
HorizontalAlignment="Center"
|
||||
Command="{x:Bind ViewModel.ImportFromWinoAccountCommand}"
|
||||
Content="{x:Bind domain:Translator.WelcomeWindow_ImportFromWinoAccount}" />
|
||||
|
||||
<HyperlinkButton
|
||||
HorizontalAlignment="Center"
|
||||
Command="{x:Bind ViewModel.ImportFromJsonCommand}"
|
||||
|
||||
@@ -495,8 +495,7 @@
|
||||
SeperatorTemplate="{StaticResource SeperatorTemplate}"
|
||||
SettingsShellPageItemTemplate="{StaticResource SettingsShellPageItemTemplate}"
|
||||
SettingsShellSectionItemTemplate="{StaticResource SettingsShellSectionItemTemplate}"
|
||||
StoreUpdateItemTemplate="{StaticResource StoreUpdateItemTemplate}"
|
||||
WinoAccountSettingsShellPageItemTemplate="{StaticResource SettingsShellWinoAccountItemTemplate}" />
|
||||
StoreUpdateItemTemplate="{StaticResource StoreUpdateItemTemplate}" />
|
||||
|
||||
</Page.Resources>
|
||||
|
||||
|
||||
@@ -10,5 +10,5 @@ public class ApplicationConfiguration : IApplicationConfiguration
|
||||
public string PublisherSharedFolderPath { get; set; }
|
||||
public string ApplicationTempFolderPath { get; set; }
|
||||
|
||||
public string SentryDNS => "https://81365d32d74c6f223a0674a2fb7bade5@o4509722249134080.ingest.de.sentry.io/4509722259095632";
|
||||
public string SentryDNS => string.Empty;
|
||||
}
|
||||
|
||||
124
Wino.Services/CloudFreeServices.cs
Normal file
124
Wino.Services/CloudFreeServices.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
using Wino.Mail.Api.Contracts.Ai;
|
||||
using Wino.Mail.Api.Contracts.Auth;
|
||||
using Wino.Mail.Api.Contracts.Common;
|
||||
using Wino.Mail.Api.Contracts.Users;
|
||||
|
||||
namespace Wino.Services;
|
||||
|
||||
public sealed class NullWinoLogger : IWinoLogger
|
||||
{
|
||||
public void SetupLogger(string fullLogFilePath) { }
|
||||
public void RefreshLoggingLevel() { }
|
||||
public void TrackEvent(string eventName, Dictionary<string, string>? properties = null) { }
|
||||
}
|
||||
|
||||
public sealed class LocalOnlyWinoAccountApiClient : IWinoAccountApiClient
|
||||
{
|
||||
private static WinoAccountApiResult<AuthResultDto> AuthDisabled() => WinoAccountApiResult<AuthResultDto>.Failure("CloudDisabled", "Cloud features are disabled in this build.");
|
||||
private static ApiEnvelope<T> Disabled<T>() => ApiEnvelope<T>.Failure("CloudDisabled", "Cloud features are disabled in this build.");
|
||||
|
||||
public Task<WinoAccountApiResult<AuthResultDto>> RegisterAsync(string email, string password, CancellationToken cancellationToken = default) => Task.FromResult(AuthDisabled());
|
||||
public Task<WinoAccountApiResult<AuthResultDto>> LoginAsync(string email, string password, CancellationToken cancellationToken = default) => Task.FromResult(AuthDisabled());
|
||||
public Task<WinoAccountApiResult<AuthResultDto>> RefreshAsync(string refreshToken, CancellationToken cancellationToken = default) => Task.FromResult(AuthDisabled());
|
||||
public Task<ApiEnvelope<EmailConfirmationResendResultDto>> ResendEmailConfirmationAsync(string endpoint, string ticket, CancellationToken cancellationToken = default) => Task.FromResult(Disabled<EmailConfirmationResendResultDto>());
|
||||
public Task<ApiEnvelope<JsonElement>> ForgotPasswordAsync(string email, CancellationToken cancellationToken = default) => Task.FromResult(Disabled<JsonElement>());
|
||||
public Task<ApiEnvelope<JsonElement>> LogoutAsync(string refreshToken, CancellationToken cancellationToken = default) => Task.FromResult(Disabled<JsonElement>());
|
||||
public Task<ApiEnvelope<AuthUserDto>> GetCurrentUserAsync(CancellationToken cancellationToken = default) => Task.FromResult(Disabled<AuthUserDto>());
|
||||
public Task<ApiEnvelope<AiStatusResultDto>> GetAiStatusAsync(CancellationToken cancellationToken = default) => Task.FromResult(Disabled<AiStatusResultDto>());
|
||||
public Task<ApiEnvelope<AiTextResultDto>> SummarizeAsync(string html, string targetLanguage, CancellationToken cancellationToken = default) => Task.FromResult(Disabled<AiTextResultDto>());
|
||||
public Task<ApiEnvelope<AiTextResultDto>> TranslateAsync(string html, string targetLanguage, CancellationToken cancellationToken = default) => Task.FromResult(Disabled<AiTextResultDto>());
|
||||
public Task<ApiEnvelope<AiTextResultDto>> RewriteAsync(string html, string mode, CancellationToken cancellationToken = default) => Task.FromResult(Disabled<AiTextResultDto>());
|
||||
public Task<ApiEnvelope<WinoStoreCollectionsIdTicketInfo>> CreateCollectionsIdTicketAsync(CancellationToken cancellationToken = default) => Task.FromResult(Disabled<WinoStoreCollectionsIdTicketInfo>());
|
||||
public Task<ApiEnvelope<WinoStoreCollectionsIdTicketInfo>> CreatePurchaseIdTicketAsync(CancellationToken cancellationToken = default) => Task.FromResult(Disabled<WinoStoreCollectionsIdTicketInfo>());
|
||||
public Task<ApiEnvelope<JsonElement>> SyncStoreEntitlementsAsync(string? storeIdKey, string? purchaseIdKey, CancellationToken cancellationToken = default) => Task.FromResult(Disabled<JsonElement>());
|
||||
public Task<string?> GetSettingsAsync(CancellationToken cancellationToken = default) => Task.FromResult<string?>(null);
|
||||
public Task SaveSettingsAsync(string settingsJson, CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
public Task<UserMailboxSyncListDto> GetMailboxesAsync(CancellationToken cancellationToken = default) => Task.FromResult(new UserMailboxSyncListDto([]));
|
||||
public Task ReplaceMailboxesAsync(ReplaceUserMailboxesRequestDto request, CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
public sealed class LocalOnlyWinoAccountProfileService : IWinoAccountProfileService
|
||||
{
|
||||
private static WinoAccountOperationResult OperationDisabled() => WinoAccountOperationResult.Failure("CloudDisabled", "Cloud features are disabled in this build.");
|
||||
private static ApiEnvelope<T> Disabled<T>() => ApiEnvelope<T>.Failure("CloudDisabled", "Cloud features are disabled in this build.");
|
||||
|
||||
public Task<WinoAccountOperationResult> RegisterAsync(string email, string password, CancellationToken cancellationToken = default) => Task.FromResult(OperationDisabled());
|
||||
public Task<WinoAccountOperationResult> LoginAsync(string email, string password, CancellationToken cancellationToken = default) => Task.FromResult(OperationDisabled());
|
||||
public Task<WinoAccountOperationResult> RefreshAsync(CancellationToken cancellationToken = default) => Task.FromResult(OperationDisabled());
|
||||
public Task<WinoAccountOperationResult> RefreshProfileAsync(CancellationToken cancellationToken = default) => Task.FromResult(OperationDisabled());
|
||||
public Task<ApiEnvelope<EmailConfirmationResendResultDto>> ResendEmailConfirmationAsync(string endpoint, string ticket, CancellationToken cancellationToken = default) => Task.FromResult(Disabled<EmailConfirmationResendResultDto>());
|
||||
public Task<ApiEnvelope<JsonElement>> ForgotPasswordAsync(string email, CancellationToken cancellationToken = default) => Task.FromResult(Disabled<JsonElement>());
|
||||
public Task<WinoAccount?> GetActiveAccountAsync() => Task.FromResult<WinoAccount?>(null);
|
||||
public Task<WinoAccount?> GetAuthenticatedAccountAsync(CancellationToken cancellationToken = default) => Task.FromResult<WinoAccount?>(null);
|
||||
public Task<bool> HasActiveAccountAsync() => Task.FromResult(false);
|
||||
public Task<ApiEnvelope<AuthUserDto>> GetCurrentUserAsync(CancellationToken cancellationToken = default) => Task.FromResult(Disabled<AuthUserDto>());
|
||||
public Task<ApiEnvelope<AiStatusResultDto>> GetAiStatusAsync(CancellationToken cancellationToken = default) => Task.FromResult(Disabled<AiStatusResultDto>());
|
||||
public Task<ApiEnvelope<AiTextResultDto>> SummarizeAsync(string html, string targetLanguage, CancellationToken cancellationToken = default) => Task.FromResult(Disabled<AiTextResultDto>());
|
||||
public Task<ApiEnvelope<AiTextResultDto>> TranslateAsync(string html, string targetLanguage, CancellationToken cancellationToken = default) => Task.FromResult(Disabled<AiTextResultDto>());
|
||||
public Task<ApiEnvelope<AiTextResultDto>> RewriteAsync(string html, string mode, CancellationToken cancellationToken = default) => Task.FromResult(Disabled<AiTextResultDto>());
|
||||
public Task<ApiEnvelope<JsonElement>> SyncStoreEntitlementsAsync(CancellationToken cancellationToken = default) => Task.FromResult(Disabled<JsonElement>());
|
||||
public Task<string?> GetSettingsAsync(CancellationToken cancellationToken = default) => Task.FromResult<string?>(null);
|
||||
public Task SaveSettingsAsync(string settingsJson, CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
public Task<UserMailboxSyncListDto> GetMailboxesAsync(CancellationToken cancellationToken = default) => Task.FromResult(new UserMailboxSyncListDto([]));
|
||||
public Task ReplaceMailboxesAsync(ReplaceUserMailboxesRequestDto request, CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
public Task<bool> ProcessBillingCallbackAsync(Uri callbackUri, CancellationToken cancellationToken = default) => Task.FromResult(false);
|
||||
public Task SignOutAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
public sealed class LocalOnlyWinoAccountDataSyncService : IWinoAccountDataSyncService
|
||||
{
|
||||
public Task<WinoAccountSyncExportResult> ExportAsync(WinoAccountSyncSelection selection, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(new WinoAccountSyncExportResult
|
||||
{
|
||||
IncludedPreferences = selection.IncludePreferences,
|
||||
IncludedAccounts = selection.IncludeAccounts,
|
||||
ExportedMailboxCount = 0
|
||||
});
|
||||
|
||||
public Task<WinoAccountSyncFileExportResult> ExportToJsonAsync(WinoAccountSyncSelection selection, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(new WinoAccountSyncFileExportResult
|
||||
{
|
||||
JsonContent = "{}",
|
||||
ExportResult = new WinoAccountSyncExportResult
|
||||
{
|
||||
IncludedPreferences = selection.IncludePreferences,
|
||||
IncludedAccounts = selection.IncludeAccounts,
|
||||
ExportedMailboxCount = 0
|
||||
}
|
||||
});
|
||||
|
||||
public Task<WinoAccountSyncImportResult> ImportAsync(WinoAccountSyncSelection selection, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(new WinoAccountSyncImportResult
|
||||
{
|
||||
IncludedPreferences = selection.IncludePreferences,
|
||||
IncludedAccounts = selection.IncludeAccounts,
|
||||
HadRemotePreferences = false,
|
||||
AppliedPreferenceCount = 0,
|
||||
FailedPreferenceCount = 0,
|
||||
ImportedMailboxCount = 0,
|
||||
SkippedDuplicateMailboxCount = 0,
|
||||
RemoteMailboxCount = 0
|
||||
});
|
||||
|
||||
public Task<WinoAccountSyncImportResult> ImportFromJsonAsync(string jsonContent, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(new WinoAccountSyncImportResult
|
||||
{
|
||||
IncludedPreferences = false,
|
||||
IncludedAccounts = false,
|
||||
HadRemotePreferences = false,
|
||||
AppliedPreferenceCount = 0,
|
||||
FailedPreferenceCount = 0,
|
||||
ImportedMailboxCount = 0,
|
||||
SkippedDuplicateMailboxCount = 0,
|
||||
RemoteMailboxCount = 0
|
||||
});
|
||||
}
|
||||
@@ -11,7 +11,7 @@ public static class ServicesContainerSetup
|
||||
services.AddSingleton<IDatabaseService, DatabaseService>();
|
||||
|
||||
services.AddSingleton<IApplicationConfiguration, ApplicationConfiguration>();
|
||||
services.AddSingleton<IWinoLogger, WinoLogger>();
|
||||
services.AddSingleton<IWinoLogger, NullWinoLogger>();
|
||||
services.AddSingleton<ILaunchProtocolService, LaunchProtocolService>();
|
||||
services.AddSingleton<IShareActivationService, ShareActivationService>();
|
||||
services.AddSingleton<IMimeFileService, MimeFileService>();
|
||||
@@ -31,9 +31,9 @@ public static class ServicesContainerSetup
|
||||
services.AddTransient<ICalendarContextMenuItemService, CalendarContextMenuItemService>();
|
||||
services.AddTransient<ISpecialImapProviderConfigResolver, SpecialImapProviderConfigResolver>();
|
||||
services.AddTransient<IKeyboardShortcutService, KeyboardShortcutService>();
|
||||
services.AddSingleton<IWinoAccountApiClient, WinoAccountApiClient>();
|
||||
services.AddSingleton<IWinoAccountProfileService, WinoAccountProfileService>();
|
||||
services.AddTransient<IWinoAccountDataSyncService, WinoAccountDataSyncService>();
|
||||
services.AddSingleton<IWinoAccountApiClient, LocalOnlyWinoAccountApiClient>();
|
||||
services.AddSingleton<IWinoAccountProfileService, LocalOnlyWinoAccountProfileService>();
|
||||
services.AddSingleton<IWinoAccountDataSyncService, LocalOnlyWinoAccountDataSyncService>();
|
||||
services.AddSingleton<IContactPictureFileService, ContactPictureFileService>();
|
||||
|
||||
services.AddTransient<ICalDavClient, CalDavClient>();
|
||||
|
||||
Reference in New Issue
Block a user