feat: Advanced Use of ReactiveUI.Validation (#44)

* Update packages, add validation messages to UWP
* Don't use the obsolete ReactiveUI.Validation APIs
* Update README.md
* Add validations for Xamarin.Forms
This commit is contained in:
Artyom V. Gorchakov 2020-10-24 01:20:11 +03:00 коммит произвёл GitHub
Родитель 74b0ea6691
Коммит 0a037dfc38
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
38 изменённых файлов: 322 добавлений и 45 удалений

Просмотреть файл

@ -47,9 +47,11 @@ To compile the <a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/"
### Technologies and Tools Used
- <a href="https://reactiveui.net/">ReactiveUI</a> modern MVVM framework
- <a href="https://github.com/reactiveui/reactiveui.validation">ReactiveUI.Validation</a> reactive validation library
- <a href="https://reactiveui.net/docs/handbook/events/">ReactiveUI.Events</a> turning regular events into observables
- <a href="https://github.com/reactiveui/DynamicData">DynamicData</a> reactive collections
- <a href="http://github.com/avaloniaui">AvaloniaUI</a> cross-platform XAML-based GUI framework
- <a href="http://github.com/worldbeater/citrus.avalonia">Citrus</a> bright and modern AvaloniaUI theme
- <a href="http://github.com/worldbeater/citrus.avalonia">Citrus.Avalonia</a> bright and modern AvaloniaUI theme
- <a href="https://github.com/reactiveui/Akavache">Akavache</a> persistent key-value store
- <a href="https://github.com/nuke-build/nuke">Nuke</a> build automation system for C#/.NET
- <a href="https://github.com/xunit/xunit">XUnit</a> unit testing tool for .NET
@ -66,7 +68,6 @@ To compile the <a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/"
- <a href="https://github.com/googleapis/google-api-dotnet-client">Google Drive</a> client SDK for .NET
- <a href="https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit">Material Design</a> XAML controls and styles
- <a href="https://github.com/MahApps/MahApps.Metro">MahApps Metro</a> XAML controls and styled windows
- <a href="https://reactiveui.net/docs/handbook/events/">ReactiveUI.Events</a> turning regular events into observables
- <a href="https://github.com/Fody/Costura">Costura.Fody</a> which embeds references as resources
- <a href="https://www.jetbrains.com/rider/">JetBrains Rider</a> and <a href="https://visualstudio.microsoft.com/">Microsoft Visual Studio</a> IDEs
- <a href="https://github.com/fornever/avaloniarider">AvaloniaRider</a> plugin for visual designer support

Просмотреть файл

@ -38,6 +38,9 @@
<TextBlock Text="Please, enter new folder name:" Margin="0 5" />
<TextBox Text="{x:Bind ViewModel.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="Please, enter the new name of the folder..." />
<TextBlock x:Name="FolderNameErrorLabel"
Text="Folder name error label"
Foreground="Red" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
@ -56,5 +59,8 @@
HorizontalAlignment="Stretch"
Margin="0 10" />
</Grid>
<TextBlock x:Name="FormErrorLabel"
Text="Form error label"
Foreground="Red" />
</StackPanel>
</UserControl>

Просмотреть файл

@ -1,5 +1,9 @@
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Formatters;
using System;
using System.Reactive.Disposables;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@ -13,7 +17,20 @@ namespace Camelotia.Presentation.Uwp.Views
public CreateFolderView()
{
InitializeComponent();
this.WhenActivated(disposables => { });
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.Name, x => x.FolderNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FolderNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FolderNameErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.Visibility)
.DisposeWith(disposables);
});
}
public ICreateFolderViewModel ViewModel

Просмотреть файл

@ -34,13 +34,22 @@
<TextBlock Text="Please, enter your user name:" Margin="0 5" />
<TextBox Text="{x:Bind ViewModel.Username, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="Please, enter the user name..." />
<TextBlock x:Name="UserNameErrorLabel"
Text="User name error label"
Foreground="Red" />
<TextBlock Text="Please, enter your password:" Margin="0 5" />
<PasswordBox Password="{x:Bind ViewModel.Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="Please, enter the password..." />
<TextBlock x:Name="PasswordErrorLabel"
Text="Password error label"
Foreground="Red" />
<Button Content="Login"
HorizontalAlignment="Stretch"
Style="{StaticResource AccentButtonStyle}"
Command="{x:Bind ViewModel.Login, Mode=OneWay}"
Margin="0 10" />
<TextBlock x:Name="FormErrorLabel"
Text="Form error label"
Foreground="Red" />
</StackPanel>
</UserControl>

Просмотреть файл

@ -1,5 +1,9 @@
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Formatters;
using System;
using System.Reactive.Disposables;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@ -13,7 +17,25 @@ namespace Camelotia.Presentation.Uwp.Views
public DirectAuthView()
{
InitializeComponent();
this.WhenActivated(disposables => { });
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.Username, x => x.UserNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Password, x => x.PasswordErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.UserNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.UserNameErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.PasswordErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.PasswordErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.Visibility)
.DisposeWith(disposables);
});
}
public IDirectAuthViewModel ViewModel

Просмотреть файл

@ -43,16 +43,31 @@
Text="{x:Bind ViewModel.Port, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="The port..." />
</Grid>
<TextBlock x:Name="HostNameErrorLabel"
Text="Host name error label"
Foreground="Red" />
<TextBlock x:Name="PortErrorLabel"
Text="Port error label"
Foreground="Red" />
<TextBlock Text="Please, enter your user name:" Margin="0 5" />
<TextBox Text="{x:Bind ViewModel.Username, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="Please, enter the user name..." />
<TextBlock x:Name="UserNameErrorLabel"
Text="User name error label"
Foreground="Red" />
<TextBlock Text="Please, enter your password:" Margin="0 5" />
<PasswordBox Password="{x:Bind ViewModel.Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="Please, enter the password..." />
<TextBlock x:Name="PasswordErrorLabel"
Text="Password error label"
Foreground="Red" />
<Button Content="Login"
HorizontalAlignment="Stretch"
Style="{StaticResource AccentButtonStyle}"
Command="{x:Bind ViewModel.Login, Mode=OneWay}"
Margin="0 10" />
<TextBlock x:Name="FormErrorLabel"
Text="Form error label"
Foreground="Red" />
</StackPanel>
</UserControl>

Просмотреть файл

@ -1,5 +1,9 @@
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Formatters;
using System;
using System.Reactive.Disposables;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@ -13,7 +17,35 @@ namespace Camelotia.Presentation.Uwp.Views
public HostAuthView()
{
InitializeComponent();
this.WhenActivated(disposables => { });
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.Address, x => x.HostNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Port, x => x.PortErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Username, x => x.UserNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Password, x => x.PasswordErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.HostNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.HostNameErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.PortErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.PortErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.UserNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.UserNameErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.PasswordErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.PasswordErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.Visibility)
.DisposeWith(disposables);
});
}
public IHostAuthViewModel ViewModel

Просмотреть файл

@ -37,7 +37,10 @@
</StackPanel>
<TextBlock Text="Please, enter new file name:" Margin="0 5" />
<TextBox Text="{x:Bind ViewModel.NewName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="Please, enter the new name of the file..." />
PlaceholderText="Please, enter the new name of the file..." />
<TextBlock x:Name="FileNameErrorLabel"
Text="File name error label"
Foreground="Red" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
@ -56,5 +59,8 @@
HorizontalAlignment="Stretch"
Margin="0 10" />
</Grid>
<TextBlock x:Name="FormErrorLabel"
Text="Form error label"
Foreground="Red" />
</StackPanel>
</UserControl>

Просмотреть файл

@ -1,5 +1,9 @@
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Formatters;
using System;
using System.Reactive.Disposables;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@ -13,7 +17,20 @@ namespace Camelotia.Presentation.Uwp.Views
public RenameFileView()
{
InitializeComponent();
this.WhenActivated(disposables => { });
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.NewName, x => x.FileNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FileNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FileNameErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.Visibility)
.DisposeWith(disposables);
});
}
public IRenameFileViewModel ViewModel

Просмотреть файл

@ -6,13 +6,13 @@
<UseWpf>true</UseWpf>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="akavache" Version="6.10.20" />
<PackageReference Include="MahApps.Metro" Version="2.2.0" />
<PackageReference Include="akavache" Version="7.1.1" />
<PackageReference Include="MahApps.Metro" Version="2.3.0" />
<PackageReference Include="MaterialDesignColors" Version="1.2.7" />
<PackageReference Include="MaterialDesignThemes" Version="3.2.0" />
<PackageReference Include="MaterialDesignThemes.MahApps" Version="0.1.5" />
<PackageReference Include="ReactiveUI.Events.WPF" Version="11.5.35" />
<PackageReference Include="ReactiveUI.WPF" Version="11.5.35" />
<PackageReference Include="ReactiveUI.Events.WPF" Version="12.1.1" />
<PackageReference Include="ReactiveUI.WPF" Version="12.1.1" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.4" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -58,8 +58,8 @@
<ItemGroup>
<PackageReference Include="System.Reactive" Version="4.4.1" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
<PackageReference Include="ReactiveUI.AndroidSupport" Version="11.5.35" />
<PackageReference Include="Xamarin.Forms" Version="4.7.0.1351" />
<PackageReference Include="ReactiveUI.AndroidSupport" Version="12.1.1" />
<PackageReference Include="Xamarin.Forms" Version="4.8.0.1560" />
<PackageReference Include="Xamarin.Android.Support.v7.Preference" Version="28.0.0.3" />
<PackageReference Include="Xamarin.Android.Support.Media.Compat" Version="28.0.0.3" />
<PackageReference Include="Xamarin.Android.Support.Fragment" Version="28.0.0.3" />

Просмотреть файл

@ -3,6 +3,11 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Camelotia.Presentation.Xamarin.App">
<Application.Resources>
<ResourceDictionary>
<Style x:Key="ErrorLabelStyle" TargetType="Label">
<Setter Property="TextColor" Value="Red" />
<Setter Property="Margin" Value="3, -5, 3, 0" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>

Просмотреть файл

@ -10,9 +10,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ReactiveUI.XamForms" Version="11.5.35" />
<PackageReference Include="ReactiveUI.Events.XamForms" Version="11.5.35" />
<PackageReference Include="Xamarin.Forms" Version="4.7.0.1351" />
<PackageReference Include="ReactiveUI.XamForms" Version="12.1.1" />
<PackageReference Include="ReactiveUI.Events.XamForms" Version="12.1.1" />
<PackageReference Include="Xamarin.Forms" Version="4.8.0.1560" />
<PackageReference Include="Xam.Plugin.Iconize" Version="3.5.0.112" />
<PackageReference Include="Xam.Plugin.Iconize.FontAwesome" Version="3.5.0.112" />
</ItemGroup>

Просмотреть файл

@ -13,6 +13,7 @@
Title="Authentication Required"
mc:Ignorable="d">
<d:ContentPage.BindingContext>
<!-- ReSharper disable once Xaml.InvalidType -->
<designTime:DesignTimeAuthViewModel />
</d:ContentPage.BindingContext>
</rxui:ReactiveTabbedPage>

Просмотреть файл

@ -26,6 +26,9 @@
TextColor="Red"
Margin="0, 15" />
<Entry Text="{Binding Name}" Placeholder="New folder name"/>
<Label x:Name="FolderNameErrorLabel"
Style="{StaticResource ErrorLabelStyle}"
VerticalOptions="Start" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
@ -40,6 +43,9 @@
Margin="0, 10"
Text="Close" />
</Grid>
<Label x:Name="FormErrorLabel"
Style="{StaticResource ErrorLabelStyle}"
VerticalOptions="Start" />
</StackLayout>
</Frame>
</ContentPage.Content>

Просмотреть файл

@ -1,5 +1,9 @@
using Camelotia.Presentation.Interfaces;
using System;
using System.Reactive.Disposables;
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Formatters;
using ReactiveUI.XamForms;
using Xamarin.Forms.Xaml;
@ -11,7 +15,20 @@ namespace Camelotia.Presentation.Xamarin.Views
public CreateFolderView()
{
InitializeComponent();
this.WhenActivated(disposables => { });
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.Name, x => x.FolderNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FolderNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FolderNameErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.IsVisible)
.DisposeWith(disposables);
});
}
}
}

Просмотреть файл

@ -26,8 +26,17 @@
TextColor="Red"
Margin="0, 15" />
<Entry Text="{Binding Username}" Placeholder="Please, enter your login" />
<Label x:Name="UserNameErrorLabel"
Style="{StaticResource ErrorLabelStyle}"
VerticalOptions="Start" />
<Entry Text="{Binding Password}" IsPassword="True" Placeholder="Please, enter your password" />
<Label x:Name="PasswordErrorLabel"
Style="{StaticResource ErrorLabelStyle}"
VerticalOptions="Start" />
<controls:AccentButton Text="Login" Command="{Binding Login}" />
<Label x:Name="FormErrorLabel"
Style="{StaticResource ErrorLabelStyle}"
VerticalOptions="Start" />
</StackLayout>
</Frame>
</ContentPage.Content>

Просмотреть файл

@ -1,7 +1,11 @@
using Camelotia.Presentation.Interfaces;
using System;
using System.Reactive.Disposables;
using Camelotia.Presentation.Interfaces;
using Xamarin.Forms.Xaml;
using ReactiveUI.XamForms;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Formatters;
namespace Camelotia.Presentation.Xamarin.Views
{
@ -11,7 +15,25 @@ namespace Camelotia.Presentation.Xamarin.Views
public DirectAuthView()
{
InitializeComponent();
this.WhenActivated(disposables => { });
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.Username, x => x.UserNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Password, x => x.PasswordErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.UserNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.UserNameErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.PasswordErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.PasswordErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.IsVisible)
.DisposeWith(disposables);
});
}
}
}

Просмотреть файл

@ -33,9 +33,24 @@
<Entry Grid.Column="0" Text="{Binding Address}" Placeholder="Please, enter host address" />
<Entry Grid.Column="1" Text="{Binding Port}" Placeholder="Port" />
</Grid>
<Label x:Name="HostNameErrorLabel"
Style="{StaticResource ErrorLabelStyle}"
VerticalOptions="Start" />
<Label x:Name="PortErrorLabel"
Style="{StaticResource ErrorLabelStyle}"
VerticalOptions="Start" />
<Entry Text="{Binding Username}" Placeholder="Please, enter your login" />
<Label x:Name="UserNameErrorLabel"
Style="{StaticResource ErrorLabelStyle}"
VerticalOptions="Start" />
<Entry Text="{Binding Password}" IsPassword="True" Placeholder="Please, enter your password" />
<Label x:Name="PasswordErrorLabel"
Style="{StaticResource ErrorLabelStyle}"
VerticalOptions="Start" />
<controls:AccentButton Command="{Binding Login}" Text="Login" />
<Label x:Name="FormErrorLabel"
Style="{StaticResource ErrorLabelStyle}"
VerticalOptions="Start" />
</StackLayout>
</Frame>
</ContentPage.Content>

Просмотреть файл

@ -1,5 +1,9 @@
using Camelotia.Presentation.Interfaces;
using System;
using System.Reactive.Disposables;
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Formatters;
using ReactiveUI.XamForms;
using Xamarin.Forms.Xaml;
@ -11,7 +15,35 @@ namespace Camelotia.Presentation.Xamarin.Views
public HostAuthView()
{
InitializeComponent();
this.WhenActivated(disposables => { });
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.Address, x => x.HostNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Port, x => x.PortErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Username, x => x.UserNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Password, x => x.PasswordErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.HostNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.HostNameErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.PortErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.PortErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.UserNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.UserNameErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.PasswordErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.PasswordErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.IsVisible)
.DisposeWith(disposables);
});
}
}
}

Просмотреть файл

@ -13,6 +13,7 @@
Title="Loading..."
mc:Ignorable="d">
<d:ContentPage.BindingContext>
<!-- ReSharper disable once Xaml.InvalidType -->
<designTime:DesignTimeMainViewModel />
</d:ContentPage.BindingContext>
</rxui:ReactiveNavigationPage>

Просмотреть файл

@ -26,6 +26,9 @@
TextColor="Red"
Margin="0, 15" />
<Entry Text="{Binding NewName}" Placeholder="New file name" />
<Label x:Name="FileNameErrorLabel"
Style="{StaticResource ErrorLabelStyle}"
VerticalOptions="Start" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
@ -40,6 +43,9 @@
Margin="0, 10"
Text="Close" />
</Grid>
<Label x:Name="FormErrorLabel"
Style="{StaticResource ErrorLabelStyle}"
VerticalOptions="Start" />
</StackLayout>
</Frame>
</ContentPage.Content>

Просмотреть файл

@ -1,5 +1,9 @@
using Camelotia.Presentation.Interfaces;
using System;
using System.Reactive.Disposables;
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Formatters;
using ReactiveUI.XamForms;
using Xamarin.Forms.Xaml;
@ -11,7 +15,20 @@ namespace Camelotia.Presentation.Xamarin.Views
public RenameFileView()
{
InitializeComponent();
this.WhenActivated(disposables => { });
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.NewName, x => x.FileNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FileNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FileNameErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.IsVisible)
.DisposeWith(disposables);
});
}
}
}

Просмотреть файл

@ -10,9 +10,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ReactiveUI" Version="11.5.35" />
<PackageReference Include="ReactiveUI.Fody" Version="11.5.35" />
<PackageReference Include="ReactiveUI.Validation" Version="1.6.4" />
<PackageReference Include="ReactiveUI" Version="12.1.1" />
<PackageReference Include="ReactiveUI.Fody" Version="12.1.1" />
<PackageReference Include="ReactiveUI.Validation" Version="1.8.6" />
</ItemGroup>
</Project>

Просмотреть файл

@ -6,7 +6,7 @@ using ReactiveUI.Validation.Helpers;
namespace Camelotia.Presentation.DesignTime
{
public class DesignTimeCreateFolderViewModel : ReactiveValidationObject<DesignTimeCreateFolderViewModel>, ICreateFolderViewModel
public class DesignTimeCreateFolderViewModel : ReactiveValidationObject, ICreateFolderViewModel
{
public DesignTimeCreateFolderViewModel() => this.ValidationRule(x => x.Name, name => false, "Validation error.");

Просмотреть файл

@ -6,7 +6,7 @@ using ReactiveUI.Validation.Helpers;
namespace Camelotia.Presentation.DesignTime
{
public class DesignTimeDirectAuthViewModel : ReactiveValidationObject<DesignTimeDirectAuthViewModel>, IDirectAuthViewModel
public class DesignTimeDirectAuthViewModel : ReactiveValidationObject, IDirectAuthViewModel
{
public DesignTimeDirectAuthViewModel() => this.ValidationRule(x => x.Username, name => false, "Validation error.");

Просмотреть файл

@ -6,7 +6,7 @@ using ReactiveUI.Validation.Helpers;
namespace Camelotia.Presentation.DesignTime
{
public class DesignTimeHostAuthViewModel : ReactiveValidationObject<DesignTimeHostAuthViewModel>, IHostAuthViewModel
public class DesignTimeHostAuthViewModel : ReactiveValidationObject, IHostAuthViewModel
{
public DesignTimeHostAuthViewModel() => this.ValidationRule(x => x.Username, name => false, "Validation error.");

Просмотреть файл

@ -6,7 +6,7 @@ using ReactiveUI.Validation.Helpers;
namespace Camelotia.Presentation.DesignTime
{
public class DesignTimeRenameFileViewModel : ReactiveValidationObject<DesignTimeRenameFileViewModel>, IRenameFileViewModel
public class DesignTimeRenameFileViewModel : ReactiveValidationObject, IRenameFileViewModel
{
public DesignTimeRenameFileViewModel() => this.ValidationRule(x => x.NewName, name => false, "Validation error.");

Просмотреть файл

@ -1,10 +1,15 @@
using System.ComponentModel;
using System.Reactive;
using ReactiveUI;
using ReactiveUI.Validation.Abstractions;
namespace Camelotia.Presentation.Interfaces
{
public interface ICreateFolderViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
public interface ICreateFolderViewModel :
INotifyPropertyChanged,
INotifyDataErrorInfo,
IValidatableViewModel,
IReactiveObject
{
bool IsLoading { get; }

Просмотреть файл

@ -1,10 +1,15 @@
using System.ComponentModel;
using System.Reactive;
using ReactiveUI;
using ReactiveUI.Validation.Abstractions;
namespace Camelotia.Presentation.Interfaces
{
public interface IDirectAuthViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
public interface IDirectAuthViewModel :
INotifyPropertyChanged,
INotifyDataErrorInfo,
IValidatableViewModel,
IReactiveObject
{
string Username { get; set; }

Просмотреть файл

@ -1,6 +1,12 @@
using ReactiveUI;
using ReactiveUI.Validation.Abstractions;
namespace Camelotia.Presentation.Interfaces
{
public interface IHostAuthViewModel : IDirectAuthViewModel
public interface IHostAuthViewModel :
IDirectAuthViewModel,
IValidatableViewModel,
IReactiveObject
{
string Address { get; set; }

Просмотреть файл

@ -1,10 +1,15 @@
using System.ComponentModel;
using System.Reactive;
using ReactiveUI;
using ReactiveUI.Validation.Abstractions;
namespace Camelotia.Presentation.Interfaces
{
public interface IRenameFileViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
public interface IRenameFileViewModel :
INotifyPropertyChanged,
INotifyDataErrorInfo,
IValidatableViewModel,
IReactiveObject
{
bool IsLoading { get; }

Просмотреть файл

@ -13,7 +13,7 @@ namespace Camelotia.Presentation.ViewModels
{
public delegate ICreateFolderViewModel CreateFolderViewModelFactory(IProviderViewModel providerViewModel);
public sealed class CreateFolderViewModel : ReactiveValidationObject<CreateFolderViewModel>, ICreateFolderViewModel
public sealed class CreateFolderViewModel : ReactiveValidationObject, ICreateFolderViewModel
{
private readonly ObservableAsPropertyHelper<string> _errorMessage;
private readonly ObservableAsPropertyHelper<bool> _hasErrorMessage;

Просмотреть файл

@ -11,7 +11,7 @@ using ReactiveUI.Validation.Helpers;
namespace Camelotia.Presentation.ViewModels
{
public sealed class DirectAuthViewModel : ReactiveValidationObject<DirectAuthViewModel>, IDirectAuthViewModel
public sealed class DirectAuthViewModel : ReactiveValidationObject, IDirectAuthViewModel
{
private readonly ObservableAsPropertyHelper<string> _errorMessage;
private readonly ObservableAsPropertyHelper<bool> _hasErrorMessage;

Просмотреть файл

@ -12,7 +12,7 @@ using VkNet.Enums;
namespace Camelotia.Presentation.ViewModels
{
public sealed class HostAuthViewModel : ReactiveValidationObject<HostAuthViewModel>, IHostAuthViewModel
public sealed class HostAuthViewModel : ReactiveValidationObject, IHostAuthViewModel
{
private readonly ObservableAsPropertyHelper<string> _errorMessage;
private readonly ObservableAsPropertyHelper<bool> _hasErrorMessage;

Просмотреть файл

@ -13,7 +13,7 @@ namespace Camelotia.Presentation.ViewModels
{
public delegate IRenameFileViewModel RenameFileViewModelFactory(IProviderViewModel providerViewModel);
public sealed class RenameFileViewModel : ReactiveValidationObject<RenameFileViewModel>, IRenameFileViewModel
public sealed class RenameFileViewModel : ReactiveValidationObject, IRenameFileViewModel
{
private readonly ObservableAsPropertyHelper<bool> _hasErrorMessage;
private readonly ObservableAsPropertyHelper<string> _errorMessage;

Просмотреть файл

@ -7,13 +7,13 @@
<ItemGroup>
<PackageReference Include="DynamicData" Version="6.17.14" />
<PackageReference Include="akavache" Version="6.10.20" />
<PackageReference Include="FluentFTP" Version="32.4.7" />
<PackageReference Include="Google.Apis.Drive.v3" Version="1.49.0.2093" />
<PackageReference Include="akavache" Version="7.1.1" />
<PackageReference Include="FluentFTP" Version="33.0.2" />
<PackageReference Include="Google.Apis.Drive.v3" Version="1.49.0.2117" />
<PackageReference Include="Octokit" Version="0.48.0" />
<PackageReference Include="ssh.net" Version="2016.1.0" />
<PackageReference Include="System.Reactive" Version="4.4.1" />
<PackageReference Include="VkNet" Version="1.56.0" />
<PackageReference Include="VkNet" Version="1.57.0" />
</ItemGroup>
</Project>

Просмотреть файл

@ -12,7 +12,7 @@
</PackageReference>
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="ReactiveUI.Testing" Version="11.5.35" />
<PackageReference Include="ReactiveUI.Testing" Version="12.1.1" />
<PackageReference Include="NSubstitute" Version="4.2.2" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">