une signature coté serveur

vnext
Paul Schneider 8 years ago
parent 7917f9db81
commit 5e180158d3
33 changed files with 698 additions and 211 deletions

@ -17,7 +17,7 @@ using System.Linq;
using System.Text;
[System.CodeDom.Compiler.GeneratedCodeAttribute("RazorTemplatePreprocessor", "4.2.0.703")]
[System.CodeDom.Compiler.GeneratedCodeAttribute("RazorTemplatePreprocessor", "4.2.1.62")]
public partial class MarkdownEditor : MarkdownEditorBase
{

@ -9,6 +9,7 @@ using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Graphics;
namespace BookAStar.Droid.Markdown
{
@ -17,6 +18,7 @@ namespace BookAStar.Droid.Markdown
protected static MarkdownDeep.Markdown markdown = new MarkdownDeep.Markdown();
public string Content { get; set; }
public bool Editable { get; set; }
public string GetHtml()
{
return markdown.Transform(Content);

@ -27,6 +27,7 @@ namespace BookAStar
using ViewModels.UserProfile;
using Pages.UserProfile;
using ViewModels.EstimateAndBilling;
using Pages.EstimatePages;
public partial class App : Application // superclass new in 1.3
{
@ -150,6 +151,7 @@ namespace BookAStar
ViewFactory.Register<EditEstimatePage, EditEstimateViewModel>();
ViewFactory.Register<UserFiles, DirectoryInfoViewModel>();
ViewFactory.Register<UserProfilePage, UserProfileViewModel>();
ViewFactory.Register<EstimateSigningPage, EditEstimateViewModel>();
ConfigManager = new GenericConfigSettingsMgr(s =>
MainSettings.AppSettings.GetValueOrDefault<string>(s, MainSettings.SettingsDefault), null);
}

@ -0,0 +1,42 @@
using System;
using Xamarin.Forms;
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 int MaxLength
{
get { return (int)GetValue(MaxLengthProperty); }
set { SetValue(MaxLengthProperty, value); }
}
public int MinLength
{
get { return (int)GetValue(MinLengthProperty); }
set { SetValue(MinLengthProperty, value); }
}
protected override void OnAttachedTo(Editor bindable)
{
bindable.TextChanged += bindable_TextChanged;
}
public bool IsValid { get; set; }
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);
}
protected override void OnDetachingFrom(Editor bindable)
{
bindable.TextChanged -= bindable_TextChanged;
}
}
}

@ -8,39 +8,14 @@ using Xamarin.Forms;
namespace BookAStar.Behaviors
{
public class EmailValidatorBehavior : Behavior<Entry>
public class EmailValidatorBehavior : RegexValidatorBehavior
{
const string emailRegex = @"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" +
@"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$";
// Creating BindableProperties with Limited write access: http://iosapi.xamarin.com/index.aspx?link=M%3AXamarin.Forms.BindableObject.SetValue(Xamarin.Forms.BindablePropertyKey%2CSystem.Object)
static readonly BindablePropertyKey IsValidPropertyKey = BindableProperty.CreateReadOnly("IsValid", typeof(bool), typeof(EmailValidatorBehavior), false);
public static readonly BindableProperty IsValidProperty = IsValidPropertyKey.BindableProperty;
public bool IsValid
{
get { return (bool)base.GetValue(IsValidProperty); }
private set { base.SetValue(IsValidPropertyKey, value); }
}
protected override void OnAttachedTo(Entry bindable)
{
bindable.TextChanged += HandleTextChanged;
}
void HandleTextChanged(object sender, TextChangedEventArgs e)
public EmailValidatorBehavior()
{
IsValid = (Regex.IsMatch(e.NewTextValue, emailRegex, RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250)));
((Entry)sender).TextColor = IsValid ? Color.Default : Color.Red;
}
protected override void OnDetachingFrom(Entry bindable)
{
bindable.TextChanged -= HandleTextChanged;
Regexp = emailRegex;
}
}
}

@ -0,0 +1,39 @@
using BookAStar.Views;
using Xamarin.Forms;
namespace BookAStar.Behaviors
{
public class MarkdownViewLengthValidator : Behavior<MarkdownView>
{
public static readonly BindableProperty MaxLengthProperty = BindableProperty.Create("MaxLength", typeof(int), typeof(MarkdownViewLengthValidator), 0);
public static readonly BindableProperty MinLengthProperty = BindableProperty.Create("MinLength", typeof(int), typeof(MarkdownViewLengthValidator), 0);
public int MaxLength
{
get { return (int)GetValue(MaxLengthProperty); }
set { SetValue(MaxLengthProperty, value); }
}
public int MinLength
{
get { return (int)GetValue(MinLengthProperty); }
set { SetValue(MinLengthProperty, value); }
}
protected override void OnAttachedTo(MarkdownView bindable)
{
bindable.Modified += bindable_TextChanged;
}
public bool IsValid { get; set; }
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);
}
protected override void OnDetachingFrom(MarkdownView bindable)
{
bindable.Modified -= bindable_TextChanged;
}
}
}

@ -1,63 +0,0 @@
using Xamarin.Forms;
namespace BookAStar.Behaviors
{
public class EditorMaxLengthValidator : Behavior<Editor>
{
public static readonly BindableProperty MaxLengthProperty = BindableProperty.Create("MaxLength", typeof(int), typeof(MaxLengthValidator), 0);
public int MaxLength
{
get { return (int)GetValue(MaxLengthProperty); }
set { SetValue(MaxLengthProperty, value); }
}
protected override void OnAttachedTo(Editor bindable)
{
bindable.TextChanged += bindable_TextChanged;
}
public bool IsValid { get; set; }
private void bindable_TextChanged(object sender, TextChangedEventArgs e)
{
IsValid = e.NewTextValue == null? false : ( e.NewTextValue.Length > 0 && e.NewTextValue.Length <= MaxLength ) ;
if (!IsValid) if (e.NewTextValue!=null) if (e.NewTextValue.Length > MaxLength)
((Editor)sender).Text = e.NewTextValue.Substring(0, MaxLength);
}
protected override void OnDetachingFrom(Editor bindable)
{
bindable.TextChanged -= bindable_TextChanged;
}
}
public class MaxLengthValidator : Behavior<Entry>
{
public static readonly BindableProperty MaxLengthProperty = BindableProperty.Create("MaxLength", typeof(int), typeof(MaxLengthValidator), 0);
public int MaxLength
{
get { return (int)GetValue(MaxLengthProperty); }
set { SetValue(MaxLengthProperty, value); }
}
protected override void OnAttachedTo(Entry bindable)
{
bindable.TextChanged += bindable_TextChanged;
}
private void bindable_TextChanged(object sender, TextChangedEventArgs e)
{
//if (MaxLength != null && MaxLength.HasValue)
if (e.NewTextValue.Length > MaxLength)
((Entry)sender).Text = e.NewTextValue.Substring(0, MaxLength);
}
protected override void OnDetachingFrom(Entry bindable)
{
bindable.TextChanged -= bindable_TextChanged;
}
}
}

@ -0,0 +1,53 @@
using System;
using System.Text.RegularExpressions;
using Xamarin.Forms;
namespace BookAStar.Behaviors
{
public class RegexValidatorBehavior : Behavior<Entry>
{
private string regexp = @"^([\d\w]+)$";
// Creating BindableProperties with Limited write access: http://iosapi.xamarin.com/index.aspx?link=M%3AXamarin.Forms.BindableObject.SetValue(Xamarin.Forms.BindablePropertyKey%2CSystem.Object)
static readonly BindablePropertyKey IsValidPropertyKey = BindableProperty.CreateReadOnly("IsValid", typeof(bool), typeof(RegexValidatorBehavior), false);
public static readonly BindableProperty IsValidProperty = IsValidPropertyKey.BindableProperty;
public bool IsValid
{
get { return (bool)base.GetValue(IsValidProperty); }
private set { base.SetValue(IsValidPropertyKey, value); }
}
protected string Regexp
{
get
{
return regexp;
}
set
{
regexp = value;
}
}
protected override void OnAttachedTo(Entry bindable)
{
bindable.TextChanged += HandleTextChanged;
}
void HandleTextChanged(object sender, TextChangedEventArgs e)
{
IsValid = (Regex.IsMatch(e.NewTextValue, regexp, RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250)));
((Entry)sender).TextColor = IsValid ? Color.Default : Color.Red;
}
protected override void OnDetachingFrom(Entry bindable)
{
bindable.TextChanged -= HandleTextChanged;
}
}
}

@ -44,14 +44,17 @@
<Compile Include="Attributes\DisplayAttribute.cs" />
<Compile Include="Behaviors\EmailValidatorBehavior.cs" />
<Compile Include="Behaviors\IntegerEntryBehavior.cs" />
<Compile Include="Behaviors\MaxLengthValidator.cs" />
<Compile Include="Behaviors\EditorMaxLengthValidator.cs" />
<Compile Include="Behaviors\DecimalValidatorBehavior.cs" />
<Compile Include="Behaviors\MarkdownViewLengthValidator.cs" />
<Compile Include="Behaviors\PickerBehavior.cs" />
<Compile Include="Behaviors\RegexValidatorBehavior.cs" />
<Compile Include="Behaviors\StarBehavior.cs" />
<Compile Include="Constants.cs" />
<Compile Include="Converters\NotValueConverter.cs" />
<Compile Include="Converters\SignalRConnectionStateToObject.cs" />
<Compile Include="Data\LocalState.cs" />
<Compile Include="Data\ApiCallFailedException.cs" />
<Compile Include="Data\EstimateEntity.cs" />
<Compile Include="Data\NonCrUD\RemoteFiles.cs" />
<Compile Include="Model\FileSystem\UserDirectoryInfo.cs" />
<Compile Include="Model\FileSystem\UserFileInfo.cs" />
@ -59,6 +62,9 @@
<Compile Include="Model\Social\Messaging\ChatStatus.cs" />
<Compile Include="Model\Social\Messaging\PrivateMessage.cs" />
<Compile Include="Model\UI\PageState.cs" />
<Compile Include="Pages\EstimatePages\EstimateSigningPage.xaml.cs">
<DependentUpon>EstimateSigningPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\EstimatePages\EstimatesClientPage.xaml.cs">
<DependentUpon>EstimatesClientPage.xaml</DependentUpon>
</Compile>
@ -89,7 +95,7 @@
<Compile Include="Pages\DocSigning.xaml.cs">
<DependentUpon>DocSigning.xaml</DependentUpon>
</Compile>
<Compile Include="ViewModels\Signing\DocSigningViewModel.cs" />
<Compile Include="ViewModels\Signing\EstimateSigningViewModel.cs" />
<Compile Include="ViewModels\Signing\SignaturePadConfigViewModel.cs" />
<Compile Include="ViewModels\UserProfile\UserProfileViewModel.cs" />
<Compile Include="ViewModels\UserProfile\DirectoryInfoViewModel.cs" />
@ -272,6 +278,12 @@
<Reference Include="Plugin.Connectivity.Abstractions">
<HintPath>..\..\packages\Xam.Plugin.Connectivity.2.2.12\lib\MonoAndroid10\Plugin.Connectivity.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Plugin.Media">
<HintPath>..\..\packages\Xam.Plugin.Media.2.3.0\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\Plugin.Media.dll</HintPath>
</Reference>
<Reference Include="Plugin.Media.Abstractions">
<HintPath>..\..\packages\Xam.Plugin.Media.2.3.0\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\Plugin.Media.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Plugin.Settings, Version=2.5.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.1.0\lib\portable-net45+wp80+win8+wpa81\Plugin.Settings.dll</HintPath>
<Private>True</Private>
@ -478,6 +490,12 @@
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\EstimatePages\EstimateSigningPage.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
<SubType>Designer</SubType>
</EmbeddedResource>
</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">

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BookAStar.Data
{
class ApiCallFailedException : Exception
{
public ApiCallFailedException(string message) : base(message)
{
}
public ApiCallFailedException(string message, Exception inner) : base(message, inner)
{
}
}
}

@ -13,7 +13,7 @@
{
// TODO estimatetemplate rating service product tag
public RemoteEntityRO<BookQueryData, long> BookQueries { get; set; }
public RemoteEntity<Estimate, long> Estimates { get; set; }
public EstimateEntity Estimates { get; set; }
public RemoteEntity<Blog, long> Blogspot { get; set; }
internal RemoteFilesEntity RemoteFiles { get; set; }
@ -41,7 +41,7 @@
public DataManager()
{
BookQueries = new RemoteEntityRO<BookQueryData, long>("bookquery", q => q.Id);
Estimates = new RemoteEntity<Estimate, long>("estimate", x => x.Id);
Estimates = new EstimateEntity();
Blogspot = new RemoteEntity<Blog, long>("blog", x=>x.Id);
Contacts = new LocalEntity<ClientProviderInfo, string>(c => c.UserId);

@ -0,0 +1,62 @@

namespace BookAStar.Data
{
using Helpers;
using Model.Workflow;
using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
public class EstimateEntity : RemoteEntity<Estimate, long>
{
public EstimateEntity() : base("estimate", e => e.Id)
{
}
public async void SignAsProvider(Estimate estimate, Stream pngStream)
{
if (estimate.Id == 0)
{
var ok = await this.Create(estimate);
if (!ok)
{
await App.DisplayAlert("Erreur d'accès au serveur", "Echec de l'envoi de l'estimation");
return;
}
this.Add(estimate);
}
using (HttpClient client = UserHelpers.CreateClient())
{
try
{
var requestContent = new MultipartFormDataContent();
var content = new StreamContent(pngStream);
var filename = $"prosign-{estimate.Id}.png";
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
content.Headers.Add("Content-Disposition", $"form-data; name=\"file\"; filename=\"{filename}\"");
requestContent.Add(content, "file", filename);
using (var response = await client.PostAsync(
Constants.YavscApiUrl + $"/pdfestimate/prosign/{estimate.Id}", requestContent))
{
if (!response.IsSuccessStatusCode)
{
var errContent = await response.Content.ReadAsStringAsync();
throw new ApiCallFailedException($"SignAsProvider: {response.StatusCode} / {errContent}");
}
var json = await response.Content.ReadAsStringAsync();
JsonConvert.PopulateObject(json, estimate);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
}
this.SaveEntity();
}
}
}

@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BookAStar.Data
{
public enum LocalState
{
UpToDate = 0,
New,
Edited,
Removed
}
}

@ -6,13 +6,6 @@ namespace BookAStar.Data.NonCrUD
{
using Helpers;
using Model.FileSystem;
using System.Linq;
/*
public class DirectoryEntryChangingEvent : EventArgs
{
public UserDirectoryInfo OldItem { get; set; }
public UserDirectoryInfo NewItem { get; set; }
}*/
public class RemoteFilesEntity : RemoteEntity<UserDirectoryInfo, FileAddress>
{

@ -22,7 +22,7 @@ namespace BookAStar.Data
public bool CanExecute(object parameter)
{
return !IsExecuting && (MainSettings.CurrentUser != null);
return !IsExecuting;
}
public RemoteEntity(string controllerName, Func<V, K> getKey) : base(getKey)
@ -36,7 +36,7 @@ namespace BookAStar.Data
protected void BeforeExecute()
{
if (IsExecuting)
throw new InvalidOperationException("Already executing");
throw new InvalidOperationException(Strings.AlreadyExecuting);
IsExecuting = true;
if (CanExecuteChanged != null)
CanExecuteChanged.Invoke(this, new EventArgs());
@ -71,7 +71,7 @@ namespace BookAStar.Data
}
catch (WebException webex)
{
throw new ServiceNotAvailable("No remote entity", webex);
throw new ServiceNotAvailable(Strings.ENoRemoteEntity, webex);
}
}
@ -123,23 +123,27 @@ namespace BookAStar.Data
return item;
}
public virtual async void Create(V item)
public virtual async Task<bool> Create(V item)
{
bool created = false;
BeforeExecute();
using (HttpClient client = UserHelpers.CreateClient())
{
var stringContent = JsonConvert.SerializeObject(item);
HttpContent content = new StringContent(
stringContent, Encoding.UTF8, "application/json"
);
using (var response = await client.PostAsync(ControllerUri, content))
{
if (!response.IsSuccessStatusCode)
created = response.IsSuccessStatusCode;
if (!created)
{
// TODO throw custom exception, and catch to inform user
var errcontent = await response.Content.ReadAsStringAsync();
Debug.WriteLine($"Create failed posting {stringContent} @ {ControllerUri.AbsoluteUri}: {errcontent}");
Debug.WriteLine(string.Format(Strings.CreationFailed));
Debug.WriteLine(errcontent);
}
else
{
@ -148,14 +152,15 @@ namespace BookAStar.Data
JsonConvert.PopulateObject(recontent, item);
}
}
}
CurrentItem = item;
AfterExecuting();
return created;
}
public virtual async void Update(V item)
public virtual async Task<bool> Update(V item)
{
var updated = false;
BeforeExecute();
var uri = GetUri(GetKey(item));
@ -166,13 +171,16 @@ namespace BookAStar.Data
);
using (var response = await client.PutAsync(uri, content))
{
if (!response.IsSuccessStatusCode)
updated = response.IsSuccessStatusCode;
if (!updated)
{// TODO throw custom exception, and catch to inform user
if (response.StatusCode == HttpStatusCode.BadRequest)
{
var recontent = await response.Content.ReadAsStringAsync();
}
var errorcontent = await response.Content.ReadAsStringAsync();
Debug.WriteLine(string.Format(Strings.UpdateFailed));
Debug.WriteLine(errorcontent);
}
else Debug.WriteLine($"Update failed ({item} @ {uri.AbsolutePath} )");
}
@ -188,6 +196,7 @@ namespace BookAStar.Data
CurrentItem = item;
AfterExecuting();
return updated;
}
public virtual async void Delete(K key)

@ -77,9 +77,14 @@ namespace BookAStar.Model.Workflow
return Bill?.Aggregate((decimal)0, (t, l) => t + l.Count * l.UnitaryCost) ?? (decimal)0;
}
}
public DateTime LatestValidationDate { get; set; }
/// <summary>
/// This validation comes from the provider.
/// As long as it's an estimate, no client validation
/// is formaly needed
/// </summary>
public DateTime ProviderValidationDate { get; set; }
public DateTime ClientApprouvalDate { get; set; }
}
}

@ -3,7 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:signature="clr-namespace:SignaturePad.Forms;assembly=SignaturePad.Forms"
xmlns:views="clr-namespace:BookAStar.Views;assembly=BookAStar"
x:Class="BookAStar.ViewModels.Signing.DocSigning">
x:Class="BookAStar.ViewModels.Signing.Signing">
<StackLayout>
<views:MarkdownView x:Name="mdView"
Editable="False"

@ -9,9 +9,9 @@ using Xamarin.Forms;
namespace BookAStar.ViewModels.Signing
{
public partial class DocSigning : ContentPage
public partial class Signing : ContentPage
{
public DocSigning()
public Signing()
{
InitializeComponent();
}

@ -47,7 +47,7 @@
<StackLayout Orientation="Horizontal" VerticalOptions="FillAndExpand">
<Editor HorizontalOptions="FillAndExpand" Text="{Binding Description, Mode=TwoWay}">
<Editor.Behaviors>
<behaviors:EditorMaxLengthValidator x:Name="descriptionValidator" MaxLength="512" />
<behaviors:EditorMaxLengthValidator x:Name="descriptionLenValidator" MaxLength="512" MinLength="3" />
</Editor.Behaviors>
</Editor>
</StackLayout>

@ -4,6 +4,7 @@
x:Class="BookAStar.Pages.EditEstimatePage"
xmlns:views="clr-namespace:BookAStar.Views;assembly=BookAStar"
xmlns:local="clr-namespace:BookAStar;assembly=BookAStar"
xmlns:behaviors="clr-namespace:BookAStar.Behaviors;assembly=BookAStar"
Style="{StaticResource PageStyle}">
<ContentPage.Resources>
<ResourceDictionary>
@ -36,7 +37,12 @@
Editable="True"
HorizontalOptions="FillAndExpand"
Markdown="{Binding Description, Mode=TwoWay}"
VerticalOptions="Start" />
VerticalOptions="Start" >
<views:MarkdownView.Behaviors>
<behaviors:MarkdownViewLengthValidator x:Name="descriptionLenValidator" MaxLength="512" MinLength="3" />
</views:MarkdownView.Behaviors>
</views:MarkdownView>
<StackLayout x:Name="biAnVaLayout">
<ListView x:Name="BillListView" ItemsSource="{Binding Bill}"
MinimumHeightRequest="40" HasUnevenRows="true" VerticalOptions="FillAndExpand"

@ -4,8 +4,10 @@ using Xamarin.Forms;
namespace BookAStar.Pages
{
using Data;
using EstimatePages;
using Model.Workflow;
using ViewModels.EstimateAndBilling;
using ViewModels.Signing;
public partial class EditEstimatePage : ContentPage
{
@ -46,54 +48,46 @@ namespace BookAStar.Pages
var bill = ((EditEstimateViewModel)BindingContext).Bill;
var lineView = new BillingLineViewModel(com)
{ ValidateCommand = new Command(() => {
bill.Add(com);
bill.Add(new BillingLineViewModel(com));
DataManager.Current.EstimationCache.SaveEntity();
})};
lineView.PropertyChanged += LineView_PropertyChanged;
App.NavigationService.NavigateTo<EditBillingLinePage>(
true, lineView );
}
protected void OnEditLine(object sender, ItemTappedEventArgs e)
{
var line = (BillingLine)e.Item;
var bill = ((EditEstimateViewModel)BindingContext).Bill;
var lineView = new BillingLineViewModel(line)
var line = (BillingLineViewModel)e.Item;
line.ValidateCommand = new Command(() =>
{
ValidateCommand = new Command(() => {
DataManager.Current.EstimationCache.SaveEntity();
})
};
lineView.PropertyChanged += LineView_PropertyChanged;
});
lineView.PropertyChanged += LineView_PropertyChanged;
App.NavigationService.NavigateTo<EditBillingLinePage>(
true, lineView );
}
private void LineView_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
DataManager.Current.EstimationCache.SaveEntity();
true, line );
}
protected void OnEstimateValidated(object sender, EventArgs e)
protected async void OnEstimateValidated(object sender, EventArgs e)
{
var thisPage = this;
var evm = (EditEstimateViewModel) BindingContext;
if (evm.Data.Id == 0)
var cmd = new Command<bool>( async (validated) =>
{
DataManager.Current.Estimates.Create(evm.Data);
// we have to manually add this item in our local collection,
// since we could prefer to update the whole collection
// from server, or whatever other scenario
DataManager.Current.Estimates.Add(evm.Data);
} else
{
DataManager.Current.Estimates.Update(evm.Data);
}
DataManager.Current.Estimates.SaveEntity();
if (validated) {
DataManager.Current.EstimationCache.Remove(evm);
DataManager.Current.EstimationCache.SaveEntity();
Navigation.PopAsync();
}
await thisPage.Navigation.PopAsync();
});
var response = await App.DisplayActionSheet(Strings.SignOrNot, Strings.DonotsignEstimate, Strings.CancelValidation, new string[] { Strings.Sign });
if (response == Strings.Sign)
{
App.NavigationService.NavigateTo<EstimateSigningPage>(true,
new EstimateSigningViewModel(evm.Data) { ValidationCommand = cmd });
}
else if (response == Strings.CancelValidation)
return;
else cmd.Execute(true);
}
}
}

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:signature="clr-namespace:SignaturePad.Forms;assembly=SignaturePad.Forms"
xmlns:views="clr-namespace:BookAStar.Views;assembly=BookAStar"
x:Class="BookAStar.Pages.EstimatePages.EstimateSigningPage">
<ContentPage.Content>
<StackLayout Padding="10,10,10,10" x:Name="mainLayout">
<ScrollView>
<StackLayout>
<Grid MinimumHeightRequest="12">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Text="{Binding Client.UserName}" ></Label>
<Label Grid.Row="0" Grid.Column="1" Text="{Binding Query.Location.Address}" ></Label>
<Label Grid.Row="0" Grid.Column="2" Text="{Binding Query.EventDate, StringFormat='{0:dddd d MMMM yyyy à hh:mm}'}" ></Label>
</Grid>
<Label Text="{Binding Title}" />
<views:MarkdownView x:Name="mdview"
HorizontalOptions="FillAndExpand"
Markdown="{Binding Description}"
/>
<ListView x:Name="billListView" ItemsSource="{Binding Bill}"
MinimumHeightRequest="40" HasUnevenRows="true" VerticalOptions="FillAndExpand"
HeightRequest="40" >
<ListView.ItemTemplate>
<DataTemplate >
<ViewCell>
<ViewCell.View>
<Grid MinimumHeightRequest="12">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30" />
<ColumnDefinition Width="90" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Description}"
Grid.Row="0" Grid.Column="0" ></Label>
<Label Text="{Binding Duration, StringFormat=\{0\}}"
Grid.Row="0" Grid.Column="1" ></Label>
<Label Text="{Binding Count}"
Grid.Row="0" Grid.Column="2" ></Label>
<Label Text="{Binding UnitaryCost}"
Grid.Row="0" Grid.Column="3"
FontFamily="Monospace"></Label>
</Grid>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Label FormattedText="{Binding FormattedTotal}"/>
</StackLayout>
</ScrollView>
<signature:SignaturePadView x:Name="padView"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
BackgroundColor="White"
CaptionText="Caption This" CaptionTextColor="Black"
ClearText="Clear Me!" ClearTextColor="Red"
PromptText="Prompt Here" PromptTextColor="Red"
SignatureLineColor="Aqua" StrokeColor="Black" StrokeWidth="2" />
<Button Clicked="OnValidate" Text="Valider cette signature" x:Name="btnValidate" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

@ -0,0 +1,84 @@
using SignaturePad.Forms;
using System;
using System.IO;
using System.Linq;
using Xamarin.Forms;
namespace BookAStar.Pages.EstimatePages
{
using Data;
using ViewModels.EstimateAndBilling;
using ViewModels.Signing;
public partial class EstimateSigningPage : ContentPage
{
public EstimateSigningPage(EstimateSigningViewModel model)
{
InitializeComponent();
BindingContext = model;
}
private async void OnValidate (object sender, EventArgs ev)
{
btnValidate.IsEnabled = false;
if (DataManager.Current.Estimates.IsExecuting)
{
await App.DisplayAlert(Strings.OperationPending, Strings.oups);
return;
}
var evm = (EditEstimateViewModel)BindingContext;
var estimate = evm.Data;
var pngStream = await padView.GetImageStreamAsync(SignatureImageFormat.Png);
pngStream.Seek(0, SeekOrigin.Begin);
DataManager.Current.Estimates.SignAsProvider(estimate, pngStream);
DataManager.Current.Estimates.SaveEntity();
await Navigation.PopAsync();
var ParentValidationCommand = ((EstimateSigningViewModel)BindingContext).ValidationCommand;
if (ParentValidationCommand != null)
ParentValidationCommand.Execute(true);
}
private async void OnGetStats(object sender, EventArgs e)
{
var points = padView.Points.ToArray();
using (var image = await padView.GetImageStreamAsync(SignatureImageFormat.Png))
{
var pointCount = points.Count();
var imageSize = image.Length / 1000;
var linesCount = points.Count(p => p == Point.Zero) + (points.Length > 0 ? 1 : 0);
await DisplayAlert("Stats", $"The signature has {linesCount} lines or {pointCount} points, and is {imageSize:#,###.0}KB (in memory) when saved as a PNG.", "Cool");
}
}
private async void OnChangeTheme(object sender, EventArgs e)
{
var action = await DisplayActionSheet("Change Theme", "Cancel", null, "White", "Black", "Aqua");
switch (action)
{
case "White":
padView.BackgroundColor = Color.White;
padView.StrokeColor = Color.Black;
padView.ClearTextColor = Color.Black;
padView.ClearText = "Clear Markers";
break;
case "Black":
padView.BackgroundColor = Color.Black;
padView.StrokeColor = Color.White;
padView.ClearTextColor = Color.White;
padView.ClearText = "Clear Chalk";
break;
case "Aqua":
padView.BackgroundColor = Color.Aqua;
padView.StrokeColor = Color.Red;
padView.ClearTextColor = Color.Black;
padView.ClearText = "Clear The Aqua";
break;
}
}
}
}

@ -33,9 +33,6 @@
</Grid>
<Label Text="{Binding Title}" />
<Label Text="{Binding Description}" />
<views:MarkdownView x:Name="mdview"
HorizontalOptions="FillAndExpand"
Markdown="{Binding Description}"

@ -1,5 +1,7 @@

using BookAStar.ViewModels.UserProfile;
using Plugin.Media;
using Plugin.Media.Abstractions;
using System;
using Xamarin.Forms;
@ -14,9 +16,31 @@ namespace BookAStar.Pages.UserProfile
AvatarButton.Clicked += AvatarButton_Clicked;
}
private void AvatarButton_Clicked (object sender, EventArgs e)
private async void AvatarButton_Clicked (object sender, EventArgs e)
{
throw new NotImplementedException();
if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
{
DisplayAlert("No Camera", ":( No camera avaialble.", "OK");
return;
}
var file = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions
{
Directory = "Sample",
Name = "test.jpg"
});
if (file == null)
return;
await DisplayAlert("File Location", file.Path, "OK");
AvatarButton.Image.File = file.Path;
/* ImageSource.FromStream(() =>
{
var stream = file.GetStream();
file.Dispose();
return stream;
}); */
}
public void OnManageFiles(object sender, EventArgs e)

@ -61,6 +61,33 @@ namespace BookAStar {
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Une execution est déjà en cours.
/// </summary>
public static string AlreadyExecuting {
get {
return ResourceManager.GetString("AlreadyExecuting", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Annuler la validation.
/// </summary>
public static string CancelValidation {
get {
return ResourceManager.GetString("CancelValidation", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à La création a échoué, contenu envoyé: {stringContent} @ Uri: {ControllerUri.AbsoluteUri}: Erreur : {errcontent}.
/// </summary>
public static string CreationFailed {
get {
return ResourceManager.GetString("CreationFailed", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Faire un devis.
/// </summary>
@ -70,6 +97,15 @@ namespace BookAStar {
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Valider le devis sans signer.
/// </summary>
public static string DonotsignEstimate {
get {
return ResourceManager.GetString("DonotsignEstimate", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Editer le devis.
/// </summary>
@ -79,6 +115,15 @@ namespace BookAStar {
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Erreur d&apos;accès aux données distantes.
/// </summary>
public static string ENoRemoteEntity {
get {
return ResourceManager.GetString("ENoRemoteEntity", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Star.
/// </summary>
@ -115,6 +160,42 @@ namespace BookAStar {
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Opération en cours.
/// </summary>
public static string OperationPending {
get {
return ResourceManager.GetString("OperationPending", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à oups..
/// </summary>
public static string oups {
get {
return ResourceManager.GetString("oups", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Signer.
/// </summary>
public static string Sign {
get {
return ResourceManager.GetString("Sign", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Signer le devis?.
/// </summary>
public static string SignOrNot {
get {
return ResourceManager.GetString("SignOrNot", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Incontournable.
/// </summary>
@ -134,7 +215,16 @@ namespace BookAStar {
}
/// <summary>
/// Recherche une chaîne localisée semblable à Voir le devis.
/// Recherche une chaîne localisée semblable à La mise à jour a échoué..
/// </summary>
public static string UpdateFailed {
get {
return ResourceManager.GetString("UpdateFailed", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Voir les devis validés.
/// </summary>
public static string ViewEstimate {
get {

@ -117,9 +117,15 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AlreadyExecuting" xml:space="preserve">
<value>Une execution est déjà en cours</value>
</data>
<data name="CancelValidation" xml:space="preserve">
<value>Annuler la validation</value>
</data>
<data name="CreationFailed" xml:space="preserve">
<value>La création a échoué, contenu envoyé: {stringContent} @ Uri: {ControllerUri.AbsoluteUri}: Erreur : {errcontent}</value>
</data>
<data name="DoEstimate" xml:space="preserve">
<value>Faire un devis</value>
</data>
@ -129,6 +135,9 @@
<data name="EditEstimate" xml:space="preserve">
<value>Editer le devis</value>
</data>
<data name="ENoRemoteEntity" xml:space="preserve">
<value>Erreur d'accès aux données distantes</value>
</data>
<data name="FiveStars" xml:space="preserve">
<value>Star</value>
<comment>Da star, da one</comment>
@ -143,6 +152,12 @@
<data name="OneStar" xml:space="preserve">
<value>Étoile montante</value>
</data>
<data name="OperationPending" xml:space="preserve">
<value>Opération en cours</value>
</data>
<data name="oups" xml:space="preserve">
<value>oups.</value>
</data>
<data name="Sign" xml:space="preserve">
<value>Signer</value>
</data>
@ -158,4 +173,7 @@
<data name="ViewEstimate" xml:space="preserve">
<value>Voir les devis validés</value>
</data>
<data name="UpdateFailed" xml:space="preserve">
<value>La mise à jour a échoué.</value>
</data>
</root>

@ -14,15 +14,42 @@ namespace BookAStar.ViewModels
/// </summary>
public class EditingViewModel: ViewModel
{
private LocalState state;
public LocalState State {
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 state;
return isDirty;
}
set
{
base.SetProperty<LocalState>(ref state, value);
base.SetProperty<bool>(ref isDirty, value);
}
}
}

@ -13,13 +13,7 @@ namespace BookAStar.ViewModels.EstimateAndBilling
public BillingLineViewModel(BillingLine data)
{
this.data = data ?? new BillingLine();
// sets durationValue & durationUnit
count = data.Count;
description = data.Description;
Duration = data.Duration;
unitaryCostText = data.UnitaryCost.ToString("G", CultureInfo.InvariantCulture);
this.Data = data;
}
private int count;
@ -48,6 +42,7 @@ namespace BookAStar.ViewModels.EstimateAndBilling
{
SetProperty<string>(ref description, value);
data.Description = description;
IsValid = !string.IsNullOrWhiteSpace(description);
}
}
decimal unitaryCost;
@ -166,5 +161,26 @@ 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);
}
}
}
}
}

@ -5,6 +5,7 @@ using BookAStar.Model;
using Xamarin.Forms;
using BookAStar.Data;
using Newtonsoft.Json;
using System.Linq;
namespace BookAStar.ViewModels.EstimateAndBilling
{
@ -17,6 +18,7 @@ namespace BookAStar.ViewModels.EstimateAndBilling
{
}
/// <summary>
/// Builds a new view model on estimate,
/// sets <c>Data</c> with given value parameter
@ -28,12 +30,18 @@ namespace BookAStar.ViewModels.EstimateAndBilling
Data = data;
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Bill_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Data.Bill = new List<BillingLine>( Bill );
Data.Bill = Bill.Select(l => l.Data).ToList();
NotifyPropertyChanged("FormattedTotal");
NotifyPropertyChanged("Bill");
}
private Estimate data;
public Estimate Data { get { return data; } set {
SetProperty<Estimate>(ref data, value);
@ -42,7 +50,9 @@ namespace BookAStar.ViewModels.EstimateAndBilling
if (data.Bill == null) data.Bill = new List<BillingLine>();
AttachedFiles = new ObservableCollection<string>(data.AttachedFiles);
AttachedGraphicList = new ObservableCollection<string>(data.AttachedGraphics);
Bill = new ObservableCollection<BillingLine>(data.Bill);
Bill = new ObservableCollection<BillingLineViewModel>(data.Bill.Select(
l => new BillingLineViewModel(l)
));
Bill.CollectionChanged += Bill_CollectionChanged;
Title = Data.Title;
Description = Data.Description;
@ -64,7 +74,7 @@ namespace BookAStar.ViewModels.EstimateAndBilling
}
[JsonIgnore]
public ObservableCollection<BillingLine> Bill
public ObservableCollection<BillingLineViewModel> Bill
{
get; protected set;
}

@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using XLabs.Forms.Mvvm;
namespace BookAStar.ViewModels.Signing
{
class DocSigningViewModel: ViewModel
{
/// <summary>
/// The doc to sign, in Markdown format
/// </summary>
public string Document { get; set; }
}
}

@ -0,0 +1,15 @@
using BookAStar.Model.Workflow;
using BookAStar.ViewModels.EstimateAndBilling;
using Xamarin.Forms;
namespace BookAStar.ViewModels.Signing
{
public class EstimateSigningViewModel: EditEstimateViewModel
{
public EstimateSigningViewModel(Estimate document)
{
Data = document;
}
public Command<bool> ValidationCommand { get; set; }
}
}

@ -26,13 +26,14 @@ namespace BookAStar.Views
}
set
{
var oldValue = Markdown;
if (Markdown != value)
{
SetValue(MarkdownProperty, value);
if (Modified != null)
{
Modified.Invoke(this, new EventArgs());
Modified.Invoke(this, new TextChangedEventArgs(oldValue, Markdown));
}
}
}
@ -46,7 +47,7 @@ namespace BookAStar.Views
set { SetValue (EditableProperty, value); }
}
public event EventHandler<EventArgs> Modified;
public event EventHandler<TextChangedEventArgs> Modified;
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{

Loading…