Platform.TryGetCurrentLocationAsync
All checks were successful
Dotnet build and test / build (pull_request) Successful in 8m30s

This commit is contained in:
Paul Schneider 2026-09-02 14:41:05 +01:00
commit 914e9486b5
Signed by: notazof
GPG key ID: 1DD5D838E5343B06
11 changed files with 395 additions and 46 deletions

View file

@ -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]

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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);
}
}
}