├── media ├── Auth.png ├── Filter.png ├── Catalog.png ├── MacAgent.png ├── Preview.png ├── Profile.png ├── Settings.png ├── Updates.png ├── OrderDetail.png ├── auth_screen.png ├── vm-settings.png ├── ShoppingCart.png ├── login_screen.png ├── order_screen.png ├── AndroidEmulator.png ├── Unsupported52.0.png ├── catalog_screen.png ├── migrated-fonts.png ├── migrated-images.png ├── pclcrypto-nuget.png ├── profile_screen.png ├── identitymodel-nuget.png ├── launch-hyperv-manager.png ├── newtonsoft-json-nuget.png ├── build-action-maui-font.png ├── build-action-maui-image.png ├── eShopOnContainers-files.png ├── set-compatibility-vs-sml.png ├── maui-community-toolkit-nuget.png ├── migrated-xaml-and-code-files.png ├── microservices-endpoint-enable.png ├── xaml-and-code-files-to-migrate.png ├── could-not-connect-to-the-debugger.png ├── microservices-endpoint-configure.png └── eShopOnContainers_Architecture_Diagram.png ├── eShopOnContainers ├── Resources │ ├── Images │ │ ├── banner.png │ │ ├── noimage.png │ │ ├── eshop_logo.png │ │ ├── default_campaign.png │ │ ├── default_product.png │ │ ├── fake_campaign_01.png │ │ ├── fake_campaign_02.png │ │ ├── fake_product_01.png │ │ ├── fake_product_02.png │ │ ├── fake_product_03.png │ │ ├── fake_product_04.png │ │ └── fake_product_05.png │ ├── Fonts │ │ ├── Montserrat-Bold.ttf │ │ ├── Montserrat-Regular.ttf │ │ ├── OpenSans-Regular.ttf │ │ ├── OpenSans-Semibold.ttf │ │ ├── SourceSansPro-Regular.ttf │ │ ├── Font_Awesome_5_Free-Solid-900.otf │ │ └── Font_Awesome_5_Free-Regular-400.otf │ ├── appicon.svg │ ├── Raw │ │ └── AboutAssets.txt │ └── appiconfg.svg ├── Validations │ ├── IValidity.cs │ ├── IValidationRule.cs │ ├── IsNotNullOrEmptyRule.cs │ └── ValidatableObject.cs ├── GlobalUsings.cs ├── Models │ ├── User │ │ ├── LogoutParameter.cs │ │ ├── PaymentInfo.cs │ │ ├── Address.cs │ │ └── UserInfo.cs │ ├── Navigation │ │ └── TabParameter.cs │ ├── Location │ │ ├── GeolocationError.cs │ │ ├── Location.cs │ │ ├── GeolocationException.cs │ │ └── Position.cs │ ├── Orders │ │ ├── CardType.cs │ │ ├── OrderStatus.cs │ │ ├── CancelOrderCommand.cs │ │ ├── OrderItem.cs │ │ └── Order.cs │ ├── Permissions │ │ ├── Permission.cs │ │ └── PermissionStatus.cs │ ├── Basket │ │ ├── CustomerBasket.cs │ │ ├── BasketItem.cs │ │ └── BasketCheckout.cs │ ├── Catalog │ │ ├── CatalogRoot.cs │ │ ├── CatalogType.cs │ │ ├── CatalogBrand.cs │ │ └── CatalogItem.cs │ ├── Marketing │ │ ├── CampaignRoot.cs │ │ ├── Campaign.cs │ │ └── CampaignItem.cs │ └── Token │ │ └── UserToken.cs ├── Services │ ├── OpenUrl │ │ ├── IOpenUrlService.cs │ │ └── OpenUrlService.cs │ ├── Theme │ │ ├── ITheme.cs │ │ └── Theme.shared.cs │ ├── Dialog │ │ ├── IDialogService.cs │ │ └── DialogService.cs │ ├── Location │ │ ├── ILocationService.cs │ │ └── LocationService.cs │ ├── User │ │ ├── IUserService.cs │ │ ├── UserService.cs │ │ └── UserMockService.cs │ ├── Navigation │ │ ├── INavigationService.cs │ │ └── MauiNavigationService.cs │ ├── Marketing │ │ ├── ICampaignService.cs │ │ ├── CampaignMockService.cs │ │ └── CampaignService.cs │ ├── Identity │ │ ├── IIdentityService.cs │ │ ├── AuthorizeRequest.cs │ │ └── IdentityService.cs │ ├── Catalog │ │ ├── ICatalogService.cs │ │ ├── CatalogService.cs │ │ └── CatalogMockService.cs │ ├── Common │ │ └── Common.cs │ ├── FixUri │ │ ├── IFixUriService.cs │ │ └── FixUriService.cs │ ├── Order │ │ ├── IOrderService.cs │ │ └── OrderService.cs │ ├── Basket │ │ ├── IBasketService.cs │ │ ├── BasketMockService.cs │ │ └── BasketService.cs │ ├── Settings │ │ ├── ISettingsService.cs │ │ └── SettingsService.cs │ ├── RequestProvider │ │ ├── IRequestProvider.cs │ │ └── HttpRequestExceptionEx.cs │ └── AppEnvironment │ │ ├── IAppEnvironmentService.cs │ │ └── AppEnvironmentService.cs ├── Properties │ └── launchSettings.json ├── Controls │ ├── AddBasketButton.xaml.cs │ ├── AddBasketButton.xaml │ ├── CustomTabbedPage.cs │ └── ToggleButton.cs ├── AppActions.cs ├── Views │ ├── Templates │ │ ├── CampaignTemplate.xaml.cs │ │ ├── OrderItemTemplate.xaml.cs │ │ ├── BasketItemTemplate.xaml.cs │ │ ├── OrderTemplate.xaml.cs │ │ ├── ProductTemplate.xaml.cs │ │ ├── CampaignTemplate.xaml │ │ ├── OrderTemplate.xaml │ │ └── ProductTemplate.xaml │ ├── SettingsView.xaml.cs │ ├── CheckoutView.xaml.cs │ ├── FiltersView.xaml.cs │ ├── CampaignView.xaml.cs │ ├── OrderDetailView.xaml.cs │ ├── BasketView.xaml.cs │ ├── CampaignDetailsView.xaml.cs │ ├── CustomNavigationView.xaml.cs │ ├── CustomNavigationView.xaml │ ├── ContentPageBase.cs │ ├── ProfileView.xaml.cs │ ├── MapView.xaml.cs │ ├── CatalogView.xaml.cs │ ├── LoginView.xaml.cs │ ├── MapView.xaml │ ├── ProfileView.xaml │ ├── FiltersView.xaml │ ├── CampaignView.xaml │ ├── CatalogView.xaml │ └── BadgeView.cs ├── Effects │ ├── EntryLineColorEffect.cs │ └── ThemeEffects.cs ├── Platforms │ ├── Android │ │ ├── Resources │ │ │ └── values │ │ │ │ └── colors.xml │ │ ├── MainApplication.cs │ │ ├── AndroidManifest.xml │ │ └── MainActivity.cs │ ├── iOS │ │ ├── AppDelegate.cs │ │ ├── Program.cs │ │ └── Info.plist │ ├── MacCatalyst │ │ ├── AppDelegate.cs │ │ ├── Program.cs │ │ ├── Entitlements.Debug.plist │ │ ├── Entitlements.Release.plist │ │ └── Info.plist │ └── Windows │ │ ├── App.xaml │ │ ├── App.xaml.cs │ │ ├── app.manifest │ │ └── Package.appxmanifest ├── Messages │ └── AddProductMessage.cs ├── Animations │ ├── Base │ │ ├── EasingType.cs │ │ └── AnimationBase.cs │ └── StoryBoard.cs ├── Exceptions │ └── ServiceAuthenticationException.cs ├── Triggers │ └── BeginAnimation.cs ├── Extensions │ ├── ICommandExtensions.cs │ └── DictionaryExtensions.cs ├── Converters │ ├── HasCountConverter.cs │ ├── DoesNotHaveCountConverter.cs │ ├── WebNavigatedEventArgsConverter.cs │ ├── FirstValidationErrorConverter.cs │ ├── ItemsToHeightConverter.cs │ ├── DoubleConverter.cs │ ├── OrderStatusToStringConverter.cs │ └── WebNavigatingEventArgsConverter.cs ├── ViewModels │ ├── Base │ │ ├── IViewModelBase.cs │ │ └── ViewModelBase.cs │ ├── MainViewModel.cs │ ├── MapViewModel.cs │ ├── CampaignDetailsViewModel.cs │ ├── ObservableCollectionEx.cs │ ├── CampaignViewModel.cs │ ├── OrderDetailViewModel.cs │ ├── ProfileViewModel.cs │ └── BasketViewModel.cs ├── GlobalSuppressions.cs ├── Helpers │ ├── RandomNumberGenerator.cs │ ├── UriHelper.cs │ └── EasingHelper.cs ├── AppShell.xaml.cs ├── AppShell.xaml └── GlobalSettings.cs ├── eShopOnContainers.UnitTests ├── Mocks │ ├── MockDialogService.cs │ ├── MockNavigationService.cs │ ├── MockViewModel.cs │ └── MockSettingsService.cs ├── Services │ ├── BasketServiceTests.cs │ ├── OrdersServiceTests.cs │ ├── MarketingServiceTests.cs │ └── CatalogServiceTests.cs ├── GlobalUsings.cs ├── TestingExtensions.cs ├── ViewModels │ ├── MainViewModelTests.cs │ ├── OrderViewModelTests.cs │ ├── MarketingViewModelTests.cs │ └── MockViewModelTests.cs └── eShopOnContainers.UnitTests.csproj ├── LICENSE ├── .github └── workflows │ └── build-validation.yml └── eShopOnContainers.sln /media/Auth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/Auth.png -------------------------------------------------------------------------------- /media/Filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/Filter.png -------------------------------------------------------------------------------- /media/Catalog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/Catalog.png -------------------------------------------------------------------------------- /media/MacAgent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/MacAgent.png -------------------------------------------------------------------------------- /media/Preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/Preview.png -------------------------------------------------------------------------------- /media/Profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/Profile.png -------------------------------------------------------------------------------- /media/Settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/Settings.png -------------------------------------------------------------------------------- /media/Updates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/Updates.png -------------------------------------------------------------------------------- /media/OrderDetail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/OrderDetail.png -------------------------------------------------------------------------------- /media/auth_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/auth_screen.png -------------------------------------------------------------------------------- /media/vm-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/vm-settings.png -------------------------------------------------------------------------------- /media/ShoppingCart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/ShoppingCart.png -------------------------------------------------------------------------------- /media/login_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/login_screen.png -------------------------------------------------------------------------------- /media/order_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/order_screen.png -------------------------------------------------------------------------------- /media/AndroidEmulator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/AndroidEmulator.png -------------------------------------------------------------------------------- /media/Unsupported52.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/Unsupported52.0.png -------------------------------------------------------------------------------- /media/catalog_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/catalog_screen.png -------------------------------------------------------------------------------- /media/migrated-fonts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/migrated-fonts.png -------------------------------------------------------------------------------- /media/migrated-images.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/migrated-images.png -------------------------------------------------------------------------------- /media/pclcrypto-nuget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/pclcrypto-nuget.png -------------------------------------------------------------------------------- /media/profile_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/profile_screen.png -------------------------------------------------------------------------------- /media/identitymodel-nuget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/identitymodel-nuget.png -------------------------------------------------------------------------------- /media/launch-hyperv-manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/launch-hyperv-manager.png -------------------------------------------------------------------------------- /media/newtonsoft-json-nuget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/newtonsoft-json-nuget.png -------------------------------------------------------------------------------- /media/build-action-maui-font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/build-action-maui-font.png -------------------------------------------------------------------------------- /media/build-action-maui-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/build-action-maui-image.png -------------------------------------------------------------------------------- /media/eShopOnContainers-files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/eShopOnContainers-files.png -------------------------------------------------------------------------------- /media/set-compatibility-vs-sml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/set-compatibility-vs-sml.png -------------------------------------------------------------------------------- /media/maui-community-toolkit-nuget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/maui-community-toolkit-nuget.png -------------------------------------------------------------------------------- /media/migrated-xaml-and-code-files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/migrated-xaml-and-code-files.png -------------------------------------------------------------------------------- /media/microservices-endpoint-enable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/microservices-endpoint-enable.png -------------------------------------------------------------------------------- /media/xaml-and-code-files-to-migrate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/xaml-and-code-files-to-migrate.png -------------------------------------------------------------------------------- /media/could-not-connect-to-the-debugger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/could-not-connect-to-the-debugger.png -------------------------------------------------------------------------------- /media/microservices-endpoint-configure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/microservices-endpoint-configure.png -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Images/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/eShopOnContainers/Resources/Images/banner.png -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Images/noimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/eShopOnContainers/Resources/Images/noimage.png -------------------------------------------------------------------------------- /media/eShopOnContainers_Architecture_Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/media/eShopOnContainers_Architecture_Diagram.png -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Images/eshop_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/eShopOnContainers/Resources/Images/eshop_logo.png -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Fonts/Montserrat-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/eShopOnContainers/Resources/Fonts/Montserrat-Bold.ttf -------------------------------------------------------------------------------- /eShopOnContainers/Validations/IValidity.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Validations; 2 | 3 | public interface IValidity 4 | { 5 | bool IsValid { get; } 6 | } -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Fonts/Montserrat-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/eShopOnContainers/Resources/Fonts/Montserrat-Regular.ttf -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/eShopOnContainers/Resources/Fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Fonts/OpenSans-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/eShopOnContainers/Resources/Fonts/OpenSans-Semibold.ttf -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Images/default_campaign.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/eShopOnContainers/Resources/Images/default_campaign.png -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Images/default_product.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/eShopOnContainers/Resources/Images/default_product.png -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Images/fake_campaign_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/eShopOnContainers/Resources/Images/fake_campaign_01.png -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Images/fake_campaign_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/eShopOnContainers/Resources/Images/fake_campaign_02.png -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Images/fake_product_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/eShopOnContainers/Resources/Images/fake_product_01.png -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Images/fake_product_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/eShopOnContainers/Resources/Images/fake_product_02.png -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Images/fake_product_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/eShopOnContainers/Resources/Images/fake_product_03.png -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Images/fake_product_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/eShopOnContainers/Resources/Images/fake_product_04.png -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Images/fake_product_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/eShopOnContainers/Resources/Images/fake_product_05.png -------------------------------------------------------------------------------- /eShopOnContainers/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using eShopOnContainers.ViewModels; 2 | global using CommunityToolkit.Mvvm.ComponentModel; 3 | global using CommunityToolkit.Mvvm.Input; -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Fonts/SourceSansPro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/eShopOnContainers/Resources/Fonts/SourceSansPro-Regular.ttf -------------------------------------------------------------------------------- /eShopOnContainers/Models/User/LogoutParameter.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.User; 2 | 3 | public class LogoutParameter 4 | { 5 | public bool Logout { get; set; } 6 | } -------------------------------------------------------------------------------- /eShopOnContainers/Models/Navigation/TabParameter.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.Navigation; 2 | 3 | public class TabParameter 4 | { 5 | public int TabIndex { get; set; } 6 | } -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Fonts/Font_Awesome_5_Free-Solid-900.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/eShopOnContainers/Resources/Fonts/Font_Awesome_5_Free-Solid-900.otf -------------------------------------------------------------------------------- /eShopOnContainers/Services/OpenUrl/IOpenUrlService.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Services.OpenUrl; 2 | 3 | public interface IOpenUrlService 4 | { 5 | Task OpenUrl(string url); 6 | } -------------------------------------------------------------------------------- /eShopOnContainers/Models/Location/GeolocationError.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.Location; 2 | 3 | public enum GeolocationError 4 | { 5 | PositionUnavailable, 6 | Unauthorized 7 | } -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Fonts/Font_Awesome_5_Free-Regular-400.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StarDev913/SonarQube/HEAD/eShopOnContainers/Resources/Fonts/Font_Awesome_5_Free-Regular-400.otf -------------------------------------------------------------------------------- /eShopOnContainers/Services/Theme/ITheme.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Services.Theme; 2 | 3 | public interface ITheme 4 | { 5 | void SetStatusBarColor(Color color, bool darkStatusBarTint); 6 | } -------------------------------------------------------------------------------- /eShopOnContainers/Models/Orders/CardType.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.Orders; 2 | 3 | public class CardType 4 | { 5 | public int Id { get; set; } 6 | public string Name { get; set; } 7 | } -------------------------------------------------------------------------------- /eShopOnContainers/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Windows Machine": { 4 | "commandName": "MsixPackage", 5 | "nativeDebugging": false 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /eShopOnContainers/Services/Dialog/IDialogService.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Services; 2 | 3 | public interface IDialogService 4 | { 5 | Task ShowAlertAsync(string message, string title, string buttonLabel); 6 | } -------------------------------------------------------------------------------- /eShopOnContainers/Models/Location/Location.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.Location; 2 | 3 | public class Location 4 | { 5 | public double Longitude { get; set; } 6 | public double Latitude { get; set; } 7 | } -------------------------------------------------------------------------------- /eShopOnContainers/Models/Permissions/Permission.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.Permissions; 2 | 3 | public enum Permission 4 | { 5 | Unknown, 6 | Location, 7 | LocationAlways, 8 | LocationWhenInUse 9 | } -------------------------------------------------------------------------------- /eShopOnContainers/Validations/IValidationRule.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Validations; 2 | 3 | public interface IValidationRule 4 | { 5 | string ValidationMessage { get; set; } 6 | 7 | bool Check(T value); 8 | } -------------------------------------------------------------------------------- /eShopOnContainers/Models/Basket/CustomerBasket.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.Basket; 2 | 3 | public class CustomerBasket 4 | { 5 | public string BuyerId { get; set; } 6 | public List Items { get; set; } 7 | } -------------------------------------------------------------------------------- /eShopOnContainers/Services/Theme/Theme.shared.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Services.Theme; 2 | 3 | public class Theme : ITheme 4 | { 5 | public void SetStatusBarColor(Color color, bool darkStatusBarTint) 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /eShopOnContainers/Controls/AddBasketButton.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Controls; 2 | 3 | public partial class AddBasketButton : Grid 4 | { 5 | public AddBasketButton() 6 | { 7 | InitializeComponent(); 8 | } 9 | } -------------------------------------------------------------------------------- /eShopOnContainers/Services/Location/ILocationService.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Services.Location; 2 | 3 | public interface ILocationService 4 | { 5 | Task UpdateUserLocation(Models.Location.Location newLocReq, string token); 6 | } -------------------------------------------------------------------------------- /eShopOnContainers/Models/Permissions/PermissionStatus.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.Permissions; 2 | 3 | public enum PermissionStatus 4 | { 5 | Denied, 6 | Disabled, 7 | Granted, 8 | Restricted, 9 | Unknown 10 | } -------------------------------------------------------------------------------- /eShopOnContainers/AppActions.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers; 2 | 3 | public static class AppActions 4 | { 5 | public static readonly AppAction 6 | ViewProfileAction = new AppAction("view_profile", "View Profile", "View your user profile"); 7 | } 8 | 9 | -------------------------------------------------------------------------------- /eShopOnContainers/Models/Orders/OrderStatus.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.Orders; 2 | 3 | public enum OrderStatus 4 | { 5 | Submitted, 6 | AwaitingValidation, 7 | StockConfirmed, 8 | Paid, 9 | Shipped, 10 | Cancelled 11 | } -------------------------------------------------------------------------------- /eShopOnContainers/Services/User/IUserService.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Models.User; 2 | 3 | namespace eShopOnContainers.Services.User 4 | { 5 | public interface IUserService 6 | { 7 | Task GetUserInfoAsync(string authToken); 8 | } 9 | } -------------------------------------------------------------------------------- /eShopOnContainers/Views/Templates/CampaignTemplate.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Views.Templates; 2 | 3 | public partial class CampaignTemplate : ContentView 4 | { 5 | public CampaignTemplate() 6 | { 7 | InitializeComponent(); 8 | } 9 | } -------------------------------------------------------------------------------- /eShopOnContainers/Views/Templates/OrderItemTemplate.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Views.Templates; 2 | 3 | public partial class OrderItemTemplate : ContentView 4 | { 5 | public OrderItemTemplate() 6 | { 7 | InitializeComponent(); 8 | } 9 | } -------------------------------------------------------------------------------- /eShopOnContainers/Views/Templates/BasketItemTemplate.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Views.Templates; 2 | 3 | public partial class BasketItemTemplate 4 | { 5 | public BasketItemTemplate() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /eShopOnContainers/Views/Templates/OrderTemplate.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Views.Templates; 2 | 3 | public partial class OrderTemplate : ContentView 4 | { 5 | public OrderTemplate() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /eShopOnContainers/Views/Templates/ProductTemplate.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Views.Templates; 2 | 3 | public partial class ProductTemplate : ContentView 4 | { 5 | public ProductTemplate() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /eShopOnContainers/Effects/EntryLineColorEffect.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Effects; 2 | 3 | public class EntryLineColorEffect : RoutingEffect 4 | { 5 | public EntryLineColorEffect() 6 | : base("eShopOnContainers.EntryLineColorEffect") 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /eShopOnContainers/Resources/appicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /eShopOnContainers/Platforms/Android/Resources/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #512BD4 4 | #2B0B98 5 | #2B0B98 6 | -------------------------------------------------------------------------------- /eShopOnContainers/Models/Catalog/CatalogRoot.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.Catalog; 2 | 3 | public class CatalogRoot 4 | { 5 | public int PageIndex { get; set; } 6 | public int PageSize { get; set; } 7 | public int Count { get; set; } 8 | public List Data { get; set; } 9 | } -------------------------------------------------------------------------------- /eShopOnContainers/Platforms/iOS/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using Foundation; 2 | 3 | namespace eShopOnContainers; 4 | 5 | [Register("AppDelegate")] 6 | public class AppDelegate : MauiUIApplicationDelegate 7 | { 8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 9 | } 10 | -------------------------------------------------------------------------------- /eShopOnContainers/Views/SettingsView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Views; 2 | 3 | public partial class SettingsView : ContentPage 4 | { 5 | public SettingsView(SettingsViewModel viewModel) 6 | { 7 | BindingContext = viewModel; 8 | InitializeComponent(); 9 | } 10 | } -------------------------------------------------------------------------------- /eShopOnContainers/Models/Catalog/CatalogType.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.Catalog; 2 | 3 | public class CatalogType 4 | { 5 | public int Id { get; set; } 6 | public string Type { get; set; } 7 | 8 | public override string ToString() 9 | { 10 | return Type; 11 | } 12 | } -------------------------------------------------------------------------------- /eShopOnContainers/Models/Orders/CancelOrderCommand.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.Orders; 2 | 3 | public class CancelOrderCommand 4 | { 5 | public int OrderNumber { get; } 6 | 7 | public CancelOrderCommand(int orderNumber) 8 | { 9 | OrderNumber = orderNumber; 10 | } 11 | } -------------------------------------------------------------------------------- /eShopOnContainers/Messages/AddProductMessage.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.Messaging.Messages; 2 | 3 | namespace eShopOnContainers.Messages; 4 | 5 | public class AddProductMessage : ValueChangedMessage 6 | { 7 | public AddProductMessage(int count) : base(count) 8 | { 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /eShopOnContainers/Models/Catalog/CatalogBrand.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.Catalog; 2 | 3 | public class CatalogBrand 4 | { 5 | public int Id { get; set; } 6 | public string Brand { get; set; } 7 | 8 | public override string ToString() 9 | { 10 | return Brand; 11 | } 12 | } -------------------------------------------------------------------------------- /eShopOnContainers/Models/Marketing/CampaignRoot.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.Marketing; 2 | 3 | public class CampaignRoot 4 | { 5 | public int PageIndex { get; set; } 6 | public int PageSize { get; set; } 7 | public int Count { get; set; } 8 | public List Data { get; set; } 9 | } -------------------------------------------------------------------------------- /eShopOnContainers/Platforms/MacCatalyst/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using Foundation; 2 | 3 | namespace eShopOnContainers; 4 | 5 | [Register("AppDelegate")] 6 | public class AppDelegate : MauiUIApplicationDelegate 7 | { 8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 9 | } 10 | -------------------------------------------------------------------------------- /eShopOnContainers/Views/CheckoutView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Views; 2 | 3 | public partial class CheckoutView : ContentPageBase 4 | { 5 | public CheckoutView(CheckoutViewModel viewModel) 6 | { 7 | BindingContext = viewModel; 8 | InitializeComponent(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /eShopOnContainers/Views/FiltersView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Views; 2 | 3 | public partial class FiltersView : ContentPage 4 | { 5 | public FiltersView(CatalogViewModel viewModel) 6 | { 7 | BindingContext = viewModel; 8 | 9 | InitializeComponent(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /eShopOnContainers.UnitTests/Mocks/MockDialogService.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.UnitTests.Mocks; 2 | 3 | public class MockDialogService : IDialogService 4 | { 5 | public Task ShowAlertAsync(string message, string title, string buttonLabel) 6 | { 7 | return Task.CompletedTask; 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /eShopOnContainers/Views/CampaignView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Views; 2 | 3 | public partial class CampaignView : ContentPageBase 4 | { 5 | public CampaignView(CampaignDetailsViewModel viewModel) 6 | { 7 | BindingContext = viewModel; 8 | InitializeComponent(); 9 | } 10 | } -------------------------------------------------------------------------------- /eShopOnContainers/Views/OrderDetailView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Views; 2 | 3 | public partial class OrderDetailView : ContentPageBase 4 | { 5 | public OrderDetailView(OrderDetailViewModel viewModel) 6 | { 7 | BindingContext = viewModel; 8 | InitializeComponent(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /eShopOnContainers/Services/Navigation/INavigationService.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Services; 2 | 3 | public interface INavigationService 4 | { 5 | Task InitializeAsync(); 6 | 7 | Task NavigateToAsync(string route, IDictionary routeParameters = null); 8 | 9 | Task PopAsync(); 10 | } -------------------------------------------------------------------------------- /eShopOnContainers/Views/BasketView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Views 2 | { 3 | public partial class BasketView : ContentPageBase 4 | { 5 | public BasketView(BasketViewModel viewModel) 6 | { 7 | BindingContext = viewModel; 8 | InitializeComponent(); 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /eShopOnContainers/Views/CampaignDetailsView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Views; 2 | 3 | public partial class CampaignDetailsView : ContentPage 4 | { 5 | public CampaignDetailsView(CampaignDetailsViewModel viewModel) 6 | { 7 | BindingContext = viewModel; 8 | InitializeComponent(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /eShopOnContainers/Animations/Base/EasingType.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Animations.Base; 2 | 3 | public enum EasingType 4 | { 5 | BounceIn, 6 | BounceOut, 7 | CubicIn, 8 | CubicInOut, 9 | CubicOut, 10 | Linear, 11 | SinIn, 12 | SinInOut, 13 | SinOut, 14 | SpringIn, 15 | SpringOut 16 | } 17 | -------------------------------------------------------------------------------- /eShopOnContainers/Services/Dialog/DialogService.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Services; 2 | 3 | public class DialogService : IDialogService 4 | { 5 | public Task ShowAlertAsync(string message, string title, string buttonLabel) 6 | { 7 | return Application.Current.MainPage.DisplayAlert(title, message, buttonLabel); 8 | } 9 | } -------------------------------------------------------------------------------- /eShopOnContainers/Services/Marketing/ICampaignService.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Models.Marketing; 2 | 3 | namespace eShopOnContainers.Services.Marketing; 4 | 5 | public interface ICampaignService 6 | { 7 | Task> GetAllCampaignsAsync(string token); 8 | Task GetCampaignByIdAsync(int id, string token); 9 | } -------------------------------------------------------------------------------- /eShopOnContainers/Services/Identity/IIdentityService.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Models.Token; 2 | 3 | namespace eShopOnContainers.Services.Identity; 4 | 5 | public interface IIdentityService 6 | { 7 | string CreateAuthorizationRequest(); 8 | string CreateLogoutRequest(string token); 9 | Task GetTokenAsync(string code); 10 | } -------------------------------------------------------------------------------- /eShopOnContainers/Validations/IsNotNullOrEmptyRule.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Validations; 2 | 3 | public class IsNotNullOrEmptyRule : IValidationRule 4 | { 5 | public string ValidationMessage { get; set; } 6 | 7 | public bool Check(T value) => 8 | value is string str && 9 | !string.IsNullOrWhiteSpace(str); 10 | } -------------------------------------------------------------------------------- /eShopOnContainers/Services/OpenUrl/OpenUrlService.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Services.OpenUrl; 2 | 3 | public class OpenUrlService : IOpenUrlService 4 | { 5 | public async Task OpenUrl(string url) 6 | { 7 | if (await Launcher.CanOpenAsync(url)) 8 | { 9 | await Launcher.OpenAsync(url); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /eShopOnContainers/Platforms/Windows/App.xaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /eShopOnContainers/Views/CustomNavigationView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Views; 2 | 3 | public partial class CustomNavigationView : NavigationPage 4 | { 5 | public CustomNavigationView() : base() 6 | { 7 | InitializeComponent(); 8 | } 9 | 10 | public CustomNavigationView(Page root) : base(root) 11 | { 12 | InitializeComponent(); 13 | } 14 | } -------------------------------------------------------------------------------- /eShopOnContainers.UnitTests/Services/BasketServiceTests.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.UnitTests; 2 | 3 | public class BasketServiceTests 4 | { 5 | [Fact] 6 | public async Task GetFakeBasketTest() 7 | { 8 | var catalogMockService = new CatalogMockService(); 9 | var result = await catalogMockService.GetCatalogAsync(); 10 | Assert.NotEmpty(result); 11 | } 12 | } -------------------------------------------------------------------------------- /eShopOnContainers/Exceptions/ServiceAuthenticationException.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Exceptions; 2 | 3 | public class ServiceAuthenticationException : Exception 4 | { 5 | public string Content { get; } 6 | 7 | public ServiceAuthenticationException() 8 | { 9 | } 10 | 11 | public ServiceAuthenticationException(string content) 12 | { 13 | Content = content; 14 | } 15 | } -------------------------------------------------------------------------------- /eShopOnContainers/Models/Marketing/Campaign.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.Marketing; 2 | 3 | public class Campaign 4 | { 5 | public int Id { get; set; } 6 | 7 | public string Name { get; set; } 8 | 9 | public string Description { get; set; } 10 | 11 | public DateTime From { get; set; } 12 | 13 | public DateTime To { get; set; } 14 | 15 | public string PictureUri { get; set; } 16 | } -------------------------------------------------------------------------------- /eShopOnContainers/Triggers/BeginAnimation.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Animations.Base; 2 | 3 | namespace eShopOnContainers.Triggers; 4 | 5 | public class BeginAnimation : TriggerAction 6 | { 7 | public AnimationBase Animation { get; set; } 8 | 9 | protected override async void Invoke(VisualElement sender) 10 | { 11 | if (Animation != null) 12 | await Animation.Begin(); 13 | } 14 | } -------------------------------------------------------------------------------- /eShopOnContainers/Extensions/ICommandExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Input; 2 | 3 | namespace eShopOnContainers; 4 | 5 | public static class ICommandExtensions 6 | { 7 | public static void AttemptNotifyCanExecuteChanged(this TCommand command) 8 | where TCommand : ICommand 9 | { 10 | if (command is IRelayCommand rc) 11 | { 12 | rc?.NotifyCanExecuteChanged(); 13 | } 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /eShopOnContainers/Views/CustomNavigationView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /eShopOnContainers/Converters/HasCountConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using CommunityToolkit.Maui.Converters; 3 | 4 | namespace eShopOnContainers.Converters; 5 | 6 | public class HasCountConverter : BaseConverterOneWay 7 | { 8 | public override bool DefaultConvertReturnValue { get; set; } = false; 9 | 10 | public override bool ConvertFrom(int value, CultureInfo culture) 11 | { 12 | return value > 0; 13 | } 14 | } -------------------------------------------------------------------------------- /eShopOnContainers/Platforms/Android/MainApplication.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.Runtime; 3 | 4 | namespace eShopOnContainers; 5 | 6 | [Application] 7 | public class MainApplication : MauiApplication 8 | { 9 | public MainApplication(IntPtr handle, JniHandleOwnership ownership) 10 | : base(handle, ownership) 11 | { 12 | } 13 | 14 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 15 | } 16 | -------------------------------------------------------------------------------- /eShopOnContainers/Platforms/iOS/Program.cs: -------------------------------------------------------------------------------- 1 | using UIKit; 2 | 3 | namespace eShopOnContainers; 4 | 5 | public class Program 6 | { 7 | // This is the main entry point of the application. 8 | static void Main(string[] args) 9 | { 10 | // if you want to use a different Application Delegate class from "AppDelegate" 11 | // you can specify it here. 12 | UIApplication.Main(args, null, typeof(AppDelegate)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /eShopOnContainers/Services/Catalog/ICatalogService.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Models.Catalog; 2 | 3 | namespace eShopOnContainers.Services.Catalog; 4 | 5 | public interface ICatalogService 6 | { 7 | Task> GetCatalogBrandAsync(); 8 | Task> FilterAsync(int catalogBrandId, int catalogTypeId); 9 | Task> GetCatalogTypeAsync(); 10 | Task> GetCatalogAsync(); 11 | } -------------------------------------------------------------------------------- /eShopOnContainers/ViewModels/Base/IViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Services; 2 | 3 | namespace eShopOnContainers.ViewModels.Base; 4 | 5 | public interface IViewModelBase : IQueryAttributable 6 | { 7 | public INavigationService NavigationService { get; } 8 | 9 | public IAsyncRelayCommand InitializeAsyncCommand { get; } 10 | 11 | public bool IsBusy { get; } 12 | 13 | public bool IsInitialized { get; } 14 | 15 | Task InitializeAsync(); 16 | } -------------------------------------------------------------------------------- /eShopOnContainers/Converters/DoesNotHaveCountConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using CommunityToolkit.Maui.Converters; 3 | 4 | namespace eShopOnContainers.Converters; 5 | 6 | public class DoesNotHaveCountConverter : BaseConverterOneWay 7 | { 8 | public override bool DefaultConvertReturnValue { get; set; } = false; 9 | 10 | public override bool ConvertFrom(int value, CultureInfo culture) 11 | { 12 | return value <= 0; 13 | } 14 | } -------------------------------------------------------------------------------- /eShopOnContainers/Platforms/MacCatalyst/Program.cs: -------------------------------------------------------------------------------- 1 | using UIKit; 2 | 3 | namespace eShopOnContainers; 4 | 5 | public class Program 6 | { 7 | // This is the main entry point of the application. 8 | static void Main(string[] args) 9 | { 10 | // if you want to use a different Application Delegate class from "AppDelegate" 11 | // you can specify it here. 12 | UIApplication.Main(args, null, typeof(AppDelegate)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /eShopOnContainers/Models/Marketing/CampaignItem.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.Marketing; 2 | 3 | public class CampaignItem 4 | { 5 | public int Id { get; set; } 6 | 7 | public string Name { get; set; } 8 | 9 | public string Description { get; set; } 10 | 11 | public DateTime From { get; set; } 12 | 13 | public DateTime To { get; set; } 14 | 15 | public string PictureUri { get; set; } 16 | 17 | public string DetailsUri { get; set; } 18 | } -------------------------------------------------------------------------------- /eShopOnContainers/Services/Common/Common.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Services.Common; 2 | 3 | public static class Common 4 | { 5 | public static string MockCatalogItemId01 = "1"; 6 | public static string MockCatalogItemId02 = "2"; 7 | public static string MockCatalogItemId03 = "3"; 8 | public static string MockCatalogItemId04 = "4"; 9 | public static string MockCatalogItemId05 = "5"; 10 | 11 | public static int MockCampaignId01 = 1; 12 | public static int MockCampaignId02 = 2; 13 | } -------------------------------------------------------------------------------- /eShopOnContainers/Services/FixUri/IFixUriService.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Models.Basket; 2 | using eShopOnContainers.Models.Catalog; 3 | using eShopOnContainers.Models.Marketing; 4 | 5 | namespace eShopOnContainers.Services.FixUri; 6 | 7 | public interface IFixUriService 8 | { 9 | void FixCatalogItemPictureUri(IEnumerable catalogItems); 10 | void FixBasketItemPictureUri(IEnumerable basketItems); 11 | void FixCampaignItemPictureUri(IEnumerable campaignItems); 12 | } -------------------------------------------------------------------------------- /eShopOnContainers/Platforms/MacCatalyst/Entitlements.Debug.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | com.apple.security.get-task-allow 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /eShopOnContainers/Models/Catalog/CatalogItem.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.Catalog; 2 | 3 | public class CatalogItem 4 | { 5 | public string Id { get; set; } 6 | public string Name { get; set; } 7 | public string Description { get; set; } 8 | public decimal Price { get; set; } 9 | public string PictureUri { get; set; } 10 | public int CatalogBrandId { get; set; } 11 | public string CatalogBrand { get; set; } 12 | public int CatalogTypeId { get; set; } 13 | public string CatalogType { get; set; } 14 | } -------------------------------------------------------------------------------- /eShopOnContainers/ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Services; 2 | using eShopOnContainers.ViewModels.Base; 3 | 4 | namespace eShopOnContainers.ViewModels; 5 | 6 | public partial class MainViewModel : ViewModelBase 7 | { 8 | public MainViewModel(INavigationService navigationService) 9 | : base(navigationService) 10 | { 11 | } 12 | 13 | [RelayCommand] 14 | private async Task SettingsAsync() 15 | { 16 | await NavigationService.NavigateToAsync("Settings"); 17 | } 18 | } -------------------------------------------------------------------------------- /eShopOnContainers/Converters/WebNavigatedEventArgsConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using CommunityToolkit.Maui.Converters; 3 | 4 | namespace eShopOnContainers.Converters; 5 | 6 | public class WebNavigatedEventArgsConverter : BaseConverterOneWay 7 | { 8 | public override string DefaultConvertReturnValue { get; set; } = string.Empty; 9 | 10 | public override string ConvertFrom(WebNavigatedEventArgs value, CultureInfo culture) 11 | { 12 | return value?.Url ?? string.Empty; 13 | } 14 | } -------------------------------------------------------------------------------- /eShopOnContainers/Services/Order/IOrderService.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Models.Basket; 2 | 3 | namespace eShopOnContainers.Services.Order; 4 | 5 | public interface IOrderService 6 | { 7 | Task CreateOrderAsync(Models.Orders.Order newOrder, string token); 8 | Task> GetOrdersAsync(string token); 9 | Task GetOrderAsync(int orderId, string token); 10 | Task CancelOrderAsync(int orderId, string token); 11 | BasketCheckout MapOrderToBasket(Models.Orders.Order order); 12 | } -------------------------------------------------------------------------------- /eShopOnContainers.UnitTests/Mocks/MockNavigationService.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.UnitTests.Mocks; 2 | 3 | public class MockNavigationService : INavigationService 4 | { 5 | public Task InitializeAsync() 6 | { 7 | return Task.CompletedTask; 8 | } 9 | 10 | public Task NavigateToAsync(string route, IDictionary? routeParameters = null) 11 | { 12 | return Task.CompletedTask; 13 | } 14 | 15 | public Task PopAsync() 16 | { 17 | return Task.CompletedTask; 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /eShopOnContainers/Models/User/PaymentInfo.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Models.Orders; 2 | 3 | namespace eShopOnContainers.Models.User; 4 | 5 | public class PaymentInfo 6 | { 7 | public Guid Id { get; set; } 8 | public string CardNumber { get; set; } 9 | public string SecurityNumber { get; set; } 10 | public int ExpirationMonth { get; set; } 11 | public int ExpirationYear { get; set; } 12 | public string CardHolderName { get; set; } 13 | public CardType CardType { get; set; } 14 | public string Expiration { get; set; } 15 | } -------------------------------------------------------------------------------- /eShopOnContainers/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage( 9 | "Interoperability", 10 | "CA1416:Validate platform compatibility", 11 | Justification = "Using latest Microsoft MAUI components", 12 | Scope = "module")] -------------------------------------------------------------------------------- /eShopOnContainers/Models/User/Address.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.User; 2 | 3 | public class Address 4 | { 5 | public Guid Id { get; set; } 6 | public string Street { get; set; } 7 | public string City { get; set; } 8 | public string State { get; set; } 9 | public string StateCode { get; set; } 10 | public string Country { get; set; } 11 | public string CountryCode { get; set; } 12 | public string ZipCode { get; set; } 13 | public double Latitude { get; set; } 14 | public double Longitude { get; set; } 15 | } -------------------------------------------------------------------------------- /eShopOnContainers/Services/Basket/IBasketService.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Models.Basket; 2 | 3 | namespace eShopOnContainers.Services.Basket; 4 | 5 | public interface IBasketService 6 | { 7 | IEnumerable LocalBasketItems { get; set; } 8 | Task GetBasketAsync(string guidUser, string token); 9 | Task UpdateBasketAsync(CustomerBasket customerBasket, string token); 10 | Task CheckoutAsync(BasketCheckout basketCheckout, string token); 11 | Task ClearBasketAsync(string guidUser, string token); 12 | } -------------------------------------------------------------------------------- /eShopOnContainers.UnitTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; 2 | global using eShopOnContainers.Services; 3 | global using eShopOnContainers.Services.AppEnvironment; 4 | global using eShopOnContainers.Services.Basket; 5 | global using eShopOnContainers.Services.Catalog; 6 | global using eShopOnContainers.Services.Marketing; 7 | global using eShopOnContainers.Services.Order; 8 | global using eShopOnContainers.Services.Settings; 9 | global using eShopOnContainers.Services.User; 10 | global using eShopOnContainers.UnitTests.Mocks; 11 | global using eShopOnContainers.ViewModels; -------------------------------------------------------------------------------- /eShopOnContainers/Platforms/MacCatalyst/Entitlements.Release.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | com.apple.security.app-sandbox 7 | 8 | com.apple.security.network.client 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /eShopOnContainers/Converters/FirstValidationErrorConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using CommunityToolkit.Maui.Converters; 3 | 4 | namespace eShopOnContainers.Converters; 5 | 6 | public class FirstValidationErrorConverter : BaseConverterOneWay, string> 7 | { 8 | public override string DefaultConvertReturnValue { get; set; } = string.Empty; 9 | 10 | public override string ConvertFrom(IEnumerable value, CultureInfo culture) 11 | { 12 | return value?.FirstOrDefault() ?? DefaultConvertReturnValue; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /eShopOnContainers/Converters/ItemsToHeightConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using CommunityToolkit.Maui.Converters; 3 | 4 | namespace eShopOnContainers.Converters 5 | { 6 | public class ItemsToHeightConverter : BaseConverterOneWay 7 | { 8 | private const int ItemHeight = 156; 9 | 10 | public override int DefaultConvertReturnValue { get; set; } = ItemHeight; 11 | 12 | public override int ConvertFrom(int value, CultureInfo culture) 13 | { 14 | return value * ItemHeight; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /eShopOnContainers/Resources/Raw/AboutAssets.txt: -------------------------------------------------------------------------------- 1 | Any raw assets you want to be deployed with your application can be placed in 2 | this directory (and child directories) and given a Build Action of "MauiAsset": 3 | 4 | 5 | 6 | These files will be deployed with you package and will be accessible using Essentials: 7 | 8 | async Task LoadMauiAsset() 9 | { 10 | using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt"); 11 | using var reader = new StreamReader(stream); 12 | 13 | var contents = reader.ReadToEnd(); 14 | } 15 | -------------------------------------------------------------------------------- /eShopOnContainers/Services/Settings/ISettingsService.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Services.Settings; 2 | 3 | public interface ISettingsService 4 | { 5 | string AuthAccessToken { get; set; } 6 | string AuthIdToken { get; set; } 7 | bool UseMocks { get; set; } 8 | string IdentityEndpointBase { get; set; } 9 | string GatewayShoppingEndpointBase { get; set; } 10 | string GatewayMarketingEndpointBase { get; set; } 11 | bool UseFakeLocation { get; set; } 12 | string Latitude { get; set; } 13 | string Longitude { get; set; } 14 | bool AllowGpsLocation { get; set; } 15 | } -------------------------------------------------------------------------------- /eShopOnContainers.UnitTests/TestingExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Input; 2 | using CommunityToolkit.Mvvm.Input; 3 | 4 | namespace eShopOnContainers.UnitTests 5 | { 6 | public static class TestingExtensions 7 | { 8 | public static async Task ExecuteUntilComplete(this ICommand command, object? parameter = null) 9 | { 10 | if (command is IAsyncRelayCommand arc) 11 | { 12 | await arc.ExecuteAsync(parameter); 13 | return; 14 | } 15 | 16 | command.Execute(parameter); 17 | } 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /eShopOnContainers/Services/RequestProvider/IRequestProvider.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Services.RequestProvider; 2 | 3 | public interface IRequestProvider 4 | { 5 | Task GetAsync(string uri, string token = ""); 6 | 7 | Task PostAsync(string uri, TResult data, string token = "", string header = ""); 8 | 9 | Task PostAsync(string uri, string data, string clientId, string clientSecret); 10 | 11 | Task PutAsync(string uri, TResult data, string token = "", string header = ""); 12 | 13 | Task DeleteAsync(string uri, string token = ""); 14 | } -------------------------------------------------------------------------------- /eShopOnContainers/Models/Token/UserToken.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace eShopOnContainers.Models.Token; 4 | 5 | public class UserToken 6 | { 7 | [JsonPropertyName("id_token")] 8 | public string IdToken { get; set; } 9 | 10 | [JsonPropertyName("access_token")] 11 | public string AccessToken { get; set; } 12 | 13 | [JsonPropertyName("expires_in")] 14 | public int ExpiresIn { get; set; } 15 | 16 | [JsonPropertyName("token_type")] 17 | public string TokenType { get; set; } 18 | 19 | [JsonPropertyName("refresh_token")] 20 | public string RefreshToken { get; set; } 21 | } -------------------------------------------------------------------------------- /eShopOnContainers/Views/ContentPageBase.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.ViewModels.Base; 2 | 3 | namespace eShopOnContainers.Views; 4 | 5 | public abstract class ContentPageBase : ContentPage 6 | { 7 | public ContentPageBase() 8 | { 9 | NavigationPage.SetBackButtonTitle(this, string.Empty); 10 | } 11 | 12 | protected override async void OnAppearing() 13 | { 14 | base.OnAppearing(); 15 | 16 | if (BindingContext is not IViewModelBase ivmb) 17 | { 18 | return; 19 | } 20 | 21 | await ivmb.InitializeAsyncCommand.ExecuteAsync(null); 22 | } 23 | } -------------------------------------------------------------------------------- /eShopOnContainers/Views/ProfileView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Views; 2 | 3 | public partial class ProfileView : ContentPageBase 4 | { 5 | private readonly ProfileViewModel _viewModel; 6 | 7 | public ProfileView(ProfileViewModel viewModel) 8 | { 9 | _viewModel = viewModel; 10 | BindingContext = viewModel; 11 | InitializeComponent(); 12 | } 13 | 14 | protected override void OnAppearing() 15 | { 16 | base.OnAppearing(); 17 | 18 | if (_viewModel.IsInitialized) 19 | { 20 | _viewModel.RefreshCommand.Execute(null); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /eShopOnContainers/Extensions/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers; 2 | 3 | public static class DictionaryExtensions 4 | { 5 | public static bool ValueAsBool(this IDictionary dictionary, string key, bool defaultValue = false) => 6 | dictionary.ContainsKey(key) && dictionary[key] is bool dictValue 7 | ? dictValue 8 | : defaultValue; 9 | 10 | public static int ValueAsInt(this IDictionary dictionary, string key, int defaultValue = 0) => 11 | dictionary.ContainsKey(key) && dictionary[key] is int intValue 12 | ? intValue 13 | : defaultValue; 14 | } 15 | -------------------------------------------------------------------------------- /eShopOnContainers/Helpers/RandomNumberGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace eShopOnContainers.Helpers; 4 | 5 | internal static class RandomNumberGenerator 6 | { 7 | public static string CreateUniqueId(int length = 64) 8 | { 9 | var bytes = PCLCrypto.WinRTCrypto.CryptographicBuffer.GenerateRandom(length); 10 | return ByteArrayToString(bytes); 11 | } 12 | 13 | private static string ByteArrayToString(byte[] array) 14 | { 15 | var hex = new StringBuilder(array.Length * 2); 16 | foreach (byte b in array) 17 | { 18 | hex.AppendFormat("{0:x2}", b); 19 | } 20 | return hex.ToString(); 21 | } 22 | } -------------------------------------------------------------------------------- /eShopOnContainers/Services/AppEnvironment/IAppEnvironmentService.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Services.Basket; 2 | using eShopOnContainers.Services.Catalog; 3 | using eShopOnContainers.Services.Marketing; 4 | using eShopOnContainers.Services.Order; 5 | using eShopOnContainers.Services.User; 6 | 7 | namespace eShopOnContainers.Services.AppEnvironment; 8 | 9 | public interface IAppEnvironmentService 10 | { 11 | IBasketService BasketService { get; } 12 | ICampaignService CampaignService { get; } 13 | ICatalogService CatalogService { get; } 14 | IOrderService OrderService { get; } 15 | IUserService UserService { get; } 16 | 17 | void UpdateDependencies(bool useMockServices); 18 | } -------------------------------------------------------------------------------- /eShopOnContainers/Services/RequestProvider/HttpRequestExceptionEx.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Services.RequestProvider; 2 | 3 | public class HttpRequestExceptionEx : HttpRequestException 4 | { 5 | public System.Net.HttpStatusCode HttpCode { get; } 6 | 7 | public HttpRequestExceptionEx(System.Net.HttpStatusCode code) : this(code, null, null) 8 | { 9 | } 10 | 11 | public HttpRequestExceptionEx(System.Net.HttpStatusCode code, string message) : this(code, message, null) 12 | { 13 | } 14 | 15 | public HttpRequestExceptionEx(System.Net.HttpStatusCode code, string message, Exception inner) : base(message, inner) 16 | { 17 | HttpCode = code; 18 | } 19 | } -------------------------------------------------------------------------------- /eShopOnContainers/Services/Identity/AuthorizeRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace eShopOnContainers.Services.Identity; 4 | 5 | public class AuthorizeRequest 6 | { 7 | readonly Uri _authorizeEndpoint; 8 | 9 | public AuthorizeRequest(string authorizeEndpoint) 10 | { 11 | _authorizeEndpoint = new Uri(authorizeEndpoint); 12 | } 13 | 14 | public string Create(IDictionary values) 15 | { 16 | var queryString = string.Join("&", values.Select(kvp => string.Format("{0}={1}", WebUtility.UrlEncode(kvp.Key), WebUtility.UrlEncode(kvp.Value))).ToArray()); 17 | return string.Format("{0}?{1}", _authorizeEndpoint.AbsoluteUri, queryString); 18 | } 19 | } -------------------------------------------------------------------------------- /eShopOnContainers.UnitTests/Services/OrdersServiceTests.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.UnitTests; 2 | 3 | public class OrdersServiceTests 4 | { 5 | [Fact] 6 | public async Task GetFakeOrderTest() 7 | { 8 | var ordersMockService = new OrderMockService(); 9 | var order = await ordersMockService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken); 10 | 11 | Assert.NotNull(order); 12 | } 13 | 14 | [Fact] 15 | public async Task GetFakeOrdersTest() 16 | { 17 | var ordersMockService = new OrderMockService(); 18 | var result = await ordersMockService.GetOrdersAsync(GlobalSetting.Instance.AuthToken); 19 | 20 | Assert.NotEmpty(result); 21 | } 22 | } -------------------------------------------------------------------------------- /eShopOnContainers.UnitTests/Services/MarketingServiceTests.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.UnitTests.Services; 2 | 3 | public class MarketingServiceTests 4 | { 5 | [Fact] 6 | public async Task GetFakeCampaigTest() 7 | { 8 | var campaignMockService = new CampaignMockService(); 9 | var order = await campaignMockService.GetCampaignByIdAsync(1, GlobalSetting.Instance.AuthToken); 10 | 11 | Assert.NotNull(order); 12 | } 13 | 14 | [Fact] 15 | public async Task GetFakeCampaignsTest() 16 | { 17 | var campaignMockService = new CampaignMockService(); 18 | var result = await campaignMockService.GetAllCampaignsAsync(GlobalSetting.Instance.AuthToken); 19 | 20 | Assert.NotEmpty(result); 21 | } 22 | } -------------------------------------------------------------------------------- /eShopOnContainers/Platforms/Android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /eShopOnContainers/Converters/DoubleConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using CommunityToolkit.Maui.Converters; 3 | 4 | namespace eShopOnContainers.Converters; 5 | 6 | public class DoubleConverter : BaseConverter 7 | { 8 | public override string DefaultConvertReturnValue { get; set; } = string.Empty; 9 | public override double DefaultConvertBackReturnValue { get; set; } = 0d; 10 | 11 | public override double ConvertBackTo(string value, CultureInfo culture) 12 | { 13 | return double.TryParse(value, out var parsed) ? parsed : DefaultConvertBackReturnValue; 14 | } 15 | 16 | public override string ConvertFrom(double value, CultureInfo culture) 17 | { 18 | return value.ToString(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /eShopOnContainers/Helpers/UriHelper.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Helpers; 2 | 3 | public static class UriHelper 4 | { 5 | public static string CombineUri(params string[] uriParts) 6 | { 7 | string uri = string.Empty; 8 | if (uriParts != null && uriParts.Length > 0) 9 | { 10 | char[] trims = new char[] { '\\', '/' }; 11 | uri = (uriParts[0] ?? string.Empty).TrimEnd(trims); 12 | for (int i = 1; i < uriParts.Length; i++) 13 | { 14 | uri = string.Format("{0}/{1}", uri.TrimEnd(trims), (uriParts[i] ?? string.Empty).TrimStart(trims)); 15 | } 16 | } 17 | return uri; 18 | } 19 | } 20 | 21 | 22 | 23 | 24 | 25 | // test 26 | -------------------------------------------------------------------------------- /eShopOnContainers/Services/User/UserService.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Helpers; 2 | using eShopOnContainers.Models.User; 3 | using eShopOnContainers.Services.RequestProvider; 4 | 5 | namespace eShopOnContainers.Services.User; 6 | 7 | public class UserService : IUserService 8 | { 9 | private readonly IRequestProvider _requestProvider; 10 | 11 | public UserService(IRequestProvider requestProvider) 12 | { 13 | _requestProvider = requestProvider; 14 | } 15 | 16 | public async Task GetUserInfoAsync(string authToken) 17 | { 18 | var uri = UriHelper.CombineUri(GlobalSetting.Instance.UserInfoEndpoint); 19 | 20 | var userInfo = await _requestProvider.GetAsync(uri, authToken); 21 | return userInfo; 22 | } 23 | } -------------------------------------------------------------------------------- /eShopOnContainers/Views/MapView.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Maui.Controls.Maps; 2 | 3 | namespace eShopOnContainers.Views; 4 | 5 | public partial class MapView : ContentPageBase 6 | { 7 | public MapView(MapViewModel viewModel) 8 | { 9 | BindingContext = viewModel; 10 | InitializeComponent(); 11 | 12 | var map = new Microsoft.Maui.Controls.Maps.Map(new Microsoft.Maui.Maps.MapSpan(new Location(0, 0), 0,0)); 13 | } 14 | 15 | async void Pin_MarkerClicked(System.Object sender, Microsoft.Maui.Controls.Maps.PinClickedEventArgs e) 16 | { 17 | e.HideInfoWindow = true; 18 | 19 | if (sender is not Pin pin) 20 | { 21 | return; 22 | } 23 | 24 | await pin.Location.OpenMapsAsync(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /eShopOnContainers.UnitTests/ViewModels/MainViewModelTests.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.UnitTests; 2 | 3 | public class MainViewModelTests 4 | { 5 | private readonly INavigationService _navigationService; 6 | 7 | public MainViewModelTests() 8 | { 9 | _navigationService = new MockNavigationService(); 10 | } 11 | 12 | [Fact] 13 | public void SettingsCommandIsNotNullWhenViewModelInstantiatedTest() 14 | { 15 | var mainViewModel = new MainViewModel(_navigationService); 16 | Assert.NotNull(mainViewModel.SettingsCommand); 17 | } 18 | 19 | [Fact] 20 | public void IsBusyPropertyIsFalseWhenViewModelInstantiatedTest() 21 | { 22 | var mainViewModel = new MainViewModel(_navigationService); 23 | Assert.False(mainViewModel.IsBusy); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /eShopOnContainers/Platforms/Windows/App.xaml.cs: -------------------------------------------------------------------------------- 1 | // To learn more about WinUI, the WinUI project structure, 2 | // and more about our project templates, see: http://aka.ms/winui-project-info. 3 | 4 | namespace eShopOnContainers.WinUI; 5 | 6 | /// 7 | /// Provides application-specific behavior to supplement the default Application class. 8 | /// 9 | public partial class App : MauiWinUIApplication 10 | { 11 | /// 12 | /// Initializes the singleton application object. This is the first line of authored code 13 | /// executed, and as such is the logical equivalent of main() or WinMain(). 14 | /// 15 | public App() => this.InitializeComponent(); 16 | 17 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 18 | } 19 | 20 | -------------------------------------------------------------------------------- /eShopOnContainers/Services/Location/LocationService.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Helpers; 2 | using eShopOnContainers.Services.RequestProvider; 3 | 4 | namespace eShopOnContainers.Services.Location; 5 | 6 | public class LocationService : ILocationService 7 | { 8 | private readonly IRequestProvider _requestProvider; 9 | 10 | private const string ApiUrlBase = "l/api/v1/locations"; 11 | 12 | public LocationService(IRequestProvider requestProvider) 13 | { 14 | _requestProvider = requestProvider; 15 | } 16 | 17 | public async Task UpdateUserLocation(Models.Location.Location newLocReq, string token) 18 | { 19 | var uri = UriHelper.CombineUri(GlobalSetting.Instance.GatewayMarketingEndpoint, ApiUrlBase); 20 | 21 | await _requestProvider.PostAsync(uri, newLocReq, token); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /eShopOnContainers/Platforms/Windows/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | true/PM 12 | PerMonitorV2, PerMonitor 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /eShopOnContainers/Helpers/EasingHelper.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Animations.Base; 2 | 3 | namespace eShopOnContainers.Helpers; 4 | 5 | public static class EasingHelper 6 | { 7 | public static Easing GetEasing(EasingType type) => type switch 8 | { 9 | EasingType.BounceIn => Easing.BounceIn, 10 | EasingType.BounceOut => Easing.BounceOut, 11 | EasingType.CubicIn => Easing.CubicIn, 12 | EasingType.CubicInOut => Easing.CubicInOut, 13 | EasingType.CubicOut => Easing.CubicOut, 14 | EasingType.Linear => Easing.Linear, 15 | EasingType.SinIn => Easing.SinIn, 16 | EasingType.SinInOut => Easing.SinInOut, 17 | EasingType.SinOut => Easing.SinOut, 18 | EasingType.SpringIn => Easing.SpringIn, 19 | EasingType.SpringOut => Easing.SpringOut, 20 | _ => null, 21 | }; 22 | } -------------------------------------------------------------------------------- /eShopOnContainers/Models/Orders/OrderItem.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace eShopOnContainers.Models.Orders; 4 | 5 | public class OrderItem 6 | { 7 | public string ProductId { get; set; } 8 | public Guid? OrderId { get; set; } 9 | 10 | [JsonPropertyName("unitprice")] 11 | public decimal UnitPrice { get; set; } 12 | 13 | [JsonPropertyName("productname")] 14 | public string ProductName { get; set; } 15 | 16 | [JsonPropertyName("pictureurl")] 17 | public string PictureUrl { get; set; } 18 | 19 | [JsonPropertyName("units")] 20 | public int Quantity { get; set; } 21 | 22 | public decimal Discount { get; set; } 23 | public decimal Total => Quantity * UnitPrice; 24 | 25 | public override string ToString() 26 | { 27 | return String.Format("Product Id: {0}, Quantity: {1}", ProductId, Quantity); 28 | } 29 | } -------------------------------------------------------------------------------- /eShopOnContainers/Controls/AddBasketButton.xaml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 14 | 21 | -------------------------------------------------------------------------------- /eShopOnContainers/Models/Basket/BasketItem.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.Basket; 2 | 3 | public class BasketItem : BindableObject 4 | { 5 | private int _quantity; 6 | 7 | public string Id { get; set; } 8 | 9 | public string ProductId { get; set; } 10 | 11 | public string ProductName { get; set; } 12 | 13 | public decimal UnitPrice { get; set; } 14 | 15 | public decimal OldUnitPrice { get; set; } 16 | 17 | public bool HasNewPrice => OldUnitPrice != 0.0m; 18 | 19 | public int Quantity 20 | { 21 | get => _quantity; 22 | set 23 | { 24 | _quantity = value; 25 | OnPropertyChanged(nameof(Quantity)); 26 | } 27 | } 28 | 29 | public string PictureUrl { get; set; } 30 | 31 | public decimal Total => Quantity * UnitPrice; 32 | 33 | public override string ToString() => 34 | $"Product Id: {ProductId}, Quantity: {Quantity}"; 35 | } -------------------------------------------------------------------------------- /eShopOnContainers/Platforms/Android/MainActivity.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.Content.PM; 3 | 4 | namespace eShopOnContainers; 5 | 6 | [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] 7 | [IntentFilter(new[] { Platform.Intent.ActionAppAction }, Categories = new[] { global::Android.Content.Intent.CategoryDefault })] 8 | public class MainActivity : MauiAppCompatActivity 9 | { 10 | protected override void OnResume() 11 | { 12 | base.OnResume(); 13 | 14 | Platform.OnResume(this); 15 | } 16 | 17 | protected override void OnNewIntent(Android.Content.Intent intent) 18 | { 19 | base.OnNewIntent(intent); 20 | 21 | Platform.OnNewIntent(intent); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /eShopOnContainers/Models/Basket/BasketCheckout.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace eShopOnContainers.Models.Basket; 4 | 5 | public class BasketCheckout 6 | { 7 | [Required] 8 | public string City { get; set; } 9 | [Required] 10 | public string Street { get; set; } 11 | [Required] 12 | public string State { get; set; } 13 | [Required] 14 | public string Country { get; set; } 15 | 16 | public string ZipCode { get; set; } 17 | [Required] 18 | public string CardNumber { get; set; } 19 | [Required] 20 | public string CardHolderName { get; set; } 21 | 22 | [Required] 23 | public DateTime CardExpiration { get; set; } 24 | 25 | [Required] 26 | public string CardSecurityNumber { get; set; } 27 | 28 | public int CardTypeId { get; set; } 29 | 30 | public string Buyer { get; set; } 31 | 32 | [Required] 33 | public Guid RequestId { get; set; } 34 | } 35 | -------------------------------------------------------------------------------- /eShopOnContainers/Converters/OrderStatusToStringConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using CommunityToolkit.Maui.Converters; 3 | using eShopOnContainers.Models.Orders; 4 | 5 | namespace eShopOnContainers.Converters; 6 | 7 | public class OrderStatusToStringConverter : BaseConverterOneWay 8 | { 9 | public override string DefaultConvertReturnValue { get; set; } = string.Empty; 10 | 11 | public override string ConvertFrom(OrderStatus value, CultureInfo culture) 12 | { 13 | return value switch 14 | { 15 | OrderStatus.AwaitingValidation => "AWAITING VALIDATION", 16 | OrderStatus.Cancelled => "CANCELLED", 17 | OrderStatus.Paid => "PAID", 18 | OrderStatus.Shipped => "SHIPPED", 19 | OrderStatus.StockConfirmed => "STOCK CONFIRMED", 20 | OrderStatus.Submitted => "SUBMITTED", 21 | _ => string.Empty 22 | }; 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /eShopOnContainers/Models/Location/GeolocationException.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.Location; 2 | 3 | public class GeolocationException : Exception 4 | { 5 | public GeolocationError Error { get; private set; } 6 | 7 | public GeolocationException(GeolocationError error) 8 | : base("A geolocation error occured: " + error) 9 | { 10 | if (!Enum.IsDefined(typeof(GeolocationError), error)) 11 | throw new ArgumentException("error is not a valid GelocationError member", nameof(error)); 12 | 13 | Error = error; 14 | } 15 | 16 | public GeolocationException(GeolocationError error, Exception innerException) 17 | : base("A geolocation error occured: " + error, innerException) 18 | { 19 | if (!Enum.IsDefined(typeof(GeolocationError), error)) 20 | throw new ArgumentException("error is not a valid GelocationError member", nameof(error)); 21 | 22 | Error = error; 23 | } 24 | } -------------------------------------------------------------------------------- /eShopOnContainers.UnitTests/Services/CatalogServiceTests.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.UnitTests; 2 | 3 | public class CatalogServiceTests 4 | { 5 | [Fact] 6 | public async Task GetFakeCatalogTest() 7 | { 8 | var catalogMockService = new CatalogMockService(); 9 | var catalog = await catalogMockService.GetCatalogAsync(); 10 | 11 | Assert.NotEmpty(catalog); 12 | } 13 | 14 | [Fact] 15 | public async Task GetFakeCatalogBrandTest() 16 | { 17 | var catalogMockService = new CatalogMockService(); 18 | var catalogBrand = await catalogMockService.GetCatalogBrandAsync(); 19 | 20 | Assert.NotEmpty(catalogBrand); 21 | } 22 | 23 | [Fact] 24 | public async Task GetFakeCatalogTypeTest() 25 | { 26 | var catalogMockService = new CatalogMockService(); 27 | var catalogType = await catalogMockService.GetCatalogTypeAsync(); 28 | 29 | Assert.NotEmpty(catalogType); 30 | } 31 | } -------------------------------------------------------------------------------- /eShopOnContainers/Services/User/UserMockService.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Models.User; 2 | 3 | namespace eShopOnContainers.Services.User; 4 | 5 | public class UserMockService : IUserService 6 | { 7 | private readonly UserInfo MockUserInfo = new() 8 | { 9 | UserId = Guid.NewGuid().ToString(), 10 | Name = "Jhon", 11 | LastName = "Doe", 12 | PreferredUsername = "Jdoe", 13 | Email = "jdoe@eshop.com", 14 | EmailVerified = true, 15 | PhoneNumber = "202-555-0165", 16 | PhoneNumberVerified = true, 17 | Address = "Seattle, WA", 18 | Street = "120 E 87th Street", 19 | ZipCode = "98101", 20 | Country = "United States", 21 | State = "Seattle", 22 | CardNumber = "378282246310005", 23 | CardHolder = "American Express", 24 | CardSecurityNumber = "1234", 25 | }; 26 | 27 | public async Task GetUserInfoAsync(string authToken) 28 | { 29 | await Task.Delay(10); 30 | return MockUserInfo; 31 | } 32 | } -------------------------------------------------------------------------------- /eShopOnContainers.UnitTests/Mocks/MockViewModel.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Validations; 2 | using eShopOnContainers.ViewModels.Base; 3 | 4 | namespace eShopOnContainers.UnitTests; 5 | 6 | public class MockViewModel : ViewModelBase 7 | { 8 | public ValidatableObject Forename { get; } = new(); 9 | 10 | public ValidatableObject Surname { get; } = new(); 11 | 12 | public MockViewModel(INavigationService navigationService) 13 | : base(navigationService) 14 | { 15 | Forename = new ValidatableObject(); 16 | Surname = new ValidatableObject(); 17 | 18 | Forename.Validations.Add(new IsNotNullOrEmptyRule { ValidationMessage = "Forename is required." }); 19 | Surname.Validations.Add(new IsNotNullOrEmptyRule { ValidationMessage = "Surname name is required." }); 20 | } 21 | 22 | public bool Validate() 23 | { 24 | bool isValidForename = Forename.Validate(); 25 | bool isValidSurname = Surname.Validate(); 26 | return isValidForename && isValidSurname; 27 | } 28 | } -------------------------------------------------------------------------------- /eShopOnContainers/Animations/StoryBoard.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Animations.Base; 2 | 3 | namespace eShopOnContainers.Animations; 4 | 5 | [ContentProperty("Animations")] 6 | public class StoryBoard : AnimationBase 7 | { 8 | public List Animations { get; } 9 | 10 | public StoryBoard() 11 | { 12 | Animations = new(); 13 | } 14 | 15 | public StoryBoard(List animations) 16 | { 17 | Animations = animations; 18 | } 19 | 20 | protected override async Task BeginAnimation() 21 | { 22 | foreach (var animation in Animations) 23 | { 24 | if (animation.Target == null) 25 | animation.Target = Target; 26 | 27 | await animation.Begin(); 28 | } 29 | } 30 | 31 | protected override async Task ResetAnimation() 32 | { 33 | foreach (var animation in Animations) 34 | { 35 | if (animation.Target == null) 36 | animation.Target = Target; 37 | 38 | await animation.Reset(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /eShopOnContainers/ViewModels/MapViewModel.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Services; 2 | using eShopOnContainers.ViewModels.Base; 3 | 4 | namespace eShopOnContainers.ViewModels; 5 | 6 | public partial class MapViewModel : ViewModelBase 7 | { 8 | [ObservableProperty] 9 | private IEnumerable _stores; 10 | 11 | public MapViewModel(INavigationService navigationService) 12 | : base(navigationService) 13 | { 14 | } 15 | 16 | public override Task InitializeAsync() 17 | { 18 | Stores = 19 | new[] 20 | { 21 | new Store 22 | { 23 | Address = "Building 92, Redmond, WA", 24 | Description = "Microsoft Visitor Center", 25 | Location = new Location(47.6423109, -122.1368406), 26 | }, 27 | }; 28 | 29 | return Task.CompletedTask; 30 | } 31 | } 32 | 33 | public record Store 34 | { 35 | public Location Location { get; set; } 36 | public string Address { get; set; } 37 | public string Description { get; set; } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /eShopOnContainers/Converters/WebNavigatingEventArgsConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using CommunityToolkit.Maui.Converters; 3 | 4 | namespace eShopOnContainers.Converters 5 | { 6 | public class WebNavigatingEventArgsConverter : ICommunityToolkitValueConverter 7 | { 8 | public Type FromType => typeof(WebNavigatingEventArgs); 9 | 10 | public Type ToType => typeof(string); 11 | 12 | public object DefaultConvertReturnValue => string.Empty; 13 | 14 | public object DefaultConvertBackReturnValue => null; 15 | 16 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 17 | { 18 | var eventArgs = value as WebNavigatingEventArgs; 19 | if (eventArgs == null) 20 | throw new ArgumentException("Expected WebNavigatingEventArgs as value", "value"); 21 | 22 | return eventArgs.Url; 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /eShopOnContainers/Services/Navigation/MauiNavigationService.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Services.Settings; 2 | 3 | namespace eShopOnContainers.Services; 4 | 5 | public class MauiNavigationService : INavigationService 6 | { 7 | private readonly ISettingsService _settingsService; 8 | 9 | public MauiNavigationService(ISettingsService settingsService) 10 | { 11 | _settingsService = settingsService; 12 | } 13 | 14 | public Task InitializeAsync() => 15 | NavigateToAsync( 16 | string.IsNullOrEmpty(_settingsService.AuthAccessToken) 17 | ? "//Login" 18 | : "//Main/Catalog"); 19 | 20 | public Task NavigateToAsync(string route, IDictionary routeParameters = null) 21 | { 22 | var shellNavigation = new ShellNavigationState(route); 23 | 24 | return routeParameters != null 25 | ? Shell.Current.GoToAsync(shellNavigation, routeParameters) 26 | : Shell.Current.GoToAsync(shellNavigation); 27 | } 28 | 29 | public Task PopAsync() => 30 | Shell.Current.GoToAsync(".."); 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 .NET Application Architecture - Reference Apps 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /eShopOnContainers/Controls/CustomTabbedPage.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Controls; 2 | 3 | public class CustomTabbedPage : TabbedPage 4 | { 5 | public static BindableProperty BadgeTextProperty = 6 | BindableProperty.CreateAttached("BadgeText", typeof(string), typeof(CustomTabbedPage), default(string), 7 | BindingMode.OneWay); 8 | 9 | public static BindableProperty BadgeColorProperty = 10 | BindableProperty.CreateAttached("BadgeColor", typeof(Color), typeof(CustomTabbedPage), Colors.Transparent, 11 | BindingMode.OneWay); 12 | 13 | public static string GetBadgeText(BindableObject view) 14 | { 15 | return (string)view.GetValue(BadgeTextProperty); 16 | } 17 | 18 | public static void SetBadgeText(BindableObject view, string value) 19 | { 20 | view.SetValue(BadgeTextProperty, value); 21 | } 22 | 23 | public static Color GetBadgeColor(BindableObject view) 24 | { 25 | return (Color)view.GetValue(BadgeColorProperty); 26 | } 27 | 28 | public static void SetBadgeColor(BindableObject view, Color value) 29 | { 30 | view.SetValue(BadgeColorProperty, value); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /eShopOnContainers.UnitTests/eShopOnContainers.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | false 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | all 17 | 18 | 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | all 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /eShopOnContainers/Views/CatalogView.xaml.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.Messaging; 2 | 3 | namespace eShopOnContainers.Views; 4 | 5 | public partial class CatalogView : ContentPageBase 6 | { 7 | public CatalogView(CatalogViewModel viewModel) 8 | { 9 | BindingContext = viewModel; 10 | 11 | InitializeComponent(); 12 | } 13 | 14 | protected override void OnAppearing() 15 | { 16 | base.OnAppearing(); 17 | 18 | WeakReferenceMessenger.Default 19 | .Register( 20 | this, 21 | async (recipient, message) => 22 | { 23 | await recipient.Dispatcher.DispatchAsync( 24 | async () => 25 | { 26 | await recipient.badge.ScaleTo(1.2); 27 | await recipient.badge.ScaleTo(1.0); 28 | }); 29 | }); 30 | } 31 | 32 | protected override void OnDisappearing() 33 | { 34 | base.OnDisappearing(); 35 | 36 | WeakReferenceMessenger.Default.Unregister(this); 37 | } 38 | } -------------------------------------------------------------------------------- /eShopOnContainers/Platforms/Windows/Package.appxmanifest: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | User Name 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /eShopOnContainers/Models/Location/Position.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Models.Location; 2 | 3 | public class Position 4 | { 5 | public DateTimeOffset Timestamp { get; set; } 6 | public double Latitude { get; set; } 7 | public double Longitude { get; set; } 8 | public double Altitude { get; set; } 9 | public double Accuracy { get; set; } 10 | public double AltitudeAccuracy { get; set; } 11 | public double Heading { get; set; } 12 | public double Speed { get; set; } 13 | 14 | public Position() 15 | { 16 | } 17 | 18 | public Position(double latitude, double longitude) 19 | { 20 | 21 | Timestamp = DateTimeOffset.UtcNow; 22 | Latitude = latitude; 23 | Longitude = longitude; 24 | } 25 | 26 | public Position(Position position) 27 | { 28 | if (position == null) 29 | throw new ArgumentNullException(nameof(position)); 30 | 31 | Timestamp = position.Timestamp; 32 | Latitude = position.Latitude; 33 | Longitude = position.Longitude; 34 | Altitude = position.Altitude; 35 | AltitudeAccuracy = position.AltitudeAccuracy; 36 | Accuracy = position.Accuracy; 37 | Heading = position.Heading; 38 | Speed = position.Speed; 39 | } 40 | } -------------------------------------------------------------------------------- /eShopOnContainers/Validations/ValidatableObject.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Validations; 2 | 3 | public class ValidatableObject : ObservableObject, IValidity 4 | { 5 | private IEnumerable _errors; 6 | private bool _isValid; 7 | private T _value; 8 | 9 | public List> Validations { get; } = new(); 10 | 11 | public IEnumerable Errors 12 | { 13 | get => _errors; 14 | private set => SetProperty(ref _errors, value); 15 | } 16 | 17 | public bool IsValid 18 | { 19 | get => _isValid; 20 | private set => SetProperty(ref _isValid, value); 21 | } 22 | 23 | public T Value 24 | { 25 | get => _value; 26 | set => SetProperty(ref _value, value); 27 | } 28 | 29 | public ValidatableObject() 30 | { 31 | _isValid = true; 32 | _errors = Enumerable.Empty(); 33 | } 34 | 35 | public bool Validate() 36 | { 37 | Errors = Validations 38 | ?.Where(v => !v.Check(Value)) 39 | ?.Select(v => v.ValidationMessage) 40 | ?.ToArray() 41 | ?? Enumerable.Empty(); 42 | 43 | IsValid = !Errors.Any(); 44 | 45 | return IsValid; 46 | } 47 | } -------------------------------------------------------------------------------- /eShopOnContainers/AppShell.xaml.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Services; 2 | using eShopOnContainers.Views; 3 | 4 | namespace eShopOnContainers; 5 | 6 | public partial class AppShell : Shell 7 | { 8 | private readonly INavigationService _navigationService; 9 | 10 | public AppShell(INavigationService navigationService) 11 | { 12 | _navigationService = navigationService; 13 | 14 | AppShell.InitializeRouting(); 15 | InitializeComponent(); 16 | } 17 | 18 | protected override async void OnHandlerChanged() 19 | { 20 | base.OnHandlerChanged(); 21 | 22 | if (Handler is not null) 23 | { 24 | await _navigationService.InitializeAsync(); 25 | } 26 | } 27 | 28 | private static void InitializeRouting() 29 | { 30 | Routing.RegisterRoute("Filter", typeof(FiltersView)); 31 | Routing.RegisterRoute("Basket", typeof(BasketView)); 32 | Routing.RegisterRoute("Basket", typeof(BasketView)); 33 | Routing.RegisterRoute("Settings", typeof(SettingsView)); 34 | Routing.RegisterRoute("OrderDetail", typeof(OrderDetailView)); 35 | Routing.RegisterRoute("CampaignDetails", typeof(CampaignDetailsView)); 36 | Routing.RegisterRoute("Checkout", typeof(CheckoutView)); 37 | } 38 | } -------------------------------------------------------------------------------- /eShopOnContainers/Platforms/MacCatalyst/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIDeviceFamily 6 | 7 | 1 8 | 2 9 | 10 | UIRequiredDeviceCapabilities 11 | 12 | arm64 13 | 14 | UISupportedInterfaceOrientations 15 | 16 | UIInterfaceOrientationPortrait 17 | UIInterfaceOrientationLandscapeLeft 18 | UIInterfaceOrientationLandscapeRight 19 | 20 | UISupportedInterfaceOrientations~ipad 21 | 22 | UIInterfaceOrientationPortrait 23 | UIInterfaceOrientationPortraitUpsideDown 24 | UIInterfaceOrientationLandscapeLeft 25 | UIInterfaceOrientationLandscapeRight 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | 32 | XSAppIconAssets 33 | Assets.xcassets/appicon.appiconset 34 | NSLocationWhenInUseUsageDescription 35 | Can we use your location when your app is being used? 36 | 37 | 38 | -------------------------------------------------------------------------------- /.github/workflows/build-validation.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths: 7 | - '**.cs' 8 | - '**.csproj' 9 | - '**.xaml' 10 | pull_request: 11 | branches: [ main ] 12 | paths: 13 | - '**.cs' 14 | - '**.csproj' 15 | - '**.xaml' 16 | 17 | env: 18 | DOTNET_VERSION: '8.0.x' # The .NET SDK version to use 19 | 20 | jobs: 21 | build: 22 | 23 | name: Build All Projects 24 | runs-on: windows-latest 25 | 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | project: 30 | - eShopOnContainers 31 | - eShopOnContainers.UnitTests 32 | 33 | steps: 34 | - uses: actions/checkout@v3 35 | - name: Setup .NET 36 | uses: actions/setup-dotnet@v3 37 | with: 38 | dotnet-version: ${{ env.DOTNET_VERSION }} 39 | 40 | - name: Setup MSBuild.exe 41 | uses: microsoft/setup-msbuild@v1.1 42 | with: 43 | vs-prerelease: true 44 | 45 | - name: Install .NET workloads 46 | shell: pwsh 47 | run: | 48 | dotnet workload install android 49 | dotnet workload install ios 50 | dotnet workload install maccatalyst 51 | dotnet workload install maui 52 | 53 | - name: Build ${{ matrix.project }} 54 | run: dotnet build "${{ matrix.project }}/${{ matrix.project }}.csproj" 55 | -------------------------------------------------------------------------------- /eShopOnContainers/Effects/ThemeEffects.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Effects; 2 | 3 | public static class ThemeEffects 4 | { 5 | public static readonly BindableProperty CircleProperty = 6 | BindableProperty.CreateAttached("Circle", typeof(bool), typeof(ThemeEffects), false, propertyChanged: OnChanged); 7 | 8 | public static bool GetCircle(BindableObject view) 9 | { 10 | return (bool)view.GetValue(CircleProperty); 11 | } 12 | 13 | public static void SetCircle(BindableObject view, bool circle) 14 | { 15 | view.SetValue(CircleProperty, circle); 16 | } 17 | 18 | 19 | private static void OnChanged(BindableObject bindable, object oldValue, object newValue) 20 | where TEffect : Effect, new() 21 | { 22 | if (bindable is not View view) 23 | { 24 | return; 25 | } 26 | 27 | if (Equals(newValue, default(TProp))) 28 | { 29 | var toRemove = view.Effects.FirstOrDefault(e => e is TEffect); 30 | if (toRemove != null) 31 | { 32 | view.Effects.Remove(toRemove); 33 | } 34 | } 35 | else 36 | { 37 | view.Effects.Add(new TEffect()); 38 | } 39 | } 40 | 41 | private class CircleEffect : RoutingEffect 42 | { 43 | public CircleEffect() 44 | : base("eShopOnContainers.CircleEffect") 45 | { 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /eShopOnContainers/Services/Marketing/CampaignMockService.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Models.Marketing; 2 | 3 | namespace eShopOnContainers.Services.Marketing; 4 | 5 | public class CampaignMockService : ICampaignService 6 | { 7 | private readonly IEnumerable _mockCampaign = 8 | new[] 9 | { 10 | new CampaignItem 11 | { 12 | Id = Common.Common.MockCampaignId01, 13 | PictureUri = "fake_campaign_01.png", 14 | Name = ".NET Bot Black Hoodie 50% OFF", 15 | Description = "Campaign Description 1", 16 | From = DateTime.Now, 17 | To = DateTime.Now.AddDays(7) 18 | }, 19 | 20 | new CampaignItem 21 | { 22 | Id = Common.Common.MockCampaignId02, 23 | PictureUri = "fake_campaign_02.png", 24 | Name = "Roslyn Red T-Shirt 3x2", 25 | Description = "Campaign Description 2", 26 | From = DateTime.Now.AddDays(-7), 27 | To = DateTime.Now.AddDays(14) 28 | } 29 | }; 30 | 31 | public async Task> GetAllCampaignsAsync(string token) 32 | { 33 | await Task.Delay(10); 34 | return _mockCampaign; 35 | } 36 | 37 | public async Task GetCampaignByIdAsync(int campaignId, string token) 38 | { 39 | await Task.Delay(10); 40 | return _mockCampaign.SingleOrDefault(c => c.Id == campaignId); 41 | } 42 | } -------------------------------------------------------------------------------- /eShopOnContainers/Platforms/iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LSRequiresIPhoneOS 6 | 7 | UIDeviceFamily 8 | 9 | 1 10 | 2 11 | 12 | UIRequiredDeviceCapabilities 13 | 14 | arm64 15 | 16 | UISupportedInterfaceOrientations 17 | 18 | UIInterfaceOrientationPortrait 19 | UIInterfaceOrientationLandscapeLeft 20 | UIInterfaceOrientationLandscapeRight 21 | 22 | UISupportedInterfaceOrientations~ipad 23 | 24 | UIInterfaceOrientationPortrait 25 | UIInterfaceOrientationPortraitUpsideDown 26 | UIInterfaceOrientationLandscapeLeft 27 | UIInterfaceOrientationLandscapeRight 28 | 29 | XSAppIconAssets 30 | Assets.xcassets/appicon.appiconset 31 | NSLocationWhenInUseUsageDescription 32 | Can we use your location when your app is being used? 33 | 34 | -------------------------------------------------------------------------------- /eShopOnContainers/Models/Orders/Order.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace eShopOnContainers.Models.Orders; 4 | 5 | public class Order 6 | { 7 | public Order() 8 | { 9 | SequenceNumber = 1; 10 | OrderItems = new List(); 11 | } 12 | 13 | public string BuyerId { get; set; } 14 | 15 | public int SequenceNumber { get; set; } 16 | 17 | [JsonPropertyName("date")] 18 | public DateTime OrderDate { get; set; } 19 | 20 | [JsonPropertyName("status")] 21 | public OrderStatus OrderStatus { get; set; } 22 | 23 | [JsonPropertyName("city")] 24 | public string ShippingCity { get; set; } 25 | 26 | [JsonPropertyName("street")] 27 | public string ShippingStreet { get; set; } 28 | 29 | [JsonPropertyName("state")] 30 | public string ShippingState { get; set; } 31 | 32 | [JsonPropertyName("country")] 33 | public string ShippingCountry { get; set; } 34 | 35 | [JsonPropertyName("zipCode")] 36 | public string ShippingZipCode { get; set; } 37 | 38 | public int CardTypeId { get; set; } 39 | 40 | public string CardNumber { get; set; } 41 | 42 | public string CardHolderName { get; set; } 43 | 44 | public DateTime CardExpiration { get; set; } 45 | 46 | public string CardSecurityNumber { get; set; } 47 | 48 | [JsonPropertyName("orderitems")] 49 | public List OrderItems { get; set; } 50 | 51 | [JsonPropertyName("total")] 52 | public decimal Total { get; set; } 53 | 54 | [JsonPropertyName("ordernumber")] 55 | public int OrderNumber { get; set; } 56 | } -------------------------------------------------------------------------------- /eShopOnContainers/ViewModels/Base/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Services; 2 | 3 | namespace eShopOnContainers.ViewModels.Base; 4 | 5 | public abstract partial class ViewModelBase : ObservableObject, IViewModelBase 6 | { 7 | private long _isBusy; 8 | 9 | public bool IsBusy => Interlocked.Read(ref _isBusy) > 0; 10 | 11 | [ObservableProperty] 12 | private bool _isInitialized; 13 | 14 | public INavigationService NavigationService { get; } 15 | 16 | public IAsyncRelayCommand InitializeAsyncCommand { get; } 17 | 18 | public ViewModelBase(INavigationService navigationService) 19 | { 20 | NavigationService = navigationService; 21 | 22 | InitializeAsyncCommand = 23 | new AsyncRelayCommand( 24 | async () => 25 | { 26 | await IsBusyFor(InitializeAsync); 27 | IsInitialized = true; 28 | }, 29 | AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler); 30 | } 31 | 32 | public virtual void ApplyQueryAttributes(IDictionary query) 33 | { 34 | } 35 | 36 | public virtual Task InitializeAsync() 37 | { 38 | return Task.CompletedTask; 39 | } 40 | 41 | public async Task IsBusyFor(Func unitOfWork) 42 | { 43 | Interlocked.Increment(ref _isBusy); 44 | OnPropertyChanged(nameof(IsBusy)); 45 | 46 | try 47 | { 48 | await unitOfWork(); 49 | } 50 | finally 51 | { 52 | Interlocked.Decrement(ref _isBusy); 53 | OnPropertyChanged(nameof(IsBusy)); 54 | } 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /eShopOnContainers/ViewModels/CampaignDetailsViewModel.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Models.Marketing; 2 | using eShopOnContainers.Services; 3 | using eShopOnContainers.Services.AppEnvironment; 4 | using eShopOnContainers.Services.Settings; 5 | using eShopOnContainers.ViewModels.Base; 6 | 7 | namespace eShopOnContainers.ViewModels; 8 | 9 | [QueryProperty(nameof(CampaignId), "Id")] 10 | public partial class CampaignDetailsViewModel : ViewModelBase 11 | { 12 | private readonly ISettingsService _settingsService; 13 | private readonly IAppEnvironmentService _appEnvironmentService; 14 | 15 | [ObservableProperty] 16 | private CampaignItem _campaign; 17 | 18 | [ObservableProperty] 19 | private bool _isDetailsSite; 20 | 21 | [ObservableProperty] 22 | private int _campaignId; 23 | 24 | public CampaignDetailsViewModel( 25 | IAppEnvironmentService appEnvironmentService, 26 | INavigationService navigationService, ISettingsService settingsService) 27 | : base(navigationService) 28 | { 29 | _appEnvironmentService = appEnvironmentService; 30 | _settingsService = settingsService; 31 | } 32 | 33 | public override async Task InitializeAsync() 34 | { 35 | await IsBusyFor( 36 | async () => 37 | { 38 | // Get campaign by id 39 | Campaign = await _appEnvironmentService.CampaignService.GetCampaignByIdAsync(CampaignId, _settingsService.AuthAccessToken); 40 | }); 41 | } 42 | 43 | [RelayCommand] 44 | private void EnableDetailsSite() 45 | { 46 | IsDetailsSite = true; 47 | } 48 | } -------------------------------------------------------------------------------- /eShopOnContainers/Models/User/UserInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace eShopOnContainers.Models.User; 4 | 5 | public class UserInfo 6 | { 7 | [JsonPropertyName("sub")] 8 | public string UserId { get; set; } 9 | 10 | [JsonPropertyName("preferred_username")] 11 | public string PreferredUsername { get; set; } 12 | 13 | [JsonPropertyName("name")] 14 | public string Name { get; set; } 15 | 16 | [JsonPropertyName("last_name")] 17 | public string LastName { get; set; } 18 | 19 | [JsonPropertyName("card_number")] 20 | public string CardNumber { get; set; } 21 | 22 | [JsonPropertyName("card_holder")] 23 | public string CardHolder { get; set; } 24 | 25 | [JsonPropertyName("card_security_number")] 26 | public string CardSecurityNumber { get; set; } 27 | 28 | [JsonPropertyName("address_city")] 29 | public string Address { get; set; } 30 | 31 | [JsonPropertyName("address_country")] 32 | public string Country { get; set; } 33 | 34 | [JsonPropertyName("address_state")] 35 | public string State { get; set; } 36 | 37 | [JsonPropertyName("address_street")] 38 | public string Street { get; set; } 39 | 40 | [JsonPropertyName("address_zip_code")] 41 | public string ZipCode { get; set; } 42 | 43 | [JsonPropertyName("email")] 44 | public string Email { get; set; } 45 | 46 | [JsonPropertyName("email_verified")] 47 | public bool EmailVerified { get; set; } 48 | 49 | [JsonPropertyName("phone_number")] 50 | public string PhoneNumber { get; set; } 51 | 52 | [JsonPropertyName("phone_number_verified")] 53 | public bool PhoneNumberVerified { get; set; } 54 | } -------------------------------------------------------------------------------- /eShopOnContainers/ViewModels/ObservableCollectionEx.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.Collections.Specialized; 3 | using System.ComponentModel; 4 | 5 | namespace eShopOnContainers.ViewModels; 6 | 7 | public class ObservableCollectionEx : ObservableCollection 8 | { 9 | public ObservableCollectionEx() : base() 10 | { 11 | } 12 | 13 | public ObservableCollectionEx(IEnumerable collection) : base(collection) 14 | { 15 | } 16 | 17 | public ObservableCollectionEx(List list) : base(list) 18 | { 19 | } 20 | 21 | public void ReloadData(IEnumerable items) 22 | { 23 | ReloadData( 24 | innerList => 25 | { 26 | foreach (var item in items) 27 | { 28 | innerList.Add(item); 29 | } 30 | }); 31 | } 32 | 33 | public void ReloadData(Action> innerListAction) 34 | { 35 | Items.Clear(); 36 | 37 | innerListAction(Items); 38 | 39 | OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count))); 40 | OnPropertyChanged(new PropertyChangedEventArgs("Items[]")); 41 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 42 | } 43 | 44 | public async Task ReloadDataAsync(Func, Task> innerListAction) 45 | { 46 | Items.Clear(); 47 | 48 | await innerListAction(Items); 49 | 50 | OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count))); 51 | OnPropertyChanged(new PropertyChangedEventArgs("Items[]")); 52 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /eShopOnContainers/Services/Marketing/CampaignService.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Helpers; 2 | using eShopOnContainers.Models.Marketing; 3 | using eShopOnContainers.Services.FixUri; 4 | using eShopOnContainers.Services.RequestProvider; 5 | using System.Collections.ObjectModel; 6 | 7 | namespace eShopOnContainers.Services.Marketing; 8 | 9 | public class CampaignService : ICampaignService 10 | { 11 | private readonly IRequestProvider _requestProvider; 12 | private readonly IFixUriService _fixUriService; 13 | 14 | private const string ApiUrlBase = "m/api/v1/campaigns"; 15 | 16 | public CampaignService(IRequestProvider requestProvider, IFixUriService fixUriService) 17 | { 18 | _requestProvider = requestProvider; 19 | _fixUriService = fixUriService; 20 | } 21 | 22 | public async Task> GetAllCampaignsAsync(string token) 23 | { 24 | var uri = UriHelper.CombineUri(GlobalSetting.Instance.GatewayMarketingEndpoint, $"{ApiUrlBase}/user"); 25 | 26 | CampaignRoot campaign = await _requestProvider.GetAsync(uri, token).ConfigureAwait(false); 27 | 28 | if (campaign?.Data != null) 29 | { 30 | _fixUriService.FixCampaignItemPictureUri(campaign?.Data); 31 | return campaign?.Data ?? Enumerable.Empty(); 32 | } 33 | 34 | return new ObservableCollection(); 35 | } 36 | 37 | public async Task GetCampaignByIdAsync(int campaignId, string token) 38 | { 39 | var uri = UriHelper.CombineUri(GlobalSetting.Instance.GatewayMarketingEndpoint, $"{ApiUrlBase}/{campaignId}"); 40 | 41 | return await _requestProvider.GetAsync(uri, token).ConfigureAwait(false); 42 | } 43 | } -------------------------------------------------------------------------------- /eShopOnContainers/Resources/appiconfg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /eShopOnContainers/ViewModels/CampaignViewModel.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Models.Marketing; 2 | using eShopOnContainers.Services; 3 | using eShopOnContainers.Services.AppEnvironment; 4 | using eShopOnContainers.Services.Settings; 5 | using eShopOnContainers.ViewModels.Base; 6 | 7 | namespace eShopOnContainers.ViewModels; 8 | 9 | public partial class CampaignViewModel : ViewModelBase 10 | { 11 | private readonly ISettingsService _settingsService; 12 | private readonly IAppEnvironmentService _appEnvironmentService; 13 | private readonly ObservableCollectionEx _campaigns = new (); 14 | 15 | public IReadOnlyList Campaigns => _campaigns; 16 | 17 | public CampaignViewModel( 18 | IAppEnvironmentService appEnvironmentService, 19 | INavigationService navigationService, ISettingsService settingsService) 20 | : base(navigationService) 21 | { 22 | _appEnvironmentService = appEnvironmentService; 23 | _settingsService = settingsService; 24 | } 25 | 26 | public override async Task InitializeAsync() 27 | { 28 | await IsBusyFor( 29 | async () => 30 | { 31 | // Get campaigns by user 32 | var campaigns = await _appEnvironmentService.CampaignService.GetAllCampaignsAsync(_settingsService.AuthAccessToken); 33 | _campaigns.ReloadData(campaigns); 34 | }); 35 | } 36 | 37 | [RelayCommand] 38 | private async Task GetCampaignDetailsAsync(CampaignItem campaign) 39 | { 40 | if (campaign is null) 41 | { 42 | return; 43 | } 44 | 45 | await NavigationService.NavigateToAsync( 46 | "CampaignDetails", 47 | new Dictionary { { nameof(Campaign.Id), campaign.Id } }); 48 | } 49 | } -------------------------------------------------------------------------------- /eShopOnContainers/Views/LoginView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace eShopOnContainers.Views; 4 | 5 | public partial class LoginView : ContentPageBase 6 | { 7 | private readonly LoginViewModel _viewModel; 8 | 9 | private bool _animate; 10 | 11 | public LoginView(LoginViewModel viewModel) 12 | { 13 | BindingContext = _viewModel = viewModel; 14 | InitializeComponent(); 15 | } 16 | 17 | protected override async void OnAppearing() 18 | { 19 | var content = Content; 20 | Content = null; 21 | Content = content; 22 | 23 | _viewModel.InvalidateMock(); 24 | 25 | if (!_viewModel.IsMock) 26 | { 27 | _animate = true; 28 | await AnimateIn(); 29 | } 30 | } 31 | 32 | protected override void OnDisappearing() 33 | { 34 | _animate = false; 35 | } 36 | 37 | public async Task AnimateIn() 38 | { 39 | if (DeviceInfo.Platform == DevicePlatform.WinUI) 40 | { 41 | return; 42 | } 43 | 44 | await AnimateItem(Banner, 10500); 45 | } 46 | 47 | private async Task AnimateItem(View uiElement, uint duration) 48 | { 49 | try 50 | { 51 | while (_animate) 52 | { 53 | await uiElement.ScaleTo(1.05, duration, Easing.SinInOut); 54 | await Task.WhenAll( 55 | uiElement.FadeTo(1, duration, Easing.SinInOut), 56 | uiElement.LayoutTo(new Rect(new Point(0, 0), new Size(uiElement.Width, uiElement.Height))), 57 | uiElement.FadeTo(.9, duration, Easing.SinInOut), 58 | uiElement.ScaleTo(1.15, duration, Easing.SinInOut) 59 | ); 60 | } 61 | } 62 | catch (Exception ex) 63 | { 64 | Debug.WriteLine(ex.Message); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /eShopOnContainers/Views/MapView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 47.6423109 22 | -122.1368406 23 | 24 | 25 | 0.01 26 | 0.01 27 | 28 | 29 | 30 | 31 | 32 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /eShopOnContainers.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31611.283 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "eShopOnContainers", "eShopOnContainers\eShopOnContainers.csproj", "{A3FDE011-C268-4319-A0BF-3151DEA5EF90}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "eShopOnContainers.UnitTests", "eShopOnContainers.UnitTests\eShopOnContainers.UnitTests.csproj", "{2F62F414-42B8-4663-96BB-FF00E63B2929}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{26C3A3EB-3DB4-46A0-91C8-500FEC1CDA16}" 11 | ProjectSection(SolutionItems) = preProject 12 | .github\workflows\build-validation.yml = .github\workflows\build-validation.yml 13 | README.md = README.md 14 | EndProjectSection 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {A3FDE011-C268-4319-A0BF-3151DEA5EF90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {A3FDE011-C268-4319-A0BF-3151DEA5EF90}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {A3FDE011-C268-4319-A0BF-3151DEA5EF90}.Debug|Any CPU.Deploy.0 = Debug|Any CPU 25 | {A3FDE011-C268-4319-A0BF-3151DEA5EF90}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {A3FDE011-C268-4319-A0BF-3151DEA5EF90}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {A3FDE011-C268-4319-A0BF-3151DEA5EF90}.Release|Any CPU.Deploy.0 = Release|Any CPU 28 | {2F62F414-42B8-4663-96BB-FF00E63B2929}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {2F62F414-42B8-4663-96BB-FF00E63B2929}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {2F62F414-42B8-4663-96BB-FF00E63B2929}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {2F62F414-42B8-4663-96BB-FF00E63B2929}.Release|Any CPU.Build.0 = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(SolutionProperties) = preSolution 34 | HideSolutionNode = FALSE 35 | EndGlobalSection 36 | GlobalSection(ExtensibilityGlobals) = postSolution 37 | SolutionGuid = {61F7FB11-1E47-470C-91E2-47F8143E1572} 38 | EndGlobalSection 39 | EndGlobal 40 | -------------------------------------------------------------------------------- /eShopOnContainers/Services/Basket/BasketMockService.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Models.Basket; 2 | 3 | namespace eShopOnContainers.Services.Basket; 4 | 5 | public class BasketMockService : IBasketService 6 | { 7 | public IEnumerable LocalBasketItems { get; set; } 8 | 9 | private CustomerBasket MockCustomBasket = new() 10 | { 11 | BuyerId = "9245fe4a-d402-451c-b9ed-9c1a04247482", 12 | Items = new List 13 | { 14 | new BasketItem { Id = "1", PictureUrl = "fake_product_01.png", ProductId = Common.Common.MockCatalogItemId01, ProductName = ".NET Bot Blue Sweatshirt (M)", Quantity = 1, UnitPrice = 19.50M }, 15 | new BasketItem { Id = "2", PictureUrl = "fake_product_04.png", ProductId = Common.Common.MockCatalogItemId04, ProductName = ".NET Black Cup", Quantity = 1, UnitPrice = 17.00M } 16 | } 17 | }; 18 | 19 | public async Task GetBasketAsync(string guidUser, string token) 20 | { 21 | await Task.Delay(10); 22 | 23 | if (string.IsNullOrEmpty(guidUser) || string.IsNullOrEmpty(token)) 24 | { 25 | return new(); 26 | } 27 | 28 | return MockCustomBasket; 29 | } 30 | 31 | public async Task UpdateBasketAsync(CustomerBasket customerBasket, string token) 32 | { 33 | await Task.Delay(10); 34 | 35 | if (string.IsNullOrEmpty(token)) 36 | { 37 | return new(); 38 | } 39 | 40 | MockCustomBasket = customerBasket; 41 | 42 | return MockCustomBasket; 43 | } 44 | 45 | public async Task ClearBasketAsync(string guidUser, string token) 46 | { 47 | await Task.Delay(10); 48 | 49 | if (string.IsNullOrEmpty(token)) 50 | { 51 | return; 52 | } 53 | 54 | if (!string.IsNullOrEmpty(guidUser)) 55 | { 56 | MockCustomBasket.Items.Clear(); 57 | 58 | LocalBasketItems = null; 59 | } 60 | } 61 | 62 | public Task CheckoutAsync(BasketCheckout basketCheckout, string token) 63 | { 64 | if (string.IsNullOrEmpty(token)) 65 | { 66 | return Task.FromResult(0); 67 | } 68 | 69 | if (basketCheckout != null) 70 | { 71 | MockCustomBasket.Items.Clear(); 72 | } 73 | 74 | return Task.FromResult(0); 75 | } 76 | } -------------------------------------------------------------------------------- /eShopOnContainers/Views/Templates/CampaignTemplate.xaml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 16 | 17 | 24 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | 44 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /eShopOnContainers/ViewModels/OrderDetailViewModel.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Models.Orders; 2 | using eShopOnContainers.Services; 3 | using eShopOnContainers.Services.AppEnvironment; 4 | using eShopOnContainers.Services.Settings; 5 | using eShopOnContainers.ViewModels.Base; 6 | 7 | namespace eShopOnContainers.ViewModels; 8 | 9 | [QueryProperty(nameof(OrderNumber), "OrderNumber")] 10 | public partial class OrderDetailViewModel : ViewModelBase 11 | { 12 | private readonly ISettingsService _settingsService; 13 | private readonly IAppEnvironmentService _appEnvironmentService; 14 | 15 | [ObservableProperty] 16 | private Order _order; 17 | 18 | [ObservableProperty] 19 | private bool _isSubmittedOrder; 20 | 21 | [ObservableProperty] 22 | private string _orderStatusText; 23 | 24 | [ObservableProperty] 25 | private int _orderNumber; 26 | 27 | public OrderDetailViewModel( 28 | IAppEnvironmentService appEnvironmentService, 29 | INavigationService navigationService, ISettingsService settingsService) 30 | : base(navigationService) 31 | { 32 | _appEnvironmentService = appEnvironmentService; 33 | _settingsService = settingsService; 34 | } 35 | 36 | public override async Task InitializeAsync() 37 | { 38 | await IsBusyFor( 39 | async () => 40 | { 41 | // Get order detail info 42 | var authToken = _settingsService.AuthAccessToken; 43 | Order = await _appEnvironmentService.OrderService.GetOrderAsync(OrderNumber, authToken); 44 | IsSubmittedOrder = Order.OrderStatus == OrderStatus.Submitted; 45 | OrderStatusText = Order.OrderStatus.ToString().ToUpper(); 46 | }); 47 | } 48 | 49 | [RelayCommand] 50 | private async Task ToggleCancelOrderAsync() 51 | { 52 | var authToken = _settingsService.AuthAccessToken; 53 | 54 | var result = await _appEnvironmentService.OrderService.CancelOrderAsync(Order.OrderNumber, authToken); 55 | 56 | if (result) 57 | { 58 | OrderStatusText = OrderStatus.Cancelled.ToString().ToUpper(); 59 | } 60 | else 61 | { 62 | Order = await _appEnvironmentService.OrderService.GetOrderAsync(Order.OrderNumber, authToken); 63 | OrderStatusText = Order.OrderStatus.ToString().ToUpper(); 64 | } 65 | 66 | IsSubmittedOrder = false; 67 | } 68 | } -------------------------------------------------------------------------------- /eShopOnContainers/Services/Basket/BasketService.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Services.RequestProvider; 2 | using eShopOnContainers.Models.Basket; 3 | using eShopOnContainers.Services.FixUri; 4 | using eShopOnContainers.Helpers; 5 | 6 | namespace eShopOnContainers.Services.Basket; 7 | 8 | public class BasketService : IBasketService 9 | { 10 | private readonly IRequestProvider _requestProvider; 11 | private readonly IFixUriService _fixUriService; 12 | 13 | private const string ApiUrlBase = "b/api/v1/basket"; 14 | 15 | public IEnumerable LocalBasketItems { get; set; } 16 | 17 | public BasketService(IRequestProvider requestProvider, IFixUriService fixUriService) 18 | { 19 | _requestProvider = requestProvider; 20 | _fixUriService = fixUriService; 21 | } 22 | 23 | public async Task GetBasketAsync(string guidUser, string token) 24 | { 25 | var uri = UriHelper.CombineUri(GlobalSetting.Instance.GatewayShoppingEndpoint, $"{ApiUrlBase}/{guidUser}"); 26 | 27 | CustomerBasket basket; 28 | 29 | try 30 | { 31 | basket = await _requestProvider.GetAsync(uri, token).ConfigureAwait(false); 32 | } 33 | catch (HttpRequestExceptionEx exception) when (exception.HttpCode == System.Net.HttpStatusCode.NotFound) 34 | { 35 | basket = null; 36 | } 37 | 38 | _fixUriService.FixBasketItemPictureUri(basket?.Items); 39 | return basket; 40 | } 41 | 42 | public async Task UpdateBasketAsync(CustomerBasket customerBasket, string token) 43 | { 44 | var uri = UriHelper.CombineUri(GlobalSetting.Instance.GatewayShoppingEndpoint, ApiUrlBase); 45 | 46 | var result = await _requestProvider.PostAsync(uri, customerBasket, token).ConfigureAwait(false); 47 | return result; 48 | } 49 | 50 | public async Task CheckoutAsync(BasketCheckout basketCheckout, string token) 51 | { 52 | var uri = UriHelper.CombineUri(GlobalSetting.Instance.GatewayShoppingEndpoint, $"{ApiUrlBase}/checkout"); 53 | 54 | await _requestProvider.PostAsync(uri, basketCheckout, token).ConfigureAwait(false); 55 | } 56 | 57 | public async Task ClearBasketAsync(string guidUser, string token) 58 | { 59 | var uri = UriHelper.CombineUri(GlobalSetting.Instance.GatewayShoppingEndpoint, $"{ApiUrlBase}/{guidUser}"); 60 | 61 | await _requestProvider.DeleteAsync(uri, token).ConfigureAwait(false); 62 | 63 | LocalBasketItems = null; 64 | } 65 | } -------------------------------------------------------------------------------- /eShopOnContainers/ViewModels/ProfileViewModel.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Models.Orders; 2 | using eShopOnContainers.Services; 3 | using eShopOnContainers.Services.AppEnvironment; 4 | using eShopOnContainers.Services.Settings; 5 | using eShopOnContainers.ViewModels.Base; 6 | 7 | namespace eShopOnContainers.ViewModels; 8 | 9 | public partial class ProfileViewModel : ViewModelBase 10 | { 11 | private readonly IAppEnvironmentService _appEnvironmentService; 12 | private readonly ISettingsService _settingsService; 13 | private readonly ObservableCollectionEx _orders; 14 | 15 | [ObservableProperty] 16 | private Order _selectedOrder; 17 | 18 | public IList Orders => _orders; 19 | 20 | public ProfileViewModel( 21 | IAppEnvironmentService appEnvironmentService, ISettingsService settingsService, 22 | INavigationService navigationService) 23 | : base(navigationService) 24 | { 25 | _appEnvironmentService = appEnvironmentService; 26 | _settingsService = settingsService; 27 | 28 | _orders = new ObservableCollectionEx(); 29 | } 30 | 31 | public override async Task InitializeAsync() 32 | { 33 | await RefreshAsync(); 34 | } 35 | 36 | [RelayCommand] 37 | private async Task LogoutAsync() 38 | { 39 | await IsBusyFor( 40 | async () => 41 | { 42 | // Logout 43 | await NavigationService.NavigateToAsync( 44 | "//Login", 45 | new Dictionary { { "Logout", true } }); 46 | }); 47 | } 48 | 49 | [RelayCommand] 50 | private async Task RefreshAsync() 51 | { 52 | if (IsBusy) 53 | { 54 | return; 55 | } 56 | 57 | await IsBusyFor( 58 | async () => 59 | { 60 | // Get orders 61 | var authToken = _settingsService.AuthAccessToken; 62 | var orders = await _appEnvironmentService.OrderService.GetOrdersAsync(authToken); 63 | 64 | _orders.ReloadData(orders); 65 | }); 66 | } 67 | 68 | [RelayCommand] 69 | private async Task OrderDetailAsync(Order order) 70 | { 71 | if (order is null || IsBusy) 72 | { 73 | return; 74 | } 75 | 76 | await NavigationService.NavigateToAsync( 77 | "OrderDetail", 78 | new Dictionary { { nameof(Order.OrderNumber), order.OrderNumber } }); 79 | } 80 | } -------------------------------------------------------------------------------- /eShopOnContainers/Services/Catalog/CatalogService.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Models.Catalog; 2 | using eShopOnContainers.Services.RequestProvider; 3 | using eShopOnContainers.Services.FixUri; 4 | using eShopOnContainers.Helpers; 5 | 6 | namespace eShopOnContainers.Services.Catalog; 7 | 8 | public class CatalogService : ICatalogService 9 | { 10 | private readonly IRequestProvider _requestProvider; 11 | private readonly IFixUriService _fixUriService; 12 | 13 | private const string ApiUrlBase = "api/v1/Catalog"; 14 | 15 | public CatalogService(IRequestProvider requestProvider, IFixUriService fixUriService) 16 | { 17 | _requestProvider = requestProvider; 18 | _fixUriService = fixUriService; 19 | } 20 | 21 | public async Task> FilterAsync(int catalogBrandId, int catalogTypeId) 22 | { 23 | var uri = UriHelper.CombineUri(GlobalSetting.Instance.GatewayShoppingEndpoint, $"{ApiUrlBase}/items/type/{catalogTypeId}/brand/{catalogBrandId}"); 24 | 25 | CatalogRoot catalog = await _requestProvider.GetAsync(uri).ConfigureAwait(false); 26 | 27 | return catalog?.Data ?? Enumerable.Empty(); 28 | } 29 | 30 | public async Task> GetCatalogAsync() 31 | { 32 | var uri = UriHelper.CombineUri(GlobalSetting.Instance.GatewayShoppingEndpoint, $"{ApiUrlBase}/items"); 33 | 34 | CatalogRoot catalog = await _requestProvider.GetAsync(uri).ConfigureAwait(false); 35 | 36 | if (catalog?.Data != null) 37 | { 38 | _fixUriService.FixCatalogItemPictureUri(catalog.Data); 39 | return catalog.Data; 40 | } 41 | else 42 | return Enumerable.Empty(); 43 | } 44 | 45 | public async Task> GetCatalogBrandAsync() 46 | { 47 | var uri = UriHelper.CombineUri(GlobalSetting.Instance.GatewayShoppingEndpoint, $"{ApiUrlBase}/catalogbrands"); 48 | 49 | IEnumerable brands = await _requestProvider.GetAsync>(uri).ConfigureAwait(false); 50 | 51 | return brands?.ToArray() ?? Enumerable.Empty(); 52 | } 53 | 54 | public async Task> GetCatalogTypeAsync() 55 | { 56 | var uri = UriHelper.CombineUri(GlobalSetting.Instance.GatewayShoppingEndpoint, $"{ApiUrlBase}/catalogtypes"); 57 | 58 | IEnumerable types = await _requestProvider.GetAsync>(uri).ConfigureAwait(false); 59 | 60 | return types?.ToArray() ?? Enumerable.Empty(); 61 | } 62 | } -------------------------------------------------------------------------------- /eShopOnContainers/Views/Templates/OrderTemplate.xaml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 33 | 36 | 40 | 43 | 47 | 50 | 54 | 57 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /eShopOnContainers/AppShell.xaml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 21 | 22 | 23 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 46 | 53 | 54 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /eShopOnContainers/Views/Templates/ProductTemplate.xaml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 17 | 18 | 25 | 26 | 33 | 34 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 53 | 54 | 58 | 59 | 60 | 61 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /eShopOnContainers/GlobalSettings.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers; 2 | 3 | public class GlobalSetting 4 | { 5 | public const string AzureTag = "Azure"; 6 | public const string MockTag = "Mock"; 7 | public const string DefaultEndpoint = "http://YOUR_IP_OR_DNS_NAME"; // i.e.: "http://YOUR_IP" or "http://YOUR_DNS_NAME" 8 | 9 | private string _baseIdentityEndpoint; 10 | private string _baseGatewayShoppingEndpoint; 11 | private string _baseGatewayMarketingEndpoint; 12 | 13 | public GlobalSetting() 14 | { 15 | AuthToken = "INSERT AUTHENTICATION TOKEN"; 16 | 17 | BaseIdentityEndpoint = DefaultEndpoint; 18 | BaseGatewayShoppingEndpoint = DefaultEndpoint; 19 | BaseGatewayMarketingEndpoint = DefaultEndpoint; 20 | } 21 | 22 | public static GlobalSetting Instance { get; } = new GlobalSetting(); 23 | 24 | public string BaseIdentityEndpoint 25 | { 26 | get => _baseIdentityEndpoint; 27 | set 28 | { 29 | _baseIdentityEndpoint = value; 30 | UpdateEndpoint(_baseIdentityEndpoint); 31 | } 32 | } 33 | 34 | public string BaseGatewayShoppingEndpoint 35 | { 36 | get => _baseGatewayShoppingEndpoint; 37 | set 38 | { 39 | _baseGatewayShoppingEndpoint = value; 40 | UpdateGatewayShoppingEndpoint(_baseGatewayShoppingEndpoint); 41 | } 42 | } 43 | 44 | public string BaseGatewayMarketingEndpoint 45 | { 46 | get => _baseGatewayMarketingEndpoint; 47 | set 48 | { 49 | _baseGatewayMarketingEndpoint = value; 50 | UpdateGatewayMarketingEndpoint(_baseGatewayMarketingEndpoint); 51 | } 52 | } 53 | 54 | public string ClientId { get; } = "xamarin"; 55 | 56 | public string ClientSecret { get; } = "secret"; 57 | 58 | public string AuthToken { get; set; } 59 | 60 | public string RegisterWebsite { get; set; } 61 | 62 | public string AuthorizeEndpoint { get; set; } 63 | 64 | public string UserInfoEndpoint { get; set; } 65 | 66 | public string TokenEndpoint { get; set; } 67 | 68 | public string LogoutEndpoint { get; set; } 69 | 70 | public string Callback { get; set; } 71 | 72 | public string LogoutCallback { get; set; } 73 | 74 | public string GatewayShoppingEndpoint { get; set; } 75 | 76 | public string GatewayMarketingEndpoint { get; set; } 77 | 78 | private void UpdateEndpoint(string endpoint) 79 | { 80 | RegisterWebsite = $"{endpoint}/Account/Register"; 81 | LogoutCallback = $"{endpoint}/Account/Redirecting"; 82 | 83 | var connectBaseEndpoint = $"{endpoint}/connect"; 84 | AuthorizeEndpoint = $"{connectBaseEndpoint}/authorize"; 85 | UserInfoEndpoint = $"{connectBaseEndpoint}/userinfo"; 86 | TokenEndpoint = $"{connectBaseEndpoint}/token"; 87 | LogoutEndpoint = $"{connectBaseEndpoint}/endsession"; 88 | 89 | var baseUri = GlobalSetting.ExtractBaseUri(endpoint); 90 | Callback = $"{baseUri}/xamarincallback"; 91 | } 92 | 93 | private void UpdateGatewayShoppingEndpoint(string endpoint) 94 | { 95 | GatewayShoppingEndpoint = endpoint; 96 | } 97 | 98 | private void UpdateGatewayMarketingEndpoint(string endpoint) 99 | { 100 | GatewayMarketingEndpoint = endpoint; 101 | } 102 | 103 | private static string ExtractBaseUri(string endpoint) 104 | { 105 | try 106 | { 107 | var uri = new Uri(endpoint); 108 | var baseUri = uri.GetLeftPart(UriPartial.Authority); 109 | 110 | return baseUri; 111 | } 112 | catch (Exception ex) 113 | { 114 | _ = ex; 115 | return DefaultEndpoint; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /eShopOnContainers/Animations/Base/AnimationBase.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace eShopOnContainers.Animations.Base; 4 | 5 | public abstract class AnimationBase : BindableObject 6 | { 7 | private bool _isRunning = false; 8 | 9 | public static readonly BindableProperty TargetProperty = BindableProperty.Create(nameof(Target), typeof(VisualElement), typeof(AnimationBase), null, 10 | propertyChanged: (bindable, oldValue, newValue) => ((AnimationBase)bindable).Target = (VisualElement)newValue); 11 | 12 | public VisualElement Target 13 | { 14 | get => (VisualElement)GetValue(TargetProperty); 15 | set => SetValue(TargetProperty, value); 16 | } 17 | 18 | public static readonly BindableProperty DurationProperty = BindableProperty.Create(nameof(Duration), typeof(string), typeof(AnimationBase), "1000", 19 | propertyChanged: (bindable, oldValue, newValue) => ((AnimationBase)bindable).Duration = (string)newValue); 20 | 21 | public string Duration 22 | { 23 | get => (string)GetValue(DurationProperty); 24 | set => SetValue(DurationProperty, value); 25 | } 26 | 27 | public static readonly BindableProperty EasingProperty = BindableProperty.Create(nameof(Easing), typeof(EasingType), typeof(AnimationBase), EasingType.Linear, 28 | propertyChanged: (bindable, oldValue, newValue) => ((AnimationBase)bindable).Easing = (EasingType)newValue); 29 | 30 | public EasingType Easing 31 | { 32 | get => (EasingType)GetValue(EasingProperty); 33 | set => SetValue(EasingProperty, value); 34 | } 35 | 36 | public static readonly BindableProperty RepeatForeverProperty = BindableProperty.Create(nameof(RepeatForever), typeof(bool), typeof(AnimationBase), false, 37 | propertyChanged: (bindable, oldValue, newValue) => ((AnimationBase)bindable).RepeatForever = (bool)newValue); 38 | 39 | public bool RepeatForever 40 | { 41 | get => (bool)GetValue(RepeatForeverProperty); 42 | set => SetValue(RepeatForeverProperty, value); 43 | } 44 | 45 | public static readonly BindableProperty DelayProperty = BindableProperty.Create(nameof(Delay), typeof(int), typeof(AnimationBase), 0, 46 | propertyChanged: (bindable, oldValue, newValue) => ((AnimationBase)bindable).Delay = (int)newValue); 47 | 48 | public int Delay 49 | { 50 | get => (int)GetValue(DelayProperty); 51 | set => SetValue(DelayProperty, value); 52 | } 53 | 54 | protected abstract Task BeginAnimation(); 55 | 56 | public async Task Begin() 57 | { 58 | try 59 | { 60 | if (!_isRunning) 61 | { 62 | _isRunning = true; 63 | 64 | await InternalBegin() 65 | .ContinueWith(t => t.Exception, TaskContinuationOptions.OnlyOnFaulted) 66 | .ConfigureAwait(false); 67 | } 68 | } 69 | catch (TaskCanceledException) 70 | { 71 | } 72 | catch (Exception ex) 73 | { 74 | Debug.WriteLine($"Exception in animation {ex}"); 75 | } 76 | } 77 | 78 | protected abstract Task ResetAnimation(); 79 | 80 | public async Task Reset() 81 | { 82 | _isRunning = false; 83 | await ResetAnimation(); 84 | } 85 | 86 | private async Task InternalBegin() 87 | { 88 | if (Delay > 0) 89 | { 90 | await Task.Delay(Delay); 91 | } 92 | 93 | if (!RepeatForever) 94 | { 95 | await BeginAnimation(); 96 | } 97 | else 98 | { 99 | do 100 | { 101 | await BeginAnimation(); 102 | await ResetAnimation(); 103 | } while (RepeatForever); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /eShopOnContainers/Views/FiltersView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 21 | 22 | 27 | 28 | 29 | 34 | 35 | 51 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /eShopOnContainers/Views/CampaignView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | 18 | 23 | 24 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 51 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /eShopOnContainers/Services/Settings/SettingsService.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.Services.Settings; 2 | 3 | public class SettingsService : ISettingsService 4 | { 5 | #region Setting Constants 6 | 7 | private const string AccessToken = "access_token"; 8 | private const string IdToken = "id_token"; 9 | private const string IdUseMocks = "use_mocks"; 10 | private const string IdIdentityBase = "url_base"; 11 | private const string IdGatewayMarketingBase = "url_marketing"; 12 | private const string IdGatewayShoppingBase = "url_shopping"; 13 | private const string IdUseFakeLocation = "use_fake_location"; 14 | private const string IdLatitude = "latitude"; 15 | private const string IdLongitude = "longitude"; 16 | private const string IdAllowGpsLocation = "allow_gps_location"; 17 | private readonly string AccessTokenDefault = string.Empty; 18 | private readonly string IdTokenDefault = string.Empty; 19 | private readonly bool UseMocksDefault = true; 20 | private readonly bool UseFakeLocationDefault = false; 21 | private readonly bool AllowGpsLocationDefault = false; 22 | private readonly double FakeLatitudeDefault = 47.604610d; 23 | private readonly double FakeLongitudeDefault = -122.315752d; 24 | private readonly string UrlIdentityDefault = GlobalSetting.Instance.BaseIdentityEndpoint; 25 | private readonly string UrlGatewayMarketingDefault = GlobalSetting.Instance.BaseGatewayMarketingEndpoint; 26 | private readonly string UrlGatewayShoppingDefault = GlobalSetting.Instance.BaseGatewayShoppingEndpoint; 27 | #endregion 28 | 29 | #region Settings Properties 30 | 31 | public string AuthAccessToken 32 | { 33 | get => Preferences.Get(AccessToken, AccessTokenDefault); 34 | set => Preferences.Set(AccessToken, value); 35 | } 36 | 37 | public string AuthIdToken 38 | { 39 | get => Preferences.Get(IdToken, IdTokenDefault); 40 | set => Preferences.Set(IdToken, value); 41 | } 42 | 43 | public bool UseMocks 44 | { 45 | get => Preferences.Get(IdUseMocks, UseMocksDefault); 46 | set => Preferences.Set(IdUseMocks, value); 47 | } 48 | 49 | public string IdentityEndpointBase 50 | { 51 | get => Preferences.Get(IdIdentityBase, UrlIdentityDefault); 52 | set 53 | { 54 | Preferences.Set(IdIdentityBase, value); 55 | GlobalSetting.Instance.BaseIdentityEndpoint = value; 56 | } 57 | } 58 | 59 | public string GatewayShoppingEndpointBase 60 | { 61 | get => Preferences.Get(IdGatewayShoppingBase, UrlGatewayShoppingDefault); 62 | set 63 | { 64 | Preferences.Set(IdGatewayShoppingBase, value); 65 | GlobalSetting.Instance.BaseGatewayShoppingEndpoint = value; 66 | } 67 | } 68 | 69 | public string GatewayMarketingEndpointBase 70 | { 71 | get => Preferences.Get(IdGatewayMarketingBase, UrlGatewayMarketingDefault); 72 | set 73 | { 74 | Preferences.Set(IdGatewayMarketingBase, value); 75 | GlobalSetting.Instance.BaseGatewayMarketingEndpoint = value; 76 | } 77 | } 78 | 79 | public bool UseFakeLocation 80 | { 81 | get => Preferences.Get(IdUseFakeLocation, UseFakeLocationDefault); 82 | set => Preferences.Set(IdUseFakeLocation, value); 83 | } 84 | 85 | public string Latitude 86 | { 87 | get => Preferences.Get(IdLatitude, FakeLatitudeDefault.ToString()); 88 | set => Preferences.Set(IdLatitude, value); 89 | } 90 | 91 | public string Longitude 92 | { 93 | get => Preferences.Get(IdLongitude, FakeLongitudeDefault.ToString()); 94 | set => Preferences.Set(IdLongitude, value); 95 | } 96 | 97 | public bool AllowGpsLocation 98 | { 99 | get => Preferences.Get(IdAllowGpsLocation, AllowGpsLocationDefault); 100 | set => Preferences.Set(IdAllowGpsLocation, value); 101 | } 102 | 103 | #endregion 104 | } -------------------------------------------------------------------------------- /eShopOnContainers/Controls/ToggleButton.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Input; 2 | 3 | namespace eShopOnContainers.Controls; 4 | 5 | public class ToggleButton : ContentView 6 | { 7 | public static readonly BindableProperty CommandProperty = 8 | BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(ToggleButton), null); 9 | 10 | public static readonly BindableProperty CommandParameterProperty = 11 | BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(ToggleButton), null); 12 | 13 | public static readonly BindableProperty CheckedProperty = 14 | BindableProperty.Create(nameof(Checked), typeof(bool), typeof(ToggleButton), false, BindingMode.TwoWay, 15 | null, propertyChanged: OnCheckedChanged); 16 | 17 | public static readonly BindableProperty AnimateProperty = 18 | BindableProperty.Create(nameof(Animate), typeof(bool), typeof(ToggleButton), false); 19 | 20 | public static readonly BindableProperty CheckedImageProperty = 21 | BindableProperty.Create(nameof(CheckedImage), typeof(ImageSource), typeof(ToggleButton), null); 22 | 23 | public static readonly BindableProperty UnCheckedImageProperty = 24 | BindableProperty.Create(nameof(UnCheckedImage), typeof(ImageSource), typeof(ToggleButton), null); 25 | 26 | private ICommand _toggleCommand; 27 | private Image _toggleImage; 28 | 29 | public ToggleButton() 30 | { 31 | Initialize(); 32 | } 33 | 34 | public ICommand Command 35 | { 36 | get => (ICommand)GetValue(CommandProperty); 37 | set => SetValue(CommandProperty, value); 38 | } 39 | 40 | public object CommandParameter 41 | { 42 | get => GetValue(CommandParameterProperty); 43 | set => SetValue(CommandParameterProperty, value); 44 | } 45 | 46 | public bool Checked 47 | { 48 | get => (bool)GetValue(CheckedProperty); 49 | set => SetValue(CheckedProperty, value); 50 | } 51 | 52 | public bool Animate 53 | { 54 | get => (bool)GetValue(AnimateProperty); 55 | set => SetValue(CheckedProperty, value); 56 | } 57 | 58 | public ImageSource CheckedImage 59 | { 60 | get => (ImageSource)GetValue(CheckedImageProperty); 61 | set => SetValue(CheckedImageProperty, value); 62 | } 63 | 64 | public ImageSource UnCheckedImage 65 | { 66 | get => (ImageSource)GetValue(UnCheckedImageProperty); 67 | set => SetValue(UnCheckedImageProperty, value); 68 | } 69 | 70 | public ICommand ToogleCommand => 71 | _toggleCommand ??= new Command(() => 72 | { 73 | Checked = !Checked; 74 | 75 | if (Command != null) 76 | { 77 | Command.Execute(CommandParameter); 78 | } 79 | }); 80 | 81 | private void Initialize() 82 | { 83 | _toggleImage = new Image(); 84 | 85 | Animate = true; 86 | 87 | GestureRecognizers.Add(new TapGestureRecognizer 88 | { 89 | Command = ToogleCommand 90 | }); 91 | 92 | _toggleImage.Source = UnCheckedImage; 93 | Content = _toggleImage; 94 | } 95 | 96 | protected override void OnParentSet() 97 | { 98 | base.OnParentSet(); 99 | _toggleImage.Source = UnCheckedImage; 100 | Content = _toggleImage; 101 | } 102 | 103 | private static async void OnCheckedChanged(BindableObject bindable, object oldValue, object newValue) 104 | { 105 | var toggleButton = (ToggleButton)bindable; 106 | 107 | if (Equals(newValue, null) && !Equals(oldValue, null)) 108 | return; 109 | 110 | toggleButton._toggleImage.Source = toggleButton.Checked 111 | ? toggleButton.CheckedImage 112 | : toggleButton.UnCheckedImage; 113 | 114 | toggleButton.Content = toggleButton._toggleImage; 115 | 116 | if (toggleButton.Animate) 117 | { 118 | await toggleButton.ScaleTo(0.9, 50, Easing.Linear); 119 | await Task.Delay(100); 120 | await toggleButton.ScaleTo(1, 50, Easing.Linear); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /eShopOnContainers.UnitTests/ViewModels/MockViewModelTests.cs: -------------------------------------------------------------------------------- 1 | namespace eShopOnContainers.UnitTests; 2 | 3 | public class MockViewModelTests 4 | { 5 | private readonly INavigationService _navigationService; 6 | 7 | public MockViewModelTests() 8 | { 9 | _navigationService = new MockNavigationService(); 10 | } 11 | 12 | [Fact] 13 | public void CheckValidationFailsWhenPropertiesAreEmptyTest() 14 | { 15 | var mockViewModel = new MockViewModel(_navigationService); 16 | 17 | bool isValid = mockViewModel.Validate(); 18 | 19 | Assert.False(isValid); 20 | Assert.Null(mockViewModel.Forename.Value); 21 | Assert.Null(mockViewModel.Surname.Value); 22 | Assert.False(mockViewModel.Forename.IsValid); 23 | Assert.False(mockViewModel.Surname.IsValid); 24 | Assert.NotEmpty(mockViewModel.Forename.Errors); 25 | Assert.NotEmpty(mockViewModel.Surname.Errors); 26 | } 27 | 28 | [Fact] 29 | public void CheckValidationFailsWhenOnlyForenameHasDataTest() 30 | { 31 | var mockViewModel = new MockViewModel(_navigationService); 32 | mockViewModel.Forename.Value = "John"; 33 | 34 | bool isValid = mockViewModel.Validate(); 35 | 36 | Assert.False(isValid); 37 | Assert.NotNull(mockViewModel.Forename.Value); 38 | Assert.Null(mockViewModel.Surname.Value); 39 | Assert.True(mockViewModel.Forename.IsValid); 40 | Assert.False(mockViewModel.Surname.IsValid); 41 | Assert.Empty(mockViewModel.Forename.Errors); 42 | Assert.NotEmpty(mockViewModel.Surname.Errors); 43 | } 44 | 45 | [Fact] 46 | public void CheckValidationPassesWhenOnlySurnameHasDataTest() 47 | { 48 | var mockViewModel = new MockViewModel(_navigationService); 49 | mockViewModel.Surname.Value = "Smith"; 50 | 51 | bool isValid = mockViewModel.Validate(); 52 | 53 | Assert.False(isValid); 54 | Assert.Null(mockViewModel.Forename.Value); 55 | Assert.NotNull(mockViewModel.Surname.Value); 56 | Assert.False(mockViewModel.Forename.IsValid); 57 | Assert.True(mockViewModel.Surname.IsValid); 58 | Assert.NotEmpty(mockViewModel.Forename.Errors); 59 | Assert.Empty(mockViewModel.Surname.Errors); 60 | } 61 | 62 | [Fact] 63 | public void CheckValidationPassesWhenBothPropertiesHaveDataTest() 64 | { 65 | var mockViewModel = new MockViewModel(_navigationService); 66 | mockViewModel.Forename.Value = "John"; 67 | mockViewModel.Surname.Value = "Smith"; 68 | 69 | bool isValid = mockViewModel.Validate(); 70 | 71 | Assert.True(isValid); 72 | Assert.NotNull(mockViewModel.Forename.Value); 73 | Assert.NotNull(mockViewModel.Surname.Value); 74 | Assert.True(mockViewModel.Forename.IsValid); 75 | Assert.True(mockViewModel.Surname.IsValid); 76 | Assert.Empty(mockViewModel.Forename.Errors); 77 | Assert.Empty(mockViewModel.Surname.Errors); 78 | } 79 | 80 | [Fact] 81 | public void SettingForenamePropertyShouldRaisePropertyChanged() 82 | { 83 | bool invoked = false; 84 | var mockViewModel = new MockViewModel(_navigationService); 85 | 86 | mockViewModel.Forename.PropertyChanged += (_, e) => 87 | { 88 | if (e?.PropertyName?.Equals(nameof(mockViewModel.Forename.Value)) ?? false) 89 | { 90 | invoked = true; 91 | } 92 | }; 93 | mockViewModel.Forename.Value = "John"; 94 | 95 | Assert.True(invoked); 96 | } 97 | 98 | [Fact] 99 | public void SettingSurnamePropertyShouldRaisePropertyChanged() 100 | { 101 | bool invoked = false; 102 | var mockViewModel = new MockViewModel(_navigationService); 103 | 104 | mockViewModel.Surname.PropertyChanged += (_, e) => 105 | { 106 | if (e?.PropertyName?.Equals(nameof(mockViewModel.Surname.Value)) ?? false) 107 | { 108 | invoked = true; 109 | } 110 | }; 111 | mockViewModel.Surname.Value = "Smith"; 112 | 113 | Assert.True(invoked); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /eShopOnContainers/Services/FixUri/FixUriService.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Text.RegularExpressions; 3 | using eShopOnContainers.Models.Basket; 4 | using eShopOnContainers.Models.Catalog; 5 | using eShopOnContainers.Models.Marketing; 6 | using eShopOnContainers.Services.Settings; 7 | 8 | namespace eShopOnContainers.Services.FixUri; 9 | 10 | public class FixUriService : IFixUriService 11 | { 12 | private readonly ISettingsService _settingsService; 13 | 14 | private readonly Regex IpRegex = new(@"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"); 15 | 16 | public FixUriService(ISettingsService settingsService) 17 | { 18 | _settingsService = settingsService; 19 | } 20 | 21 | public void FixCatalogItemPictureUri(IEnumerable catalogItems) 22 | { 23 | if (catalogItems is null) 24 | { 25 | return; 26 | } 27 | 28 | try 29 | { 30 | if (!_settingsService.UseMocks && _settingsService.IdentityEndpointBase != GlobalSetting.DefaultEndpoint) 31 | { 32 | foreach (var catalogItem in catalogItems) 33 | { 34 | MatchCollection serverResult = IpRegex.Matches(catalogItem.PictureUri); 35 | MatchCollection localResult = IpRegex.Matches(_settingsService.IdentityEndpointBase); 36 | 37 | if (serverResult.Count != -1 && localResult.Count != -1) 38 | { 39 | var serviceIp = serverResult[0].Value; 40 | var localIp = localResult[0].Value; 41 | 42 | catalogItem.PictureUri = catalogItem.PictureUri.Replace(serviceIp, localIp); 43 | } 44 | } 45 | } 46 | } 47 | catch (Exception ex) 48 | { 49 | Debug.WriteLine(ex.Message); 50 | } 51 | } 52 | 53 | public void FixBasketItemPictureUri(IEnumerable basketItems) 54 | { 55 | if (basketItems is null) 56 | { 57 | return; 58 | } 59 | 60 | try 61 | { 62 | if (!_settingsService.UseMocks && _settingsService.IdentityEndpointBase != GlobalSetting.DefaultEndpoint) 63 | { 64 | foreach (var basketItem in basketItems) 65 | { 66 | MatchCollection serverResult = IpRegex.Matches(basketItem.PictureUrl); 67 | MatchCollection localResult = IpRegex.Matches(_settingsService.IdentityEndpointBase); 68 | 69 | if (serverResult.Count != -1 && localResult.Count != -1) 70 | { 71 | var serviceIp = serverResult[0].Value; 72 | var localIp = localResult[0].Value; 73 | basketItem.PictureUrl = basketItem.PictureUrl.Replace(serviceIp, localIp); 74 | } 75 | } 76 | } 77 | } 78 | catch (Exception ex) 79 | { 80 | Debug.WriteLine(ex.Message); 81 | } 82 | } 83 | 84 | public void FixCampaignItemPictureUri(IEnumerable campaignItems) 85 | { 86 | if (campaignItems is null) 87 | { 88 | return; 89 | } 90 | 91 | try 92 | { 93 | if (!_settingsService.UseMocks && _settingsService.IdentityEndpointBase != GlobalSetting.DefaultEndpoint) 94 | { 95 | foreach (var campaignItem in campaignItems) 96 | { 97 | MatchCollection serverResult = IpRegex.Matches(campaignItem.PictureUri); 98 | MatchCollection localResult = IpRegex.Matches(_settingsService.IdentityEndpointBase); 99 | 100 | if (serverResult.Count != -1 && localResult.Count != -1) 101 | { 102 | var serviceIp = serverResult[0].Value; 103 | var localIp = localResult[0].Value; 104 | 105 | campaignItem.PictureUri = campaignItem.PictureUri.Replace(serviceIp, localIp); 106 | } 107 | } 108 | } 109 | } 110 | catch (Exception ex) 111 | { 112 | Debug.WriteLine(ex.Message); 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /eShopOnContainers/ViewModels/BasketViewModel.cs: -------------------------------------------------------------------------------- 1 | using eShopOnContainers.Models.Basket; 2 | using eShopOnContainers.Services; 3 | using eShopOnContainers.Services.AppEnvironment; 4 | using eShopOnContainers.Services.Settings; 5 | using eShopOnContainers.ViewModels.Base; 6 | 7 | namespace eShopOnContainers.ViewModels; 8 | 9 | public partial class BasketViewModel : ViewModelBase 10 | { 11 | private readonly IAppEnvironmentService _appEnvironmentService; 12 | private readonly ISettingsService _settingsService; 13 | private readonly ObservableCollectionEx _basketItems = new (); 14 | 15 | public int BadgeCount => _basketItems?.Sum(basketItem => basketItem.Quantity) ?? 0; 16 | 17 | public decimal Total => _basketItems?.Sum(basketItem => basketItem.Quantity * basketItem.UnitPrice) ?? 0m; 18 | 19 | public IReadOnlyList BasketItems => _basketItems; 20 | 21 | public BasketViewModel( 22 | IAppEnvironmentService appEnvironmentService, 23 | INavigationService navigationService, ISettingsService settingsService) 24 | : base(navigationService) 25 | { 26 | _appEnvironmentService = appEnvironmentService; 27 | _settingsService = settingsService; 28 | 29 | _basketItems = new ObservableCollectionEx(); 30 | } 31 | 32 | public override async Task InitializeAsync() 33 | { 34 | var authToken = _settingsService.AuthAccessToken; 35 | var userInfo = await _appEnvironmentService.UserService.GetUserInfoAsync(authToken); 36 | 37 | // Update Basket 38 | var basket = await _appEnvironmentService.BasketService.GetBasketAsync(userInfo.UserId, authToken); 39 | 40 | if (basket != null && basket.Items != null && basket.Items.Any()) 41 | { 42 | await _basketItems.ReloadDataAsync( 43 | async innerList => 44 | { 45 | foreach (var basketItem in basket.Items.ToArray()) 46 | { 47 | await AddBasketItemAsync(basketItem, innerList); 48 | } 49 | }); 50 | } 51 | } 52 | 53 | [RelayCommand] 54 | private Task AddAsync(BasketItem item) 55 | { 56 | return AddBasketItemAsync(item, _basketItems); 57 | } 58 | 59 | private async Task AddBasketItemAsync(BasketItem item, IList basketItems) 60 | { 61 | basketItems.Add(item); 62 | 63 | var authToken = _settingsService.AuthAccessToken; 64 | var userInfo = await _appEnvironmentService.UserService.GetUserInfoAsync(authToken); 65 | var basket = await _appEnvironmentService.BasketService.GetBasketAsync(userInfo.UserId, authToken); 66 | if (basket != null) 67 | { 68 | basket.Items.Add(item); 69 | await _appEnvironmentService.BasketService.UpdateBasketAsync(basket, authToken); 70 | } 71 | 72 | ReCalculateTotal(); 73 | } 74 | 75 | [RelayCommand] 76 | private async Task DeleteAsync(BasketItem item) 77 | { 78 | _basketItems.Remove(item); 79 | 80 | var authToken = _settingsService.AuthAccessToken; 81 | var userInfo = await _appEnvironmentService.UserService.GetUserInfoAsync(authToken); 82 | var basket = await _appEnvironmentService.BasketService.GetBasketAsync(userInfo.UserId, authToken); 83 | if (basket != null) 84 | { 85 | basket.Items.Remove(item); 86 | await _appEnvironmentService.BasketService.UpdateBasketAsync(basket, authToken); 87 | } 88 | 89 | ReCalculateTotal(); 90 | } 91 | 92 | public void ClearBasketItems() 93 | { 94 | _basketItems.Clear(); 95 | 96 | ReCalculateTotal(); 97 | } 98 | 99 | private void ReCalculateTotal() 100 | { 101 | OnPropertyChanged(nameof(BadgeCount)); 102 | OnPropertyChanged(nameof(Total)); 103 | } 104 | 105 | [RelayCommand] 106 | private async Task CheckoutAsync() 107 | { 108 | if (_basketItems?.Any() ?? false) 109 | { 110 | _appEnvironmentService.BasketService.LocalBasketItems = _basketItems; 111 | await NavigationService.NavigateToAsync("Checkout"); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /eShopOnContainers/Views/CatalogView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |