Skip to content

Commit 703d87e

Browse files
David BritchDavid Britch
authored andcommitted
LoginView displays validation errors when using mock services.
1 parent 433de15 commit 703d87e

6 files changed

Lines changed: 103 additions & 23 deletions

File tree

src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<Color x:Key="GreenColor">#00A69C</Color>
1818
<Color x:Key="DarkGreenColor">#00857D</Color>
1919
<Color x:Key="GrayColor">#e2e2e2</Color>
20+
<Color x:Key="ErrorColor">#ff5252</Color>
2021

2122
<!-- FONTS -->
2223
<OnPlatform
@@ -100,6 +101,7 @@
100101
<!-- CONVERTERS -->
101102
<converters:CountToBoolConverter x:Key="CountToBoolConverter" />
102103
<converters:DatetimeConverter x:Key="DatetimeConverter" />
104+
<converters:FirstValidationErrorConverter x:Key="FirstValidationErrorConverter" />
103105
<converters:ImageConverter x:Key="ImageConverter" />
104106
<converters:ItemTappedEventArgsConverter x:Key="ItemTappedEventArgsConverter" />
105107
<converters:InverseCountToBoolConverter x:Key="InverseCountToBoolConverter" />
@@ -109,6 +111,14 @@
109111
<converters:WebNavigatingEventArgsConverter x:Key="WebNavigatingEventArgsConverter" />
110112

111113
<!-- STYLES -->
114+
<Style x:Key="ValidationErrorLabelStyle"
115+
TargetType="{x:Type Label}">
116+
<Setter Property="TextColor"
117+
Value="{StaticResource ErrorColor}" />
118+
<Setter Property="FontSize"
119+
Value="{StaticResource LittleSize}" />
120+
</Style>
121+
112122
<Style x:Key="EntryStyle"
113123
TargetType="{x:Type Entry}">
114124
<Setter Property="FontFamily"
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Globalization;
4+
using System.Linq;
5+
using Xamarin.Forms;
6+
7+
namespace eShopOnContainers.Core.Converters
8+
{
9+
public class FirstValidationErrorConverter : IValueConverter
10+
{
11+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
12+
{
13+
ICollection<string> errors = value as ICollection<string>;
14+
return errors != null && errors.Count > 0 ? errors.ElementAt(0) : null;
15+
}
16+
17+
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
18+
{
19+
throw new NotImplementedException();
20+
}
21+
}
22+
}

src/Mobile/eShopOnContainers/eShopOnContainers.Core/Validations/ValidatableObject.cs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,37 @@
11
using eShopOnContainers.Core.ViewModels.Base;
22
using System.Collections.Generic;
3-
using System.Collections.ObjectModel;
43
using System.Linq;
54

65
namespace eShopOnContainers.Core.Validations
76
{
87
public class ValidatableObject<T> : ExtendedBindableObject, IValidity
98
{
109
private readonly List<IValidationRule<T>> _validations;
11-
private readonly ObservableCollection<string> _errors;
10+
private List<string> _errors;
1211
private T _value;
1312
private bool _isValid;
1413

1514
public List<IValidationRule<T>> Validations => _validations;
1615

17-
public ObservableCollection<string> Errors => _errors;
16+
public List<string> Errors
17+
{
18+
get
19+
{
20+
return _errors;
21+
}
22+
set
23+
{
24+
_errors = value;
25+
RaisePropertyChanged(() => Errors);
26+
}
27+
}
1828

1929
public T Value
2030
{
2131
get
2232
{
2333
return _value;
2434
}
25-
2635
set
2736
{
2837
_value = value;
@@ -36,19 +45,17 @@ public bool IsValid
3645
{
3746
return _isValid;
3847
}
39-
4048
set
4149
{
4250
_isValid = value;
43-
_errors.Clear();
4451
RaisePropertyChanged(() => IsValid);
4552
}
4653
}
4754

4855
public ValidatableObject()
4956
{
5057
_isValid = true;
51-
_errors = new ObservableCollection<string>();
58+
_errors = new List<string>();
5259
_validations = new List<IValidationRule<T>>();
5360
}
5461

@@ -59,11 +66,7 @@ public bool Validate()
5966
IEnumerable<string> errors = _validations.Where(v => !v.Check(Value))
6067
.Select(v => v.ValidationMessage);
6168

62-
foreach (var error in errors)
63-
{
64-
Errors.Add(error);
65-
}
66-
69+
Errors = errors.ToList();
6770
IsValid = !Errors.Any();
6871

6972
return this.IsValid;

src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/LoginViewModel.cs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using eShopOnContainers.Core.Models.User;
33
using eShopOnContainers.Core.Services.Identity;
44
using eShopOnContainers.Core.Services.OpenUrl;
5-
using eShopOnContainers.Core.Services.User;
65
using eShopOnContainers.Core.Validations;
76
using eShopOnContainers.Core.ViewModels.Base;
87
using IdentityModel.Client;
@@ -128,6 +127,10 @@ public string LoginUrl
128127

129128
public ICommand SettingsCommand => new Command(async () => await SettingsAsync());
130129

130+
public ICommand ValidateUserNameCommand => new Command(() => ValidateUserName());
131+
132+
public ICommand ValidatePasswordCommand => new Command(() => ValidatePassword());
133+
131134
public override Task InitializeAsync(object navigationData)
132135
{
133136
if(navigationData is LogoutParameter)
@@ -250,16 +253,26 @@ private async Task SettingsAsync()
250253

251254
private bool Validate()
252255
{
253-
bool isValidUser = _userName.Validate();
254-
bool isValidPassword = _password.Validate();
256+
bool isValidUser = ValidateUserName();
257+
bool isValidPassword = ValidatePassword();
255258

256259
return isValidUser && isValidPassword;
257260
}
258261

262+
private bool ValidateUserName()
263+
{
264+
return _userName.Validate();
265+
}
266+
267+
private bool ValidatePassword()
268+
{
269+
return _password.Validate();
270+
}
271+
259272
private void AddValidations()
260273
{
261-
_userName.Validations.Add(new IsNotNullOrEmptyRule<string> { ValidationMessage = "Username should not be empty" });
262-
_password.Validations.Add(new IsNotNullOrEmptyRule<string> { ValidationMessage = "Password should not be empty" });
274+
_userName.Validations.Add(new IsNotNullOrEmptyRule<string> { ValidationMessage = "A username is required." });
275+
_password.Validations.Add(new IsNotNullOrEmptyRule<string> { ValidationMessage = "A password is required" });
263276
}
264277

265278
public void InvalidateMock()

src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/LoginView.xaml

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
xmlns:animations="clr-namespace:eShopOnContainers.Core.Animations;assembly=eShopOnContainers.Core"
77
xmlns:triggers="clr-namespace:eShopOnContainers.Core.Triggers;assembly=eShopOnContainers.Core"
88
xmlns:behaviors="clr-namespace:eShopOnContainers.Core.Behaviors;assembly=eShopOnContainers.Core"
9+
xmlns:effects="clr-namespace:eShopOnContainers.Core.Effects;assembly=eShopOnContainers.Core"
910
viewModelBase:ViewModelLocator.AutoWireViewModel="true">
1011
<ContentPage.Title>
1112
<OnPlatform
@@ -177,30 +178,60 @@
177178
Margin="24">
178179
<Label
179180
Text="User name or email"
180-
Style="{StaticResource HeaderLabelStyle}"/>
181-
<Entry
182-
Text="{Binding UserName.Value, Mode=TwoWay}">
183-
<Entry.Style>
181+
Style="{StaticResource HeaderLabelStyle}" />
182+
<Entry Text="{Binding UserName.Value, Mode=TwoWay}">
183+
<Entry.Style>
184184
<OnPlatform x:TypeArguments="Style"
185185
iOS="{StaticResource EntryStyle}"
186186
Android="{StaticResource EntryStyle}"
187187
WinPhone="{StaticResource UwpEntryStyle}"/>
188188
</Entry.Style>
189+
<Entry.Behaviors>
190+
<behaviors:EventToCommandBehavior
191+
EventName="TextChanged"
192+
Command="{Binding ValidateUserNameCommand}" />
193+
</Entry.Behaviors>
194+
<Entry.Triggers>
195+
<DataTrigger
196+
TargetType="Entry"
197+
Binding="{Binding UserName.IsValid}"
198+
Value="False">
199+
<Setter Property="effects:LineColorEffect.LineColor" Value="{StaticResource ErrorColor}" />
200+
</DataTrigger>
201+
</Entry.Triggers>
189202
</Entry>
203+
<Label
204+
Text="{Binding UserName.Errors, Converter={StaticResource FirstValidationErrorConverter}"
205+
Style="{StaticResource ValidationErrorLabelStyle}" />
190206
<Label
191207
Text="Password"
192208
Style="{StaticResource HeaderLabelStyle}"/>
193209
<Entry
194210
IsPassword="True"
195-
Text="{Binding Password.Value, Mode=TwoWay}"
196-
Style="{StaticResource EntryStyle}">
211+
Text="{Binding Password.Value, Mode=TwoWay}">
197212
<Entry.Style>
198213
<OnPlatform x:TypeArguments="Style"
199214
iOS="{StaticResource EntryStyle}"
200215
Android="{StaticResource EntryStyle}"
201216
WinPhone="{StaticResource UwpEntryStyle}"/>
202217
</Entry.Style>
218+
<Entry.Behaviors>
219+
<behaviors:EventToCommandBehavior
220+
EventName="TextChanged"
221+
Command="{Binding ValidatePasswordCommand}" />
222+
</Entry.Behaviors>
223+
<Entry.Triggers>
224+
<DataTrigger
225+
TargetType="Entry"
226+
Binding="{Binding Password.IsValid}"
227+
Value="False">
228+
<Setter Property="effects:LineColorEffect.LineColor" Value="{StaticResource ErrorColor}" />
229+
</DataTrigger>
230+
</Entry.Triggers>
203231
</Entry>
232+
<Label
233+
Text="{Binding Password.Errors, Converter={StaticResource FirstValidationErrorConverter}"
234+
Style="{StaticResource ValidationErrorLabelStyle}" />
204235
</StackLayout>
205236
<!-- LOGIN BUTTON -->
206237
<Grid

src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@
164164
<DependentUpon>ProductTemplate.xaml</DependentUpon>
165165
</Compile>
166166
<Compile Include="Converters\WebNavigatingEventArgsConverter.cs" />
167+
<Compile Include="Converters\FirstValidationErrorConverter.cs" />
167168
</ItemGroup>
168169
<ItemGroup>
169170
<None Include="app.config" />

0 commit comments

Comments
 (0)