feat: Refactor client of login part.
This commit is contained in:
parent
ec75cf88f3
commit
b1582ad4d4
@ -5,9 +5,17 @@
|
|||||||
<map>
|
<map>
|
||||||
<entry key="Flawless.Client.Avanonia/Views/MainWindow.axaml" value="Flawless.Client.Avanonia/Flawless.Client.Avanonia.csproj" />
|
<entry key="Flawless.Client.Avanonia/Views/MainWindow.axaml" value="Flawless.Client.Avanonia/Flawless.Client.Avanonia.csproj" />
|
||||||
<entry key="Flawless.Client/App.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
<entry key="Flawless.Client/App.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
|
<entry key="Flawless.Client/Theme/ToggleSwitch.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
|
<entry key="Flawless.Client/Views/HelloView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
<entry key="Flawless.Client/Views/HelloWindowView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
<entry key="Flawless.Client/Views/HelloWindowView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
|
<entry key="Flawless.Client/Views/LoginView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
|
<entry key="Flawless.Client/Views/MainView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
<entry key="Flawless.Client/Views/MainWindow.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
<entry key="Flawless.Client/Views/MainWindow.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
<entry key="Flawless.Client/Views/MainWindowView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
<entry key="Flawless.Client/Views/MainWindowView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
|
<entry key="Flawless.Client/Views/RegisterView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
|
<entry key="Flawless.Client/Views/ServerConnectView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
|
<entry key="Flawless.Client/Views/ServerConnectionView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
|
<entry key="Flawless.Client/Views/ServerSetupView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@ -17,14 +17,22 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJwtBearerHandler_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F61447741f88e235f7cd1a276ef5abe648b2dee4b210873893d178b861c9d0_003FJwtBearerHandler_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJwtBearerHandler_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F61447741f88e235f7cd1a276ef5abe648b2dee4b210873893d178b861c9d0_003FJwtBearerHandler_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APath_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa6b7f037ba7b44df80b8d3aa7e58eeb2e8e938_003F28_003F6a41ec86_003FPath_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APath_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa6b7f037ba7b44df80b8d3aa7e58eeb2e8e938_003F28_003F6a41ec86_003FPath_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReactiveObject_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F404d064a80dc4960b93f90c9bd69770750810_003F5c_003F228dd86b_003FReactiveObject_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReactiveObject_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F404d064a80dc4960b93f90c9bd69770750810_003F5c_003F228dd86b_003FReactiveObject_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReactiveUrsaView_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F01a3316df1f14aff8404556f39e0d9e52200_003F3c_003F7b186404_003FReactiveUrsaView_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReactiveUrsaWindow_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F01a3316df1f14aff8404556f39e0d9e52200_003F1f_003F9c292772_003FReactiveUrsaWindow_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARefitSettings_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fa2b3a0b86cd053ed984c171eb448b551b9a2a0c89c5a68191996cffac5d85d2b_003FRefitSettings_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARefitSettings_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fa2b3a0b86cd053ed984c171eb448b551b9a2a0c89c5a68191996cffac5d85d2b_003FRefitSettings_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceProvider_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fce37be1a06b16c6faa02038d2cc477dd3bca5b217ceeb41c5f2ad45c1bf9_003FServiceProvider_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceProvider_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fce37be1a06b16c6faa02038d2cc477dd3bca5b217ceeb41c5f2ad45c1bf9_003FServiceProvider_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASignInManager_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F40411c547364428dafc988a7615774e28b910_003F6d_003F1a409232_003FSignInManager_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASignInManager_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F40411c547364428dafc988a7615774e28b910_003F6d_003F1a409232_003FSignInManager_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStringValue_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fee39d1e9346e41aa9d44f0e1b1c6630f76268_003F49_003Fb92346b2_003FStringValue_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStringValue_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fee39d1e9346e41aa9d44f0e1b1c6630f76268_003F49_003Fb92346b2_003FStringValue_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATestMethodInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F5ef53d675c5d34a6b85963919015dc0c1b06e5ea9834aac59ae6911f4c6f38_003FTestMethodInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATestMethodInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F5ef53d675c5d34a6b85963919015dc0c1b06e5ea9834aac59ae6911f4c6f38_003FTestMethodInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUrsaWindow_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6c6e6c071c6e4189b3f95226392ec1b565000_003Fd0_003F599b987a_003FUrsaWindow_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUserManager_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb3abda585dc54c3f81f64fdda8fc5ba72b708_003F2f_003F1d20f21a_003FUserManager_00601_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUserManager_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb3abda585dc54c3f81f64fdda8fc5ba72b708_003F2f_003F1d20f21a_003FUserManager_00601_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUserManager_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fd56cb0a089b14dab96ad3ee133819f966d938_003F9c_003F183f8355_003FUserManager_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUserManager_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fd56cb0a089b14dab96ad3ee133819f966d938_003F9c_003F183f8355_003FUserManager_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AValueTuple_00602_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa6b7f037ba7b44df80b8d3aa7e58eeb2e8e938_003Fa7_003F76eb4679_003FValueTuple_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AValueTuple_00602_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa6b7f037ba7b44df80b8d3aa7e58eeb2e8e938_003Fa7_003F76eb4679_003FValueTuple_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AViewForMixins_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F404d064a80dc4960b93f90c9bd69770750810_003F65_003F2791a1c7_003FViewForMixins_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/Environment/AssemblyExplorer/XmlDocument/@EntryValue"><AssemblyExplorer>
|
||||||
|
<Assembly Path="C:\Users\Cardi\.nuget\packages\irihi.ursa\1.10.0\lib\net8.0\Ursa.dll" />
|
||||||
|
<Assembly Path="C:\Users\Cardi\.nuget\packages\irihi.ursa.themes.semi\1.10.0\lib\netstandard2.0\Ursa.Themes.Semi.dll" />
|
||||||
|
</AssemblyExplorer></s:String>
|
||||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=b5573ef9_002Db554_002D4a56_002D82c4_002D2531c8feef65/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" IsLocked="True" Name="PathValidationTest" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=b5573ef9_002Db554_002D4a56_002D82c4_002D2531c8feef65/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" IsLocked="True" Name="PathValidationTest" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||||
<TestAncestor>
|
<TestAncestor>
|
||||||
<TestId>MSTest::5B1CB26D-99F5-491A-B368-7E3552FE67E9::net9.0::Flawless.Abstract.Test.WorkPathTestUnit</TestId>
|
<TestId>MSTest::5B1CB26D-99F5-491A-B368-7E3552FE67E9::net9.0::Flawless.Abstract.Test.WorkPathTestUnit</TestId>
|
||||||
|
|||||||
@ -1,63 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Flawless.Client.Remote;
|
|
||||||
using Refit;
|
|
||||||
|
|
||||||
namespace Flawless.Client;
|
|
||||||
|
|
||||||
public static class ApiHelper
|
|
||||||
{
|
|
||||||
private static IFlawlessServer? _gateway;
|
|
||||||
|
|
||||||
public static void ClearGateway()
|
|
||||||
{
|
|
||||||
Status = null;
|
|
||||||
ServerUrl = null;
|
|
||||||
_gateway = null;
|
|
||||||
Token = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task SetGatewayAsync(string host)
|
|
||||||
{
|
|
||||||
var setting = new RefitSettings
|
|
||||||
{
|
|
||||||
AuthorizationHeaderValueGetter = (req, ct) => ApiHelper.Token != null ?
|
|
||||||
Task.FromResult<string>($"Bearer {ApiHelper.Token}") : Task.FromResult(string.Empty)
|
|
||||||
};
|
|
||||||
|
|
||||||
var tempGateway = RestService.For<IFlawlessServer>(host, setting);
|
|
||||||
Status = await tempGateway.Status();
|
|
||||||
ServerUrl = host;
|
|
||||||
_gateway = tempGateway;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string? ServerUrl { get; private set; }
|
|
||||||
|
|
||||||
public static ServerStatusResponse? Status { get; private set; }
|
|
||||||
|
|
||||||
public static TokenInfo? Token { get; set; }
|
|
||||||
|
|
||||||
public static bool IsGatewayReady => _gateway != null;
|
|
||||||
public static IFlawlessServer Gateway => _gateway ?? throw new InvalidProgramException("Not set gateway yet!");
|
|
||||||
|
|
||||||
|
|
||||||
public static bool RequireRefreshToken()
|
|
||||||
{
|
|
||||||
if (Token == null) return true;
|
|
||||||
if (DateTime.UtcNow.AddMinutes(1) > Token.Expiration) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async ValueTask<bool> TryRefreshTokenAsync()
|
|
||||||
{
|
|
||||||
if (Token == null) return false;
|
|
||||||
try { Token = await Gateway.Refresh(Token); }
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
await Console.Error.WriteLineAsync(e.ToString());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +1,10 @@
|
|||||||
<Application xmlns="https://github.com/avaloniaui"
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:semi="https://irihi.tech/semi"
|
||||||
x:Class="Flawless.Client.App"
|
x:Class="Flawless.Client.App"
|
||||||
xmlns:local="using:Flawless.Client"
|
RequestedThemeVariant="Light">
|
||||||
RequestedThemeVariant="Default">
|
|
||||||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
|
||||||
|
|
||||||
<Application.DataTemplates>
|
|
||||||
<local:ViewLocator/>
|
|
||||||
</Application.DataTemplates>
|
|
||||||
|
|
||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
<FluentTheme />
|
<semi:SemiTheme Locale="zh-cn"/>
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
</Application>
|
</Application>
|
||||||
@ -1,8 +1,11 @@
|
|||||||
|
using System.Reflection;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Flawless.Client.ViewModels;
|
using Flawless.Client.ViewModels;
|
||||||
using Flawless.Client.Views;
|
using Flawless.Client.Views;
|
||||||
|
using ReactiveUI;
|
||||||
|
using Splat;
|
||||||
|
|
||||||
namespace Flawless.Client;
|
namespace Flawless.Client;
|
||||||
|
|
||||||
@ -11,13 +14,14 @@ public partial class App : Application
|
|||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetExecutingAssembly());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnFrameworkInitializationCompleted()
|
public override void OnFrameworkInitializationCompleted()
|
||||||
{
|
{
|
||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
desktop.MainWindow = new MainWindowView
|
desktop.MainWindow = new MainWindowView()
|
||||||
{
|
{
|
||||||
DataContext = new MainWindowViewModel()
|
DataContext = new MainWindowViewModel()
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,13 +11,12 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Models\" />
|
<Folder Include="Models\" />
|
||||||
<AvaloniaResource Include="Assets\**" />
|
<AvaloniaResource Include="Assets\**" />
|
||||||
<Folder Include="Service\" />
|
<Folder Include="Theme\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="11.2.1" />
|
<PackageReference Include="Avalonia" Version="11.2.1" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="11.2.1" />
|
<PackageReference Include="Avalonia.Desktop" Version="11.2.1" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.1" />
|
|
||||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.1" />
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.1" />
|
||||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.1">
|
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.1">
|
||||||
@ -25,6 +24,9 @@
|
|||||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.2.1" />
|
<PackageReference Include="Avalonia.ReactiveUI" Version="11.2.1" />
|
||||||
|
<PackageReference Include="Irihi.Ursa" Version="1.10.0" />
|
||||||
|
<PackageReference Include="Irihi.Ursa.ReactiveUIExtension" Version="1.0.1" />
|
||||||
|
<PackageReference Include="Irihi.Ursa.Themes.Semi" Version="1.10.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.3" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.3" />
|
||||||
<PackageReference Include="ReactiveUI.SourceGenerators" Version="2.1.27">
|
<PackageReference Include="ReactiveUI.SourceGenerators" Version="2.1.27">
|
||||||
@ -32,6 +34,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Refit" Version="8.0.0" />
|
<PackageReference Include="Refit" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Semi.Avalonia" Version="11.2.1.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using System;
|
using System;
|
||||||
|
using Avalonia.Dialogs;
|
||||||
|
|
||||||
namespace Flawless.Client;
|
namespace Flawless.Client;
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ sealed class Program
|
|||||||
// Avalonia configuration, don't remove; also used by visual designer.
|
// Avalonia configuration, don't remove; also used by visual designer.
|
||||||
public static AppBuilder BuildAvaloniaApp()
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
=> AppBuilder.Configure<App>()
|
=> AppBuilder.Configure<App>()
|
||||||
|
.UseManagedSystemDialogs()
|
||||||
.UsePlatformDetect()
|
.UsePlatformDetect()
|
||||||
.WithInterFont()
|
.WithInterFont()
|
||||||
.LogToTrace()
|
.LogToTrace()
|
||||||
|
|||||||
102
Flawless.Client/Service/Api.cs
Normal file
102
Flawless.Client/Service/Api.cs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Flawless.Client.Remote;
|
||||||
|
using ReactiveUI;
|
||||||
|
using Refit;
|
||||||
|
|
||||||
|
namespace Flawless.Client.Service;
|
||||||
|
|
||||||
|
public class Api
|
||||||
|
{
|
||||||
|
#region Instance
|
||||||
|
|
||||||
|
private static Api? _instance;
|
||||||
|
|
||||||
|
public static Api Current => _instance ??= new Api();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public IObservable<bool> IsLoggedIn => _isLoggedIn;
|
||||||
|
|
||||||
|
public IObservable<string?> ServerUrl => _serverUrl;
|
||||||
|
|
||||||
|
public IObservable<ServerStatusResponse?> Status => _status;
|
||||||
|
|
||||||
|
public IObservable<TokenInfo?> Token => _token;
|
||||||
|
|
||||||
|
private readonly ReactiveProperty<bool> _isLoggedIn = new(false);
|
||||||
|
|
||||||
|
private readonly ReactiveProperty<string?> _serverUrl = new(string.Empty);
|
||||||
|
|
||||||
|
private readonly ReactiveProperty<ServerStatusResponse?> _status = new(null);
|
||||||
|
|
||||||
|
private readonly ReactiveProperty<TokenInfo?> _token = new(null);
|
||||||
|
|
||||||
|
#region GatewayConfig
|
||||||
|
|
||||||
|
private IFlawlessServer? _gateway;
|
||||||
|
|
||||||
|
public bool IsGatewayReady => _gateway != null;
|
||||||
|
|
||||||
|
public IFlawlessServer Gateway => _gateway ?? throw new InvalidProgramException("Not set gateway yet!");
|
||||||
|
|
||||||
|
public void ClearGateway()
|
||||||
|
{
|
||||||
|
_gateway = null;
|
||||||
|
_isLoggedIn.Value = false;
|
||||||
|
_status.Value = null;
|
||||||
|
_serverUrl.Value = null;
|
||||||
|
_token.Value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetGatewayAsync(string host)
|
||||||
|
{
|
||||||
|
var setting = new RefitSettings
|
||||||
|
{
|
||||||
|
AuthorizationHeaderValueGetter = (req, ct) => _token.Value != null ?
|
||||||
|
Task.FromResult<string>($"Bearer {_token}") : Task.FromResult(string.Empty)
|
||||||
|
};
|
||||||
|
|
||||||
|
var tempGateway = RestService.For<IFlawlessServer>(host, setting);
|
||||||
|
_status.Value = await tempGateway.Status();
|
||||||
|
_serverUrl.Value = host;
|
||||||
|
_gateway = tempGateway;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask LoginAsync(string username, string password)
|
||||||
|
{
|
||||||
|
_token.Value = await _gateway.Login(new LoginRequest
|
||||||
|
{
|
||||||
|
Username = username,
|
||||||
|
Password = password
|
||||||
|
});
|
||||||
|
|
||||||
|
_isLoggedIn.Value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region TokenOperations
|
||||||
|
|
||||||
|
public bool RequireRefreshToken()
|
||||||
|
{
|
||||||
|
if (_token.Value == null) return true;
|
||||||
|
if (DateTime.UtcNow.AddMinutes(1) > _token.Value.Expiration) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<bool> TryRefreshTokenAsync()
|
||||||
|
{
|
||||||
|
if (_token.Value == null) return false;
|
||||||
|
try { _token.Value = await Gateway.Refresh(_token.Value); }
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
await Console.Error.WriteLineAsync(e.ToString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -1,30 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Controls.Templates;
|
|
||||||
using Flawless.Client.ViewModels;
|
|
||||||
|
|
||||||
namespace Flawless.Client;
|
|
||||||
|
|
||||||
public class ViewLocator : IDataTemplate
|
|
||||||
{
|
|
||||||
public Control? Build(object? param)
|
|
||||||
{
|
|
||||||
if (param is null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
|
|
||||||
var type = Type.GetType(name);
|
|
||||||
|
|
||||||
if (type != null)
|
|
||||||
{
|
|
||||||
return (Control)Activator.CreateInstance(type)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new TextBlock { Text = "Not Found: " + name };
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Match(object? data)
|
|
||||||
{
|
|
||||||
return data is ViewModelBase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,64 +1,56 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
|
using System.Reactive.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Flawless.Client.Remote;
|
using Flawless.Client.Remote;
|
||||||
|
using Flawless.Client.Service;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ReactiveUI.SourceGenerators;
|
using ReactiveUI.SourceGenerators;
|
||||||
using Refit;
|
using Refit;
|
||||||
|
|
||||||
namespace Flawless.Client.ViewModels;
|
namespace Flawless.Client.ViewModels;
|
||||||
|
|
||||||
public partial class LoginViewModel : UserControlViewModelBase
|
public partial class LoginViewModel : ViewModelBase, IRoutableViewModel
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public string? UrlPathSegment { get; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
public IScreen HostScreen { get; }
|
||||||
|
|
||||||
[Reactive] private string _username = String.Empty;
|
[Reactive] private string _username = String.Empty;
|
||||||
|
|
||||||
[Reactive] private string _password = String.Empty;
|
[Reactive] private string _password = String.Empty;
|
||||||
|
|
||||||
[Reactive] private string _issue = String.Empty;
|
[Reactive(SetModifier = AccessModifier.Protected)] private string _issue = String.Empty;
|
||||||
|
|
||||||
private MainWindowViewModel _main;
|
public IObservable<bool> CanLogin;
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> LoginCommand { get; }
|
public IObservable<bool> CanRegister => Api.Current.Status.Select(s => s != null && s.AllowPublicRegister);
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> RegisterCommand { get; }
|
public LoginViewModel(IScreen hostScreen)
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> ChooseServerCommand { get; }
|
|
||||||
|
|
||||||
public LoginViewModel(MainWindowViewModel main)
|
|
||||||
{
|
{
|
||||||
_main = main;
|
HostScreen = hostScreen;
|
||||||
Title = $"Login into '{ApiHelper.ServerUrl}'";
|
|
||||||
|
|
||||||
var canLogin = this.WhenAnyValue(
|
CanLogin = this.WhenAnyValue(
|
||||||
x => x.Username,
|
x => x.Username,
|
||||||
x => x.Password,
|
x => x.Password,
|
||||||
(user, pass) => !string.IsNullOrEmpty(user) && user.Length > 3 && !string.IsNullOrEmpty(pass) && pass.Length >= 6
|
(user, pass) => !string.IsNullOrEmpty(user) && user.Length > 3 && !string.IsNullOrEmpty(pass) && pass.Length >= 6
|
||||||
);
|
);
|
||||||
|
|
||||||
LoginCommand = ReactiveCommand.CreateFromTask(OnLoginAsync, canLogin);
|
|
||||||
RegisterCommand = ReactiveCommand.Create(OnRegister);
|
|
||||||
ChooseServerCommand = ReactiveCommand.Create(OnChooseServer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnChooseServer()
|
[ReactiveCommand(CanExecute = nameof(CanRegister))]
|
||||||
|
private void Register()
|
||||||
{
|
{
|
||||||
_main.RedirectToServerSetup();
|
HostScreen.Router.Navigate.Execute(new RegisterViewModel(HostScreen));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRegister()
|
[ReactiveCommand(CanExecute = nameof(CanLogin))]
|
||||||
{
|
private async Task LoginAsync()
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task OnLoginAsync()
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ApiHelper.Token = await ApiHelper.Gateway.Login(new LoginRequest
|
await Api.Current.LoginAsync(Username, Password);
|
||||||
{
|
|
||||||
Username = _username,
|
|
||||||
Password = _password
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (ApiException ex)
|
catch (ApiException ex)
|
||||||
{
|
{
|
||||||
@ -73,4 +65,5 @@ public partial class LoginViewModel : UserControlViewModelBase
|
|||||||
|
|
||||||
Console.WriteLine($"Login as '{Username}' success!");
|
Console.WriteLine($"Login as '{Username}' success!");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,25 +1,22 @@
|
|||||||
using ReactiveUI.SourceGenerators;
|
using System.Reactive;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using Flawless.Client.Service;
|
||||||
|
using ReactiveUI;
|
||||||
|
using ReactiveUI.SourceGenerators;
|
||||||
|
|
||||||
namespace Flawless.Client.ViewModels;
|
namespace Flawless.Client.ViewModels;
|
||||||
|
|
||||||
public partial class MainWindowViewModel : ViewModelBase
|
public partial class MainWindowViewModel : ViewModelBase, IScreen
|
||||||
{
|
{
|
||||||
[Reactive(SetModifier = AccessModifier.Protected)]
|
public RoutingState Router { get; } = new RoutingState();
|
||||||
private UserControlViewModelBase _currentView;
|
|
||||||
|
public ReactiveCommand<Unit, IRoutableViewModel> GoBackCommand => Router.NavigateBack;
|
||||||
|
|
||||||
|
[Reactive] private bool _requireLogin = true;
|
||||||
|
|
||||||
|
|
||||||
public MainWindowViewModel()
|
public MainWindowViewModel()
|
||||||
{
|
{
|
||||||
CurrentView = new ServerSetupViewModel(this);
|
Api.Current.IsLoggedIn.Select(x => !x).BindTo(this, vm => vm.RequireLogin);
|
||||||
}
|
|
||||||
|
|
||||||
public void RedirectToLogin()
|
|
||||||
{
|
|
||||||
CurrentView = new LoginViewModel(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RedirectToServerSetup()
|
|
||||||
{
|
|
||||||
CurrentView = new ServerSetupViewModel(this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
62
Flawless.Client/ViewModels/RegisterViewModel.cs
Normal file
62
Flawless.Client/ViewModels/RegisterViewModel.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reactive;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia;
|
||||||
|
using Flawless.Client.Remote;
|
||||||
|
using Flawless.Client.Service;
|
||||||
|
using ReactiveUI;
|
||||||
|
using ReactiveUI.SourceGenerators;
|
||||||
|
using Refit;
|
||||||
|
|
||||||
|
namespace Flawless.Client.ViewModels;
|
||||||
|
|
||||||
|
public partial class RegisterViewModel : ViewModelBase, IRoutableViewModel
|
||||||
|
{
|
||||||
|
|
||||||
|
public string? UrlPathSegment { get; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
public IScreen HostScreen { get; }
|
||||||
|
|
||||||
|
[Reactive] private string _email;
|
||||||
|
|
||||||
|
[Reactive] private string _username;
|
||||||
|
|
||||||
|
[Reactive] private string _password;
|
||||||
|
|
||||||
|
[Reactive(SetModifier = AccessModifier.Protected)] private string _issue;
|
||||||
|
|
||||||
|
public RegisterViewModel(IScreen hostScreen)
|
||||||
|
{
|
||||||
|
HostScreen = hostScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[ReactiveCommand]
|
||||||
|
private async Task RegisterAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Api.Current.Gateway.Register(new RegisterRequest
|
||||||
|
{
|
||||||
|
Email = _email,
|
||||||
|
Username = _username,
|
||||||
|
Password = _password
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
await Api.Current.LoginAsync(Username, Password);
|
||||||
|
}
|
||||||
|
catch (ApiException ex)
|
||||||
|
{
|
||||||
|
await Console.Error.WriteLineAsync($"Register as '{Username}' Failed: {ex.Content}");
|
||||||
|
Issue = ex.Content ?? String.Empty;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await Console.Error.WriteLineAsync($"Register as '{Username}' Failed: {ex}");
|
||||||
|
Issue = ex.Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"Register as '{Username}' success!");
|
||||||
|
}
|
||||||
|
}
|
||||||
34
Flawless.Client/ViewModels/ServerConnectViewModel.cs
Normal file
34
Flawless.Client/ViewModels/ServerConnectViewModel.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reactive;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Reactive.Threading.Tasks;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Flawless.Client.Service;
|
||||||
|
using ReactiveUI;
|
||||||
|
using ReactiveUI.SourceGenerators;
|
||||||
|
using Ursa.ReactiveUIExtension;
|
||||||
|
|
||||||
|
namespace Flawless.Client.ViewModels;
|
||||||
|
|
||||||
|
public partial class ServerConnectViewModel : ViewModelBase, IScreen
|
||||||
|
{
|
||||||
|
public RoutingState Router { get; } = new RoutingState();
|
||||||
|
|
||||||
|
public ReactiveCommand<Unit, IRoutableViewModel> GoBackCommand => Router.NavigateBack;
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
private string _title = String.Empty;
|
||||||
|
|
||||||
|
[ReactiveCommand]
|
||||||
|
private async ValueTask OpenRepoPageAsync()
|
||||||
|
{
|
||||||
|
if (Api.Current.RequireRefreshToken()) await Router.NavigateAndReset.Execute(new ServerSetupViewModel(this)).ToTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerConnectViewModel()
|
||||||
|
{
|
||||||
|
Router.CurrentViewModel
|
||||||
|
.Select(vm => vm?.GetType().Name.Replace("ViewModel", string.Empty) ?? "Hello")
|
||||||
|
.BindTo(this, vm => vm.Title);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,47 +2,45 @@
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Flawless.Client.Service;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ReactiveUI.SourceGenerators;
|
using ReactiveUI.SourceGenerators;
|
||||||
using Refit;
|
using Refit;
|
||||||
|
|
||||||
namespace Flawless.Client.ViewModels;
|
namespace Flawless.Client.ViewModels;
|
||||||
|
|
||||||
public partial class ServerSetupViewModel : UserControlViewModelBase
|
public partial class ServerSetupViewModel : ViewModelBase, IRoutableViewModel
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public string? UrlPathSegment { get; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
public IScreen HostScreen { get; }
|
||||||
|
|
||||||
[Reactive] private string _host = "http://localhost:5256/";
|
[Reactive] private string _host = "http://localhost:5256/";
|
||||||
|
|
||||||
[Reactive] private string? _issue;
|
[Reactive(SetModifier = AccessModifier.Protected)] private string? _issue;
|
||||||
|
|
||||||
private MainWindowViewModel _main;
|
public IObservable<bool> CanSetHost { get; }
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> SetHostCommand { get; }
|
public ServerSetupViewModel(IScreen hostScreen)
|
||||||
|
|
||||||
|
|
||||||
private static readonly Regex httpRegex = new Regex(
|
|
||||||
@"^(?i)https?://(([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}|(\d{1,3}\.){3}\d{1,3})(:\d{1,5})?(/[\w\-\.~%!$&'()*+,;=:@/?#]*)?(?-i)$",
|
|
||||||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
|
||||||
|
|
||||||
public ServerSetupViewModel(MainWindowViewModel main)
|
|
||||||
{
|
{
|
||||||
_main = main;
|
HostScreen = hostScreen;
|
||||||
Title = "Connect to a Flawless Server";
|
|
||||||
|
|
||||||
// Must clear this gateway
|
// Must clear this gateway
|
||||||
if (ApiHelper.IsGatewayReady) ApiHelper.ClearGateway();
|
if (Api.Current.IsGatewayReady) Api.Current.ClearGateway();
|
||||||
|
|
||||||
SetHostCommand = ReactiveCommand.CreateFromTask(
|
CanSetHost = this.WhenAnyValue(x => x.Host, s => !string.IsNullOrWhiteSpace(s));
|
||||||
FetchServerDataAsync,
|
|
||||||
this.WhenAnyValue(x => x.Host, s => !string.IsNullOrWhiteSpace(s)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task FetchServerDataAsync()
|
|
||||||
|
[ReactiveCommand]
|
||||||
|
private async Task SetHostAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Issue = string.Empty;
|
Issue = string.Empty;
|
||||||
await ApiHelper.SetGatewayAsync(Host);
|
await Api.Current.SetGatewayAsync(Host);
|
||||||
_main.RedirectToLogin();
|
HostScreen.Router.Navigate.Execute(new LoginViewModel(HostScreen));
|
||||||
}
|
}
|
||||||
catch (ApiException ex)
|
catch (ApiException ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,15 +1,5 @@
|
|||||||
using Avalonia.Controls;
|
using ReactiveUI;
|
||||||
using ReactiveUI;
|
|
||||||
using ReactiveUI.SourceGenerators;
|
|
||||||
|
|
||||||
namespace Flawless.Client.ViewModels;
|
namespace Flawless.Client.ViewModels;
|
||||||
|
|
||||||
public abstract class ViewModelBase : ReactiveObject
|
public abstract class ViewModelBase : ReactiveObject {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract partial class UserControlViewModelBase : ViewModelBase
|
|
||||||
{
|
|
||||||
[Reactive(SetModifier = AccessModifier.Protected)]
|
|
||||||
private string _title;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:vm="using:Flawless.Client.ViewModels"
|
xmlns:vm="using:Flawless.Client.ViewModels"
|
||||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="400"
|
mc:Ignorable="d" d:DesignWidth="380" d:DesignHeight="540"
|
||||||
x:Class="Flawless.Client.Views.LoginView"
|
x:Class="Flawless.Client.Views.LoginView"
|
||||||
x:DataType="vm:LoginViewModel">
|
x:DataType="vm:LoginViewModel">
|
||||||
|
|
||||||
@ -14,13 +14,11 @@
|
|||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
|
|
||||||
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Spacing="10">
|
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Spacing="10">
|
||||||
<Image Source="/Assets/avalonia-logo.ico" Margin="20 20" MaxWidth="150"/>
|
|
||||||
<TextBox Watermark="Username" Text="{Binding Username, Mode=TwoWay}"/>
|
<TextBox Watermark="Username" Text="{Binding Username, Mode=TwoWay}"/>
|
||||||
<TextBox Watermark="Password" PasswordChar="*" Text="{Binding Password, Mode=TwoWay}"/>
|
<TextBox Watermark="Password" PasswordChar="*" Text="{Binding Password, Mode=TwoWay}"/>
|
||||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="10">
|
||||||
<Button Content="Login" Command="{Binding LoginCommand}"/>
|
<Button Content="Login" Command="{Binding LoginCommand}"/>
|
||||||
<Button Content="Register" Command="{Binding RegisterCommand}"/>
|
<Button Content="Register" Command="{Binding RegisterCommand}"/>
|
||||||
<Button Content="Choose Server..." Command="{Binding ChooseServerCommand}"/>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Label Content="{Binding Issue}" Foreground="Red"/>
|
<Label Content="{Binding Issue}" Foreground="Red"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@ -1,12 +1,18 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using Flawless.Client.ViewModels;
|
||||||
|
using ReactiveUI;
|
||||||
|
using Ursa.ReactiveUIExtension;
|
||||||
|
|
||||||
namespace Flawless.Client.Views;
|
namespace Flawless.Client.Views;
|
||||||
|
|
||||||
public partial class LoginView : UserControl
|
public partial class LoginView : ReactiveUrsaView<LoginViewModel>
|
||||||
{
|
{
|
||||||
public LoginView()
|
public LoginView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
this.WhenActivated(disposables => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
10
Flawless.Client/Views/MainView.axaml
Normal file
10
Flawless.Client/Views/MainView.axaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:ursa="https://irihi.tech/ursa"
|
||||||
|
xmlns:semi="https://irihi.tech/semi"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="1280" d:DesignHeight="768"
|
||||||
|
x:Class="Flawless.Client.Views.MainView">
|
||||||
|
<Label Content="Greet!"/>
|
||||||
|
</UserControl>
|
||||||
13
Flawless.Client/Views/MainView.axaml.cs
Normal file
13
Flawless.Client/Views/MainView.axaml.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace Flawless.Client.Views;
|
||||||
|
|
||||||
|
public partial class MainView : UserControl
|
||||||
|
{
|
||||||
|
public MainView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,23 +1,26 @@
|
|||||||
<Window x:Class="Flawless.Client.Views.MainWindowView"
|
<ursa:UrsaWindow x:Class="Flawless.Client.Views.MainWindowView"
|
||||||
|
xmlns:ursa="clr-namespace:Ursa.Controls;assembly=Ursa"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:rxui="http://reactiveui.net"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:vm="using:Flawless.Client.ViewModels"
|
xmlns:vm="using:Flawless.Client.ViewModels"
|
||||||
Title="{Binding CurrentView.Title}"
|
xmlns:views="clr-namespace:Flawless.Client.Views"
|
||||||
d:DesignHeight="400" d:DesignWidth="400"
|
|
||||||
x:CompileBindings="True"
|
|
||||||
x:DataType="vm:MainWindowViewModel"
|
x:DataType="vm:MainWindowViewModel"
|
||||||
CanResize="False"
|
Title="Flawless Version Control"
|
||||||
Width="400" Height="400"
|
mc:Ignorable="d" d:DesignWidth="1024" d:DesignHeight="768"
|
||||||
Icon="/Assets/avalonia-logo.ico"
|
x:CompileBindings="True"
|
||||||
mc:Ignorable="d">
|
CanResize="True" MinWidth="800" MinHeight="600"
|
||||||
|
Icon="/Assets/avalonia-logo.ico">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<vm:MainWindowViewModel />
|
<vm:MainWindowViewModel />
|
||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
|
|
||||||
<StackPanel Margin="30">
|
<Panel>
|
||||||
<TransitioningContentControl Content="{Binding CurrentView}" />
|
<views:ServerConnectView IsVisible="{Binding !!RequireLogin, Mode=OneWay}"/>
|
||||||
</StackPanel>
|
<views:MainView IsVisible="{Binding !RequireLogin, Mode=OneWay}"/>
|
||||||
</Window>
|
<ursa:OverlayDialogHost/>
|
||||||
|
</Panel>
|
||||||
|
</ursa:UrsaWindow>
|
||||||
@ -1,11 +1,16 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using Flawless.Client.ViewModels;
|
||||||
|
using ReactiveUI;
|
||||||
|
using Ursa.ReactiveUIExtension;
|
||||||
|
|
||||||
namespace Flawless.Client.Views;
|
namespace Flawless.Client.Views;
|
||||||
|
|
||||||
public partial class MainWindowView : Window
|
public partial class MainWindowView : ReactiveUrsaWindow<MainWindowViewModel>
|
||||||
{
|
{
|
||||||
public MainWindowView()
|
public MainWindowView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
this.WhenActivated(disposables => { });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
25
Flawless.Client/Views/RegisterView.axaml
Normal file
25
Flawless.Client/Views/RegisterView.axaml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:vm="clr-namespace:Flawless.Client.ViewModels"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="380" d:DesignHeight="540"
|
||||||
|
x:Class="Flawless.Client.Views.RegisterView"
|
||||||
|
x:DataType="vm:RegisterViewModel">
|
||||||
|
|
||||||
|
<Design.DataContext>
|
||||||
|
<!-- This only sets the DataContext for the previewer in an IDE,
|
||||||
|
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||||
|
<vm:RegisterViewModel/>
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Spacing="10">
|
||||||
|
<TextBox Watermark="Email" Text="{Binding Email, Mode=TwoWay}"/>
|
||||||
|
<TextBox Watermark="Username" Text="{Binding Username, Mode=TwoWay}"/>
|
||||||
|
<TextBox Watermark="Password" PasswordChar="*" Text="{Binding Password, Mode=TwoWay}"/>
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="{DynamicResource Semi}">
|
||||||
|
<Button Content="Register" Command="{Binding RegisterCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<Label Content="{Binding Issue}" Foreground="Red"/>
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>
|
||||||
15
Flawless.Client/Views/RegisterView.axaml.cs
Normal file
15
Flawless.Client/Views/RegisterView.axaml.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using Flawless.Client.ViewModels;
|
||||||
|
using ReactiveUI;
|
||||||
|
using Ursa.ReactiveUIExtension;
|
||||||
|
|
||||||
|
namespace Flawless.Client.Views;
|
||||||
|
|
||||||
|
public partial class RegisterView : ReactiveUrsaView<RegisterViewModel>
|
||||||
|
{
|
||||||
|
public RegisterView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
this.WhenActivated(disposables => { });
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Flawless.Client/Views/ServerConnectView.axaml
Normal file
28
Flawless.Client/Views/ServerConnectView.axaml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:vm="using:Flawless.Client.ViewModels"
|
||||||
|
xmlns:reactiveUi="http://reactiveui.net"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Flawless.Client.Views.ServerConnectView"
|
||||||
|
x:DataType="vm:ServerConnectViewModel">
|
||||||
|
|
||||||
|
<Border Classes="Shadow" Theme="{StaticResource CardBorder}" MaxWidth="400" MaxHeight="400">
|
||||||
|
<DockPanel>
|
||||||
|
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" VerticalAlignment="Center" Spacing="18">
|
||||||
|
<Button Command="{Binding GoBackCommand}"
|
||||||
|
Theme="{DynamicResource BorderlessButton}"
|
||||||
|
Content="Back"/>
|
||||||
|
<Label FontSize="24" Content="{Binding Title}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<reactiveUi:RoutedViewHost Router="{Binding Router}">
|
||||||
|
<reactiveUi:RoutedViewHost.DefaultContent>
|
||||||
|
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||||
|
<Button Content="Login to Flawless" Command="{Binding OpenRepoPageAsyncCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</reactiveUi:RoutedViewHost.DefaultContent>
|
||||||
|
</reactiveUi:RoutedViewHost>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
||||||
15
Flawless.Client/Views/ServerConnectView.axaml.cs
Normal file
15
Flawless.Client/Views/ServerConnectView.axaml.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Flawless.Client.ViewModels;
|
||||||
|
|
||||||
|
namespace Flawless.Client.Views;
|
||||||
|
|
||||||
|
public partial class ServerConnectView : UserControl
|
||||||
|
{
|
||||||
|
public ServerConnectView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
DataContext = new ServerConnectViewModel();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,7 +3,7 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:vm="clr-namespace:Flawless.Client.ViewModels"
|
xmlns:vm="clr-namespace:Flawless.Client.ViewModels"
|
||||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="400"
|
mc:Ignorable="d" d:DesignWidth="380" d:DesignHeight="540"
|
||||||
x:Class="Flawless.Client.Views.ServerSetupView"
|
x:Class="Flawless.Client.Views.ServerSetupView"
|
||||||
x:DataType="vm:ServerSetupViewModel">
|
x:DataType="vm:ServerSetupViewModel">
|
||||||
|
|
||||||
@ -14,7 +14,6 @@
|
|||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
|
|
||||||
<StackPanel Spacing="10" HorizontalAlignment="Stretch" VerticalAlignment="Center">
|
<StackPanel Spacing="10" HorizontalAlignment="Stretch" VerticalAlignment="Center">
|
||||||
<Image Source="/Assets/avalonia-logo.ico" Margin="20 20" MaxWidth="150"/>
|
|
||||||
<TextBox Watermark="Host" Text="{Binding Host, Mode=TwoWay}"/>
|
<TextBox Watermark="Host" Text="{Binding Host, Mode=TwoWay}"/>
|
||||||
<Button Content="Connect" Command="{Binding SetHostCommand}"/>
|
<Button Content="Connect" Command="{Binding SetHostCommand}"/>
|
||||||
<Label Content="{Binding Issue}" Foreground="Red"/>
|
<Label Content="{Binding Issue}" Foreground="Red"/>
|
||||||
|
|||||||
@ -1,13 +1,19 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using Flawless.Client.ViewModels;
|
||||||
|
using ReactiveUI;
|
||||||
|
using Ursa.ReactiveUIExtension;
|
||||||
|
|
||||||
namespace Flawless.Client.Views;
|
namespace Flawless.Client.Views;
|
||||||
|
|
||||||
public partial class ServerSetupView : UserControl
|
public partial class ServerSetupView : ReactiveUrsaView<ServerSetupViewModel>
|
||||||
{
|
{
|
||||||
|
|
||||||
public ServerSetupView()
|
public ServerSetupView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
this.WhenActivated(disposables => { });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user