Friday, 1 August 2025

MAUI E-Commerce Laptop Shop App

 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

  1. Product Listing Page

  2. Product Detail Page

  3. Shopping Cart Page

  4. Checkout Page

  5. Payment Gateway Integration

  6. Order Confirmation Page

2. Models

csharp
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

csharp
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

xml
<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

xml
<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

xml
<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

xml
<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

csharp
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:

csharp
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

csharp
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:

  1. MacBook Pro: https://store.storeimages.cdn-apple.com/4982/as-images.apple.com/is/mbp14-spacegray-select-202110

  2. 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

  3. HP Spectre: https://ssl-product-images.www8-hp.com/digmedialib/prodimg/lowres/c07962448.png

  4. Lenovo ThinkPad: https://p1-ofp.static.pub/fes/cms/2022/05/06/3hx0x0j5v2w1j0h4z9m5yq3j1x9x1x945893.png

9. Navigation Service

csharp
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

  1. Dummy Data: The ProductService contains sample laptop data. You can expand this with more products.

  2. Image Loading: Use FFImageLoading package for better image handling:

    xml
    <ffimageloading:CachedImage Source="{Binding Product.ImageUrl}" 
                              Aspect="AspectFit"
                              HeightRequest="200"/>
  3. Payment Gateway: For a real implementation, you would:

    • Create API services for payment processing

    • Implement secure credit card handling

    • Comply with PCI DSS standards

  4. Validation: Add proper validation for:

    • Shipping information

    • Payment details

    • Cart items

  5. Error Handling: Implement proper error handling for:

    • Network issues

    • Payment failures

    • Invalid user input

  6. 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

Cricket Streaming MAUI App: Complete Solution

This document outlines a comprehensive solution for building a cricket streaming MAUI app with subscription plans, geo-restrictions, and pre...

Ads2