187 lines
5.5 KiB
Dart
187 lines
5.5 KiB
Dart
import 'dart:math' as math;
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_map/flutter_map.dart';
|
|
import 'package:latlong2/latlong.dart' as ll;
|
|
import 'map_provider_interface.dart';
|
|
|
|
class OpenStreetMapProvider implements AppMapProvider {
|
|
@override
|
|
AppMapType get type => AppMapType.openStreetMap;
|
|
|
|
@override
|
|
String get name => 'OpenStreetMap';
|
|
|
|
@override
|
|
Widget buildMapView({
|
|
required BuildContext context,
|
|
required AppLatLng center,
|
|
required double zoom,
|
|
required List<AppMapMarker> markers,
|
|
required List<AppPolyline> polylines,
|
|
required bool isFollowMode,
|
|
required MapConfig config,
|
|
required ValueChanged<AppLatLng>? onPositionChanged,
|
|
required VoidCallback? onUserInteraction,
|
|
}) {
|
|
return _OsmMapView(
|
|
center: center,
|
|
zoom: zoom,
|
|
markers: markers,
|
|
polylines: polylines,
|
|
isFollowMode: isFollowMode,
|
|
config: config,
|
|
onPositionChanged: onPositionChanged,
|
|
onUserInteraction: onUserInteraction,
|
|
);
|
|
}
|
|
}
|
|
|
|
class _OsmMapView extends StatefulWidget {
|
|
final AppLatLng center;
|
|
final double zoom;
|
|
final List<AppMapMarker> markers;
|
|
final List<AppPolyline> polylines;
|
|
final bool isFollowMode;
|
|
final MapConfig config;
|
|
final ValueChanged<AppLatLng>? onPositionChanged;
|
|
final VoidCallback? onUserInteraction;
|
|
|
|
const _OsmMapView({
|
|
required this.center,
|
|
required this.zoom,
|
|
required this.markers,
|
|
required this.polylines,
|
|
required this.isFollowMode,
|
|
required this.config,
|
|
required this.onPositionChanged,
|
|
required this.onUserInteraction,
|
|
});
|
|
|
|
@override
|
|
State<_OsmMapView> createState() => _OsmMapViewState();
|
|
}
|
|
|
|
class _OsmMapViewState extends State<_OsmMapView> {
|
|
late final MapController _mapController;
|
|
ll.LatLng? _lastFollowPos;
|
|
DateTime? _lastFollowTime;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_mapController = MapController();
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(covariant _OsmMapView oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
if (widget.isFollowMode) {
|
|
final centerLl = ll.LatLng(widget.center.latitude, widget.center.longitude);
|
|
final now = DateTime.now();
|
|
final posDelta = _lastFollowPos == null
|
|
? 1.0
|
|
: (centerLl.latitude - _lastFollowPos!.latitude).abs() +
|
|
(centerLl.longitude - _lastFollowPos!.longitude).abs();
|
|
|
|
final shouldMove = _lastFollowPos == null ||
|
|
(posDelta > 0.00005 && (_lastFollowTime == null || now.difference(_lastFollowTime!).inMilliseconds > 500));
|
|
|
|
if (shouldMove) {
|
|
_lastFollowPos = centerLl;
|
|
_lastFollowTime = now;
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (mounted) {
|
|
try {
|
|
_mapController.move(centerLl, widget.zoom);
|
|
} catch (_) {}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_mapController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final centerLl = ll.LatLng(widget.center.latitude, widget.center.longitude);
|
|
final media = MediaQuery.of(context);
|
|
final sizeKey = '${media.orientation}_${media.size.width.toInt()}x${media.size.height.toInt()}';
|
|
|
|
return FlutterMap(
|
|
key: ValueKey('osm_map_$sizeKey'),
|
|
mapController: _mapController,
|
|
options: MapOptions(
|
|
initialCenter: centerLl,
|
|
initialZoom: widget.zoom,
|
|
minZoom: widget.config.minZoom,
|
|
maxZoom: widget.config.maxZoom,
|
|
backgroundColor: const Color(0xFF0F0F0F),
|
|
onPositionChanged: (position, hasGesture) {
|
|
if (hasGesture) {
|
|
widget.onUserInteraction?.call();
|
|
}
|
|
if (widget.onPositionChanged != null) {
|
|
final c = position.center;
|
|
widget.onPositionChanged!(AppLatLng(c.latitude, c.longitude));
|
|
}
|
|
},
|
|
),
|
|
children: [
|
|
TileLayer(
|
|
key: ValueKey('${widget.config.tileUrlTemplate}_${widget.config.isDarkMode}'),
|
|
urlTemplate: widget.config.tileUrlTemplate,
|
|
subdomains: widget.config.subdomains,
|
|
userAgentPackageName: 'app.motorsignal.motorcycle',
|
|
tileBuilder:
|
|
widget.config.isDarkMode ? darkModeTileBuilder : null,
|
|
maxZoom: 20,
|
|
maxNativeZoom: 19,
|
|
),
|
|
PolylineLayer(
|
|
polylines: widget.polylines.map((p) {
|
|
return Polyline(
|
|
points: p.points.map((pt) => ll.LatLng(pt.latitude, pt.longitude)).toList(),
|
|
strokeWidth: p.width,
|
|
color: p.color,
|
|
);
|
|
}).toList(),
|
|
),
|
|
MarkerLayer(
|
|
markers: widget.markers.map((m) {
|
|
return Marker(
|
|
point: ll.LatLng(m.position.latitude, m.position.longitude),
|
|
width: 50,
|
|
height: 50,
|
|
child: GestureDetector(
|
|
onTap: m.onTap,
|
|
child: Transform.rotate(
|
|
angle: (m.heading * math.pi) / 180.0,
|
|
child: m.iconWidget ??
|
|
const Icon(
|
|
Icons.navigation,
|
|
color: Colors.cyanAccent,
|
|
size: 36,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
RichAttributionWidget(
|
|
attributions: [
|
|
TextSourceAttribution(
|
|
widget.config.attributionText,
|
|
textStyle: const TextStyle(color: Colors.white70, fontSize: 10),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|