flutter_map_smart 1.0.0
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.
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,
),
];
}