nepal_map 0.1.0 copy "nepal_map: ^0.1.0" to clipboard
nepal_map: ^0.1.0 copied to clipboard

A fully interactive, customizable Flutter widget for displaying and interacting with the map of Nepal, with district- and province-level selection, hover, styling, markers, and heatmap support.

example/lib/main.dart

import 'dart:math';

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

void main() => runApp(const NepalMapExampleApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Nepal Map — Full Demo',
      theme: ThemeData(colorSchemeSeed: Colors.blue, useMaterial3: true),
      home: const FullDemoPage(),
    );
  }
}

class FullDemoPage extends StatefulWidget {
  const FullDemoPage({super.key});

  @override
  State<FullDemoPage> createState() => _FullDemoPageState();
}

class _FullDemoPageState extends State<FullDemoPage> {
  late final NepalMapController _controller;
  String _lastEvent = 'Tap a district to get started';
  String? _hovered;

  // Feature demo state
  bool _showLayers = false;
  bool _showHeatmap = false;
  bool _showMarkers = false;

  // Theme demo state — cycles through theme presets
  int _themeIndex = 0;
  final List<String> _themeLabels = [
    'Default',
    'Province Rainbow',
    'Dark Mode',
    'Custom Colors',
  ];

  @override
  void initState() {
    super.initState();
    _controller = NepalMapController(mode: NepalSelectionMode.singleDistrict);
    _controller.addListener(_rebuild);
  }

  void _rebuild() => setState(() {});

  @override
  void dispose() {
    _controller.removeListener(_rebuild);
    _controller.dispose();
    super.dispose();
  }

  void _setMode(NepalSelectionMode mode) {
    setState(() {
      _controller.setMode(mode);
      _lastEvent = 'Switched to ${_labelFor(mode)}';
    });
  }

  String _labelFor(NepalSelectionMode mode) {
    switch (mode) {
      case NepalSelectionMode.singleDistrict:
        return 'Single district';
      case NepalSelectionMode.multiDistrict:
        return 'Multi district';
      case NepalSelectionMode.singleProvince:
        return 'Single province';
      case NepalSelectionMode.multiProvince:
        return 'Multi province';
      case NepalSelectionMode.none:
        return 'None';
    }
  }

  // --- Coverage layer demo ---
  void _toggleCoverageLayers() {
    setState(() {
      _showLayers = !_showLayers;
      if (_showLayers) {
        // Bank branches layer (blue) — central/eastern districts
        _controller.addCoverageLayer(const CoverageLayer(
          id: 'bank_branches',
          name: 'Bank Branches',
          districtIds: [
            'kathmandu', 'lalitpur', 'bhaktapur', 'chitwan',
            'biratnagar', 'janakpur', 'birgunj', 'dharan',
          ],
          color: Color(0xFF2196F3),
          opacity: 0.4,
        ));

        // Delivery zones layer (orange) — valley + nearby
        _controller.addCoverageLayer(const CoverageLayer(
          id: 'delivery_zones',
          name: 'Delivery Zones',
          districtIds: [
            'kathmandu', 'lalitpur', 'bhaktapur', 'dhading',
            'nuwakot', 'sindhuli', 'rasuwa',
          ],
          color: Colors.orange,
          opacity: 0.35,
        ));
      } else {
        _controller.clearCoverageLayers();
      }
    });
  }

  // --- Heatmap demo ---
  void _toggleHeatmap() {
    setState(() {
      _showHeatmap = !_showHeatmap;
      if (_showHeatmap) {
        // Simulated population data (in thousands)
        final data = {
          'kathmandu': 177.0,
          'lalitpur': 29.0,
          'bhaktapur': 30.0,
          'biratnagar': 15.0,
          'janakpur': 12.0,
          'pokhara': 10.0,
          'birgunj': 10.0,
          'dharan': 8.0,
          'butwal': 9.0,
          'bhairahawa': 11.0,
          'chitwan': 13.0,
          'jhapa': 7.0,
          'morang': 8.0,
          'sunsari': 6.0,
          'kapilvastu': 5.0,
          'parasi': 7.0,
          'rukum_west': 2.0,
          'humla': 1.0,
          'mugu': 1.0,
          'dolpa': 1.0,
          'mustang': 2.0,
          'myagdi': 3.0,
          'gorkha': 5.0,
          'sindhupalchok': 4.0,
          'nuwakot': 3.0,
          'dhading': 4.0,
          'kavrepalanchok': 6.0,
          'sindhuli': 3.0,
          'rasuwa': 1.0,
        };

        _controller.setHeatmap(HeatmapConfig(
          dataMap: data,
          label: 'Population (thousands)',
          minValue: 0,
          maxValue: 200,
          colors: [
            const Color(0xFFE8F5E9), // light green
            const Color(0xFF66BB6A), // green
            const Color(0xFFFFA726), // orange
            const Color(0xFFE53935), // red
          ],
        ));
      } else {
        _controller.clearHeatmap();
      }
    });
  }

  // --- Markers demo ---
  void _toggleMarkers() {
    setState(() {
      _showMarkers = !_showMarkers;
      if (_showMarkers) {
        _controller.addMarkers([
          // City markers anchored to district centroids
          const MapMarker(
            id: 'kathmandu_city',
            districtId: 'kathmandu',
            size: 28,
            child: _CityMarker(label: 'KTM', color: Colors.red),
          ),
          const MapMarker(
            id: 'pokhara_city',
            districtId: 'kaski',
            size: 24,
            child: _CityMarker(label: 'PKR', color: Colors.purple),
          ),
          const MapMarker(
            id: 'biratnagar_city',
            districtId: 'morang',
            size: 22,
            child: _CityMarker(label: 'BIR', color: Colors.teal),
          ),
          const MapMarker(
            id: 'janakpur_city',
            districtId: 'dhanusha',
            size: 22,
            child: _CityMarker(label: 'JNP', color: Colors.amber),
          ),
          const MapMarker(
            id: 'butwal_city',
            districtId: 'rupandehi',
            size: 22,
            child: _CityMarker(label: 'BTW', color: Colors.indigo),
          ),
        ]);
      } else {
        _controller.clearMarkers();
      }
    });
  }

  // --- Theme demo ---
  void _cycleTheme() {
    setState(() {
      _themeIndex = (_themeIndex + 1) % _themeLabels.length;
    });
  }

  NepalMapTheme? _getTheme() {
    switch (_themeIndex) {
      case 0:
        // Default — no custom theme
        return null;
      case 1:
        // Province rainbow colors with styled tooltip
        return NepalMapTheme(
          colors: NepalMapColors(
            provinceColors: NepalMapThemePresets.provinceRainbow,
            selectedColor: Colors.blue[800]!,
            hoverColor: Colors.blue[300]!,
            borderColor: Colors.white70,
            borderWidth: 1.5,
          ),
          tooltipStyle: TooltipStyle(
            backgroundColor: Colors.black87,
            borderRadius: 8,
            elevation: 4,
            padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
          ),
          markerStyle: const MarkerStyle(
            color: Colors.deepOrange,
            icon: Icons.place,
          ),
        );
      case 2:
        // Dark mode
        return NepalMapTheme(
          colors: NepalMapThemePresets.dark.copyWith(
            borderColor: const Color(0xFF757575),
            borderWidth: 1.2,
          ),
          tooltipStyle: TooltipStyle(
            backgroundColor: const Color(0xFF424242),
            textColor: Colors.white,
            borderRadius: 4,
            elevation: 6,
          ),
        );
      case 3:
        // Custom per-district colors + custom tooltip
        return NepalMapTheme(
          colors: NepalMapColors(
            baseColor: const Color(0xFFF3E5F5),
            selectedColor: Colors.purple[700]!,
            hoverColor: Colors.purple[200]!,
            borderColor: Colors.purple[300]!,
            borderWidth: 1.5,
            districtColors: {
              'kathmandu': Colors.amber[300]!,
              'lalitpur': Colors.amber[200]!,
              'bhaktapur': Colors.amber[100]!,
              'kaski': Colors.teal[200]!,
              'morang': Colors.green[200]!,
              'dhanusha': Colors.orange[200]!,
              'chitwan': Colors.lightGreen[200]!,
            },
          ),
          tooltipStyle: TooltipStyle(
            backgroundColor: Colors.purple[900]!,
            textColor: Colors.white,
            textStyle: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
            padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
            borderRadius: 12,
            offset: const Offset(16, -40),
            elevation: 8,
          ),
          markerStyle: const MarkerStyle(
            color: Colors.purple,
            icon: Icons.location_on,
          ),
        );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Nepal Map — Full Demo'),
        actions: [
          // Feature toggle buttons
          _featureChip('Layers', _showLayers, _toggleCoverageLayers),
          _featureChip('Heatmap', _showHeatmap, _toggleHeatmap),
          _featureChip('Markers', _showMarkers, _toggleMarkers),
          // Theme cycle button
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 4),
            child: FilterChip(
              label: Text('🎨 ${_themeLabels[_themeIndex]}'),
              selected: false,
              onSelected: (_) => _cycleTheme(),
              backgroundColor: Colors.grey.shade200,
            ),
          ),
        ],
      ),
      body: Row(
        children: [
          // Map takes most of the space
          Expanded(
            flex: 3,
            child: Column(
              children: [
                // Selection mode chips
                Padding(
                  padding: const EdgeInsets.all(12),
                  child: Wrap(
                    spacing: 8,
                    runSpacing: 8,
                    children: NepalSelectionMode.values
                        .where((m) => m != NepalSelectionMode.none)
                        .map((mode) {
                      final selected = _controller.mode == mode;
                      return ChoiceChip(
                        label: Text(_labelFor(mode)),
                        selected: selected,
                        onSelected: (_) => _setMode(mode),
                      );
                    }).toList(),
                  ),
                ),
                // Map widget
                Expanded(
                  child: Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 12),
                    child: Stack(
                      children: [
                        NepalMapWidget(
                          controller: _controller,
                          theme: _getTheme(),
                          onDistrictTap: (district) {
                            setState(() {
                              _lastEvent =
                                  'Tapped ${district.name} (Province ${district.provinceNumber}, HQ: ${district.headquarter})';
                            });
                          },
                          onProvinceTap: (provinceNumber, districts) {
                            final isSelected =
                                _controller.isProvinceSelected(provinceNumber);
                            setState(() {
                              _lastEvent = isSelected
                                  ? 'Province $provinceNumber selected (${districts.length} districts)'
                                  : 'Province $provinceNumber deselected';
                            });
                          },
                          onDistrictHover: (district) {
                            setState(() {
                              _hovered = district?.name;
                            });
                          },
                        ),
                        Positioned(
                          right: 8,
                          top: 8,
                          child: _ZoomControls(controller: _controller),
                        ),
                      ],
                    ),
                  ),
                ),
                // Status bar
                Padding(
                  padding: const EdgeInsets.all(12),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        _lastEvent,
                        style: Theme.of(context).textTheme.bodyMedium,
                      ),
                      const SizedBox(height: 4),
                      if (_controller.mode.isDistrictMode)
                        Text(
                          'Selected districts: ${_controller.selectedDistricts.join(", ")}',
                          style: Theme.of(context).textTheme.bodySmall,
                        ),
                      if (_controller.mode.isProvinceMode)
                        Text(
                          'Selected provinces: ${_controller.selectedProvinces.join(", ")}',
                          style: Theme.of(context).textTheme.bodySmall,
                        ),
                      Align(
                        alignment: Alignment.centerRight,
                        child: TextButton(
                          onPressed: _controller.clearSelection,
                          child: const Text('Clear selection'),
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
          // Legend sidebar
          if (_showLayers || _showHeatmap || _showMarkers)
            Expanded(
              flex: 1,
              child: Padding(
                padding: const EdgeInsets.all(12),
                child: LegendWidget(
                  controller: _controller,
                  title: 'Legend',
                  showLayers: _showLayers,
                  showHeatmap: _showHeatmap,
                  showSelection: true,
                ),
              ),
            ),
        ],
      ),
    );
  }

  Widget _featureChip(String label, bool active, VoidCallback onTap) {
    return FilterChip(
      label: Text(label),
      selected: active,
      onSelected: (_) => onTap(),
      selectedColor: Colors.blue.shade100,
    );
  }
}

/// Small floating zoom controls.
class _ZoomControls extends StatelessWidget {
  const _ZoomControls({required this.controller});

  final NepalMapController controller;

  @override
  Widget build(BuildContext context) {
    return Material(
      elevation: 2,
      borderRadius: BorderRadius.circular(8),
      color: Theme.of(context).colorScheme.surface,
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          IconButton(
            tooltip: 'Zoom in',
            icon: const Icon(Icons.add),
            onPressed: controller.zoomIn,
          ),
          IconButton(
            tooltip: 'Zoom out',
            icon: const Icon(Icons.remove),
            onPressed: controller.zoomOut,
          ),
          IconButton(
            tooltip: 'Reset view',
            icon: const Icon(Icons.refresh),
            onPressed: controller.resetView,
          ),
          IconButton(
            tooltip: 'Fit to screen',
            icon: const Icon(Icons.fit_screen),
            onPressed: controller.fitToScreen,
          ),
        ],
      ),
    );
  }
}

/// Compact city label marker widget.
class _CityMarker extends StatelessWidget {
  const _CityMarker({required this.label, required this.color});

  final String label;
  final Color color;

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: color,
        shape: BoxShape.circle,
        border: Border.all(color: Colors.white, width: 1.5),
        boxShadow: [
          BoxShadow(color: Colors.black26, blurRadius: 3, offset: const Offset(0, 1)),
        ],
      ),
      alignment: Alignment.center,
      child: Text(
        label,
        style: const TextStyle(
          color: Colors.white,
          fontSize: 8,
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }
}
9
likes
150
points
126
downloads

Documentation

API reference

Publisher

verified publisherramkoirala.com.np

Weekly Downloads

A fully interactive, customizable Flutter widget for displaying and interacting with the map of Nepal, with district- and province-level selection, hover, styling, markers, and heatmap support.

Homepage
Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, vector_math

More

Packages that depend on nepal_map