- fix: auth logic part 2

This commit is contained in:
2025-08-07 22:34:25 +03:00
parent 6a6bb4f27c
commit 6c967efd85
17 changed files with 440 additions and 397 deletions

View File

@@ -0,0 +1,99 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
namespace Nebula.Launcher.ViewModels.Pages;
public sealed class ArrayUnitConfigControl : Border, IConfigControl
{
private readonly List<IConfigControl> _itemControls = [];
private readonly StackPanel _itemsPanel = new StackPanel() { Orientation = Orientation.Vertical };
private readonly Button _addButton = new Button() { Content = new Label()
{
Content = "Add Item"
}, Classes = { "ConfigBorder" }};
private readonly int _oldCount;
private readonly Type _elementType;
private readonly StackPanel _panel = new();
public string ConfigName { get; }
public bool Dirty => _itemControls.Any(dirty => dirty.Dirty) || _itemControls.Count != _oldCount;
public ArrayUnitConfigControl(string name, object value)
{
Classes.Add("ConfigBorder");
_elementType = value.GetType().GetElementType()!;
ConfigName = name;
_panel.Orientation = Orientation.Vertical;
_panel.Spacing = 4f;
_itemsPanel.Spacing = 4f;
_panel.Children.Add(new Label { Content = name });
_panel.Children.Add(_itemsPanel);
_panel.Children.Add(_addButton);
_addButton.Click += (_, _) => AddItem(ConfigControlHelper.CreateDefaultValue(_elementType)!);
Child = _panel;
SetValue(value);
_oldCount = _itemControls.Count;
}
private void AddItem(object value)
{
var itemPanel = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 };
var control = ConfigControlHelper.GetConfigControl(_itemControls.Count.ToString(), value);
var removeButton = new Button { Content = new Label(){ Content = "Remove" }, Classes = { "ConfigBorder" }};
removeButton.Click += (_, _) =>
{
_itemControls.Remove(control);
_itemsPanel.Children.Remove(itemPanel);
};
((Control)control).Margin = new Thickness(5);
itemPanel.Children.Add((Control)control);
itemPanel.Children.Add(removeButton);
_itemsPanel.Children.Add(itemPanel);
_itemControls.Add(control);
}
public void SetValue(object value)
{
_itemControls.Clear();
_itemsPanel.Children.Clear();
if (value is IEnumerable list)
{
foreach (var item in list)
{
AddItem(item);
}
}
}
public object GetValue()
{
return ConvertArray(_itemControls.Select(c => c.GetValue()).ToArray(), _elementType);
}
public static Array ConvertArray(Array sourceArray, Type targetType)
{
int length = sourceArray.Length;
var newArray = Array.CreateInstance(targetType, length);
for (int i = 0; i < length; i++)
{
var value = sourceArray.GetValue(i);
var converted = Convert.ChangeType(value, targetType);
newArray.SetValue(converted, i);
}
return newArray;
}
}

View File

@@ -0,0 +1,86 @@
using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using Nebula.Shared.Configurations;
namespace Nebula.Launcher.Configurations;
public abstract class ComplexConVarBinder<T> : INotifyPropertyChanged, INotifyPropertyChanging
{
private readonly ConVarObserver<T> _baseConVar;
private readonly Lock _lock = new();
private readonly SemaphoreSlim _valueChangeSemaphore = new(1, 1);
public T? Value
{
get
{
lock (_lock)
{
return _baseConVar.Value;
}
}
set
{
_ = SetValueAsync(value);
}
}
public bool HasValue
{
get
{
lock (_lock)
{
return _baseConVar.HasValue;
}
}
}
protected ComplexConVarBinder(ConVarObserver<T> baseConVar)
{
_baseConVar = baseConVar ?? throw new ArgumentNullException(nameof(baseConVar));
_baseConVar.PropertyChanged += BaseConVarOnPropertyChanged;
_baseConVar.PropertyChanging += BaseConVarOnPropertyChanging;
}
private async Task SetValueAsync(T? value)
{
await _valueChangeSemaphore.WaitAsync().ConfigureAwait(false);
try
{
var newValue = await OnValueChange(value).ConfigureAwait(false);
lock (_lock)
{
_baseConVar.Value = newValue;
}
}
finally
{
_valueChangeSemaphore.Release();
}
}
protected abstract Task<T?> OnValueChange(T? newValue);
private void BaseConVarOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(HasValue)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
}
private void BaseConVarOnPropertyChanging(object? sender, PropertyChangingEventArgs e)
{
PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(HasValue)));
PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(Value)));
}
public event PropertyChangedEventHandler? PropertyChanged;
public event PropertyChangingEventHandler? PropertyChanging;
}

View File

@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
namespace Nebula.Launcher.ViewModels.Pages;
public sealed class ComplexUnitConfigControl : Border, IConfigControl
{
private readonly List<(PropertyInfo, IConfigControl)> _units = [];
private Type _objectType = typeof(object);
private readonly StackPanel _panel = new();
public string ConfigName { get; }
public bool Dirty => _units.Any(dirty => dirty.Item2.Dirty);
public ComplexUnitConfigControl(string name, object obj)
{
Classes.Add("ConfigBorder");
_panel.Orientation = Orientation.Vertical;
_panel.Spacing = 4f;
ConfigName = name;
Child = _panel;
SetValue(obj);
}
public void SetValue(object value)
{
_units.Clear();
_panel.Children.Clear();
_objectType = value.GetType();
_panel.Children.Add(new Label()
{
Content = ConfigName
});
foreach (var propInfo in _objectType.GetProperties())
{
if(propInfo.PropertyType.IsInterface)
continue;
var propValue = propInfo.GetValue(value);
var control = ConfigControlHelper.GetConfigControl(propInfo.Name, propValue!);
((Control)control).Margin = new Thickness(5);
_panel.Children.Add((Control)control);
_units.Add((propInfo,control));
}
}
public object GetValue()
{
var obj = ConfigControlHelper.CreateDefaultValue(_objectType);
foreach (var (fieldInfo, configControl) in _units)
{
fieldInfo.SetValue(obj, configControl.GetValue());
}
return obj!;
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Linq;
namespace Nebula.Launcher.ViewModels.Pages;
public static class ConfigControlHelper{
public static IConfigControl GetConfigControl(string name,object value)
{
switch (value)
{
case string stringValue:
return new StringUnitConfigControl(name, stringValue);
case int intValue:
return new IntUnitConfigControl(name, intValue);
case float floatValue:
return new FloatUnitConfigControl(name, floatValue);
}
var valueType = value.GetType();
if (valueType.IsArray)
return new ArrayUnitConfigControl(name, value);
return new ComplexUnitConfigControl(name, value);
}
public static object? CreateDefaultValue(Type type)
{
if(type.IsValueType)
return Activator.CreateInstance(type);
var ctor = type.GetConstructors().First();
var parameters = ctor.GetParameters()
.Select(p => CreateDefaultValue(p.ParameterType))
.ToArray();
return ctor.Invoke(parameters);
}
}

View File

@@ -0,0 +1,19 @@
using System.Globalization;
namespace Nebula.Launcher.ViewModels.Pages;
public sealed class FloatUnitConfigControl(string name, float value) : UnitConfigControl<float>(name, value)
{
public CultureInfo CultureInfo = CultureInfo.InvariantCulture;
public override void SetConfValue(float value)
{
ConfigValue = value.ToString(CultureInfo);
}
public override float GetConfValue()
{
return float.Parse(ConfigValue, CultureInfo);
}
}

View File

@@ -0,0 +1,9 @@
namespace Nebula.Launcher.ViewModels.Pages;
public interface IConfigControl
{
public string ConfigName { get; }
public bool Dirty {get;}
public abstract void SetValue(object value);
public abstract object GetValue();
}

View File

@@ -0,0 +1,14 @@
namespace Nebula.Launcher.ViewModels.Pages;
public sealed class IntUnitConfigControl(string name, int value) : UnitConfigControl<int>(name, value)
{
public override void SetConfValue(int value)
{
ConfigValue = value.ToString();
}
public override int GetConfValue()
{
return int.Parse(ConfigValue);
}
}

View File

@@ -0,0 +1,14 @@
namespace Nebula.Launcher.ViewModels.Pages;
public sealed class StringUnitConfigControl(string name, string value) : UnitConfigControl<string>(name, value)
{
public override void SetConfValue(string value)
{
ConfigValue = value;
}
public override string GetConfValue()
{
return ConfigValue;
}
}

View File

@@ -0,0 +1,53 @@
using Avalonia.Controls;
using Avalonia.Layout;
namespace Nebula.Launcher.ViewModels.Pages;
public abstract class UnitConfigControl<T> : Border, IConfigControl where T : notnull
{
private readonly Label _nameLabel = new();
private readonly TextBox _valueLabel = new();
private string _originalValue;
private StackPanel _panel = new();
public string ConfigName { get; }
public bool Dirty => _originalValue != ConfigValue;
protected string ConfigValue
{
get => _valueLabel.Text ?? string.Empty;
set => _valueLabel.Text = value;
}
public UnitConfigControl(string name, T value)
{
Classes.Add("ConfigBorder");
ConfigName = name;
_panel.Orientation = Orientation.Horizontal;
_panel.Children.Add(_nameLabel);
_panel.Children.Add(_valueLabel);
_nameLabel.Content = name;
_nameLabel.VerticalAlignment = VerticalAlignment.Center;
Child = _panel;
SetConfValue(value);
_originalValue = ConfigValue;
}
public abstract void SetConfValue(T value);
public abstract T GetConfValue();
public void SetValue(object value)
{
SetConfValue((T)value);
}
public object GetValue()
{
return GetConfValue()!;
}
}

View File

@@ -68,12 +68,16 @@ public partial class LocalisationService
public class LocaledText : MarkupExtension
{
public string Key { get; set; }
public Dictionary<string, object>? Options { get; set; }
public LocaledText(string key) => Key = key;
public LocaledText()
{
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
// Fetch the localized string using the key
return LocalisationService.GetString(Key);
return LocalisationService.GetString(Key, Options);
}
}

View File

@@ -40,16 +40,8 @@ public partial class MainViewModel : ViewModelBase
[ObservableProperty] private bool _isPopupClosable = true;
[ObservableProperty] private bool _popup;
[ObservableProperty] private ListItemTemplate? _selectedListItem;
public bool IsLoggedIn => AccountInfoViewModel.Credentials.Value is not null;
[ObservableProperty] private string? _loginText = LocalisationService.GetString("auth-current-login-no-name");
public string LoginText => LocalisationService.GetString("auth-current-login-name",
new Dictionary<string, object>
{
{ "login", AccountInfoViewModel.Credentials.Value?.Login ?? "" },
{ "auth_server", AccountInfoViewModel.CurrentAuthServerName}
});
[GenerateProperty] private LocalisationService LocalisationService { get; } // Не убирать! Без этой хуйни вся локализация идет в пизду!
[GenerateProperty] private AccountInfoViewModel AccountInfoViewModel { get; }
[GenerateProperty] private DebugService DebugService { get; } = default!;
@@ -74,15 +66,29 @@ public partial class MainViewModel : ViewModelBase
protected override void Initialise()
{
AccountInfoViewModel.PropertyChanged += (sender, args) =>
AccountInfoViewModel.Credentials.PropertyChanged += (_, args) =>
{
if (args.PropertyName != nameof(AccountInfoViewModel.Credentials))
return;
if (args.PropertyName is not nameof(AccountInfoViewModel.Credentials.Value)) return;
OnPropertyChanged(nameof(LoginText));
OnPropertyChanged(nameof(IsLoggedIn));
if(AccountInfoViewModel.Credentials.HasValue)
{
LoginText =
LocalisationService.GetString("auth-current-login-name",
new Dictionary<string, object>
{
{ "login", AccountInfoViewModel.Credentials.Value?.Login ?? "" },
{
"auth_server",
AccountInfoViewModel.GetServerAuthName(AccountInfoViewModel.Credentials.Value) ?? ""
}
});
}
else
{
LoginText = LocalisationService.GetString("auth-current-login-no-name");
}
};
_logger = DebugService.GetLogger(this);
using var stream = typeof(MainViewModel).Assembly

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using Nebula.Launcher.Configurations;
using Nebula.Launcher.Models.Auth;
using Nebula.Launcher.Services;
using Nebula.Launcher.ViewModels.Popup;
@@ -41,9 +42,8 @@ public partial class AccountInfoViewModel : ViewModelBase
public ObservableCollection<ProfileAuthCredentials> Accounts { get; } = new();
public ObservableCollection<AuthServerCredentials> AuthUrls { get; } = new();
public string CurrentAuthServerName => GetServerAuthName(Credentials.Value);
public ComplexConVarBinder<AuthTokenCredentials?> Credentials;
public ComplexConVarBinder<AuthTokenCredentials?> Credentials { get; private set; }
private ILogger _logger;
@@ -303,8 +303,8 @@ public partial class AccountInfoViewModel : ViewModelBase
{
var unexpectedError = new Exception(LocalisationService.GetString("auth-error"), e);
_logger.Error(unexpectedError);
PopupMessageService.Popup(unexpectedError);
return null;
//PopupMessageService.Popup(unexpectedError);
return authTokenCredentials;
}
}
@@ -364,7 +364,6 @@ public partial class AccountInfoViewModel : ViewModelBase
{
accountInfoViewModel.IsLogged = false;
accountInfoViewModel._logger.Log("clearing credentials");
accountInfoViewModel.OnPropertyChanged(nameof(CurrentAuthServerName));
return null;
}
@@ -408,8 +407,7 @@ public partial class AccountInfoViewModel : ViewModelBase
}
accountInfoViewModel.IsLogged = true;
accountInfoViewModel.OnPropertyChanged(nameof(CurrentAuthServerName));
return currProfile;
}
}

View File

@@ -1,16 +1,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
using Nebula.Launcher.Services;
using Nebula.Launcher.ViewModels.Popup;
using Nebula.Launcher.Views.Pages;
@@ -119,288 +112,4 @@ public partial class ConfigurationViewModel : ViewModelBase
{
InitConfiguration();
}
}
public static class ConfigControlHelper{
public static IConfigControl GetConfigControl(string name,object value)
{
switch (value)
{
case string stringValue:
return new StringUnitConfigControl(name, stringValue);
case int intValue:
return new IntUnitConfigControl(name, intValue);
case float floatValue:
return new FloatUnitConfigControl(name, floatValue);
}
var valueType = value.GetType();
if (valueType.IsArray)
return new ArrayUnitConfigControl(name, value);
return new ComplexUnitConfigControl(name, value);
}
public static object? CreateDefaultValue(Type type)
{
if(type.IsValueType)
return Activator.CreateInstance(type);
var ctor = type.GetConstructors().First();
var parameters = ctor.GetParameters()
.Select(p => CreateDefaultValue(p.ParameterType))
.ToArray();
return ctor.Invoke(parameters);
}
}
public sealed class ComplexUnitConfigControl : Border, IConfigControl
{
private readonly List<(PropertyInfo, IConfigControl)> _units = [];
private Type _objectType = typeof(object);
private readonly StackPanel _panel = new();
public string ConfigName { get; }
public bool Dirty => _units.Any(dirty => dirty.Item2.Dirty);
public ComplexUnitConfigControl(string name, object obj)
{
Classes.Add("ConfigBorder");
_panel.Orientation = Orientation.Vertical;
_panel.Spacing = 4f;
ConfigName = name;
Child = _panel;
SetValue(obj);
}
public void SetValue(object value)
{
_units.Clear();
_panel.Children.Clear();
_objectType = value.GetType();
_panel.Children.Add(new Label()
{
Content = ConfigName
});
foreach (var propInfo in _objectType.GetProperties())
{
if(propInfo.PropertyType.IsInterface)
continue;
var propValue = propInfo.GetValue(value);
var control = ConfigControlHelper.GetConfigControl(propInfo.Name, propValue!);
((Control)control).Margin = new Thickness(5);
_panel.Children.Add((Control)control);
_units.Add((propInfo,control));
}
}
public object GetValue()
{
var obj = ConfigControlHelper.CreateDefaultValue(_objectType);
foreach (var (fieldInfo, configControl) in _units)
{
fieldInfo.SetValue(obj, configControl.GetValue());
}
return obj!;
}
}
public sealed class ArrayUnitConfigControl : Border, IConfigControl
{
private readonly List<IConfigControl> _itemControls = [];
private readonly StackPanel _itemsPanel = new StackPanel() { Orientation = Orientation.Vertical };
private readonly Button _addButton = new Button() { Content = new Label()
{
Content = "Add Item"
}, Classes = { "ConfigBorder" }};
private readonly int _oldCount;
private readonly Type _elementType;
private readonly StackPanel _panel = new();
public string ConfigName { get; }
public bool Dirty => _itemControls.Any(dirty => dirty.Dirty) || _itemControls.Count != _oldCount;
public ArrayUnitConfigControl(string name, object value)
{
Classes.Add("ConfigBorder");
_elementType = value.GetType().GetElementType()!;
ConfigName = name;
_panel.Orientation = Orientation.Vertical;
_panel.Spacing = 4f;
_itemsPanel.Spacing = 4f;
_panel.Children.Add(new Label { Content = name });
_panel.Children.Add(_itemsPanel);
_panel.Children.Add(_addButton);
_addButton.Click += (_, _) => AddItem(ConfigControlHelper.CreateDefaultValue(_elementType)!);
Child = _panel;
SetValue(value);
_oldCount = _itemControls.Count;
}
private void AddItem(object value)
{
var itemPanel = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 };
var control = ConfigControlHelper.GetConfigControl(_itemControls.Count.ToString(), value);
var removeButton = new Button { Content = new Label(){ Content = "Remove" }, Classes = { "ConfigBorder" }};
removeButton.Click += (_, _) =>
{
_itemControls.Remove(control);
_itemsPanel.Children.Remove(itemPanel);
};
((Control)control).Margin = new Thickness(5);
itemPanel.Children.Add((Control)control);
itemPanel.Children.Add(removeButton);
_itemsPanel.Children.Add(itemPanel);
_itemControls.Add(control);
}
public void SetValue(object value)
{
_itemControls.Clear();
_itemsPanel.Children.Clear();
if (value is IEnumerable list)
{
foreach (var item in list)
{
AddItem(item);
}
}
}
public object GetValue()
{
return ConvertArray(_itemControls.Select(c => c.GetValue()).ToArray(), _elementType);
}
public static Array ConvertArray(Array sourceArray, Type targetType)
{
int length = sourceArray.Length;
var newArray = Array.CreateInstance(targetType, length);
for (int i = 0; i < length; i++)
{
var value = sourceArray.GetValue(i);
var converted = Convert.ChangeType(value, targetType);
newArray.SetValue(converted, i);
}
return newArray;
}
}
public abstract class UnitConfigControl<T> : Border, IConfigControl where T : notnull
{
private readonly Label _nameLabel = new();
private readonly TextBox _valueLabel = new();
private string _originalValue;
private StackPanel _panel = new();
public string ConfigName { get; }
public bool Dirty => _originalValue != ConfigValue;
protected string ConfigValue
{
get => _valueLabel.Text ?? string.Empty;
set => _valueLabel.Text = value;
}
public UnitConfigControl(string name, T value)
{
Classes.Add("ConfigBorder");
ConfigName = name;
_panel.Orientation = Orientation.Horizontal;
_panel.Children.Add(_nameLabel);
_panel.Children.Add(_valueLabel);
_nameLabel.Content = name;
_nameLabel.VerticalAlignment = VerticalAlignment.Center;
Child = _panel;
SetConfValue(value);
_originalValue = ConfigValue;
}
public abstract void SetConfValue(T value);
public abstract T GetConfValue();
public void SetValue(object value)
{
SetConfValue((T)value);
}
public object GetValue()
{
return GetConfValue()!;
}
}
public sealed class StringUnitConfigControl(string name, string value) : UnitConfigControl<string>(name, value)
{
public override void SetConfValue(string value)
{
ConfigValue = value;
}
public override string GetConfValue()
{
return ConfigValue;
}
}
public sealed class IntUnitConfigControl(string name, int value) : UnitConfigControl<int>(name, value)
{
public override void SetConfValue(int value)
{
ConfigValue = value.ToString();
}
public override int GetConfValue()
{
return int.Parse(ConfigValue);
}
}
public sealed class FloatUnitConfigControl(string name, float value) : UnitConfigControl<float>(name, value)
{
public CultureInfo CultureInfo = CultureInfo.InvariantCulture;
public override void SetConfValue(float value)
{
ConfigValue = value.ToString(CultureInfo);
}
public override float GetConfValue()
{
return float.Parse(ConfigValue, CultureInfo);
}
}
public interface IConfigControl
{
public string ConfigName { get; }
public bool Dirty {get;}
public abstract void SetValue(object value);
public abstract object GetValue();
}

View File

@@ -133,8 +133,9 @@
Path="/Assets/svg/user.svg"
Width="10" />
<Panel>
<TextBlock Foreground="#777777" IsVisible="{Binding IsLoggedIn}" Text="{Binding LoginText}"/>
<TextBlock Foreground="#777777" IsVisible="{Binding !IsLoggedIn}" Text="{services:LocaledText auth-current-login-no-name}"/>
<TextBlock
Foreground="#777777"
Text="{Binding LoginText}"/>
</Panel>
</StackPanel>
</Button>

View File

@@ -191,11 +191,11 @@
<StackPanel>
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal" Spacing="5">
<customControls:LocalizedLabel LocalId="account-auth-hello"/>
<TextBlock Text="{Binding CurrentLogin}" />
<TextBlock Text="{Binding Credentials.Value.Login}" />
</StackPanel>
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal" Spacing="5">
<customControls:LocalizedLabel LocalId="account-auth-current-server"/>
<TextBlock Text="{Binding CurrentAuthServerName}" />
<TextBlock Text="{Binding Credentials.Value.AuthServer}" />
</StackPanel>
</StackPanel>
</Label>