feat: Refactor client of login part.
This commit is contained in:
parent
ec75cf88f3
commit
b1582ad4d4
@ -5,9 +5,17 @@
|
||||
<map>
|
||||
<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/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/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/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>
|
||||
</option>
|
||||
</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_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_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_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_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_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_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_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">
|
||||
<TestAncestor>
|
||||
<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"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:semi="https://irihi.tech/semi"
|
||||
x:Class="Flawless.Client.App"
|
||||
xmlns:local="using:Flawless.Client"
|
||||
RequestedThemeVariant="Default">
|
||||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||
|
||||
<Application.DataTemplates>
|
||||
<local:ViewLocator/>
|
||||
</Application.DataTemplates>
|
||||
RequestedThemeVariant="Light">
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
<semi:SemiTheme Locale="zh-cn"/>
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
@ -1,8 +1,11 @@
|
||||
using System.Reflection;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Flawless.Client.ViewModels;
|
||||
using Flawless.Client.Views;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Flawless.Client;
|
||||
|
||||
@ -11,13 +14,14 @@ public partial class App : Application
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetExecutingAssembly());
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindowView
|
||||
desktop.MainWindow = new MainWindowView()
|
||||
{
|
||||
DataContext = new MainWindowViewModel()
|
||||
};
|
||||
|
||||
@ -11,13 +11,12 @@
|
||||
<ItemGroup>
|
||||
<Folder Include="Models\" />
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
<Folder Include="Service\" />
|
||||
<Folder Include="Theme\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" 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" />
|
||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.1">
|
||||
@ -25,6 +24,9 @@
|
||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||
</PackageReference>
|
||||
<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.Hosting" Version="9.0.3" />
|
||||
<PackageReference Include="ReactiveUI.SourceGenerators" Version="2.1.27">
|
||||
@ -32,6 +34,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Refit" Version="8.0.0" />
|
||||
<PackageReference Include="Semi.Avalonia" Version="11.2.1.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using Avalonia;
|
||||
using Avalonia.ReactiveUI;
|
||||
using System;
|
||||
using Avalonia.Dialogs;
|
||||
|
||||
namespace Flawless.Client;
|
||||
|
||||
@ -16,6 +17,7 @@ sealed class Program
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
=> AppBuilder.Configure<App>()
|
||||
.UseManagedSystemDialogs()
|
||||
.UsePlatformDetect()
|
||||
.WithInterFont()
|
||||
.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.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Flawless.Client.Remote;
|
||||
using Flawless.Client.Service;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
using Refit;
|
||||
|
||||
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 _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 ReactiveCommand<Unit, Unit> ChooseServerCommand { get; }
|
||||
|
||||
public LoginViewModel(MainWindowViewModel main)
|
||||
public LoginViewModel(IScreen hostScreen)
|
||||
{
|
||||
_main = main;
|
||||
Title = $"Login into '{ApiHelper.ServerUrl}'";
|
||||
HostScreen = hostScreen;
|
||||
|
||||
var canLogin = this.WhenAnyValue(
|
||||
CanLogin = this.WhenAnyValue(
|
||||
x => x.Username,
|
||||
x => x.Password,
|
||||
(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()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private async Task OnLoginAsync()
|
||||
[ReactiveCommand(CanExecute = nameof(CanLogin))]
|
||||
private async Task LoginAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
ApiHelper.Token = await ApiHelper.Gateway.Login(new LoginRequest
|
||||
{
|
||||
Username = _username,
|
||||
Password = _password
|
||||
});
|
||||
await Api.Current.LoginAsync(Username, Password);
|
||||
}
|
||||
catch (ApiException ex)
|
||||
{
|
||||
@ -73,4 +65,5 @@ public partial class LoginViewModel : UserControlViewModelBase
|
||||
|
||||
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;
|
||||
|
||||
public partial class MainWindowViewModel : ViewModelBase
|
||||
public partial class MainWindowViewModel : ViewModelBase, IScreen
|
||||
{
|
||||
[Reactive(SetModifier = AccessModifier.Protected)]
|
||||
private UserControlViewModelBase _currentView;
|
||||
public RoutingState Router { get; } = new RoutingState();
|
||||
|
||||
public ReactiveCommand<Unit, IRoutableViewModel> GoBackCommand => Router.NavigateBack;
|
||||
|
||||
[Reactive] private bool _requireLogin = true;
|
||||
|
||||
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
CurrentView = new ServerSetupViewModel(this);
|
||||
}
|
||||
|
||||
public void RedirectToLogin()
|
||||
{
|
||||
CurrentView = new LoginViewModel(this);
|
||||
}
|
||||
|
||||
public void RedirectToServerSetup()
|
||||
{
|
||||
CurrentView = new ServerSetupViewModel(this);
|
||||
Api.Current.IsLoggedIn.Select(x => !x).BindTo(this, vm => vm.RequireLogin);
|
||||
}
|
||||
}
|
||||
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.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Flawless.Client.Service;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
using Refit;
|
||||
|
||||
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? _issue;
|
||||
[Reactive(SetModifier = AccessModifier.Protected)] private string? _issue;
|
||||
|
||||
private MainWindowViewModel _main;
|
||||
public IObservable<bool> CanSetHost { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SetHostCommand { get; }
|
||||
|
||||
|
||||
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)
|
||||
public ServerSetupViewModel(IScreen hostScreen)
|
||||
{
|
||||
_main = main;
|
||||
Title = "Connect to a Flawless Server";
|
||||
HostScreen = hostScreen;
|
||||
|
||||
// Must clear this gateway
|
||||
if (ApiHelper.IsGatewayReady) ApiHelper.ClearGateway();
|
||||
if (Api.Current.IsGatewayReady) Api.Current.ClearGateway();
|
||||
|
||||
SetHostCommand = ReactiveCommand.CreateFromTask(
|
||||
FetchServerDataAsync,
|
||||
this.WhenAnyValue(x => x.Host, s => !string.IsNullOrWhiteSpace(s)));
|
||||
CanSetHost = this.WhenAnyValue(x => x.Host, s => !string.IsNullOrWhiteSpace(s));
|
||||
}
|
||||
|
||||
private async Task FetchServerDataAsync()
|
||||
|
||||
[ReactiveCommand]
|
||||
private async Task SetHostAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
Issue = string.Empty;
|
||||
await ApiHelper.SetGatewayAsync(Host);
|
||||
_main.RedirectToLogin();
|
||||
await Api.Current.SetGatewayAsync(Host);
|
||||
HostScreen.Router.Navigate.Execute(new LoginViewModel(HostScreen));
|
||||
}
|
||||
catch (ApiException ex)
|
||||
{
|
||||
|
||||
@ -1,15 +1,5 @@
|
||||
using Avalonia.Controls;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Flawless.Client.ViewModels;
|
||||
|
||||
public abstract class ViewModelBase : ReactiveObject
|
||||
{
|
||||
}
|
||||
|
||||
public abstract partial class UserControlViewModelBase : ViewModelBase
|
||||
{
|
||||
[Reactive(SetModifier = AccessModifier.Protected)]
|
||||
private string _title;
|
||||
}
|
||||
public abstract class ViewModelBase : ReactiveObject {}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
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:DataType="vm:LoginViewModel">
|
||||
|
||||
@ -14,13 +14,11 @@
|
||||
</Design.DataContext>
|
||||
|
||||
<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="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="Register" Command="{Binding RegisterCommand}"/>
|
||||
<Button Content="Choose Server..." Command="{Binding ChooseServerCommand}"/>
|
||||
</StackPanel>
|
||||
<Label Content="{Binding Issue}" Foreground="Red"/>
|
||||
</StackPanel>
|
||||
|
||||
@ -1,12 +1,18 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
using Flawless.Client.ViewModels;
|
||||
using ReactiveUI;
|
||||
using Ursa.ReactiveUIExtension;
|
||||
|
||||
namespace Flawless.Client.Views;
|
||||
|
||||
public partial class LoginView : UserControl
|
||||
public partial class LoginView : ReactiveUrsaView<LoginViewModel>
|
||||
{
|
||||
public LoginView()
|
||||
{
|
||||
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:rxui="http://reactiveui.net"
|
||||
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"
|
||||
Title="{Binding CurrentView.Title}"
|
||||
d:DesignHeight="400" d:DesignWidth="400"
|
||||
x:CompileBindings="True"
|
||||
xmlns:views="clr-namespace:Flawless.Client.Views"
|
||||
x:DataType="vm:MainWindowViewModel"
|
||||
CanResize="False"
|
||||
Width="400" Height="400"
|
||||
Icon="/Assets/avalonia-logo.ico"
|
||||
mc:Ignorable="d">
|
||||
Title="Flawless Version Control"
|
||||
mc:Ignorable="d" d:DesignWidth="1024" d:DesignHeight="768"
|
||||
x:CompileBindings="True"
|
||||
CanResize="True" MinWidth="800" MinHeight="600"
|
||||
Icon="/Assets/avalonia-logo.ico">
|
||||
|
||||
<Design.DataContext>
|
||||
<vm:MainWindowViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<StackPanel Margin="30">
|
||||
<TransitioningContentControl Content="{Binding CurrentView}" />
|
||||
</StackPanel>
|
||||
</Window>
|
||||
<Panel>
|
||||
<views:ServerConnectView IsVisible="{Binding !!RequireLogin, Mode=OneWay}"/>
|
||||
<views:MainView IsVisible="{Binding !RequireLogin, Mode=OneWay}"/>
|
||||
<ursa:OverlayDialogHost/>
|
||||
</Panel>
|
||||
</ursa:UrsaWindow>
|
||||
@ -1,11 +1,16 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.ReactiveUI;
|
||||
using Flawless.Client.ViewModels;
|
||||
using ReactiveUI;
|
||||
using Ursa.ReactiveUIExtension;
|
||||
|
||||
namespace Flawless.Client.Views;
|
||||
|
||||
public partial class MainWindowView : Window
|
||||
public partial class MainWindowView : ReactiveUrsaWindow<MainWindowViewModel>
|
||||
{
|
||||
public MainWindowView()
|
||||
{
|
||||
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
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:DataType="vm:ServerSetupViewModel">
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
</Design.DataContext>
|
||||
|
||||
<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}"/>
|
||||
<Button Content="Connect" Command="{Binding SetHostCommand}"/>
|
||||
<Label Content="{Binding Issue}" Foreground="Red"/>
|
||||
|
||||
@ -1,13 +1,19 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
using Flawless.Client.ViewModels;
|
||||
using ReactiveUI;
|
||||
using Ursa.ReactiveUIExtension;
|
||||
|
||||
namespace Flawless.Client.Views;
|
||||
|
||||
public partial class ServerSetupView : UserControl
|
||||
public partial class ServerSetupView : ReactiveUrsaView<ServerSetupViewModel>
|
||||
{
|
||||
|
||||
public ServerSetupView()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.WhenActivated(disposables => { });
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user