Browse Source

Merge pull request #521 from dotnet-architecture/xamarin-location

Removed geolocation plugin from mobile apps
pull/522/head
David Britch 7 years ago
committed by GitHub
parent
commit
f49518dc31
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1285 additions and 103 deletions
  1. +15
    -9
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs
  2. +8
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/GeolocationError.cs
  3. +27
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/GeolocationException.cs
  4. +43
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/Position.cs
  5. +10
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Permissions/Permission.cs
  6. +11
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Permissions/PermissionStatus.cs
  7. +6
    -6
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Basket/BasketMockService.cs
  8. +10
    -10
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Catalog/CatalogMockService.cs
  9. +16
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs
  10. +0
    -3
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/LocationService.cs
  11. +2
    -2
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Marketing/CampaignMockService.cs
  12. +3
    -3
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderMockService.cs
  13. +12
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Permissions/IPermissionsService.cs
  14. +1
    -1
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/User/UserMockService.cs
  15. +2
    -2
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/LoginViewModel.cs
  16. +6
    -5
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/SettingsViewModel.cs
  17. +4
    -1
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj
  18. +2
    -2
      src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs
  19. +84
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Extensions/LocationExtensions.cs
  20. +6
    -4
      src/Mobile/eShopOnContainers/eShopOnContainers.Droid/MainApplication.cs
  21. +110
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/GeolocationSingleListener.cs
  22. +152
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs
  23. +242
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs
  24. +4
    -15
      src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj
  25. +0
    -3
      src/Mobile/eShopOnContainers/eShopOnContainers.Droid/packages.config
  26. +7
    -5
      src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.Droid/MainApplication.cs
  27. +0
    -15
      src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.Droid/eShopOnContainers.TestRunner.Droid.csproj
  28. +0
    -3
      src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.Droid/packages.config
  29. +0
    -6
      src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.iOS/eShopOnContainers.TestRunner.iOS.csproj
  30. +0
    -1
      src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.iOS/packages.config
  31. +33
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Helpers/Timeout.cs
  32. +127
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Services/LocationServiceImplementation.cs
  33. +2
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Windows/eShopOnContainers.Windows.csproj
  34. +110
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/GeolocationSingleUpdateDelegate.cs
  35. +99
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs
  36. +128
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs
  37. +3
    -6
      src/Mobile/eShopOnContainers/eShopOnContainers.iOS/eShopOnContainers.iOS.csproj
  38. +0
    -1
      src/Mobile/eShopOnContainers/eShopOnContainers.iOS/packages.config

+ 15
- 9
src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs View File

@ -1,9 +1,11 @@
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 Plugin.Geolocator;
using System;
using System.Diagnostics;
using System.Globalization;
using System.Threading.Tasks;
using Xamarin.Forms;
@ -48,12 +50,10 @@ namespace eShopOnContainers
{
await InitNavigation();
}
if (_settingsService.AllowGpsLocation && !_settingsService.UseFakeLocation)
{
await GetGpsLocation();
}
if (!_settingsService.UseMocks && !string.IsNullOrEmpty(_settingsService.AuthAccessToken))
{
await SendCurrentLocation();
@ -69,17 +69,23 @@ namespace eShopOnContainers
private async Task GetGpsLocation()
{
var locator = CrossGeolocator.Current;
var dependencyService = ViewModelLocator.Resolve<IDependencyService>();
var locator = dependencyService.Get<ILocationServiceImplementation>();
if (locator.IsGeolocationEnabled && locator.IsGeolocationAvailable)
{
locator.AllowsBackgroundUpdates = true;
locator.DesiredAccuracy = 50;
var position = await locator.GetPositionAsync();
_settingsService.Latitude = position.Latitude.ToString();
_settingsService.Longitude = position.Longitude.ToString();
try
{
var position = await locator.GetPositionAsync();
_settingsService.Latitude = position.Latitude.ToString();
_settingsService.Longitude = position.Longitude.ToString();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
else
{


+ 8
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/GeolocationError.cs View File

@ -0,0 +1,8 @@
namespace eShopOnContainers.Core.Models.Location
{
public enum GeolocationError
{
PositionUnavailable,
Unauthorized
}
}

+ 27
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/GeolocationException.cs View File

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

+ 43
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/Position.cs View File

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

+ 10
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Permissions/Permission.cs View File

@ -0,0 +1,10 @@
namespace eShopOnContainers.Core.Models.Permissions
{
public enum Permission
{
Unknown,
Location,
LocationAlways,
LocationWhenInUse
}
}

+ 11
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Permissions/PermissionStatus.cs View File

@ -0,0 +1,11 @@
namespace eShopOnContainers.Core.Models.Permissions
{
public enum PermissionStatus
{
Denied,
Disabled,
Granted,
Restricted,
Unknown
}
}

+ 6
- 6
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Basket/BasketMockService.cs View File

@ -13,16 +13,16 @@ namespace eShopOnContainers.Core.Services.Basket
BuyerId = "9245fe4a-d402-451c-b9ed-9c1a04247482",
Items = new List<BasketItem>
{
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<CustomerBasket> 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<CustomerBasket> 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))
{


+ 10
- 10
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Catalog/CatalogMockService.cs View File

@ -24,40 +24,40 @@ namespace eShopOnContainers.Core.Services.Catalog
private ObservableCollection<CatalogItem> MockCatalog = new ObservableCollection<CatalogItem>
{
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<ObservableCollection<CatalogItem>> GetCatalogAsync()
{
await Task.Delay(500);
await Task.Delay(10);
return MockCatalog;
}
public async Task<ObservableCollection<CatalogItem>> 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<ObservableCollection<CatalogBrand>> GetCatalogBrandAsync()
{
await Task.Delay(500);
await Task.Delay(10);
return MockCatalogBrand;
}
public async Task<ObservableCollection<CatalogType>> GetCatalogTypeAsync()
{
await Task.Delay(500);
await Task.Delay(10);
return MockCatalogType;
}


+ 16
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs View File

@ -0,0 +1,16 @@
using eShopOnContainers.Core.Models.Location;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace eShopOnContainers.Core.Services.Location
{
public interface ILocationServiceImplementation
{
double DesiredAccuracy { get; set; }
bool IsGeolocationAvailable { get; }
bool IsGeolocationEnabled { get; }
Task<Position> GetPositionAsync(TimeSpan? timeout = null, CancellationToken? token = null);
}
}

+ 0
- 3
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/LocationService.cs View File

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

+ 2
- 2
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Marketing/CampaignMockService.cs View File

@ -38,13 +38,13 @@ namespace eShopOnContainers.Core.Services.Marketing
public async Task<ObservableCollection<CampaignItem>> GetAllCampaignsAsync(string token)
{
await Task.Delay(500);
await Task.Delay(10);
return _mockCampaign;
}
public async Task<CampaignItem> GetCampaignByIdAsync(int campaignId, string token)
{
await Task.Delay(500);
await Task.Delay(10);
return _mockCampaign.SingleOrDefault(c => c.Id == campaignId);
}
}

+ 3
- 3
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderMockService.cs View File

@ -74,7 +74,7 @@ namespace eShopOnContainers.Core.Services.Order
public async Task<ObservableCollection<Models.Orders.Order>> 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<Models.Orders.Order> 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))
{


+ 12
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Permissions/IPermissionsService.cs View File

@ -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<PermissionStatus> CheckPermissionStatusAsync(Permission permission);
Task<Dictionary<Permission, PermissionStatus>> RequestPermissionsAsync(params Permission[] permissions);
}
}

+ 1
- 1
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/User/UserMockService.cs View File

@ -28,7 +28,7 @@ namespace eShopOnContainers.Core.Services.User
public async Task<UserInfo> GetUserInfoAsync(string authToken)
{
await Task.Delay(500);
await Task.Delay(10);
return MockUserInfo;
}
}

+ 2
- 2
src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/LoginViewModel.cs View File

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


+ 6
- 5
src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/SettingsViewModel.cs View File

@ -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;
@ -307,8 +309,7 @@ namespace eShopOnContainers.Core.ViewModels
}
}
private void UpdateUseAzureServices()
{
// Save use mocks services to local storage
@ -342,7 +343,7 @@ namespace eShopOnContainers.Core.ViewModels
{
if (_allowGpsLocation)
{
var locator = CrossGeolocator.Current;
var locator = _dependencyService.Get<ILocationServiceImplementation>();
if (!locator.IsGeolocationEnabled)
{
_allowGpsLocation = false;


+ 4
- 1
src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj View File

@ -10,9 +10,12 @@
<PackageReference Include="SlideOverKit" Version="2.1.5" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="PCLCrypto" Version="2.0.147" />
<PackageReference Include="Xam.Plugin.Geolocator" Version="3.0.4" />
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.3.4" />
<PackageReference Include="Xamarin.Forms" Version="2.5.0.122203" />
<PackageReference Include="IdentityModel" Version="3.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Services\Permissions\" />
<Folder Include="Models\Permissions\" />
</ItemGroup>
</Project>

+ 2
- 2
src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs View File

@ -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,7 +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)PermissionsService.Current.Value).OnRequestPermissionResult(requestCode, permissions, grantResults);
}
}
}


+ 84
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Extensions/LocationExtensions.cs View File

@ -0,0 +1,84 @@
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);
}
}
}

+ 6
- 4
src/Mobile/eShopOnContainers/eShopOnContainers.Droid/MainApplication.cs View File

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


+ 110
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/GeolocationSingleListener.cs View File

@ -0,0 +1,110 @@
using Android.Locations;
using Android.OS;
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
{
public class GeolocationSingleListener : Java.Lang.Object, ILocationListener
{
readonly object _locationSync = new object();
readonly Action _finishedCallback;
readonly float _desiredAccuracy;
readonly Timer _timer;
readonly TaskCompletionSource<Position> _tcs = new TaskCompletionSource<Position>();
HashSet<string> _activeProviders = new HashSet<string>();
Android.Locations.Location _bestLocation;
public Task<Position> Task => _tcs.Task;
public GeolocationSingleListener(LocationManager manager, float desiredAccuracy, int timeout, IEnumerable<string> activeProviders, Action finishedCallback)
{
_desiredAccuracy = desiredAccuracy;
_finishedCallback = finishedCallback;
_activeProviders = new HashSet<string>(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());
}
}
}

+ 152
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs View File

@ -0,0 +1,152 @@
using Android.App;
using Android.Content;
using Android.Locations;
using Android.OS;
using eShopOnContainers.Core.Models.Location;
using eShopOnContainers.Core.Models.Permissions;
using eShopOnContainers.Core.Services.Location;
using eShopOnContainers.Droid.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
[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<bool> CheckPermissionsAsync()
{
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 PermissionsService.Current.Value.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<Position> GetPositionAsync(TimeSpan? timeout = null, CancellationToken? cancelToken = null)
{
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<Position>();
var providers = new List<string>();
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
}
}

+ 242
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs View File

@ -0,0 +1,242 @@
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;
using System.Threading.Tasks;
namespace eShopOnContainers.Droid.Services
{
public class PermissionsService : IPermissionsService
{
const int _permissionCode = 25;
object _locker = new object();
TaskCompletionSource<Dictionary<Permission, PermissionStatus>> _tcs;
Dictionary<Permission, PermissionStatus> _results;
IList<string> _requestedPermissions;
public static Lazy<IPermissionsService> Current = new Lazy<IPermissionsService>(() => new PermissionsService(), LazyThreadSafetyMode.PublicationOnly);
#region Internal Implementation
List<string> GetManifestNames(Permission permission)
{
var permissionNames = new List<string>();
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;
}
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<PermissionStatus> 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<Dictionary<Permission, PermissionStatus>> RequestPermissionsAsync(params Permission[] permissions)
{
if (_tcs != null && !_tcs.Task.IsCompleted)
{
_tcs.SetCanceled();
_tcs = null;
}
lock (_locker)
{
_results = new Dictionary<Permission, PermissionStatus>();
}
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<string>();
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<Dictionary<Permission, PermissionStatus>>();
ActivityCompat.RequestPermissions((Activity)context, permissionsToRequest.ToArray(), _permissionCode);
return await _tcs.Task.ConfigureAwait(false);
}
#endregion
}
}

+ 4
- 15
src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj View File

@ -145,27 +145,12 @@
<Reference Include="Acr.UserDialogs.Interface">
<HintPath>..\..\..\..\packages\Acr.UserDialogs.6.5.1\lib\MonoAndroid10\Acr.UserDialogs.Interface.dll</HintPath>
</Reference>
<Reference Include="Plugin.CurrentActivity">
<HintPath>..\..\..\..\packages\Plugin.CurrentActivity.1.0.1\lib\MonoAndroid10\Plugin.CurrentActivity.dll</HintPath>
</Reference>
<Reference Include="Plugin.Permissions.Abstractions">
<HintPath>..\..\..\..\packages\Plugin.Permissions.2.2.1\lib\MonoAndroid10\Plugin.Permissions.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Plugin.Permissions">
<HintPath>..\..\..\..\packages\Plugin.Permissions.2.2.1\lib\MonoAndroid10\Plugin.Permissions.dll</HintPath>
</Reference>
<Reference Include="SlideOverKit">
<HintPath>..\..\..\..\packages\SlideOverKit.2.1.5\lib\MonoAndroid10\SlideOverKit.dll</HintPath>
</Reference>
<Reference Include="SlideOverKit.Droid">
<HintPath>..\..\..\..\packages\SlideOverKit.2.1.5\lib\MonoAndroid10\SlideOverKit.Droid.dll</HintPath>
</Reference>
<Reference Include="Plugin.Geolocator.Abstractions">
<HintPath>..\..\..\..\packages\Xam.Plugin.Geolocator.3.0.4\lib\MonoAndroid10\Plugin.Geolocator.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Plugin.Geolocator">
<HintPath>..\..\..\..\packages\Xam.Plugin.Geolocator.3.0.4\lib\MonoAndroid10\Plugin.Geolocator.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http.Extensions">
<HintPath>..\..\..\..\packages\Microsoft.Net.Http.2.2.28\lib\monoandroid\System.Net.Http.Extensions.dll</HintPath>
</Reference>
@ -227,6 +212,10 @@
<Compile Include="Effects\BaseContainerEffect.cs" />
<Compile Include="Activities\SplashActivity.cs" />
<Compile Include="Services\SettingsServiceImplementation.cs" />
<Compile Include="Services\PermissionsService.cs" />
<Compile Include="Services\LocationServiceImplementation.cs" />
<Compile Include="Services\GeolocationSingleListener.cs" />
<Compile Include="Extensions\LocationExtensions.cs" />
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="..\CommonResources\Fonts\Montserrat-Bold.ttf">


+ 0
- 3
src/Mobile/eShopOnContainers/eShopOnContainers.Droid/packages.config View File

@ -17,8 +17,6 @@
<package id="PInvoke.Kernel32" version="0.3.2" targetFramework="monoandroid80" />
<package id="PInvoke.NCrypt" version="0.3.2" targetFramework="monoandroid80" />
<package id="PInvoke.Windows.Core" version="0.3.2" targetFramework="monoandroid80" />
<package id="Plugin.CurrentActivity" version="1.0.1" targetFramework="monoandroid80" />
<package id="Plugin.Permissions" version="2.2.1" targetFramework="monoandroid80" />
<package id="SlideOverKit" version="2.1.5" targetFramework="monoandroid80" />
<package id="Splat" version="2.0.0" targetFramework="monoandroid80" />
<package id="System.AppContext" version="4.3.0" targetFramework="monoandroid80" />
@ -69,7 +67,6 @@
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="monoandroid80" />
<package id="System.Xml.XmlDocument" version="4.3.0" targetFramework="monoandroid80" />
<package id="Validation" version="2.2.8" targetFramework="monoandroid80" />
<package id="Xam.Plugin.Geolocator" version="3.0.4" targetFramework="monoandroid80" />
<package id="Xamarin.Android.Support.Animated.Vector.Drawable" version="25.4.0.2" targetFramework="monoandroid80" />
<package id="Xamarin.Android.Support.Annotations" version="25.4.0.2" targetFramework="monoandroid80" />
<package id="Xamarin.Android.Support.Compat" version="25.4.0.2" targetFramework="monoandroid80" />


+ 7
- 5
src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.Droid/MainApplication.cs View File

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


+ 0
- 15
src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.Droid/eShopOnContainers.TestRunner.Droid.csproj View File

@ -52,12 +52,6 @@
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="mscorlib" />
<Reference Include="Plugin.Permissions, Version=2.2.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\..\..\packages\Plugin.Permissions.2.2.1\lib\MonoAndroid10\Plugin.Permissions.dll</HintPath>
</Reference>
<Reference Include="Plugin.Permissions.Abstractions, Version=2.2.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\..\..\packages\Plugin.Permissions.2.2.1\lib\MonoAndroid10\Plugin.Permissions.Abstractions.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
@ -157,15 +151,6 @@
<Reference Include="Plugin.Settings">
<HintPath>..\..\..\..\packages\Xam.Plugins.Settings.3.1.1\lib\MonoAndroid10\Plugin.Settings.dll</HintPath>
</Reference>
<Reference Include="Plugin.CurrentActivity">
<HintPath>..\..\..\..\packages\Plugin.CurrentActivity.1.0.1\lib\MonoAndroid10\Plugin.CurrentActivity.dll</HintPath>
</Reference>
<Reference Include="Plugin.Geolocator.Abstractions">
<HintPath>..\..\..\..\packages\Xam.Plugin.Geolocator.3.0.4\lib\MonoAndroid10\Plugin.Geolocator.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Plugin.Geolocator">
<HintPath>..\..\..\..\packages\Xam.Plugin.Geolocator.3.0.4\lib\MonoAndroid10\Plugin.Geolocator.dll</HintPath>
</Reference>
<Reference Include="PInvoke.Windows.Core">
<HintPath>..\..\..\..\packages\PInvoke.Windows.Core.0.3.2\lib\portable-net45+win+wpa81+MonoAndroid10+xamarinios10+MonoTouch10\PInvoke.Windows.Core.dll</HintPath>
</Reference>


+ 0
- 3
src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.Droid/packages.config View File

@ -14,8 +14,6 @@
<package id="PInvoke.Kernel32" version="0.3.2" targetFramework="monoandroid80" />
<package id="PInvoke.NCrypt" version="0.3.2" targetFramework="monoandroid80" />
<package id="PInvoke.Windows.Core" version="0.3.2" targetFramework="monoandroid80" />
<package id="Plugin.CurrentActivity" version="1.0.1" targetFramework="monoandroid80" />
<package id="Plugin.Permissions" version="2.2.1" targetFramework="monoandroid80" />
<package id="SlideOverKit" version="2.1.5" targetFramework="monoandroid80" />
<package id="Splat" version="2.0.0" targetFramework="monoandroid80" />
<package id="System.AppContext" version="4.3.0" targetFramework="monoandroid80" />
@ -66,7 +64,6 @@
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="monoandroid80" />
<package id="System.Xml.XmlDocument" version="4.3.0" targetFramework="monoandroid80" />
<package id="Validation" version="2.2.8" targetFramework="monoandroid80" />
<package id="Xam.Plugin.Geolocator" version="3.0.4" targetFramework="monoandroid80" />
<package id="Xamarin.Android.Support.Animated.Vector.Drawable" version="25.4.0.2" targetFramework="monoandroid80" />
<package id="Xamarin.Android.Support.Annotations" version="25.4.0.2" targetFramework="monoandroid80" />
<package id="Xamarin.Android.Support.Compat" version="25.4.0.2" targetFramework="monoandroid80" />


+ 0
- 6
src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.iOS/eShopOnContainers.TestRunner.iOS.csproj View File

@ -149,12 +149,6 @@
<Reference Include="Newtonsoft.Json">
<HintPath>..\..\..\..\packages\Newtonsoft.Json.10.0.3\lib\netstandard1.3\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Plugin.Geolocator.Abstractions">
<HintPath>..\..\..\..\packages\Xam.Plugin.Geolocator.3.0.4\lib\Xamarin.iOS10\Plugin.Geolocator.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Plugin.Geolocator">
<HintPath>..\..\..\..\packages\Xam.Plugin.Geolocator.3.0.4\lib\Xamarin.iOS10\Plugin.Geolocator.dll</HintPath>
</Reference>
<Reference Include="SlideOverKit">
<HintPath>..\..\..\..\packages\SlideOverKit.2.1.5\lib\Xamarin.iOS10\SlideOverKit.dll</HintPath>
</Reference>


+ 0
- 1
src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.iOS/packages.config View File

@ -65,7 +65,6 @@
<package id="System.Xml.XmlDocument" version="4.3.0" targetFramework="xamarinios10" />
<package id="Validation" version="2.2.8" targetFramework="xamarinios10" />
<package id="WebP.Touch" version="1.0.7" targetFramework="xamarinios10" />
<package id="Xam.Plugin.Geolocator" version="3.0.4" targetFramework="xamarinios10" />
<package id="Xamarin.FFImageLoading" version="2.3.4" targetFramework="xamarinios10" />
<package id="Xamarin.FFImageLoading.Forms" version="2.3.4" targetFramework="xamarinios10" />
<package id="Xamarin.Forms" version="2.5.0.122203" targetFramework="xamarinios10" />


+ 33
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Helpers/Timeout.cs View File

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

+ 127
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Services/LocationServiceImplementation.cs View File

@ -0,0 +1,127 @@
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(LocationServiceImplementation))]
namespace eShopOnContainers.Windows.Services
{
public class LocationServiceImplementation : ILocationServiceImplementation
{
double _desiredAccuracy;
Geolocator _locator = new Geolocator();
public LocationServiceImplementation()
{
DesiredAccuracy = 100;
}
#region Internal Implementation
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;
}
#endregion
#region ILocationServiceImplementation
public double DesiredAccuracy
{
get { return _desiredAccuracy; }
set
{
_desiredAccuracy = value;
_locator.DesiredAccuracy = (value < 100) ? PositionAccuracy.High : PositionAccuracy.Default;
}
}
public bool IsGeolocationAvailable
{
get
{
var status = _locator.LocationStatus;
while (status == PositionStatus.Initializing)
{
Task.Delay(10).Wait();
status = _locator.LocationStatus;
}
return status != PositionStatus.NotAvailable;
}
}
public bool IsGeolocationEnabled
{
get
{
var status = _locator.LocationStatus;
while (status == PositionStatus.Initializing)
{
Task.Delay(10).Wait();
status = _locator.LocationStatus;
}
return status != PositionStatus.Disabled && status != PositionStatus.NotAvailable;
}
}
public Task<Position> 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)
throw new ArgumentOutOfRangeException(nameof(timeout));
if (!cancelToken.HasValue)
cancelToken = CancellationToken.None;
var pos = _locator.GetGeopositionAsync(TimeSpan.FromTicks(0), TimeSpan.FromDays(365));
cancelToken.Value.Register(o => ((IAsyncOperation<Geoposition>)o).Cancel(), pos);
var timer = new eShopOnContainers.Windows.Helpers.Timeout(timeoutMilliseconds, pos.Cancel);
var tcs = new TaskCompletionSource<Position>();
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
}
}

+ 2
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Windows/eShopOnContainers.Windows.csproj View File

@ -115,12 +115,14 @@
<Compile Include="Effects\EntryLineColorEffect.cs" />
<Compile Include="Extensions\VisualTreeExtensions.cs" />
<Compile Include="Helpers\ColorHelper.cs" />
<Compile Include="Helpers\Timeout.cs" />
<Compile Include="MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Renderers\CustomTabbedPageRenderer.cs" />
<Compile Include="Renderers\SlideDownMenuPageRenderer.cs" />
<Compile Include="Services\LocationServiceImplementation.cs" />
<Compile Include="Services\SettingsServiceImplementation.cs" />
</ItemGroup>
<ItemGroup>


+ 110
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/GeolocationSingleUpdateDelegate.cs View File

@ -0,0 +1,110 @@
using CoreLocation;
using eShopOnContainers.Core.Models.Location;
using Foundation;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace eShopOnContainers.iOS.Services
{
internal class GeolocationSingleUpdateDelegate : CLLocationManagerDelegate
{
bool _haveLocation;
readonly Position _position = new Position();
readonly double _desiredAccuracy;
readonly TaskCompletionSource<Position> _tcs;
readonly CLLocationManager _manager;
public Task<Position> Task => _tcs?.Task;
public GeolocationSingleUpdateDelegate(CLLocationManager manager, double desiredAccuracy, int timeout, CancellationToken cancelToken)
{
_manager = manager;
_tcs = new TaskCompletionSource<Position>(manager);
_desiredAccuracy = desiredAccuracy;
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 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)
{
_position.Timestamp = DateTimeOffset.UtcNow;
}
_haveLocation = true;
if (_position.Accuracy <= _desiredAccuracy)
{
_tcs.TrySetResult(new Position(_position));
StopListening();
}
}
private void StopListening()
{
_manager.StopUpdatingLocation();
}
}
}

+ 99
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs View File

@ -0,0 +1,99 @@
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;
using System.Threading.Tasks;
using UIKit;
[assembly: Xamarin.Forms.Dependency(typeof(LocationServiceImplementation))]
namespace eShopOnContainers.iOS.Services
{
public class LocationServiceImplementation : ILocationServiceImplementation
{
Lazy<IPermissionsService> _permissionsService;
public LocationServiceImplementation()
{
DesiredAccuracy = 100;
_permissionsService = new Lazy<IPermissionsService>(() => new PermissionsService(), LazyThreadSafetyMode.PublicationOnly);
}
#region Internal Implementation
async Task<bool> CheckPermissions(Permission 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.Value.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 double DesiredAccuracy { get; set; }
public bool IsGeolocationAvailable => true;
public bool IsGeolocationEnabled
{
get
{
var status = CLLocationManager.Status;
return CLLocationManager.LocationServicesEnabled;
}
}
public async Task<Position> GetPositionAsync(TimeSpan? timeout, CancellationToken? cancelToken = null)
{
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<Position> tcs;
var manager = GetManager();
manager.DesiredAccuracy = DesiredAccuracy;
// 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<Position>(manager);
var singleListener = new GeolocationSingleUpdateDelegate(manager, DesiredAccuracy, timeoutMilliseconds, cancelToken.Value);
manager.Delegate = singleListener;
manager.StartUpdatingLocation();
return await singleListener.Task;
}
#endregion
}
}

+ 128
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs View File

@ -0,0 +1,128 @@
using CoreLocation;
using eShopOnContainers.Core.Models.Permissions;
using eShopOnContainers.Core.Services.Permissions;
using Foundation;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UIKit;
namespace eShopOnContainers.iOS.Services
{
public class PermissionsService : IPermissionsService
{
CLLocationManager _locationManager;
#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<PermissionStatus> 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<CLAuthorizationChangedEventArgs> authCallback = null;
var tcs = new TaskCompletionSource<PermissionStatus>();
_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.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<PermissionStatus> CheckPermissionStatusAsync(Permission permission)
{
switch (permission)
{
case Permission.LocationWhenInUse:
return Task.FromResult(GetLocationPermissionStatus(permission));
}
return Task.FromResult(PermissionStatus.Granted);
}
public async Task<Dictionary<Permission, PermissionStatus>> RequestPermissionsAsync(params Permission[] permissions)
{
var results = new Dictionary<Permission, PermissionStatus>();
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
}
}

+ 3
- 6
src/Mobile/eShopOnContainers/eShopOnContainers.iOS/eShopOnContainers.iOS.csproj View File

@ -126,6 +126,9 @@
<BundleResource Include="Resources\menu_campaigns%403x.png" />
<None Include="packages.config" />
<Compile Include="Services\SettingsServiceImplementation.cs" />
<Compile Include="Services\LocationServiceImplementation.cs" />
<Compile Include="Services\PermissionsService.cs" />
<Compile Include="Services\GeolocationSingleUpdateDelegate.cs" />
</ItemGroup>
<ItemGroup>
<BundleResource Include="Resources\Icon-60%403x.png" />
@ -198,12 +201,6 @@
<Reference Include="Acr.UserDialogs.Interface">
<HintPath>..\..\..\..\packages\Acr.UserDialogs.6.5.1\lib\Xamarin.iOS10\Acr.UserDialogs.Interface.dll</HintPath>
</Reference>
<Reference Include="Plugin.Geolocator.Abstractions">
<HintPath>..\..\..\..\packages\Xam.Plugin.Geolocator.3.0.4\lib\Xamarin.iOS10\Plugin.Geolocator.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Plugin.Geolocator">
<HintPath>..\..\..\..\packages\Xam.Plugin.Geolocator.3.0.4\lib\Xamarin.iOS10\Plugin.Geolocator.dll</HintPath>
</Reference>
<Reference Include="PInvoke.Windows.Core">
<HintPath>..\..\..\..\packages\PInvoke.Windows.Core.0.3.2\lib\portable-net45+win+wpa81+MonoAndroid10+xamarinios10+MonoTouch10\PInvoke.Windows.Core.dll</HintPath>
</Reference>


+ 0
- 1
src/Mobile/eShopOnContainers/eShopOnContainers.iOS/packages.config View File

@ -65,7 +65,6 @@
<package id="System.Xml.XmlDocument" version="4.3.0" targetFramework="xamarinios10" />
<package id="Validation" version="2.2.8" targetFramework="xamarinios10" />
<package id="WebP.Touch" version="1.0.7" targetFramework="xamarinios10" />
<package id="Xam.Plugin.Geolocator" version="3.0.4" targetFramework="xamarinios10" />
<package id="Xamarin.FFImageLoading" version="2.3.4" targetFramework="xamarinios10" />
<package id="Xamarin.FFImageLoading.Forms" version="2.3.4" targetFramework="xamarinios10" />
<package id="Xamarin.Forms" version="2.5.0.122203" targetFramework="xamarinios10" />

Loading…
Cancel
Save