diff --git a/example/lib/pages/camera.dart b/example/lib/pages/camera.dart index 58547cf..3e0036c 100644 --- a/example/lib/pages/camera.dart +++ b/example/lib/pages/camera.dart @@ -15,8 +15,10 @@ // ignore_for_file: public_member_api_docs import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:google_navigation_flutter/google_navigation_flutter.dart'; + import '../utils/utils.dart'; import '../widgets/widgets.dart'; @@ -52,74 +54,106 @@ class _CameraPageState extends ExamplePageState { } Future _startNavigation() async { - showMessage('Starting navigation.'); - if (!await GoogleMapsNavigator.areTermsAccepted()) { - await GoogleMapsNavigator.showTermsAndConditionsDialog( - 'test_title', - 'test_company_name', + try { + showMessage('Starting navigation.'); + + if (!await GoogleMapsNavigator.areTermsAccepted()) { + final bool accepted = + await GoogleMapsNavigator.showTermsAndConditionsDialog( + 'test_title', + 'test_company_name', + ); + if (!accepted) { + showMessage('Terms not accepted. Navigation cancelled.'); + return; + } + } + + await GoogleMapsNavigator.initializeNavigationSession(); + + await GoogleMapsNavigator.simulator.setUserLocation( + const LatLng(latitude: 37.528560, longitude: -122.361996), ); - } - await GoogleMapsNavigator.initializeNavigationSession(); - - /// Simulate location. - await GoogleMapsNavigator.simulator.setUserLocation( - const LatLng(latitude: 37.528560, longitude: -122.361996)); - - final Destinations msg = Destinations( - waypoints: [ - NavigationWaypoint.withLatLngTarget( - title: 'Grace Cathedral', - target: const LatLng( - latitude: 37.791957, - longitude: -122.412529, - ), - ), - ], - displayOptions: NavigationDisplayOptions(showDestinationMarkers: false), - ); - setState(() {}); - final NavigationRouteStatus status = - await GoogleMapsNavigator.setDestinations(msg); - - if (status == NavigationRouteStatus.statusOk) { - await GoogleMapsNavigator.startGuidance(); - await GoogleMapsNavigator.simulator.simulateLocationsAlongExistingRoute(); - await _navigationViewController - .followMyLocation(CameraPerspective.tilted); + final Destinations msg = Destinations( + waypoints: [ + NavigationWaypoint.withLatLngTarget( + title: 'Grace Cathedral', + target: const LatLng( + latitude: 37.791957, + longitude: -122.412529, + ), + ), + ], + displayOptions: NavigationDisplayOptions(showDestinationMarkers: false), + ); + final NavigationRouteStatus status = + await GoogleMapsNavigator.setDestinations(msg); + + if (status == NavigationRouteStatus.statusOk) { + await GoogleMapsNavigator.startGuidance(); + await GoogleMapsNavigator.simulator + .simulateLocationsAlongExistingRoute(); + await _navigationViewController + .followMyLocation(CameraPerspective.tilted); + + setState(() { + _navigationRunning = true; + }); + } else { + showMessage('Starting navigation failed: Invalid route status'); + } + } catch (e) { + showMessage('Navigation error: ${e.toString()}'); setState(() { - _navigationRunning = true; + _navigationRunning = false; }); - } else { - showMessage('Starting navigation failed.'); } } Future _stopNavigation() async { - if (_navigationRunning) { - await GoogleMapsNavigator.cleanup(); + if (!_navigationRunning) return; + try { + await GoogleMapsNavigator.cleanup(); setState(() { _navigationRunning = false; }); + } catch (e) { + showMessage('Error stopping navigation: ${e.toString()}'); } } @override void dispose() { - if (_navigationRunning) { - GoogleMapsNavigator.cleanup(); + try { + if (_navigationRunning) { + GoogleMapsNavigator.cleanup(); + } + } catch (e) { + debugPrint('Error during disposal: ${e.toString()}'); } super.dispose(); } void calculateFocusCenter() { - final double screenWidth = MediaQuery.of(context).size.width; - final double screenHeight = MediaQuery.of(context).size.height; - final double appBarHeight = AppBar().preferredSize.height; - _focusX = screenWidth / 2; - _focusY = (screenHeight - appBarHeight) / 2; + try { + final MediaQueryData mediaQuery = MediaQuery.of(context); + final double screenWidth = mediaQuery.size.width; + final double screenHeight = mediaQuery.size.height; + final double appBarHeight = AppBar().preferredSize.height; + final double statusBarHeight = mediaQuery.padding.top; + + _focusX = screenWidth / 2; + // Account for status bar and app bar in Y calculation + _focusY = (screenHeight - (appBarHeight + statusBarHeight)) / 2; + } catch (e) { + // Fallback to reasonable defaults if calculation fails + _focusX = 0; + _focusY = 0; + debugPrint('Error calculating focus center: ${e.toString()}'); + } } @override @@ -238,6 +272,24 @@ class _CameraPageState extends ExamplePageState { setState(() {}); } + Future _moveCameraWithAnimation(CameraUpdate cameraUpdate) async { + try { + if (_animationsEnabled) { + await _navigationViewController.animateCamera( + cameraUpdate, + duration: _animationDuration != null + ? Duration(milliseconds: _animationDuration!) + : null, + onFinished: showAnimationFinishedMessage, + ); + } else { + await _navigationViewController.moveCamera(cameraUpdate); + } + } catch (e) { + showMessage('Camera movement error: ${e.toString()}'); + } + } + @override Widget buildOverlayContent(BuildContext context) { final ButtonStyle threeButtonRowStyle = Theme.of(context) @@ -310,22 +362,15 @@ class _CameraPageState extends ExamplePageState { ElevatedButton( onPressed: () { final CameraUpdate cameraUpdate = - CameraUpdate.newCameraPosition(const CameraPosition( - bearing: 270.0, - target: LatLng(latitude: 51.5160895, longitude: -0.1294527), - tilt: 30.0, - zoom: 17.0, - )); - if (_animationsEnabled) { - _navigationViewController.animateCamera(cameraUpdate, - duration: _animationDuration != null - ? Duration(milliseconds: _animationDuration!) - : null, - onFinished: (bool success) => - showAnimationFinishedMessage(success)); - } else { - _navigationViewController.moveCamera(cameraUpdate); - } + CameraUpdate.newCameraPosition( + const CameraPosition( + bearing: 270.0, + target: LatLng(latitude: 51.5160895, longitude: -0.1294527), + tilt: 30.0, + zoom: 17.0, + ), + ); + _moveCameraWithAnimation(cameraUpdate); }, child: const Text('newCameraPosition'), ), @@ -333,16 +378,7 @@ class _CameraPageState extends ExamplePageState { onPressed: () { final CameraUpdate cameraUpdate = CameraUpdate.scrollBy(150.0, -225.0); - if (_animationsEnabled) { - _navigationViewController.animateCamera(cameraUpdate, - duration: _animationDuration != null - ? Duration(milliseconds: _animationDuration!) - : null, - onFinished: (bool success) => - showAnimationFinishedMessage(success)); - } else { - _navigationViewController.moveCamera(cameraUpdate); - } + _moveCameraWithAnimation(cameraUpdate); }, child: const Text('scrollBy'), ), @@ -357,16 +393,7 @@ class _CameraPageState extends ExamplePageState { final CameraUpdate cameraUpdate = CameraUpdate.newLatLng( const LatLng(latitude: 56.1725505, longitude: 10.1850512), ); - if (_animationsEnabled) { - _navigationViewController.animateCamera(cameraUpdate, - duration: _animationDuration != null - ? Duration(milliseconds: _animationDuration!) - : null, - onFinished: (bool success) => - showAnimationFinishedMessage(success)); - } else { - _navigationViewController.moveCamera(cameraUpdate); - } + _moveCameraWithAnimation(cameraUpdate); }, child: const Text('newLatLng'), ), @@ -381,16 +408,7 @@ class _CameraPageState extends ExamplePageState { ), padding: 10.0, ); - if (_animationsEnabled) { - _navigationViewController.animateCamera(cameraUpdate, - duration: _animationDuration != null - ? Duration(milliseconds: _animationDuration!) - : null, - onFinished: (bool success) => - showAnimationFinishedMessage(success)); - } else { - _navigationViewController.moveCamera(cameraUpdate); - } + _moveCameraWithAnimation(cameraUpdate); }, child: const Text('newLatLngBounds'), ), @@ -401,16 +419,7 @@ class _CameraPageState extends ExamplePageState { final CameraUpdate cameraUpdate = CameraUpdate.newLatLngZoom( const LatLng(latitude: 37.4231613, longitude: -122.087159), 11.0); - if (_animationsEnabled) { - _navigationViewController.animateCamera(cameraUpdate, - duration: _animationDuration != null - ? Duration(milliseconds: _animationDuration!) - : null, - onFinished: (bool success) => - showAnimationFinishedMessage(success)); - } else { - _navigationViewController.moveCamera(cameraUpdate); - } + _moveCameraWithAnimation(cameraUpdate); }, child: const Text('newLatLngZoom'), ), @@ -422,16 +431,7 @@ class _CameraPageState extends ExamplePageState { ElevatedButton( onPressed: () { final CameraUpdate cameraUpdate = CameraUpdate.zoomBy(-0.5); - if (_animationsEnabled) { - _navigationViewController.animateCamera(cameraUpdate, - duration: _animationDuration != null - ? Duration(milliseconds: _animationDuration!) - : null, - onFinished: (bool success) => - showAnimationFinishedMessage(success)); - } else { - _navigationViewController.moveCamera(cameraUpdate); - } + _moveCameraWithAnimation(cameraUpdate); }, child: const Text('zoomBy'), ), @@ -441,16 +441,7 @@ class _CameraPageState extends ExamplePageState { 1.0, focus: Offset(_focusX, _focusY), ); - if (_animationsEnabled) { - _navigationViewController.animateCamera(cameraUpdate, - duration: _animationDuration != null - ? Duration(milliseconds: _animationDuration!) - : null, - onFinished: (bool success) => - showAnimationFinishedMessage(success)); - } else { - _navigationViewController.moveCamera(cameraUpdate); - } + _moveCameraWithAnimation(cameraUpdate); }, child: const Text('zoomBy with focus'), ), @@ -464,16 +455,7 @@ class _CameraPageState extends ExamplePageState { style: threeButtonRowStyle, onPressed: () { final CameraUpdate cameraUpdate = CameraUpdate.zoomIn(); - if (_animationsEnabled) { - _navigationViewController.animateCamera(cameraUpdate, - duration: _animationDuration != null - ? Duration(milliseconds: _animationDuration!) - : null, - onFinished: (bool success) => - showAnimationFinishedMessage(success)); - } else { - _navigationViewController.moveCamera(cameraUpdate); - } + _moveCameraWithAnimation(cameraUpdate); }, child: const Text('zoomIn'), ), @@ -481,16 +463,7 @@ class _CameraPageState extends ExamplePageState { style: threeButtonRowStyle, onPressed: () { final CameraUpdate cameraUpdate = CameraUpdate.zoomOut(); - if (_animationsEnabled) { - _navigationViewController.animateCamera(cameraUpdate, - duration: _animationDuration != null - ? Duration(milliseconds: _animationDuration!) - : null, - onFinished: (bool success) => - showAnimationFinishedMessage(success)); - } else { - _navigationViewController.moveCamera(cameraUpdate); - } + _moveCameraWithAnimation(cameraUpdate); }, child: const Text('zoomOut'), ), @@ -498,17 +471,7 @@ class _CameraPageState extends ExamplePageState { style: threeButtonRowStyle, onPressed: () { final CameraUpdate cameraUpdate = CameraUpdate.zoomTo(16.0); - - if (_animationsEnabled) { - _navigationViewController.animateCamera(cameraUpdate, - duration: _animationDuration != null - ? Duration(milliseconds: _animationDuration!) - : null, - onFinished: (bool success) => - showAnimationFinishedMessage(success)); - } else { - _navigationViewController.moveCamera(cameraUpdate); - } + _moveCameraWithAnimation(cameraUpdate); }, child: const Text('zoomTo'), ), diff --git a/example/lib/pages/markers.dart b/example/lib/pages/markers.dart index f937469..a1155d2 100644 --- a/example/lib/pages/markers.dart +++ b/example/lib/pages/markers.dart @@ -49,139 +49,207 @@ class _MarkersPageState extends ExamplePageState { Future addMarkerToMap() async { // Add a marker to the current camera position. - final CameraPosition position = - await _navigationViewController.getCameraPosition(); - final MarkerOptions options = MarkerOptions( + try { + final CameraPosition position = + await _navigationViewController.getCameraPosition(); + + final MarkerOptions options = MarkerOptions( position: position.target, - infoWindow: const InfoWindow(title: 'Name', snippet: 'Snippet here')); + infoWindow: const InfoWindow(title: 'Name', snippet: 'Snippet here'), + ); + + final List addedMarkers = + await _navigationViewController.addMarkers([options]); + + if (addedMarkers.isEmpty || addedMarkers.first == null) { + showMessage('Failed to add marker'); + return; + } - final List addedMarkers = - await _navigationViewController.addMarkers([options]); - if (addedMarkers.isNotEmpty) { - final Marker marker = addedMarkers.first!; setState(() { + final Marker marker = addedMarkers.first!; _markers.add(marker); - _markers = _markers + [marker]; _selectedMarker = marker; }); + } catch (e) { + showMessage('Error adding marker: ${e.toString()}'); } } Future _removeMarker() async { - await _navigationViewController.removeMarkers([_selectedMarker!]); - setState(() { - _markers.remove(_selectedMarker); - _selectedMarker = null; - }); + if (_selectedMarker == null) return; + + try { + await _navigationViewController.removeMarkers([_selectedMarker!]); + setState(() { + _markers.remove(_selectedMarker); + _selectedMarker = null; + }); + } catch (e) { + showMessage('Error removing marker: ${e.toString()}'); + } } Future clearMarkers() async { - await _navigationViewController.clearMarkers(); - setState(() { - _markers.clear(); - }); + try { + await _navigationViewController.clearMarkers(); + setState(() { + _markers.clear(); + _selectedMarker = null; + }); + } catch (e) { + showMessage('Error clearing markers: ${e.toString()}'); + } } Future _updateSelectedMarkerWithOptions(MarkerOptions options) async { - final Marker newMarker = _selectedMarker!.copyWith(options: options); + if (_selectedMarker == null) return; + + try { + final Marker newMarker = _selectedMarker!.copyWith(options: options); + final List markers = + await _navigationViewController.updateMarkers([newMarker]); + + final Marker? updatedMarker = markers.firstOrNull; + if (updatedMarker == null) { + showMessage('Failed to update marker'); + return; + } - final List markers = - await _navigationViewController.updateMarkers([newMarker]); - final Marker? marker = markers.firstOrNull; - if (marker != null) { setState(() { _markers = _markers .where((Marker element) => element != _selectedMarker) - .toList(); - _selectedMarker = marker; - _markers = _markers + [marker]; + .toList() + ..add(updatedMarker); + _selectedMarker = updatedMarker; }); + } catch (e) { + showMessage('Error updating marker: ${e.toString()}'); } } - Future _toggleVisibility() async { - final bool oldVisibility = _selectedMarker!.options.visible; + // Helper methods for marker property updates + Future _updateMarkerProperty({ + bool? visible, + bool? draggable, + bool? flat, + double? alpha, + double? zIndex, + }) async { + if (_selectedMarker == null) return; + + final MarkerOptions currentOptions = _selectedMarker!.options; + final MarkerOptions newOptions = currentOptions.copyWith( + visible: visible, + draggable: draggable, + flat: flat, + alpha: alpha, + zIndex: zIndex, + ); + + await _updateSelectedMarkerWithOptions(newOptions); + } - await _updateSelectedMarkerWithOptions( - _selectedMarker!.options.copyWith(visible: !oldVisibility)); + Future _toggleVisibility() async { + await _updateMarkerProperty( + visible: !_selectedMarker!.options.visible, + ); } Future _toggleDraggable() async { - final bool oldDraggable = _selectedMarker!.options.draggable; - await _updateSelectedMarkerWithOptions( - _selectedMarker!.options.copyWith(draggable: !oldDraggable)); + await _updateMarkerProperty( + draggable: !_selectedMarker!.options.draggable, + ); } Future _toggleFlat() async { - final bool oldFlat = _selectedMarker!.options.flat; - await _updateSelectedMarkerWithOptions( - _selectedMarker!.options.copyWith(flat: !oldFlat)); + await _updateMarkerProperty( + flat: !_selectedMarker!.options.flat, + ); } Future _setAlpha() async { final double oldAlpha = _selectedMarker!.options.alpha; - final double newAlpha = _alphas.elementAtOrNull( - _alphas.indexWhere((double e) => e == oldAlpha) + 1) ?? - _alphas[0]; - - await _updateSelectedMarkerWithOptions( - _selectedMarker!.options.copyWith(alpha: newAlpha)); + final double newAlpha = _getNextValue(_alphas, oldAlpha); + await _updateMarkerProperty(alpha: newAlpha); } Future _setZIndex() async { final double oldZIndex = _selectedMarker!.options.zIndex; - final double newZIndex = _zIndexes.elementAtOrNull( - _zIndexes.indexWhere((double e) => e == oldZIndex) + 1) ?? - _zIndexes[0]; + final double newZIndex = _getNextValue(_zIndexes, oldZIndex); + await _updateMarkerProperty(zIndex: newZIndex); + } - await _updateSelectedMarkerWithOptions( - _selectedMarker!.options.copyWith(zIndex: newZIndex)); + T _getNextValue(List values, T currentValue) { + final int currentIndex = values.indexOf(currentValue); + return values.elementAtOrNull(currentIndex + 1) ?? values.first; } Future _getOrCreateCustomImageFromAsset() async { - if (_registeredCustomIcon != null) { - // Custom image already registered. + try { + if (_registeredCustomIcon != null) { + return _registeredCustomIcon!; + } + + const AssetImage assetImage = AssetImage('assets/marker1.png'); + final ImageConfiguration configuration = + createLocalImageConfiguration(context); + + final AssetBundleImageKey assetBundleImageKey = + await assetImage.obtainKey(configuration); + + final ByteData imageBytes = + await rootBundle.load(assetBundleImageKey.name); + + _registeredCustomIcon = await registerBitmapImage( + bitmap: imageBytes, + imagePixelRatio: assetBundleImageKey.scale, + ); + return _registeredCustomIcon!; + } catch (e) { + showMessage('Error loading custom marker icon: ${e.toString()}'); + rethrow; } - - // Example how to load mipmapped bitmap for asset. - const AssetImage assetImage = AssetImage('assets/marker1.png'); - final ImageConfiguration configuration = - createLocalImageConfiguration(context); - final AssetBundleImageKey assetBundleImageKey = - await assetImage.obtainKey(configuration); - final double imagePixelRatio = assetBundleImageKey.scale; - final ByteData imageBytes = await rootBundle.load(assetBundleImageKey.name); - _registeredCustomIcon = await registerBitmapImage( - bitmap: imageBytes, imagePixelRatio: imagePixelRatio); - return _registeredCustomIcon!; } Future _unRegisterUnusedCustomImage() async { - if (_registeredCustomIcon != null) { - // Do not unregister marker image if it is still used by some marker. - if (_markers.any((Marker marker) => + try { + if (_registeredCustomIcon == null) return; + + // Check if image is still in use + final bool isImageInUse = _markers.any((Marker marker) => marker.options.icon.registeredImageId == - _registeredCustomIcon!.registeredImageId)) { - return; + _registeredCustomIcon!.registeredImageId); + + if (!isImageInUse) { + await unregisterImage(_registeredCustomIcon!); + _registeredCustomIcon = null; } - await unregisterImage(_registeredCustomIcon!); - _registeredCustomIcon = null; + } catch (e) { + showMessage('Error unregistering custom icon: ${e.toString()}'); } } Future _toggleCustomIcon() async { - assert(_selectedMarker != null, 'No marker selected'); - if (_selectedMarker!.options.icon.registeredImageId == null) { - final ImageDescriptor customMarkerIcon = - await _getOrCreateCustomImageFromAsset(); - await _updateSelectedMarkerWithOptions( - _selectedMarker!.options.copyWith(icon: customMarkerIcon)); - } else { - await _updateSelectedMarkerWithOptions(_selectedMarker!.options - .copyWith(icon: ImageDescriptor.defaultImage)); - await _unRegisterUnusedCustomImage(); + if (_selectedMarker == null) return; + + try { + if (_selectedMarker!.options.icon.registeredImageId == null) { + final ImageDescriptor customMarkerIcon = + await _getOrCreateCustomImageFromAsset(); + await _updateSelectedMarkerWithOptions( + _selectedMarker!.options.copyWith(icon: customMarkerIcon), + ); + } else { + await _updateSelectedMarkerWithOptions( + _selectedMarker!.options.copyWith(icon: ImageDescriptor.defaultImage), + ); + await _unRegisterUnusedCustomImage(); + } + } catch (e) { + showMessage('Error toggling custom icon: ${e.toString()}'); } } @@ -364,4 +432,14 @@ class _MarkersPageState extends ExamplePageState { ScaffoldMessenger.of(context).showSnackBar(snackBar); } } + + @override + void dispose() { + try { + _unRegisterUnusedCustomImage(); + } catch (e) { + debugPrint('Error during disposal: ${e.toString()}'); + } + super.dispose(); + } }