Platform.TryGetCurrentLocationAsync
All checks were successful
Dotnet build and test / build (pull_request) Successful in 8m30s
All checks were successful
Dotnet build and test / build (pull_request) Successful in 8m30s
This commit is contained in:
parent
55d305f295
commit
914e9486b5
11 changed files with 395 additions and 46 deletions
|
|
@ -1,4 +1,5 @@
|
|||
using Android.App;
|
||||
using Android;
|
||||
using Android.Runtime;
|
||||
using Avalonia;
|
||||
using Avalonia.Android;
|
||||
|
|
@ -9,6 +10,9 @@ using Avalonia.Controls;
|
|||
using Avalonia.Styling;
|
||||
using Yavsc.Api.Client;
|
||||
|
||||
[assembly: UsesPermission(Manifest.Permission.AccessFineLocation)]
|
||||
[assembly: UsesPermission(Manifest.Permission.AccessCoarseLocation)]
|
||||
|
||||
namespace PostIt.Android
|
||||
{
|
||||
[Application]
|
||||
|
|
|
|||
|
|
@ -57,6 +57,17 @@ public class MainActivity : AvaloniaMainActivity
|
|||
|
||||
}
|
||||
|
||||
public override void OnRequestPermissionsResult(int requestCode, string[]? permissions, Permission[]? grantResults)
|
||||
{
|
||||
if (PostIt.Android.Services.AndroidCurrentLocationProvider
|
||||
.HandlePermissionResult(requestCode, grantResults))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
internal static class AndroidOidcCallbackSink
|
||||
{
|
||||
private static System.Threading.Tasks.TaskCompletionSource<string>? _pending;
|
||||
|
|
|
|||
|
|
@ -14,11 +14,12 @@ internal static class PlatformBootstrap
|
|||
{
|
||||
internal static void InitPlatform()
|
||||
{
|
||||
|
||||
Platform.CreateBrowser = () =>
|
||||
{
|
||||
var activity = MainActivity.Current;
|
||||
return activity is null ? null : new AndroidSystemBrowser(activity);
|
||||
};
|
||||
|
||||
Platform.TryGetCurrentLocationAsync = AndroidCurrentLocationProvider.TryGetCurrentLocationAsync;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,130 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Android;
|
||||
using Android.App;
|
||||
using Android.Content.PM;
|
||||
using Android.Locations;
|
||||
using AndroidX.Core.App;
|
||||
using AndroidX.Core.Content;
|
||||
using PostIt.Services;
|
||||
|
||||
namespace PostIt.Android.Services;
|
||||
|
||||
internal static class AndroidCurrentLocationProvider
|
||||
{
|
||||
public static async Task<CurrentLocationResult> TryGetCurrentLocationAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var activity = MainActivity.Current;
|
||||
if (activity is null)
|
||||
{
|
||||
return CurrentLocationResult.Unavailable("L'activité Android n'est pas encore prête.");
|
||||
}
|
||||
|
||||
var permissionGranted = await LocationPermissionBroker.EnsureGrantedAsync(activity, cancellationToken).ConfigureAwait(false);
|
||||
if (!permissionGranted)
|
||||
{
|
||||
return CurrentLocationResult.PermissionDenied();
|
||||
}
|
||||
|
||||
var locationManager = activity.GetSystemService(global::Android.Content.Context.LocationService) as LocationManager;
|
||||
if (locationManager is null)
|
||||
{
|
||||
return CurrentLocationResult.Unavailable("Le service de localisation Android est indisponible.");
|
||||
}
|
||||
|
||||
var location = locationManager.GetProviders(enabledOnly: true)?
|
||||
.Select(provider => locationManager.GetLastKnownLocation(provider))
|
||||
.Where(candidate => candidate is not null)
|
||||
.OrderByDescending(candidate => candidate!.Time)
|
||||
.ThenBy(candidate => candidate!.Accuracy)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (location is null)
|
||||
{
|
||||
return CurrentLocationResult.Unavailable("Aucune position n'est disponible. Activez la localisation du système puis réessayez.");
|
||||
}
|
||||
|
||||
return CurrentLocationResult.Success(location.Latitude, location.Longitude);
|
||||
}
|
||||
|
||||
public static bool HandlePermissionResult(int requestCode, Permission[]? grantResults)
|
||||
=> LocationPermissionBroker.HandleResult(requestCode, grantResults);
|
||||
|
||||
private static class LocationPermissionBroker
|
||||
{
|
||||
private const int RequestCode = 4042;
|
||||
private static readonly string[] RequestedPermissions =
|
||||
{
|
||||
Manifest.Permission.AccessFineLocation,
|
||||
Manifest.Permission.AccessCoarseLocation,
|
||||
};
|
||||
|
||||
private static readonly object SyncRoot = new();
|
||||
private static TaskCompletionSource<bool>? _pendingRequest;
|
||||
|
||||
public static Task<bool> EnsureGrantedAsync(Activity activity, CancellationToken cancellationToken)
|
||||
{
|
||||
if (HasLocationPermission(activity))
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (_pendingRequest is null)
|
||||
{
|
||||
_pendingRequest = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
ActivityCompat.RequestPermissions(activity, RequestedPermissions, RequestCode);
|
||||
}
|
||||
|
||||
if (!cancellationToken.CanBeCanceled)
|
||||
{
|
||||
return _pendingRequest.Task;
|
||||
}
|
||||
|
||||
return WaitAsync(_pendingRequest.Task, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool HandleResult(int requestCode, Permission[]? grantResults)
|
||||
{
|
||||
if (requestCode != RequestCode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var granted = grantResults is { Length: > 0 } && grantResults.All(result => result == Permission.Granted);
|
||||
TaskCompletionSource<bool>? pendingRequest;
|
||||
lock (SyncRoot)
|
||||
{
|
||||
pendingRequest = _pendingRequest;
|
||||
_pendingRequest = null;
|
||||
}
|
||||
|
||||
pendingRequest?.TrySetResult(granted);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool HasLocationPermission(Activity activity)
|
||||
{
|
||||
return ContextCompat.CheckSelfPermission(activity, Manifest.Permission.AccessFineLocation) == Permission.Granted
|
||||
|| ContextCompat.CheckSelfPermission(activity, Manifest.Permission.AccessCoarseLocation) == Permission.Granted;
|
||||
}
|
||||
|
||||
private static async Task<bool> WaitAsync(Task<bool> task, CancellationToken cancellationToken)
|
||||
{
|
||||
using var registration = cancellationToken.Register(() =>
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
_pendingRequest?.TrySetCanceled(cancellationToken);
|
||||
_pendingRequest = null;
|
||||
}
|
||||
});
|
||||
|
||||
return await task.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using PostIt.Services;
|
||||
using PostIt.ViewModels;
|
||||
using Yavsc;
|
||||
using Yavsc.Abstract.Workflow;
|
||||
|
|
@ -58,6 +59,61 @@ public class BillingCommandPageViewModelTests
|
|||
Assert.Contains("n'est pas encore pris en charge", vm.StatusMessage, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SubmitAsync_allows_missing_coordinates_and_omits_them_from_payload()
|
||||
{
|
||||
var api = new RecordingApi();
|
||||
var client = new BillingApiClient(api, "https://business.example/api/v1/");
|
||||
var vm = new BillingCommandPageViewModel(
|
||||
new ActivityBrowseItemDto { Code = "dev", Name = "Développement" },
|
||||
new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" },
|
||||
new CommandFormSummaryDto { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" },
|
||||
client)
|
||||
{
|
||||
EventDateText = "2026-09-02 14:30",
|
||||
Reason = "Point de cadrage",
|
||||
Address = "1 rue du Test",
|
||||
LatitudeText = string.Empty,
|
||||
LongitudeText = string.Empty,
|
||||
Consent = true,
|
||||
};
|
||||
|
||||
await vm.SubmitCommand.ExecuteAsync(null);
|
||||
|
||||
using var json = JsonDocument.Parse(JsonSerializer.Serialize(api.LastBody));
|
||||
var location = json.RootElement.GetProperty("Location");
|
||||
Assert.Equal("1 rue du Test", location.GetProperty("Address").GetString());
|
||||
Assert.False(location.TryGetProperty("Latitude", out _));
|
||||
Assert.False(location.TryGetProperty("Longitude", out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UseCurrentLocationAsync_prefills_coordinates_from_platform_provider()
|
||||
{
|
||||
var original = Platform.TryGetCurrentLocationAsync;
|
||||
try
|
||||
{
|
||||
Platform.TryGetCurrentLocationAsync = _ => Task.FromResult(CurrentLocationResult.Success(48.8566, 2.3522));
|
||||
|
||||
var api = new RecordingApi();
|
||||
var client = new BillingApiClient(api, "https://business.example/api/v1/");
|
||||
var vm = new BillingCommandPageViewModel(
|
||||
new ActivityBrowseItemDto { Code = "dev", Name = "Développement" },
|
||||
new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" },
|
||||
new CommandFormSummaryDto { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" },
|
||||
client);
|
||||
|
||||
await vm.UseCurrentLocationCommand.ExecuteAsync(null);
|
||||
|
||||
Assert.Equal("48.8566", vm.LatitudeText);
|
||||
Assert.Equal("2.3522", vm.LongitudeText);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Platform.TryGetCurrentLocationAsync = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InitializeAsync_loads_prestations_for_brush_and_submit_posts_selected_prestation()
|
||||
{
|
||||
|
|
@ -216,4 +272,4 @@ public class BillingCommandPageViewModelTests
|
|||
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
28
src/PostIt/PostIt/Services/CurrentLocationResult.cs
Normal file
28
src/PostIt/PostIt/Services/CurrentLocationResult.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
namespace PostIt.Services;
|
||||
|
||||
public sealed class CurrentLocationResult
|
||||
{
|
||||
private CurrentLocationResult(bool isSuccess, bool isPermissionDenied, double? latitude, double? longitude, string message)
|
||||
{
|
||||
IsSuccess = isSuccess;
|
||||
IsPermissionDenied = isPermissionDenied;
|
||||
Latitude = latitude;
|
||||
Longitude = longitude;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public bool IsSuccess { get; }
|
||||
public bool IsPermissionDenied { get; }
|
||||
public double? Latitude { get; }
|
||||
public double? Longitude { get; }
|
||||
public string Message { get; }
|
||||
|
||||
public static CurrentLocationResult Success(double latitude, double longitude, string? message = null)
|
||||
=> new(true, false, latitude, longitude, message ?? "Position récupérée.");
|
||||
|
||||
public static CurrentLocationResult PermissionDenied(string? message = null)
|
||||
=> new(false, true, null, null, message ?? "La géolocalisation n'est pas autorisée.");
|
||||
|
||||
public static CurrentLocationResult Unavailable(string? message = null)
|
||||
=> new(false, false, null, null, message ?? "La géolocalisation n'est pas disponible sur cette plateforme.");
|
||||
}
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
using System;
|
||||
using IdentityModel.OidcClient.Browser;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PostIt.Services;
|
||||
|
||||
|
|
@ -37,4 +40,12 @@ public static class Platform
|
|||
/// </summary>
|
||||
public static System.Func<IBrowser?>? CreateBrowser { get; set; } =
|
||||
() => new CustomSchemeBrowser(CustomScheme);
|
||||
|
||||
/// <summary>
|
||||
/// Optional platform hook used by the shared billing form to request a
|
||||
/// current device position. Platforms that do not expose a native
|
||||
/// location provider can leave the default delegate in place.
|
||||
/// </summary>
|
||||
public static Func<CancellationToken, Task<CurrentLocationResult>> TryGetCurrentLocationAsync { get; set; } =
|
||||
_ => Task.FromResult(CurrentLocationResult.Unavailable());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using PostIt.Services;
|
||||
using Yavsc;
|
||||
using Yavsc.Abstract.Workflow;
|
||||
using Yavsc.Api.Client;
|
||||
using Yavsc.Models.Billing;
|
||||
using Yavsc.Models.Haircut;
|
||||
using Yavsc.Models.Relationship;
|
||||
|
||||
namespace PostIt.ViewModels;
|
||||
|
||||
|
|
@ -67,6 +67,8 @@ public partial class BillingCommandPageViewModel : ViewModelBase
|
|||
[ObservableProperty]
|
||||
public partial QueryStatus CommandStatus { get; set; } = QueryStatus.Inserted;
|
||||
|
||||
public bool CanUseCurrentLocation => IsSupported && !IsBusy;
|
||||
|
||||
public string Title => Form.Title;
|
||||
public string PerformerLabel => Performer.UserName;
|
||||
public string ActivityLabel => Activity.Name;
|
||||
|
|
@ -122,6 +124,12 @@ public partial class BillingCommandPageViewModel : ViewModelBase
|
|||
OnPropertyChanged(nameof(SubmitLabel));
|
||||
}
|
||||
|
||||
partial void OnIsBusyChanged(bool value)
|
||||
{
|
||||
OnPropertyChanged(nameof(CanUseCurrentLocation));
|
||||
UseCurrentLocationCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
public async Task InitializeAsync(BillingQueryDetailsDto? existingQuery = null)
|
||||
{
|
||||
if (!IsBrush && !IsMultiBrush)
|
||||
|
|
@ -198,27 +206,17 @@ public partial class BillingCommandPageViewModel : ViewModelBase
|
|||
return;
|
||||
}
|
||||
|
||||
if (!TryParseCoordinate(LatitudeText, out var latitude))
|
||||
if (!TryParseCoordinates(out var latitude, out var longitude, out var coordinateError))
|
||||
{
|
||||
StatusMessage = "Latitude invalide.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryParseCoordinate(LongitudeText, out var longitude))
|
||||
{
|
||||
StatusMessage = "Longitude invalide.";
|
||||
StatusMessage = coordinateError;
|
||||
return;
|
||||
}
|
||||
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
var location = new Location
|
||||
{
|
||||
Address = Address.Trim(),
|
||||
Latitude = latitude,
|
||||
Longitude = longitude,
|
||||
};
|
||||
var address = Address.Trim();
|
||||
var locationPayload = BuildLocationPayload(address, latitude, longitude);
|
||||
|
||||
var payload = new BillingQueryDetailsDto
|
||||
{
|
||||
|
|
@ -233,9 +231,9 @@ public partial class BillingCommandPageViewModel : ViewModelBase
|
|||
AdditionalInfo = string.IsNullOrWhiteSpace(AdditionalInfo) ? string.Empty : AdditionalInfo.Trim(),
|
||||
Location = new BillingLocationDto
|
||||
{
|
||||
Address = location.Address,
|
||||
Latitude = location.Latitude,
|
||||
Longitude = location.Longitude,
|
||||
Address = address,
|
||||
Latitude = latitude,
|
||||
Longitude = longitude,
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -253,7 +251,7 @@ public partial class BillingCommandPageViewModel : ViewModelBase
|
|||
PerformerId = Performer.PerformerId,
|
||||
Consent,
|
||||
EventDate = eventDate,
|
||||
Location = location,
|
||||
Location = locationPayload,
|
||||
Reason = payload.Reason,
|
||||
Status = payload.Status,
|
||||
}).ConfigureAwait(true);
|
||||
|
|
@ -281,7 +279,7 @@ public partial class BillingCommandPageViewModel : ViewModelBase
|
|||
PerformerId = Performer.PerformerId,
|
||||
Consent,
|
||||
EventDate = (DateTime?)eventDate,
|
||||
Location = location,
|
||||
Location = locationPayload,
|
||||
PrestationId = SelectedPrestation.Id,
|
||||
AdditionalInfo = string.IsNullOrWhiteSpace(AdditionalInfo) ? null : AdditionalInfo.Trim(),
|
||||
Status = payload.Status,
|
||||
|
|
@ -311,7 +309,7 @@ public partial class BillingCommandPageViewModel : ViewModelBase
|
|||
PerformerId = Performer.PerformerId,
|
||||
Consent,
|
||||
EventDate = eventDate,
|
||||
Location = location,
|
||||
Location = locationPayload,
|
||||
Prestations = selectedPrestations.Select(x => new { PrestationId = x.Id }).ToList(),
|
||||
Status = payload.Status,
|
||||
}).ConfigureAwait(true);
|
||||
|
|
@ -336,6 +334,44 @@ public partial class BillingCommandPageViewModel : ViewModelBase
|
|||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanUseCurrentLocation))]
|
||||
private async Task UseCurrentLocationAsync()
|
||||
{
|
||||
if (!CanUseCurrentLocation)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
var result = await Platform.TryGetCurrentLocationAsync(default).ConfigureAwait(true);
|
||||
if (!result.IsSuccess || !result.Latitude.HasValue || !result.Longitude.HasValue)
|
||||
{
|
||||
StatusMessage = result.Message;
|
||||
return;
|
||||
}
|
||||
|
||||
LatitudeText = result.Latitude.Value.ToString(CultureInfo.InvariantCulture);
|
||||
LongitudeText = result.Longitude.Value.ToString(CultureInfo.InvariantCulture);
|
||||
StatusMessage = string.IsNullOrWhiteSpace(Address)
|
||||
? "Position récupérée. Complétez l'adresse puis envoyez la commande."
|
||||
: result.Message;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
StatusMessage = "La récupération de la position a été annulée.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = $"Impossible de récupérer la position: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyExistingQuery(BillingQueryDetailsDto existingQuery)
|
||||
{
|
||||
ExistingQueryId = existingQuery.Id;
|
||||
|
|
@ -354,8 +390,8 @@ public partial class BillingCommandPageViewModel : ViewModelBase
|
|||
if (existingQuery.Location is not null)
|
||||
{
|
||||
Address = existingQuery.Location.Address ?? string.Empty;
|
||||
LatitudeText = existingQuery.Location.Latitude.ToString(CultureInfo.InvariantCulture);
|
||||
LongitudeText = existingQuery.Location.Longitude.ToString(CultureInfo.InvariantCulture);
|
||||
LatitudeText = existingQuery.Location.Latitude?.ToString(CultureInfo.InvariantCulture) ?? string.Empty;
|
||||
LongitudeText = existingQuery.Location.Longitude?.ToString(CultureInfo.InvariantCulture) ?? string.Empty;
|
||||
}
|
||||
|
||||
if (IsBrush && existingQuery.PrestationId is not null)
|
||||
|
|
@ -396,4 +432,59 @@ public partial class BillingCommandPageViewModel : ViewModelBase
|
|||
return double.TryParse(text, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.CurrentCulture, out value)
|
||||
|| double.TryParse(text, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out value);
|
||||
}
|
||||
|
||||
private static object BuildLocationPayload(string address, double? latitude, double? longitude)
|
||||
{
|
||||
if (latitude.HasValue && longitude.HasValue)
|
||||
{
|
||||
return new
|
||||
{
|
||||
Address = address,
|
||||
Latitude = latitude.Value,
|
||||
Longitude = longitude.Value,
|
||||
};
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
Address = address,
|
||||
};
|
||||
}
|
||||
|
||||
private bool TryParseCoordinates(out double? latitude, out double? longitude, out string error)
|
||||
{
|
||||
latitude = null;
|
||||
longitude = null;
|
||||
error = string.Empty;
|
||||
|
||||
var latitudeMissing = string.IsNullOrWhiteSpace(LatitudeText);
|
||||
var longitudeMissing = string.IsNullOrWhiteSpace(LongitudeText);
|
||||
|
||||
if (latitudeMissing && longitudeMissing)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (latitudeMissing != longitudeMissing)
|
||||
{
|
||||
error = "Latitude et longitude doivent être renseignées ensemble, ou laissées vides toutes les deux.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryParseCoordinate(LatitudeText, out var parsedLatitude))
|
||||
{
|
||||
error = "Latitude invalide.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryParseCoordinate(LongitudeText, out var parsedLongitude))
|
||||
{
|
||||
error = "Longitude invalide.";
|
||||
return false;
|
||||
}
|
||||
|
||||
latitude = parsedLatitude;
|
||||
longitude = parsedLongitude;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
x:DataType="vm:BillingCommandPageViewModel"
|
||||
Header="Commande billing">
|
||||
<ScrollViewer>
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="Auto,*" Margin="12">
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="Auto,*" Margin="12">
|
||||
<TextBlock Grid.Row="0" Grid.ColumnSpan="2"
|
||||
Text="{Binding Title}"
|
||||
FontSize="18"
|
||||
|
|
@ -40,18 +40,25 @@
|
|||
<TextBlock Grid.Row="6" Text="Adresse" VerticalAlignment="Center" Margin="0,0,12,8" />
|
||||
<TextBox Grid.Row="6" Grid.Column="1" Text="{Binding Address, Mode=TwoWay}" Margin="0,0,0,8" />
|
||||
|
||||
<TextBlock Grid.Row="7" Text="Latitude" VerticalAlignment="Center" Margin="0,0,12,8" />
|
||||
<TextBox Grid.Row="7" Grid.Column="1" Text="{Binding LatitudeText, Mode=TwoWay}" Margin="0,0,0,8" />
|
||||
<Button Grid.Row="7"
|
||||
Grid.ColumnSpan="2"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="0,0,0,8"
|
||||
Content="Utiliser ma position"
|
||||
Command="{Binding UseCurrentLocationCommand}" />
|
||||
|
||||
<TextBlock Grid.Row="8" Text="Longitude" VerticalAlignment="Center" Margin="0,0,12,8" />
|
||||
<TextBox Grid.Row="8" Grid.Column="1" Text="{Binding LongitudeText, Mode=TwoWay}" Margin="0,0,0,8" />
|
||||
<TextBlock Grid.Row="8" Text="Latitude" VerticalAlignment="Center" Margin="0,0,12,8" />
|
||||
<TextBox Grid.Row="8" Grid.Column="1" Text="{Binding LatitudeText, Mode=TwoWay}" PlaceholderText="Optionnel" Margin="0,0,0,8" />
|
||||
|
||||
<TextBlock Grid.Row="9"
|
||||
<TextBlock Grid.Row="9" Text="Longitude" VerticalAlignment="Center" Margin="0,0,12,8" />
|
||||
<TextBox Grid.Row="9" Grid.Column="1" Text="{Binding LongitudeText, Mode=TwoWay}" PlaceholderText="Optionnel" Margin="0,0,0,8" />
|
||||
|
||||
<TextBlock Grid.Row="10"
|
||||
Text="Prestation"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,12,8"
|
||||
IsVisible="{Binding ShowsSinglePrestation}" />
|
||||
<ComboBox Grid.Row="9"
|
||||
<ComboBox Grid.Row="10"
|
||||
Grid.Column="1"
|
||||
ItemsSource="{Binding AvailablePrestations}"
|
||||
SelectedItem="{Binding SelectedPrestation, Mode=TwoWay}"
|
||||
|
|
@ -67,12 +74,12 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="10"
|
||||
<TextBlock Grid.Row="11"
|
||||
Text="Prestations"
|
||||
VerticalAlignment="Top"
|
||||
Margin="0,0,12,8"
|
||||
IsVisible="{Binding ShowsMultiplePrestations}" />
|
||||
<ListBox Grid.Row="10"
|
||||
<ListBox Grid.Row="11"
|
||||
Grid.Column="1"
|
||||
ItemsSource="{Binding MultiPrestations}"
|
||||
IsVisible="{Binding ShowsMultiplePrestations}"
|
||||
|
|
@ -90,23 +97,23 @@
|
|||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<TextBlock Grid.Row="11"
|
||||
<TextBlock Grid.Row="12"
|
||||
Text="Informations"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,12,8"
|
||||
IsVisible="{Binding ShowsAdditionalInfo}" />
|
||||
<TextBox Grid.Row="11"
|
||||
<TextBox Grid.Row="12"
|
||||
Grid.Column="1"
|
||||
Text="{Binding AdditionalInfo, Mode=TwoWay}"
|
||||
Margin="0,0,0,8"
|
||||
IsVisible="{Binding ShowsAdditionalInfo}" />
|
||||
|
||||
<CheckBox Grid.Row="12" Grid.ColumnSpan="2"
|
||||
<CheckBox Grid.Row="13" Grid.ColumnSpan="2"
|
||||
Content="Je consens à la création de cette commande"
|
||||
IsChecked="{Binding Consent, Mode=TwoWay}"
|
||||
Margin="0,4,0,12" />
|
||||
|
||||
<Grid Grid.Row="13" Grid.ColumnSpan="2" ColumnDefinitions="Auto,12,*,Auto">
|
||||
<Grid Grid.Row="14" Grid.ColumnSpan="2" ColumnDefinitions="Auto,12,*,Auto">
|
||||
<Button Grid.Column="0"
|
||||
Content="{Binding SubmitLabel}"
|
||||
Command="{Binding SubmitCommand}"
|
||||
|
|
|
|||
|
|
@ -286,12 +286,22 @@ public sealed class BillingApiClient
|
|||
return null;
|
||||
}
|
||||
|
||||
return new
|
||||
var payload = new Dictionary<string, object?>
|
||||
{
|
||||
Address = location.Address,
|
||||
Latitude = location.Latitude,
|
||||
Longitude = location.Longitude,
|
||||
["Address"] = location.Address,
|
||||
};
|
||||
|
||||
if (location.Latitude.HasValue)
|
||||
{
|
||||
payload["Latitude"] = location.Latitude.Value;
|
||||
}
|
||||
|
||||
if (location.Longitude.HasValue)
|
||||
{
|
||||
payload["Longitude"] = location.Longitude.Value;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
private sealed class BillingLocationResponse
|
||||
|
|
@ -351,4 +361,4 @@ public sealed class BillingApiClient
|
|||
{
|
||||
public long PrestationId { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,6 @@ public sealed class BillingQueryDetailsDto
|
|||
public sealed class BillingLocationDto
|
||||
{
|
||||
public string Address { get; set; } = string.Empty;
|
||||
public double Latitude { get; set; }
|
||||
public double Longitude { get; set; }
|
||||
}
|
||||
public double? Latitude { get; set; }
|
||||
public double? Longitude { get; set; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue