Browse Source

Add lykos signup and username profile image display

pull/324/head
Ionite 1 year ago
parent
commit
f61b1c8da2
No known key found for this signature in database
  1. 23
      StabilityMatrix.Avalonia/Services/AccountsService.cs
  2. 2
      StabilityMatrix.Avalonia/Services/IAccountsService.cs
  3. 41
      StabilityMatrix.Avalonia/ViewModels/Dialogs/LykosLoginViewModel.cs
  4. 22
      StabilityMatrix.Avalonia/ViewModels/Settings/AccountSettingsViewModel.cs
  5. 20
      StabilityMatrix.Avalonia/Views/Dialogs/LykosLoginDialog.axaml
  6. 11
      StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml
  7. 12
      StabilityMatrix.Core/Api/ILykosAuthApi.cs
  8. 12
      StabilityMatrix.Core/Exceptions/AppException.cs
  9. 2
      StabilityMatrix.Core/Models/Api/Lykos/GetUserResponse.cs
  10. 3
      StabilityMatrix.Core/Models/Api/Lykos/LykosAccount.cs
  11. 3
      StabilityMatrix.Core/Models/Api/Lykos/LykosAccountTokens.cs
  12. 8
      StabilityMatrix.Core/Models/Api/Lykos/PostAccountRequest.cs
  13. 3
      StabilityMatrix.Core/Models/Api/Lykos/PostLoginResponse.cs

23
StabilityMatrix.Avalonia/Services/AccountsService.cs

@ -40,10 +40,25 @@ public class AccountsService : IAccountsService
{
var secrets = GlobalUserSecrets.LoadFromFile();
var loginResponse = await lykosAuthApi.PostLogin(new PostLoginRequest(email, password));
var tokens = await lykosAuthApi.PostLogin(new PostLoginRequest(email, password));
secrets.LykosAccessToken = loginResponse.AccessToken;
secrets.LykosRefreshToken = loginResponse.RefreshToken;
secrets.LykosAccessToken = tokens.AccessToken;
secrets.LykosRefreshToken = tokens.RefreshToken;
secrets.SaveToFile();
await RefreshAsync();
}
public async Task LykosSignupAsync(string email, string password, string username)
{
var secrets = GlobalUserSecrets.LoadFromFile();
var tokens = await lykosAuthApi.PostAccount(
new PostAccountRequest(email, password, password, username)
);
secrets.LykosAccessToken = tokens.AccessToken;
secrets.LykosRefreshToken = tokens.RefreshToken;
secrets.SaveToFile();
await RefreshAsync();
@ -75,7 +90,7 @@ public class AccountsService : IAccountsService
{
try
{
var user = await lykosAuthApi.GetUser("dev@ionite.io");
var user = await lykosAuthApi.GetUserSelf();
OnLykosAccountStatusUpdate(
new LykosAccountStatusUpdateEventArgs { IsConnected = true, User = user }

2
StabilityMatrix.Avalonia/Services/IAccountsService.cs

@ -10,6 +10,8 @@ public interface IAccountsService
LykosAccountStatusUpdateEventArgs? LykosStatus { get; }
Task LykosSignupAsync(string email, string password, string username);
Task LykosLoginAsync(string email, string password);
Task LykosLogoutAsync();

41
StabilityMatrix.Avalonia/ViewModels/Dialogs/LykosLoginViewModel.cs

@ -5,11 +5,13 @@ using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using Refit;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views.Dialogs;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Exceptions;
using StabilityMatrix.Core.Validators;
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
@ -44,6 +46,12 @@ public partial class LykosLoginViewModel : TaskDialogViewModelBase
[Required, RequiresMatch<string>(nameof(Password))]
private string? confirmPassword;
[ObservableProperty]
private AppException? loginError;
[ObservableProperty]
private AppException? signupError;
public LykosLoginViewModel(IAccountsService accountsService)
{
this.accountsService = accountsService;
@ -55,7 +63,12 @@ public partial class LykosLoginViewModel : TaskDialogViewModelBase
}
[RelayCommand(CanExecute = nameof(CanExecuteContinueButtonClick))]
private async Task OnContinueButtonClick()
private Task OnContinueButtonClick()
{
return IsSignupMode ? SignupAsync() : LoginAsync();
}
private async Task LoginAsync()
{
try
{
@ -63,9 +76,31 @@ public partial class LykosLoginViewModel : TaskDialogViewModelBase
CloseDialog(TaskDialogStandardResult.OK);
}
catch (Exception e)
catch (OperationCanceledException)
{
LoginError = new AppException("Request timed out", "Please try again later");
}
catch (ApiException e)
{
LoginError = new AppException("Failed to login", $"{e.StatusCode} - {e.Message}");
}
}
private async Task SignupAsync()
{
try
{
await accountsService.LykosSignupAsync(Email!, Password!, Username!);
CloseDialog(TaskDialogStandardResult.OK);
}
catch (OperationCanceledException)
{
SignupError = new AppException("Request timed out", "Please try again later");
}
catch (ApiException e)
{
Console.WriteLine(e);
SignupError = new AppException("Failed to signup", $"{e.StatusCode} - {e.Message}");
}
}

22
StabilityMatrix.Avalonia/ViewModels/Settings/AccountSettingsViewModel.cs

@ -1,4 +1,7 @@
using System.Threading.Tasks;
using System;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
@ -38,8 +41,25 @@ public partial class AccountSettingsViewModel : PageViewModelBase
private bool isLykosConnected;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(LykosProfileImageUrl))]
private GetUserResponse? lykosUser;
[ObservableProperty]
private string? lykosProfileImageUrl;
partial void OnLykosUserChanged(GetUserResponse? value)
{
if (value?.Id is { } userEmail)
{
userEmail = userEmail.Trim().ToLowerInvariant();
var hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(userEmail));
var hash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
LykosProfileImageUrl = $"https://gravatar.com/avatar/{hash}?s=512&d=retro";
}
}
public AccountSettingsViewModel(
IAccountsService accountsService,
ServiceManager<ViewModelBase> vmFactory

20
StabilityMatrix.Avalonia/Views/Dialogs/LykosLoginDialog.axaml

@ -29,7 +29,7 @@
<sg:SpacedGrid
Margin="-4,32,-4,8"
RowDefinitions="Auto,Auto,*"
RowDefinitions="Auto,Auto,Auto,*"
RowSpacing="8">
<!-- Username -->
<TextBox
@ -44,11 +44,18 @@
Watermark="{x:Static lang:Resources.Label_Password}"
Grid.Row="1" />
<ui:InfoBar
Severity="Error"
IsOpen="{Binding LoginError, Converter={x:Static ObjectConverters.IsNotNull}}"
Title="{Binding LoginError.Message}"
Message="{Binding LoginError.Details}"
Grid.Row="2"/>
<controls:HyperlinkIconButton
VerticalAlignment="Bottom"
Content="Forgot Password?"
NavigateUri="{x:Static sm:Assets.LykosForgotPasswordUrl}"
Grid.Row="2"/>
Grid.Row="3"/>
</sg:SpacedGrid>
</TabItem>
@ -58,7 +65,7 @@
<sg:SpacedGrid
Margin="-4,32,-4,8"
RowDefinitions="Auto,Auto,Auto,Auto"
RowDefinitions="Auto,Auto,Auto,Auto,Auto"
RowSpacing="8">
<!-- Email -->
<TextBox
@ -86,6 +93,13 @@
Watermark="{x:Static lang:Resources.Label_ConfirmPassword}"
UseFloatingWatermark="True"
Grid.Row="3" />
<ui:InfoBar
Severity="Error"
IsOpen="{Binding SignupError, Converter={x:Static ObjectConverters.IsNotNull}}"
Title="{Binding SignupError.Message}"
Message="{Binding SignupError.Details}"
Grid.Row="4"/>
</sg:SpacedGrid>
</TabItem>

11
StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml

@ -68,22 +68,24 @@
<sg:SpacedGrid Margin="0,4,0,16" ColumnDefinitions="*,*">
<StackPanel Orientation="Horizontal" Spacing="16">
<fluentIcons:SymbolIcon
IsVisible="{Binding LykosProfileImageUrl, Converter={x:Static StringConverters.IsNullOrEmpty}}"
Width="64"
Height="64"
IsFilled="True"
FontSize="64"
Symbol="Person" />
<!--<controls:BetterAdvancedImage
<controls:BetterAdvancedImage
HorizontalAlignment="Left"
Source="Avares://StabilityMatrix.Avalonia/Assets/noimage.png"
IsVisible="{Binding LykosProfileImageUrl, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Source="{Binding LykosProfileImageUrl}"
Width="64"
Height="64"
CornerRadius="8" />-->
CornerRadius="8" />
<StackPanel>
<TextBlock
Margin="-1,0,0,0"
Text="{Binding LykosUser.AccountId, FallbackValue=''}"
Text="{Binding LykosUser.Account.Name, FallbackValue=''}"
Theme="{DynamicResource SubtitleTextBlockStyle}" />
<TextBlock Text="{Binding LykosUser.Id, FallbackValue=''}" />
</StackPanel>
@ -99,6 +101,7 @@
<controls:SettingsAccountLinkExpander.IconSource>
<ui:ImageIconSource Source="{StaticResource BrandsLykos}" />
</controls:SettingsAccountLinkExpander.IconSource>
</controls:SettingsAccountLinkExpander>
<sg:SpacedGrid RowDefinitions="Auto,*,*" RowSpacing="4">

12
StabilityMatrix.Core/Api/ILykosAuthApi.cs

@ -10,8 +10,18 @@ public interface ILykosAuthApi
[Get("/api/Users/{email}")]
Task<GetUserResponse> GetUser(string email, CancellationToken cancellationToken = default);
[Headers("Authorization: Bearer")]
[Get("/api/Users/me")]
Task<GetUserResponse> GetUserSelf(CancellationToken cancellationToken = default);
[Post("/api/Accounts")]
Task<LykosAccountTokens> PostAccount(
[Body] PostAccountRequest request,
CancellationToken cancellationToken = default
);
[Post("/api/Login")]
Task<PostLoginResponse> PostLogin(
Task<LykosAccountTokens> PostLogin(
[Body] PostLoginRequest request,
CancellationToken cancellationToken = default
);

12
StabilityMatrix.Core/Exceptions/AppException.cs

@ -5,5 +5,17 @@ namespace StabilityMatrix.Core.Exceptions;
/// </summary>
public class AppException : ApplicationException
{
public override string Message { get; }
public string? Details { get; init; }
public AppException(string message)
{
Message = message;
}
public AppException(string message, string details)
{
Message = message;
Details = details;
}
}

2
StabilityMatrix.Core/Models/Api/Lykos/GetUserResponse.cs

@ -2,7 +2,7 @@
public record GetUserResponse(
string Id,
string AccountId,
LykosAccount Account,
int UserLevel,
string PatreonId,
bool IsEmailVerified

3
StabilityMatrix.Core/Models/Api/Lykos/LykosAccount.cs

@ -0,0 +1,3 @@
namespace StabilityMatrix.Core.Models.Api.Lykos;
public record LykosAccount(string Id, string Name);

3
StabilityMatrix.Core/Models/Api/Lykos/LykosAccountTokens.cs

@ -0,0 +1,3 @@
namespace StabilityMatrix.Core.Models.Api.Lykos;
public record LykosAccountTokens(string AccessToken, string RefreshToken);

8
StabilityMatrix.Core/Models/Api/Lykos/PostAccountRequest.cs

@ -0,0 +1,8 @@
namespace StabilityMatrix.Core.Models.Api.Lykos;
public record PostAccountRequest(
string Email,
string Password,
string ConfirmPassword,
string AccountName
);

3
StabilityMatrix.Core/Models/Api/Lykos/PostLoginResponse.cs

@ -1,3 +0,0 @@
namespace StabilityMatrix.Core.Models.Api.Lykos;
public record PostLoginResponse(string AccessToken, string RefreshToken);
Loading…
Cancel
Save