A validation model

Signing and validating the estimate
This commit is contained in:
Paul Schneider 2016-12-08 18:10:01 +01:00
commit 044f145035
28 changed files with 492 additions and 190 deletions

View file

@ -387,9 +387,9 @@
</AndroidResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Yavsc.Client\Yavsc.Client.csproj">
<ProjectReference Include="..\..\YavscLib\YavscLib.csproj">
<Project>{67f9d3a8-f71e-4428-913f-c37ae82cdb24}</Project>
<Name>Yavsc.Client</Name>
<Name>YavscLib</Name>
</ProjectReference>
<ProjectReference Include="..\BookAStar\BookAStar.csproj">
<Project>{A0815650-0A0A-47B0-8826-771F0E1AD137}</Project>

View file

@ -171,10 +171,6 @@
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Yavsc.Client\Yavsc.Client.csproj">
<Project>{67F9D3A8-F71E-4428-913F-C37AE82CDB24}</Project>
<Name>Yavsc.Client</Name>
</ProjectReference>
<ProjectReference Include="..\BookAStar\BookAStar.csproj">
<Project>{A0815650-0A0A-47B0-8826-771F0E1AD137}</Project>
<Name>BookAStar</Name>

View file

@ -16,6 +16,8 @@
<Color x:Key="PageForegroundColor">#000000</Color>
<Color x:Key="TextColor">#000000</Color>
<Color x:Key="LabelColor">#000000</Color>
<Color x:Key="ErrorTextColor">#500000</Color>
<Color x:Key="HeadingTextColor">Black</Color>
<Color x:Key="NormalTextColor">Blue</Color>
<Color x:Key="GroupingTextColor">#5050ff</Color>
@ -43,9 +45,16 @@
<Setter Property="VerticalOptions" Value="Start" />
</Style>
<Style x:Key="ErrorLabelStyle" BasedOn="{StaticResource InputLabelStyle}" TargetType="Label">
<Setter Property="FontSize" Value="Large" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="TextColor" Value="{StaticResource ErrorTextColor}" />
</Style>
<Style x:Key="LabelStyle" TargetType="Label">
<Setter Property="TextColor" Value="{StaticResource LabelColor}" />
<Setter Property="LineBreakMode" Value="WordWrap" />
<Setter Property="HorizontalOptions" Value="FillAndExpand" />
</Style>
<Style x:Key="BigLabel" BasedOn="{StaticResource LabelStyle}" TargetType="Label">

View file

@ -19,7 +19,6 @@ namespace BookAStar
using Data;
using Interfaces;
using Model;
using Model.UI;
using Pages;
using Plugin.Connectivity;
using Model.Social.Messaging;
@ -28,6 +27,7 @@ namespace BookAStar
using Pages.UserProfile;
using ViewModels.EstimateAndBilling;
using Pages.EstimatePages;
using ViewModels;
public partial class App : Application // superclass new in 1.3
{

View file

@ -5,12 +5,17 @@ namespace BookAStar.Behaviors
{
public class EditorMaxLengthValidator : Behavior<Editor>
{
public static readonly BindableProperty MaxLengthProperty = BindableProperty.Create("MaxLength", typeof(int), typeof(EditorMaxLengthValidator), 0);
public static readonly BindableProperty MinLengthProperty = BindableProperty.Create("MinLength", typeof(int), typeof(EditorMaxLengthValidator), 0);
public static readonly BindableProperty MaxLengthProperty =
BindableProperty.Create("MaxLength", typeof(int), typeof(EditorMaxLengthValidator), 0);
public static readonly BindableProperty MinLengthProperty =
BindableProperty.Create("MinLength", typeof(int), typeof(EditorMaxLengthValidator), 0);
public static readonly BindableProperty IsValidProperty =
BindableProperty.Create("IsValid", typeof(bool), typeof(EditorMaxLengthValidator), false);
public static readonly BindableProperty ErrorProperty =
BindableProperty.Create("Error", typeof(string), typeof(EditorMaxLengthValidator), null);
public int MaxLength
{
get { return (int)GetValue(MaxLengthProperty); }
get { return (int) GetValue(MaxLengthProperty); }
set { SetValue(MaxLengthProperty, value); }
}
public int MinLength
@ -24,13 +29,40 @@ namespace BookAStar.Behaviors
bindable.TextChanged += bindable_TextChanged;
}
public bool IsValid { get; set; }
public bool IsValid {
get { return (bool)GetValue(IsValidProperty); }
set { SetValue(IsValidProperty, value);
}
}
public string Error
{
get {
return (string)GetValue(ErrorProperty);
}
set
{
SetValue(ErrorProperty, value);
}
}
private void bindable_TextChanged(object sender, TextChangedEventArgs e)
{
IsValid = e.NewTextValue == null? false : ( e.NewTextValue.Length >= MinLength && e.NewTextValue.Length <= MaxLength ) ;
if (!IsValid) if (e.NewTextValue!=null) if (e.NewTextValue.Length > MaxLength)
((Editor)sender).Text = e.NewTextValue.Substring(0, MaxLength);
if (e.NewTextValue != null && e.NewTextValue.Length >= MinLength)
{
if (e.NewTextValue.Length > MaxLength)
{
((Editor)sender).Text = e.NewTextValue.Substring(0, MaxLength);
Error = Strings.YourTextWasTooLong;
}
else Error = "";
IsValid = true;
}
else
{
Error = string.Format(Strings.MinMaxStringValidationError,
MinLength, MaxLength);
IsValid = false;
}
}
protected override void OnDetachingFrom(Editor bindable)

View file

@ -6,7 +6,10 @@ namespace BookAStar.Behaviors
{
public static readonly BindableProperty MinProperty = BindableProperty.Create("Min", typeof(int), typeof(IntegerEntryBehavior), 0);
public static readonly BindableProperty MaxProperty = BindableProperty.Create("Max", typeof(int), typeof(IntegerEntryBehavior), 0);
public static readonly BindableProperty IsValidProperty =
BindableProperty.Create("IsValid", typeof(bool), typeof(IntegerEntryBehavior), false);
public static readonly BindableProperty ErrorProperty =
BindableProperty.Create("Error", typeof(string), typeof(IntegerEntryBehavior), null);
public int Min
{
get { return (int)GetValue(MinProperty); }
@ -30,8 +33,13 @@ namespace BookAStar.Behaviors
if (int.TryParse(e.NewTextValue, out val))
{
IsValid = (Min > Max) || (Max >= val && val >= Min);
Error = string.Format(Strings.MinMaxIntError, Min, Max);
}
else
{
IsValid = false;
Error = "";
}
else IsValid = false;
}
protected override void OnDetachingFrom(Entry bindable)
@ -39,6 +47,26 @@ namespace BookAStar.Behaviors
bindable.TextChanged -= bindable_TextChanged;
}
public bool IsValid { get; private set; }
public bool IsValid
{
get {
return (bool) GetValue(IsValidProperty);
}
set
{
SetValue(IsValidProperty, value);
}
}
public string Error
{
get
{
return (string) GetValue(ErrorProperty);
}
set
{
SetValue(ErrorProperty, value);
}
}
}
}

View file

@ -61,7 +61,9 @@
<Compile Include="Model\Settings\SignatureSettings.cs" />
<Compile Include="Model\Social\Messaging\ChatStatus.cs" />
<Compile Include="Model\Social\Messaging\PrivateMessage.cs" />
<Compile Include="Model\UI\PageState.cs" />
<Compile Include="ViewModels\Validation\Error.cs" />
<Compile Include="ViewModels\Validation\ModelState.cs" />
<Compile Include="ViewModels\PageState.cs" />
<Compile Include="Pages\EstimatePages\EstimateSigningPage.xaml.cs">
<DependentUpon>EstimateSigningPage.xaml</DependentUpon>
</Compile>
@ -91,7 +93,7 @@
<DesignTime>True</DesignTime>
<DependentUpon>Strings.en.resx</DependentUpon>
</Compile>
<Compile Include="ViewModels\EditingViewModel.cs" />
<Compile Include="ViewModels\Validation\EditingViewModel.cs" />
<Compile Include="Pages\DocSigning.xaml.cs">
<DependentUpon>DocSigning.xaml</DependentUpon>
</Compile>
@ -99,6 +101,7 @@
<Compile Include="ViewModels\Signing\SignaturePadConfigViewModel.cs" />
<Compile Include="ViewModels\UserProfile\UserProfileViewModel.cs" />
<Compile Include="ViewModels\UserProfile\DirectoryInfoViewModel.cs" />
<Compile Include="ViewModels\Validation\ErrorSeverity.cs" />
<Compile Include="Views\EnumPicker.cs" />
<Compile Include="Converters\BooleanToObjectConverter.cs" />
<Compile Include="Converters\EnumConverter.cs" />
@ -250,6 +253,7 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Model\Billing\" />
<Folder Include="Model\UI\" />
</ItemGroup>
<ItemGroup>
<Reference Include="ExifLib, Version=1.0.1.0, Culture=neutral, processorArchitecture=MSIL">
@ -363,12 +367,6 @@
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Yavsc.Client\Yavsc.Client.csproj">
<Project>{67f9d3a8-f71e-4428-913f-c37ae82cdb24}</Project>
<Name>Yavsc.Client</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\EstimatePages\EditEstimatePage.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
@ -496,6 +494,12 @@
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\YavscLib\YavscLib.csproj">
<Project>{67f9d3a8-f71e-4428-913f-c37ae82cdb24}</Project>
<Name>YavscLib</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Import Project="..\..\packages\Xamarin.Forms.2.3.2.127\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\Xamarin.Forms.targets" Condition="Exists('..\..\packages\Xamarin.Forms.2.3.2.127\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\Xamarin.Forms.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">

View file

@ -3,11 +3,10 @@
using Model;
using Model.Blog;
using Model.Workflow;
using Model.UI;
using Model.Social.Messaging;
using Model.FileSystem;
using ViewModels.EstimateAndBilling;
using NonCrUD;
using ViewModels;
public class DataManager
{

View file

@ -65,31 +65,25 @@ namespace BookAStar.Pages
if (editEstimateViewModel == null)
{
// First search for an existing estimate
var estimateToEdit = DataManager.Current.Estimates.FirstOrDefault(
estimate=> estimate.CommandId == bookQueryViewModel.Id
editEstimateViewModel = DataManager.Current.EstimationCache.FirstOrDefault(
estimate=> estimate.Query.Id == bookQueryViewModel.Id
);
if (estimateToEdit == null)
if (editEstimateViewModel == null)
{
DataManager.Current.Contacts.Merge(BookQuery.Client);
DataManager.Current.Contacts.SaveEntity();
estimateToEdit = new Estimate
editEstimateViewModel = new EditEstimateViewModel( new Estimate
{
ClientId = BookQuery.Client.UserId,
CommandId = BookQuery.Id,
OwnerId = MainSettings.CurrentUser.Id,
Id = 0
};
editEstimateViewModel = new EditEstimateViewModel(estimateToEdit);
});
DataManager.Current.EstimationCache.Add(editEstimateViewModel);
}
else
editEstimateViewModel = new EditEstimateViewModel(estimateToEdit);
DataManager.Current.EstimationCache.Add(editEstimateViewModel);
}
App.NavigationService.NavigateTo<EditEstimatePage>(true,
editEstimateViewModel);
}
private async void OnViewEstimate(object sender, EventArgs ev)

View file

@ -44,12 +44,16 @@
<StackLayout x:Name="mainStackLayout">
<Label Text="Description de la ligne de facture"
Style="{StaticResource InputLabelStyle}"></Label>
<StackLayout Orientation="Horizontal" VerticalOptions="FillAndExpand">
<Editor HorizontalOptions="FillAndExpand" Text="{Binding Description, Mode=TwoWay}">
<Editor HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Text="{Binding Description, Mode=TwoWay}">
<Editor.Behaviors>
<behaviors:EditorMaxLengthValidator x:Name="descriptionLenValidator" MaxLength="512" MinLength="3" />
<behaviors:EditorMaxLengthValidator x:Name="descriptionLenValidator" MaxLength="12" MinLength="3" />
</Editor.Behaviors>
</Editor>
<StackLayout Orientation="Horizontal">
<Image x:Name="descriptionSuccessErrorImage"
Style="{Binding Source={x:Reference descriptionLenValidator}, Path=IsValid, Converter={StaticResource boolToStyleImage}}" />
<Label Text="{Binding Source={x:Reference descriptionLenValidator}, Path=Error}"
Style="{StaticResource ErrorLabelStyle}"></Label>
</StackLayout>
<Label Text="Durée de la prestation"
@ -74,18 +78,21 @@
</StackLayout>
<Label Text="Quantité facturée" Style="{StaticResource InputLabelStyle}"></Label>
<StackLayout Orientation="Horizontal">
<Entry Text="{Binding Count, Mode=TwoWay}" Placeholder="Quantité" Keyboard="Numeric"
Style="{StaticResource BigEntry}">
<Entry.Behaviors>
<behaviors:IntegerEntryBehavior x:Name="countValidator" Min="0" Max="10" />
</Entry.Behaviors>
</Entry>
<Image x:Name="countSuccessErrorImage"
<StackLayout Orientation="Horizontal">
<Image x:Name="countSuccessErrorImage"
Style="{Binding Source={x:Reference countValidator},
Path=IsValid,
Converter={StaticResource boolToStyleImage}}" />
</StackLayout>
<Label Text="{Binding Source={x:Reference countValidator}, Path=Error}"
Style="{StaticResource ErrorLabelStyle}"></Label>
</StackLayout>
<Label Text="Prix unitaire" Style="{StaticResource InputLabelStyle}"></Label>
<StackLayout Orientation="Horizontal">
@ -106,7 +113,7 @@
Command="{Binding ValidateCommand}"
Clicked="OnValidateClicked"></Button>
<Button Text="Supprimer"
Command="{Binding DeleteCommand}"
Command="{Binding RemoveCommand}"
Clicked="OnDeleteClicked"></Button>
</StackLayout>
</StackLayout>

View file

@ -17,14 +17,17 @@ namespace BookAStar.Pages
picker.Items.Add(du);
BindingContext = model;
}
public void OnDeleteClicked(object sender, EventArgs e)
{
throw new NotImplementedException();
this.Navigation.PopAsync();
}
public void OnValidateClicked (object sender, EventArgs e)
{
this.Navigation.PopAsync();
}
protected override bool OnBackButtonPressed()
{
var bvm = (BillingLineViewModel)BindingContext;

View file

@ -2,9 +2,11 @@
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="BookAStar.Pages.EditEstimatePage"
xmlns:converters="clr-namespace:BookAStar.Converters;assembly=BookAStar"
xmlns:views="clr-namespace:BookAStar.Views;assembly=BookAStar"
xmlns:local="clr-namespace:BookAStar;assembly=BookAStar"
xmlns:behaviors="clr-namespace:BookAStar.Behaviors;assembly=BookAStar"
xmlns:extensions="clr-namespace:BookAStar.Extensions;assembly=BookAStar"
Style="{StaticResource PageStyle}">
<ContentPage.Resources>
<ResourceDictionary>
@ -14,14 +16,32 @@
<Style TargetType="Button">
<Setter Property="Style" Value="{StaticResource ButtonStyle}" />
</Style>
<converters:BooleanToObjectConverter x:Key="boolToStyleImage" x:TypeArguments="Style">
<converters:BooleanToObjectConverter.FalseObject>
<Style TargetType="Image">
<Setter Property="HeightRequest" Value="20" />
<Setter Property="Source"
Value="{extensions:ImageResource BookAStar.Images.Validation.error.png}" />
</Style>
</converters:BooleanToObjectConverter.FalseObject>
<converters:BooleanToObjectConverter.TrueObject>
<Style TargetType="Image">
<Setter Property="HeightRequest" Value="20" />
<Setter Property="Source"
Value="{extensions:ImageResource BookAStar.Images.Validation.success.png}" />
</Style>
</converters:BooleanToObjectConverter.TrueObject>
</converters:BooleanToObjectConverter>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<ScrollView>
<StackLayout Padding="10,10,10,10" x:Name="mainLayout">
<Grid MinimumHeightRequest="12">
<Grid HeightRequest="120">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
@ -35,9 +55,7 @@
<Entry Placeholder="Saisissez un titre pour ce devis" Text="{Binding Title, Mode=TwoWay}" />
<views:MarkdownView x:Name="mdview"
Editable="True"
HorizontalOptions="FillAndExpand"
Markdown="{Binding Description, Mode=TwoWay}"
VerticalOptions="Start" >
Markdown="{Binding Description, Mode=TwoWay}">
<views:MarkdownView.Behaviors>
<behaviors:MarkdownViewLengthValidator x:Name="descriptionLenValidator" MaxLength="512" MinLength="3" />
@ -45,13 +63,12 @@
</views:MarkdownView>
<StackLayout x:Name="biAnVaLayout">
<ListView x:Name="BillListView" ItemsSource="{Binding Bill}"
MinimumHeightRequest="40" HasUnevenRows="true" VerticalOptions="FillAndExpand"
HeightRequest="40" ItemTapped="OnEditLine">
HasUnevenRows="true" ItemTapped="OnEditLine">
<ListView.ItemTemplate>
<DataTemplate >
<ViewCell>
<ViewCell.View>
<Grid MinimumHeightRequest="12">
<ViewCell.View Padding="5,5,5,5" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
@ -60,6 +77,7 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30" />
<ColumnDefinition Width="90" />
<ColumnDefinition Width="16" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Description}"
Grid.Row="0" Grid.Column="0" ></Label>
@ -70,6 +88,9 @@
<Label Text="{Binding UnitaryCost}"
Grid.Row="0" Grid.Column="3"
FontFamily="Monospace"></Label>
<Image Grid.Row="0" Grid.Column="4" Style="{Binding
Path=ViewModelState.IsValid,
Converter={StaticResource boolToStyleImage}}" ></Image>
</Grid>
</ViewCell.View>
</ViewCell>
@ -83,6 +104,5 @@
</StackLayout>
</StackLayout>
</StackLayout>
</ScrollView>
</ContentPage.Content>
</ContentPage>

View file

@ -57,11 +57,20 @@ namespace BookAStar.Pages
protected void OnEditLine(object sender, ItemTappedEventArgs e)
{
var line = (BillingLineViewModel)e.Item;
// update the validation command, that
// was creating a new line in the bill at creation time,
// now one only wants to update the line
line.ValidateCommand = new Command(() =>
{
DataManager.Current.EstimationCache.SaveEntity();
});
// and setup a removal command, that was not expected at creation time
var evm = (EditEstimateViewModel)BindingContext;
line.RemoveCommand = new Command(() =>
{
evm.Bill.Remove(line);
DataManager.Current.EstimationCache.SaveEntity();
});
App.NavigationService.NavigateTo<EditBillingLinePage>(
true, line );
}
@ -78,7 +87,9 @@ namespace BookAStar.Pages
}
await thisPage.Navigation.PopAsync();
});
var response = await App.DisplayActionSheet(Strings.SignOrNot, Strings.DonotsignEstimate, Strings.CancelValidation, new string[] { Strings.Sign });
var response = await App.DisplayActionSheet(
Strings.SignOrNot, Strings.DonotsignEstimate,
Strings.CancelValidation, new string[] { Strings.Sign });
if (response == Strings.Sign)
{
App.NavigationService.NavigateTo<EstimateSigningPage>(true,

View file

@ -142,6 +142,42 @@ namespace BookAStar {
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Valeur invalide.
/// </summary>
public static string InvalidValue {
get {
return ResourceManager.GetString("InvalidValue", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Veuillez saisir une valeur entre {0} et {1}..
/// </summary>
public static string MinMaxIntError {
get {
return ResourceManager.GetString("MinMaxIntError", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Veuillez saisir une description (entre {0} et {1} caractères)..
/// </summary>
public static string MinMaxStringValidationError {
get {
return ResourceManager.GetString("MinMaxStringValidationError", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Pas de description.
/// </summary>
public static string NoDescription {
get {
return ResourceManager.GetString("NoDescription", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Un artiste.
/// </summary>
@ -231,5 +267,14 @@ namespace BookAStar {
return ResourceManager.GetString("ViewEstimate", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Votre texte a été taillé car il était trop long..
/// </summary>
public static string YourTextWasTooLong {
get {
return ResourceManager.GetString("YourTextWasTooLong", resourceCulture);
}
}
}
}

View file

@ -176,4 +176,19 @@
<data name="UpdateFailed" xml:space="preserve">
<value>La mise à jour a échoué.</value>
</data>
<data name="MinMaxStringValidationError" xml:space="preserve">
<value>Veuillez saisir une description (entre {0} et {1} caractères).</value>
</data>
<data name="YourTextWasTooLong" xml:space="preserve">
<value>Votre texte a été taillé car il était trop long.</value>
</data>
<data name="NoDescription" xml:space="preserve">
<value>Pas de description</value>
</data>
<data name="InvalidValue" xml:space="preserve">
<value>Valeur invalide</value>
</data>
<data name="MinMaxIntError" xml:space="preserve">
<value>Veuillez saisir une valeur entre {0} et {1}.</value>
</data>
</root>

View file

@ -1,56 +0,0 @@
using BookAStar.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using XLabs.Forms.Mvvm;
namespace BookAStar.ViewModels
{
/// <summary>
/// Used to make the DataManager know how
/// to sync local and remote data
/// </summary>
public class EditingViewModel: ViewModel
{
bool existsRemotely;
public bool ExistsRemotely
{
get
{
return existsRemotely;
}
set
{
base.SetProperty<bool>(ref existsRemotely, value);
}
}
bool isValid;
public bool IsValid
{
get
{
return isValid;
}
set
{
base.SetProperty<bool>(ref isValid, value);
}
}
bool isDirty;
public bool IsDirty
{
get
{
return isDirty;
}
set
{
base.SetProperty<bool>(ref isDirty, value);
}
}
}
}

View file

@ -1,19 +1,54 @@
using BookAStar.Attributes;
using BookAStar.Interfaces;
using BookAStar.Model.Workflow;
using BookAStar.ViewModels.Validation;
using System;
using System.Globalization;
using System.Windows.Input;
using System.ComponentModel;
namespace BookAStar.ViewModels.EstimateAndBilling
{
public class BillingLineViewModel : EditingViewModel, IBillingLine
public class BillingLineViewModel : EditingViewModel<BillingLine>, IBillingLine
{
BillingLine data;
public ICommand RemoveCommand { get; set; }
public ICommand ValidateCommand { set; get; }
public BillingLineViewModel(BillingLine data)
public BillingLineViewModel(BillingLine data): base(data)
{
this.Data = data;
CheckCommand = new Action<BillingLine, ModelState>(
(l,s) => {
if (string.IsNullOrWhiteSpace(l.Description))
{
s.AddError("Description",Strings.NoDescription);
}
if (l.UnitaryCost < 0) { s.AddError("UnitaryCost", Strings.InvalidValue); }
if (l.Count < 0) { s.AddError("Count", Strings.InvalidValue); }
});
SyncData();
}
private void SyncData()
{
if (Data != null)
{
// set durationValue, durationUnit
Duration = Data.Duration;
// other redondant representation
count = Data.Count;
description = Data.Description;
unitaryCostText = Data.UnitaryCost.ToString("G", CultureInfo.InvariantCulture);
}
CheckCommand(Data, ViewModelState);
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.PropertyName=="Data")
{
SyncData();
}
}
private int count;
@ -27,7 +62,7 @@ namespace BookAStar.ViewModels.EstimateAndBilling
set
{
SetProperty<int>(ref count, value);
data.Count = count;
Data.Count = count;
}
}
private string description;
@ -41,10 +76,10 @@ namespace BookAStar.ViewModels.EstimateAndBilling
set
{
SetProperty<string>(ref description, value);
data.Description = description;
IsValid = !string.IsNullOrWhiteSpace(description);
Data.Description = description;
}
}
decimal unitaryCost;
public decimal UnitaryCost
{
@ -56,7 +91,7 @@ namespace BookAStar.ViewModels.EstimateAndBilling
set
{
SetProperty<decimal>(ref unitaryCost, value);
data.UnitaryCost = unitaryCost;
Data.UnitaryCost = unitaryCost;
}
}
@ -71,7 +106,7 @@ namespace BookAStar.ViewModels.EstimateAndBilling
set
{
SetProperty<int>(ref durationValue, value, "DurationValue");
data.Duration = this.Duration;
Data.Duration = this.Duration;
}
}
@ -94,7 +129,7 @@ pour décrire la quantité de travail associée à ce type de service")]
set
{
SetProperty<DurationUnits>(ref durationUnit, value, "DurationUnit");
data.Duration = this.Duration;
Data.Duration = this.Duration;
}
}
@ -120,7 +155,6 @@ pour décrire la quantité de travail associée à ce type de service")]
}
}
public ICommand ValidateCommand { set; get; }
public TimeSpan Duration
{
@ -161,26 +195,5 @@ pour décrire la quantité de travail associée à ce type de service")]
}
}
public BillingLine Data
{
get
{
return data;
}
set
{
SetProperty<BillingLine>(ref data, value);
if (data != null)
{
// sets durationValue & durationUnit
count = data.Count;
description = data.Description;
Duration = data.Duration;
unitaryCostText = data.UnitaryCost.ToString("G", CultureInfo.InvariantCulture);
}
}
}
}
}

View file

@ -6,18 +6,13 @@ using Xamarin.Forms;
using BookAStar.Data;
using Newtonsoft.Json;
using System.Linq;
using BookAStar.ViewModels.Validation;
using System.ComponentModel;
namespace BookAStar.ViewModels.EstimateAndBilling
{
public class EditEstimateViewModel : EditingViewModel
public class EditEstimateViewModel : EditingViewModel<Estimate>
{
/// <summary>
/// For deserialization
/// </summary>
public EditEstimateViewModel()
{
}
/// <summary>
/// Builds a new view model on estimate,
@ -25,11 +20,37 @@ namespace BookAStar.ViewModels.EstimateAndBilling
/// </summary>
/// <param name="data"></param>
/// <param name="localState"></param>
public EditEstimateViewModel(Estimate data)
public EditEstimateViewModel(Estimate data) : base(data)
{
Data = data;
SyncData();
}
private void SyncData()
{
if (Data.AttachedFiles == null) Data.AttachedFiles = new List<string>();
if (Data.AttachedGraphics == null) Data.AttachedGraphics = new List<string>();
if (Data.Bill == null) Data.Bill = new List<BillingLine>();
AttachedFiles = new ObservableCollection<string>(Data.AttachedFiles);
AttachedGraphicList = new ObservableCollection<string>(Data.AttachedGraphics);
Bill = new ObservableCollection<BillingLineViewModel>(Data.Bill.Select(
l => new BillingLineViewModel(l)
));
Bill.CollectionChanged += Bill_CollectionChanged;
Title = Data.Title;
Description = Data.Description;
NotifyPropertyChanged("FormattedTotal");
NotifyPropertyChanged("Query");
NotifyPropertyChanged("CLient");
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.PropertyName == "Data")
{
SyncData();
}
}
/// <summary>
///
/// </summary>
@ -42,25 +63,6 @@ namespace BookAStar.ViewModels.EstimateAndBilling
NotifyPropertyChanged("Bill");
}
private Estimate data;
public Estimate Data { get { return data; } set {
SetProperty<Estimate>(ref data, value);
if (data.AttachedFiles == null) data.AttachedFiles = new List<string>();
if (data.AttachedGraphics == null) data.AttachedGraphics = new List<string>();
if (data.Bill == null) data.Bill = new List<BillingLine>();
AttachedFiles = new ObservableCollection<string>(data.AttachedFiles);
AttachedGraphicList = new ObservableCollection<string>(data.AttachedGraphics);
Bill = new ObservableCollection<BillingLineViewModel>(data.Bill.Select(
l => new BillingLineViewModel(l)
));
Bill.CollectionChanged += Bill_CollectionChanged;
Title = Data.Title;
Description = Data.Description;
NotifyPropertyChanged("FormattedTotal");
NotifyPropertyChanged("Query");
NotifyPropertyChanged("CLient");
} }
[JsonIgnore]
public ObservableCollection<string> AttachedFiles
{
@ -124,7 +126,7 @@ namespace BookAStar.ViewModels.EstimateAndBilling
{
get
{
OnPlatform<Font> lfs = (OnPlatform<Font>)App.Current.Resources["LargeFontSize"];
OnPlatform<Font> lfs = (OnPlatform<Font>)App.Current.Resources["MediumFontSize"];
OnPlatform<Color> etc = (OnPlatform<Color>)App.Current.Resources["EmphasisTextColor"];
return new FormattedString

View file

@ -1,6 +1,6 @@
using XLabs.Forms.Mvvm;
namespace BookAStar.Model.UI
namespace BookAStar.ViewModels
{
internal class PageState
{

View file

@ -6,9 +6,8 @@ namespace BookAStar.ViewModels.Signing
{
public class EstimateSigningViewModel: EditEstimateViewModel
{
public EstimateSigningViewModel(Estimate document)
public EstimateSigningViewModel(Estimate document): base(document)
{
Data = document;
}
public Command<bool> ValidationCommand { get; set; }
}

View file

@ -0,0 +1,83 @@
using BookAStar.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using XLabs.Forms.Mvvm;
using System.ComponentModel;
namespace BookAStar.ViewModels.Validation
{
/// <summary>
/// Used to make the DataManager know how
/// to sync local and remote data
/// </summary>
public class EditingViewModel<DataType>: ViewModel
{
public Action<DataType, ModelState> CheckCommand { set; get; }
public DataType Data { get; set; }
private ModelState viewModelState = new ModelState();
public ModelState ViewModelState
{
get
{
return viewModelState;
}
set
{
base.SetProperty<ModelState>(ref viewModelState, value);
}
}
public EditingViewModel(DataType data)
{
this.Data = data;
ViewModelState = new ModelState();
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (CheckCommand != null)
{
ViewModelState.Clear();
CheckCommand(Data, ViewModelState);
}
}
/* NOTE : I had a dream.
bool existsRemotely;
public bool ExistsRemotely
{
get
{
return existsRemotely;
}
set
{
base.SetProperty<bool>(ref existsRemotely, value);
}
}
bool isDirty;
public bool IsDirty
{
get
{
return isDirty;
}
set
{
base.SetProperty<bool>(ref isDirty, value);
}
}
*/
}
}

View file

@ -0,0 +1,13 @@
namespace BookAStar.ViewModels.Validation
{
public class InputError
{
public InputError(string errorMessage, ErrorSeverity severity)
{
Text = errorMessage;
Severity = severity;
}
public string Text { get; set; }
public ErrorSeverity Severity { get; set; }
}
}

View file

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BookAStar.ViewModels.Validation
{
public enum ErrorSeverity
{
Crippling,
Bearable
}
}

View file

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace BookAStar.ViewModels.Validation
{
public class ModelState : BindableObject
{
public static readonly BindableProperty IsValidProperty =
BindableProperty.Create("IsValid", typeof(bool), typeof(ModelState), false);
public static readonly BindableProperty ErrorsProperty =
BindableProperty.Create("Errors", typeof(Dictionary<string,List<InputError>>), typeof(ModelState), null);
public ModelState()
{
Errors = new Dictionary<string, List<InputError>>();
}
public bool IsValid
{
get
{
return (bool) GetValue(IsValidProperty);
}
}
public Dictionary<string, List<InputError>> Errors
{
get
{
return (Dictionary<string, List<InputError>>)GetValue(ErrorsProperty);
}
set
{
SetValue(ErrorsProperty, value);
}
}
public virtual void AddError(string propertyName, string errorMessage, ErrorSeverity severity = ErrorSeverity.Crippling)
{
InputError e = new InputError(errorMessage, severity) ;
if (Errors.ContainsKey(propertyName))
{
var errList = Errors[propertyName];
errList.Add(e);
}
else
{
Errors.Add(propertyName, new List<InputError>(new InputError [] { e }));
}
if (e.Severity < ErrorSeverity.Bearable)
SetValue(IsValidProperty, false);
}
public virtual void Clear ()
{
Errors.Clear();
SetValue(IsValidProperty, true);
}
}
}