flutter_map_smart 1.0.0 copy "flutter_map_smart: ^1.0.0" to clipboard
flutter_map_smart: ^1.0.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 'dart:ui';
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 }

// ✨ Premium Design System
class PremiumDesign {
  static const primary = Color(0xFF6366F1); // Indigo
  static const secondary = Color(0xFFEC4899); // Pink
  static const background = Color(0xFF0F172A); // Slate 900
  static const surface = Color(0xFF1E293B); // Slate 800
  static const glassBase = Color(0x33FFFFFF);

  static final BoxShadow softShadow = BoxShadow(
    color: Colors.black.withValues(alpha: 0.1),
    blurRadius: 20,
    offset: const Offset(0, 10),
  );
}

class GlassContainer extends StatelessWidget {
  final Widget child;
  final double blur;
  final double opacity;
  final double borderRadius;
  final EdgeInsets? padding;

  const GlassContainer({
    super.key,
    required this.child,
    this.blur = 10,
    this.opacity = 0.1,
    this.borderRadius = 20,
    this.padding,
  });

  @override
  Widget build(BuildContext context) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(borderRadius),
      child: BackdropFilter(
        filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur),
        child: Container(
          padding: padding,
          decoration: BoxDecoration(
            color: Colors.white.withValues(alpha: opacity),
            borderRadius: BorderRadius.circular(borderRadius),
            border: Border.all(
              color: Colors.white.withValues(alpha: 0.2),
              width: 1.5,
            ),
          ),
          child: child,
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        brightness: Brightness.dark,
        fontFamily: 'Inter',
        colorScheme: ColorScheme.fromSeed(
          seedColor: PremiumDesign.primary,
          brightness: Brightness.dark,
        ),
        scaffoldBackgroundColor: PremiumDesign.background,
        appBarTheme: const AppBarTheme(
          backgroundColor: Colors.transparent,
          surfaceTintColor: Colors.transparent,
          elevation: 0,
          centerTitle: false,
          titleTextStyle: TextStyle(
            fontSize: 24,
            fontWeight: FontWeight.bold,
            letterSpacing: -0.5,
          ),
        ),
      ),
      home: const ExampleSelector(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBodyBehindAppBar: true,
      appBar: AppBar(title: const Text('Smart OSM Map')),
      body: Stack(
        children: [
          // Background Gradient
          Positioned.fill(
            child: Container(
              decoration: const BoxDecoration(
                gradient: LinearGradient(
                  begin: Alignment.topLeft,
                  end: Alignment.bottomRight,
                  colors: [
                    PremiumDesign.background,
                    Color(0xFF1E1B4B), // Deep Indigo
                    Color(0xFF312E81), // Indigo 900
                  ],
                ),
              ),
            ),
          ),

          // Decorative Blobs
          Positioned(
            top: -100,
            right: -100,
            child: Container(
              width: 300,
              height: 300,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                color: PremiumDesign.primary.withValues(alpha: 0.15),
              ),
            ),
          ),

          SafeArea(
            child: CustomScrollView(
              physics: const BouncingScrollPhysics(),
              slivers: [
                SliverPadding(
                  padding: const EdgeInsets.all(24),
                  sliver: SliverToBoxAdapter(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          'Explore Capabilities',
                          style: TextStyle(
                            fontSize: 14,
                            color: Colors.white.withValues(alpha: 0.6),
                            fontWeight: FontWeight.w500,
                            letterSpacing: 1.2,
                          ),
                        ),
                        const SizedBox(height: 8),
                        const Text(
                          'Interactive Map Showcase',
                          style: TextStyle(
                            fontSize: 28,
                            fontWeight: FontWeight.bold,
                            color: Colors.white,
                            letterSpacing: -0.5,
                          ),
                        ),
                      ],
                    ),
                  ),
                ),

                const SliverPadding(
                  padding: EdgeInsets.symmetric(horizontal: 24),
                  sliver: SliverToBoxAdapter(
                    child: _SectionHeader(title: 'Core Features'),
                  ),
                ),

                SliverPadding(
                  padding: const EdgeInsets.all(24),
                  sliver: SliverGrid(
                    gridDelegate:
                        const SliverGridDelegateWithFixedCrossAxisCount(
                          crossAxisCount: 2,
                          mainAxisSpacing: 16,
                          crossAxisSpacing: 16,
                          childAspectRatio: 0.85,
                        ),
                    delegate: SliverChildListDelegate([
                      _ExampleCard(
                        title: 'Basic Usage',
                        subtitle: 'Markers & Clusters',
                        icon: Icons.map_rounded,
                        color: const Color(0xFF6366F1),
                        example: const BasicExample(),
                      ),
                      _ExampleCard(
                        title: 'Live Tracking',
                        subtitle: 'Real-time Location',
                        icon: Icons.my_location_rounded,
                        color: const Color(0xFF10B981),
                        example: const LocationExample(),
                      ),
                      _ExampleCard(
                        title: 'Security',
                        subtitle: 'Permission Flow',
                        icon: Icons.shield_rounded,
                        color: const Color(0xFFF59E0B),
                        example: const PermissionExample(),
                      ),
                    ]),
                  ),
                ),

                const SliverPadding(
                  padding: EdgeInsets.symmetric(horizontal: 24),
                  sliver: SliverToBoxAdapter(
                    child: _SectionHeader(title: 'Advanced & Optimization'),
                  ),
                ),

                SliverPadding(
                  padding: const EdgeInsets.all(24),
                  sliver: SliverList(
                    delegate: SliverChildListDelegate([
                      _ExampleTile(
                        title: 'Custom Styling',
                        subtitle: 'Bespoke markers and cluster themes',
                        icon: Icons.palette_rounded,
                        example: const StylingExample(),
                      ),
                      const SizedBox(height: 12),
                      _ExampleTile(
                        title: 'Extreme Performance',
                        subtitle: 'Stress testing with 500+ markers',
                        icon: Icons.bolt_rounded,
                        example: const PerformanceExample(),
                      ),
                      const SizedBox(height: 12),
                      _ExampleTile(
                        title: 'Image Processing',
                        subtitle: 'Network asset & error fallback',
                        icon: Icons.cloud_done_rounded,
                        example: const NetworkImageExample(),
                      ),
                    ]),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// πŸ“¦ Reusable Premium Place Card
class PlaceDetailCard extends StatelessWidget {
  final Place place;
  final VoidCallback? onClose;

  const PlaceDetailCard({super.key, required this.place, this.onClose});

  @override
  Widget build(BuildContext context) {
    return GlassContainer(
      opacity: 0.1,
      borderRadius: 24,
      padding: const EdgeInsets.all(20),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              if (place.image != null)
                ClipRRect(
                  borderRadius: BorderRadius.circular(16),
                  child: Image.network(
                    place.image!,
                    width: 80,
                    height: 80,
                    fit: BoxFit.cover,
                    errorBuilder: (_, __, ___) => Container(
                      width: 80,
                      height: 80,
                      color: Colors.white.withValues(alpha: 0.05),
                      child: const Icon(
                        Icons.image_not_supported_rounded,
                        color: Colors.white54,
                      ),
                    ),
                  ),
                )
              else
                Container(
                  width: 80,
                  height: 80,
                  decoration: BoxDecoration(
                    color: place.getTypeColor().withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(16),
                  ),
                  child: Icon(
                    Icons.place_rounded,
                    color: place.getTypeColor(),
                    size: 32,
                  ),
                ),
              const SizedBox(width: 16),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      place.name,
                      style: const TextStyle(
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                        color: Colors.white,
                      ),
                    ),
                    const SizedBox(height: 4),
                    Container(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 8,
                        vertical: 4,
                      ),
                      decoration: BoxDecoration(
                        color: place.getTypeColor().withValues(alpha: 0.2),
                        borderRadius: BorderRadius.circular(6),
                      ),
                      child: Text(
                        place.type.name.toUpperCase(),
                        style: TextStyle(
                          fontSize: 10,
                          fontWeight: FontWeight.bold,
                          color: place.getTypeColor(),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
              if (onClose != null)
                IconButton(
                  onPressed: onClose,
                  icon: const Icon(Icons.close_rounded, color: Colors.white54),
                ),
            ],
          ),
          const SizedBox(height: 16),
          Text(
            place.description,
            style: TextStyle(
              fontSize: 14,
              color: Colors.white.withValues(alpha: 0.7),
              height: 1.5,
            ),
          ),
          const SizedBox(height: 20),
          Row(
            children: [
              Expanded(
                child: ElevatedButton.icon(
                  onPressed: () {},
                  style: ElevatedButton.styleFrom(
                    backgroundColor: PremiumDesign.primary,
                    foregroundColor: Colors.white,
                    padding: const EdgeInsets.symmetric(vertical: 12),
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(12),
                    ),
                  ),
                  icon: const Icon(Icons.directions_rounded),
                  label: const Text('Directions'),
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: OutlinedButton.icon(
                  onPressed: () {},
                  style: OutlinedButton.styleFrom(
                    foregroundColor: Colors.white,
                    side: BorderSide(
                      color: Colors.white.withValues(alpha: 0.2),
                    ),
                    padding: const EdgeInsets.symmetric(vertical: 12),
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(12),
                    ),
                  ),
                  icon: const Icon(Icons.share_rounded),
                  label: const Text('Share'),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class _SectionHeader extends StatelessWidget {
  final String title;
  const _SectionHeader({required this.title});

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(top: 16, bottom: 8),
      child: Row(
        children: [
          Container(
            width: 4,
            height: 16,
            decoration: BoxDecoration(
              color: PremiumDesign.primary,
              borderRadius: BorderRadius.circular(2),
            ),
          ),
          const SizedBox(width: 8),
          Text(
            title.toUpperCase(),
            style: TextStyle(
              fontSize: 12,
              fontWeight: FontWeight.bold,
              color: Colors.white.withValues(alpha: 0.4),
              letterSpacing: 1.5,
            ),
          ),
        ],
      ),
    );
  }
}

class _ExampleCard extends StatelessWidget {
  final String title;
  final String subtitle;
  final IconData icon;
  final Color color;
  final Widget example;

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

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: () => Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => example),
      ),
      borderRadius: BorderRadius.circular(24),
      child: GlassContainer(
        opacity: 0.05,
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Container(
              padding: const EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: color.withValues(alpha: 0.2),
                borderRadius: BorderRadius.circular(16),
              ),
              child: Icon(icon, color: color, size: 28),
            ),
            const Spacer(),
            Text(
              title,
              style: const TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
                color: Colors.white,
              ),
            ),
            const SizedBox(height: 4),
            Text(
              subtitle,
              style: TextStyle(
                fontSize: 13,
                color: Colors.white.withValues(alpha: 0.5),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

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 InkWell(
      onTap: () => Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => example),
      ),
      borderRadius: BorderRadius.circular(20),
      child: GlassContainer(
        opacity: 0.05,
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
        child: Row(
          children: [
            Container(
              padding: const EdgeInsets.all(10),
              decoration: BoxDecoration(
                color: Colors.white.withValues(alpha: 0.05),
                borderRadius: BorderRadius.circular(12),
              ),
              child: Icon(icon, color: Colors.white, size: 24),
            ),
            const SizedBox(width: 16),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    title,
                    style: const TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.w600,
                      color: Colors.white,
                    ),
                  ),
                  Text(
                    subtitle,
                    style: TextStyle(
                      fontSize: 13,
                      color: Colors.white.withValues(alpha: 0.5),
                    ),
                  ),
                ],
              ),
            ),
            Icon(
              Icons.chevron_right_rounded,
              color: Colors.white.withValues(alpha: 0.3),
            ),
          ],
        ),
      ),
    );
  }
}

// πŸ“ 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 matches = [
    const Place(
      name: 'India Gate',
      lat: 28.6129,
      lng: 77.2295,
      image:
          'https://images.unsplash.com/photo-1587474260584-136574528ed5?auto=format&fit=crop&w=400&q=80',
      description:
          'The India Gate is a war memorial located astride the Rajpath, on the eastern edge of the "ceremonial axis" of New Delhi.',
      type: PlaceType.landmark,
    ),
    const Place(
      name: 'Red Fort',
      lat: 28.6562,
      lng: 77.2410,
      image:
          'https://images.unsplash.com/photo-1585123334904-845d60e97b29?auto=format&fit=crop&w=400&q=80',
      description:
          'The Red Fort is a historic fort in the city of Delhi in India that served as the main residence of the Mughal Emperors.',
      type: PlaceType.landmark,
    ),
    const Place(
      name: 'Connaught Place',
      lat: 28.6315,
      lng: 77.2167,
      image: null,
      description:
          'Connaught Place is one of the main financial, commercial and business centres in New Delhi, India.',
      type: PlaceType.restaurant,
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBodyBehindAppBar: true,
      appBar: AppBar(
        title: const Text('Basic Usage'),
        leading: IconButton(
          icon: const Icon(Icons.arrow_back_ios_new_rounded),
          onPressed: () => Navigator.pop(context),
        ),
      ),
      body: Stack(
        children: [
          FlutterMapSmart.simple(
            items: matches,
            latitude: (p) => p.lat,
            longitude: (p) => p.lng,
            markerImage: (p) => p.image,
            onTap: (p) {
              setState(() => _selectedPlace = p);
            },
          ),

          // Top Search Bar Mockup
          Positioned(
            top: MediaQuery.of(context).padding.top + 70,
            left: 20,
            right: 20,
            child: GlassContainer(
              opacity: 0.1,
              borderRadius: 30,
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
              child: Row(
                children: [
                  const Icon(
                    Icons.search_rounded,
                    color: Colors.white54,
                    size: 20,
                  ),
                  const SizedBox(width: 12),
                  const Expanded(
                    child: Text(
                      'Search places...',
                      style: TextStyle(color: Colors.white38, fontSize: 14),
                    ),
                  ),
                  Container(
                    padding: const EdgeInsets.all(6),
                    decoration: BoxDecoration(
                      color: PremiumDesign.primary.withValues(alpha: 0.2),
                      shape: BoxShape.circle,
                    ),
                    child: const Icon(
                      Icons.filter_list_rounded,
                      color: Colors.white,
                      size: 16,
                    ),
                  ),
                ],
              ),
            ),
          ),

          // Animated Place Card
          if (_selectedPlace != null)
            Positioned(
              left: 20,
              right: 20,
              bottom: 40,
              child: TweenAnimationBuilder<double>(
                duration: const Duration(milliseconds: 300),
                tween: Tween(begin: 0.0, end: 1.0),
                curve: Curves.easeOutBack,
                builder: (context, value, child) {
                  return Transform.translate(
                    offset: Offset(0, 50 * (1 - value)),
                    child: Opacity(
                      opacity: value,
                      child: PlaceDetailCard(
                        place: _selectedPlace!,
                        onClose: () => setState(() => _selectedPlace = null),
                      ),
                    ),
                  );
                },
              ),
            ),

          // Floating Info Button
          Positioned(
            top: MediaQuery.of(context).padding.top + 10,
            right: 20,
            child: GlassContainer(
              opacity: 0.1,
              borderRadius: 30,
              padding: EdgeInsets.zero,
              child: IconButton(
                icon: const Icon(
                  Icons.help_outline_rounded,
                  color: Colors.white,
                ),
                onPressed: () => _showTopSheet(context),
              ),
            ),
          ),
        ],
      ),
    );
  }

  void _showTopSheet(BuildContext context) {
    showGeneralDialog(
      context: context,
      barrierDismissible: true,
      barrierLabel: 'Info',
      pageBuilder: (context, anim1, anim2) => Align(
        alignment: Alignment.topCenter,
        child: Container(
          margin: const EdgeInsets.all(24),
          child: Material(
            color: Colors.transparent,
            child: GlassContainer(
              blur: 20,
              opacity: 0.2,
              padding: const EdgeInsets.all(24),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  const Text(
                    'Basic Implementation',
                    style: TextStyle(
                      fontSize: 20,
                      fontWeight: FontWeight.bold,
                      color: Colors.white,
                    ),
                  ),
                  const SizedBox(height: 8),
                  const Text(
                    'Demonstrating markers, clustering, and bespoke tap interactions.',
                    textAlign: TextAlign.center,
                    style: TextStyle(color: Colors.white70),
                  ),
                  const SizedBox(height: 16),
                  ElevatedButton(
                    onPressed: () => Navigator.pop(context),
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Colors.white,
                      foregroundColor: PremiumDesign.background,
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(12),
                      ),
                    ),
                    child: const Text('Continue'),
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

// πŸ“ 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;
  Place? _selectedPlace;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBodyBehindAppBar: true,
      appBar: AppBar(
        title: const Text('Location & Nearby'),
        leading: IconButton(
          icon: const Icon(Icons.arrow_back_ios_new_rounded),
          onPressed: () => Navigator.pop(context),
        ),
      ),
      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: PremiumDesign.primary.withValues(alpha: 0.1),
            onTap: (p) => setState(() => _selectedPlace = p),
          ),

          Positioned(
            top: MediaQuery.of(context).padding.top + 70,
            left: 20,
            right: 20,
            child: GlassContainer(
              opacity: 0.1,
              padding: const EdgeInsets.all(16),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  _buildToggle(
                    icon: Icons.location_on_rounded,
                    title: 'Live Location',
                    value: showLocation,
                    onChanged: (v) => setState(() => showLocation = v),
                  ),
                  _buildToggle(
                    icon: Icons.radar_rounded,
                    title: 'Nearby Search',
                    value: enableNearby,
                    enabled: showLocation,
                    onChanged: (v) => setState(() => enableNearby = v),
                  ),
                  if (enableNearby) ...[
                    const Padding(
                      padding: EdgeInsets.symmetric(vertical: 12),
                      child: Divider(color: Colors.white12, height: 1),
                    ),
                    _buildRadiusSlider(),
                  ],
                ],
              ),
            ),
          ),

          if (_selectedPlace != null)
            Positioned(
              left: 20,
              right: 20,
              bottom: 40,
              child: TweenAnimationBuilder<double>(
                duration: const Duration(milliseconds: 300),
                tween: Tween(begin: 0.0, end: 1.0),
                curve: Curves.easeOutBack,
                builder: (context, value, child) {
                  return Transform.translate(
                    offset: Offset(0, 50 * (1 - value)),
                    child: Opacity(
                      opacity: value,
                      child: PlaceDetailCard(
                        place: _selectedPlace!,
                        onClose: () => setState(() => _selectedPlace = null),
                      ),
                    ),
                  );
                },
              ),
            ),
        ],
      ),
    );
  }

  Widget _buildToggle({
    required IconData icon,
    required String title,
    required bool value,
    required Function(bool) onChanged,
    bool enabled = true,
  }) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4),
      child: Opacity(
        opacity: enabled ? 1.0 : 0.4,
        child: Row(
          children: [
            Container(
              padding: const EdgeInsets.all(8),
              decoration: BoxDecoration(
                color: Colors.white.withValues(alpha: 0.05),
                borderRadius: BorderRadius.circular(10),
              ),
              child: Icon(icon, size: 20, color: Colors.white),
            ),
            const SizedBox(width: 12),
            Expanded(
              child: Text(
                title,
                style: const TextStyle(
                  fontSize: 15,
                  fontWeight: FontWeight.w500,
                  color: Colors.white,
                ),
              ),
            ),
            Switch(
              value: value,
              onChanged: enabled ? onChanged : null,
              activeTrackColor: PremiumDesign.primary,
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildRadiusSlider() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            const Text(
              'Search Radius',
              style: TextStyle(fontSize: 13, color: Colors.white70),
            ),
            Text(
              '${radiusKm.toInt()} km',
              style: const TextStyle(
                fontSize: 13,
                fontWeight: FontWeight.bold,
                color: PremiumDesign.primary,
              ),
            ),
          ],
        ),
        SliderTheme(
          data: SliderTheme.of(context).copyWith(
            trackHeight: 4,
            thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 8),
            overlayShape: const RoundSliderOverlayShape(overlayRadius: 16),
          ),
          child: Slider(
            value: radiusKm,
            min: 1,
            max: 50,
            onChanged: (v) => setState(() => radiusKm = v),
          ),
        ),
      ],
    );
  }
}

// πŸ“ 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(
      extendBodyBehindAppBar: true,
      appBar: AppBar(
        title: const Text('Permission Handling'),
        leading: IconButton(
          icon: const Icon(Icons.arrow_back_ios_new_rounded),
          onPressed: () => Navigator.pop(context),
        ),
      ),
      body: Stack(
        children: [
          FlutterMapSmart.simple(
            items: _generateDelhiPlaces(),
            latitude: (p) => p.lat,
            longitude: (p) => p.lng,
            markerImage: (p) => p.image,
            showUserLocation: true,
            onLocationPermissionGranted: () {
              setState(() => permissionStatus = 'Permission Granted');
            },
            onLocationPermissionDenied: () {
              setState(() => permissionStatus = 'Permission Denied');
            },
            onLocationPermissionDeniedForever: () {
              setState(() => permissionStatus = 'Permission Denied Forever');
            },
            onLocationServiceDisabled: () {
              setState(() => permissionStatus = 'Location Service Disabled');
            },
          ),

          if (permissionStatus != null)
            Positioned(
              top: MediaQuery.of(context).padding.top + 70,
              left: 20,
              right: 20,
              child: TweenAnimationBuilder<double>(
                duration: const Duration(milliseconds: 400),
                tween: Tween(begin: 0.0, end: 1.0),
                curve: Curves.easeOutBack,
                builder: (context, value, child) {
                  return Transform.translate(
                    offset: Offset(0, -20 * (1 - value)),
                    child: Opacity(
                      opacity: value,
                      child: GlassContainer(
                        blur: 20,
                        opacity: 0.2,
                        padding: const EdgeInsets.symmetric(
                          horizontal: 16,
                          vertical: 12,
                        ),
                        child: Row(
                          children: [
                            const Icon(
                              Icons.info_outline_rounded,
                              color: Colors.white70,
                              size: 20,
                            ),
                            const SizedBox(width: 12),
                            Expanded(
                              child: Text(
                                permissionStatus!,
                                style: const TextStyle(
                                  color: Colors.white,
                                  fontWeight: FontWeight.w500,
                                ),
                              ),
                            ),
                            IconButton(
                              icon: const Icon(
                                Icons.close_rounded,
                                color: Colors.white38,
                                size: 18,
                              ),
                              onPressed: () =>
                                  setState(() => permissionStatus = null),
                            ),
                          ],
                        ),
                      ),
                    ),
                  );
                },
              ),
            ),

          Positioned(
            bottom: 40,
            left: 20,
            right: 20,
            child: GlassContainer(
              opacity: 0.1,
              padding: const EdgeInsets.all(24),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Permission States',
                    style: TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                      color: Colors.white,
                    ),
                  ),
                  const SizedBox(height: 16),
                  _buildPermissionItem(
                    Icons.check_circle_outline_rounded,
                    'Granted',
                    'Access to device location allowed',
                  ),
                  _buildPermissionItem(
                    Icons.error_outline_rounded,
                    'Denied',
                    'Access was refused by the user',
                  ),
                  _buildPermissionItem(
                    Icons.block_rounded,
                    'Denied Forever',
                    'Permanently blocked in settings',
                  ),
                  _buildPermissionItem(
                    Icons.location_off_rounded,
                    'Disabled',
                    'GPS is turned off on the device',
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildPermissionItem(IconData icon, String title, String description) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 16),
      child: Row(
        children: [
          Icon(icon, color: Colors.white38, size: 20),
          const SizedBox(width: 16),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  title,
                  style: const TextStyle(
                    fontWeight: FontWeight.bold,
                    color: Colors.white,
                    fontSize: 14,
                  ),
                ),
                Text(
                  description,
                  style: const TextStyle(color: Colors.white54, fontSize: 12),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// πŸ“ Example 4: Custom Styling
class StylingExample extends StatefulWidget {
  const StylingExample({super.key});

  @override
  State<StylingExample> createState() => _StylingExampleState();
}

class _StylingExampleState extends State<StylingExample> {
  Place? _selectedPlace;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBodyBehindAppBar: true,
      appBar: AppBar(
        title: const Text('Custom Styling'),
        leading: IconButton(
          icon: const Icon(Icons.arrow_back_ios_new_rounded),
          onPressed: () => Navigator.pop(context),
        ),
      ),
      body: Stack(
        children: [
          FlutterMapSmart.simple(
            items: _generateDelhiPlaces(),
            latitude: (p) => p.lat,
            longitude: (p) => p.lng,
            markerImage: (p) => p.image,
            markerSize: 56,
            markerBorderColor: PremiumDesign.secondary,
            clusterColor: PremiumDesign.secondary,
            radiusColor: PremiumDesign.secondary.withValues(alpha: 0.1),
            showUserLocation: true,
            enableNearby: true,
            nearbyRadiusKm: 15,
            onTap: (p) => setState(() => _selectedPlace = p),
          ),

          Positioned(
            top: MediaQuery.of(context).padding.top + 70,
            left: 20,
            right: 20,
            child: GlassContainer(
              opacity: 0.1,
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
              child: Row(
                children: [
                  const Icon(
                    Icons.palette_rounded,
                    color: PremiumDesign.secondary,
                    size: 20,
                  ),
                  const SizedBox(width: 12),
                  const Expanded(
                    child: Text(
                      'Bespoke marker themes & cluster styles',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 13,
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),

          if (_selectedPlace != null)
            Positioned(
              left: 20,
              right: 20,
              bottom: 40,
              child: PlaceDetailCard(
                place: _selectedPlace!,
                onClose: () => setState(() => _selectedPlace = null),
              ),
            ),
        ],
      ),
    );
  }
}

// πŸ“ Example 5: Performance Test
class PerformanceExample extends StatefulWidget {
  const PerformanceExample({super.key});

  @override
  State<PerformanceExample> createState() => _PerformanceExampleState();
}

class _PerformanceExampleState extends State<PerformanceExample> {
  final places = List.generate(500, (i) {
    final lat = 28.5 + (i % 20) * 0.01;
    final lng = 77.1 + (i ~/ 20) * 0.01;
    return Place(
      name: 'Place $i',
      lat: lat,
      lng: lng,
      image: i % 10 == 0 ? 'https://picsum.photos/seed/${i + 100}/200' : null,
      description: 'Stress testing marker rendering performance with $i items.',
      type: PlaceType.tech,
    );
  });

  Place? _selectedPlace;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBodyBehindAppBar: true,
      appBar: AppBar(
        title: const Text('Performance'),
        leading: IconButton(
          icon: const Icon(Icons.arrow_back_ios_new_rounded),
          onPressed: () => Navigator.pop(context),
        ),
      ),
      body: Stack(
        children: [
          FlutterMapSmart.simple(
            items: places,
            latitude: (p) => p.lat,
            longitude: (p) => p.lng,
            markerImage: (p) => p.image,
            useClustering: true,
            onTap: (p) => setState(() => _selectedPlace = p),
          ),

          Positioned(
            top: MediaQuery.of(context).padding.top + 70,
            left: 20,
            right: 20,
            child: GlassContainer(
              opacity: 0.1,
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
              child: Row(
                children: [
                  const Icon(Icons.bolt_rounded, color: Colors.amber, size: 20),
                  const SizedBox(width: 12),
                  const Text(
                    'STRESS TEST: ',
                    style: TextStyle(
                      color: Colors.amber,
                      fontWeight: FontWeight.bold,
                      fontSize: 13,
                    ),
                  ),
                  Text(
                    '${places.length} Active Markers',
                    style: const TextStyle(
                      color: Colors.white,
                      fontSize: 13,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                ],
              ),
            ),
          ),

          if (_selectedPlace != null)
            Positioned(
              left: 20,
              right: 20,
              bottom: 40,
              child: PlaceDetailCard(
                place: _selectedPlace!,
                onClose: () => setState(() => _selectedPlace = null),
              ),
            ),
        ],
      ),
    );
  }
}

// πŸ“ Example 6: Network Images
class NetworkImageExample extends StatefulWidget {
  const NetworkImageExample({super.key});

  @override
  State<NetworkImageExample> createState() => _NetworkImageExampleState();
}

class _NetworkImageExampleState extends State<NetworkImageExample> {
  final places = [
    const Place(
      name: 'High Definition',
      lat: 28.6129,
      lng: 77.2295,
      image:
          'https://images.unsplash.com/photo-1548013146-72479768bada?auto=format&fit=crop&w=400&q=80',
      description:
          'Demonstrating seamless network image loading with cached placeholders.',
      type: PlaceType.landmark,
    ),
    const Place(
      name: 'Modern Architecture',
      lat: 28.6315,
      lng: 77.2167,
      image:
          'https://images.unsplash.com/photo-1512436991641-6745cdb1723f?auto=format&fit=crop&w=400&q=80',
      description: 'Another high-fidelity remote asset loading test.',
      type: PlaceType.landmark,
    ),
    const Place(
      name: 'Resilient Loading',
      lat: 28.6562,
      lng: 77.2410,
      image: 'https://broken-image-link.com/404.jpg',
      description:
          'Tests automatic fallback to type-specific icons when images fail.',
      type: PlaceType.landmark,
    ),
  ];

  Place? _selectedPlace;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBodyBehindAppBar: true,
      appBar: AppBar(
        title: const Text('Network Assets'),
        leading: IconButton(
          icon: const Icon(Icons.arrow_back_ios_new_rounded),
          onPressed: () => Navigator.pop(context),
        ),
      ),
      body: Stack(
        children: [
          FlutterMapSmart.simple(
            items: places,
            latitude: (p) => p.lat,
            longitude: (p) => p.lng,
            markerImage: (p) => p.image,
            onTap: (p) => setState(() => _selectedPlace = p),
          ),

          Positioned(
            top: MediaQuery.of(context).padding.top + 70,
            left: 20,
            right: 20,
            child: GlassContainer(
              opacity: 0.1,
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
              child: Row(
                children: [
                  const Icon(
                    Icons.cloud_sync_rounded,
                    color: Colors.blueAccent,
                    size: 20,
                  ),
                  const SizedBox(width: 12),
                  const Expanded(
                    child: Text(
                      'Real-time image processing & error resilience',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 13,
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),

          if (_selectedPlace != null)
            Positioned(
              left: 20,
              right: 20,
              bottom: 40,
              child: PlaceDetailCard(
                place: _selectedPlace!,
                onClose: () => setState(() => _selectedPlace = null),
              ),
            ),
        ],
      ),
    );
  }
}

// Helper function
List<Place> _generateDelhiPlaces() {
  return const [
    Place(
      name: 'India Gate',
      lat: 28.6129,
      lng: 77.2295,
      image:
          'https://images.unsplash.com/photo-1587474260584-136574528ed5?auto=format&fit=crop&w=400&q=80',
      description:
          'The India Gate is a war memorial located astride the Rajpath, on the eastern edge of the "ceremonial axis" of New Delhi.',
      type: PlaceType.landmark,
    ),
    Place(
      name: 'Red Fort',
      lat: 28.6562,
      lng: 77.2410,
      image:
          'https://images.unsplash.com/photo-1585123334904-845d60e97b29?auto=format&fit=crop&w=400&q=80',
      description:
          'The Red Fort is a historic fort in the city of Delhi in India that served as the main residence of the Mughal Emperors.',
      type: PlaceType.landmark,
    ),
    Place(
      name: 'Qutub Minar',
      lat: 28.5245,
      lng: 77.1855,
      image:
          'https://images.unsplash.com/photo-1524492412937-b28074a5d7da?auto=format&fit=crop&w=400&q=80',
      description:
          'The Qutub Minar, also spelled as Qutab Minar, is a minaret that forms part of the Qutb complex, a UNESCO World Heritage Site.',
      type: PlaceType.landmark,
    ),
    Place(
      name: 'Lotus Temple',
      lat: 28.5535,
      lng: 77.2588,
      image:
          'https://images.unsplash.com/photo-1564507592333-c60657ece523?auto=format&fit=crop&w=400&q=80',
      description:
          'The Lotus Temple, located in Delhi, India, is a BahΓ‘ΚΌΓ­ House of Worship that was dedicated in December 1986.',
      type: PlaceType.landmark,
    ),
  ];
}
4
likes
160
points
121
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, latlong2

More

Packages that depend on flutter_map_smart