feat: create cloud-free Wino Mail fork

This commit is contained in:
root
2026-04-29 01:32:51 +02:00
parent ef12e0ff3a
commit e2cc79a471
37 changed files with 137 additions and 3905 deletions

View File

@@ -41,7 +41,6 @@ public enum WinoPage
EmailTemplatesPage,
CreateEmailTemplatePage,
StoragePage,
WinoAccountManagementPage,
WelcomePageV2,
WelcomeHostPage,
ProviderSelectionPage,

View File

@@ -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();
}

View File

@@ -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.

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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()
{

View File

@@ -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)

View File

@@ -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="&#xE945;" />
</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="&#xE73E;"
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>

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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="&#xE8D7;" />
<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="&#xE946;" />
<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>

View File

@@ -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;
}
}

View File

@@ -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="&#xE8D7;" />
<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="&#xEA18;" />
<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>

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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;
}
}

View File

@@ -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),

View File

@@ -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
{

View File

@@ -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="&#xE8D7;" />
<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="&#xE946;" />
<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>

View File

@@ -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)));
}
}

View File

@@ -1,7 +0,0 @@
using Wino.Core.ViewModels;
namespace Wino.Views.Abstract;
public abstract class WinoAccountManagementPageAbstract : SettingsPageBase<WinoAccountManagementPageViewModel>
{
}

View File

@@ -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="&#xE945;" />
</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

View File

@@ -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;

View File

@@ -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">

View File

@@ -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();
}
}

View File

@@ -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="&#xE77B;" />
</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="&#xE945;" />
</controls:SettingsCard.HeaderIcon>-->
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
</StackPanel>

View File

@@ -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="&#xE72E;" />
</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="&#xF3B1;" />
</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>

View File

@@ -1,11 +0,0 @@
using Wino.Views.Abstract;
namespace Wino.Views.Settings;
public sealed partial class WinoAccountManagementPage : WinoAccountManagementPageAbstract
{
public WinoAccountManagementPage()
{
InitializeComponent();
}
}

View File

@@ -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();
}

View File

@@ -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}"

View File

@@ -495,8 +495,7 @@
SeperatorTemplate="{StaticResource SeperatorTemplate}"
SettingsShellPageItemTemplate="{StaticResource SettingsShellPageItemTemplate}"
SettingsShellSectionItemTemplate="{StaticResource SettingsShellSectionItemTemplate}"
StoreUpdateItemTemplate="{StaticResource StoreUpdateItemTemplate}"
WinoAccountSettingsShellPageItemTemplate="{StaticResource SettingsShellWinoAccountItemTemplate}" />
StoreUpdateItemTemplate="{StaticResource StoreUpdateItemTemplate}" />
</Page.Resources>

View File

@@ -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;
}

View 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
});
}

View File

@@ -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>();