Here's a comprehensive design for a MAUI e-commerce app for a laptop shop with cart functionality and payment gateway integration.
App Structure
1. Main Components
Product Listing Page
Product Detail Page
Shopping Cart Page
Checkout Page
Payment Gateway Integration
Order Confirmation Page
2. Models
public class Product { public int Id { get; set; } public string Name { get; set; } public string Description { get; set; } public decimal Price { get; set; } public string ImageUrl { get; set; } public string Brand { get; set; } public string Processor { get; set; } public int RAM { get; set; } // in GB public int Storage { get; set; } // in GB } public class CartItem { public Product Product { get; set; } public int Quantity { get; set; } } public class Order { public int Id { get; set; } public DateTime OrderDate { get; set; } public List<CartItem> Items { get; set; } public decimal TotalAmount { get; set; } public string ShippingAddress { get; set; } public string PaymentMethod { get; set; } }
3. Services
public class ProductService { private List<Product> _products = new List<Product> { new Product { Id = 1, Name = "MacBook Pro 14\"", Description = "Apple M1 Pro chip, 16GB RAM, 512GB SSD", Price = 1999.00m, ImageUrl = "https://example.com/macbook-pro.jpg", Brand = "Apple", Processor = "M1 Pro", RAM = 16, Storage = 512 }, new Product { Id = 2, Name = "Dell XPS 15", Description = "Intel i7-11800H, 16GB RAM, 1TB SSD", Price = 1799.99m, ImageUrl = "https://example.com/dell-xps.jpg", Brand = "Dell", Processor = "Intel i7-11800H", RAM = 16, Storage = 1024 }, // Add more dummy products }; public List<Product> GetProducts() => _products; public Product GetProductById(int id) => _products.FirstOrDefault(p => p.Id == id); } public class CartService { private List<CartItem> _cartItems = new List<CartItem>(); public void AddToCart(Product product, int quantity = 1) { var existingItem = _cartItems.FirstOrDefault(i => i.Product.Id == product.Id); if (existingItem != null) { existingItem.Quantity += quantity; } else { _cartItems.Add(new CartItem { Product = product, Quantity = quantity }); } } public void RemoveFromCart(int productId) { var item = _cartItems.FirstOrDefault(i => i.Product.Id == productId); if (item != null) { _cartItems.Remove(item); } } public void UpdateQuantity(int productId, int quantity) { var item = _cartItems.FirstOrDefault(i => i.Product.Id == productId); if (item != null) { item.Quantity = quantity; } } public List<CartItem> GetCartItems() => _cartItems; public decimal GetTotal() => _cartItems.Sum(i => i.Product.Price * i.Quantity); public void ClearCart() => _cartItems.Clear(); }
4. Pages Implementation
ProductListingPage.xaml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="LaptopShop.Views.ProductListingPage" Title="Laptops"> <CollectionView ItemsSource="{Binding Products}" SelectionMode="Single" SelectionChangedCommand="{Binding ProductSelectedCommand}" SelectionChangedCommandParameter="{Binding SelectedItem, Source={RelativeSource Self}}"> <CollectionView.ItemsLayout> <GridItemsLayout Orientation="Vertical" Span="2" /> </CollectionView.ItemsLayout> <CollectionView.ItemTemplate> <DataTemplate> <Frame Padding="10" Margin="5" CornerRadius="10"> <StackLayout> <Image Source="{Binding ImageUrl}" Aspect="AspectFit" HeightRequest="120"/> <Label Text="{Binding Name}" FontAttributes="Bold"/> <Label Text="{Binding Price, StringFormat='${0}'}" TextColor="Green"/> <Label Text="{Binding Brand}" FontSize="Micro"/> </StackLayout> </Frame> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> </ContentPage>
ProductDetailPage.xaml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="LaptopShop.Views.ProductDetailPage" Title="Product Details"> <ScrollView> <StackLayout Padding="20" Spacing="15"> <Image Source="{Binding Product.ImageUrl}" Aspect="AspectFit" HeightRequest="200"/> <Label Text="{Binding Product.Name}" FontSize="Title" FontAttributes="Bold"/> <Label Text="{Binding Product.Price, StringFormat='${0}'}" TextColor="Green" FontSize="Subtitle"/> <Label Text="Specifications" FontAttributes="Bold"/> <Label Text="{Binding Product.Description}"/> <Label Text="{Binding Product.Processor, StringFormat='Processor: {0}'}"/> <Label Text="{Binding Product.RAM, StringFormat='RAM: {0}GB'}"/> <Label Text="{Binding Product.Storage, StringFormat='Storage: {0}GB SSD'}"/> <StackLayout Orientation="Horizontal" Spacing="10"> <Button Text="Add to Cart" Command="{Binding AddToCartCommand}" HorizontalOptions="FillAndExpand"/> <Button Text="Buy Now" Command="{Binding BuyNowCommand}" HorizontalOptions="FillAndExpand"/> </StackLayout> </StackLayout> </ScrollView> </ContentPage>
CartPage.xaml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="LaptopShop.Views.CartPage" Title="Your Cart"> <Grid RowDefinitions="*,Auto"> <CollectionView ItemsSource="{Binding CartItems}" Grid.Row="0"> <CollectionView.ItemTemplate> <DataTemplate> <Frame Padding="10" Margin="5" CornerRadius="10"> <Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="Auto,Auto,Auto"> <Image Source="{Binding Product.ImageUrl}" Aspect="AspectFit" HeightRequest="60" Grid.RowSpan="3"/> <Label Text="{Binding Product.Name}" FontAttributes="Bold" Grid.Column="1"/> <Label Text="{Binding Product.Price, StringFormat='${0}'}" TextColor="Green" Grid.Column="1" Grid.Row="1"/> <Stepper Value="{Binding Quantity}" Minimum="1" Maximum="10" Grid.Column="2"/> <Button Text="Remove" Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:CartViewModel}}, Path=RemoveItemCommand}" CommandParameter="{Binding Product.Id}" Grid.Column="2" Grid.Row="1"/> </Grid> </Frame> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> <Frame Grid.Row="1" Padding="10" Margin="5" CornerRadius="10"> <StackLayout> <Label Text="{Binding Total, StringFormat='Total: ${0}'}" FontSize="Subtitle" FontAttributes="Bold"/> <Button Text="Proceed to Checkout" Command="{Binding CheckoutCommand}"/> </StackLayout> </Frame> </Grid> </ContentPage>
CheckoutPage.xaml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="LaptopShop.Views.CheckoutPage" Title="Checkout"> <ScrollView> <StackLayout Padding="20" Spacing="15"> <Label Text="Shipping Information" FontSize="Title" FontAttributes="Bold"/> <Entry Placeholder="Full Name" Text="{Binding ShippingInfo.FullName}"/> <Entry Placeholder="Address" Text="{Binding ShippingInfo.Address}"/> <Entry Placeholder="City" Text="{Binding ShippingInfo.City}"/> <Entry Placeholder="State" Text="{Binding ShippingInfo.State}"/> <Entry Placeholder="Zip Code" Text="{Binding ShippingInfo.ZipCode}"/> <Entry Placeholder="Phone" Text="{Binding ShippingInfo.Phone}" Keyboard="Telephone"/> <Label Text="Payment Method" FontSize="Title" FontAttributes="Bold" Margin="0,20,0,0"/> <Picker Title="Select Payment Method" ItemsSource="{Binding PaymentMethods}" SelectedItem="{Binding SelectedPaymentMethod}"/> <Frame IsVisible="{Binding ShowCreditCardFields}"> <StackLayout> <Entry Placeholder="Card Number" Keyboard="Numeric"/> <Entry Placeholder="Expiry Date (MM/YY)"/> <Entry Placeholder="CVV" Keyboard="Numeric"/> <Entry Placeholder="Cardholder Name"/> </StackLayout> </Frame> <Label Text="Order Summary" FontSize="Title" FontAttributes="Bold" Margin="0,20,0,0"/> <CollectionView ItemsSource="{Binding CartItems}" HeightRequest="150"> <CollectionView.ItemTemplate> <DataTemplate> <Grid ColumnDefinitions="*,Auto"> <Label Text="{Binding Product.Name}"/> <Label Text="{Binding Product.Price, StringFormat='${0} x {1}', Converter={StaticResource QuantityConverter}, ConverterParameter={Binding Quantity}}" Grid.Column="1"/> </Grid> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> <Label Text="{Binding Total, StringFormat='Total: ${0}'}" FontSize="Subtitle" FontAttributes="Bold"/> <Button Text="Place Order" Command="{Binding PlaceOrderCommand}"/> </StackLayout> </ScrollView> </ContentPage>
5. ViewModels
public class ProductListingViewModel : BaseViewModel { private readonly ProductService _productService; private readonly INavigationService _navigationService; public ObservableCollection<Product> Products { get; } = new(); public ICommand ProductSelectedCommand { get; } public ProductListingViewModel(ProductService productService, INavigationService navigationService) { _productService = productService; _navigationService = navigationService; ProductSelectedCommand = new Command<Product>(OnProductSelected); LoadProducts(); } private void LoadProducts() { var products = _productService.GetProducts(); Products.Clear(); foreach (var product in products) { Products.Add(product); } } private async void OnProductSelected(Product product) { if (product == null) return; await _navigationService.NavigateToAsync<ProductDetailViewModel>(("product", product)); } } public class ProductDetailViewModel : BaseViewModel { private readonly CartService _cartService; private readonly INavigationService _navigationService; public Product Product { get; } public ICommand AddToCartCommand { get; } public ICommand BuyNowCommand { get; } public ProductDetailViewModel(Product product, CartService cartService, INavigationService navigationService) { Product = product; _cartService = cartService; _navigationService = navigationService; AddToCartCommand = new Command(AddToCart); BuyNowCommand = new Command(BuyNow); } private void AddToCart() { _cartService.AddToCart(Product); // Show toast/snackbar } private async void BuyNow() { _cartService.AddToCart(Product); await _navigationService.NavigateToAsync<CartViewModel>(); } } public class CartViewModel : BaseViewModel { private readonly CartService _cartService; private readonly INavigationService _navigationService; public ObservableCollection<CartItem> CartItems { get; } = new(); public decimal Total => _cartService.GetTotal(); public ICommand RemoveItemCommand { get; } public ICommand CheckoutCommand { get; } public CartViewModel(CartService cartService, INavigationService navigationService) { _cartService = cartService; _navigationService = navigationService; RemoveItemCommand = new Command<int>(RemoveItem); CheckoutCommand = new Command(Checkout); LoadCartItems(); } private void LoadCartItems() { CartItems.Clear(); foreach (var item in _cartService.GetCartItems()) { CartItems.Add(item); } } private void RemoveItem(int productId) { _cartService.RemoveFromCart(productId); LoadCartItems(); OnPropertyChanged(nameof(Total)); } private async void Checkout() { if (CartItems.Count == 0) { // Show alert return; } await _navigationService.NavigateToAsync<CheckoutViewModel>(); } } public class CheckoutViewModel : BaseViewModel { private readonly CartService _cartService; private readonly INavigationService _navigationService; private readonly IOrderService _orderService; public ObservableCollection<CartItem> CartItems { get; } = new(); public decimal Total => _cartService.GetTotal(); public ShippingInfo ShippingInfo { get; } = new(); public List<string> PaymentMethods { get; } = new() { "Credit Card", "PayPal", "Bank Transfer" }; public string SelectedPaymentMethod { get; set; } public bool ShowCreditCardFields => SelectedPaymentMethod == "Credit Card"; public ICommand PlaceOrderCommand { get; } public CheckoutViewModel(CartService cartService, INavigationService navigationService, IOrderService orderService) { _cartService = cartService; _navigationService = navigationService; _orderService = orderService; PlaceOrderCommand = new Command(PlaceOrder); LoadCartItems(); } private void LoadCartItems() { CartItems.Clear(); foreach (var item in _cartService.GetCartItems()) { CartItems.Add(item); } } private async void PlaceOrder() { // Validate shipping info var order = new Order { OrderDate = DateTime.Now, Items = _cartService.GetCartItems().ToList(), TotalAmount = Total, ShippingAddress = $"{ShippingInfo.Address}, {ShippingInfo.City}, {ShippingInfo.State} {ShippingInfo.ZipCode}", PaymentMethod = SelectedPaymentMethod }; var success = await _orderService.PlaceOrder(order); if (success) { _cartService.ClearCart(); await _navigationService.NavigateToAsync<OrderConfirmationViewModel>(("order", order)); } else { // Show error } } } public class OrderConfirmationViewModel : BaseViewModel { public Order Order { get; } public ICommand ContinueShoppingCommand { get; } public OrderConfirmationViewModel(Order order, INavigationService navigationService) { Order = order; ContinueShoppingCommand = new Command(async () => { await navigationService.NavigateToAsync<ProductListingViewModel>(); await navigationService.PopToRootAsync(); }); } }
6. Payment Gateway Integration
For payment processing, you can integrate with services like Stripe, PayPal, or other payment processors. Here's a basic example using a mock payment service:
public interface IPaymentService { Task<bool> ProcessPayment(decimal amount, string paymentMethod, Dictionary<string, string> paymentDetails); } public class MockPaymentService : IPaymentService { public async Task<bool> ProcessPayment(decimal amount, string paymentMethod, Dictionary<string, string> paymentDetails) { // Simulate API call delay await Task.Delay(1000); // In a real app, this would call the actual payment gateway API // For demo purposes, we'll just return true return true; } }
7. App Startup and Dependency Injection
public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); }); // Register services builder.Services.AddSingleton<ProductService>(); builder.Services.AddSingleton<CartService>(); builder.Services.AddSingleton<IOrderService, OrderService>(); builder.Services.AddSingleton<IPaymentService, MockPaymentService>(); builder.Services.AddSingleton<INavigationService, NavigationService>(); // Register view models builder.Services.AddTransient<ProductListingViewModel>(); builder.Services.AddTransient<ProductDetailViewModel>(); builder.Services.AddTransient<CartViewModel>(); builder.Services.AddTransient<CheckoutViewModel>(); builder.Services.AddTransient<OrderConfirmationViewModel>(); // Register pages builder.Services.AddTransient<ProductListingPage>(); builder.Services.AddTransient<ProductDetailPage>(); builder.Services.AddTransient<CartPage>(); builder.Services.AddTransient<CheckoutPage>(); builder.Services.AddTransient<OrderConfirmationPage>(); return builder.Build(); } }
8. Dummy Images
For dummy laptop images, you can use placeholder services or actual laptop images from manufacturers:
MacBook Pro:
https://store.storeimages.cdn-apple.com/4982/as-images.apple.com/is/mbp14-spacegray-select-202110
Dell XPS:
https://i.dell.com/is/image/DellContent/content/dam/ss2/product-images/dell-client-products/notebooks/xps-notebooks/xps-15-9520/media-gallery/notebook-xps-9520-t-black-gallery-1.psd?fmt=png-alpha&pscan=auto&scl=1&wid=3319&hei=2405&qlt=100,0&resMode=sharp2&size=3319,2405
HP Spectre:
https://ssl-product-images.www8-hp.com/digmedialib/prodimg/lowres/c07962448.png
Lenovo ThinkPad:
https://p1-ofp.static.pub/fes/cms/2022/05/06/3hx0x0j5v2w1j0h4z9m5yq3j1x9x1x945893.png
9. Navigation Service
public interface INavigationService { Task NavigateToAsync<TViewModel>() where TViewModel : BaseViewModel; Task NavigateToAsync<TViewModel>(params (string parameterName, object value)[] parameters) where TViewModel : BaseViewModel; Task PopAsync(); Task PopToRootAsync(); } public class NavigationService : INavigationService { private readonly IServiceProvider _services; public NavigationService(IServiceProvider services) { _services = services; } private INavigation Navigation { get { var navigation = Application.Current?.MainPage?.Navigation; if (navigation is null) { throw new Exception("Navigation is not available"); } return navigation; } } public Task NavigateToAsync<TViewModel>() where TViewModel : BaseViewModel { return NavigateToAsync(typeof(TViewModel)); } public Task NavigateToAsync<TViewModel>(params (string parameterName, object value)[] parameters) where TViewModel : BaseViewModel { return InternalNavigateToAsync(typeof(TViewModel), parameters); } private async Task InternalNavigateToAsync(Type viewModelType, params (string parameterName, object value)[] parameters) { var page = GetPage(viewModelType); if (page is not null) { var viewModel = _services.GetService(viewModelType) as BaseViewModel; if (viewModel is not null) { foreach (var (parameterName, value) in parameters) viewModel.ApplyQueryAttributes(new Dictionary<string, object> { { parameterName, value } }); page.BindingContext = viewModel; } await Navigation.PushAsync(page); } } private Page GetPage(Type viewModelType) { if (viewModelType == typeof(ProductListingViewModel)) return _services.GetService<ProductListingPage>(); if (viewModelType == typeof(ProductDetailViewModel)) return _services.GetService<ProductDetailPage>(); if (viewModelType == typeof(CartViewModel)) return _services.GetService<CartPage>(); if (viewModelType == typeof(CheckoutViewModel)) return _services.GetService<CheckoutPage>(); if (viewModelType == typeof(OrderConfirmationViewModel)) return _services.GetService<OrderConfirmationPage>(); throw new Exception($"No page found for view model type: {viewModelType}"); } public Task PopAsync() { return Navigation.PopAsync(); } public Task PopToRootAsync() { return Navigation.PopToRootAsync(); } }
Implementation Notes
Dummy Data: The
ProductService
contains sample laptop data. You can expand this with more products.Image Loading: Use
FFImageLoading
package for better image handling:<ffimageloading:CachedImage Source="{Binding Product.ImageUrl}" Aspect="AspectFit" HeightRequest="200"/>
Payment Gateway: For a real implementation, you would:
Create API services for payment processing
Implement secure credit card handling
Comply with PCI DSS standards
Validation: Add proper validation for:
Shipping information
Payment details
Cart items
Error Handling: Implement proper error handling for:
Network issues
Payment failures
Invalid user input
UI Improvements:
Add loading indicators
Implement pull-to-refresh
Add search and filtering capabilities
Implement dark mode support
This implementation provides a complete foundation for a MAUI e-commerce laptop shop app with all the requested features. You can further customize the UI and add more features as needed.
No comments:
Post a Comment