blob: a1f1ad4088043c9fea9796e190d0c73ffb923493 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of google_maps_flutter;
typedef void MapCreatedCallback(GoogleMapController controller);
class GoogleMap extends StatefulWidget {
const GoogleMap({
@required this.initialCameraPosition,
this.onMapCreated,
this.gestureRecognizers,
this.compassEnabled = true,
this.cameraTargetBounds = CameraTargetBounds.unbounded,
this.mapType = MapType.normal,
this.minMaxZoomPreference = MinMaxZoomPreference.unbounded,
this.rotateGesturesEnabled = true,
this.scrollGesturesEnabled = true,
this.zoomGesturesEnabled = true,
this.tiltGesturesEnabled = true,
this.trackCameraPosition = false,
this.myLocationEnabled = false,
}) : assert(initialCameraPosition != null);
final MapCreatedCallback onMapCreated;
/// The initial position of the map's camera.
final CameraPosition initialCameraPosition;
/// True if the map should show a compass when rotated.
final bool compassEnabled;
/// Geographical bounding box for the camera target.
final CameraTargetBounds cameraTargetBounds;
/// Type of map tiles to be rendered.
final MapType mapType;
/// Preferred bounds for the camera zoom level.
///
/// Actual bounds depend on map data and device.
final MinMaxZoomPreference minMaxZoomPreference;
/// True if the map view should respond to rotate gestures.
final bool rotateGesturesEnabled;
/// True if the map view should respond to scroll gestures.
final bool scrollGesturesEnabled;
/// True if the map view should respond to zoom gestures.
final bool zoomGesturesEnabled;
/// True if the map view should respond to tilt gestures.
final bool tiltGesturesEnabled;
/// True if the map view should relay camera move events to Flutter.
final bool trackCameraPosition;
/// True if a "My Location" layer should be shown on the map.
///
/// This layer includes a location indicator at the current device location,
/// as well as a My Location button.
/// * The indicator is a small blue dot if the device is stationary, or a
/// chevron if the device is moving.
/// * The My Location button animates to focus on the user's current location
/// if the user's location is currently known.
///
/// Enabling this feature requires adding location permissions to both native
/// platforms of your app.
/// * On Android add either
/// `<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />`
/// or `<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />`
/// to your `AndroidManifest.xml` file. `ACCESS_COARSE_LOCATION` returns a
/// location with an accuracy approximately equivalent to a city block, while
/// `ACCESS_FINE_LOCATION` returns as precise a location as possible, although
/// it consumes more battery power. You will also need to request these
/// permissions during run-time. If they are not granted, the My Location
/// feature will fail silently.
/// * On iOS add a `NSLocationWhenInUseUsageDescription` key to your
/// `Info.plist` file. This will automatically prompt the user for permissions
/// when the map tries to turn on the My Location layer.
final bool myLocationEnabled;
/// Which gestures should be consumed by the map.
///
/// It is possible for other gesture recognizers to be competing with the map on pointer
/// events, e.g if the map is inside a [ListView] the [ListView] will want to handle
/// vertical drags. The map will claim gestures that are recognized by any of the
/// recognizers on this list.
///
/// When this set is empty or null, the map will only handle pointer events for gestures that
/// were not claimed by any other gesture recognizer.
final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
@override
State createState() => _GoogleMapState();
}
class _GoogleMapState extends State<GoogleMap> {
final Completer<GoogleMapController> _controller =
Completer<GoogleMapController>();
_GoogleMapOptions _googleMapOptions;
@override
Widget build(BuildContext context) {
final Map<String, dynamic> creationParams = <String, dynamic>{
'initialCameraPosition': widget.initialCameraPosition?._toMap(),
'options': _GoogleMapOptions.fromWidget(widget).toMap(),
};
if (defaultTargetPlatform == TargetPlatform.android) {
return AndroidView(
viewType: 'plugins.flutter.io/google_maps',
onPlatformViewCreated: onPlatformViewCreated,
gestureRecognizers: widget.gestureRecognizers,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
return UiKitView(
viewType: 'plugins.flutter.io/google_maps',
onPlatformViewCreated: onPlatformViewCreated,
gestureRecognizers: widget.gestureRecognizers,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
}
return Text(
'$defaultTargetPlatform is not yet supported by the maps plugin');
}
@override
void initState() {
super.initState();
_googleMapOptions = _GoogleMapOptions.fromWidget(widget);
}
@override
void didUpdateWidget(GoogleMap oldWidget) {
super.didUpdateWidget(oldWidget);
final _GoogleMapOptions newOptions = _GoogleMapOptions.fromWidget(widget);
final Map<String, dynamic> updates =
_googleMapOptions.updatesMap(newOptions);
_updateOptions(updates);
_googleMapOptions = newOptions;
}
void _updateOptions(Map<String, dynamic> updates) async {
if (updates.isEmpty) {
return;
}
final GoogleMapController controller = await _controller.future;
controller._updateMapOptions(updates);
}
Future<void> onPlatformViewCreated(int id) async {
final GoogleMapController controller =
await GoogleMapController.init(id, widget.initialCameraPosition);
_controller.complete(controller);
if (widget.onMapCreated != null) {
widget.onMapCreated(controller);
}
}
}
/// Configuration options for the GoogleMaps user interface.
///
/// When used to change configuration, null values will be interpreted as
/// "do not change this configuration option".
class _GoogleMapOptions {
_GoogleMapOptions({
this.compassEnabled,
this.cameraTargetBounds,
this.mapType,
this.minMaxZoomPreference,
this.rotateGesturesEnabled,
this.scrollGesturesEnabled,
this.tiltGesturesEnabled,
this.trackCameraPosition,
this.zoomGesturesEnabled,
this.myLocationEnabled,
});
static _GoogleMapOptions fromWidget(GoogleMap map) {
return _GoogleMapOptions(
compassEnabled: map.compassEnabled,
cameraTargetBounds: map.cameraTargetBounds,
mapType: map.mapType,
minMaxZoomPreference: map.minMaxZoomPreference,
rotateGesturesEnabled: map.rotateGesturesEnabled,
scrollGesturesEnabled: map.scrollGesturesEnabled,
tiltGesturesEnabled: map.tiltGesturesEnabled,
trackCameraPosition: map.trackCameraPosition,
zoomGesturesEnabled: map.zoomGesturesEnabled,
myLocationEnabled: map.myLocationEnabled,
);
}
final bool compassEnabled;
final CameraTargetBounds cameraTargetBounds;
final MapType mapType;
final MinMaxZoomPreference minMaxZoomPreference;
final bool rotateGesturesEnabled;
final bool scrollGesturesEnabled;
final bool tiltGesturesEnabled;
final bool trackCameraPosition;
final bool zoomGesturesEnabled;
final bool myLocationEnabled;
Map<String, dynamic> toMap() {
final Map<String, dynamic> optionsMap = <String, dynamic>{};
void addIfNonNull(String fieldName, dynamic value) {
if (value != null) {
optionsMap[fieldName] = value;
}
}
addIfNonNull('compassEnabled', compassEnabled);
addIfNonNull('cameraTargetBounds', cameraTargetBounds?._toJson());
addIfNonNull('mapType', mapType?.index);
addIfNonNull('minMaxZoomPreference', minMaxZoomPreference?._toJson());
addIfNonNull('rotateGesturesEnabled', rotateGesturesEnabled);
addIfNonNull('scrollGesturesEnabled', scrollGesturesEnabled);
addIfNonNull('tiltGesturesEnabled', tiltGesturesEnabled);
addIfNonNull('zoomGesturesEnabled', zoomGesturesEnabled);
addIfNonNull('trackCameraPosition', trackCameraPosition);
addIfNonNull('myLocationEnabled', myLocationEnabled);
return optionsMap;
}
Map<String, dynamic> updatesMap(_GoogleMapOptions newOptions) {
final Map<String, dynamic> prevOptionsMap = toMap();
return newOptions.toMap()
..removeWhere(
(String key, dynamic value) => prevOptionsMap[key] == value);
}
}