From 41398ab03ab2b3202415e62e49ecde9a1c1c9d99 Mon Sep 17 00:00:00 2001 From: David Britch Date: Tue, 20 Feb 2018 12:22:58 +0000 Subject: [PATCH 01/13] Work in progress. --- .../eShopOnContainers.Core/App.xaml.cs | 9 +- .../Models/Location/ActivityType.cs | 10 + .../Models/Location/Address.cs | 38 +++ .../Models/Location/GeolocationError.cs | 8 + .../Models/Location/GeolocationException.cs | 27 ++ .../Models/Location/ListenerSettings.cs | 15 ++ .../Models/Location/Position.cs | 43 ++++ .../Models/Location/PositionErrorEventArgs.cs | 14 + .../Models/Location/PositionEventArgs.cs | 17 ++ .../Models/Permissions/Permission.cs | 10 + .../Models/Permissions/PermissionStatus.cs | 11 + .../ILocationServiceImplementation.cs | 19 ++ .../Services/Location/LocationService.cs | 3 - .../Permissions/IPermissionsService.cs | 12 + .../ViewModels/SettingsViewModel.cs | 8 +- .../eShopOnContainers.Core.csproj | 5 +- .../Activities/MainActivity.cs | 1 + .../MainApplication.cs | 10 +- .../Services/PermissionService.cs | 241 ++++++++++++++++++ .../eShopOnContainers.Droid.csproj | 16 +- .../eShopOnContainers.Droid/packages.config | 3 - .../GeolocationSingleUpdateDelegate.cs | 139 ++++++++++ .../Services/LocationServiceImplementation.cs | 150 +++++++++++ .../Services/PermissionsService.cs | 143 +++++++++++ .../eShopOnContainers.iOS.csproj | 9 +- .../eShopOnContainers.iOS/packages.config | 1 - 26 files changed, 923 insertions(+), 39 deletions(-) create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/ActivityType.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/Address.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/GeolocationError.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/GeolocationException.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/ListenerSettings.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/Position.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/PositionErrorEventArgs.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/PositionEventArgs.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Permissions/Permission.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Permissions/PermissionStatus.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Permissions/IPermissionsService.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionService.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/GeolocationSingleUpdateDelegate.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs index 0270bb961..7207e2204 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs @@ -3,11 +3,11 @@ using eShopOnContainers.Core.Services.Location; using eShopOnContainers.Core.Services.Settings; using eShopOnContainers.Core.ViewModels.Base; using eShopOnContainers.Services; -using Plugin.Geolocator; using System.Globalization; using System.Threading.Tasks; using Xamarin.Forms; using Xamarin.Forms.Xaml; +using eShopOnContainers.Core.Services.Dependency; [assembly: XamlCompilation(XamlCompilationOptions.Compile)] namespace eShopOnContainers @@ -69,13 +69,16 @@ namespace eShopOnContainers private async Task GetGpsLocation() { - var locator = CrossGeolocator.Current; + var dependencyService = ViewModelLocator.Resolve(); + var locator = dependencyService.Get(); if (locator.IsGeolocationEnabled && locator.IsGeolocationAvailable) { - locator.AllowsBackgroundUpdates = true; + //locationService.AllowsBackgroundUpdates = true; locator.DesiredAccuracy = 50; + await Task.Delay(5000); + var position = await locator.GetPositionAsync(); _settingsService.Latitude = position.Latitude.ToString(); diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/ActivityType.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/ActivityType.cs new file mode 100644 index 000000000..3240b1bad --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/ActivityType.cs @@ -0,0 +1,10 @@ +namespace eShopOnContainers.Core.Models.Location +{ + public enum ActivityType + { + Other, + AutomotiveNavigation, + Fitness, + OtherNavigation + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/Address.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/Address.cs new file mode 100644 index 000000000..a4be9e1c7 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/Address.cs @@ -0,0 +1,38 @@ +using System; + +namespace eShopOnContainers.Core.Models.Location +{ + public class Address + { + public double Latitude { get; set; } + public double Longitude { get; set; } + public string CountryCode { get; set; } + public string CountryName { get; set; } + public string FeatureName { get; set; } + public string PostalCode { get; set; } + public string SubLocality { get; set; } + public string Thoroughfare { get; set; } + public string SubThoroughfare { get; set; } + public string Locality { get; set; } + public string AdminArea { get; set; } + public string SubAdminArea { get; set; } + + public Address(Address address) + { + if (address == null) + throw new ArgumentNullException(nameof(address)); + + CountryCode = address.CountryCode; + CountryName = address.CountryName; + Latitude = address.Latitude; + Longitude = address.Longitude; + FeatureName = address.FeatureName; + PostalCode = address.PostalCode; + SubLocality = address.SubLocality; + Thoroughfare = address.Thoroughfare; + SubThoroughfare = address.SubThoroughfare; + SubAdminArea = address.SubAdminArea; + AdminArea = address.AdminArea; + } + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/GeolocationError.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/GeolocationError.cs new file mode 100644 index 000000000..d8ac11184 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/GeolocationError.cs @@ -0,0 +1,8 @@ +namespace eShopOnContainers.Core.Models.Location +{ + public enum GeolocationError + { + PositionUnavailable, + Unauthorized + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/GeolocationException.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/GeolocationException.cs new file mode 100644 index 000000000..39b89483a --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/GeolocationException.cs @@ -0,0 +1,27 @@ +using System; + +namespace eShopOnContainers.Core.Models.Location +{ + public class GeolocationException : Exception + { + public GeolocationError Error { get; private set; } + + public GeolocationException(GeolocationError error) + : base("A geolocation error occured: " + error) + { + if (!Enum.IsDefined(typeof(GeolocationError), error)) + throw new ArgumentException("error is not a valid GelocationError member", "error"); + + Error = error; + } + + public GeolocationException(GeolocationError error, Exception innerException) + : base("A geolocation error occured: " + error, innerException) + { + if (!Enum.IsDefined(typeof(GeolocationError), error)) + throw new ArgumentException("error is not a valid GelocationError member", "error"); + + Error = error; + } + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/ListenerSettings.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/ListenerSettings.cs new file mode 100644 index 000000000..40849b86f --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/ListenerSettings.cs @@ -0,0 +1,15 @@ +using System; + +namespace eShopOnContainers.Core.Models.Location +{ + public class ListenerSettings + { + public bool AllowBackgroundUpdates { get; set; } = false; + public bool PauseLocationUpdatesAutomatically { get; set; } = true; + public ActivityType ActivityType { get; set; } = ActivityType.Other; + public bool ListenForSignificantChanges { get; set; } = false; + public bool DeferLocationUpdates { get; set; } = false; + public double? DeferralDistanceMeters { get; set; } = 500; + public TimeSpan? DeferralTime { get; set; } = TimeSpan.FromMinutes(5); + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/Position.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/Position.cs new file mode 100644 index 000000000..fab7ed732 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/Position.cs @@ -0,0 +1,43 @@ +using System; + +namespace eShopOnContainers.Core.Models.Location +{ + public class Position + { + public DateTimeOffset Timestamp { get; set; } + public double Latitude { get; set; } + public double Longitude { get; set; } + public double Altitude { get; set; } + public double Accuracy { get; set; } + public double AltitudeAccuracy { get; set; } + public double Heading { get; set; } + public double Speed { get; set; } + + public Position() + { + } + + public Position(double latitude, double longitude) + { + + Timestamp = DateTimeOffset.UtcNow; + Latitude = latitude; + Longitude = longitude; + } + + public Position(Position position) + { + if (position == null) + throw new ArgumentNullException("position"); + + Timestamp = position.Timestamp; + Latitude = position.Latitude; + Longitude = position.Longitude; + Altitude = position.Altitude; + AltitudeAccuracy = position.AltitudeAccuracy; + Accuracy = position.Accuracy; + Heading = position.Heading; + Speed = position.Speed; + } + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/PositionErrorEventArgs.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/PositionErrorEventArgs.cs new file mode 100644 index 000000000..6c5e3cd24 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/PositionErrorEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace eShopOnContainers.Core.Models.Location +{ + public class PositionErrorEventArgs : EventArgs + { + public GeolocationError Error { get; private set; } + + public PositionErrorEventArgs(GeolocationError error) + { + Error = error; + } + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/PositionEventArgs.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/PositionEventArgs.cs new file mode 100644 index 000000000..2107b05e5 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/PositionEventArgs.cs @@ -0,0 +1,17 @@ +using System; + +namespace eShopOnContainers.Core.Models.Location +{ + public class PositionEventArgs : EventArgs + { + public Position Position { get; private set; } + + public PositionEventArgs(Position position) + { + if (position == null) + throw new ArgumentNullException("position"); + + Position = position; + } + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Permissions/Permission.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Permissions/Permission.cs new file mode 100644 index 000000000..bee5de424 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Permissions/Permission.cs @@ -0,0 +1,10 @@ +namespace eShopOnContainers.Core.Models.Permissions +{ + public enum Permission + { + Unknown, + Location, + LocationAlways, + LocationWhenInUse + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Permissions/PermissionStatus.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Permissions/PermissionStatus.cs new file mode 100644 index 000000000..2a8f789f6 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Permissions/PermissionStatus.cs @@ -0,0 +1,11 @@ +namespace eShopOnContainers.Core.Models.Permissions +{ + public enum PermissionStatus + { + Denied, + Disabled, + Granted, + Restricted, + Unknown + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs new file mode 100644 index 000000000..c83a710ea --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using eShopOnContainers.Core.Models.Location; + +namespace eShopOnContainers.Core.Services.Location +{ + public interface ILocationServiceImplementation + { + event EventHandler PositionError; + event EventHandler PositionChanged; + + double DesiredAccuracy { get; set; } + bool IsGeolocationAvailable { get; } + bool IsGeolocationEnabled { get; } + + Task GetPositionAsync(TimeSpan? timeout = null, CancellationToken? token = null, bool includeHeading = false); + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/LocationService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/LocationService.cs index deaa60936..f20ae6339 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/LocationService.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/LocationService.cs @@ -16,11 +16,8 @@ namespace eShopOnContainers.Core.Services.Location public async Task UpdateUserLocation(eShopOnContainers.Core.Models.Location.Location newLocReq, string token) { UriBuilder builder = new UriBuilder(GlobalSetting.Instance.LocationEndpoint); - builder.Path = "api/v1/locations"; - string uri = builder.ToString(); - await _requestProvider.PostAsync(uri, newLocReq, token); } } diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Permissions/IPermissionsService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Permissions/IPermissionsService.cs new file mode 100644 index 000000000..b72061cc1 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Permissions/IPermissionsService.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using eShopOnContainers.Core.Models.Permissions; + +namespace eShopOnContainers.Core.Services.Permissions +{ + public interface IPermissionsService + { + Task CheckPermissionStatusAsync(Permission permission); + Task> RequestPermissionsAsync(params Permission[] permissions); + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/SettingsViewModel.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/SettingsViewModel.cs index 1e478b85c..8e86847b7 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/SettingsViewModel.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/SettingsViewModel.cs @@ -3,11 +3,11 @@ using eShopOnContainers.Core.Models.User; using eShopOnContainers.Core.Services.Location; using eShopOnContainers.Core.Services.Settings; using eShopOnContainers.Core.ViewModels.Base; -using Plugin.Geolocator; using System.Globalization; using System.Threading.Tasks; using System.Windows.Input; using Xamarin.Forms; +using eShopOnContainers.Core.Services.Dependency; namespace eShopOnContainers.Core.ViewModels { @@ -29,11 +29,13 @@ namespace eShopOnContainers.Core.ViewModels private readonly ISettingsService _settingsService; private readonly ILocationService _locationService; + private readonly IDependencyService _dependencyService; - public SettingsViewModel(ISettingsService settingsService, ILocationService locationService) + public SettingsViewModel(ISettingsService settingsService, ILocationService locationService, IDependencyService dependencyService) { _settingsService = settingsService; _locationService = locationService; + _dependencyService = dependencyService; _useAzureServices = !_settingsService.UseMocks; _endpoint = _settingsService.UrlBase; @@ -342,7 +344,7 @@ namespace eShopOnContainers.Core.ViewModels { if (_allowGpsLocation) { - var locator = CrossGeolocator.Current; + var locator = _dependencyService.Get(); if (!locator.IsGeolocationEnabled) { _allowGpsLocation = false; diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj index 06de19f11..26fb0d89a 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj @@ -10,9 +10,12 @@ - + + + + diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs index d72898f40..12ea046ac 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs @@ -57,6 +57,7 @@ namespace eShopOnContainers.Droid.Activities public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) { base.OnRequestPermissionsResult(requestCode, permissions, grantResults); + PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults); } } diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/MainApplication.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/MainApplication.cs index 74e5a3883..8096eb268 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/MainApplication.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/MainApplication.cs @@ -1,7 +1,7 @@ using Android.App; +using Android.Content; using Android.OS; using Android.Runtime; -using Plugin.CurrentActivity; using System; namespace eShopOnContainers.Droid @@ -9,6 +9,8 @@ namespace eShopOnContainers.Droid [Application] public class MainApplication : Application, Application.IActivityLifecycleCallbacks { + internal static Context CurrentContext { get; private set; } + public MainApplication(IntPtr handle, JniHandleOwnership transer) : base(handle, transer) { @@ -28,7 +30,7 @@ namespace eShopOnContainers.Droid public void OnActivityCreated(Activity activity, Bundle savedInstanceState) { - CrossCurrentActivity.Current.Activity = activity; + CurrentContext = activity; } public void OnActivityDestroyed(Activity activity) @@ -41,7 +43,7 @@ namespace eShopOnContainers.Droid public void OnActivityResumed(Activity activity) { - CrossCurrentActivity.Current.Activity = activity; + CurrentContext = activity; } public void OnActivitySaveInstanceState(Activity activity, Bundle outState) @@ -50,7 +52,7 @@ namespace eShopOnContainers.Droid public void OnActivityStarted(Activity activity) { - CrossCurrentActivity.Current.Activity = activity; + CurrentContext = activity; } public void OnActivityStopped(Activity activity) diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionService.cs new file mode 100644 index 000000000..268d71771 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionService.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using eShopOnContainers.Core.Models.Permissions; +using eShopOnContainers.Core.Services.Permissions; +using System.Linq; +using Android; +using Android.App; +using Android.Support.V4.App; +using Android.Support.V4.Content; +using System.Diagnostics; +using Android.Icu.Text; + +namespace eShopOnContainers.Droid.Services +{ + public class PermissionService : IPermissionsService + { + const int _permissionCode = 25; + object _locker = new object(); + TaskCompletionSource> _tcs; + Dictionary _results; + IList _requestedPermissions; + + #region Internal Implementation + + List GetManifestNames(Permission permission) + { + var permissionNames = new List(); + switch (permission) + { + case Permission.LocationAlways: + case Permission.LocationWhenInUse: + case Permission.Location: + { + if (HasPermissionInManifest(Manifest.Permission.AccessCoarseLocation)) + permissionNames.Add(Manifest.Permission.AccessCoarseLocation); + + if (HasPermissionInManifest(Manifest.Permission.AccessFineLocation)) + permissionNames.Add(Manifest.Permission.AccessFineLocation); + } + break; + } + return permissionNames; + } + + bool HasPermissionInManifest(string permission) + { + try + { + if (_requestedPermissions != null) + return _requestedPermissions.Any(r => r.Equals(permission, StringComparison.InvariantCultureIgnoreCase)); + + // Try to use current activity else application context + var context = MainApplication.CurrentContext ?? Application.Context; + + if (context == null) + { + Debug.WriteLine("Unable to detect current Activity or Application Context."); + return false; + } + + var info = context.PackageManager.GetPackageInfo(context.PackageName, Android.Content.PM.PackageInfoFlags.Permissions); + if (info == null) + { + Debug.WriteLine("Unable to get package info, will not be able to determine permissions to request."); + return false; + } + + _requestedPermissions = info.RequestedPermissions; + if (_requestedPermissions == null) + { + Debug.WriteLine("There are no requested permissions, please check to ensure you have marked the permissions that you want to request."); + return false; + } + + return _requestedPermissions.Any(r => r.Equals(permission, StringComparison.InvariantCultureIgnoreCase)); + } + catch (Exception ex) + { + Console.Write("Unable to check manifest for permission: " + ex); + } + return false; + } + + static Permission GetPermissionForManifestName(string permission) + { + switch (permission) + { + case Manifest.Permission.AccessCoarseLocation: + case Manifest.Permission.AccessFineLocation: + return Permission.Location; + } + + return Permission.Unknown; + } + + + public void OnRequestPermissionResult(int requestCode, string[] permissions, Android.Content.PM.Permission[] grantResults) + { + if (requestCode != _permissionCode) + return; + + if (_tcs == null) + return; + + for (var i = 0; i < permissions.Length; i++) + { + if (_tcs.Task.Status == TaskStatus.Canceled) + return; + + var permission = GetPermissionForManifestName(permissions[i]); + if (permission == Permission.Unknown) + continue; + + lock (_locker) + { + if (permission == Permission.Location) + { + if (!_results.ContainsKey(Permission.LocationWhenInUse)) + _results.Add(Permission.LocationWhenInUse, grantResults[i] == Android.Content.PM.Permission.Granted ? PermissionStatus.Granted : PermissionStatus.Denied); + } + + if (!_results.ContainsKey(permission)) + _results.Add(permission, grantResults[i] == Android.Content.PM.Permission.Granted ? PermissionStatus.Granted : PermissionStatus.Denied); + } + } + + _tcs.TrySetResult(_results); + } + + #endregion + + #region IPermissionsService Implementation + + public Task CheckPermissionStatusAsync(Permission permission) + { + var names = GetManifestNames(permission); + if (names == null) + { + Debug.WriteLine("No Android specific permissions needed for: " + permission); + return Task.FromResult(PermissionStatus.Granted); + } + + if (names.Count == 0) + { + Debug.WriteLine("No permissions found in manifest for: " + permission); + return Task.FromResult(PermissionStatus.Unknown); + } + + var context = MainApplication.CurrentContext ?? Application.Context; + if (context == null) + { + Debug.WriteLine("Unable to detect current Activity or Application Context."); + return Task.FromResult(PermissionStatus.Unknown); + } + + bool targetsMOrHigher = context.ApplicationInfo.TargetSdkVersion >= Android.OS.BuildVersionCodes.M; + foreach (var name in names) + { + if (targetsMOrHigher) + { + if (ContextCompat.CheckSelfPermission(context, name) != Android.Content.PM.Permission.Granted) + return Task.FromResult(PermissionStatus.Denied); + } + else + { + if (PermissionChecker.CheckSelfPermission(context, name) != PermissionChecker.PermissionGranted) + return Task.FromResult(PermissionStatus.Denied); + } + } + return Task.FromResult(PermissionStatus.Granted); + } + + public async Task> RequestPermissionsAsync(params Permission[] permissions) + { + if (_tcs != null && !_tcs.Task.IsCompleted) + { + _tcs.SetCanceled(); + _tcs = null; + } + lock (_locker) + { + _results = new Dictionary(); + } + + var context = MainApplication.CurrentContext; + if (context == null) + { + Debug.WriteLine("Unable to detect current Activity."); + foreach (var permission in permissions) + { + lock (_locker) + { + if (!_results.ContainsKey(permission)) + _results.Add(permission, PermissionStatus.Unknown); + } + } + + return _results; + } + + var permissionsToRequest = new List(); + foreach (var permission in permissions) + { + var result = await CheckPermissionStatusAsync(permission).ConfigureAwait(false); + if (result != PermissionStatus.Granted) + { + var names = GetManifestNames(permission); + if ((names?.Count ?? 0) == 0) + { + lock (_locker) + { + if (!_results.ContainsKey(permission)) + _results.Add(permission, PermissionStatus.Unknown); + } + continue; + } + + permissionsToRequest.AddRange(names); + } + else + { + lock (_locker) + { + if (!_results.ContainsKey(permission)) + _results.Add(permission, PermissionStatus.Granted); + } + } + } + + if (permissionsToRequest.Count == 0) + return _results; + + _tcs = new TaskCompletionSource>(); + ActivityCompat.RequestPermissions((Activity)context, permissionsToRequest.ToArray(), _permissionCode); + return await _tcs.Task.ConfigureAwait(false); + } + + #endregion + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj index 18c325634..83189bc72 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj @@ -145,27 +145,12 @@ ..\..\..\..\packages\Acr.UserDialogs.6.5.1\lib\MonoAndroid10\Acr.UserDialogs.Interface.dll - - ..\..\..\..\packages\Plugin.CurrentActivity.1.0.1\lib\MonoAndroid10\Plugin.CurrentActivity.dll - - - ..\..\..\..\packages\Plugin.Permissions.2.2.1\lib\MonoAndroid10\Plugin.Permissions.Abstractions.dll - - - ..\..\..\..\packages\Plugin.Permissions.2.2.1\lib\MonoAndroid10\Plugin.Permissions.dll - ..\..\..\..\packages\SlideOverKit.2.1.5\lib\MonoAndroid10\SlideOverKit.dll ..\..\..\..\packages\SlideOverKit.2.1.5\lib\MonoAndroid10\SlideOverKit.Droid.dll - - ..\..\..\..\packages\Xam.Plugin.Geolocator.3.0.4\lib\MonoAndroid10\Plugin.Geolocator.Abstractions.dll - - - ..\..\..\..\packages\Xam.Plugin.Geolocator.3.0.4\lib\MonoAndroid10\Plugin.Geolocator.dll - ..\..\..\..\packages\Microsoft.Net.Http.2.2.28\lib\monoandroid\System.Net.Http.Extensions.dll @@ -227,6 +212,7 @@ + diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/packages.config b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/packages.config index 274cc15e9..2efab0dea 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/packages.config +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/packages.config @@ -17,8 +17,6 @@ - - @@ -69,7 +67,6 @@ - diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/GeolocationSingleUpdateDelegate.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/GeolocationSingleUpdateDelegate.cs new file mode 100644 index 000000000..110ac1eff --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/GeolocationSingleUpdateDelegate.cs @@ -0,0 +1,139 @@ +using System; +using CoreLocation; +using Foundation; +using System.Threading.Tasks; +using System.Threading; +using System.Linq; +using eShopOnContainers.Core.Models.Location; + +namespace eShopOnContainers.iOS.Services +{ + internal class GeolocationSingleUpdateDelegate : CLLocationManagerDelegate + { + bool _haveHeading; + bool _haveLocation; + readonly Position _position = new Position(); + CLHeading _bestHeading; + + readonly double _desiredAccuracy; + readonly bool _includeHeading; + readonly TaskCompletionSource _tcs; + readonly CLLocationManager _manager; + + public Task Task => _tcs?.Task; + + public GeolocationSingleUpdateDelegate(CLLocationManager manager, double desiredAccuracy, bool includeHeading, int timeout, CancellationToken cancelToken) + { + _manager = manager; + _tcs = new TaskCompletionSource(manager); + _desiredAccuracy = desiredAccuracy; + _includeHeading = includeHeading; + + if (timeout != Timeout.Infinite) + { + Timer t = null; + t = new Timer(s => + { + if (_haveLocation) + _tcs.TrySetResult(new Position(_position)); + else + _tcs.TrySetCanceled(); + + StopListening(); + t.Dispose(); + }, null, timeout, 0); + } + + cancelToken.Register(() => + { + StopListening(); + _tcs.TrySetCanceled(); + }); + } + + public override void AuthorizationChanged(CLLocationManager manager, CLAuthorizationStatus status) + { + // If user has services disabled, throw an exception for consistency. + if (status == CLAuthorizationStatus.Denied || status == CLAuthorizationStatus.Restricted) + { + StopListening(); + _tcs.TrySetException(new GeolocationException(GeolocationError.Unauthorized)); + } + } + + public override void Failed(CLLocationManager manager, NSError error) + { + switch ((CLError)(int)error.Code) + { + case CLError.Network: + StopListening(); + _tcs.SetException(new GeolocationException(GeolocationError.PositionUnavailable)); + break; + case CLError.LocationUnknown: + StopListening(); + _tcs.TrySetException(new GeolocationException(GeolocationError.PositionUnavailable)); + break; + } + } + + public override bool ShouldDisplayHeadingCalibration(CLLocationManager manager) => true; + + public override void UpdatedLocation(CLLocationManager manager, CLLocation newLocation, CLLocation oldLocation) + { + if (newLocation.HorizontalAccuracy < 0) + return; + + if (_haveLocation && newLocation.HorizontalAccuracy > _position.Accuracy) + return; + + _position.Accuracy = newLocation.HorizontalAccuracy; + _position.Altitude = newLocation.Altitude; + _position.AltitudeAccuracy = newLocation.VerticalAccuracy; + _position.Latitude = newLocation.Coordinate.Latitude; + _position.Longitude = newLocation.Coordinate.Longitude; + _position.Speed = newLocation.Speed; + + try + { + _position.Timestamp = new DateTimeOffset((DateTime)newLocation.Timestamp); + } + catch (Exception ex) + { + _position.Timestamp = DateTimeOffset.UtcNow; + } + _haveLocation = true; + + if ((!_includeHeading || _haveHeading) && _position.Accuracy <= _desiredAccuracy) + { + _tcs.TrySetResult(new Position(_position)); + StopListening(); + } + } + + public override void UpdatedHeading(CLLocationManager manager, CLHeading newHeading) + { + if (newHeading.HeadingAccuracy < 0) + return; + if (_bestHeading != null && newHeading.HeadingAccuracy >= _bestHeading.HeadingAccuracy) + return; + + _bestHeading = newHeading; + _position.Heading = newHeading.TrueHeading; + _haveHeading = true; + + if (_haveLocation && _position.Accuracy <= _desiredAccuracy) + { + _tcs.TrySetResult(new Position(_position)); + StopListening(); + } + } + + private void StopListening() + { + if (CLLocationManager.HeadingAvailable) + _manager.StopUpdatingHeading(); + + _manager.StopUpdatingLocation(); + } + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs new file mode 100644 index 000000000..12bef0775 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs @@ -0,0 +1,150 @@ +using eShopOnContainers.iOS.Services; +using eShopOnContainers.Core.Services.Location; +using CoreLocation; +using eShopOnContainers.Core.Models.Location; +using Foundation; +using System; +using System.Threading.Tasks; +using System.Threading; +using UIKit; +using eShopOnContainers.Core.Models.Permissions; +using eShopOnContainers.Core.Services.Permissions; + +[assembly: Xamarin.Forms.Dependency(typeof(LocationServiceImplementation))] +namespace eShopOnContainers.iOS.Services +{ + public class LocationServiceImplementation : ILocationServiceImplementation + { + bool _deferringUpdates; + readonly CLLocationManager _manager; + Position _lastPosition; + + public event EventHandler PositionError; + public event EventHandler PositionChanged; + public double DesiredAccuracy { get; set; } + public bool IsGeolocationAvailable => true; + public bool IsGeolocationEnabled + { + get + { + var status = CLLocationManager.Status; + return CLLocationManager.LocationServicesEnabled; + } + } + + public bool SupportsHeading => CLLocationManager.HeadingAvailable; + + public LocationServiceImplementation() + { + DesiredAccuracy = 100; + //_manager = GetManager(); + //_manager.AuthorizationChanged += OnAuthorizationChanged; + //_manager.Failed += OnFailed; + //_manager.UpdatedLocation += OnUpdatedLocation; + //_manager.UpdatedHeading += OnUpdatedHeading; + //_manager.DeferredUpdatesFinished += OnDeferredUpdatesFinished; + } + + void OnDeferredUpdatesFinished(object sender, NSErrorEventArgs e) => _deferringUpdates = false; + + #region Internal Implementation + + async Task CheckPermissions(Permission permission) + { + IPermissionsService permissionsService = new PermissionsService(); + var status = await permissionsService.CheckPermissionStatusAsync(permission); + if (status != PermissionStatus.Granted) + { + Console.WriteLine("Currently do not have Location permissions, requesting permissions"); + + var request = await permissionsService.RequestPermissionsAsync(permission); + if (request[permission] != PermissionStatus.Granted) + { + Console.WriteLine("Location permission denied, can not get positions async."); + return false; + } + } + return true; + } + + CLLocationManager GetManager() + { + CLLocationManager manager = null; + new NSObject().InvokeOnMainThread(() => manager = new CLLocationManager()); + return manager; + } + + #endregion + + #region ILocationServiceImplementation + + public async Task GetPositionAsync(TimeSpan? timeout, CancellationToken? cancelToken = null, bool includeHeading = false) + { + var permission = Permission.LocationWhenInUse; + var hasPermission = await CheckPermissions(permission); + if (!hasPermission) + throw new GeolocationException(GeolocationError.Unauthorized); + + var timeoutMilliseconds = timeout.HasValue ? (int)timeout.Value.TotalMilliseconds : Timeout.Infinite; + if (timeoutMilliseconds <= 0 && timeoutMilliseconds != Timeout.Infinite) + throw new ArgumentOutOfRangeException(nameof(timeout), "Timeout must be positive or Timeout.Infinite"); + if (!cancelToken.HasValue) + cancelToken = CancellationToken.None; + + TaskCompletionSource tcs; + + var manager = GetManager(); + manager.DesiredAccuracy = DesiredAccuracy; + + // Permit background updates if background location mode is enabled + if (UIDevice.CurrentDevice.CheckSystemVersion(9, 0)) + { + var backgroundModes = NSBundle.MainBundle.InfoDictionary[(NSString)"UIBackgroundModes"] as NSArray; + manager.AllowsBackgroundLocationUpdates = backgroundModes != null && (backgroundModes.Contains((NSString)"Location") || backgroundModes.Contains((NSString)"location")); + } + + // Always prevent location update pausing since we're only listening for a single update + if (UIDevice.CurrentDevice.CheckSystemVersion(6, 0)) + manager.PausesLocationUpdatesAutomatically = false; + + tcs = new TaskCompletionSource(manager); + var singleListener = new GeolocationSingleUpdateDelegate(manager, DesiredAccuracy, includeHeading, timeoutMilliseconds, cancelToken.Value); + manager.Delegate = singleListener; + manager.StartUpdatingLocation(); + + if (includeHeading && SupportsHeading) + manager.StartUpdatingHeading(); + + return await singleListener.Task; + + //tcs = new TaskCompletionSource(); + //if (_lastPosition == null) + //{ + // if (cancelToken != CancellationToken.None) + // cancelToken.Value.Register(() => tcs.TrySetCanceled()); + + // EventHandler gotError = null; + // gotError = (s, e) => + // { + // tcs.TrySetException(new GeolocationException(e.Error)); + // PositionError -= gotError; + // }; + // PositionError += gotError; + + // EventHandler gotPosition = null; + // gotPosition = (s, e) => + // { + // tcs.TrySetResult(e.Position); + // PositionChanged += gotPosition; + // }; + // PositionChanged += gotPosition; + //} + //else + // tcs.SetResult(_lastPosition); + + //return await tcs.Task; + } + + #endregion + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs new file mode 100644 index 000000000..6f87b60dc --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using eShopOnContainers.Core.Models.Permissions; +using eShopOnContainers.Core.Services.Permissions; +using CoreLocation; +using UIKit; +using Foundation; + +namespace eShopOnContainers.iOS.Services +{ + public class PermissionsService : IPermissionsService + { + #region Internal Implementation + + PermissionStatus GetLocationPermissionStatus(Permission permission) + { + if (!CLLocationManager.LocationServicesEnabled) + return PermissionStatus.Disabled; + + var status = CLLocationManager.Status; + + if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0)) + { + switch (status) + { + case CLAuthorizationStatus.AuthorizedAlways: + case CLAuthorizationStatus.AuthorizedWhenInUse: + return PermissionStatus.Granted; + case CLAuthorizationStatus.Denied: + return PermissionStatus.Denied; + case CLAuthorizationStatus.Restricted: + return PermissionStatus.Restricted; + default: + return PermissionStatus.Unknown; + } + } + + switch (status) + { + case CLAuthorizationStatus.Authorized: + return PermissionStatus.Granted; + case CLAuthorizationStatus.Denied: + return PermissionStatus.Denied; + case CLAuthorizationStatus.Restricted: + return PermissionStatus.Restricted; + default: + return PermissionStatus.Unknown; + } + } + + Task RequestLocationPermissionAsync(Permission permission = Permission.Location) + { + if (CLLocationManager.Status == CLAuthorizationStatus.AuthorizedWhenInUse && permission == Permission.LocationAlways) + { + // Don't do anything and request it + } + else if (GetLocationPermissionStatus(permission) != PermissionStatus.Unknown) + return Task.FromResult(GetLocationPermissionStatus(permission)); + + if (!UIDevice.CurrentDevice.CheckSystemVersion(8, 0)) + { + return Task.FromResult(PermissionStatus.Unknown); + } + + EventHandler authCallback = null; + var tcs = new TaskCompletionSource(); + var locationManager = new CLLocationManager(); + + authCallback = (sender, e) => + { + if (e.Status == CLAuthorizationStatus.NotDetermined) + return; + locationManager.AuthorizationChanged -= authCallback; + tcs.TrySetResult(GetLocationPermissionStatus(permission)); + }; + locationManager.AuthorizationChanged += authCallback; + + var info = NSBundle.MainBundle.InfoDictionary; + //if (permission == Permission.Location) + //{ + // if (info.ContainsKey(new NSString("NSLocationAlwaysUsageDescription"))) + // locationManager.RequestAlwaysAuthorization(); + // else if (info.ContainsKey(new NSString("NSLocationWhenInUseUsageDescription"))) + // locationManager.RequestWhenInUseAuthorization(); + // else + // throw new UnauthorizedAccessException("On iOS 8.0 and higher you must set either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription in your Info.plist file to enable Authorization Requests for Location updates!"); + //} + //else if (permission == Permission.LocationAlways) + //{ + // if (info.ContainsKey(new NSString("NSLocationAlwaysUsageDescription"))) + // locationManager.RequestAlwaysAuthorization(); + // else + // throw new UnauthorizedAccessException("On iOS 8.0 and higher you must set either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription in your Info.plist file to enable Authorization Requests for Location updates!"); + //} + if (permission == Permission.LocationWhenInUse) + { + if (info.ContainsKey(new NSString("NSLocationWhenInUseUsageDescription"))) + locationManager.RequestWhenInUseAuthorization(); + else + throw new UnauthorizedAccessException("On iOS 8.0 and higher you must set either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription in your Info.plist file to enable Authorization Requests for Location updates."); + } + return tcs.Task; + } + + #endregion + + #region IPermissionsServiceImplementation + + public Task CheckPermissionStatusAsync(Permission permission) + { + switch (permission) + { + case Permission.LocationWhenInUse: + return Task.FromResult(GetLocationPermissionStatus(permission)); + } + return Task.FromResult(PermissionStatus.Granted); + } + + public async Task> RequestPermissionsAsync(params Permission[] permissions) + { + var results = new Dictionary(); + foreach (var permission in permissions) + { + if (results.ContainsKey(permission)) + continue; + + switch (permission) + { + case Permission.LocationWhenInUse: + results.Add(permission, await RequestLocationPermissionAsync(permission).ConfigureAwait(false)); + break; + } + + if (!results.ContainsKey(permission)) + results.Add(permission, PermissionStatus.Granted); + } + return results; + } + + #endregion + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/eShopOnContainers.iOS.csproj b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/eShopOnContainers.iOS.csproj index 05e42b858..6ea0a4465 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/eShopOnContainers.iOS.csproj +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/eShopOnContainers.iOS.csproj @@ -126,6 +126,9 @@ + + + @@ -198,12 +201,6 @@ ..\..\..\..\packages\Acr.UserDialogs.6.5.1\lib\Xamarin.iOS10\Acr.UserDialogs.Interface.dll - - ..\..\..\..\packages\Xam.Plugin.Geolocator.3.0.4\lib\Xamarin.iOS10\Plugin.Geolocator.Abstractions.dll - - - ..\..\..\..\packages\Xam.Plugin.Geolocator.3.0.4\lib\Xamarin.iOS10\Plugin.Geolocator.dll - ..\..\..\..\packages\PInvoke.Windows.Core.0.3.2\lib\portable-net45+win+wpa81+MonoAndroid10+xamarinios10+MonoTouch10\PInvoke.Windows.Core.dll diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/packages.config b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/packages.config index 0669c2fa0..86db43622 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/packages.config +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/packages.config @@ -65,7 +65,6 @@ - From d2bd5a55ec819e7086ddbd352de80cf150c59b33 Mon Sep 17 00:00:00 2001 From: David Britch Date: Tue, 20 Feb 2018 13:14:56 +0000 Subject: [PATCH 02/13] UWP location service added. --- .../Helpers/Timeout.cs | 33 ++++ .../Services/LocationServiceImplementation.cs | 166 ++++++++++++++++++ .../eShopOnContainers.Windows.csproj | 2 + 3 files changed, 201 insertions(+) create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Helpers/Timeout.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Services/LocationServiceImplementation.cs diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Helpers/Timeout.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Helpers/Timeout.cs new file mode 100644 index 000000000..7306a3c78 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Helpers/Timeout.cs @@ -0,0 +1,33 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace eShopOnContainers.Windows.Helpers +{ + internal class Timeout + { + public const int Infinite = -1; + readonly CancellationTokenSource _canceller = new CancellationTokenSource(); + + public Timeout(int timeout, Action timesUp) + { + if (timeout == Infinite) + return; + if (timeout < 0) + throw new ArgumentOutOfRangeException("timeoutMilliseconds"); + if (timesUp == null) + throw new ArgumentNullException("timesUp"); + + Task.Delay(TimeSpan.FromMilliseconds(timeout), _canceller.Token).ContinueWith(t => + { + if (!t.IsCanceled) + timesUp(); + }); + } + + public void Cancel() + { + _canceller.Cancel(); + } + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Services/LocationServiceImplementation.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Services/LocationServiceImplementation.cs new file mode 100644 index 000000000..27749744a --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Services/LocationServiceImplementation.cs @@ -0,0 +1,166 @@ +using eShopOnContainers.Core.Models.Location; +using eShopOnContainers.Core.Services.Location; +using System; +using System.Threading; +using System.Threading.Tasks; +using Windows.Devices.Geolocation; +using Windows.Foundation; + +[assembly: Xamarin.Forms.Dependency(typeof(ILocationServiceImplementation))] +namespace eShopOnContainers.Windows.Services +{ + public class LocationServiceImplementation : ILocationServiceImplementation + { + double _desiredAccuracy; + Geolocator _locator = new Geolocator(); + + public LocationServiceImplementation() + { + DesiredAccuracy = 100; + } + + #region Internal Implementation + + Geolocator GetGeolocator() + { + var loc = _locator; + if (loc == null) + { + _locator = new Geolocator(); + _locator.StatusChanged += OnLocatorStatusChanged; + loc = _locator; + } + return loc; + } + + PositionStatus GetGeolocatorStatus() + { + var loc = GetGeolocator(); + return loc.LocationStatus; + } + + static Position GetPosition(Geoposition position) + { + var pos = new Position + { + Accuracy = position.Coordinate.Accuracy, + Altitude = position.Coordinate.Point.Position.Altitude, + Latitude = position.Coordinate.Point.Position.Latitude, + Longitude = position.Coordinate.Point.Position.Longitude, + Timestamp = position.Coordinate.Timestamp.ToUniversalTime() + }; + + if (position.Coordinate.Heading != null) + pos.Heading = position.Coordinate.Heading.Value; + if (position.Coordinate.Speed != null) + pos.Speed = position.Coordinate.Speed.Value; + if (position.Coordinate.AltitudeAccuracy.HasValue) + pos.AltitudeAccuracy = position.Coordinate.AltitudeAccuracy.Value; + + return pos; + } + + async void OnLocatorStatusChanged(Geolocator sender, StatusChangedEventArgs e) + { + GeolocationError error; + + switch (e.Status) + { + case PositionStatus.Disabled: + error = GeolocationError.Unauthorized; + break; + case PositionStatus.NoData: + error = GeolocationError.PositionUnavailable; + break; + default: + return; + } + _locator = null; + } + + #endregion + + #region ILocationServiceImplementation + + public double DesiredAccuracy + { + get { return _desiredAccuracy; } + set + { + _desiredAccuracy = value; + GetGeolocator().DesiredAccuracy = (value < 100) ? PositionAccuracy.High : PositionAccuracy.Default; + } + } + + public event EventHandler PositionError; + public event EventHandler PositionChanged; + + public bool IsGeolocationAvailable + { + get + { + var status = GetGeolocatorStatus(); + while (status == PositionStatus.Initializing) + { + Task.Delay(10).Wait(); + status = GetGeolocatorStatus(); + } + return status != PositionStatus.NotAvailable; + } + } + + public bool IsGeolocationEnabled + { + get + { + var status = GetGeolocatorStatus(); + while (status == PositionStatus.Initializing) + { + Task.Delay(10).Wait(); + status = GetGeolocatorStatus(); + } + return status != PositionStatus.Disabled && status != PositionStatus.NotAvailable; + } + } + + public Task GetPositionAsync(TimeSpan? timeout = null, CancellationToken? cancelToken = null, bool includeHeading = false) + { + var timeoutMilliseconds = timeout.HasValue ? (int)timeout.Value.TotalMilliseconds : eShopOnContainers.Windows.Helpers.Timeout.Infinite; + if (timeoutMilliseconds < 0 && timeoutMilliseconds != eShopOnContainers.Windows.Helpers.Timeout.Infinite) + throw new ArgumentOutOfRangeException(nameof(timeout)); + + if (!cancelToken.HasValue) + cancelToken = CancellationToken.None; + + var pos = GetGeolocator().GetGeopositionAsync(TimeSpan.FromTicks(0), TimeSpan.FromDays(365)); + cancelToken.Value.Register(o => ((IAsyncOperation)o).Cancel(), pos); + var timer = new eShopOnContainers.Windows.Helpers.Timeout(timeoutMilliseconds, pos.Cancel); + var tcs = new TaskCompletionSource(); + + pos.Completed = (op, s) => + { + timer.Cancel(); + + switch (s) + { + case AsyncStatus.Canceled: + tcs.SetCanceled(); + break; + case AsyncStatus.Completed: + tcs.SetResult(GetPosition(op.GetResults())); + break; + case AsyncStatus.Error: + var ex = op.ErrorCode; + if (ex is UnauthorizedAccessException) + ex = new GeolocationException(GeolocationError.Unauthorized, ex); + + tcs.SetException(ex); + break; + } + }; + return tcs.Task; + } + + #endregion + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Windows/eShopOnContainers.Windows.csproj b/src/Mobile/eShopOnContainers/eShopOnContainers.Windows/eShopOnContainers.Windows.csproj index a1ca59d2f..b879d31c6 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Windows/eShopOnContainers.Windows.csproj +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Windows/eShopOnContainers.Windows.csproj @@ -115,12 +115,14 @@ + MainPage.xaml + From 04296e7a14120dc42b718fffa9c61217d2726d04 Mon Sep 17 00:00:00 2001 From: David Britch Date: Tue, 20 Feb 2018 15:28:06 +0000 Subject: [PATCH 03/13] Android LocationService implementation complete. --- .../eShopOnContainers.Core/App.xaml.cs | 2 - .../ILocationServiceImplementation.cs | 3 - .../Activities/MainActivity.cs | 5 +- .../Extensions/LocationExtensions.cs | 85 ++++++++++ .../Services/GeolocationSingleListener.cs | 112 +++++++++++++ .../Services/LocationServiceImplementation.cs | 155 ++++++++++++++++++ ...issionService.cs => PermissionsService.cs} | 7 +- .../eShopOnContainers.Droid.csproj | 5 +- 8 files changed, 363 insertions(+), 11 deletions(-) create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Extensions/LocationExtensions.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/GeolocationSingleListener.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs rename src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/{PermissionService.cs => PermissionsService.cs} (98%) diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs index 7207e2204..2d7322d14 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs @@ -77,8 +77,6 @@ namespace eShopOnContainers //locationService.AllowsBackgroundUpdates = true; locator.DesiredAccuracy = 50; - await Task.Delay(5000); - var position = await locator.GetPositionAsync(); _settingsService.Latitude = position.Latitude.ToString(); diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs index c83a710ea..ba85764ea 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs @@ -7,9 +7,6 @@ namespace eShopOnContainers.Core.Services.Location { public interface ILocationServiceImplementation { - event EventHandler PositionError; - event EventHandler PositionChanged; - double DesiredAccuracy { get; set; } bool IsGeolocationAvailable { get; } bool IsGeolocationEnabled { get; } diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs index 12ea046ac..aef0ef1b3 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs @@ -7,9 +7,9 @@ using Android.Runtime; using Android.Views; using FFImageLoading; using FFImageLoading.Forms.Droid; -using Plugin.Permissions; using System; using Xamarin.Forms.Platform.Android; +using eShopOnContainers.Droid.Services; namespace eShopOnContainers.Droid.Activities { @@ -57,8 +57,7 @@ namespace eShopOnContainers.Droid.Activities public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) { base.OnRequestPermissionsResult(requestCode, permissions, grantResults); - - PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults); + PermissionsService.Instance.OnRequestPermissionResult(requestCode, permissions, grantResults); } } } diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Extensions/LocationExtensions.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Extensions/LocationExtensions.cs new file mode 100644 index 000000000..abb76c1f5 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Extensions/LocationExtensions.cs @@ -0,0 +1,85 @@ +using eShopOnContainers.Core.Models.Location; +using System; + +namespace eShopOnContainers.Droid.Extensions +{ + public static class LocationExtensions + { + static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + static int TwoMinutes = 120000; + + internal static Position ToPosition(this Android.Locations.Location location) + { + var p = new Position(); + if (location.HasAccuracy) + p.Accuracy = location.Accuracy; + if (location.HasAltitude) + p.Altitude = location.Altitude; + if (location.HasBearing) + p.Heading = location.Bearing; + if (location.HasSpeed) + p.Speed = location.Speed; + + p.Longitude = location.Longitude; + p.Latitude = location.Latitude; + p.Timestamp = location.GetTimestamp(); + return p; + } + + internal static DateTimeOffset GetTimestamp(this Android.Locations.Location location) + { + try + { + return new DateTimeOffset(Epoch.AddMilliseconds(location.Time)); + } + catch (Exception e) + { + return new DateTimeOffset(Epoch); + } + } + + internal static bool IsBetterLocation(this Android.Locations.Location location, Android.Locations.Location bestLocation) + { + + if (bestLocation == null) + return true; + + var timeDelta = location.Time - bestLocation.Time; + var isSignificantlyNewer = timeDelta > TwoMinutes; + var isSignificantlyOlder = timeDelta < -TwoMinutes; + var isNewer = timeDelta > 0; + + if (isSignificantlyNewer) + return true; + + if (isSignificantlyOlder) + return false; + + var accuracyDelta = (int)(location.Accuracy - bestLocation.Accuracy); + var isLessAccurate = accuracyDelta > 0; + var isMoreAccurate = accuracyDelta < 0; + var isSignificantlyLessAccurage = accuracyDelta > 200; + + var isFromSameProvider = IsSameProvider(location.Provider, bestLocation.Provider); + + if (isMoreAccurate) + return true; + + if (isNewer && !isLessAccurate) + return true; + + if (isNewer && !isSignificantlyLessAccurage && isFromSameProvider) + return true; + + return false; + } + + internal static bool IsSameProvider(string provider1, string provider2) + { + if (provider1 == null) + return provider2 == null; + + return provider1.Equals(provider2); + } + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/GeolocationSingleListener.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/GeolocationSingleListener.cs new file mode 100644 index 000000000..db8f13198 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/GeolocationSingleListener.cs @@ -0,0 +1,112 @@ +using System; +using System.Threading.Tasks; +using Android.Locations; +using Android.OS; +using System.Threading; +using System.Collections.Generic; +using Android.Runtime; +using eShopOnContainers.Core.Models.Location; +using eShopOnContainers.Droid.Extensions; + +namespace eShopOnContainers.Droid.Services +{ + public class GeolocationSingleListener : Java.Lang.Object, ILocationListener + { + readonly object _locationSync = new object(); + readonly Action _finishedCallback; + readonly float _desiredAccuracy; + readonly Timer _timer; + readonly TaskCompletionSource _tcs = new TaskCompletionSource(); + HashSet _activeProviders = new HashSet(); + Android.Locations.Location _bestLocation; + + public Task Task => _tcs.Task; + + public GeolocationSingleListener(LocationManager manager, float desiredAccuracy, int timeout, IEnumerable activeProviders, Action finishedCallback) + { + _desiredAccuracy = desiredAccuracy; + _finishedCallback = finishedCallback; + + _activeProviders = new HashSet(activeProviders); + + foreach (var provider in activeProviders) + { + var location = manager.GetLastKnownLocation(provider); + if (location != null && location.IsBetterLocation(_bestLocation)) + _bestLocation = location; + } + + + if (timeout != Timeout.Infinite) + _timer = new Timer(TimesUp, null, timeout, 0); + } + + public void Cancel() => _tcs.TrySetCanceled(); + + public void OnLocationChanged(Android.Locations.Location location) + { + if (location.Accuracy <= _desiredAccuracy) + { + Finish(location); + return; + } + + lock (_locationSync) + { + if (location.IsBetterLocation(_bestLocation)) + _bestLocation = location; + } + } + + public void OnProviderDisabled(string provider) + { + lock (_activeProviders) + { + if (_activeProviders.Remove(provider) && _activeProviders.Count == 0) + _tcs.TrySetException(new GeolocationException(GeolocationError.PositionUnavailable)); + } + } + + public void OnProviderEnabled(string provider) + { + lock (_activeProviders) + _activeProviders.Add(provider); + } + + public void OnStatusChanged(string provider, [GeneratedEnum] Availability status, Bundle extras) + { + switch (status) + { + case Availability.Available: + OnProviderEnabled(provider); + break; + + case Availability.OutOfService: + OnProviderDisabled(provider); + break; + } + } + + void TimesUp(object state) + { + lock (_locationSync) + { + if (_bestLocation == null) + { + if (_tcs.TrySetCanceled()) + _finishedCallback?.Invoke(); + } + else + { + Finish(_bestLocation); + } + } + } + + void Finish(Android.Locations.Location location) + { + _finishedCallback?.Invoke(); + _tcs.TrySetResult(location.ToPosition()); + } + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs new file mode 100644 index 000000000..f17d1bef6 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs @@ -0,0 +1,155 @@ +using Android.App; +using Android.Content; +using eShopOnContainers.Droid.Services; +using System; +using eShopOnContainers.Core.Services.Location; +using eShopOnContainers.Core.Models.Location; +using eShopOnContainers.Core.Services.Permissions; +using eShopOnContainers.Core.Models.Permissions; +using System.Threading; +using System.Threading.Tasks; +using Android.Locations; +using System.Linq; +using System.Collections.Generic; +using Android.OS; + +[assembly: Xamarin.Forms.Dependency(typeof(LocationServiceImplementation))] +namespace eShopOnContainers.Droid.Services +{ + public class LocationServiceImplementation : ILocationServiceImplementation + { + #region Internal Implementation + + LocationManager _locationManager; + GeolocationSingleListener _singleListener = null; + + string[] Providers => Manager.GetProviders(enabledOnly: false).ToArray(); + string[] IgnoredProviders => new string[] { LocationManager.PassiveProvider, "local_database" }; + + public static string[] ProvidersToUse { get; set; } = new string[] { }; + + LocationManager Manager + { + get + { + if (_locationManager == null) + _locationManager = (LocationManager)Application.Context.GetSystemService(Context.LocationService); + return _locationManager; + } + } + + public LocationServiceImplementation() + { + DesiredAccuracy = 100; + } + + async Task CheckPermissionsAsync() + { + IPermissionsService permissionsService = new PermissionsService(); + var status = await permissionsService.CheckPermissionStatusAsync(Permission.Location); + if (status != PermissionStatus.Granted) + { + Console.WriteLine("Currently do not have Location permissions, requesting permissions"); + + var request = await permissionsService.RequestPermissionsAsync(Permission.Location); + if (request[Permission.Location] != PermissionStatus.Granted) + { + Console.WriteLine("Location permission denied."); + return false; + } + } + + return true; + } + + #endregion + + #region ILocationServiceImplementation + + public double DesiredAccuracy { get; set; } + + public bool IsGeolocationAvailable => Providers.Length > 0; + + public bool IsGeolocationEnabled => Providers.Any(p => !IgnoredProviders.Contains(p) && Manager.IsProviderEnabled(p)); + + public async Task GetPositionAsync(TimeSpan? timeout = null, CancellationToken? cancelToken = null, bool includeHeading = false) + { + var timeoutMilliseconds = timeout.HasValue ? (int)timeout.Value.TotalMilliseconds : Timeout.Infinite; + if (timeoutMilliseconds <= 0 && timeoutMilliseconds != Timeout.Infinite) + throw new ArgumentOutOfRangeException(nameof(timeout), "Timeout must be greater than or equal to 0"); + + if (!cancelToken.HasValue) + cancelToken = CancellationToken.None; + + var hasPermission = await CheckPermissionsAsync(); + if (!hasPermission) + throw new GeolocationException(GeolocationError.Unauthorized); + + var tcs = new TaskCompletionSource(); + + var providers = new List(); + if (ProvidersToUse == null || ProvidersToUse.Length == 0) + providers.AddRange(Providers); + else + { + foreach (var provider in Providers) + { + if (ProvidersToUse?.Contains(provider) ?? false) + continue; + providers.Add(provider); + } + } + + void SingleListenerFinishCallback() + { + if (_singleListener == null) + return; + + for (int i = 0; i < providers.Count; ++i) + Manager.RemoveUpdates(_singleListener); + } + + _singleListener = new GeolocationSingleListener(Manager, (float)DesiredAccuracy, timeoutMilliseconds, providers.Where(Manager.IsProviderEnabled), finishedCallback: SingleListenerFinishCallback); + if (cancelToken != CancellationToken.None) + { + cancelToken.Value.Register(() => + { + _singleListener.Cancel(); + + for (int i = 0; i < providers.Count; ++i) + Manager.RemoveUpdates(_singleListener); + }, true); + } + + try + { + var looper = Looper.MyLooper() ?? Looper.MainLooper; + int enabled = 0; + for (var i = 0; i < providers.Count; ++i) + { + if (Manager.IsProviderEnabled(providers[i])) + enabled++; + + Manager.RequestLocationUpdates(providers[i], 0, 0, _singleListener, looper); + } + + if (enabled == 0) + { + for (int i = 0; i < providers.Count; ++i) + Manager.RemoveUpdates(_singleListener); + + tcs.SetException(new GeolocationException(GeolocationError.PositionUnavailable)); + return await tcs.Task; + } + } + catch (Java.Lang.SecurityException ex) + { + tcs.SetException(new GeolocationException(GeolocationError.Unauthorized, ex)); + return await tcs.Task; + } + return await _singleListener.Task; + } + + #endregion + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs similarity index 98% rename from src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionService.cs rename to src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs index 268d71771..36399cdbe 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionService.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs @@ -9,11 +9,10 @@ using Android.App; using Android.Support.V4.App; using Android.Support.V4.Content; using System.Diagnostics; -using Android.Icu.Text; namespace eShopOnContainers.Droid.Services { - public class PermissionService : IPermissionsService + public class PermissionsService : IPermissionsService { const int _permissionCode = 25; object _locker = new object(); @@ -21,6 +20,8 @@ namespace eShopOnContainers.Droid.Services Dictionary _results; IList _requestedPermissions; + internal static PermissionsService Instance; + #region Internal Implementation List GetManifestNames(Permission permission) @@ -134,6 +135,8 @@ namespace eShopOnContainers.Droid.Services public Task CheckPermissionStatusAsync(Permission permission) { + Instance = this; + var names = GetManifestNames(permission); if (names == null) { diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj index 83189bc72..7d699f9d2 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj @@ -212,7 +212,10 @@ - + + + + From 1fa48de6672cc8713a8fbf999653fafb3367639a Mon Sep 17 00:00:00 2001 From: David Britch Date: Tue, 20 Feb 2018 15:58:42 +0000 Subject: [PATCH 04/13] Android LocationService refactoring. --- .../Models/Location/ActivityType.cs | 10 ----- .../Models/Location/Address.cs | 38 ------------------- .../Models/Location/ListenerSettings.cs | 15 -------- .../Models/Location/PositionErrorEventArgs.cs | 14 ------- .../Models/Location/PositionEventArgs.cs | 17 --------- .../ILocationServiceImplementation.cs | 2 +- .../Activities/MainActivity.cs | 5 ++- .../Extensions/LocationExtensions.cs | 1 - .../Services/GeolocationSingleListener.cs | 2 - .../Services/LocationServiceImplementation.cs | 12 +++--- .../Services/PermissionsService.cs | 7 +--- 11 files changed, 11 insertions(+), 112 deletions(-) delete mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/ActivityType.cs delete mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/Address.cs delete mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/ListenerSettings.cs delete mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/PositionErrorEventArgs.cs delete mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/PositionEventArgs.cs diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/ActivityType.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/ActivityType.cs deleted file mode 100644 index 3240b1bad..000000000 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/ActivityType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace eShopOnContainers.Core.Models.Location -{ - public enum ActivityType - { - Other, - AutomotiveNavigation, - Fitness, - OtherNavigation - } -} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/Address.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/Address.cs deleted file mode 100644 index a4be9e1c7..000000000 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/Address.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; - -namespace eShopOnContainers.Core.Models.Location -{ - public class Address - { - public double Latitude { get; set; } - public double Longitude { get; set; } - public string CountryCode { get; set; } - public string CountryName { get; set; } - public string FeatureName { get; set; } - public string PostalCode { get; set; } - public string SubLocality { get; set; } - public string Thoroughfare { get; set; } - public string SubThoroughfare { get; set; } - public string Locality { get; set; } - public string AdminArea { get; set; } - public string SubAdminArea { get; set; } - - public Address(Address address) - { - if (address == null) - throw new ArgumentNullException(nameof(address)); - - CountryCode = address.CountryCode; - CountryName = address.CountryName; - Latitude = address.Latitude; - Longitude = address.Longitude; - FeatureName = address.FeatureName; - PostalCode = address.PostalCode; - SubLocality = address.SubLocality; - Thoroughfare = address.Thoroughfare; - SubThoroughfare = address.SubThoroughfare; - SubAdminArea = address.SubAdminArea; - AdminArea = address.AdminArea; - } - } -} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/ListenerSettings.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/ListenerSettings.cs deleted file mode 100644 index 40849b86f..000000000 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/ListenerSettings.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace eShopOnContainers.Core.Models.Location -{ - public class ListenerSettings - { - public bool AllowBackgroundUpdates { get; set; } = false; - public bool PauseLocationUpdatesAutomatically { get; set; } = true; - public ActivityType ActivityType { get; set; } = ActivityType.Other; - public bool ListenForSignificantChanges { get; set; } = false; - public bool DeferLocationUpdates { get; set; } = false; - public double? DeferralDistanceMeters { get; set; } = 500; - public TimeSpan? DeferralTime { get; set; } = TimeSpan.FromMinutes(5); - } -} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/PositionErrorEventArgs.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/PositionErrorEventArgs.cs deleted file mode 100644 index 6c5e3cd24..000000000 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/PositionErrorEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace eShopOnContainers.Core.Models.Location -{ - public class PositionErrorEventArgs : EventArgs - { - public GeolocationError Error { get; private set; } - - public PositionErrorEventArgs(GeolocationError error) - { - Error = error; - } - } -} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/PositionEventArgs.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/PositionEventArgs.cs deleted file mode 100644 index 2107b05e5..000000000 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/PositionEventArgs.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace eShopOnContainers.Core.Models.Location -{ - public class PositionEventArgs : EventArgs - { - public Position Position { get; private set; } - - public PositionEventArgs(Position position) - { - if (position == null) - throw new ArgumentNullException("position"); - - Position = position; - } - } -} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs index ba85764ea..87fa84d0e 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs @@ -11,6 +11,6 @@ namespace eShopOnContainers.Core.Services.Location bool IsGeolocationAvailable { get; } bool IsGeolocationEnabled { get; } - Task GetPositionAsync(TimeSpan? timeout = null, CancellationToken? token = null, bool includeHeading = false); + Task GetPositionAsync(TimeSpan? timeout = null, CancellationToken? token = null); } } diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs index aef0ef1b3..4cb0bc83f 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs @@ -10,6 +10,7 @@ using FFImageLoading.Forms.Droid; using System; using Xamarin.Forms.Platform.Android; using eShopOnContainers.Droid.Services; +using eShopOnContainers.Core.Services.Permissions; namespace eShopOnContainers.Droid.Activities { @@ -20,6 +21,8 @@ namespace eShopOnContainers.Droid.Activities ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] public class MainActivity : FormsAppCompatActivity { + public static IPermissionsService PermissionsService = new PermissionsService(); + protected override void OnCreate(Bundle bundle) { FormsAppCompatActivity.ToolbarResource = Resource.Layout.Toolbar; @@ -57,7 +60,7 @@ namespace eShopOnContainers.Droid.Activities public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) { base.OnRequestPermissionsResult(requestCode, permissions, grantResults); - PermissionsService.Instance.OnRequestPermissionResult(requestCode, permissions, grantResults); + ((PermissionsService)PermissionsService).OnRequestPermissionResult(requestCode, permissions, grantResults); } } } diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Extensions/LocationExtensions.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Extensions/LocationExtensions.cs index abb76c1f5..41ea2ad3e 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Extensions/LocationExtensions.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Extensions/LocationExtensions.cs @@ -59,7 +59,6 @@ namespace eShopOnContainers.Droid.Extensions var isLessAccurate = accuracyDelta > 0; var isMoreAccurate = accuracyDelta < 0; var isSignificantlyLessAccurage = accuracyDelta > 200; - var isFromSameProvider = IsSameProvider(location.Provider, bestLocation.Provider); if (isMoreAccurate) diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/GeolocationSingleListener.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/GeolocationSingleListener.cs index db8f13198..4710daedf 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/GeolocationSingleListener.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/GeolocationSingleListener.cs @@ -26,7 +26,6 @@ namespace eShopOnContainers.Droid.Services { _desiredAccuracy = desiredAccuracy; _finishedCallback = finishedCallback; - _activeProviders = new HashSet(activeProviders); foreach (var provider in activeProviders) @@ -36,7 +35,6 @@ namespace eShopOnContainers.Droid.Services _bestLocation = location; } - if (timeout != Timeout.Infinite) _timer = new Timer(TimesUp, null, timeout, 0); } diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs index f17d1bef6..b29e6ff85 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs @@ -4,7 +4,6 @@ using eShopOnContainers.Droid.Services; using System; using eShopOnContainers.Core.Services.Location; using eShopOnContainers.Core.Models.Location; -using eShopOnContainers.Core.Services.Permissions; using eShopOnContainers.Core.Models.Permissions; using System.Threading; using System.Threading.Tasks; @@ -12,6 +11,7 @@ using Android.Locations; using System.Linq; using System.Collections.Generic; using Android.OS; +using eShopOnContainers.Droid.Activities; [assembly: Xamarin.Forms.Dependency(typeof(LocationServiceImplementation))] namespace eShopOnContainers.Droid.Services @@ -45,20 +45,18 @@ namespace eShopOnContainers.Droid.Services async Task CheckPermissionsAsync() { - IPermissionsService permissionsService = new PermissionsService(); - var status = await permissionsService.CheckPermissionStatusAsync(Permission.Location); + var status = await MainActivity.PermissionsService.CheckPermissionStatusAsync(Permission.Location); if (status != PermissionStatus.Granted) { - Console.WriteLine("Currently do not have Location permissions, requesting permissions"); + Console.WriteLine("Currently do not have Location permissions, requesting permissions."); - var request = await permissionsService.RequestPermissionsAsync(Permission.Location); + var request = await MainActivity.PermissionsService.RequestPermissionsAsync(Permission.Location); if (request[Permission.Location] != PermissionStatus.Granted) { Console.WriteLine("Location permission denied."); return false; } } - return true; } @@ -72,7 +70,7 @@ namespace eShopOnContainers.Droid.Services public bool IsGeolocationEnabled => Providers.Any(p => !IgnoredProviders.Contains(p) && Manager.IsProviderEnabled(p)); - public async Task GetPositionAsync(TimeSpan? timeout = null, CancellationToken? cancelToken = null, bool includeHeading = false) + public async Task GetPositionAsync(TimeSpan? timeout = null, CancellationToken? cancelToken = null) { var timeoutMilliseconds = timeout.HasValue ? (int)timeout.Value.TotalMilliseconds : Timeout.Infinite; if (timeoutMilliseconds <= 0 && timeoutMilliseconds != Timeout.Infinite) diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs index 36399cdbe..08399a331 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs @@ -20,8 +20,6 @@ namespace eShopOnContainers.Droid.Services Dictionary _results; IList _requestedPermissions; - internal static PermissionsService Instance; - #region Internal Implementation List GetManifestNames(Permission permission) @@ -83,7 +81,7 @@ namespace eShopOnContainers.Droid.Services return false; } - static Permission GetPermissionForManifestName(string permission) + Permission GetPermissionForManifestName(string permission) { switch (permission) { @@ -95,7 +93,6 @@ namespace eShopOnContainers.Droid.Services return Permission.Unknown; } - public void OnRequestPermissionResult(int requestCode, string[] permissions, Android.Content.PM.Permission[] grantResults) { if (requestCode != _permissionCode) @@ -135,8 +132,6 @@ namespace eShopOnContainers.Droid.Services public Task CheckPermissionStatusAsync(Permission permission) { - Instance = this; - var names = GetManifestNames(permission); if (names == null) { From b9835a3e721253d7897ea019aea08e393cc7d24b Mon Sep 17 00:00:00 2001 From: David Britch Date: Tue, 20 Feb 2018 17:52:19 +0000 Subject: [PATCH 05/13] iOS LocationService complete. --- .../eShopOnContainers.Core/App.xaml.cs | 5 +- .../Services/Basket/BasketMockService.cs | 12 ++-- .../Services/Catalog/CatalogMockService.cs | 20 +++---- .../Services/Marketing/CampaignMockService.cs | 4 +- .../Services/Order/OrderMockService.cs | 6 +- .../Services/User/UserMockService.cs | 2 +- .../ViewModels/LoginViewModel.cs | 4 +- .../GeolocationSingleUpdateDelegate.cs | 34 +---------- .../Services/LocationServiceImplementation.cs | 57 +------------------ .../Services/PermissionsService.cs | 17 ------ 10 files changed, 31 insertions(+), 130 deletions(-) diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs index 2d7322d14..cc8b4cc58 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs @@ -48,12 +48,10 @@ namespace eShopOnContainers { await InitNavigation(); } - if (_settingsService.AllowGpsLocation && !_settingsService.UseFakeLocation) { await GetGpsLocation(); } - if (!_settingsService.UseMocks && !string.IsNullOrEmpty(_settingsService.AuthAccessToken)) { await SendCurrentLocation(); @@ -74,9 +72,10 @@ namespace eShopOnContainers if (locator.IsGeolocationEnabled && locator.IsGeolocationAvailable) { - //locationService.AllowsBackgroundUpdates = true; locator.DesiredAccuracy = 50; + // Delay getting the position to ensure that the UI has finished updating + await Task.Delay(2000); var position = await locator.GetPositionAsync(); _settingsService.Latitude = position.Latitude.ToString(); diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Basket/BasketMockService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Basket/BasketMockService.cs index 60a13af1e..8c54a8993 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Basket/BasketMockService.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Basket/BasketMockService.cs @@ -13,16 +13,16 @@ namespace eShopOnContainers.Core.Services.Basket BuyerId = "9245fe4a-d402-451c-b9ed-9c1a04247482", Items = new List { - new BasketItem { Id = "1", PictureUrl = Device.RuntimePlatform != Device.UWP ? "fake_product_01.png" : "Assets/fake_product_01.png", ProductId = Common.Common.MockCatalogItemId01, ProductName = ".NET Bot Blue Sweatshirt (M)", Quantity = 1, UnitPrice = 19.50M }, - new BasketItem { Id = "2", PictureUrl = Device.RuntimePlatform != Device.UWP ? "fake_product_04.png" : "Assets/fake_product_04.png", ProductId = Common.Common.MockCatalogItemId04, ProductName = ".NET Black Cupt", Quantity = 1, UnitPrice = 17.00M } + new BasketItem { Id = "1", PictureUrl = Device.RuntimePlatform != Device.UWP ? "fake_product_01.png" : "Assets/fake_product_01.png", ProductId = Common.Common.MockCatalogItemId01, ProductName = ".NET Bot Blue Sweatshirt (M)", Quantity = 1, UnitPrice = 19.50M }, + new BasketItem { Id = "2", PictureUrl = Device.RuntimePlatform != Device.UWP ? "fake_product_04.png" : "Assets/fake_product_04.png", ProductId = Common.Common.MockCatalogItemId04, ProductName = ".NET Black Cupt", Quantity = 1, UnitPrice = 17.00M } } }; public async Task GetBasketAsync(string guidUser, string token) { - await Task.Delay(500); + await Task.Delay(10); - if(string.IsNullOrEmpty(guidUser) || string.IsNullOrEmpty(token)) + if (string.IsNullOrEmpty(guidUser) || string.IsNullOrEmpty(token)) { return new CustomerBasket(); } @@ -32,7 +32,7 @@ namespace eShopOnContainers.Core.Services.Basket public async Task UpdateBasketAsync(CustomerBasket customerBasket, string token) { - await Task.Delay(500); + await Task.Delay(10); if (string.IsNullOrEmpty(token)) { @@ -46,7 +46,7 @@ namespace eShopOnContainers.Core.Services.Basket public async Task ClearBasketAsync(string guidUser, string token) { - await Task.Delay(500); + await Task.Delay(10); if (string.IsNullOrEmpty(token)) { diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Catalog/CatalogMockService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Catalog/CatalogMockService.cs index 213b083f2..db6aee837 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Catalog/CatalogMockService.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Catalog/CatalogMockService.cs @@ -24,40 +24,40 @@ namespace eShopOnContainers.Core.Services.Catalog private ObservableCollection MockCatalog = new ObservableCollection { - new CatalogItem { Id = Common.Common.MockCatalogItemId01, PictureUri = Device.RuntimePlatform != Device.UWP ? "fake_product_01.png" : "Assets/fake_product_01.png", Name = ".NET Bot Blue Sweatshirt (M)", Price = 19.50M, CatalogBrandId = 2, CatalogBrand = "Visual Studio", CatalogTypeId = 2, CatalogType = "T-Shirt" }, - new CatalogItem { Id = Common.Common.MockCatalogItemId02, PictureUri = Device.RuntimePlatform != Device.UWP ? "fake_product_02.png" : "Assets/fake_product_02.png", Name = ".NET Bot Purple Sweatshirt (M)", Price = 19.50M, CatalogBrandId = 2, CatalogBrand = "Visual Studio", CatalogTypeId = 2, CatalogType = "T-Shirt" }, - new CatalogItem { Id = Common.Common.MockCatalogItemId03, PictureUri = Device.RuntimePlatform != Device.UWP ? "fake_product_03.png" : "Assets/fake_product_03.png", Name = ".NET Bot Black Sweatshirt (M)", Price = 19.95M, CatalogBrandId = 2, CatalogBrand = "Visual Studio", CatalogTypeId = 2, CatalogType = "T-Shirt" }, - new CatalogItem { Id = Common.Common.MockCatalogItemId04, PictureUri = Device.RuntimePlatform != Device.UWP ? "fake_product_04.png" : "Assets/fake_product_04.png", Name = ".NET Black Cupt", Price = 17.00M, CatalogBrandId = 2, CatalogBrand = "Visual Studio", CatalogTypeId = 1, CatalogType = "Mug" }, - new CatalogItem { Id = Common.Common.MockCatalogItemId05, PictureUri = Device.RuntimePlatform != Device.UWP ? "fake_product_05.png" : "Assets/fake_product_05.png", Name = "Azure Black Sweatshirt (M)", Price = 19.50M, CatalogBrandId = 1, CatalogBrand = "Azure", CatalogTypeId = 2, CatalogType = "T-Shirt" } + new CatalogItem { Id = Common.Common.MockCatalogItemId01, PictureUri = Device.RuntimePlatform != Device.UWP ? "fake_product_01.png" : "Assets/fake_product_01.png", Name = ".NET Bot Blue Sweatshirt (M)", Price = 19.50M, CatalogBrandId = 2, CatalogBrand = "Visual Studio", CatalogTypeId = 2, CatalogType = "T-Shirt" }, + new CatalogItem { Id = Common.Common.MockCatalogItemId02, PictureUri = Device.RuntimePlatform != Device.UWP ? "fake_product_02.png" : "Assets/fake_product_02.png", Name = ".NET Bot Purple Sweatshirt (M)", Price = 19.50M, CatalogBrandId = 2, CatalogBrand = "Visual Studio", CatalogTypeId = 2, CatalogType = "T-Shirt" }, + new CatalogItem { Id = Common.Common.MockCatalogItemId03, PictureUri = Device.RuntimePlatform != Device.UWP ? "fake_product_03.png" : "Assets/fake_product_03.png", Name = ".NET Bot Black Sweatshirt (M)", Price = 19.95M, CatalogBrandId = 2, CatalogBrand = "Visual Studio", CatalogTypeId = 2, CatalogType = "T-Shirt" }, + new CatalogItem { Id = Common.Common.MockCatalogItemId04, PictureUri = Device.RuntimePlatform != Device.UWP ? "fake_product_04.png" : "Assets/fake_product_04.png", Name = ".NET Black Cupt", Price = 17.00M, CatalogBrandId = 2, CatalogBrand = "Visual Studio", CatalogTypeId = 1, CatalogType = "Mug" }, + new CatalogItem { Id = Common.Common.MockCatalogItemId05, PictureUri = Device.RuntimePlatform != Device.UWP ? "fake_product_05.png" : "Assets/fake_product_05.png", Name = "Azure Black Sweatshirt (M)", Price = 19.50M, CatalogBrandId = 1, CatalogBrand = "Azure", CatalogTypeId = 2, CatalogType = "T-Shirt" } }; public async Task> GetCatalogAsync() { - await Task.Delay(500); + await Task.Delay(10); return MockCatalog; } public async Task> FilterAsync(int catalogBrandId, int catalogTypeId) { - await Task.Delay(500); + await Task.Delay(10); return MockCatalog .Where(c => c.CatalogBrandId == catalogBrandId && - c.CatalogTypeId == catalogTypeId) + c.CatalogTypeId == catalogTypeId) .ToObservableCollection(); } public async Task> GetCatalogBrandAsync() { - await Task.Delay(500); + await Task.Delay(10); return MockCatalogBrand; } public async Task> GetCatalogTypeAsync() { - await Task.Delay(500); + await Task.Delay(10); return MockCatalogType; } diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Marketing/CampaignMockService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Marketing/CampaignMockService.cs index e24405b53..fdc7c75fd 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Marketing/CampaignMockService.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Marketing/CampaignMockService.cs @@ -38,13 +38,13 @@ namespace eShopOnContainers.Core.Services.Marketing public async Task> GetAllCampaignsAsync(string token) { - await Task.Delay(500); + await Task.Delay(10); return _mockCampaign; } public async Task GetCampaignByIdAsync(int campaignId, string token) { - await Task.Delay(500); + await Task.Delay(10); return _mockCampaign.SingleOrDefault(c => c.Id == campaignId); } } diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderMockService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderMockService.cs index 5fdc7c281..185989255 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderMockService.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderMockService.cs @@ -74,7 +74,7 @@ namespace eShopOnContainers.Core.Services.Order public async Task> GetOrdersAsync(string token) { - await Task.Delay(500); + await Task.Delay(10); if (!string.IsNullOrEmpty(token)) { @@ -88,7 +88,7 @@ namespace eShopOnContainers.Core.Services.Order public async Task GetOrderAsync(int orderId, string token) { - await Task.Delay(500); + await Task.Delay(10); if (!string.IsNullOrEmpty(token)) return MockOrders @@ -99,7 +99,7 @@ namespace eShopOnContainers.Core.Services.Order public async Task CreateOrderAsync(Models.Orders.Order newOrder, string token) { - await Task.Delay(500); + await Task.Delay(10); if (!string.IsNullOrEmpty(token)) { diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/User/UserMockService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/User/UserMockService.cs index 5b32bc296..fb75ec03e 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/User/UserMockService.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/User/UserMockService.cs @@ -28,7 +28,7 @@ namespace eShopOnContainers.Core.Services.User public async Task GetUserInfoAsync(string authToken) { - await Task.Delay(500); + await Task.Delay(10); return MockUserInfo; } } diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/LoginViewModel.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/LoginViewModel.cs index 02711fb39..2ed8cf83c 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/LoginViewModel.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/LoginViewModel.cs @@ -160,7 +160,7 @@ namespace eShopOnContainers.Core.ViewModels { try { - await Task.Delay(1000); + await Task.Delay(10); isAuthenticated = true; } @@ -189,7 +189,7 @@ namespace eShopOnContainers.Core.ViewModels { IsBusy = true; - await Task.Delay(500); + await Task.Delay(10); LoginUrl = _identityService.CreateAuthorizationRequest(); diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/GeolocationSingleUpdateDelegate.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/GeolocationSingleUpdateDelegate.cs index 110ac1eff..980aa8a55 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/GeolocationSingleUpdateDelegate.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/GeolocationSingleUpdateDelegate.cs @@ -10,24 +10,19 @@ namespace eShopOnContainers.iOS.Services { internal class GeolocationSingleUpdateDelegate : CLLocationManagerDelegate { - bool _haveHeading; bool _haveLocation; readonly Position _position = new Position(); - CLHeading _bestHeading; - readonly double _desiredAccuracy; - readonly bool _includeHeading; readonly TaskCompletionSource _tcs; readonly CLLocationManager _manager; public Task Task => _tcs?.Task; - public GeolocationSingleUpdateDelegate(CLLocationManager manager, double desiredAccuracy, bool includeHeading, int timeout, CancellationToken cancelToken) + public GeolocationSingleUpdateDelegate(CLLocationManager manager, double desiredAccuracy, int timeout, CancellationToken cancelToken) { _manager = manager; _tcs = new TaskCompletionSource(manager); _desiredAccuracy = desiredAccuracy; - _includeHeading = includeHeading; if (timeout != Timeout.Infinite) { @@ -76,8 +71,6 @@ namespace eShopOnContainers.iOS.Services } } - public override bool ShouldDisplayHeadingCalibration(CLLocationManager manager) => true; - public override void UpdatedLocation(CLLocationManager manager, CLLocation newLocation, CLLocation oldLocation) { if (newLocation.HorizontalAccuracy < 0) @@ -97,31 +90,13 @@ namespace eShopOnContainers.iOS.Services { _position.Timestamp = new DateTimeOffset((DateTime)newLocation.Timestamp); } - catch (Exception ex) + catch (Exception) { _position.Timestamp = DateTimeOffset.UtcNow; } _haveLocation = true; - if ((!_includeHeading || _haveHeading) && _position.Accuracy <= _desiredAccuracy) - { - _tcs.TrySetResult(new Position(_position)); - StopListening(); - } - } - - public override void UpdatedHeading(CLLocationManager manager, CLHeading newHeading) - { - if (newHeading.HeadingAccuracy < 0) - return; - if (_bestHeading != null && newHeading.HeadingAccuracy >= _bestHeading.HeadingAccuracy) - return; - - _bestHeading = newHeading; - _position.Heading = newHeading.TrueHeading; - _haveHeading = true; - - if (_haveLocation && _position.Accuracy <= _desiredAccuracy) + if (_position.Accuracy <= _desiredAccuracy) { _tcs.TrySetResult(new Position(_position)); StopListening(); @@ -130,9 +105,6 @@ namespace eShopOnContainers.iOS.Services private void StopListening() { - if (CLLocationManager.HeadingAvailable) - _manager.StopUpdatingHeading(); - _manager.StopUpdatingLocation(); } } diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs index 12bef0775..6e77dc7c0 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs @@ -15,12 +15,6 @@ namespace eShopOnContainers.iOS.Services { public class LocationServiceImplementation : ILocationServiceImplementation { - bool _deferringUpdates; - readonly CLLocationManager _manager; - Position _lastPosition; - - public event EventHandler PositionError; - public event EventHandler PositionChanged; public double DesiredAccuracy { get; set; } public bool IsGeolocationAvailable => true; public bool IsGeolocationEnabled @@ -32,21 +26,11 @@ namespace eShopOnContainers.iOS.Services } } - public bool SupportsHeading => CLLocationManager.HeadingAvailable; - public LocationServiceImplementation() { DesiredAccuracy = 100; - //_manager = GetManager(); - //_manager.AuthorizationChanged += OnAuthorizationChanged; - //_manager.Failed += OnFailed; - //_manager.UpdatedLocation += OnUpdatedLocation; - //_manager.UpdatedHeading += OnUpdatedHeading; - //_manager.DeferredUpdatesFinished += OnDeferredUpdatesFinished; } - void OnDeferredUpdatesFinished(object sender, NSErrorEventArgs e) => _deferringUpdates = false; - #region Internal Implementation async Task CheckPermissions(Permission permission) @@ -78,7 +62,7 @@ namespace eShopOnContainers.iOS.Services #region ILocationServiceImplementation - public async Task GetPositionAsync(TimeSpan? timeout, CancellationToken? cancelToken = null, bool includeHeading = false) + public async Task GetPositionAsync(TimeSpan? timeout, CancellationToken? cancelToken = null) { var permission = Permission.LocationWhenInUse; var hasPermission = await CheckPermissions(permission); @@ -96,53 +80,16 @@ namespace eShopOnContainers.iOS.Services var manager = GetManager(); manager.DesiredAccuracy = DesiredAccuracy; - // Permit background updates if background location mode is enabled - if (UIDevice.CurrentDevice.CheckSystemVersion(9, 0)) - { - var backgroundModes = NSBundle.MainBundle.InfoDictionary[(NSString)"UIBackgroundModes"] as NSArray; - manager.AllowsBackgroundLocationUpdates = backgroundModes != null && (backgroundModes.Contains((NSString)"Location") || backgroundModes.Contains((NSString)"location")); - } - // Always prevent location update pausing since we're only listening for a single update if (UIDevice.CurrentDevice.CheckSystemVersion(6, 0)) manager.PausesLocationUpdatesAutomatically = false; tcs = new TaskCompletionSource(manager); - var singleListener = new GeolocationSingleUpdateDelegate(manager, DesiredAccuracy, includeHeading, timeoutMilliseconds, cancelToken.Value); + var singleListener = new GeolocationSingleUpdateDelegate(manager, DesiredAccuracy, timeoutMilliseconds, cancelToken.Value); manager.Delegate = singleListener; manager.StartUpdatingLocation(); - if (includeHeading && SupportsHeading) - manager.StartUpdatingHeading(); - return await singleListener.Task; - - //tcs = new TaskCompletionSource(); - //if (_lastPosition == null) - //{ - // if (cancelToken != CancellationToken.None) - // cancelToken.Value.Register(() => tcs.TrySetCanceled()); - - // EventHandler gotError = null; - // gotError = (s, e) => - // { - // tcs.TrySetException(new GeolocationException(e.Error)); - // PositionError -= gotError; - // }; - // PositionError += gotError; - - // EventHandler gotPosition = null; - // gotPosition = (s, e) => - // { - // tcs.TrySetResult(e.Position); - // PositionChanged += gotPosition; - // }; - // PositionChanged += gotPosition; - //} - //else - // tcs.SetResult(_lastPosition); - - //return await tcs.Task; } #endregion diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs index 6f87b60dc..8d21757dc 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs @@ -19,7 +19,6 @@ namespace eShopOnContainers.iOS.Services return PermissionStatus.Disabled; var status = CLLocationManager.Status; - if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0)) { switch (status) @@ -77,22 +76,6 @@ namespace eShopOnContainers.iOS.Services locationManager.AuthorizationChanged += authCallback; var info = NSBundle.MainBundle.InfoDictionary; - //if (permission == Permission.Location) - //{ - // if (info.ContainsKey(new NSString("NSLocationAlwaysUsageDescription"))) - // locationManager.RequestAlwaysAuthorization(); - // else if (info.ContainsKey(new NSString("NSLocationWhenInUseUsageDescription"))) - // locationManager.RequestWhenInUseAuthorization(); - // else - // throw new UnauthorizedAccessException("On iOS 8.0 and higher you must set either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription in your Info.plist file to enable Authorization Requests for Location updates!"); - //} - //else if (permission == Permission.LocationAlways) - //{ - // if (info.ContainsKey(new NSString("NSLocationAlwaysUsageDescription"))) - // locationManager.RequestAlwaysAuthorization(); - // else - // throw new UnauthorizedAccessException("On iOS 8.0 and higher you must set either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription in your Info.plist file to enable Authorization Requests for Location updates!"); - //} if (permission == Permission.LocationWhenInUse) { if (info.ContainsKey(new NSString("NSLocationWhenInUseUsageDescription"))) From d412a00a3ccd366a98c4f4696d442bc00e159601 Mon Sep 17 00:00:00 2001 From: David Britch Date: Wed, 21 Feb 2018 09:37:07 +0000 Subject: [PATCH 06/13] Location plugins removed from test runners. --- .../MainApplication.cs | 12 +++++++----- .../eShopOnContainers.TestRunner.Droid.csproj | 15 --------------- .../packages.config | 3 --- .../eShopOnContainers.TestRunner.iOS.csproj | 6 ------ .../packages.config | 1 - 5 files changed, 7 insertions(+), 30 deletions(-) diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.Droid/MainApplication.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.Droid/MainApplication.cs index f950dba1f..5605b7ff6 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.Droid/MainApplication.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.Droid/MainApplication.cs @@ -1,7 +1,7 @@ using Android.App; +using Android.Content; using Android.OS; using Android.Runtime; -using Plugin.CurrentActivity; using System; namespace eShopOnContainers.TestRunner.Droid @@ -10,8 +10,10 @@ namespace eShopOnContainers.TestRunner.Droid [Application] public class MainApplication : Application, Application.IActivityLifecycleCallbacks { + internal static Context CurrentContext { get; private set; } + public MainApplication(IntPtr handle, JniHandleOwnership transer) - :base(handle, transer) + : base(handle, transer) { } @@ -30,7 +32,7 @@ namespace eShopOnContainers.TestRunner.Droid public void OnActivityCreated(Activity activity, Bundle savedInstanceState) { - CrossCurrentActivity.Current.Activity = activity; + CurrentContext = activity; } public void OnActivityDestroyed(Activity activity) @@ -43,7 +45,7 @@ namespace eShopOnContainers.TestRunner.Droid public void OnActivityResumed(Activity activity) { - CrossCurrentActivity.Current.Activity = activity; + CurrentContext = activity; } public void OnActivitySaveInstanceState(Activity activity, Bundle outState) @@ -52,7 +54,7 @@ namespace eShopOnContainers.TestRunner.Droid public void OnActivityStarted(Activity activity) { - CrossCurrentActivity.Current.Activity = activity; + CurrentContext = activity; } public void OnActivityStopped(Activity activity) diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.Droid/eShopOnContainers.TestRunner.Droid.csproj b/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.Droid/eShopOnContainers.TestRunner.Droid.csproj index b9d4ac2ba..fee481b8a 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.Droid/eShopOnContainers.TestRunner.Droid.csproj +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.Droid/eShopOnContainers.TestRunner.Droid.csproj @@ -52,12 +52,6 @@ - - ..\..\..\..\packages\Plugin.Permissions.2.2.1\lib\MonoAndroid10\Plugin.Permissions.dll - - - ..\..\..\..\packages\Plugin.Permissions.2.2.1\lib\MonoAndroid10\Plugin.Permissions.Abstractions.dll - @@ -157,15 +151,6 @@ ..\..\..\..\packages\Xam.Plugins.Settings.3.1.1\lib\MonoAndroid10\Plugin.Settings.dll - - ..\..\..\..\packages\Plugin.CurrentActivity.1.0.1\lib\MonoAndroid10\Plugin.CurrentActivity.dll - - - ..\..\..\..\packages\Xam.Plugin.Geolocator.3.0.4\lib\MonoAndroid10\Plugin.Geolocator.Abstractions.dll - - - ..\..\..\..\packages\Xam.Plugin.Geolocator.3.0.4\lib\MonoAndroid10\Plugin.Geolocator.dll - ..\..\..\..\packages\PInvoke.Windows.Core.0.3.2\lib\portable-net45+win+wpa81+MonoAndroid10+xamarinios10+MonoTouch10\PInvoke.Windows.Core.dll diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.Droid/packages.config b/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.Droid/packages.config index f593b5b94..c487853a8 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.Droid/packages.config +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.Droid/packages.config @@ -14,8 +14,6 @@ - - @@ -66,7 +64,6 @@ - diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.iOS/eShopOnContainers.TestRunner.iOS.csproj b/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.iOS/eShopOnContainers.TestRunner.iOS.csproj index 9f945a929..4a83a29a8 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.iOS/eShopOnContainers.TestRunner.iOS.csproj +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.iOS/eShopOnContainers.TestRunner.iOS.csproj @@ -149,12 +149,6 @@ ..\..\..\..\packages\Newtonsoft.Json.10.0.3\lib\netstandard1.3\Newtonsoft.Json.dll - - ..\..\..\..\packages\Xam.Plugin.Geolocator.3.0.4\lib\Xamarin.iOS10\Plugin.Geolocator.Abstractions.dll - - - ..\..\..\..\packages\Xam.Plugin.Geolocator.3.0.4\lib\Xamarin.iOS10\Plugin.Geolocator.dll - ..\..\..\..\packages\SlideOverKit.2.1.5\lib\Xamarin.iOS10\SlideOverKit.dll diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.iOS/packages.config b/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.iOS/packages.config index c34af2dfc..6bbbed083 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.iOS/packages.config +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.iOS/packages.config @@ -65,7 +65,6 @@ - From 7a7d8a87a1a1281aac18daba3ac271d20123605a Mon Sep 17 00:00:00 2001 From: David Britch Date: Wed, 21 Feb 2018 10:01:24 +0000 Subject: [PATCH 07/13] Using statements sorted. --- .../ILocationServiceImplementation.cs | 4 +- .../Services/GeolocationSingleListener.cs | 10 ++-- .../Services/LocationServiceImplementation.cs | 16 +++--- .../Services/PermissionsService.cs | 14 ++--- .../Services/LocationServiceImplementation.cs | 54 +++---------------- .../GeolocationSingleUpdateDelegate.cs | 9 ++-- .../Services/LocationServiceImplementation.cs | 12 ++--- .../Services/PermissionsService.cs | 10 ++-- 8 files changed, 44 insertions(+), 85 deletions(-) diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs index 87fa84d0e..84abd98fc 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs @@ -1,7 +1,7 @@ -using System; +using eShopOnContainers.Core.Models.Location; +using System; using System.Threading; using System.Threading.Tasks; -using eShopOnContainers.Core.Models.Location; namespace eShopOnContainers.Core.Services.Location { diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/GeolocationSingleListener.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/GeolocationSingleListener.cs index 4710daedf..b54899a43 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/GeolocationSingleListener.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/GeolocationSingleListener.cs @@ -1,12 +1,12 @@ -using System; -using System.Threading.Tasks; -using Android.Locations; +using Android.Locations; using Android.OS; -using System.Threading; -using System.Collections.Generic; using Android.Runtime; using eShopOnContainers.Core.Models.Location; using eShopOnContainers.Droid.Extensions; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace eShopOnContainers.Droid.Services { diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs index b29e6ff85..f44527741 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs @@ -1,17 +1,17 @@ using Android.App; using Android.Content; -using eShopOnContainers.Droid.Services; -using System; -using eShopOnContainers.Core.Services.Location; +using Android.Locations; +using Android.OS; using eShopOnContainers.Core.Models.Location; using eShopOnContainers.Core.Models.Permissions; +using eShopOnContainers.Core.Services.Location; +using eShopOnContainers.Droid.Activities; +using eShopOnContainers.Droid.Services; +using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; -using Android.Locations; -using System.Linq; -using System.Collections.Generic; -using Android.OS; -using eShopOnContainers.Droid.Activities; [assembly: Xamarin.Forms.Dependency(typeof(LocationServiceImplementation))] namespace eShopOnContainers.Droid.Services diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs index 08399a331..a9b1a0845 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs @@ -1,14 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using eShopOnContainers.Core.Models.Permissions; -using eShopOnContainers.Core.Services.Permissions; -using System.Linq; -using Android; +using Android; using Android.App; using Android.Support.V4.App; using Android.Support.V4.Content; +using eShopOnContainers.Core.Models.Permissions; +using eShopOnContainers.Core.Services.Permissions; +using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; namespace eShopOnContainers.Droid.Services { diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Services/LocationServiceImplementation.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Services/LocationServiceImplementation.cs index 27749744a..016cf5c3c 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Services/LocationServiceImplementation.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Services/LocationServiceImplementation.cs @@ -21,24 +21,6 @@ namespace eShopOnContainers.Windows.Services #region Internal Implementation - Geolocator GetGeolocator() - { - var loc = _locator; - if (loc == null) - { - _locator = new Geolocator(); - _locator.StatusChanged += OnLocatorStatusChanged; - loc = _locator; - } - return loc; - } - - PositionStatus GetGeolocatorStatus() - { - var loc = GetGeolocator(); - return loc.LocationStatus; - } - static Position GetPosition(Geoposition position) { var pos = new Position @@ -60,24 +42,6 @@ namespace eShopOnContainers.Windows.Services return pos; } - async void OnLocatorStatusChanged(Geolocator sender, StatusChangedEventArgs e) - { - GeolocationError error; - - switch (e.Status) - { - case PositionStatus.Disabled: - error = GeolocationError.Unauthorized; - break; - case PositionStatus.NoData: - error = GeolocationError.PositionUnavailable; - break; - default: - return; - } - _locator = null; - } - #endregion #region ILocationServiceImplementation @@ -88,22 +52,19 @@ namespace eShopOnContainers.Windows.Services set { _desiredAccuracy = value; - GetGeolocator().DesiredAccuracy = (value < 100) ? PositionAccuracy.High : PositionAccuracy.Default; + _locator.DesiredAccuracy = (value < 100) ? PositionAccuracy.High : PositionAccuracy.Default; } } - public event EventHandler PositionError; - public event EventHandler PositionChanged; - public bool IsGeolocationAvailable { get { - var status = GetGeolocatorStatus(); + var status = _locator.LocationStatus; while (status == PositionStatus.Initializing) { Task.Delay(10).Wait(); - status = GetGeolocatorStatus(); + status = _locator.LocationStatus; } return status != PositionStatus.NotAvailable; } @@ -113,17 +74,17 @@ namespace eShopOnContainers.Windows.Services { get { - var status = GetGeolocatorStatus(); + var status = _locator.LocationStatus; while (status == PositionStatus.Initializing) { Task.Delay(10).Wait(); - status = GetGeolocatorStatus(); + status = _locator.LocationStatus; } return status != PositionStatus.Disabled && status != PositionStatus.NotAvailable; } } - public Task GetPositionAsync(TimeSpan? timeout = null, CancellationToken? cancelToken = null, bool includeHeading = false) + public Task GetPositionAsync(TimeSpan? timeout = null, CancellationToken? cancelToken = null) { var timeoutMilliseconds = timeout.HasValue ? (int)timeout.Value.TotalMilliseconds : eShopOnContainers.Windows.Helpers.Timeout.Infinite; if (timeoutMilliseconds < 0 && timeoutMilliseconds != eShopOnContainers.Windows.Helpers.Timeout.Infinite) @@ -132,7 +93,7 @@ namespace eShopOnContainers.Windows.Services if (!cancelToken.HasValue) cancelToken = CancellationToken.None; - var pos = GetGeolocator().GetGeopositionAsync(TimeSpan.FromTicks(0), TimeSpan.FromDays(365)); + var pos = _locator.GetGeopositionAsync(TimeSpan.FromTicks(0), TimeSpan.FromDays(365)); cancelToken.Value.Register(o => ((IAsyncOperation)o).Cancel(), pos); var timer = new eShopOnContainers.Windows.Helpers.Timeout(timeoutMilliseconds, pos.Cancel); var tcs = new TaskCompletionSource(); @@ -140,7 +101,6 @@ namespace eShopOnContainers.Windows.Services pos.Completed = (op, s) => { timer.Cancel(); - switch (s) { case AsyncStatus.Canceled: diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/GeolocationSingleUpdateDelegate.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/GeolocationSingleUpdateDelegate.cs index 980aa8a55..dd8380b97 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/GeolocationSingleUpdateDelegate.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/GeolocationSingleUpdateDelegate.cs @@ -1,10 +1,9 @@ -using System; -using CoreLocation; +using CoreLocation; +using eShopOnContainers.Core.Models.Location; using Foundation; -using System.Threading.Tasks; +using System; using System.Threading; -using System.Linq; -using eShopOnContainers.Core.Models.Location; +using System.Threading.Tasks; namespace eShopOnContainers.iOS.Services { diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs index 6e77dc7c0..babd6e45d 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs @@ -1,14 +1,14 @@ -using eShopOnContainers.iOS.Services; -using eShopOnContainers.Core.Services.Location; -using CoreLocation; +using CoreLocation; using eShopOnContainers.Core.Models.Location; +using eShopOnContainers.Core.Models.Permissions; +using eShopOnContainers.Core.Services.Location; +using eShopOnContainers.Core.Services.Permissions; +using eShopOnContainers.iOS.Services; using Foundation; using System; -using System.Threading.Tasks; using System.Threading; +using System.Threading.Tasks; using UIKit; -using eShopOnContainers.Core.Models.Permissions; -using eShopOnContainers.Core.Services.Permissions; [assembly: Xamarin.Forms.Dependency(typeof(LocationServiceImplementation))] namespace eShopOnContainers.iOS.Services diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs index 8d21757dc..8392bd1df 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs @@ -1,11 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; +using CoreLocation; using eShopOnContainers.Core.Models.Permissions; using eShopOnContainers.Core.Services.Permissions; -using CoreLocation; -using UIKit; using Foundation; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using UIKit; namespace eShopOnContainers.iOS.Services { From 2e87eacded3dc5adfb2448ab60e45c258dd4577e Mon Sep 17 00:00:00 2001 From: David Britch Date: Wed, 21 Feb 2018 10:13:41 +0000 Subject: [PATCH 08/13] Windows LocationService fixed. --- .../eShopOnContainers.Core/ViewModels/SettingsViewModel.cs | 3 +-- .../Services/LocationServiceImplementation.cs | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/SettingsViewModel.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/SettingsViewModel.cs index 8e86847b7..1969880cd 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/SettingsViewModel.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/SettingsViewModel.cs @@ -309,8 +309,7 @@ namespace eShopOnContainers.Core.ViewModels } } - - + private void UpdateUseAzureServices() { // Save use mocks services to local storage diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Services/LocationServiceImplementation.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Services/LocationServiceImplementation.cs index 016cf5c3c..db20dfbef 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Services/LocationServiceImplementation.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Services/LocationServiceImplementation.cs @@ -1,12 +1,13 @@ using eShopOnContainers.Core.Models.Location; using eShopOnContainers.Core.Services.Location; +using eShopOnContainers.Windows.Services; using System; using System.Threading; using System.Threading.Tasks; using Windows.Devices.Geolocation; using Windows.Foundation; -[assembly: Xamarin.Forms.Dependency(typeof(ILocationServiceImplementation))] +[assembly: Xamarin.Forms.Dependency(typeof(LocationServiceImplementation))] namespace eShopOnContainers.Windows.Services { public class LocationServiceImplementation : ILocationServiceImplementation From 0f0a7fb698476cf3b48c930061949582a7c8173a Mon Sep 17 00:00:00 2001 From: David Britch Date: Wed, 21 Feb 2018 14:29:19 +0000 Subject: [PATCH 09/13] App now gracefully handles the location service being turned off in the Windows OS. --- .../eShopOnContainers.Core/App.xaml.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs index cc8b4cc58..bd41c96aa 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs @@ -1,13 +1,15 @@ using eShopOnContainers.Core.Models.Location; +using eShopOnContainers.Core.Services.Dependency; using eShopOnContainers.Core.Services.Location; using eShopOnContainers.Core.Services.Settings; using eShopOnContainers.Core.ViewModels.Base; using eShopOnContainers.Services; +using System; +using System.Diagnostics; using System.Globalization; using System.Threading.Tasks; using Xamarin.Forms; using Xamarin.Forms.Xaml; -using eShopOnContainers.Core.Services.Dependency; [assembly: XamlCompilation(XamlCompilationOptions.Compile)] namespace eShopOnContainers @@ -76,10 +78,17 @@ namespace eShopOnContainers // Delay getting the position to ensure that the UI has finished updating await Task.Delay(2000); - var position = await locator.GetPositionAsync(); + try + { + var position = await locator.GetPositionAsync(); - _settingsService.Latitude = position.Latitude.ToString(); - _settingsService.Longitude = position.Longitude.ToString(); + _settingsService.Latitude = position.Latitude.ToString(); + _settingsService.Longitude = position.Longitude.ToString(); + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } } else { From a43086fe2cc4adcee3f8901ae3aed292eb98594d Mon Sep 17 00:00:00 2001 From: David Britch Date: Wed, 21 Feb 2018 15:59:56 +0000 Subject: [PATCH 10/13] Removed Task.Delay test. --- src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs index bd41c96aa..adecba788 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs @@ -76,8 +76,6 @@ namespace eShopOnContainers { locator.DesiredAccuracy = 50; - // Delay getting the position to ensure that the UI has finished updating - await Task.Delay(2000); try { var position = await locator.GetPositionAsync(); From 32c85b327f90a0ddb43bbb7e6bc22746ff570604 Mon Sep 17 00:00:00 2001 From: David Britch Date: Wed, 21 Feb 2018 16:52:27 +0000 Subject: [PATCH 11/13] iOS LocationService threading issues resolved. --- .../Services/LocationServiceImplementation.cs | 5 ++-- .../Services/PermissionsService.cs | 24 +++++++++++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs index babd6e45d..abd85b17d 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs @@ -35,13 +35,12 @@ namespace eShopOnContainers.iOS.Services async Task CheckPermissions(Permission permission) { - IPermissionsService permissionsService = new PermissionsService(); - var status = await permissionsService.CheckPermissionStatusAsync(permission); + var status = await PermissionsService.Current.CheckPermissionStatusAsync(permission); if (status != PermissionStatus.Granted) { Console.WriteLine("Currently do not have Location permissions, requesting permissions"); - var request = await permissionsService.RequestPermissionsAsync(permission); + var request = await PermissionsService.Current.RequestPermissionsAsync(permission); if (request[permission] != PermissionStatus.Granted) { Console.WriteLine("Location permission denied, can not get positions async."); diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs index 8392bd1df..2b7ce5c43 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs @@ -11,6 +11,22 @@ namespace eShopOnContainers.iOS.Services { public class PermissionsService : IPermissionsService { + CLLocationManager _locationManager; + + static Lazy implementation = new Lazy(CreatePermissions, System.Threading.LazyThreadSafetyMode.PublicationOnly); + static IPermissionsService CreatePermissions() + { + return new PermissionsService(); + } + + public static IPermissionsService Current + { + get + { + return implementation.Value; + } + } + #region Internal Implementation PermissionStatus GetLocationPermissionStatus(Permission permission) @@ -64,22 +80,22 @@ namespace eShopOnContainers.iOS.Services EventHandler authCallback = null; var tcs = new TaskCompletionSource(); - var locationManager = new CLLocationManager(); + _locationManager = new CLLocationManager(); authCallback = (sender, e) => { if (e.Status == CLAuthorizationStatus.NotDetermined) return; - locationManager.AuthorizationChanged -= authCallback; + _locationManager.AuthorizationChanged -= authCallback; tcs.TrySetResult(GetLocationPermissionStatus(permission)); }; - locationManager.AuthorizationChanged += authCallback; + _locationManager.AuthorizationChanged += authCallback; var info = NSBundle.MainBundle.InfoDictionary; if (permission == Permission.LocationWhenInUse) { if (info.ContainsKey(new NSString("NSLocationWhenInUseUsageDescription"))) - locationManager.RequestWhenInUseAuthorization(); + _locationManager.RequestWhenInUseAuthorization(); else throw new UnauthorizedAccessException("On iOS 8.0 and higher you must set either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription in your Info.plist file to enable Authorization Requests for Location updates."); } From 01cf30ebb8317f4944c110e1573e7ad3750168f9 Mon Sep 17 00:00:00 2001 From: David Britch Date: Wed, 21 Feb 2018 17:11:17 +0000 Subject: [PATCH 12/13] iOS LocationService thread safety architecture changes. --- .../Services/LocationServiceImplementation.cs | 27 ++++++++++--------- .../Services/PermissionsService.cs | 14 ---------- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs index abd85b17d..ee7aabed4 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs @@ -15,32 +15,24 @@ namespace eShopOnContainers.iOS.Services { public class LocationServiceImplementation : ILocationServiceImplementation { - public double DesiredAccuracy { get; set; } - public bool IsGeolocationAvailable => true; - public bool IsGeolocationEnabled - { - get - { - var status = CLLocationManager.Status; - return CLLocationManager.LocationServicesEnabled; - } - } + Lazy _permissionsService; public LocationServiceImplementation() { DesiredAccuracy = 100; + _permissionsService = new Lazy(() => new PermissionsService(), LazyThreadSafetyMode.PublicationOnly); } #region Internal Implementation async Task CheckPermissions(Permission permission) { - var status = await PermissionsService.Current.CheckPermissionStatusAsync(permission); + var status = await _permissionsService.Value.CheckPermissionStatusAsync(permission); if (status != PermissionStatus.Granted) { Console.WriteLine("Currently do not have Location permissions, requesting permissions"); - var request = await PermissionsService.Current.RequestPermissionsAsync(permission); + var request = await _permissionsService.Value.RequestPermissionsAsync(permission); if (request[permission] != PermissionStatus.Granted) { Console.WriteLine("Location permission denied, can not get positions async."); @@ -61,6 +53,17 @@ namespace eShopOnContainers.iOS.Services #region ILocationServiceImplementation + public double DesiredAccuracy { get; set; } + public bool IsGeolocationAvailable => true; + public bool IsGeolocationEnabled + { + get + { + var status = CLLocationManager.Status; + return CLLocationManager.LocationServicesEnabled; + } + } + public async Task GetPositionAsync(TimeSpan? timeout, CancellationToken? cancelToken = null) { var permission = Permission.LocationWhenInUse; diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs index 2b7ce5c43..d0d3e06c8 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs @@ -13,20 +13,6 @@ namespace eShopOnContainers.iOS.Services { CLLocationManager _locationManager; - static Lazy implementation = new Lazy(CreatePermissions, System.Threading.LazyThreadSafetyMode.PublicationOnly); - static IPermissionsService CreatePermissions() - { - return new PermissionsService(); - } - - public static IPermissionsService Current - { - get - { - return implementation.Value; - } - } - #region Internal Implementation PermissionStatus GetLocationPermissionStatus(Permission permission) From 6e7a0ace16a6da5080770a6ede8b5bc2ee3df50d Mon Sep 17 00:00:00 2001 From: David Britch Date: Wed, 21 Feb 2018 17:31:51 +0000 Subject: [PATCH 13/13] LocationService threading issues fixed on Android. --- .../eShopOnContainers/eShopOnContainers.Core/App.xaml.cs | 1 - .../eShopOnContainers.Droid/Activities/MainActivity.cs | 5 +---- .../Services/LocationServiceImplementation.cs | 5 ++--- .../eShopOnContainers.Droid/Services/PermissionsService.cs | 3 +++ 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs index adecba788..207d076f9 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs @@ -79,7 +79,6 @@ namespace eShopOnContainers try { var position = await locator.GetPositionAsync(); - _settingsService.Latitude = position.Latitude.ToString(); _settingsService.Longitude = position.Longitude.ToString(); } diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs index 4cb0bc83f..c6f49b090 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs @@ -10,7 +10,6 @@ using FFImageLoading.Forms.Droid; using System; using Xamarin.Forms.Platform.Android; using eShopOnContainers.Droid.Services; -using eShopOnContainers.Core.Services.Permissions; namespace eShopOnContainers.Droid.Activities { @@ -21,8 +20,6 @@ namespace eShopOnContainers.Droid.Activities ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] public class MainActivity : FormsAppCompatActivity { - public static IPermissionsService PermissionsService = new PermissionsService(); - protected override void OnCreate(Bundle bundle) { FormsAppCompatActivity.ToolbarResource = Resource.Layout.Toolbar; @@ -60,7 +57,7 @@ namespace eShopOnContainers.Droid.Activities public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) { base.OnRequestPermissionsResult(requestCode, permissions, grantResults); - ((PermissionsService)PermissionsService).OnRequestPermissionResult(requestCode, permissions, grantResults); + ((PermissionsService)PermissionsService.Current.Value).OnRequestPermissionResult(requestCode, permissions, grantResults); } } } diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs index f44527741..7d6f5f40f 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs @@ -5,7 +5,6 @@ using Android.OS; using eShopOnContainers.Core.Models.Location; using eShopOnContainers.Core.Models.Permissions; using eShopOnContainers.Core.Services.Location; -using eShopOnContainers.Droid.Activities; using eShopOnContainers.Droid.Services; using System; using System.Collections.Generic; @@ -45,12 +44,12 @@ namespace eShopOnContainers.Droid.Services async Task CheckPermissionsAsync() { - var status = await MainActivity.PermissionsService.CheckPermissionStatusAsync(Permission.Location); + var status = await PermissionsService.Current.Value.CheckPermissionStatusAsync(Permission.Location); if (status != PermissionStatus.Granted) { Console.WriteLine("Currently do not have Location permissions, requesting permissions."); - var request = await MainActivity.PermissionsService.RequestPermissionsAsync(Permission.Location); + var request = await PermissionsService.Current.Value.RequestPermissionsAsync(Permission.Location); if (request[Permission.Location] != PermissionStatus.Granted) { Console.WriteLine("Location permission denied."); diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs index a9b1a0845..c1fbd2f98 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace eShopOnContainers.Droid.Services @@ -20,6 +21,8 @@ namespace eShopOnContainers.Droid.Services Dictionary _results; IList _requestedPermissions; + public static Lazy Current = new Lazy(() => new PermissionsService(), LazyThreadSafetyMode.PublicationOnly); + #region Internal Implementation List GetManifestNames(Permission permission)