flutter_map_smart 1.1.0 copy "flutter_map_smart: ^1.1.0" to clipboard
flutter_map_smart: ^1.1.0 copied to clipboard

A plug-and-play OpenStreetMap widget with clustering, image markers, user location, and nearby radius support.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_map_smart/flutter_map_smart.dart';

void main() {
  runApp(const MyApp());
}

class Place {
  final String name;
  final double lat;
  final double lng;
  final String? image;
  final String description;
  final PlaceType type;

  const Place({
    required this.name,
    required this.lat,
    required this.lng,
    this.image,
    required this.description,
    required this.type,
  });

  Color getTypeColor() {
    switch (type) {
      case PlaceType.restaurant:
        return const Color(0xFFEF4444);
      case PlaceType.hotel:
        return const Color(0xFF3B82F6);
      case PlaceType.landmark:
        return const Color(0xFFF59E0B);
      case PlaceType.hospital:
        return const Color(0xFF10B981);
      case PlaceType.tech:
        return const Color(0xFF8B5CF6);
    }
  }
}

enum PlaceType { restaurant, hotel, landmark, hospital, tech }

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        brightness: Brightness.light,
        fontFamily: 'Inter',
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF1E293B), // Slate 800
          primary: const Color(0xFF1E293B),
          secondary: const Color(0xFF6366F1), // Indigo
          surface: Colors.white,
        ),
        scaffoldBackgroundColor: const Color(0xFFF1F5F9),
        appBarTheme: const AppBarTheme(
          backgroundColor: Colors.white,
          surfaceTintColor: Colors.transparent,
          elevation: 0,
          centerTitle: false,
          titleTextStyle: TextStyle(
            color: Color(0xFF1E293B),
            fontSize: 20,
            fontWeight: FontWeight.w700,
          ),
          iconTheme: IconThemeData(color: Color(0xFF1E293B)),
        ),
        cardTheme: CardThemeData(
          elevation: 0,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(16),
          ),
          color: Colors.white,
        ),
      ),
      home: const ExampleSelector(),
    );
  }
}

// ✨ Standard UI Components
class IconBox extends StatelessWidget {
  final IconData icon;
  final Color color;
  final double size;

  const IconBox({
    super.key,
    required this.icon,
    required this.color,
    this.size = 20,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(8),
      decoration: BoxDecoration(
        color: color.withValues(alpha: 0.1),
        borderRadius: BorderRadius.circular(8),
      ),
      child: Icon(icon, color: color, size: size),
    );
  }
}

// 🎯 Main selector - Modern clean design
class ExampleSelector extends StatelessWidget {
  const ExampleSelector({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Smart OSM Map')),
      body: ListView(
        padding: const EdgeInsets.all(20),
        children: const [
          _SectionHeader(title: 'Fundamentals'),
          SizedBox(height: 12),
          _ExampleTile(
            title: 'Basic Usage',
            subtitle: 'Simple map with markers',
            icon: Icons.map_rounded,
            example: BasicExample(),
          ),
          SizedBox(height: 12),
          _ExampleTile(
            title: 'Location & Nearby',
            subtitle: 'Proximity based search',
            icon: Icons.location_on_rounded,
            example: LocationExample(),
          ),
          SizedBox(height: 12),
          _ExampleTile(
            title: 'Permissions',
            subtitle: 'Handling location access',
            icon: Icons.security_rounded,
            example: PermissionExample(),
          ),
          SizedBox(height: 32),
          _SectionHeader(title: 'Advanced'),
          SizedBox(height: 12),
          _ExampleTile(
            title: 'Custom Styling',
            subtitle: 'Themes and colors',
            icon: Icons.palette_rounded,
            example: StylingExample(),
          ),
          SizedBox(height: 12),
          _ExampleTile(
            title: 'Performance',
            subtitle: 'Clustering 1000+ points',
            icon: Icons.speed_rounded,
            example: PerformanceExample(),
          ),
          SizedBox(height: 12),
          _ExampleTile(
            title: 'Network Assets',
            subtitle: 'Loading remote images',
            icon: Icons.cloud_download_rounded,
            example: NetworkImageExample(),
          ),
        ],
      ),
    );
  }
}

class _SectionHeader extends StatelessWidget {
  final String title;

  const _SectionHeader({required this.title});

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(left: 4),
      child: Text(
        title,
        style: const TextStyle(
          fontSize: 13,
          fontWeight: FontWeight.w600,
          color: Color(0xFF64748B),
          letterSpacing: 0.2,
        ),
      ),
    );
  }
}

class _ExampleTile extends StatelessWidget {
  final String title;
  final String subtitle;
  final IconData icon;
  final Widget example;

  const _ExampleTile({
    required this.title,
    required this.subtitle,
    required this.icon,
    required this.example,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: const Color(0xFFE2E8F0)),
      ),
      child: ListTile(
        onTap: () =>
            Navigator.push(context, MaterialPageRoute(builder: (_) => example)),
        contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
        leading: Container(
          padding: const EdgeInsets.all(8),
          decoration: BoxDecoration(
            color: const Color(0xFFF1F5F9),
            borderRadius: BorderRadius.circular(8),
          ),
          child: Icon(icon, color: const Color(0xFF1E293B), size: 24),
        ),
        title: Text(
          title,
          style: const TextStyle(
            fontSize: 16,
            fontWeight: FontWeight.w600,
            color: Color(0xFF1E293B),
          ),
        ),
        subtitle: Text(
          subtitle,
          style: const TextStyle(fontSize: 13, color: Color(0xFF64748B)),
        ),
        trailing: const Icon(
          Icons.chevron_right_rounded,
          color: Color(0xFF94A3B8),
        ),
      ),
    );
  }
}

// 📍 Example 1: Basic Usage
class BasicExample extends StatefulWidget {
  const BasicExample({super.key});

  @override
  State<BasicExample> createState() => _BasicExampleState();
}

class _BasicExampleState extends State<BasicExample> {
  Place? _selectedPlace;

  final places = [
    const Place(
      name: 'India Gate',
      lat: 28.6129,
      lng: 77.2295,
      image: 'assets/images/india_gate.png',
      description: 'War memorial in New Delhi',
      type: PlaceType.landmark,
    ),
    const Place(
      name: 'Red Fort',
      lat: 28.6562,
      lng: 77.2410,
      image: 'assets/images/red_fort.png',
      description: 'Historic Mughal fort',
      type: PlaceType.landmark,
    ),
    const Place(
      name: 'No Image Marker',
      lat: 28.6315,
      lng: 77.2167,
      image: null,
      description: 'Demonstrates default marker',
      type: PlaceType.landmark,
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Basic Usage'),
        backgroundColor: Colors.white,
      ),
      body: Stack(
        children: [
          FlutterMapSmart.simple(
            items: places,
            latitude: (p) => p.lat,
            longitude: (p) => p.lng,
            markerImage: (p) => p.image,
            onTap: (place) => setState(() => _selectedPlace = place),
          ),
          if (_selectedPlace != null)
            Positioned(
              bottom: 24,
              left: 20,
              right: 20,
              child: _PlaceDetailCard(
                place: _selectedPlace!,
                onClose: () => setState(() => _selectedPlace = null),
              ),
            ),
        ],
      ),
    );
  }
}

class _PlaceDetailCard extends StatelessWidget {
  final Place place;
  final VoidCallback onClose;

  const _PlaceDetailCard({required this.place, required this.onClose});

  @override
  Widget build(BuildContext context) {
    final color = place.getTypeColor();

    return Container(
      padding: const EdgeInsets.all(20),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(16),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withValues(alpha: 0.06),
            blurRadius: 20,
            offset: const Offset(0, 8),
          ),
        ],
        border: Border.all(color: const Color(0xFFE2E8F0)),
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              Container(
                width: 44,
                height: 44,
                decoration: BoxDecoration(
                  color: color.withValues(alpha: 0.1),
                  borderRadius: BorderRadius.circular(10),
                ),
                child: Icon(Icons.location_on_rounded, color: color, size: 22),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      place.name,
                      style: const TextStyle(
                        fontSize: 17,
                        fontWeight: FontWeight.w700,
                        color: Color(0xFF1E293B),
                      ),
                    ),
                    Text(
                      place.type.name.toUpperCase(),
                      style: TextStyle(
                        fontSize: 11,
                        fontWeight: FontWeight.w700,
                        color: color,
                        letterSpacing: 0.5,
                      ),
                    ),
                  ],
                ),
              ),
              IconButton(
                onPressed: onClose,
                icon: const Icon(Icons.close_rounded, size: 20),
                style: IconButton.styleFrom(
                  backgroundColor: const Color(0xFFF1F5F9),
                  padding: EdgeInsets.zero,
                ),
              ),
            ],
          ),
          const SizedBox(height: 16),
          Text(
            place.description,
            style: const TextStyle(
              fontSize: 14,
              color: Color(0xFF64748B),
              height: 1.5,
            ),
          ),
          const SizedBox(height: 20),
          SizedBox(
            width: double.infinity,
            child: ElevatedButton(
              onPressed: () {},
              style: ElevatedButton.styleFrom(
                backgroundColor: const Color(0xFF1E293B),
                foregroundColor: Colors.white,
                elevation: 0,
                padding: const EdgeInsets.symmetric(vertical: 16),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(10),
                ),
              ),
              child: const Text(
                'View Details',
                style: TextStyle(fontWeight: FontWeight.w600),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

// 📍 Example 2: Location & Nearby
class LocationExample extends StatefulWidget {
  const LocationExample({super.key});

  @override
  State<LocationExample> createState() => _LocationExampleState();
}

class _LocationExampleState extends State<LocationExample> {
  bool showLocation = false;
  bool enableNearby = false;
  double radiusKm = 10;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Location & Nearby'),
        backgroundColor: Colors.white,
      ),
      body: Stack(
        children: [
          FlutterMapSmart.simple(
            items: _generateDelhiPlaces(),
            latitude: (p) => p.lat,
            longitude: (p) => p.lng,
            markerImage: (p) => p.image,
            showUserLocation: showLocation,
            enableNearby: enableNearby,
            nearbyRadiusKm: radiusKm,
            radiusColor: const Color(0xFF6366F1).withValues(alpha: 0.12),
          ),
          Positioned(
            bottom: 24,
            left: 20,
            right: 20,
            child: Container(
              padding: const EdgeInsets.all(20),
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(16),
                boxShadow: [
                  BoxShadow(
                    color: Colors.black.withValues(alpha: 0.06),
                    blurRadius: 20,
                    offset: const Offset(0, 4),
                  ),
                ],
                border: Border.all(color: const Color(0xFFE2E8F0)),
              ),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Location Settings',
                    style: TextStyle(
                      fontSize: 15,
                      fontWeight: FontWeight.bold,
                      color: Color(0xFF1E293B),
                    ),
                  ),
                  const SizedBox(height: 16),
                  _buildToggle(
                    icon: Icons.my_location_rounded,
                    title: 'Live Tracking',
                    value: showLocation,
                    onChanged: (v) => setState(() => showLocation = v),
                    activeColor: const Color(0xFF6366F1),
                  ),
                  const SizedBox(height: 12),
                  _buildToggle(
                    icon: Icons.radar_rounded,
                    title: 'Proximity Filter',
                    value: enableNearby,
                    enabled: showLocation,
                    subtitle: 'Within ${radiusKm.toInt()} km',
                    onChanged: (v) => setState(() => enableNearby = v),
                    activeColor: const Color(0xFF6366F1),
                  ),
                  if (enableNearby) ...[
                    const SizedBox(height: 20),
                    _buildRadiusSlider(),
                  ],
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildToggle({
    required IconData icon,
    required String title,
    required bool value,
    required Function(bool) onChanged,
    required Color activeColor,
    bool enabled = true,
    String? subtitle,
  }) {
    return Row(
      children: [
        IconBox(
          icon: icon,
          color: value ? activeColor : const Color(0xFF94A3B8),
        ),
        const SizedBox(width: 12),
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                title,
                style: const TextStyle(
                  fontSize: 14,
                  fontWeight: FontWeight.w600,
                  color: Color(0xFF1E293B),
                ),
              ),
              if (subtitle != null)
                Text(
                  subtitle,
                  style: const TextStyle(
                    fontSize: 11,
                    color: Color(0xFF64748B),
                  ),
                ),
            ],
          ),
        ),
        SizedBox(
          height: 32,
          child: Switch.adaptive(
            value: value,
            onChanged: enabled ? onChanged : null,
            activeTrackColor: activeColor,
          ),
        ),
      ],
    );
  }

  Widget _buildRadiusSlider() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            const Text(
              'Search Radius',
              style: TextStyle(
                fontSize: 13,
                fontWeight: FontWeight.w600,
                color: Color(0xFF64748B),
              ),
            ),
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
              decoration: BoxDecoration(
                color: const Color(0xFF10B981).withValues(alpha: 0.1),
                borderRadius: BorderRadius.circular(8),
              ),
              child: Text(
                '${radiusKm.toInt()} km',
                style: const TextStyle(
                  fontSize: 12,
                  fontWeight: FontWeight.w800,
                  color: Color(0xFF10B981),
                ),
              ),
            ),
          ],
        ),
        Slider(
          value: radiusKm,
          min: 1,
          max: 50,
          divisions: 49,
          onChanged: (v) => setState(() => radiusKm = v),
          activeColor: const Color(0xFF10B981),
          inactiveColor: const Color(0xFFE2E8F0),
        ),
      ],
    );
  }
}

// 📍 Example 3: Permission Handling
class PermissionExample extends StatefulWidget {
  const PermissionExample({super.key});

  @override
  State<PermissionExample> createState() => _PermissionExampleState();
}

class _PermissionExampleState extends State<PermissionExample> {
  String? permissionStatus;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Permissions'),
        backgroundColor: Colors.white,
      ),
      body: Stack(
        children: [
          FlutterMapSmart.simple(
            items: _generateDelhiPlaces(),
            latitude: (p) => p.lat,
            longitude: (p) => p.lng,
            markerImage: (p) => p.image,
            showUserLocation: true,
            onLocationPermissionGranted: () {
              setState(() => permissionStatus = 'Location permission granted');
            },
            onLocationPermissionDenied: () {
              setState(() => permissionStatus = 'Location permission denied');
            },
            onLocationPermissionDeniedForever: () {
              setState(
                () =>
                    permissionStatus = 'Location permission permanently denied',
              );
            },
            onLocationServiceDisabled: () {
              setState(
                () => permissionStatus = 'Location services are disabled',
              );
            },
          ),
          if (permissionStatus != null)
            Positioned(
              top: 20,
              left: 20,
              right: 20,
              child: Container(
                padding: const EdgeInsets.all(16),
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.circular(12),
                  border: Border.all(color: const Color(0xFFE2E8F0)),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withValues(alpha: 0.05),
                      blurRadius: 10,
                      offset: const Offset(0, 4),
                    ),
                  ],
                ),
                child: Row(
                  children: [
                    const Icon(
                      Icons.info_outline_rounded,
                      color: Color(0xFF6366F1),
                      size: 20,
                    ),
                    const SizedBox(width: 12),
                    Expanded(
                      child: Text(
                        permissionStatus!,
                        style: const TextStyle(
                          fontSize: 13,
                          fontWeight: FontWeight.w600,
                          color: Color(0xFF1E293B),
                        ),
                      ),
                    ),
                    IconButton(
                      icon: const Icon(Icons.close_rounded, size: 18),
                      onPressed: () => setState(() => permissionStatus = null),
                    ),
                  ],
                ),
              ),
            ),
          Positioned(
            bottom: 24,
            left: 20,
            right: 20,
            child: Container(
              padding: const EdgeInsets.all(20),
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(16),
                border: Border.all(color: const Color(0xFFE2E8F0)),
                boxShadow: [
                  BoxShadow(
                    color: Colors.black.withValues(alpha: 0.05),
                    blurRadius: 10,
                    offset: const Offset(0, 4),
                  ),
                ],
              ),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Permission Statuses',
                    style: TextStyle(
                      fontSize: 15,
                      fontWeight: FontWeight.bold,
                      color: Color(0xFF1E293B),
                    ),
                  ),
                  const SizedBox(height: 16),
                  _buildPermissionItem(
                    Icons.check_circle_rounded,
                    'Granted',
                    Colors.green,
                  ),
                  _buildPermissionItem(
                    Icons.error_outline_rounded,
                    'Denied',
                    Colors.orange,
                  ),
                  _buildPermissionItem(
                    Icons.cancel_outlined,
                    'Restricted',
                    Colors.red,
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildPermissionItem(IconData icon, String title, Color color) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 12),
      child: Row(
        children: [
          Icon(icon, color: color, size: 18),
          const SizedBox(width: 12),
          Text(
            title,
            style: const TextStyle(
              fontSize: 14,
              fontWeight: FontWeight.w600,
              color: Color(0xFF1E293B),
            ),
          ),
        ],
      ),
    );
  }
}

// 📍 Example 4: Custom Styling
class StylingExample extends StatelessWidget {
  const StylingExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Custom Styling'),
        backgroundColor: Colors.white,
      ),
      body: Stack(
        children: [
          FlutterMapSmart.simple(
            items: _generateDelhiPlaces(),
            latitude: (p) => p.lat,
            longitude: (p) => p.lng,
            markerImage: (p) => p.image,
            markerSize: 64,
            markerBorderColor: (p) => p.getTypeColor(),
            clusterColor: const Color(0xFF6366F1),
            radiusColor: const Color(0xFF6366F1).withValues(alpha: 0.1),
            showUserLocation: true,
            enableNearby: true,
            nearbyRadiusKm: 15,
          ),
          Positioned(
            top: 20,
            left: 20,
            right: 20,
            child: Container(
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(12),
                border: Border.all(color: const Color(0xFFE2E8F0)),
                boxShadow: [
                  BoxShadow(
                    color: Colors.black.withValues(alpha: 0.05),
                    blurRadius: 10,
                    offset: const Offset(0, 4),
                  ),
                ],
              ),
              child: const Row(
                children: [
                  IconBox(
                    icon: Icons.palette_rounded,
                    color: Color(0xFF8B5CF6),
                  ),
                  SizedBox(width: 12),
                  Expanded(
                    child: Text(
                      'Custom marker aesthetics and cluster themes',
                      style: TextStyle(
                        fontSize: 13,
                        fontWeight: FontWeight.w600,
                        color: Color(0xFF1E293B),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

// 📍 Example 5: Performance Test
class PerformanceExample extends StatelessWidget {
  const PerformanceExample({super.key});

  @override
  Widget build(BuildContext context) {
    final places = List.generate(1000, (i) {
      final lat = 28.5 + (i % 40) * 0.01;
      final lng = 77.1 + (i ~/ 40) * 0.01;
      return Place(
        name: 'Node $i',
        lat: lat,
        lng: lng,
        image: i % 5 == 0 ? 'https://picsum.photos/100/100?random=$i' : null,
        description: 'High-frequency data point $i',
        type: PlaceType.tech,
      );
    });

    return Scaffold(
      appBar: AppBar(
        title: const Text('Performance'),
        backgroundColor: Colors.white,
      ),
      body: Stack(
        children: [
          FlutterMapSmart.simple(
            items: places,
            latitude: (p) => p.lat,
            longitude: (p) => p.lng,
            markerImage: (p) => p.image,
            useClustering: true,
          ),
          Positioned(
            top: 20,
            left: 20,
            right: 20,
            child: Container(
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(12),
                border: Border.all(color: const Color(0xFFE2E8F0)),
                boxShadow: [
                  BoxShadow(
                    color: Colors.black.withValues(alpha: 0.05),
                    blurRadius: 10,
                    offset: const Offset(0, 4),
                  ),
                ],
              ),
              child: Row(
                children: [
                  const IconBox(
                    icon: Icons.speed_rounded,
                    color: Color(0xFFEF4444),
                  ),
                  const SizedBox(width: 12),
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          '${places.length} Items Rendered',
                          style: const TextStyle(
                            fontSize: 14,
                            fontWeight: FontWeight.w700,
                            color: Color(0xFF1E293B),
                          ),
                        ),
                        const Text(
                          'Optimized spatial clustering enabled',
                          style: TextStyle(
                            fontSize: 12,
                            color: Color(0xFF64748B),
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

// 📍 Example 6: Network Images
class NetworkImageExample extends StatelessWidget {
  const NetworkImageExample({super.key});

  @override
  Widget build(BuildContext context) {
    final places = [
      const Place(
        name: 'Network Image 01',
        lat: 28.6129,
        lng: 77.2295,
        image: 'https://picsum.photos/400/400?random=10',
        description: 'Remote asset resolution',
        type: PlaceType.tech,
      ),
      const Place(
        name: 'Network Image 02',
        lat: 28.6315,
        lng: 77.2167,
        image: 'https://picsum.photos/400/400?random=20',
        description: 'Distributed image caching',
        type: PlaceType.tech,
      ),
      const Place(
        name: 'Error Handling',
        lat: 28.6562,
        lng: 77.2410,
        image: 'https://invalid-url.com/404.jpg',
        description: 'Graceful fallback for missing assets',
        type: PlaceType.landmark,
      ),
    ];

    return Scaffold(
      appBar: AppBar(
        title: const Text('Network Assets'),
        backgroundColor: Colors.white,
      ),
      body: Stack(
        children: [
          FlutterMapSmart.simple(
            items: places,
            latitude: (p) => p.lat,
            longitude: (p) => p.lng,
            markerImage: (p) => p.image,
          ),
          Positioned(
            bottom: 24,
            left: 20,
            right: 20,
            child: Container(
              padding: const EdgeInsets.all(20),
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(16),
                border: Border.all(color: const Color(0xFFE2E8F0)),
                boxShadow: [
                  BoxShadow(
                    color: Colors.black.withValues(alpha: 0.05),
                    blurRadius: 10,
                    offset: const Offset(0, 4),
                  ),
                ],
              ),
              child: const Row(
                children: [
                  IconBox(
                    icon: Icons.cloud_done_rounded,
                    color: Color(0xFF06B6D4),
                  ),
                  SizedBox(width: 16),
                  Expanded(
                    child: Text(
                      'Network asset synchronization with error recovery',
                      style: TextStyle(
                        fontSize: 13,
                        fontWeight: FontWeight.w600,
                        color: Color(0xFF1E293B),
                        height: 1.4,
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

// Helper function
List<Place> _generateDelhiPlaces() {
  return const [
    Place(
      name: 'India Gate',
      lat: 28.6129,
      lng: 77.2295,
      image: 'assets/images/india_gate.png',
      description: 'War memorial',
      type: PlaceType.landmark,
    ),
    Place(
      name: 'Red Fort',
      lat: 28.6562,
      lng: 77.2410,
      image: 'assets/images/red_fort.png',
      description: 'Historic fort',
      type: PlaceType.landmark,
    ),
    Place(
      name: 'Qutub Minar',
      lat: 28.5245,
      lng: 77.1855,
      image: 'assets/images/qutub_minar.png',
      description: 'Ancient minaret',
      type: PlaceType.landmark,
    ),
    Place(
      name: 'Connaught Place',
      lat: 28.6315,
      lng: 77.2167,
      image: 'assets/images/connaught_place.png',
      description: 'Shopping district',
      type: PlaceType.landmark,
    ),
  ];
}
4
likes
160
points
103
downloads

Publisher

unverified uploader

Weekly Downloads

A plug-and-play OpenStreetMap widget with clustering, image markers, user location, and nearby radius support.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, flutter_map, flutter_map_marker_cluster, geolocator, http, latlong2

More

Packages that depend on flutter_map_smart