More updates on wino acc.

This commit is contained in:
Burak Kaan Köse
2026-03-18 17:43:56 +01:00
parent a3b43fd079
commit f306f6eb1c
14 changed files with 519 additions and 387 deletions
@@ -6,6 +6,7 @@
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}"
@@ -16,15 +17,14 @@
<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 Height="140" CornerRadius="12">
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Offset="0" Color="#1A818CF8" />
@@ -99,9 +99,7 @@
Height="28"
Fill="White" />
<!-- Person body -->
<Path
Data="M28 68 A20 16 0 0 1 68 68"
Fill="White" />
<Path Data="M28 68 A20 16 0 0 1 68 68" Fill="White" />
<!-- Plus badge -->
<Ellipse
@@ -239,6 +237,12 @@
</Border>
</StackPanel>
<TextBlock
x:Name="ErrorTextBlock"
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
TextWrapping="WrapWholeWords"
Visibility="Collapsed" />
<!-- Input fields -->
<StackPanel Spacing="12">
<TextBox
@@ -266,9 +270,7 @@
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
CornerRadius="12">
<StackPanel Spacing="10">
<TextBlock
Style="{StaticResource BodyStrongTextBlockStyle}"
Text="{x:Bind domain:Translator.WinoAccount_RegisterDialog_PrivacyTitle}" />
<TextBlock Style="{StaticResource BodyStrongTextBlockStyle}" Text="{x:Bind domain:Translator.WinoAccount_RegisterDialog_PrivacyTitle}" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
@@ -281,8 +283,8 @@
<CheckBox
x:Name="PrivacyPolicyCheckBox"
Checked="InputChanged"
Unchecked="InputChanged"
Content="{x:Bind domain:Translator.WinoAccount_RegisterDialog_PrivacyCheckbox}" />
Content="{x:Bind domain:Translator.WinoAccount_RegisterDialog_PrivacyCheckbox}"
Unchecked="InputChanged" />
</StackPanel>
</Border>
@@ -293,12 +295,6 @@
HorizontalAlignment="Left"
IsActive="False"
Visibility="Collapsed" />
<TextBlock
x:Name="ErrorTextBlock"
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
TextWrapping="WrapWholeWords"
Visibility="Collapsed" />
</StackPanel>
</ScrollViewer>
</ContentDialog>
@@ -3,6 +3,10 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using System.Text.Json;
using CommunityToolkit.Mvvm.ComponentModel;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
@@ -44,6 +48,59 @@ public class PreferencesService(IConfigurationService configurationService) : Ob
RenderPlaintextLinks = RenderPlaintextLinks
};
public string ExportPreferences()
{
var settings = new Dictionary<string, object?>(StringComparer.Ordinal);
foreach (var property in GetSyncablePreferenceProperties())
{
settings[property.Name] = property.GetValue(this);
}
using var stream = new MemoryStream();
using (var writer = new Utf8JsonWriter(stream))
{
writer.WriteStartObject();
foreach (var setting in settings)
{
WritePreferenceValue(writer, setting.Key, setting.Value);
}
writer.WriteEndObject();
}
return Encoding.UTF8.GetString(stream.ToArray());
}
public (int appliedCount, int failedCount) ImportPreferences(string settingsJson)
{
using var document = JsonDocument.Parse(settingsJson);
var rootElement = document.RootElement;
var appliedCount = 0;
var failedCount = 0;
foreach (var property in GetSyncablePreferenceProperties())
{
if (!rootElement.TryGetProperty(property.Name, out var value) || value.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined)
{
continue;
}
try
{
property.SetValue(this, ReadPreferenceValue(property.PropertyType, value));
appliedCount++;
}
catch (Exception)
{
failedCount++;
}
}
return (appliedCount, failedCount);
}
public MailListDisplayMode MailItemDisplayMode
{
get => _configurationService.Get(nameof(MailItemDisplayMode), MailListDisplayMode.Spacious);
@@ -368,6 +425,122 @@ public class PreferencesService(IConfigurationService configurationService) : Ob
return daysOfWeek;
}
private static void WritePreferenceValue(Utf8JsonWriter writer, string propertyName, object? value)
{
if (value == null)
{
writer.WriteNull(propertyName);
return;
}
switch (value)
{
case string stringValue:
writer.WriteString(propertyName, stringValue);
return;
case bool boolValue:
writer.WriteBoolean(propertyName, boolValue);
return;
case int intValue:
writer.WriteNumber(propertyName, intValue);
return;
case long longValue:
writer.WriteNumber(propertyName, longValue);
return;
case double doubleValue:
writer.WriteNumber(propertyName, doubleValue);
return;
case float floatValue:
writer.WriteNumber(propertyName, floatValue);
return;
case Guid guidValue:
writer.WriteString(propertyName, guidValue);
return;
case TimeSpan timeSpanValue:
writer.WriteString(propertyName, timeSpanValue.ToString("c", CultureInfo.InvariantCulture));
return;
}
var valueType = Nullable.GetUnderlyingType(value.GetType()) ?? value.GetType();
if (valueType.IsEnum)
{
writer.WriteString(propertyName, value.ToString());
return;
}
writer.WriteString(propertyName, Convert.ToString(value, CultureInfo.InvariantCulture));
}
private static object? ReadPreferenceValue(Type propertyType, JsonElement value)
{
var targetType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;
if (value.ValueKind == JsonValueKind.Null)
{
return null;
}
if (targetType == typeof(string))
{
return value.GetString() ?? string.Empty;
}
if (targetType == typeof(bool))
{
return value.GetBoolean();
}
if (targetType == typeof(int))
{
return value.GetInt32();
}
if (targetType == typeof(long))
{
return value.GetInt64();
}
if (targetType == typeof(double))
{
return value.GetDouble();
}
if (targetType == typeof(float))
{
return value.GetSingle();
}
if (targetType == typeof(Guid))
{
return Guid.Parse(value.GetString() ?? string.Empty);
}
if (targetType == typeof(TimeSpan))
{
return TimeSpan.Parse(value.GetString() ?? string.Empty, CultureInfo.InvariantCulture);
}
if (targetType.IsEnum)
{
return Enum.Parse(targetType, value.GetString() ?? string.Empty, true);
}
return Convert.ChangeType(value.GetString(), targetType, CultureInfo.InvariantCulture);
}
private static IEnumerable<PropertyInfo> GetSyncablePreferenceProperties()
{
foreach (var property in typeof(IPreferencesService).GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
if (!property.CanRead || !property.CanWrite || property.GetIndexParameters().Length > 0)
{
continue;
}
yield return property;
}
}
}
@@ -18,6 +18,7 @@ public static class WinoAccountAuthErrorTranslator
ApiErrorCodes.AccountLocked => Translator.WinoAccount_Error_AccountLocked,
ApiErrorCodes.AccountBanned => Translator.WinoAccount_Error_AccountBanned,
ApiErrorCodes.AccountSuspended => Translator.WinoAccount_Error_AccountSuspended,
ApiErrorCodes.EmailNotConfirmed => Translator.WinoAccount_Error_EmailNotConfirmed,
ApiErrorCodes.RefreshTokenInvalid => Translator.WinoAccount_Error_RefreshTokenInvalid,
ApiErrorCodes.EmailAlreadyRegistered => Translator.WinoAccount_Error_EmailAlreadyRegistered,
ApiErrorCodes.ExternalLoginEmailRequired => Translator.WinoAccount_Error_ExternalLoginEmailRequired,
@@ -74,6 +74,99 @@
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_AiPackSectionHeader}" />
<controls:SettingsCard
MaxWidth="520"
Description="{x:Bind domain:Translator.WinoAccount_Management_AiPackPromoDescription}">
<controls:SettingsCard.HeaderIcon>
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE945;" />
</controls:SettingsCard.HeaderIcon>
<controls:SettingsCard.Header>
<StackPanel Spacing="8">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock FontWeight="SemiBold" Text="{x:Bind domain:Translator.WinoAccount_Management_AiPackPromoTitle}" />
<Border
Padding="8,2"
Background="{ThemeResource SystemAccentColor}"
CornerRadius="4">
<TextBlock
FontSize="10"
FontWeight="Bold"
Foreground="White"
Text="PRO" />
</Border>
</StackPanel>
<TextBlock
MaxWidth="400"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind domain:Translator.WinoAccount_Management_AiPackPromoDescription}"
TextWrapping="WrapWholeWords" />
<StackPanel Orientation="Horizontal" Spacing="16">
<StackPanel Orientation="Horizontal" Spacing="4">
<FontIcon
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="12"
Glyph="&#xE8C1;" />
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureTranslate}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="4">
<FontIcon
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="12"
Glyph="&#xE70F;" />
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureRewrite}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="4">
<FontIcon
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="12"
Glyph="&#xE8FD;" />
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureSummarize}" />
</StackPanel>
</StackPanel>
<Border
Padding="12,8"
Background="{ThemeResource SystemFillColorCautionBackgroundBrush}"
CornerRadius="8">
<TextBlock
Foreground="{ThemeResource SystemFillColorCautionBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind domain:Translator.WinoAccount_Management_PurchaseRequiresSignIn}"
TextWrapping="WrapWholeWords" />
</Border>
<StackPanel
Margin="0,4,0,0"
Orientation="Horizontal"
Spacing="12">
<Button
Command="{x:Bind ViewModel.BuyAiPackCommand}"
Content="{x:Bind domain:Translator.Buttons_Purchase}"
IsEnabled="{x:Bind ViewModel.CanBuyAiPack, Mode=OneWay}"
Style="{StaticResource AccentButtonStyle}" />
<ProgressRing
Width="18"
Height="18"
IsActive="True"
Visibility="{x:Bind ViewModel.IsAiPackCheckoutInProgress, Mode=OneWay}" />
<TextBlock
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}">
<Run Text="{x:Bind domain:Translator.WinoAccount_Management_AiPackPromoPrice}" />
<Run Text=" · " />
<Run Text="{x:Bind domain:Translator.WinoAccount_Management_AiPackPromoRequests}" />
</TextBlock>
</StackPanel>
</StackPanel>
</controls:SettingsCard.Header>
</controls:SettingsCard>
</StackPanel>
</StackPanel>
@@ -181,9 +274,15 @@
Orientation="Horizontal"
Spacing="12">
<Button
Command="{x:Bind ViewModel.OpenBuyPageCommand}"
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackGetButton}"
Command="{x:Bind ViewModel.BuyAiPackCommand}"
Content="{x:Bind domain:Translator.Buttons_Purchase}"
IsEnabled="{x:Bind ViewModel.CanBuyAiPack, Mode=OneWay}"
Style="{StaticResource AccentButtonStyle}" />
<ProgressRing
Width="18"
Height="18"
IsActive="True"
Visibility="{x:Bind ViewModel.IsAiPackCheckoutInProgress, Mode=OneWay}" />
<TextBlock
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"