|
|
|
@ -1,8 +1,7 @@
|
|
|
|
|
using System; |
|
|
|
|
using System.Collections.Generic; |
|
|
|
|
using System.ComponentModel.DataAnnotations; |
|
|
|
|
using System.Security.Cryptography; |
|
|
|
|
using System.Text; |
|
|
|
|
using System.Text.Json; |
|
|
|
|
using System.Threading.Tasks; |
|
|
|
|
using AsyncAwaitBestPractices; |
|
|
|
|
using Avalonia.Controls; |
|
|
|
@ -10,19 +9,22 @@ using Avalonia.Threading;
|
|
|
|
|
using CommunityToolkit.Mvvm.ComponentModel; |
|
|
|
|
using CommunityToolkit.Mvvm.Input; |
|
|
|
|
using FluentAvalonia.UI.Controls; |
|
|
|
|
using StabilityMatrix.Avalonia.Controls; |
|
|
|
|
using StabilityMatrix.Avalonia.Languages; |
|
|
|
|
using StabilityMatrix.Avalonia.Services; |
|
|
|
|
using StabilityMatrix.Avalonia.ViewModels.Base; |
|
|
|
|
using StabilityMatrix.Avalonia.ViewModels.Dialogs; |
|
|
|
|
using StabilityMatrix.Avalonia.Views.Dialogs; |
|
|
|
|
using StabilityMatrix.Avalonia.Views.Settings; |
|
|
|
|
using StabilityMatrix.Core.Api; |
|
|
|
|
using StabilityMatrix.Core.Attributes; |
|
|
|
|
using StabilityMatrix.Core.Extensions; |
|
|
|
|
using StabilityMatrix.Core.Models.Api; |
|
|
|
|
using StabilityMatrix.Core.Models.Api.Lykos; |
|
|
|
|
using StabilityMatrix.Core.Processes; |
|
|
|
|
using StabilityMatrix.Core.Services; |
|
|
|
|
using Symbol = FluentIcons.Common.Symbol; |
|
|
|
|
using SymbolIconSource = FluentIcons.FluentAvalonia.SymbolIconSource; |
|
|
|
|
using TeachingTip = StabilityMatrix.Core.Models.Settings.TeachingTip; |
|
|
|
|
|
|
|
|
|
namespace StabilityMatrix.Avalonia.ViewModels.Settings; |
|
|
|
|
|
|
|
|
@ -31,6 +33,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Settings;
|
|
|
|
|
public partial class AccountSettingsViewModel : PageViewModelBase |
|
|
|
|
{ |
|
|
|
|
private readonly IAccountsService accountsService; |
|
|
|
|
private readonly ISettingsManager settingsManager; |
|
|
|
|
private readonly ServiceManager<ViewModelBase> vmFactory; |
|
|
|
|
private readonly INotificationService notificationService; |
|
|
|
|
private readonly ILykosAuthApi lykosAuthApi; |
|
|
|
@ -42,13 +45,6 @@ public partial class AccountSettingsViewModel : PageViewModelBase
|
|
|
|
|
public override IconSource IconSource => |
|
|
|
|
new SymbolIconSource { Symbol = Symbol.Person, IsFilled = true }; |
|
|
|
|
|
|
|
|
|
[ObservableProperty] |
|
|
|
|
private bool isLykosConnected; |
|
|
|
|
|
|
|
|
|
[ObservableProperty] |
|
|
|
|
[NotifyPropertyChangedFor(nameof(LykosProfileImageUrl))] |
|
|
|
|
private GetUserResponse? lykosUser; |
|
|
|
|
|
|
|
|
|
[ObservableProperty] |
|
|
|
|
private string? lykosProfileImageUrl; |
|
|
|
|
|
|
|
|
@ -56,33 +52,24 @@ public partial class AccountSettingsViewModel : PageViewModelBase
|
|
|
|
|
private bool isPatreonConnected; |
|
|
|
|
|
|
|
|
|
[ObservableProperty] |
|
|
|
|
private bool isCivitConnected; |
|
|
|
|
|
|
|
|
|
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(); |
|
|
|
|
[NotifyPropertyChangedFor(nameof(LykosProfileImageUrl))] |
|
|
|
|
private LykosAccountStatusUpdateEventArgs lykosStatus = |
|
|
|
|
LykosAccountStatusUpdateEventArgs.Disconnected; |
|
|
|
|
|
|
|
|
|
LykosProfileImageUrl = $"https://gravatar.com/avatar/{hash}?s=512&d=retro"; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
LykosProfileImageUrl = null; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
[ObservableProperty] |
|
|
|
|
private CivitAccountStatusUpdateEventArgs civitStatus = |
|
|
|
|
CivitAccountStatusUpdateEventArgs.Disconnected; |
|
|
|
|
|
|
|
|
|
public AccountSettingsViewModel( |
|
|
|
|
IAccountsService accountsService, |
|
|
|
|
ISettingsManager settingsManager, |
|
|
|
|
ServiceManager<ViewModelBase> vmFactory, |
|
|
|
|
INotificationService notificationService, |
|
|
|
|
ILykosAuthApi lykosAuthApi |
|
|
|
|
) |
|
|
|
|
{ |
|
|
|
|
this.accountsService = accountsService; |
|
|
|
|
this.settingsManager = settingsManager; |
|
|
|
|
this.vmFactory = vmFactory; |
|
|
|
|
this.notificationService = notificationService; |
|
|
|
|
this.lykosAuthApi = lykosAuthApi; |
|
|
|
@ -91,11 +78,18 @@ public partial class AccountSettingsViewModel : PageViewModelBase
|
|
|
|
|
{ |
|
|
|
|
Dispatcher.UIThread.Post(() => |
|
|
|
|
{ |
|
|
|
|
IsLykosConnected = args.IsConnected; |
|
|
|
|
LykosUser = args.User; |
|
|
|
|
LykosStatus = args; |
|
|
|
|
IsPatreonConnected = args.IsPatreonConnected; |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
accountsService.CivitAccountStatusUpdate += (_, args) => |
|
|
|
|
{ |
|
|
|
|
Dispatcher.UIThread.Post(() => |
|
|
|
|
{ |
|
|
|
|
CivitStatus = args; |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <inheritdoc /> |
|
|
|
@ -111,56 +105,67 @@ public partial class AccountSettingsViewModel : PageViewModelBase
|
|
|
|
|
accountsService.RefreshAsync().SafeFireAndForget(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
[RelayCommand] |
|
|
|
|
private async Task ConnectCivit() |
|
|
|
|
private async Task<bool> BeforeConnectCheck() |
|
|
|
|
{ |
|
|
|
|
var textFields = new TextBoxField[] { new() { Label = "API Key" } }; |
|
|
|
|
|
|
|
|
|
var dialog = DialogHelper.CreateTextEntryDialog("Connect CivitAI Account", "", textFields); |
|
|
|
|
|
|
|
|
|
// Show credentials storage notice if not seen |
|
|
|
|
if ( |
|
|
|
|
await dialog.ShowAsync() != ContentDialogResult.Primary |
|
|
|
|
|| textFields[0].Text is not { } json |
|
|
|
|
!settingsManager.Settings.SeenTeachingTips.Contains( |
|
|
|
|
TeachingTip.AccountsCredentialsStorageNotice |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
{ |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// TODO |
|
|
|
|
await Task.Delay(200); |
|
|
|
|
var dialog = new BetterContentDialog |
|
|
|
|
{ |
|
|
|
|
Title = "About Account Credentials", |
|
|
|
|
Content = """
|
|
|
|
|
Account credentials and tokens are stored locally on your computer, with at-rest AES encryption. |
|
|
|
|
|
|
|
|
|
If you make changes to your computer hardware, you may need to re-login to your accounts. |
|
|
|
|
|
|
|
|
|
Account tokens will not be viewable after saving, please make a note of them if you need to use them elsewhere. |
|
|
|
|
""",
|
|
|
|
|
PrimaryButtonText = Resources.Action_Continue, |
|
|
|
|
CloseButtonText = Resources.Action_Cancel, |
|
|
|
|
DefaultButton = ContentDialogButton.Primary, |
|
|
|
|
MaxDialogWidth = 400 |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
IsCivitConnected = true; |
|
|
|
|
if (await dialog.ShowAsync() != ContentDialogResult.Primary) |
|
|
|
|
{ |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
[RelayCommand] |
|
|
|
|
private async Task DisconnectCivit() |
|
|
|
|
{ |
|
|
|
|
await Task.Yield(); |
|
|
|
|
settingsManager.Transaction( |
|
|
|
|
s => s.SeenTeachingTips.Add(TeachingTip.AccountsCredentialsStorageNotice) |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
IsCivitConnected = false; |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
[RelayCommand] |
|
|
|
|
private async Task ConnectLykos() |
|
|
|
|
{ |
|
|
|
|
if (!await BeforeConnectCheck()) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
var vm = vmFactory.Get<LykosLoginViewModel>(); |
|
|
|
|
if (await vm.ShowDialogAsync() == TaskDialogStandardResult.OK) |
|
|
|
|
{ |
|
|
|
|
IsLykosConnected = true; |
|
|
|
|
await accountsService.RefreshAsync(); |
|
|
|
|
} |
|
|
|
|
await vm.ShowDialogAsync(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
[RelayCommand] |
|
|
|
|
private async Task DisconnectLykos() |
|
|
|
|
private Task DisconnectLykos() |
|
|
|
|
{ |
|
|
|
|
await accountsService.LykosLogoutAsync(); |
|
|
|
|
return accountsService.LykosLogoutAsync(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
[RelayCommand] |
|
|
|
|
private async Task ConnectPatreon() |
|
|
|
|
{ |
|
|
|
|
if (LykosUser?.Id is null) |
|
|
|
|
if (!await BeforeConnectCheck()) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
if (LykosStatus.User?.Id is null) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
var urlResult = await notificationService.TryAsync( |
|
|
|
@ -196,25 +201,78 @@ public partial class AccountSettingsViewModel : PageViewModelBase
|
|
|
|
|
await notificationService.TryAsync(accountsService.LykosPatreonOAuthLogoutAsync()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*[RelayCommand] |
|
|
|
|
private async Task ConnectCivitAccountOld() |
|
|
|
|
[RelayCommand] |
|
|
|
|
private async Task ConnectCivit() |
|
|
|
|
{ |
|
|
|
|
if (!await BeforeConnectCheck()) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
var textFields = new TextBoxField[] |
|
|
|
|
{ |
|
|
|
|
var textFields = new TextBoxField[] { new() { Label = "API Key" } }; |
|
|
|
|
new() |
|
|
|
|
{ |
|
|
|
|
Label = Resources.Label_ApiKey, |
|
|
|
|
Validator = s => |
|
|
|
|
{ |
|
|
|
|
if (string.IsNullOrWhiteSpace(s)) |
|
|
|
|
{ |
|
|
|
|
throw new ValidationException("API key is required"); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
var dialog = DialogHelper.CreateTextEntryDialog( |
|
|
|
|
"Connect CivitAI Account", |
|
|
|
|
"""
|
|
|
|
|
Login to [CivitAI](https://civitai.com/) and head to your [Account](https://civitai.com/user/account) page |
|
|
|
|
|
|
|
|
|
var dialog = DialogHelper.CreateTextEntryDialog("Connect CivitAI Account", "", textFields); |
|
|
|
|
Add a new API key and paste it below |
|
|
|
|
""",
|
|
|
|
|
"avares://StabilityMatrix.Avalonia/Assets/guide-civitai-api.webp", |
|
|
|
|
textFields |
|
|
|
|
); |
|
|
|
|
dialog.PrimaryButtonText = Resources.Action_Connect; |
|
|
|
|
|
|
|
|
|
if ( |
|
|
|
|
await dialog.ShowAsync() != ContentDialogResult.Primary |
|
|
|
|
|| textFields[0].Text is not { } json |
|
|
|
|
|| textFields[0].Text is not { } apiToken |
|
|
|
|
) |
|
|
|
|
{ |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var secrets = GlobalUserSecrets.LoadFromFile()!; |
|
|
|
|
secrets.CivitApiToken = json; |
|
|
|
|
secrets.SaveToFile(); |
|
|
|
|
var result = await notificationService.TryAsync(accountsService.CivitLoginAsync(apiToken)); |
|
|
|
|
|
|
|
|
|
RefreshCivitAccount().SafeFireAndForget(); |
|
|
|
|
}*/ |
|
|
|
|
if (result.IsSuccessful) |
|
|
|
|
{ |
|
|
|
|
await accountsService.RefreshAsync(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
[RelayCommand] |
|
|
|
|
private Task DisconnectCivit() |
|
|
|
|
{ |
|
|
|
|
return accountsService.CivitLogoutAsync(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Update the Lykos profile image URL when the user changes. |
|
|
|
|
/// </summary> |
|
|
|
|
partial void OnLykosStatusChanged(LykosAccountStatusUpdateEventArgs? value) |
|
|
|
|
{ |
|
|
|
|
if (value?.User?.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"; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
LykosProfileImageUrl = null; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|