sharemap_maplib_flutter 0.0.3 copy "sharemap_maplib_flutter: ^0.0.3" to clipboard
sharemap_maplib_flutter: ^0.0.3 copied to clipboard

A MapLibre-based map widget for Flutter integrating ShareMap APIs, supporting dynamic POI layer toggling, HMAC-signed requests, and custom style loading.

example/lib/main.dart

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:sharemap_maplib_flutter/sharemap_maplib_flutter.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ShareMap MapLibre Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        brightness: Brightness.dark,
        primaryColor: const Color(0xFF0284C7), // Sky blue
        scaffoldBackgroundColor: const Color(0xFF0F172A), // Dark slate
        cardColor: const Color(0xFF1E293B),
        fontFamily: 'Inter',
        colorScheme: const ColorScheme.dark(
          primary: Color(0xFF38BDF8),
          secondary: Color(0xFF34D399), // Emerald accent
          surface: Color(0xFF1E293B),
        ),
      ),
      home: const MapDemoScreen(),
    );
  }
}

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

  @override
  State<MapDemoScreen> createState() => _MapDemoScreenState();
}

class _MapDemoScreenState extends State<MapDemoScreen> {
  final _apiKeyController = TextEditingController();
  final _secretKeyController = TextEditingController();
  String _apiKey = '';
  String _secretKey = '';
  bool _isMapLaunched = false;
  ShareMapStyle _selectedStyle = ShareMapStyle.sharemap;
  MapLibreMapController? _mapController;
  LatLng? _clickedLocation;
  String _mapStatus = 'Ready';
  bool _showSettings = true;
  final List<String> _activeLayerCodes = ['vn-industrial-zones']; // Dynamic list of active layer codes

  // Sensible start coordinate (Hanoi, Vietnam)
  static const _initialPosition = CameraPosition(
    target: LatLng(21.0285, 105.8542),
    zoom: 12.0,
  );

  @override
  void dispose() {
    _apiKeyController.dispose();
    _secretKeyController.dispose();
    super.dispose();
  }

  void _handleMapCreated(MapLibreMapController controller) {
    setState(() {
      _mapController = controller;
      _mapStatus = 'Map Initialized';
    });
  }

  void _handleStyleLoaded() {
    setState(() {
      _mapStatus = 'Style Fully Loaded';
    });
  }

  void _handleMapClick(Point<double> point, LatLng latLng) {
    setState(() {
      _clickedLocation = latLng;
      _mapStatus =
          'Clicked at: ${latLng.latitude.toStringAsFixed(4)}, ${latLng.longitude.toStringAsFixed(4)}';
    });

    // We can also animate the camera to the clicked location to showcase controller capability
    _mapController?.animateCamera(CameraUpdate.newLatLng(latLng));
  }

  void _zoomIn() {
    _mapController?.animateCamera(CameraUpdate.zoomIn());
  }

  void _zoomOut() {
    _mapController?.animateCamera(CameraUpdate.zoomOut());
  }

  void _centerTo(LatLng target) {
    _mapController?.animateCamera(
      CameraUpdate.newCameraPosition(
        CameraPosition(target: target, zoom: 14.0),
      ),
    );
    setState(() {
      _mapStatus = 'Centered camera';
    });
  }

  @override
  Widget build(BuildContext context) {
    final isDesktop = MediaQuery.of(context).size.width > 800;

    return Scaffold(
      body: Stack(
        children: [
          // 1. Map View or Setup Screen
          Positioned.fill(
            child: !_isMapLaunched
                ? _buildSetupView()
                : ShareMapLibre(
                    mode: ShareMapMode.dev,
                    xApiKey: _apiKey.isEmpty ? null : _apiKey,
                    secretKey: _secretKey.isEmpty ? null : _secretKey,
                    mapStyle: _selectedStyle,
                    onMapCreated: _handleMapCreated,
                    initialCameraPosition: _initialPosition,
                    onStyleLoadedCallback: _handleStyleLoaded,
                    onMapClick: _handleMapClick,
                    myLocationEnabled: false,
                    layerCodes: _activeLayerCodes.isEmpty ? null : _activeLayerCodes,
                  ),
          ),

          // 2. Custom Sleek Controller Overlay (only shown if map is active)
          if (_isMapLaunched && _mapController != null) ...[
            _buildSleekControlsOverlay(),
            _buildSideControlPanel(isDesktop),
            _buildStatusToast(),
          ],

          // 3. Setup trigger floating button if hidden on mobile
          if (_isMapLaunched && !isDesktop)
            Positioned(
              top: 16,
              left: 16,
              child: SafeArea(
                child: FloatingActionButton.small(
                  backgroundColor: const Color(0xFF1E293B).withValues(alpha: 0.9),
                  foregroundColor: Colors.white,
                  onPressed: () {
                    setState(() {
                      _showSettings = !_showSettings;
                    });
                  },
                  child: Icon(
                    _showSettings ? Icons.close_rounded : Icons.menu_rounded,
                  ),
                ),
              ),
            ),
        ],
      ),
    );
  }

  Widget _buildSetupView() {
    return Container(
      decoration: const BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment.topRight,
          end: Alignment.bottomLeft,
          colors: [
            Color(0xFF0F172A),
            Color(0xFF1E1B4B), // Indigo hints
          ],
        ),
      ),
      child: Center(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(24),
          child: Container(
            constraints: const BoxConstraints(maxWidth: 460),
            padding: const EdgeInsets.all(32),
            decoration: BoxDecoration(
              color: const Color(0xFF1E293B).withValues(alpha: 0.9),
              borderRadius: BorderRadius.circular(24),
              border: Border.all(color: const Color(0xFF334155), width: 1.5),
              boxShadow: [
                BoxShadow(
                  color: Colors.black.withValues(alpha: 0.4),
                  blurRadius: 30,
                  offset: const Offset(0, 15),
                ),
              ],
            ),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                // Header Logo/Icon
                const Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Icon(Icons.map_rounded, color: Color(0xFF38BDF8), size: 40),
                    SizedBox(width: 12),
                    Text(
                      'ShareMap Live',
                      style: TextStyle(
                        fontSize: 28,
                        fontWeight: FontWeight.w800,
                        letterSpacing: 0.5,
                        color: Colors.white,
                      ),
                    ),
                  ],
                ),
                const SizedBox(height: 16),
                const Text(
                  'Enter your ShareMap API Key to access premium vector styles and map tiles compiled natively in MapLibre.',
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    color: Color(0xFF94A3B8),
                    fontSize: 14,
                    height: 1.5,
                  ),
                ),
                const SizedBox(height: 32),

                // API Key Field
                const Text(
                  'API KEY (x-api-key)',
                  style: TextStyle(
                    color: Color(0xFF38BDF8),
                    fontSize: 12,
                    fontWeight: FontWeight.bold,
                    letterSpacing: 1,
                  ),
                ),
                const SizedBox(height: 8),
                TextField(
                  controller: _apiKeyController,
                  obscureText: true,
                  style: const TextStyle(color: Colors.white, letterSpacing: 2),
                  decoration: InputDecoration(
                    hintText: 'Enter x-api-key here...',
                    hintStyle: const TextStyle(
                      color: Color(0xFF64748B),
                      letterSpacing: 0.5,
                    ),
                    filled: true,
                    fillColor: const Color(0xFF0F172A),
                    contentPadding: const EdgeInsets.symmetric(
                      horizontal: 16,
                      vertical: 16,
                    ),
                    border: OutlineInputBorder(
                      borderRadius: BorderRadius.circular(12),
                      borderSide: const BorderSide(color: Color(0xFF334155)),
                    ),
                    focusedBorder: OutlineInputBorder(
                      borderRadius: BorderRadius.circular(12),
                      borderSide: const BorderSide(
                        color: Color(0xFF38BDF8),
                        width: 1.5,
                      ),
                    ),
                    prefixIcon: const Icon(
                      Icons.vpn_key_rounded,
                      color: Color(0xFF38BDF8),
                    ),
                  ),
                ),
                const SizedBox(height: 16),

                // Secret Key Field
                const Text(
                  'SECRET KEY (Optional for HMAC)',
                  style: TextStyle(
                    color: Color(0xFF38BDF8),
                    fontSize: 12,
                    fontWeight: FontWeight.bold,
                    letterSpacing: 1,
                  ),
                ),
                const SizedBox(height: 8),
                TextField(
                  controller: _secretKeyController,
                  obscureText: true,
                  style: const TextStyle(color: Colors.white, letterSpacing: 2),
                  decoration: InputDecoration(
                    hintText: 'Enter secret key here...',
                    hintStyle: const TextStyle(
                      color: Color(0xFF64748B),
                      letterSpacing: 0.5,
                    ),
                    filled: true,
                    fillColor: const Color(0xFF0F172A),
                    contentPadding: const EdgeInsets.symmetric(
                      horizontal: 16,
                      vertical: 16,
                    ),
                    border: OutlineInputBorder(
                      borderRadius: BorderRadius.circular(12),
                      borderSide: const BorderSide(color: Color(0xFF334155)),
                    ),
                    focusedBorder: OutlineInputBorder(
                      borderRadius: BorderRadius.circular(12),
                      borderSide: const BorderSide(
                        color: Color(0xFF38BDF8),
                        width: 1.5,
                      ),
                    ),
                    prefixIcon: const Icon(
                      Icons.security_rounded,
                      color: Color(0xFF38BDF8),
                    ),
                  ),
                ),
                const SizedBox(height: 24),

                // Style Selection
                const Text(
                  'INITIAL MAP THEME',
                  style: TextStyle(
                    color: Color(0xFF38BDF8),
                    fontSize: 12,
                    fontWeight: FontWeight.bold,
                    letterSpacing: 1,
                  ),
                ),
                const SizedBox(height: 10),
                Container(
                  padding: const EdgeInsets.symmetric(horizontal: 12),
                  decoration: BoxDecoration(
                    color: const Color(0xFF0F172A),
                    borderRadius: BorderRadius.circular(12),
                    border: Border.all(color: const Color(0xFF334155)),
                  ),
                  child: DropdownButtonHideUnderline(
                    child: DropdownButton<ShareMapStyle>(
                      value: _selectedStyle,
                      dropdownColor: const Color(0xFF0F172A),
                      isExpanded: true,
                      style: const TextStyle(color: Colors.white, fontSize: 14),
                      items: ShareMapStyle.values.map((style) {
                        return DropdownMenuItem(
                          value: style,
                          child: Text(
                            style.name.toUpperCase(),
                            style: const TextStyle(fontWeight: FontWeight.bold),
                          ),
                        );
                      }).toList(),
                      onChanged: (val) {
                        if (val != null) {
                          setState(() {
                            _selectedStyle = val;
                          });
                        }
                      },
                    ),
                  ),
                ),
                const SizedBox(height: 36),

                // Launch Button
                SizedBox(
                  height: 52,
                  child: ElevatedButton(
                    onPressed: () {
                      final apiKeyVal = _apiKeyController.text.trim();
                      final secretKeyVal = _secretKeyController.text.trim();
                      setState(() {
                        _apiKey = apiKeyVal;
                        _secretKey = secretKeyVal;
                        _isMapLaunched = true;
                      });
                    },
                    style: ElevatedButton.styleFrom(
                      backgroundColor: const Color(0xFF0284C7),
                      foregroundColor: Colors.white,
                      elevation: 0,
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(12),
                      ),
                    ),
                    child: const Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text(
                          'Launch Map View',
                          style: TextStyle(
                            fontSize: 16,
                            fontWeight: FontWeight.bold,
                            letterSpacing: 0.5,
                          ),
                        ),
                        SizedBox(width: 8),
                        Icon(Icons.arrow_forward_rounded, size: 20),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildSleekControlsOverlay() {
    return Positioned(
      bottom: 24,
      right: 24,
      child: PointerInterceptor(
        child: Column(
          mainAxisSize: MainAxisSize.min,
        children: [
          // Zoom panel
          Container(
            decoration: BoxDecoration(
              color: const Color(0xFF1E293B).withValues(alpha: 0.9),
              borderRadius: BorderRadius.circular(12),
              border: Border.all(color: const Color(0xFF334155)),
            ),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                IconButton(
                  icon: const Icon(Icons.add_rounded, color: Colors.white),
                  onPressed: _zoomIn,
                  tooltip: 'Zoom In',
                ),
                Divider(
                  color: const Color(0xFF334155),
                  height: 1,
                  thickness: 1,
                ),
                IconButton(
                  icon: const Icon(Icons.remove_rounded, color: Colors.white),
                  onPressed: _zoomOut,
                  tooltip: 'Zoom Out',
                ),
              ],
            ),
          ),
          const SizedBox(height: 12),
          // Clear Cache action button
          FloatingActionButton.small(
            backgroundColor: const Color(0xFFF43F5E), // Rose red
            foregroundColor: Colors.white,
            onPressed: () {
              ShareMapLibre.clearCache();
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(
                  content: Text('ShareMap style cache cleared!'),
                  backgroundColor: Color(0xFF10B981), // Emerald green
                ),
              );
            },
            tooltip: 'Clear Cache',
            child: const Icon(Icons.delete_sweep_rounded),
          ),
        ],
      ),
     ),
    );
  }

  Widget _buildSideControlPanel(bool isDesktop) {
    if (!isDesktop && !_showSettings) return const SizedBox.shrink();

    return Positioned(
      top: isDesktop ? 24 : 76,
      left: 24,
      bottom: isDesktop ? 24 : 76,
      child: PointerInterceptor(
        child: Container(
          width: 320,
          padding: const EdgeInsets.all(24),
        decoration: BoxDecoration(
          color: const Color(0xFF1E293B).withValues(alpha: 0.9),
          borderRadius: BorderRadius.circular(20),
          border: Border.all(color: const Color(0xFF334155)),
          boxShadow: [
            BoxShadow(
              color: Colors.black.withValues(alpha: 0.3),
              blurRadius: 20,
              offset: const Offset(0, 10),
            ),
          ],
        ),
        child: SingleChildScrollView(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              Row(
                children: [
                  const Icon(Icons.settings_rounded, color: Color(0xFF38BDF8)),
                  const SizedBox(width: 8),
                  const Expanded(
                    child: Text(
                      'Map Controls',
                      style: TextStyle(
                        fontSize: 18,
                        fontWeight: FontWeight.bold,
                        color: Colors.white,
                      ),
                    ),
                  ),
                  if (!isDesktop)
                    IconButton(
                      icon: const Icon(
                        Icons.close_rounded,
                        color: Color(0xFF94A3B8),
                      ),
                      onPressed: () => setState(() => _showSettings = false),
                    ),
                ],
              ),
              const SizedBox(height: 16),
              const Divider(color: Color(0xFF334155)),
              const SizedBox(height: 12),

              // Active Key info
              const Text(
                'ACTIVE API KEY',
                style: TextStyle(
                  color: Color(0xFF64748B),
                  fontSize: 10,
                  fontWeight: FontWeight.bold,
                  letterSpacing: 0.5,
                ),
              ),
              const SizedBox(height: 4),
              Row(
                children: [
                  const Icon(
                    Icons.key_rounded,
                    size: 14,
                    color: Color(0xFF34D399),
                  ),
                  const SizedBox(width: 6),
                  Expanded(
                    child: Text(
                      _apiKey.isEmpty
                          ? 'NONE (Optional)'
                          : (_apiKey.length > 12
                                ? '${_apiKey.substring(0, 12)}...'
                                : _apiKey),
                      style: const TextStyle(
                        color: Colors.white,
                        fontSize: 13,
                        fontFamily: 'Courier',
                      ),
                    ),
                  ),
                  TextButton(
                    onPressed: () {
                      setState(() {
                        _isMapLaunched = false;
                        _mapController = null;
                      });
                    },
                    style: TextButton.styleFrom(
                      padding: EdgeInsets.zero,
                      minimumSize: const Size(50, 30),
                      tapTargetSize: MaterialTapTargetSize.shrinkWrap,
                    ),
                    child: const Text(
                      'Change',
                      style: TextStyle(color: Color(0xFFF43F5E), fontSize: 12),
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 8),
              const Text(
                'ACTIVE SECRET KEY',
                style: TextStyle(
                  color: Color(0xFF64748B),
                  fontSize: 10,
                  fontWeight: FontWeight.bold,
                  letterSpacing: 0.5,
                ),
              ),
              const SizedBox(height: 4),
              Row(
                children: [
                  const Icon(
                    Icons.security_rounded,
                    size: 14,
                    color: Color(0xFF38BDF8),
                  ),
                  const SizedBox(width: 6),
                  Expanded(
                    child: Text(
                      _secretKey.isEmpty
                          ? 'NONE (Optional)'
                          : (_secretKey.length > 12
                                ? '${_secretKey.substring(0, 12)}...'
                                : _secretKey),
                      style: const TextStyle(
                        color: Colors.white,
                        fontSize: 13,
                        fontFamily: 'Courier',
                      ),
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 24),

              // Dynamic Style selection
              const Text(
                'SWITCH STYLE THEME',
                style: TextStyle(
                  color: Color(0xFF64748B),
                  fontSize: 10,
                  fontWeight: FontWeight.bold,
                  letterSpacing: 0.5,
                ),
              ),
              const SizedBox(height: 8),
              ...ShareMapStyle.values.map((style) {
                final isSelected = _selectedStyle == style;
                return Padding(
                  padding: const EdgeInsets.only(bottom: 8.0),
                  child: InkWell(
                    onTap: () {
                      setState(() {
                        _selectedStyle = style;
                      });
                    },
                    borderRadius: BorderRadius.circular(10),
                    child: Container(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 16,
                        vertical: 12,
                      ),
                      decoration: BoxDecoration(
                        color: isSelected
                            ? const Color(0xFF0284C7).withValues(alpha: 0.2)
                            : Colors.transparent,
                        borderRadius: BorderRadius.circular(10),
                        border: Border.all(
                          color: isSelected
                              ? const Color(0xFF0284C7)
                              : const Color(0xFF334155),
                          width: 1.5,
                        ),
                      ),
                      child: Row(
                        children: [
                          Icon(
                            isSelected
                                ? Icons.radio_button_checked_rounded
                                : Icons.radio_button_off_rounded,
                            color: isSelected
                                ? const Color(0xFF38BDF8)
                                : const Color(0xFF64748B),
                            size: 18,
                          ),
                          const SizedBox(width: 12),
                          Text(
                            style.name.toUpperCase(),
                            style: TextStyle(
                              color: isSelected
                                  ? Colors.white
                                  : const Color(0xFF94A3B8),
                              fontWeight: isSelected
                                  ? FontWeight.bold
                                  : FontWeight.normal,
                              fontSize: 14,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                );
              }),
              const SizedBox(height: 24),

              // Layer Codes Control
              const Text(
                'POI LAYER CODES',
                style: TextStyle(
                  color: Color(0xFF64748B),
                  fontSize: 10,
                  fontWeight: FontWeight.bold,
                  letterSpacing: 0.5,
                ),
              ),
              const SizedBox(height: 8),

              // Preset vn_industrial_zones switch
              SwitchListTile(
                value: _activeLayerCodes.contains('vn-industrial-zones'),
                title: const Text(
                  'Industrial Zones',
                  style: TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.bold),
                ),
                subtitle: const Text(
                  'vn-industrial-zones',
                  style: TextStyle(color: Color(0xFF94A3B8), fontSize: 11),
                ),
                activeTrackColor: const Color(0xFF38BDF8),
                contentPadding: EdgeInsets.zero,
                onChanged: (bool val) {
                  setState(() {
                    if (val) {
                      if (!_activeLayerCodes.contains('vn-industrial-zones')) {
                        _activeLayerCodes.add('vn-industrial-zones');
                      }
                    } else {
                      _activeLayerCodes.remove('vn-industrial-zones');
                    }
                  });
                },
              ),

              const SizedBox(height: 8),

              // Custom text input for adding other codes
              Row(
                children: [
                  Expanded(
                    child: TextField(
                      style: const TextStyle(color: Colors.white, fontSize: 13),
                      decoration: InputDecoration(
                        hintText: 'Enter custom layer code...',
                        hintStyle: const TextStyle(color: Color(0xFF64748B), fontSize: 12),
                        isDense: true,
                        contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
                        filled: true,
                        fillColor: const Color(0xFF0F172A),
                        border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(8),
                          borderSide: const BorderSide(color: Color(0xFF334155)),
                        ),
                        focusedBorder: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(8),
                          borderSide: const BorderSide(color: Color(0xFF38BDF8)),
                        ),
                      ),
                      onSubmitted: (value) {
                        final code = value.trim();
                        if (code.isNotEmpty && !_activeLayerCodes.contains(code)) {
                          setState(() {
                            _activeLayerCodes.add(code);
                          });
                        }
                      },
                    ),
                  ),
                ],
              ),

              if (_activeLayerCodes.isNotEmpty) ...[
                const SizedBox(height: 12),
                Wrap(
                  spacing: 6,
                  runSpacing: 6,
                  children: _activeLayerCodes.map((code) {
                    return InputChip(
                      label: Text(
                        code,
                        style: const TextStyle(fontSize: 11, color: Colors.white),
                      ),
                      backgroundColor: const Color(0xFF0284C7).withValues(alpha: 0.3),
                      side: const BorderSide(color: Color(0xFF0284C7)),
                      onDeleted: () {
                        setState(() {
                          _activeLayerCodes.remove(code);
                        });
                      },
                      deleteIcon: const Icon(Icons.close_rounded, size: 14, color: Colors.white70),
                      materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
                    );
                  }).toList(),
                ),
              ],

              const SizedBox(height: 24),

              // Quick Location bookmarks
              const Text(
                'QUICK DESTINATIONS',
                style: TextStyle(
                  color: Color(0xFF64748B),
                  fontSize: 10,
                  fontWeight: FontWeight.bold,
                  letterSpacing: 0.5,
                ),
              ),
              const SizedBox(height: 8),
              _buildBookmarkButton(
                'Thang Long IZ (Hanoi)',
                const LatLng(21.1350, 105.7890),
              ),
              const SizedBox(height: 6),
              _buildBookmarkButton(
                'Hanoi Office',
                const LatLng(21.0285, 105.8542),
              ),
              const SizedBox(height: 6),
              _buildBookmarkButton(
                'Ho Chi Minh Hub',
                const LatLng(10.7626, 106.6601),
              ),
              const SizedBox(height: 6),
              _buildBookmarkButton(
                'Da Nang Branch',
                const LatLng(16.0544, 108.2022),
              ),

              if (_clickedLocation != null) ...[
                const SizedBox(height: 24),
                const Text(
                  'LAST CLOCKED COORDINATE',
                  style: TextStyle(
                    color: Color(0xFF64748B),
                    fontSize: 10,
                    fontWeight: FontWeight.bold,
                    letterSpacing: 0.5,
                  ),
                ),
                const SizedBox(height: 6),
                Container(
                  padding: const EdgeInsets.all(12),
                  decoration: BoxDecoration(
                    color: const Color(0xFF0F172A),
                    borderRadius: BorderRadius.circular(10),
                  ),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: [
                      Text(
                        'LAT: ${_clickedLocation!.latitude.toStringAsFixed(6)}',
                        style: const TextStyle(
                          color: Colors.white,
                          fontSize: 12,
                          fontFamily: 'Courier',
                        ),
                      ),
                      const SizedBox(height: 4),
                      Text(
                        'LNG: ${_clickedLocation!.longitude.toStringAsFixed(6)}',
                        style: const TextStyle(
                          color: Colors.white,
                          fontSize: 12,
                          fontFamily: 'Courier',
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ],
          ),
        ),
      ),
     ),
    );
  }

  Widget _buildBookmarkButton(String label, LatLng position) {
    return ElevatedButton.icon(
      onPressed: () => _centerTo(position),
      icon: const Icon(Icons.location_on_rounded, size: 16),
      label: Text(label),
      style: ElevatedButton.styleFrom(
        alignment: Alignment.centerLeft,
        backgroundColor: const Color(0xFF0F172A),
        foregroundColor: Colors.white,
        elevation: 0,
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
      ),
    );
  }

  Widget _buildStatusToast() {
    return Positioned(
      top: 16,
      right: 16,
      child: SafeArea(
        child: PointerInterceptor(
          child: Container(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
            decoration: BoxDecoration(
              color: const Color(0xFF1E293B).withValues(alpha: 0.9),
            borderRadius: BorderRadius.circular(12),
            border: Border.all(color: const Color(0xFF334155)),
          ),
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              Container(
                width: 8,
                height: 8,
                decoration: const BoxDecoration(
                  color: Color(0xFF34D399), // green status dot
                  shape: BoxShape.circle,
                ),
              ),
              const SizedBox(width: 10),
              Text(
                _mapStatus,
                style: const TextStyle(
                  color: Color(0xFFE2E8F0),
                  fontSize: 12,
                  fontWeight: FontWeight.w600,
                ),
              ),
            ],
          ),
        ),
       ),
      ),
    );
  }
}
0
likes
160
points
40
downloads

Documentation

API reference

Publisher

verified publishersharemap.live

Weekly Downloads

A MapLibre-based map widget for Flutter integrating ShareMap APIs, supporting dynamic POI layer toggling, HMAC-signed requests, and custom style loading.

Homepage
Repository (GitHub)

License

MIT (license)

Dependencies

crypto, flutter, http, maplibre_gl, pointer_interceptor

More

Packages that depend on sharemap_maplib_flutter