Friday, 1 August 2025

MAUI App with Real-time Driver Tracking

 Create a simplified Uber-like MAUI app with the following features:

  • Map integration (Google Maps for Android, Apple Maps for iOS)

  • Automatic driver assignment after 10 seconds

  • Real-time driver movement simulation (2km from user's location)

  • No login/signup screens

  • Basic Uber-like UI

Implementation

1. Create a new MAUI project

2. Install required NuGet packages

text
dotnet add package Microsoft.Maui.Controls.Maps
dotnet add package Plugin.Geolocator

3. Platform-specific setup

Android:

  • Add Google Maps API key to AndroidManifest.xml

  • Add required permissions:

xml
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />

iOS:

  • Add NSLocationWhenInUseUsageDescription to Info.plist

  • Add required entitlements for Apple Maps

4. Main Page Implementation

csharp
// MainPage.xaml.cs
using Microsoft.Maui.Controls.Maps;
using Microsoft.Maui.Maps;
using Plugin.Geolocator;
using System.Timers;

public partial class MainPage : ContentPage
{
    private Pin _driverPin;
    private Map _map;
    private Location _userLocation;
    private Location _driverLocation;
    private Timer _driverMovementTimer;
    private bool _driverAssigned = false;
    private Button _bookRideButton;
    private Grid _rideStatusPanel;
    private Label _rideStatusLabel;
    private Label _driverInfoLabel;
    private Label _etaLabel;

    public MainPage()
    {
        InitializeComponent();
        SetupUI();
        LoadMap();
    }

    private void SetupUI()
    {
        // Uber-like dark theme
        this.BackgroundColor = Color.FromArgb("#000000");

        // Main layout
        var mainGrid = new Grid
        {
            RowDefinitions =
            {
                new RowDefinition { Height = GridLength.Star },
                new RowDefinition { Height = GridLength.Auto }
            }
        };

        // Map
        _map = new Map
        {
            IsShowingUser = true,
            MapType = MapType.Street
        };
        mainGrid.Add(_map, 0, 0);

        // Bottom panel
        var bottomPanel = new Grid
        {
            BackgroundColor = Color.FromArgb("#FFFFFF"),
            Padding = new Thickness(20),
            RowDefinitions =
            {
                new RowDefinition { Height = GridLength.Auto },
                new RowDefinition { Height = GridLength.Auto }
            }
        };

        // Where to? panel
        var searchPanel = new Frame
        {
            BackgroundColor = Color.FromArgb("#F5F5F5"),
            CornerRadius = 8,
            Padding = new Thickness(15),
            Content = new Label
            {
                Text = "Where to?",
                TextColor = Color.FromArgb("#9E9E9E"),
                FontSize = 18
            }
        };
        bottomPanel.Add(searchPanel, 0, 0);

        // Book ride button
        _bookRideButton = new Button
        {
            Text = "BOOK RIDE",
            BackgroundColor = Color.FromArgb("#000000"),
            TextColor = Color.FromArgb("#FFFFFF"),
            FontSize = 18,
            FontAttributes = FontAttributes.Bold,
            CornerRadius = 8,
            HeightRequest = 50,
            Margin = new Thickness(0, 15, 0, 0)
        };
        _bookRideButton.Clicked += OnBookRideClicked;
        bottomPanel.Add(_bookRideButton, 0, 1);

        // Ride status panel (initially hidden)
        _rideStatusPanel = new Grid
        {
            BackgroundColor = Color.FromArgb("#FFFFFF"),
            Padding = new Thickness(20),
            IsVisible = false,
            RowDefinitions =
            {
                new RowDefinition { Height = GridLength.Auto },
                new RowDefinition { Height = GridLength.Auto },
                new RowDefinition { Height = GridLength.Auto }
            }
        };

        _rideStatusLabel = new Label
        {
            Text = "Driver assigned",
            FontSize = 18,
            FontAttributes = FontAttributes.Bold,
            HorizontalOptions = LayoutOptions.Center
        };
        _rideStatusPanel.Add(_rideStatusLabel, 0, 0);

        _driverInfoLabel = new Label
        {
            Text = "Driver: John Doe - Toyota Prius (ABC123)",
            FontSize = 16,
            Margin = new Thickness(0, 10, 0, 0),
            HorizontalOptions = LayoutOptions.Center
        };
        _rideStatusPanel.Add(_driverInfoLabel, 0, 1);

        _etaLabel = new Label
        {
            Text = "ETA: 5 minutes",
            FontSize = 16,
            Margin = new Thickness(0, 10, 0, 0),
            HorizontalOptions = LayoutOptions.Center
        };
        _rideStatusPanel.Add(_etaLabel, 0, 2);

        bottomPanel.Add(_rideStatusPanel, 0, 1);

        mainGrid.Add(bottomPanel, 0, 1);

        Content = mainGrid;
    }

    private async void LoadMap()
    {
        try
        {
            var locator = CrossGeolocator.Current;
            var position = await locator.GetPositionAsync(TimeSpan.FromSeconds(10));
            _userLocation = new Location(position.Latitude, position.Longitude);

            _map.MoveToRegion(MapSpan.FromCenterAndRadius(
                _userLocation,
                Distance.FromKilometers(1)));

            // Add user pin
            var userPin = new Pin
            {
                Label = "You are here",
                Location = _userLocation,
                Type = PinType.Generic
            };
            _map.Pins.Add(userPin);
        }
        catch (Exception ex)
        {
            await DisplayAlert("Error", "Unable to get location: " + ex.Message, "OK");
        }
    }

    private async void OnBookRideClicked(object sender, EventArgs e)
    {
        _bookRideButton.IsEnabled = false;
        _bookRideButton.Text = "Searching for driver...";

        // Simulate driver search (10 seconds)
        await Task.Delay(10000);

        AssignDriver();
    }

    private void AssignDriver()
    {
        if (_userLocation == null) return;

        // Create driver location 2km away from user (random direction)
        var random = new Random();
        var angle = random.NextDouble() * 2 * Math.PI;
        var distanceKm = 2.0;
        var earthRadiusKm = 6371.0;

        var lat1 = _userLocation.Latitude * Math.PI / 180.0;
        var lon1 = _userLocation.Longitude * Math.PI / 180.0;

        var lat2 = Math.Asin(Math.Sin(lat1) * Math.Cos(distanceKm / earthRadiusKm) +
                   Math.Cos(lat1) * Math.Sin(distanceKm / earthRadiusKm) * Math.Cos(angle));
        var lon2 = lon1 + Math.Atan2(Math.Sin(angle) * Math.Sin(distanceKm / earthRadiusKm) * Math.Cos(lat1),
                   Math.Cos(distanceKm / earthRadiusKm) - Math.Sin(lat1) * Math.Sin(lat2));

        _driverLocation = new Location(lat2 * 180.0 / Math.PI, lon2 * 180.0 / Math.PI);

        // Add driver pin
        _driverPin = new Pin
        {
            Label = "Your driver",
            Location = _driverLocation,
            Type = PinType.Place
        };
        _map.Pins.Add(_driverPin);

        // Show both locations on map
        var latMin = Math.Min(_userLocation.Latitude, _driverLocation.Latitude);
        var latMax = Math.Max(_userLocation.Latitude, _driverLocation.Latitude);
        var lonMin = Math.Min(_userLocation.Longitude, _driverLocation.Longitude);
        var lonMax = Math.Max(_userLocation.Longitude, _driverLocation.Longitude);

        var center = new Location((latMin + latMax) / 2, (lonMin + lonMax) / 2);
        var span = new MapSpan(center, latMax - latMin + 0.02, lonMax - lonMin + 0.02);
        _map.MoveToRegion(span);

        _driverAssigned = true;
        _bookRideButton.IsVisible = false;
        _rideStatusPanel.IsVisible = true;

        // Start driver movement simulation
        StartDriverMovement();
    }

    private void StartDriverMovement()
    {
        _driverMovementTimer = new Timer(1000); // Update every second
        _driverMovementTimer.Elapsed += (sender, e) =>
        {
            MainThread.BeginInvokeOnMainThread(() =>
            {
                if (!_driverAssigned) return;

                // Move driver 0.0001 degrees (~11 meters) towards user each second
                var latDiff = _userLocation.Latitude - _driverLocation.Latitude;
                var lonDiff = _userLocation.Longitude - _driverLocation.Longitude;

                var step = 0.0001;
                var distance = Math.Sqrt(latDiff * latDiff + lonDiff * lonDiff);
                if (distance < step) step = distance; // Don't overshoot

                var newLat = _driverLocation.Latitude + (latDiff / distance) * step;
                var newLon = _driverLocation.Longitude + (lonDiff / distance) * step;

                _driverLocation = new Location(newLat, newLon);
                _driverPin.Location = _driverLocation;

                // Update ETA
                var remainingDistanceKm = CalculateDistance(_driverLocation, _userLocation);
                var etaMinutes = (int)(remainingDistanceKm / 0.5 * 60); // Assuming 30 km/h speed
                _etaLabel.Text = $"ETA: {etaMinutes} minute{(etaMinutes != 1 ? "s" : "")}";

                // If driver arrived
                if (remainingDistanceKm < 0.01) // 10 meters
                {
                    _driverMovementTimer.Stop();
                    _rideStatusLabel.Text = "Driver has arrived!";
                }
            });
        };
        _driverMovementTimer.Start();
    }

    private double CalculateDistance(Location loc1, Location loc2)
    {
        // Haversine formula to calculate distance between two points
        var lat1 = loc1.Latitude * Math.PI / 180.0;
        var lon1 = loc1.Longitude * Math.PI / 180.0;
        var lat2 = loc2.Latitude * Math.PI / 180.0;
        var lon2 = loc2.Longitude * Math.PI / 180.0;

        var dLat = lat2 - lat1;
        var dLon = lon2 - lon1;

        var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
                Math.Cos(lat1) * Math.Cos(lat2) *
                Math.Sin(dLon / 2) * Math.Sin(dLon / 2);
        var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));

        return 6371 * c; // Distance in km
    }

    protected override void OnDisappearing()
    {
        base.OnDisappearing();
        _driverMovementTimer?.Stop();
    }
}

5. App.xaml.cs

csharp
public partial class App : Application
{
    public App()
    {
        InitializeComponent();

        MainPage = new NavigationPage(new MainPage());
    }
}

Features Implemented

  1. Map Integration:

    • Uses MAUI Maps which automatically uses Google Maps on Android and Apple Maps on iOS

    • Shows user location and driver location

  2. Driver Assignment:

    • After clicking "BOOK RIDE", waits 10 seconds then assigns a driver

    • Driver appears 2km away from user in a random direction

  3. Real-time Movement:

    • Driver moves towards user at a realistic speed (~30 km/h)

    • Updates position every second

    • ETA is calculated and updated in real-time

  4. Uber-like UI:

    • Dark theme like Uber

    • Simple booking flow

    • Ride status panel showing driver info and ETA

  5. Location Services:

    • Uses Geolocator plugin to get accurate user location

    • Proper permissions set up for both platforms

Improvements Needed

  1. Map Customization:

    • Add custom map pins for user and driver

    • Add route drawing between driver and user

  2. Real-world Simulation:

    • Use actual road directions instead of straight-line movement

    • Add traffic simulation

  3. Performance:

    • Optimize location updates to balance accuracy and battery life

    • Consider using background services for real-world apps

  4. Error Handling:

    • Better handling of location permission denials

    • Network connectivity checks

This implementation provides a solid foundation for an Uber-like app with the core functionality you requested, while keeping it simple with just a few screens and no authentication requirements.

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