yandex_map_desktop 0.1.3
yandex_map_desktop: ^0.1.3 copied to clipboard
Yandex Maps JavaScript API 2.1 for Flutter desktop — renders real Yandex tiles, placemarks and polylines inside an Edge WebView2 (Windows) or WKWebView (macOS). Same widget API as yandex_mapkit so it [...]
// ignore_for_file: avoid_print
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:yandex_map_desktop/yandex_map_desktop.dart';
// ---------------------------------------------------------------------------
// TODO: Replace with your own Yandex Maps JavaScript API key.
// Get one at https://developer.tech.yandex.com — enable
// "JavaScript API and HTTP Geocoder" and set domain restrictions
// to "Without restrictions".
// ---------------------------------------------------------------------------
const String _kApiKey = 'YOUR_JS_API_KEY';
// Moscow city centre — used as the default camera target.
const Point _kMoscow = Point(latitude: 55.755864, longitude: 37.617698);
// Two recognisable Moscow landmarks used to draw a sample polyline.
const Point _kKremlin = Point(latitude: 55.752023, longitude: 37.617499);
const Point _kRedSquare = Point(latitude: 55.753930, longitude: 37.620795);
const Point _kBolshoyTheatre = Point(latitude: 55.760153, longitude: 37.618644);
const Point _kLubyanka = Point(latitude: 55.759968, longitude: 37.626346);
void main() {
runApp(const MyApp());
}
// ---------------------------------------------------------------------------
// Root application widget
// ---------------------------------------------------------------------------
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Yandex Map Desktop — Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorSchemeSeed: Colors.indigo,
useMaterial3: true,
),
home: const MapDemoScreen(),
);
}
}
// ---------------------------------------------------------------------------
// Main demo screen
// ---------------------------------------------------------------------------
class MapDemoScreen extends StatefulWidget {
const MapDemoScreen({super.key});
@override
State<MapDemoScreen> createState() => _MapDemoScreenState();
}
class _MapDemoScreenState extends State<MapDemoScreen> {
// Holds the controller once the map signals it is ready.
YandexMapDesktopController? _controller;
// Running list of map objects sent to the widget.
// Initialised with a demo polyline and two landmark placemarks.
late List<MapObject> _mapObjects;
// Displayed in the AppBar subtitle — updated on every camera change.
CameraPosition _cameraPosition = const CameraPosition(
target: _kMoscow,
zoom: 12,
);
// Counter used to generate unique MapObjectIds for tap-added placemarks.
int _tapMarkerCount = 0;
// Random number generator for the FAB "random marker" feature.
final math.Random _random = math.Random();
@override
void initState() {
super.initState();
_mapObjects = _buildInitialObjects();
}
// ---------------------------------------------------------------------------
// Initial map objects
// ---------------------------------------------------------------------------
List<MapObject> _buildInitialObjects() {
return [
// --- A polyline tracing a short walking route through central Moscow ---
PolylineMapObject(
mapId: const MapObjectId('central_moscow_route'),
coordinates: const [
_kKremlin,
_kRedSquare,
_kBolshoyTheatre,
_kLubyanka,
],
strokeColor: Colors.indigo,
strokeWidth: 4,
onTap: (obj) {
print('Polyline tapped: ${obj.mapId.value}');
},
),
// --- Kremlin placemark ---
PlacemarkMapObject(
mapId: const MapObjectId('kremlin'),
point: _kKremlin,
icon: const PlacemarkIcon(color: Colors.deepOrange, size: 40),
label: 'Kremlin',
onTap: (obj, point) {
print('Tapped: ${obj.label} at $point');
},
),
// --- Red Square placemark ---
PlacemarkMapObject(
mapId: const MapObjectId('red_square'),
point: _kRedSquare,
icon: const PlacemarkIcon(color: Colors.red, size: 40),
label: 'Red Square',
onTap: (obj, point) {
print('Tapped: ${obj.label} at $point');
},
),
// --- Bolshoy Theatre placemark ---
PlacemarkMapObject(
mapId: const MapObjectId('bolshoy'),
point: _kBolshoyTheatre,
icon: const PlacemarkIcon(color: Colors.purple, size: 36),
label: 'Bolshoy Theatre',
onTap: (obj, point) {
print('Tapped: ${obj.label} at $point');
},
),
];
}
// ---------------------------------------------------------------------------
// Map callbacks
// ---------------------------------------------------------------------------
void _onMapCreated(YandexMapDesktopController controller) {
setState(() => _controller = controller);
print('YandexMapDesktop: map ready');
}
/// When the user taps the map background, drop a blue marker and show a
/// SnackBar with the tapped coordinate.
void _onMapTap(Point point) {
final id = 'tap_marker_${++_tapMarkerCount}';
final marker = PlacemarkMapObject(
mapId: MapObjectId(id),
point: point,
icon: const PlacemarkIcon(color: Colors.blue, size: 32),
label: 'Tap #$_tapMarkerCount',
onTap: (obj, _) => _removeMarker(obj.mapId),
);
setState(() {
_mapObjects = [..._mapObjects, marker];
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Marker added at '
'${point.latitude.toStringAsFixed(5)}, '
'${point.longitude.toStringAsFixed(5)} '
'(tap it to remove)',
),
duration: const Duration(seconds: 3),
behavior: SnackBarBehavior.floating,
),
);
}
/// Right-click / long-press asks whether to add a marker at that position.
void _onMapLongTap(Point point) {
showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Add marker here?'),
content: Text(
'${point.latitude.toStringAsFixed(5)}, '
'${point.longitude.toStringAsFixed(5)}',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text('Cancel'),
),
FilledButton(
onPressed: () => Navigator.pop(ctx, true),
child: const Text('Add'),
),
],
),
).then((confirmed) {
if (confirmed == true) _onMapTap(point);
});
}
/// Keeps the AppBar subtitle in sync with camera movement.
void _onCameraPositionChanged(CameraPosition pos, bool finished) {
// Only trigger a rebuild once the movement ends to avoid excessive redraws.
if (finished) {
setState(() => _cameraPosition = pos);
}
}
// ---------------------------------------------------------------------------
// FAB — random placemark near Moscow
// ---------------------------------------------------------------------------
void _addRandomMarker() {
// Scatter within ±0.05 degrees (~5 km) around Moscow centre.
final lat = _kMoscow.latitude + (_random.nextDouble() - 0.5) * 0.1;
final lng = _kMoscow.longitude + (_random.nextDouble() - 0.5) * 0.1;
final point = Point(latitude: lat, longitude: lng);
final id = 'random_${++_tapMarkerCount}';
final colors = [
Colors.green,
Colors.teal,
Colors.amber,
Colors.cyan,
Colors.pink,
];
final color = colors[_tapMarkerCount % colors.length];
setState(() {
_mapObjects = [
..._mapObjects,
PlacemarkMapObject(
mapId: MapObjectId(id),
point: point,
icon: PlacemarkIcon(color: color, size: 34),
label: 'Random #$_tapMarkerCount',
onTap: (obj, _) => _removeMarker(obj.mapId),
),
];
});
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/// Removes a map object by its [id] from the list.
void _removeMarker(MapObjectId id) {
setState(() {
_mapObjects = _mapObjects.where((o) => o.mapId != id).toList();
});
}
/// Clears every dynamically-added marker (anything after the initial set).
void _clearDynamicMarkers() {
final initial = _buildInitialObjects();
final initialIds = initial.map((o) => o.mapId).toSet();
setState(() {
_mapObjects =
_mapObjects.where((o) => initialIds.contains(o.mapId)).toList();
_tapMarkerCount = 0;
});
}
// ---------------------------------------------------------------------------
// Build
// ---------------------------------------------------------------------------
@override
Widget build(BuildContext context) {
final lat = _cameraPosition.target.latitude.toStringAsFixed(4);
final lng = _cameraPosition.target.longitude.toStringAsFixed(4);
final zoom = _cameraPosition.zoom.toStringAsFixed(1);
return Scaffold(
appBar: AppBar(
title: const Text('Yandex Map Windows Demo'),
// Live camera subtitle — updated whenever the camera stops moving.
bottom: PreferredSize(
preferredSize: const Size.fromHeight(20),
child: Padding(
padding: const EdgeInsets.only(bottom: 6),
child: Text(
'lat $lat lng $lng zoom $zoom',
style: Theme.of(context)
.textTheme
.labelSmall
?.copyWith(color: Colors.white70),
),
),
),
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Colors.white,
actions: [
// ---- Zoom in ----
IconButton(
tooltip: 'Zoom in',
icon: const Icon(Icons.add),
onPressed: _controller == null
? null
: () => _controller!.moveCamera(CameraUpdate.zoomIn()),
),
// ---- Zoom out ----
IconButton(
tooltip: 'Zoom out',
icon: const Icon(Icons.remove),
onPressed: _controller == null
? null
: () => _controller!.moveCamera(CameraUpdate.zoomOut()),
),
// ---- Fly to Moscow centre ----
IconButton(
tooltip: 'Reset to Moscow',
icon: const Icon(Icons.my_location),
onPressed: _controller == null
? null
: () => _controller!.moveCamera(
CameraUpdate.newCameraPosition(
const CameraPosition(target: _kMoscow, zoom: 12),
),
),
),
// ---- Clear dynamic markers ----
IconButton(
tooltip: 'Clear added markers',
icon: const Icon(Icons.delete_sweep_outlined),
onPressed: _clearDynamicMarkers,
),
],
),
// ---- Map fills the remaining space ----
body: YandexMapDesktop(
apiKey: _kApiKey,
initialCameraPosition: const CameraPosition(
target: _kMoscow,
zoom: 12,
),
mapObjects: _mapObjects,
onMapCreated: _onMapCreated,
onMapTap: _onMapTap,
onMapLongTap: _onMapLongTap,
onCameraPositionChanged: _onCameraPositionChanged,
),
// ---- FAB drops a random marker ----
floatingActionButton: FloatingActionButton.extended(
onPressed: _addRandomMarker,
icon: const Icon(Icons.add_location_alt_outlined),
label: const Text('Random marker'),
tooltip: 'Add a random placemark near Moscow',
),
);
}
}