From bbeed7adb3fcda22b80bf1fbdd87533c7c3f6e5b Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Mon, 11 May 2026 17:57:39 +0900 Subject: [PATCH 1/4] [google_maps_flutter] Update google_maps_flutter to 2.16.0 * Update google_maps_flutter to 2.16.0. * Update google_maps_flutter_platform_interface to 2.15.0. * Add support for ground overlays (bounds-based) via google.maps.GroundOverlay. * Forward the colorScheme map option to google.maps.ColorScheme. --- packages/google_maps_flutter/CHANGELOG.md | 8 + packages/google_maps_flutter/README.md | 4 +- .../example/lib/ground_overlay.dart | 156 ++++++++++++++++++ .../google_maps_flutter/example/lib/main.dart | 2 + .../google_maps_flutter/example/pubspec.yaml | 4 +- .../lib/google_maps_flutter_tizen.dart | 2 + .../google_maps_flutter/lib/src/convert.dart | 133 +++++++++++++++ .../lib/src/google_maps_controller.dart | 53 ++++++ .../lib/src/google_maps_flutter_tizen.dart | 62 +++++++ .../lib/src/ground_overlay.dart | 51 ++++++ .../lib/src/ground_overlays.dart | 93 +++++++++++ .../google_maps_flutter/lib/src/util.dart | 94 +++++++++++ packages/google_maps_flutter/pubspec.yaml | 4 +- 13 files changed, 660 insertions(+), 6 deletions(-) create mode 100644 packages/google_maps_flutter/example/lib/ground_overlay.dart create mode 100644 packages/google_maps_flutter/lib/src/ground_overlay.dart create mode 100644 packages/google_maps_flutter/lib/src/ground_overlays.dart diff --git a/packages/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/CHANGELOG.md index 125ffd71d..cb7a5262c 100644 --- a/packages/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/CHANGELOG.md @@ -2,6 +2,14 @@ * Update code format. +## 0.1.14 + +* Update google_maps_flutter to 2.16.0. +* Update google_maps_flutter_platform_interface to 2.15.0. +* Add support for ground overlays (bounds-based) using + `google.maps.GroundOverlay`. +* Forward the `colorScheme` map option to `google.maps.ColorScheme`. + ## 0.1.13 * Update the LICENSE file so that it is recognized by pub.dev. diff --git a/packages/google_maps_flutter/README.md b/packages/google_maps_flutter/README.md index 25a753d08..0cbdb4161 100644 --- a/packages/google_maps_flutter/README.md +++ b/packages/google_maps_flutter/README.md @@ -20,8 +20,8 @@ This package is not an _endorsed_ implementation of `google_maps_flutter`. There ```yaml dependencies: - google_maps_flutter: ^2.10.0 - google_maps_flutter_tizen: ^0.1.13 + google_maps_flutter: ^2.16.0 + google_maps_flutter_tizen: ^0.1.14 ``` For detailed usage, see https://pub.dev/packages/google_maps_flutter#sample-usage. diff --git a/packages/google_maps_flutter/example/lib/ground_overlay.dart b/packages/google_maps_flutter/example/lib/ground_overlay.dart new file mode 100644 index 000000000..8176ea392 --- /dev/null +++ b/packages/google_maps_flutter/example/lib/ground_overlay.dart @@ -0,0 +1,156 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: public_member_api_docs + +import 'package:flutter/material.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; + +import 'page.dart'; + +class GroundOverlayPage extends GoogleMapExampleAppPage { + const GroundOverlayPage({Key? key}) + : super(const Icon(Icons.map), 'Ground overlay', key: key); + + @override + Widget build(BuildContext context) { + return const GroundOverlayBody(); + } +} + +class GroundOverlayBody extends StatefulWidget { + const GroundOverlayBody({super.key}); + + @override + State createState() => GroundOverlayBodyState(); +} + +class GroundOverlayBodyState extends State { + GoogleMapController? controller; + GroundOverlay? _groundOverlay; + int _groundOverlayIndex = 0; + + static const LatLng _mapCenter = LatLng(37.422026, -122.085329); + + final LatLngBounds _bounds1 = LatLngBounds( + southwest: const LatLng(37.42, -122.09), + northeast: const LatLng(37.423, -122.084), + ); + final LatLngBounds _bounds2 = LatLngBounds( + southwest: const LatLng(37.421, -122.091), + northeast: const LatLng(37.424, -122.08), + ); + + late LatLngBounds _currentBounds = _bounds1; + + // ignore: use_setters_to_change_properties + void _onMapCreated(GoogleMapController controller) { + this.controller = controller; + } + + Future _addGroundOverlay() async { + final AssetMapBitmap image = await AssetMapBitmap.create( + createLocalImageConfiguration(context), + 'assets/red_square.png', + bitmapScaling: MapBitmapScaling.none, + ); + + _groundOverlayIndex += 1; + + final GroundOverlay overlay = GroundOverlay.fromBounds( + groundOverlayId: GroundOverlayId('ground_overlay_$_groundOverlayIndex'), + image: image, + bounds: _currentBounds, + onTap: _toggleBounds, + ); + + setState(() { + _groundOverlay = overlay; + }); + } + + void _removeGroundOverlay() { + setState(() { + _groundOverlay = null; + }); + } + + void _toggleTransparency() { + if (_groundOverlay == null) { + return; + } + setState(() { + final double transparency = + _groundOverlay!.transparency == 0.0 ? 0.5 : 0.0; + _groundOverlay = + _groundOverlay!.copyWith(transparencyParam: transparency); + }); + } + + void _toggleVisible() { + if (_groundOverlay == null) { + return; + } + setState(() { + _groundOverlay = + _groundOverlay!.copyWith(visibleParam: !_groundOverlay!.visible); + }); + } + + Future _toggleBounds() async { + setState(() { + _currentBounds = _currentBounds == _bounds1 ? _bounds2 : _bounds1; + }); + await _addGroundOverlay(); + } + + @override + Widget build(BuildContext context) { + final Set overlays = { + if (_groundOverlay != null) _groundOverlay!, + }; + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: GoogleMap( + initialCameraPosition: const CameraPosition( + target: _mapCenter, + zoom: 14.0, + ), + groundOverlays: overlays, + onMapCreated: _onMapCreated, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + TextButton( + onPressed: _groundOverlay == null ? _addGroundOverlay : null, + child: const Text('Add'), + ), + TextButton( + onPressed: _groundOverlay != null ? _removeGroundOverlay : null, + child: const Text('Remove'), + ), + TextButton( + onPressed: _groundOverlay == null ? null : _toggleTransparency, + child: const Text('Toggle transparency'), + ), + TextButton( + onPressed: _groundOverlay == null ? null : _toggleVisible, + child: const Text('Toggle visible'), + ), + TextButton( + onPressed: _groundOverlay == null ? null : _toggleBounds, + child: const Text('Change bounds'), + ), + ], + ), + ], + ); + } +} diff --git a/packages/google_maps_flutter/example/lib/main.dart b/packages/google_maps_flutter/example/lib/main.dart index dd647233d..2a3effab2 100644 --- a/packages/google_maps_flutter/example/lib/main.dart +++ b/packages/google_maps_flutter/example/lib/main.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'animate_camera.dart'; import 'clustering.dart'; +import 'ground_overlay.dart'; import 'heatmap.dart'; import 'lite_mode.dart'; import 'map_click.dart'; @@ -40,6 +41,7 @@ final List _allPages = [ const SnapshotPage(), const LiteModePage(), const TileOverlayPage(), + const GroundOverlayPage(), const ClusteringPage(), const MapIdPage(), const HeatmapPage(), diff --git a/packages/google_maps_flutter/example/pubspec.yaml b/packages/google_maps_flutter/example/pubspec.yaml index d3bbf87c2..a78e6c283 100644 --- a/packages/google_maps_flutter/example/pubspec.yaml +++ b/packages/google_maps_flutter/example/pubspec.yaml @@ -9,8 +9,8 @@ environment: dependencies: flutter: sdk: flutter - google_maps_flutter: ^2.10.0 - google_maps_flutter_platform_interface: ^2.10.0 + google_maps_flutter: ^2.16.0 + google_maps_flutter_platform_interface: ^2.15.0 google_maps_flutter_tizen: path: ../ diff --git a/packages/google_maps_flutter/lib/google_maps_flutter_tizen.dart b/packages/google_maps_flutter/lib/google_maps_flutter_tizen.dart index f747a8261..eb6c1ca62 100644 --- a/packages/google_maps_flutter/lib/google_maps_flutter_tizen.dart +++ b/packages/google_maps_flutter/lib/google_maps_flutter_tizen.dart @@ -26,6 +26,8 @@ part 'src/circles.dart'; part 'src/convert.dart'; part 'src/google_maps_controller.dart'; part 'src/google_maps_flutter_tizen.dart'; +part 'src/ground_overlay.dart'; +part 'src/ground_overlays.dart'; part 'src/marker.dart'; part 'src/markers.dart'; part 'src/marker_clustering.dart'; diff --git a/packages/google_maps_flutter/lib/src/convert.dart b/packages/google_maps_flutter/lib/src/convert.dart index a11897132..0e7d3b37a 100644 --- a/packages/google_maps_flutter/lib/src/convert.dart +++ b/packages/google_maps_flutter/lib/src/convert.dart @@ -16,6 +16,62 @@ Map _mapTypeToMapTypeId = { 4: 'hybrid', }; +// Builds the raw map options map from a [MapConfiguration]. +// +// This mirrors the platform interface's `jsonForMapConfiguration` helper +// (which is not exported), plus [MapConfiguration.colorScheme]. +// +// Intentionally omitted: +// * `cloudMapId` — deprecated alias for `mapId`, which is already serialized. +// * `markerType` — only relevant to advanced markers, which are not supported +// on Tizen. +Map _mapOptionsFromConfiguration(MapConfiguration config) { + final EdgeInsets? padding = config.padding; + return { + if (config.compassEnabled != null) 'compassEnabled': config.compassEnabled, + if (config.mapToolbarEnabled != null) + 'mapToolbarEnabled': config.mapToolbarEnabled, + if (config.cameraTargetBounds != null) + 'cameraTargetBounds': config.cameraTargetBounds!.toJson(), + if (config.mapType != null) 'mapType': config.mapType!.index, + if (config.minMaxZoomPreference != null) + 'minMaxZoomPreference': config.minMaxZoomPreference!.toJson(), + if (config.rotateGesturesEnabled != null) + 'rotateGesturesEnabled': config.rotateGesturesEnabled, + if (config.scrollGesturesEnabled != null) + 'scrollGesturesEnabled': config.scrollGesturesEnabled, + if (config.tiltGesturesEnabled != null) + 'tiltGesturesEnabled': config.tiltGesturesEnabled, + if (config.zoomControlsEnabled != null) + 'zoomControlsEnabled': config.zoomControlsEnabled, + if (config.zoomGesturesEnabled != null) + 'zoomGesturesEnabled': config.zoomGesturesEnabled, + if (config.liteModeEnabled != null) + 'liteModeEnabled': config.liteModeEnabled, + if (config.trackCameraPosition != null) + 'trackCameraPosition': config.trackCameraPosition, + if (config.myLocationEnabled != null) + 'myLocationEnabled': config.myLocationEnabled, + if (config.myLocationButtonEnabled != null) + 'myLocationButtonEnabled': config.myLocationButtonEnabled, + if (padding != null) + 'padding': [ + padding.top, + padding.left, + padding.bottom, + padding.right, + ], + if (config.indoorViewEnabled != null) + 'indoorEnabled': config.indoorViewEnabled, + if (config.trafficEnabled != null) 'trafficEnabled': config.trafficEnabled, + if (config.buildingsEnabled != null) + 'buildingsEnabled': config.buildingsEnabled, + if (config.mapId != null) 'mapId': config.mapId, + if (config.style != null) 'style': config.style, + if (config.colorScheme != null) 'colorScheme': config.colorScheme!.index, + }; +} + String? _getCameraBounds(dynamic option) { if (option is! List || option.first == null) { return null; @@ -90,9 +146,33 @@ String _rawOptionsToString(Map rawOptions) { options += ", gestureHandling: 'auto'"; } + final String? colorScheme = _colorSchemeToJs(rawOptions['colorScheme']); + if (colorScheme != null) { + options += ', colorScheme: $colorScheme'; + } + return options; } +// Maps a serialized MapColorScheme value from the platform interface to the +// JavaScript google.maps.ColorScheme enum. +String? _colorSchemeToJs(Object? value) { + if (value is! int) { + return null; + } + // The platform interface serializes MapColorScheme as its enum index: + // 0 = light, 1 = dark, 2 = followSystem. + switch (value) { + case 0: + return 'google.maps.ColorScheme.LIGHT'; + case 1: + return 'google.maps.ColorScheme.DARK'; + case 2: + return 'google.maps.ColorScheme.FOLLOW_SYSTEM'; + } + return null; +} + String _applyInitialPosition(CameraPosition initialPosition, String options) { options += ', zoom: ${initialPosition.zoom}'; options += @@ -400,3 +480,56 @@ util.GCircleOptions _circleOptionsFromCircle(Circle circle) { ..visible = circle.visible ..zIndex = circle.zIndex; } + +util.GGroundOverlayOptions? _groundOverlayOptionsFromGroundOverlay( + GroundOverlay groundOverlay, +) { + // The JS Maps GroundOverlay only supports bounds-based positioning. Skip + // position-only overlays — the platform interface allows the field but the + // JS API has no equivalent. + final LatLngBounds? bounds = groundOverlay.bounds; + if (bounds == null) { + debugPrint( + 'GroundOverlay ${groundOverlay.groundOverlayId.value} skipped: ' + 'the Google Maps JavaScript API only supports bounds-based ' + 'ground overlays.', + ); + return null; + } + final String? imageUrl = _imageUrlFromMapBitmap(groundOverlay.image); + if (imageUrl == null) { + debugPrint( + 'GroundOverlay ${groundOverlay.groundOverlayId.value} skipped: ' + 'unsupported image source.', + ); + return null; + } + return util.GGroundOverlayOptions() + ..url = "'$imageUrl'" + ..bounds = '{south:${bounds.southwest.latitude},' + ' west:${bounds.southwest.longitude},' + ' north:${bounds.northeast.latitude},' + ' east:${bounds.northeast.longitude}}' + ..clickable = groundOverlay.clickable + ..opacity = 1.0 - groundOverlay.transparency + ..visible = groundOverlay.visible; +} + +String? _imageUrlFromMapBitmap(MapBitmap bitmap) { + final List iconConfig = bitmap.toJson() as List; + if (iconConfig.isEmpty) { + return null; + } + if (iconConfig[0] == 'asset' && iconConfig.length >= 2) { + final Map assetConfig = + iconConfig[1]! as Map; + return '../${assetConfig['assetName']}'; + } + if (iconConfig[0] == 'bytes' && iconConfig.length >= 2) { + final Map assetConfig = + iconConfig[1]! as Map; + final List bytes = assetConfig['byteData']! as List; + return 'data:image/png;base64,${base64Encode(bytes)}'; + } + return null; +} diff --git a/packages/google_maps_flutter/lib/src/google_maps_controller.dart b/packages/google_maps_flutter/lib/src/google_maps_controller.dart index ef5ec149a..53e7c4ab8 100644 --- a/packages/google_maps_flutter/lib/src/google_maps_controller.dart +++ b/packages/google_maps_flutter/lib/src/google_maps_controller.dart @@ -22,6 +22,7 @@ class GoogleMapsController { Set polylines = const {}, Set circles = const {}, Set clusterManagers = const {}, + Set groundOverlays = const {}, Map mapOptions = const {}, }) : _mapId = mapId, _streamController = streamController, @@ -31,6 +32,7 @@ class GoogleMapsController { _polylines = polylines, _circles = circles, _clusterManagers = clusterManagers, + _groundOverlays = groundOverlays, _rawMapOptions = mapOptions { _circlesController = CirclesController(stream: _streamController); _polygonsController = PolygonsController(stream: _streamController); @@ -42,6 +44,9 @@ class GoogleMapsController { stream: _streamController, clusterManagersController: _clusterManagersController!, ); + _groundOverlaysController = GroundOverlaysController( + stream: _streamController, + ); } // The internal ID of the map. Used to broadcast events, DOM IDs and everything where a unique ID is needed. @@ -55,6 +60,7 @@ class GoogleMapsController { final Set _polylines; final Set _circles; final Set _clusterManagers; + final Set _groundOverlays; final Completer _pageFinishedCompleter = Completer(); WebViewWidget? _webview; // The raw options passed by the user, before converting to maps. @@ -159,6 +165,10 @@ class GoogleMapsController { ) ..addJavaScriptChannel('PolygonClick', onMessageReceived: _onPolygonClick) ..addJavaScriptChannel('CircleClick', onMessageReceived: _onCircleClick) + ..addJavaScriptChannel( + 'GroundOverlayClick', + onMessageReceived: _onGroundOverlayClick, + ) ..loadFile(path); _webview = WebViewWidget(controller: controller); @@ -220,6 +230,7 @@ class GoogleMapsController { PolylinesController? _polylinesController; MarkersController? _markersController; ClusterManagersController? _clusterManagersController; + GroundOverlaysController? _groundOverlaysController; // Keeps track if _attachGeometryControllers has been called or not. bool _controllersBoundToMap = false; @@ -460,6 +471,24 @@ class GoogleMapsController { } } + void _onGroundOverlayClick(JavaScriptMessage message) { + try { + final dynamic id = json.decode(message.message); + if (_groundOverlaysController != null && id is int) { + final GroundOverlayId? groundOverlayId = + _groundOverlaysController!._idToGroundOverlayId[id]; + final GroundOverlayController? groundOverlay = + _groundOverlaysController! + ._groundOverlayIdToController[groundOverlayId]; + if (groundOverlay?.tapEvent != null) { + groundOverlay?.tapEvent!(); + } + } + } catch (e) { + debugPrint('JavaScript Error: $e'); + } + } + /// Initializes the map from the stored `rawOptions`. /// /// This is called by the [GoogleMapsPlugin.init] method when appropriate. @@ -478,6 +507,7 @@ class GoogleMapsController { circles: _circles, polygons: _polygons, polylines: _polylines, + groundOverlays: _groundOverlays, ); _initClustering(_clusterManagers); @@ -509,12 +539,17 @@ class GoogleMapsController { _clusterManagersController != null, 'Cannot attach a map to a null ClusterManagersController instance.', ); + assert( + _groundOverlaysController != null, + 'Cannot attach a map to a null GroundOverlaysController instance.', + ); _circlesController!.bindToMap(_mapId, _webview!); _polygonsController!.bindToMap(_mapId, _webview!); _polylinesController!.bindToMap(_mapId, _webview!); _markersController!.bindToMap(_mapId, _webview!); _clusterManagersController!.bindToMap(_mapId, _webview!); + _groundOverlaysController!.bindToMap(_mapId, _webview!); util.webController = controller; _controllersBoundToMap = true; @@ -530,6 +565,7 @@ class GoogleMapsController { Set circles = const {}, Set polygons = const {}, Set polylines = const {}, + Set groundOverlays = const {}, }) { assert( _controllersBoundToMap, @@ -543,6 +579,7 @@ class GoogleMapsController { _circlesController!.addCircles(circles); _polygonsController!.addPolygons(polygons); _polylinesController!.addPolylines(polylines); + _groundOverlaysController!.addGroundOverlays(groundOverlays); } // Merges new options coming from the plugin into the _rawMapOptions map. @@ -819,6 +856,21 @@ class GoogleMapsController { ); } + /// Applies [GroundOverlayUpdates] to the currently managed ground overlays. + void updateGroundOverlays(GroundOverlayUpdates updates) { + assert( + _groundOverlaysController != null, + 'Cannot update ground overlays after dispose().', + ); + _groundOverlaysController?.addGroundOverlays(updates.groundOverlaysToAdd); + _groundOverlaysController?.changeGroundOverlays( + updates.groundOverlaysToChange, + ); + _groundOverlaysController?.removeGroundOverlays( + updates.groundOverlayIdsToRemove, + ); + } + /// Shows the [InfoWindow] of the marker identified by its [MarkerId]. void showInfoWindow(MarkerId markerId) { assert( @@ -855,6 +907,7 @@ class GoogleMapsController { _polylinesController = null; _markersController = null; _clusterManagersController = null; + _groundOverlaysController = null; _streamController.close(); } } diff --git a/packages/google_maps_flutter/lib/src/google_maps_flutter_tizen.dart b/packages/google_maps_flutter/lib/src/google_maps_flutter_tizen.dart index 57df7ad49..9172efea7 100644 --- a/packages/google_maps_flutter/lib/src/google_maps_flutter_tizen.dart +++ b/packages/google_maps_flutter/lib/src/google_maps_flutter_tizen.dart @@ -119,6 +119,14 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { _map(mapId).updateClusterManagers(clusterManagerUpdates); } + @override + Future updateGroundOverlays( + GroundOverlayUpdates groundOverlayUpdates, { + required int mapId, + }) async { + _map(mapId).updateGroundOverlays(groundOverlayUpdates); + } + @override Future clearTileCache( TileOverlayId tileOverlayId, { @@ -300,6 +308,11 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { return _events(mapId).whereType(); } + @override + Stream onGroundOverlayTap({required int mapId}) { + return _events(mapId).whereType(); + } + /// Disposes of the current map. It can't be used afterwards! @override void dispose({required int mapId}) { @@ -307,6 +320,28 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { _mapById.remove(mapId); } + @override + Widget buildViewWithConfiguration( + int creationId, + PlatformViewCreatedCallback onPlatformViewCreated, { + required MapWidgetConfiguration widgetConfiguration, + MapConfiguration mapConfiguration = const MapConfiguration(), + MapObjects mapObjects = const MapObjects(), + }) { + return _buildView( + creationId, + onPlatformViewCreated, + initialCameraPosition: widgetConfiguration.initialCameraPosition, + markers: mapObjects.markers, + polygons: mapObjects.polygons, + polylines: mapObjects.polylines, + circles: mapObjects.circles, + groundOverlays: mapObjects.groundOverlays, + gestureRecognizers: widgetConfiguration.gestureRecognizers, + mapOptions: _mapOptionsFromConfiguration(mapConfiguration), + ); + } + @override Widget buildView( int creationId, @@ -320,6 +355,32 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { Set>? gestureRecognizers = const >{}, Map mapOptions = const {}, + }) { + return _buildView( + creationId, + onPlatformViewCreated, + initialCameraPosition: initialCameraPosition, + markers: markers, + polygons: polygons, + polylines: polylines, + circles: circles, + gestureRecognizers: gestureRecognizers, + mapOptions: mapOptions, + ); + } + + Widget _buildView( + int creationId, + PlatformViewCreatedCallback onPlatformViewCreated, { + required CameraPosition initialCameraPosition, + Set markers = const {}, + Set polygons = const {}, + Set polylines = const {}, + Set circles = const {}, + Set groundOverlays = const {}, + Set>? gestureRecognizers = + const >{}, + Map mapOptions = const {}, }) { // Bail fast if we've already rendered this map ID... if (_mapById[creationId]?.webview != null) { @@ -337,6 +398,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { polygons: polygons, polylines: polylines, circles: circles, + groundOverlays: groundOverlays, mapOptions: mapOptions, )..init(); // Initialize the controller diff --git a/packages/google_maps_flutter/lib/src/ground_overlay.dart b/packages/google_maps_flutter/lib/src/ground_overlay.dart new file mode 100644 index 000000000..5b678236f --- /dev/null +++ b/packages/google_maps_flutter/lib/src/ground_overlay.dart @@ -0,0 +1,51 @@ +// Copyright 2021 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2013 The Flutter 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_tizen.dart'; + +/// The `GroundOverlayController` class wraps a [GGroundOverlay] and its +/// `onTap` behavior. +class GroundOverlayController { + /// Creates a `GroundOverlayController` that wraps a [GGroundOverlay] object + /// and its `onTap` behavior. + GroundOverlayController({ + required util.GGroundOverlay groundOverlay, + ui.VoidCallback? onTap, + WebViewController? controller, + }) : _groundOverlay = groundOverlay, + tapEvent = onTap { + _addGroundOverlayEvent(controller); + } + + util.GGroundOverlay? _groundOverlay; + + /// Ground overlay component's tap event. + ui.VoidCallback? tapEvent; + + Future _addGroundOverlayEvent(WebViewController? controller) async { + final String command = + "$_groundOverlay.addListener('click', (event) => GroundOverlayClick.postMessage(JSON.stringify(${_groundOverlay?.id})));"; + await controller!.runJavaScript(command); + } + + /// Updates the options of the wrapped [GGroundOverlay] object. + /// + /// This cannot be called after [remove]. + void update(util.GGroundOverlayOptions options) { + assert( + _groundOverlay != null, + 'Cannot `update` GroundOverlay after calling `remove`.', + ); + _groundOverlay!.options = options; + } + + /// Disposes of the currently wrapped [GGroundOverlay]. + void remove() { + if (_groundOverlay != null) { + _groundOverlay!.map = null; + _groundOverlay = null; + } + } +} diff --git a/packages/google_maps_flutter/lib/src/ground_overlays.dart b/packages/google_maps_flutter/lib/src/ground_overlays.dart new file mode 100644 index 000000000..d663aab57 --- /dev/null +++ b/packages/google_maps_flutter/lib/src/ground_overlays.dart @@ -0,0 +1,93 @@ +// Copyright 2021 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2013 The Flutter 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_tizen.dart'; + +/// This class manages a set of [GroundOverlayController]s associated to a +/// [GoogleMapController]. +class GroundOverlaysController extends GeometryController { + /// Initializes the cache. The [StreamController] comes from the + /// [GoogleMapController], and is shared with other controllers. + GroundOverlaysController({ + required StreamController> stream, + }) : _streamController = stream, + _groundOverlayIdToController = + {}, + _idToGroundOverlayId = {}; + + // A cache of [GroundOverlayController]s indexed by their [GroundOverlayId]. + final Map + _groundOverlayIdToController; + final Map _idToGroundOverlayId; + + // The stream over which ground overlays broadcast events. + final StreamController> _streamController; + + /// Adds a set of [GroundOverlay] objects to the cache. + /// + /// Wraps each [GroundOverlay] into its corresponding + /// [GroundOverlayController]. + void addGroundOverlays(Set groundOverlaysToAdd) { + groundOverlaysToAdd.forEach(_addGroundOverlay); + } + + void _addGroundOverlay(GroundOverlay? groundOverlay) { + if (groundOverlay == null) { + return; + } + + final util.GGroundOverlayOptions? populationOptions = + _groundOverlayOptionsFromGroundOverlay(groundOverlay); + if (populationOptions == null) { + return; + } + + final util.GGroundOverlay gGroundOverlay = util.GGroundOverlay( + populationOptions, + ); + final GroundOverlayController controller = GroundOverlayController( + groundOverlay: gGroundOverlay, + onTap: () { + _onGroundOverlayTap(groundOverlay.groundOverlayId); + }, + controller: util.webController, + ); + _idToGroundOverlayId[gGroundOverlay.id] = groundOverlay.groundOverlayId; + _groundOverlayIdToController[groundOverlay.groundOverlayId] = controller; + } + + /// Updates a set of [GroundOverlay] objects with new options. + void changeGroundOverlays(Set groundOverlaysToChange) { + groundOverlaysToChange.forEach(_changeGroundOverlay); + } + + void _changeGroundOverlay(GroundOverlay groundOverlay) { + final GroundOverlayController? groundOverlayController = + _groundOverlayIdToController[groundOverlay.groundOverlayId]; + final util.GGroundOverlayOptions? options = + _groundOverlayOptionsFromGroundOverlay(groundOverlay); + if (options == null) { + return; + } + groundOverlayController?.update(options); + } + + /// Removes a set of [GroundOverlayId]s from the cache. + void removeGroundOverlays(Set groundOverlayIdsToRemove) { + groundOverlayIdsToRemove.forEach(_removeGroundOverlay); + } + + void _removeGroundOverlay(GroundOverlayId groundOverlayId) { + final GroundOverlayController? groundOverlayController = + _groundOverlayIdToController[groundOverlayId]; + groundOverlayController?.remove(); + _groundOverlayIdToController.remove(groundOverlayId); + } + + bool _onGroundOverlayTap(GroundOverlayId groundOverlayId) { + _streamController.add(GroundOverlayTapEvent(mapId, groundOverlayId)); + return false; + } +} diff --git a/packages/google_maps_flutter/lib/src/util.dart b/packages/google_maps_flutter/lib/src/util.dart index c49b4553a..4e8b7fdc7 100644 --- a/packages/google_maps_flutter/lib/src/util.dart +++ b/packages/google_maps_flutter/lib/src/util.dart @@ -709,6 +709,100 @@ class GMarkerClustererOptions { } } +/// This class represents an image overlay drawn on the map and oriented +/// against the Earth's surface. +class GGroundOverlay { + /// GGroundOverlay Constructor. + GGroundOverlay([GGroundOverlayOptions? opts]) + : id = _gid++, + _options = opts { + _createGroundOverlay(opts); + } + + Future _createGroundOverlay(GGroundOverlayOptions? opts) async { + final String url = opts?.url ?? "''"; + final String bounds = opts?.bounds ?? '{}'; + final String command = + 'var ${toString()} = new google.maps.GroundOverlay($url, $bounds, $opts);' + ' ${toString()}.id = $id;'; + await webController!.runJavaScript(command); + } + + /// GGroundOverlay id. + final int id; + static int _gid = 0; + GGroundOverlayOptions? _options; + + /// Caches GGroundOverlay's options. + GGroundOverlayOptions? get options => _options; + + @override + String toString() { + return 'groundOverlay$id'; + } + + /// Sets map. + set map(Object? /*GMap?*/ map) => _setMap(map); + + /// Sets ground overlay options. + /// + /// The JavaScript Maps GroundOverlay only exposes `setMap` and `setOpacity`, + /// so only [GGroundOverlayOptions.opacity] and [GGroundOverlayOptions.visible] + /// are reflected here. Changes to `url`, `bounds`, or `clickable` require a + /// new [GGroundOverlay] instance. + set options(GGroundOverlayOptions? options) { + if (_options == null || options == null) { + _options = options; + return; + } + if (_options!.opacity != options.opacity) { + _options!.opacity = options.opacity; + _setOpacity(options.opacity); + } + if (_options!.visible != options.visible) { + _options!.visible = options.visible; + _setMap(options.visible == false ? null : 'map'); + } + } + + Future _setMap(Object? map) async { + await callMethod(this, 'setMap', [map]); + } + + Future _setOpacity(num? opacity) async { + await callMethod(this, 'setOpacity', [opacity]); + } +} + +/// This class defines GroundOverlay's options. +class GGroundOverlayOptions { + /// GGroundOverlayOptions Constructor. + GGroundOverlayOptions(); + + /// The image URL passed to the JS GroundOverlay constructor, already quoted + /// as a JS string literal (e.g. `'data:image/png;base64,...'`). + String? url; + + /// The bounds passed to the JS GroundOverlay constructor as a JS object + /// literal: `{south:.., west:.., north:.., east:..}`. + String? bounds; + + /// Whether click events are handled. + bool? clickable; + + /// Opacity (0.0 transparent through 1.0 opaque). + num? opacity; + + /// Whether this ground overlay is visible on the map. + bool? visible; + + @override + String toString() { + return '{clickable:$clickable, opacity:$opacity,' + ' map: ${visible == false ? 'null' : 'map'}}'; + } +} + /// Returns webview controller instance WebViewController? webController; diff --git a/packages/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/pubspec.yaml index 30864ac34..75b2e7220 100644 --- a/packages/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter_tizen description: Tizen implementation of the google_maps_flutter plugin. homepage: https://github.com/flutter-tizen/plugins repository: https://github.com/flutter-tizen/plugins/tree/master/packages/google_maps_flutter -version: 0.1.13 +version: 0.1.14 environment: sdk: ">=3.4.0 <4.0.0" @@ -17,7 +17,7 @@ flutter: dependencies: flutter: sdk: flutter - google_maps_flutter_platform_interface: ^2.10.0 + google_maps_flutter_platform_interface: ^2.15.0 stream_transform: ^2.0.0 webview_flutter: ^4.10.0 webview_flutter_lwe: ^0.3.7 From dc79d07789ee9672d2db3895a652ebd0335894e2 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Mon, 11 May 2026 20:39:02 +0900 Subject: [PATCH 2/4] [google_maps_flutter] Fix ground overlay change/remove handling --- .../lib/src/ground_overlays.dart | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/google_maps_flutter/lib/src/ground_overlays.dart b/packages/google_maps_flutter/lib/src/ground_overlays.dart index d663aab57..470a50021 100644 --- a/packages/google_maps_flutter/lib/src/ground_overlays.dart +++ b/packages/google_maps_flutter/lib/src/ground_overlays.dart @@ -68,10 +68,24 @@ class GroundOverlaysController extends GeometryController { _groundOverlayIdToController[groundOverlay.groundOverlayId]; final util.GGroundOverlayOptions? options = _groundOverlayOptionsFromGroundOverlay(groundOverlay); - if (options == null) { + if (groundOverlayController == null || options == null) { return; } - groundOverlayController?.update(options); + // The JS Maps GroundOverlay only exposes setMap and setOpacity, so + // url, bounds, and clickable cannot be mutated in place. Recreate the + // overlay when any of these change so updates from updateGroundOverlays + // are not silently dropped. + final util.GGroundOverlayOptions? current = + groundOverlayController._groundOverlay?.options; + if (current == null || + current.url != options.url || + current.bounds != options.bounds || + current.clickable != options.clickable) { + _removeGroundOverlay(groundOverlay.groundOverlayId); + _addGroundOverlay(groundOverlay); + return; + } + groundOverlayController.update(options); } /// Removes a set of [GroundOverlayId]s from the cache. @@ -82,7 +96,10 @@ class GroundOverlaysController extends GeometryController { void _removeGroundOverlay(GroundOverlayId groundOverlayId) { final GroundOverlayController? groundOverlayController = _groundOverlayIdToController[groundOverlayId]; - groundOverlayController?.remove(); + if (groundOverlayController != null) { + _idToGroundOverlayId.remove(groundOverlayController._groundOverlay?.id); + groundOverlayController.remove(); + } _groundOverlayIdToController.remove(groundOverlayId); } From c903ae85ba2f3d83063f5d53c72dc16aad6d56c4 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Mon, 11 May 2026 20:44:47 +0900 Subject: [PATCH 3/4] ++ --- packages/google_maps_flutter/CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/CHANGELOG.md index cb7a5262c..c14478638 100644 --- a/packages/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/CHANGELOG.md @@ -1,7 +1,3 @@ -## NEXT - -* Update code format. - ## 0.1.14 * Update google_maps_flutter to 2.16.0. @@ -9,6 +5,7 @@ * Add support for ground overlays (bounds-based) using `google.maps.GroundOverlay`. * Forward the `colorScheme` map option to `google.maps.ColorScheme`. +* * Update code format. ## 0.1.13 From da9fc2096de70ae2e03222f5e0b5ecd5a3612e46 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Wed, 13 May 2026 13:54:40 +0900 Subject: [PATCH 4/4] Update packages/google_maps_flutter/lib/src/ground_overlay.dart Co-authored-by: Seungsoo Lee --- packages/google_maps_flutter/lib/src/ground_overlay.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google_maps_flutter/lib/src/ground_overlay.dart b/packages/google_maps_flutter/lib/src/ground_overlay.dart index 5b678236f..94d4a7f59 100644 --- a/packages/google_maps_flutter/lib/src/ground_overlay.dart +++ b/packages/google_maps_flutter/lib/src/ground_overlay.dart @@ -1,4 +1,4 @@ -// Copyright 2021 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2026 Samsung Electronics Co., Ltd. All rights reserved. // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file.