diff --git a/deploy/az/servicebus/readme.md b/deploy/az/servicebus/readme.md index 3ed8e8a0e..16da4c7b2 100644 --- a/deploy/az/servicebus/readme.md +++ b/deploy/az/servicebus/readme.md @@ -3,7 +3,7 @@ The ARM template `sbusdeploy.json` and its parameter file (`sbusdeploy.parameters.json`) are used to deploy following resources: 1. One Service Bus namespace -2. One Service Bus +2. One Service Bus topic 3. Subscriptions used by application ## Editing sbusdeploy.parameters.json file @@ -17,17 +17,8 @@ be set is: Once parameter file is edited you can deploy it using [create-resources script](../readme.md). -i. e. if you are in windows, to deploy sql databases in a new resourcegroup located in westus, go to `deploy\az` folder and type: +i. e. if you are in windows, to deploy servicebus in a new resourcegroup located in westus, go to `deploy\az` folder and type: ``` create-resources.cmd servicebus\sbusdeploy newResourceGroup -c westus -``` - - - - - - - - - +``` \ No newline at end of file diff --git a/docs/Containerized-Docker-Application-Lifecycle-with-Microsoft-Platform-and-Tools-(eBook)_v1.1_Deprecated.pdf b/docs/Containerized-Docker-Application-Lifecycle-with-Microsoft-Platform-and-Tools-(eBook)_v1.1-Deprecated.pdf similarity index 100% rename from docs/Containerized-Docker-Application-Lifecycle-with-Microsoft-Platform-and-Tools-(eBook)_v1.1_Deprecated.pdf rename to docs/Containerized-Docker-Application-Lifecycle-with-Microsoft-Platform-and-Tools-(eBook)_v1.1-Deprecated.pdf diff --git a/docs/Microservicios-NET-Arquitectura-para-aplicaciones-NET-Contenerizadas-(Microsoft-for-Kindle)-es-ES.mobi b/docs/Microservicios-NET-Arquitectura-para-aplicaciones-NET-Contenerizadas-(Microsoft-for-Kindle)-es-ES.mobi new file mode 100644 index 000000000..1900fc281 Binary files /dev/null and b/docs/Microservicios-NET-Arquitectura-para-aplicaciones-NET-Contenerizadas-(Microsoft-for-Kindle)-es-ES.mobi differ diff --git a/docs/Microservicios-NET-Arquitectura-para-aplicaciones-NET-Contenerizadas-(Microsoft-for-eReader)-es-ES.epub b/docs/Microservicios-NET-Arquitectura-para-aplicaciones-NET-Contenerizadas-(Microsoft-for-eReader)-es-ES.epub new file mode 100644 index 000000000..3fbff2fa8 Binary files /dev/null and b/docs/Microservicios-NET-Arquitectura-para-aplicaciones-NET-Contenerizadas-(Microsoft-for-eReader)-es-ES.epub differ diff --git a/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook)-zh-CN-v2-older.pdf b/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook)-zh-CN-v2-older.pdf new file mode 100644 index 000000000..452ae61d0 Binary files /dev/null and b/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook)-zh-CN-v2-older.pdf differ diff --git a/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook)-zh-CN.pdf b/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook)-zh-CN.pdf index 452ae61d0..32c265632 100644 Binary files a/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook)-zh-CN.pdf and b/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook)-zh-CN.pdf differ diff --git a/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-for-Kindle)-v1.mobi b/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-for-Kindle)-v1.mobi new file mode 100644 index 000000000..728f15755 Binary files /dev/null and b/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-for-Kindle)-v1.mobi differ diff --git a/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-for-eReader)-v1.epub b/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-for-eReader)-v1.epub new file mode 100644 index 000000000..5d4a8b491 Binary files /dev/null and b/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-for-eReader)-v1.epub differ diff --git a/k8s/conf_cloud.yml b/k8s/conf_cloud.yml index 0f079fd3f..624d3a59b 100644 --- a/k8s/conf_cloud.yml +++ b/k8s/conf_cloud.yml @@ -6,29 +6,29 @@ metadata: app: eshop data: # Basket.API entries - BasketBus: CONNECTION_STRING (NAME OF RABBITMQ CONTAINER OR Endpoint=sb://XXXX in case of using Azure) + BasketBus: CONNECTION_STRING (NAME OF RABBITMQ CONTAINER OR Endpoint=sb://XXXX for topic in case of using Azure) BasketRedisConStr: REDIS CONNECTION STRING FOR BASKET # Catalog.API entries - CatalogBus: CONNECTION_STRING (NAME OF RABBITMQ CONTAINER OR Endpoint=sb://XXXX in case of using Azure) + CatalogBus: CONNECTION_STRING (NAME OF RABBITMQ CONTAINER OR Endpoint=sb://XXXX for topic in case of using Azure) CatalogSqlDb: Catalog SQL SERVER CONNECTION STRING (Server=xxxx;Intial Catalog=yyy;....) # Identity.API entries IdentitySqlDb: Identity SQL SERVER CONNECTION STRING (Server=xxxx;Intial Catalog=yyy;....) # Locations.API entries - LocationsBus: CONNECTION_STRING (NAME OF RABBITMQ CONTAINER OR Endpoint=sb://XXXX in case of using Azure) + LocationsBus: CONNECTION_STRING (NAME OF RABBITMQ CONTAINER OR Endpoint=sb://XXXX for topic in case of using Azure) LocationsNoSqlDb: Locations MongoDb ConnectionString LocationsNoSqlDbName: Locations MongoDb database (LocationsDb) # Marketing.API entries - MarketingBus: CONNECTION_STRING (NAME OF RABBITMQ CONTAINER OR Endpoint=sb://XXXX in case of using Azure) + MarketingBus: CONNECTION_STRING (NAME OF RABBITMQ CONTAINER OR Endpoint=sb://XXXX for topic in case of using Azure) MarketingNoSqlDb: Marketing MongoDb ConnectionString MarketingNoSqlDbName: Marketing MongoDb database (MarketingDb) MarketingSqlDb: Marketing SQL SERVER CONNECTION STRING (Server=xxxx;Intial Catalog=yyy;....) # Ordering.API entries - OrderingBus: CONNECTION_STRING (NAME OF RABBITMQ CONTAINER OR Endpoint=sb://XXXX in case of using Azure) + OrderingBus: CONNECTION_STRING (NAME OF RABBITMQ CONTAINER OR Endpoint=sb://XXXX for topic in case of using Azure) OrderingSqlDb: Ordering SQL SERVER CONNECTION STRING (Server=xxxx;Intial Catalog=yyy;....) GracePeriodManager_GracePeriodTime: "1" GracePeriodManager_CheckUpdateTime: "15000" # Payment.API entries - PaymentBus: CONNECTION_STRING (NAME OF RABBITMQ CONTAINER OR Endpoint=sb://XXXX in case of using Azure) + PaymentBus: CONNECTION_STRING (NAME OF RABBITMQ CONTAINER OR Endpoint=sb://XXXX for topic in case of using Azure) # Global entries UseAzureServiceBus: "TRUE" IF USE AZURE SB ("FALSE" FOR USING RABBITMQ) keystore: REDIS CONNECTION STRING FOR KEYSTORE \ No newline at end of file diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs index 0270bb961..207d076f9 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs @@ -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(); + var locator = dependencyService.Get(); 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 { 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/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/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/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/Location/ILocationServiceImplementation.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs new file mode 100644 index 000000000..84abd98fc --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs @@ -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 GetPositionAsync(TimeSpan? timeout = null, CancellationToken? token = null); + } +} 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/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/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/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.Core/ViewModels/SettingsViewModel.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/SettingsViewModel.cs index 1e478b85c..1969880cd 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; @@ -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(); 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..4b823bbd0 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,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.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..41ea2ad3e --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Extensions/LocationExtensions.cs @@ -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); + } + } +} 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/GeolocationSingleListener.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/GeolocationSingleListener.cs new file mode 100644 index 000000000..b54899a43 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/GeolocationSingleListener.cs @@ -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 _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..df3b4f34c --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs @@ -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 CheckPermissionsAsync() + { + var status = await PermissionsService.Instance.CheckPermissionStatusAsync(Permission.Location); + if (status != PermissionStatus.Granted) + { + Console.WriteLine("Currently do not have Location permissions, requesting permissions."); + + var request = await PermissionsService.Instance.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) + { + 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/PermissionsService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs new file mode 100644 index 000000000..e8be858ad --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs @@ -0,0 +1,241 @@ +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 +{ + public class PermissionsService : IPermissionsService + { + const int _permissionCode = 25; + object _locker = new object(); + TaskCompletionSource> _tcs; + Dictionary _results; + IList _requestedPermissions; + + public static IPermissionsService Instance = new PermissionsService(); + + #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; + } + + 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..7d699f9d2 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,10 @@ + + + + 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.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 @@ - 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..db20dfbef --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Services/LocationServiceImplementation.cs @@ -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 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)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 + 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..dd8380b97 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/GeolocationSingleUpdateDelegate.cs @@ -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 _tcs; + readonly CLLocationManager _manager; + + public Task Task => _tcs?.Task; + + public GeolocationSingleUpdateDelegate(CLLocationManager manager, double desiredAccuracy, int timeout, CancellationToken cancelToken) + { + _manager = manager; + _tcs = new TaskCompletionSource(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(); + } + } +} 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..7b8a6f0ff --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/LocationServiceImplementation.cs @@ -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 + { + IPermissionsService _permissionsService; + + public LocationServiceImplementation() + { + DesiredAccuracy = 100; + _permissionsService = new PermissionsService(); + } + + #region Internal Implementation + + async Task CheckPermissions(Permission permission) + { + 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 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; + 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; + + // 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, timeoutMilliseconds, cancelToken.Value); + manager.Delegate = singleListener; + manager.StartUpdatingLocation(); + + return await singleListener.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..d0d3e06c8 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.iOS/Services/PermissionsService.cs @@ -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 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(); + _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 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 @@ - diff --git a/src/Services/Basket/Basket.API/Dockerfile b/src/Services/Basket/Basket.API/Dockerfile index 22f0c6486..a3cd2be2c 100644 --- a/src/Services/Basket/Basket.API/Dockerfile +++ b/src/Services/Basket/Basket.API/Dockerfile @@ -2,7 +2,7 @@ FROM microsoft/aspnetcore:2.0.3 AS base WORKDIR /app EXPOSE 80 -FROM microsoft/aspnetcore-build:2.0 AS build +FROM aspnetcore-build:2.0.5-2.1.4 AS build WORKDIR /src COPY . . RUN dotnet restore -nowarn:msb3202,nu1503 diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs index 76a78bad7..546483b40 100644 --- a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs +++ b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs @@ -30,7 +30,8 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Even private async Task UpdatePriceInBasketItems(int productId, decimal newPrice, decimal oldPrice, CustomerBasket basket) { - var itemsToUpdate = basket?.Items?.Where(x => int.Parse(x.ProductId) == productId).ToList(); + string match = productId.ToString(); + var itemsToUpdate = basket?.Items?.Where(x => x.ProductId == match).ToList(); if (itemsToUpdate != null) { diff --git a/src/Services/Catalog/Catalog.API/Dockerfile b/src/Services/Catalog/Catalog.API/Dockerfile index a73b51dcf..a5ea6b645 100644 --- a/src/Services/Catalog/Catalog.API/Dockerfile +++ b/src/Services/Catalog/Catalog.API/Dockerfile @@ -2,7 +2,7 @@ FROM microsoft/aspnetcore:2.0.3 AS base WORKDIR /app EXPOSE 80 -FROM microsoft/aspnetcore-build:2.0 AS build +FROM aspnetcore-build:2.0.5-2.1.4 AS build WORKDIR /src COPY . . RUN dotnet restore -nowarn:msb3202,nu1503 diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs b/src/Services/Catalog/Catalog.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs index e618d3eed..41d3f08e1 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs @@ -39,7 +39,7 @@ namespace Catalog.API.Infrastructure.Filters { var json = new JsonErrorResponse { - Messages = new[] { "An error ocurr.Try it again." } + Messages = new[] { "An error ocurred." } }; if (env.IsDevelopment()) diff --git a/src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs b/src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs index 058c58dbc..3dbf28171 100644 --- a/src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs +++ b/src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs @@ -33,7 +33,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data if (!context.Users.Any()) { - await context.Users.AddRangeAsync(useCustomizationData + context.Users.AddRange(useCustomizationData ? GetUsersFromFile(contentRootPath, logger) : GetDefaultUser()); diff --git a/src/Services/Identity/Identity.API/Data/ConfigurationDbContextSeed.cs b/src/Services/Identity/Identity.API/Data/ConfigurationDbContextSeed.cs index db43c6019..b109bf896 100644 --- a/src/Services/Identity/Identity.API/Data/ConfigurationDbContextSeed.cs +++ b/src/Services/Identity/Identity.API/Data/ConfigurationDbContextSeed.cs @@ -27,17 +27,17 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data if (!await context.Clients.AnyAsync()) { - await context.Clients.AddRangeAsync(Config.GetClients(clientUrls).Select(client => client.ToEntity())); + context.Clients.AddRange(Config.GetClients(clientUrls).Select(client => client.ToEntity())); } if (!await context.IdentityResources.AnyAsync()) { - await context.IdentityResources.AddRangeAsync(Config.GetResources().Select(resource => resource.ToEntity())); + context.IdentityResources.AddRange(Config.GetResources().Select(resource => resource.ToEntity())); } if (!await context.ApiResources.AnyAsync()) { - await context.ApiResources.AddRangeAsync(Config.GetApis().Select(api => api.ToEntity())); + context.ApiResources.AddRange(Config.GetApis().Select(api => api.ToEntity())); } await context.SaveChangesAsync(); diff --git a/src/Services/Identity/Identity.API/Dockerfile b/src/Services/Identity/Identity.API/Dockerfile index e5b453731..f7a4b0c25 100644 --- a/src/Services/Identity/Identity.API/Dockerfile +++ b/src/Services/Identity/Identity.API/Dockerfile @@ -2,7 +2,7 @@ FROM microsoft/aspnetcore:2.0.3 AS base WORKDIR /app EXPOSE 80 -FROM microsoft/aspnetcore-build:2.0 AS build +FROM aspnetcore-build:2.0.5-2.1.4 AS build WORKDIR /src COPY . . RUN dotnet restore -nowarn:msb3202,nu1503 diff --git a/src/Services/Location/Locations.API/Dockerfile b/src/Services/Location/Locations.API/Dockerfile index 9f42ab1f9..f8c731295 100644 --- a/src/Services/Location/Locations.API/Dockerfile +++ b/src/Services/Location/Locations.API/Dockerfile @@ -2,7 +2,7 @@ FROM microsoft/aspnetcore:2.0.3 AS base WORKDIR /app EXPOSE 80 -FROM microsoft/aspnetcore-build:2.0 AS build +FROM aspnetcore-build:2.0.5-2.1.4 AS build WORKDIR /src COPY . . RUN dotnet restore -nowarn:msb3202,nu1503 diff --git a/src/Services/Marketing/Marketing.API/Dockerfile b/src/Services/Marketing/Marketing.API/Dockerfile index dea524632..b1772f9b6 100644 --- a/src/Services/Marketing/Marketing.API/Dockerfile +++ b/src/Services/Marketing/Marketing.API/Dockerfile @@ -2,7 +2,7 @@ FROM microsoft/aspnetcore:2.0.3 AS base WORKDIR /app EXPOSE 80 -FROM microsoft/aspnetcore-build:2.0 AS build +FROM aspnetcore-build:2.0.5-2.1.4 AS build WORKDIR /src COPY . . RUN dotnet restore -nowarn:msb3202,nu1503 diff --git a/src/Services/Ordering/Ordering.API/Dockerfile b/src/Services/Ordering/Ordering.API/Dockerfile index d3076209f..8df9cf7a0 100644 --- a/src/Services/Ordering/Ordering.API/Dockerfile +++ b/src/Services/Ordering/Ordering.API/Dockerfile @@ -2,7 +2,7 @@ FROM microsoft/aspnetcore:2.0.3 AS base WORKDIR /app EXPOSE 80 -FROM microsoft/aspnetcore-build:2.0 AS build +FROM aspnetcore-build:2.0.5-2.1.4 AS build WORKDIR /src COPY . . RUN dotnet restore -nowarn:msb3202,nu1503 diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/OrderingContextSeed.cs b/src/Services/Ordering/Ordering.API/Infrastructure/OrderingContextSeed.cs index 4f3f0b0f2..53c53f052 100644 --- a/src/Services/Ordering/Ordering.API/Infrastructure/OrderingContextSeed.cs +++ b/src/Services/Ordering/Ordering.API/Infrastructure/OrderingContextSeed.cs @@ -39,7 +39,7 @@ if (!context.CardTypes.Any()) { - await context.CardTypes.AddRangeAsync(useCustomizationData + context.CardTypes.AddRange(useCustomizationData ? GetCardTypesFromFile(contentRootPath, logger) : GetPredefinedCardTypes()); @@ -48,7 +48,7 @@ if (!context.OrderStatus.Any()) { - await context.OrderStatus.AddRangeAsync(useCustomizationData + context.OrderStatus.AddRange(useCustomizationData ? GetOrderStatusFromFile(contentRootPath, logger) : GetPredefinedOrderStatus()); } diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile b/src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile index 391d8c17b..2577e5064 100644 --- a/src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile @@ -2,7 +2,7 @@ FROM microsoft/aspnetcore:2.0.3 AS base WORKDIR /app EXPOSE 80 -FROM microsoft/aspnetcore-build:2.0 AS build +FROM aspnetcore-build:2.0.5-2.1.4 AS build WORKDIR /src COPY . . RUN dotnet restore -nowarn:msb3202,nu1503 diff --git a/src/Services/Ordering/Ordering.Domain/SeedWork/Enumeration.cs b/src/Services/Ordering/Ordering.Domain/SeedWork/Enumeration.cs index ecf248be9..ff261a61c 100644 --- a/src/Services/Ordering/Ordering.Domain/SeedWork/Enumeration.cs +++ b/src/Services/Ordering/Ordering.Domain/SeedWork/Enumeration.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -11,9 +11,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork public int Id { get; private set; } - protected Enumeration() - { - } + protected Enumeration(){} protected Enumeration(int id, string name) { @@ -21,10 +19,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork Name = name; } - public override string ToString() - { - return Name; - } + public override string ToString() => Name; public static IEnumerable GetAll() where T : Enumeration, new() { @@ -37,9 +32,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork var locatedValue = info.GetValue(instance) as T; if (locatedValue != null) - { yield return locatedValue; - } } } @@ -48,9 +41,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork var otherValue = obj as Enumeration; if (otherValue == null) - { return false; - } var typeMatches = GetType().Equals(obj.GetType()); var valueMatches = Id.Equals(otherValue.Id); @@ -58,10 +49,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork return typeMatches && valueMatches; } - public override int GetHashCode() - { - return Id.GetHashCode(); - } + public override int GetHashCode() => Id.GetHashCode(); public static int AbsoluteDifference(Enumeration firstValue, Enumeration secondValue) { @@ -86,18 +74,11 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork var matchingItem = GetAll().FirstOrDefault(predicate); if (matchingItem == null) - { - var message = string.Format("'{0}' is not a valid {1} in {2}", value, description, typeof(T)); - - throw new InvalidOperationException(message); - } + throw new InvalidOperationException($"'{value}' is not a valid {description} in {typeof(T)}"); return matchingItem; } - public int CompareTo(object other) - { - return Id.CompareTo(((Enumeration)other).Id); - } + public int CompareTo(object other) => Id.CompareTo(((Enumeration) other).Id); } } diff --git a/src/Services/Payment/Payment.API/Dockerfile b/src/Services/Payment/Payment.API/Dockerfile index 896a81f84..691301289 100644 --- a/src/Services/Payment/Payment.API/Dockerfile +++ b/src/Services/Payment/Payment.API/Dockerfile @@ -2,7 +2,7 @@ FROM microsoft/aspnetcore:2.0.3 AS base WORKDIR /app EXPOSE 80 -FROM microsoft/aspnetcore-build:2.0 AS build +FROM aspnetcore-build:2.0.5-2.1.4 AS build WORKDIR /src COPY . . RUN dotnet restore -nowarn:msb3202,nu1503 diff --git a/src/Web/WebMVC/Dockerfile b/src/Web/WebMVC/Dockerfile index 5fb657dd8..9cb9a7fb7 100644 --- a/src/Web/WebMVC/Dockerfile +++ b/src/Web/WebMVC/Dockerfile @@ -2,7 +2,7 @@ FROM microsoft/aspnetcore:2.0.3 AS base WORKDIR /app EXPOSE 80 -FROM microsoft/aspnetcore-build:2.0 AS build +FROM aspnetcore-build:2.0.5-2.1.4 AS build WORKDIR /src COPY . . RUN dotnet restore -nowarn:msb3202,nu1503 diff --git a/src/Web/WebSPA/Dockerfile b/src/Web/WebSPA/Dockerfile index 47032f41f..938850901 100644 --- a/src/Web/WebSPA/Dockerfile +++ b/src/Web/WebSPA/Dockerfile @@ -2,7 +2,7 @@ FROM microsoft/aspnetcore:2.0.3 AS base WORKDIR /app EXPOSE 80 -FROM microsoft/aspnetcore-build:2.0 AS build +FROM aspnetcore-build:2.0.5-2.1.4 AS build WORKDIR /src COPY . . RUN dotnet restore -nowarn:msb3202,nu1503 diff --git a/src/Web/WebStatus/Dockerfile b/src/Web/WebStatus/Dockerfile index b143bd875..0c6f6b6b5 100644 --- a/src/Web/WebStatus/Dockerfile +++ b/src/Web/WebStatus/Dockerfile @@ -2,7 +2,7 @@ FROM microsoft/aspnetcore:2.0.3 AS base WORKDIR /app EXPOSE 80 -FROM microsoft/aspnetcore-build:2.0 AS build +FROM microsoft/aspnetcore-build:2.0.5-2.1.4 AS build WORKDIR /src COPY . . RUN dotnet restore -nowarn:msb3202,nu1503 @@ -16,3 +16,4 @@ FROM base AS final WORKDIR /app COPY --from=publish /app . ENTRYPOINT ["dotnet", "WebStatus.dll"] + diff --git a/test/Services/IntegrationTests/Services/Basket/BasketScenarios.cs b/test/Services/IntegrationTests/Services/Basket/BasketScenarios.cs index 451b1fbf7..747efc8ed 100644 --- a/test/Services/IntegrationTests/Services/Basket/BasketScenarios.cs +++ b/test/Services/IntegrationTests/Services/Basket/BasketScenarios.cs @@ -58,6 +58,15 @@ namespace IntegrationTests.Services.Basket string BuildBasket() { var order = new CustomerBasket("1234"); + + order.Items.Add(new BasketItem + { + ProductId = "1", + ProductName = ".NET Bot Black Hoodie", + UnitPrice = 10, + Quantity = 1 + }); + return JsonConvert.SerializeObject(order); } @@ -70,10 +79,10 @@ namespace IntegrationTests.Services.Basket State = "state", Country = "coutry", ZipCode = "zipcode", - CardNumber = "CardNumber", + CardNumber = "1234567890123456", CardHolderName = "CardHolderName", - CardExpiration = DateTime.UtcNow, - CardSecurityNumber = "1234", + CardExpiration = DateTime.UtcNow.AddDays(1), + CardSecurityNumber = "123", CardTypeId = 1, Buyer = "Buyer", RequestId = Guid.NewGuid()