diff --git a/.gitignore b/.gitignore index 14fd0e15..0d4bb32f 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,7 @@ ServiceDefinitions.json xcuserdata/ .swiftpm/ .last_build_id +.build/ # Android local.properties diff --git a/README.md b/README.md index 15d5994a..8b9e4e3a 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ This repository contains a Flutter plugin that provides a [Google Navigation](ht | | Android | iOS | | ------------------------------- | ------------- | --------- | -| **Minimum mobile OS supported** | API level 23+ | iOS 16.0+ | +| **Minimum mobile OS supported** | API level 24+ | iOS 16.0+ | * A Flutter project * A Google Cloud project @@ -43,7 +43,7 @@ Set the `minSdk` in `android/app/build.gradle`: ```groovy android { defaultConfig { - minSdk 23 + minSdk 24 } } ``` @@ -199,6 +199,32 @@ This parameter has only an effect on Android. ``` +#### Using Map IDs +You can configure your map by providing a `mapId` parameter during map initialization. Map IDs are created in the [Google Cloud Console](https://console.cloud.google.com/google/maps-apis/studio/maps) and allow you to [enable various Google Maps Platform features](https://developers.google.com/maps/documentation/android-sdk/map-ids/mapid-over#features-available), such as cloud-based map styling. + +> [!NOTE] +> The `mapId` can only be set once during map initialization and cannot be changed afterwards. Both `GoogleMapsMapView` and `GoogleMapsNavigationView` support the `mapId` parameter. + +For `GoogleMapsMapView`: + +```dart +GoogleMapsMapView( + mapId: 'YOUR_MAP_ID', // Can only be set during initialization + ... +) +``` + +For `GoogleMapsNavigationView`: + +```dart +GoogleMapsNavigationView( + mapId: 'YOUR_MAP_ID', // Can only be set during initialization + ... +) +``` + +For more information about map IDs and how to create them, see the [Google Maps Platform documentation](https://developers.google.com/maps/documentation/get-map-id). + See the [example](./example) directory for a complete navigation sample app. ### Requesting and handling permissions diff --git a/android/build.gradle b/android/build.gradle index 5eafe88c..ad4af5ce 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -70,14 +70,14 @@ android { } defaultConfig { - minSdk = 23 + minSdk = 24 consumerProguardFiles 'proguard.txt' } dependencies { - implementation 'androidx.car.app:app:1.4.0' - implementation 'androidx.car.app:app-projected:1.4.0' - implementation 'com.google.android.libraries.navigation:navigation:6.2.2' + implementation 'androidx.car.app:app:1.7.0' + implementation 'androidx.car.app:app-projected:1.7.0' + implementation 'com.google.android.libraries.navigation:navigation:7.2.0' testImplementation 'org.jetbrains.kotlin:kotlin-test' testImplementation 'io.mockk:mockk:1.13.8' testImplementation 'junit:junit:4.13.2' diff --git a/android/src/main/kotlin/com/google/maps/flutter/navigation/Convert.kt b/android/src/main/kotlin/com/google/maps/flutter/navigation/Convert.kt index 7f6bac33..95ee3621 100644 --- a/android/src/main/kotlin/com/google/maps/flutter/navigation/Convert.kt +++ b/android/src/main/kotlin/com/google/maps/flutter/navigation/Convert.kt @@ -82,6 +82,7 @@ object Convert { options.minZoomPreference?.let { googleMapOptions.minZoomPreference(it.toFloat()) } options.maxZoomPreference?.let { googleMapOptions.maxZoomPreference(it.toFloat()) } googleMapOptions.zoomControlsEnabled(options.zoomControlsEnabled) + options.mapId?.let { googleMapOptions.mapId(it) } return MapOptions(googleMapOptions, options.padding) } @@ -310,8 +311,8 @@ object Convert { */ fun convertWaypointToDto(waypoint: Waypoint): NavigationWaypointDto { return NavigationWaypointDto( - waypoint.title, - convertLatLngToDto(waypoint.position), + waypoint.title ?: "", + waypoint.position?.let { convertLatLngToDto(it) }, waypoint.placeId, waypoint.preferSameSideOfRoad, waypoint.preferredHeading.takeIf { it != -1 }?.toLong(), @@ -362,6 +363,7 @@ object Convert { */ fun convertDisplayOptionsFromDto(displayOptions: NavigationDisplayOptionsDto): DisplayOptions { return DisplayOptions().apply { + // Only set if explicitly provided, otherwise SDK defaults are used. if (displayOptions.showDestinationMarkers != null) { this.hideDestinationMarkers(!displayOptions.showDestinationMarkers) } @@ -474,6 +476,7 @@ object Convert { Navigator.RouteStatus.OK -> RouteStatusDto.STATUS_OK Navigator.RouteStatus.QUOTA_CHECK_FAILED -> RouteStatusDto.QUOTA_CHECK_FAILED Navigator.RouteStatus.WAYPOINT_ERROR -> RouteStatusDto.WAYPOINT_ERROR + Navigator.RouteStatus.DUPLICATE_WAYPOINTS_ERROR -> RouteStatusDto.DUPLICATE_WAYPOINTS_ERROR } } @@ -802,17 +805,17 @@ object Convert { private fun convertNavInfoStepInfo(stepInfo: StepInfo): StepInfoDto { return StepInfoDto( - distanceFromPrevStepMeters = stepInfo.distanceFromPrevStepMeters.toLong(), - timeFromPrevStepSeconds = stepInfo.timeFromPrevStepSeconds.toLong(), + distanceFromPrevStepMeters = stepInfo.distanceFromPrevStepMeters?.toLong() ?: 0L, + timeFromPrevStepSeconds = stepInfo.timeFromPrevStepSeconds?.toLong() ?: 0L, drivingSide = convertDrivingSide(stepInfo.drivingSide), exitNumber = stepInfo.exitNumber, - fullInstructions = stepInfo.fullInstructionText, - fullRoadName = stepInfo.fullRoadName, - simpleRoadName = stepInfo.simpleRoadName, - roundaboutTurnNumber = stepInfo.roundaboutTurnNumber.toLong(), - stepNumber = stepInfo.stepNumber.toLong(), + fullInstructions = stepInfo.fullInstructionText ?: "", + fullRoadName = stepInfo.fullRoadName ?: "", + simpleRoadName = stepInfo.simpleRoadName ?: "", + roundaboutTurnNumber = stepInfo.roundaboutTurnNumber?.toLong() ?: 0L, + stepNumber = stepInfo.stepNumber?.toLong() ?: 0L, lanes = - stepInfo.lanes.map { lane -> + stepInfo.lanes?.map { lane -> LaneDto( laneDirections = lane.laneDirections().map { laneDirection -> diff --git a/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsBaseMapView.kt b/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsBaseMapView.kt index 5f790424..6038b3aa 100644 --- a/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsBaseMapView.kt +++ b/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsBaseMapView.kt @@ -530,13 +530,23 @@ abstract class GoogleMapsBaseMapView( return getMap().isTrafficEnabled } + fun isBuildingsEnabled(): Boolean { + return getMap().isBuildingsEnabled + } + + fun setBuildingsEnabled(enabled: Boolean) { + getMap().isBuildingsEnabled = enabled + } + fun getMyLocation(): Location? { // Remove this functionality and either guide users to use separate flutter // library for geolocation or implement separate method under // [GoogleMapsNavigationSessionManager] to fetch the location // using the [FusedLocationProviderApi]. - @Suppress("DEPRECATION") - return getMap().myLocation + @Suppress("DEPRECATION") val location = getMap().myLocation + // Return null explicitly if location is not available to avoid NullPointerException + // when the platform channel tries to serialize the Location object + return if (location != null && location.provider != null) location else null } fun getCameraPosition(): CameraPosition { diff --git a/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsNavigationSessionManager.kt b/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsNavigationSessionManager.kt index e084e901..b4f9eddc 100644 --- a/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsNavigationSessionManager.kt +++ b/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsNavigationSessionManager.kt @@ -26,6 +26,7 @@ import com.google.android.gms.maps.model.LatLng import com.google.android.libraries.mapsplatform.turnbyturn.model.NavInfo import com.google.android.libraries.navigation.CustomRoutesOptions import com.google.android.libraries.navigation.DisplayOptions +import com.google.android.libraries.navigation.GpsAvailabilityChangeEvent import com.google.android.libraries.navigation.NavigationApi import com.google.android.libraries.navigation.NavigationApi.NavigatorListener import com.google.android.libraries.navigation.Navigator @@ -342,10 +343,17 @@ constructor( remainingTimeOrDistanceChangedListener = Navigator.RemainingTimeOrDistanceChangedListener { val timeAndDistance = getNavigator().currentTimeAndDistance - navigationSessionEventApi.onRemainingTimeOrDistanceChanged( - timeAndDistance.seconds.toDouble(), - timeAndDistance.meters.toDouble(), - ) {} + // Only send event if we have valid time and distance data + if ( + timeAndDistance != null && + timeAndDistance.seconds != null && + timeAndDistance.meters != null + ) { + navigationSessionEventApi.onRemainingTimeOrDistanceChanged( + timeAndDistance.seconds.toDouble(), + timeAndDistance.meters.toDouble(), + ) {} + } } } @@ -495,7 +503,7 @@ constructor( * @return [TimeAndDistance] object. */ fun getCurrentTimeAndDistance(): TimeAndDistance { - return getNavigator().currentTimeAndDistance + return getNavigator().currentTimeAndDistance!! } /** @@ -749,6 +757,17 @@ constructor( override fun onGpsAvailabilityUpdate(isGpsAvailable: Boolean) { navigationSessionEventApi.onGpsAvailabilityUpdate(isGpsAvailable) {} } + + override fun onGpsAvailabilityChange(event: GpsAvailabilityChangeEvent?) { + if (event != null) { + navigationSessionEventApi.onGpsAvailabilityChange( + GpsAvailabilityChangeEventDto( + isGpsLost = event.isGpsLost, + isGpsValidForNavigation = event.isGpsValidForNavigation, + ) + ) {} + } + } } getRoadSnappedLocationProvider()?.addLocationListener(roadSnappedLocationListener) } diff --git a/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsNavigationView.kt b/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsNavigationView.kt index d4ee0ccf..09268f80 100644 --- a/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsNavigationView.kt +++ b/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsNavigationView.kt @@ -22,6 +22,7 @@ import android.view.View import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.libraries.navigation.NavigationView import com.google.android.libraries.navigation.OnNavigationUiChangedListener +import com.google.android.libraries.navigation.PromptVisibilityChangedListener import io.flutter.plugin.platform.PlatformView class GoogleMapsNavigationView @@ -51,6 +52,8 @@ internal constructor( null private var _onNavigationUIEnabledChanged: OnNavigationUiChangedListener? = null + private var _onPromptVisibilityChanged: PromptVisibilityChangedListener? = null + override fun getView(): View { return _navigationView } @@ -110,6 +113,10 @@ internal constructor( _navigationView.removeOnNavigationUiChangedListener(_onNavigationUIEnabledChanged) _onNavigationUIEnabledChanged = null } + if (_onPromptVisibilityChanged != null) { + _navigationView.removePromptVisibilityChangedListener(_onPromptVisibilityChanged) + _onPromptVisibilityChanged = null + } // When view is disposed, all of these lifecycle functions must be // called to properly dispose navigation view and prevent leaks. @@ -171,6 +178,11 @@ internal constructor( } _navigationView.addOnNavigationUiChangedListener(_onNavigationUIEnabledChanged) + _onPromptVisibilityChanged = PromptVisibilityChangedListener { promptVisible -> + viewEventApi?.onPromptVisibilityChanged(getViewId().toLong(), promptVisible) {} + } + _navigationView.addPromptVisibilityChangedListener(_onPromptVisibilityChanged) + super.initListeners() } @@ -246,6 +258,14 @@ internal constructor( _isReportIncidentButtonEnabled = enabled } + fun isIncidentReportingAvailable(): Boolean { + return _navigationView.isIncidentReportingAvailable() + } + + fun showReportIncidentsPanel() { + _navigationView.showReportIncidentsPanel() + } + fun isTrafficPromptsEnabled(): Boolean { return _isTrafficPromptsEnabled } diff --git a/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsViewMessageHandler.kt b/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsViewMessageHandler.kt index d0176148..8fe3f67c 100644 --- a/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsViewMessageHandler.kt +++ b/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsViewMessageHandler.kt @@ -368,6 +368,22 @@ class GoogleMapsViewMessageHandler(private val viewRegistry: GoogleMapsViewRegis getNavigationView(viewId.toInt()).setReportIncidentButtonEnabled(enabled) } + override fun isIncidentReportingAvailable(viewId: Long): Boolean { + return getNavigationView(viewId.toInt()).isIncidentReportingAvailable() + } + + override fun showReportIncidentsPanel(viewId: Long) { + getNavigationView(viewId.toInt()).showReportIncidentsPanel() + } + + override fun isBuildingsEnabled(viewId: Long): Boolean { + return getView(viewId.toInt()).isBuildingsEnabled() + } + + override fun setBuildingsEnabled(viewId: Long, enabled: Boolean) { + getView(viewId.toInt()).setBuildingsEnabled(enabled) + } + override fun isTrafficPromptsEnabled(viewId: Long): Boolean { return getNavigationView(viewId.toInt()).isTrafficPromptsEnabled() } diff --git a/android/src/main/kotlin/com/google/maps/flutter/navigation/messages.g.kt b/android/src/main/kotlin/com/google/maps/flutter/navigation/messages.g.kt index 0de11556..e4317645 100644 --- a/android/src/main/kotlin/com/google/maps/flutter/navigation/messages.g.kt +++ b/android/src/main/kotlin/com/google/maps/flutter/navigation/messages.g.kt @@ -594,6 +594,11 @@ data class MapOptionsDto( val cameraTargetBounds: LatLngBoundsDto? = null, /** Specifies the padding for the map. */ val padding: MapPaddingDto? = null, + /** + * The map ID for advanced map options eg. cloud-based map styling. This value can only be set on + * map initialization and cannot be changed afterwards. + */ + val mapId: String? = null, ) { companion object { fun fromList(pigeonVar_list: List): MapOptionsDto { @@ -611,6 +616,7 @@ data class MapOptionsDto( val zoomControlsEnabled = pigeonVar_list[11] as Boolean val cameraTargetBounds = pigeonVar_list[12] as LatLngBoundsDto? val padding = pigeonVar_list[13] as MapPaddingDto? + val mapId = pigeonVar_list[14] as String? return MapOptionsDto( cameraPosition, mapType, @@ -626,6 +632,7 @@ data class MapOptionsDto( zoomControlsEnabled, cameraTargetBounds, padding, + mapId, ) } } @@ -646,6 +653,7 @@ data class MapOptionsDto( zoomControlsEnabled, cameraTargetBounds, padding, + mapId, ) } @@ -1511,7 +1519,9 @@ data class RoutingOptionsDto( /** Generated class from Pigeon that represents data sent in messages. */ data class NavigationDisplayOptionsDto( val showDestinationMarkers: Boolean? = null, + /** Deprecated: This option now defaults to true. */ val showStopSigns: Boolean? = null, + /** Deprecated: This option now defaults to true. */ val showTrafficLights: Boolean? = null, ) { companion object { @@ -1755,6 +1765,36 @@ data class SpeedingUpdatedEventDto( override fun hashCode(): Int = toList().hashCode() } +/** Generated class from Pigeon that represents data sent in messages. */ +data class GpsAvailabilityChangeEventDto( + val isGpsLost: Boolean, + val isGpsValidForNavigation: Boolean, +) { + companion object { + fun fromList(pigeonVar_list: List): GpsAvailabilityChangeEventDto { + val isGpsLost = pigeonVar_list[0] as Boolean + val isGpsValidForNavigation = pigeonVar_list[1] as Boolean + return GpsAvailabilityChangeEventDto(isGpsLost, isGpsValidForNavigation) + } + } + + fun toList(): List { + return listOf(isGpsLost, isGpsValidForNavigation) + } + + override fun equals(other: Any?): Boolean { + if (other !is GpsAvailabilityChangeEventDto) { + return false + } + if (this === other) { + return true + } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) + } + + override fun hashCode(): Int = toList().hashCode() +} + /** Generated class from Pigeon that represents data sent in messages. */ data class SpeedAlertOptionsThresholdPercentageDto( val percentage: Double, @@ -2001,45 +2041,45 @@ data class LaneDto( * Generated class from Pigeon that represents data sent in messages. */ data class StepInfoDto( - /** Distance in meters from the previous step to this step. */ - val distanceFromPrevStepMeters: Long, - /** Time in seconds from the previous step to this step. */ - val timeFromPrevStepSeconds: Long, + /** Distance in meters from the previous step to this step if available, otherwise null. */ + val distanceFromPrevStepMeters: Long? = null, + /** Time in seconds from the previous step to this step if available, otherwise null. */ + val timeFromPrevStepSeconds: Long? = null, /** Whether this step is on a drive-on-right or drive-on-left route. */ val drivingSide: DrivingSideDto, /** The exit number if it exists. */ val exitNumber: String? = null, - /** The full text of the instruction for this step. */ - val fullInstructions: String, - /** The full road name for this step. */ - val fullRoadName: String, - /** The simplified version of the road name. */ - val simpleRoadName: String, + /** The full text of the instruction for this step if available, otherwise null. */ + val fullInstructions: String? = null, + /** The full road name for this step if available, otherwise null. */ + val fullRoadName: String? = null, + /** The simplified version of the road name if available, otherwise null. */ + val simpleRoadName: String? = null, /** * The counted number of the exit to take relative to the location where the roundabout was - * entered. + * entered if available, otherwise null. */ - val roundaboutTurnNumber: Long, - /** The list of available lanes at the end of this route step. */ - val lanes: List, + val roundaboutTurnNumber: Long? = null, + /** The list of available lanes at the end of this route step if available, otherwise null. */ + val lanes: List? = null, /** The maneuver for this step. */ val maneuver: ManeuverDto, - /** The index of the step in the list of all steps in the route. */ - val stepNumber: Long, + /** The index of the step in the list of all steps in the route if available, otherwise null. */ + val stepNumber: Long? = null, ) { companion object { fun fromList(pigeonVar_list: List): StepInfoDto { - val distanceFromPrevStepMeters = pigeonVar_list[0] as Long - val timeFromPrevStepSeconds = pigeonVar_list[1] as Long + val distanceFromPrevStepMeters = pigeonVar_list[0] as Long? + val timeFromPrevStepSeconds = pigeonVar_list[1] as Long? val drivingSide = pigeonVar_list[2] as DrivingSideDto val exitNumber = pigeonVar_list[3] as String? - val fullInstructions = pigeonVar_list[4] as String - val fullRoadName = pigeonVar_list[5] as String - val simpleRoadName = pigeonVar_list[6] as String - val roundaboutTurnNumber = pigeonVar_list[7] as Long - val lanes = pigeonVar_list[8] as List + val fullInstructions = pigeonVar_list[4] as String? + val fullRoadName = pigeonVar_list[5] as String? + val simpleRoadName = pigeonVar_list[6] as String? + val roundaboutTurnNumber = pigeonVar_list[7] as Long? + val lanes = pigeonVar_list[8] as List? val maneuver = pigeonVar_list[9] as ManeuverDto - val stepNumber = pigeonVar_list[10] as Long + val stepNumber = pigeonVar_list[10] as Long? return StepInfoDto( distanceFromPrevStepMeters, timeFromPrevStepSeconds, @@ -2354,33 +2394,38 @@ private open class messagesPigeonCodec : StandardMessageCodec() { } 182.toByte() -> { return (readValue(buffer) as? List)?.let { - SpeedAlertOptionsThresholdPercentageDto.fromList(it) + GpsAvailabilityChangeEventDto.fromList(it) } } 183.toByte() -> { - return (readValue(buffer) as? List)?.let { SpeedAlertOptionsDto.fromList(it) } + return (readValue(buffer) as? List)?.let { + SpeedAlertOptionsThresholdPercentageDto.fromList(it) + } } 184.toByte() -> { + return (readValue(buffer) as? List)?.let { SpeedAlertOptionsDto.fromList(it) } + } + 185.toByte() -> { return (readValue(buffer) as? List)?.let { RouteSegmentTrafficDataRoadStretchRenderingDataDto.fromList(it) } } - 185.toByte() -> { + 186.toByte() -> { return (readValue(buffer) as? List)?.let { RouteSegmentTrafficDataDto.fromList(it) } } - 186.toByte() -> { + 187.toByte() -> { return (readValue(buffer) as? List)?.let { RouteSegmentDto.fromList(it) } } - 187.toByte() -> { + 188.toByte() -> { return (readValue(buffer) as? List)?.let { LaneDirectionDto.fromList(it) } } - 188.toByte() -> { + 189.toByte() -> { return (readValue(buffer) as? List)?.let { LaneDto.fromList(it) } } - 189.toByte() -> { + 190.toByte() -> { return (readValue(buffer) as? List)?.let { StepInfoDto.fromList(it) } } - 190.toByte() -> { + 191.toByte() -> { return (readValue(buffer) as? List)?.let { NavInfoDto.fromList(it) } } else -> super.readValueOfType(type, buffer) @@ -2601,42 +2646,46 @@ private open class messagesPigeonCodec : StandardMessageCodec() { stream.write(181) writeValue(stream, value.toList()) } - is SpeedAlertOptionsThresholdPercentageDto -> { + is GpsAvailabilityChangeEventDto -> { stream.write(182) writeValue(stream, value.toList()) } - is SpeedAlertOptionsDto -> { + is SpeedAlertOptionsThresholdPercentageDto -> { stream.write(183) writeValue(stream, value.toList()) } - is RouteSegmentTrafficDataRoadStretchRenderingDataDto -> { + is SpeedAlertOptionsDto -> { stream.write(184) writeValue(stream, value.toList()) } - is RouteSegmentTrafficDataDto -> { + is RouteSegmentTrafficDataRoadStretchRenderingDataDto -> { stream.write(185) writeValue(stream, value.toList()) } - is RouteSegmentDto -> { + is RouteSegmentTrafficDataDto -> { stream.write(186) writeValue(stream, value.toList()) } - is LaneDirectionDto -> { + is RouteSegmentDto -> { stream.write(187) writeValue(stream, value.toList()) } - is LaneDto -> { + is LaneDirectionDto -> { stream.write(188) writeValue(stream, value.toList()) } - is StepInfoDto -> { + is LaneDto -> { stream.write(189) writeValue(stream, value.toList()) } - is NavInfoDto -> { + is StepInfoDto -> { stream.write(190) writeValue(stream, value.toList()) } + is NavInfoDto -> { + stream.write(191) + writeValue(stream, value.toList()) + } else -> super.writeValue(stream, value) } } @@ -2797,6 +2846,14 @@ interface MapViewApi { fun setReportIncidentButtonEnabled(viewId: Long, enabled: Boolean) + fun isIncidentReportingAvailable(viewId: Long): Boolean + + fun showReportIncidentsPanel(viewId: Long) + + fun isBuildingsEnabled(viewId: Long): Boolean + + fun setBuildingsEnabled(viewId: Long, enabled: Boolean) + fun getCameraPosition(viewId: Long): CameraPositionDto fun getVisibleRegion(viewId: Long): LatLngBoundsDto @@ -4120,6 +4177,101 @@ interface MapViewApi { channel.setMessageHandler(null) } } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.google_navigation_flutter.MapViewApi.isIncidentReportingAvailable$separatedMessageChannelSuffix", + codec, + ) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val viewIdArg = args[0] as Long + val wrapped: List = + try { + listOf(api.isIncidentReportingAvailable(viewIdArg)) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.google_navigation_flutter.MapViewApi.showReportIncidentsPanel$separatedMessageChannelSuffix", + codec, + ) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val viewIdArg = args[0] as Long + val wrapped: List = + try { + api.showReportIncidentsPanel(viewIdArg) + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.google_navigation_flutter.MapViewApi.isBuildingsEnabled$separatedMessageChannelSuffix", + codec, + ) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val viewIdArg = args[0] as Long + val wrapped: List = + try { + listOf(api.isBuildingsEnabled(viewIdArg)) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.google_navigation_flutter.MapViewApi.setBuildingsEnabled$separatedMessageChannelSuffix", + codec, + ) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val viewIdArg = args[0] as Long + val enabledArg = args[1] as Boolean + val wrapped: List = + try { + api.setBuildingsEnabled(viewIdArg, enabledArg) + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel( @@ -5628,6 +5780,29 @@ class ViewEventApi( } } + fun onPromptVisibilityChanged( + viewIdArg: Long, + promptVisibleArg: Boolean, + callback: (Result) -> Unit, + ) { + val separatedMessageChannelSuffix = + if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = + "dev.flutter.pigeon.google_navigation_flutter.ViewEventApi.onPromptVisibilityChanged$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(viewIdArg, promptVisibleArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(MessagesPigeonUtils.createConnectionError(channelName))) + } + } + } + fun onMyLocationClicked(viewIdArg: Long, callback: (Result) -> Unit) { val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" @@ -6786,6 +6961,29 @@ class NavigationSessionEventApi( } } + /** Android-only event. */ + fun onGpsAvailabilityChange( + eventArg: GpsAvailabilityChangeEventDto, + callback: (Result) -> Unit, + ) { + val separatedMessageChannelSuffix = + if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = + "dev.flutter.pigeon.google_navigation_flutter.NavigationSessionEventApi.onGpsAvailabilityChange$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(eventArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(MessagesPigeonUtils.createConnectionError(channelName))) + } + } + } + /** Turn-by-Turn navigation events. */ fun onNavInfo(navInfoArg: NavInfoDto, callback: (Result) -> Unit) { val separatedMessageChannelSuffix = diff --git a/android/src/test/kotlin/com/google/maps/flutter/navigation/ConvertTest.kt b/android/src/test/kotlin/com/google/maps/flutter/navigation/ConvertTest.kt index 297cb746..4fe19f12 100644 --- a/android/src/test/kotlin/com/google/maps/flutter/navigation/ConvertTest.kt +++ b/android/src/test/kotlin/com/google/maps/flutter/navigation/ConvertTest.kt @@ -153,8 +153,9 @@ internal class ConvertTest { ) val convertedNone = Convert.convertDisplayOptionsFromDto(none) - assertEquals(false, convertedNone.showStopSigns) - assertEquals(false, convertedNone.showTrafficLights) + // When not specified, SDK defaults are used: stop signs and traffic lights are shown + assertEquals(true, convertedNone.showStopSigns) + assertEquals(true, convertedNone.showTrafficLights) assertEquals(false, convertedNone.hideDestinationMarkers) val convertedAllFalse = Convert.convertDisplayOptionsFromDto(allFalse) diff --git a/example/README.md b/example/README.md index e6d1b9fd..9b486d78 100644 --- a/example/README.md +++ b/example/README.md @@ -17,6 +17,11 @@ Run the app with the API key as a Dart define. flutter run --dart-define MAPS_API_KEY=YOUR_API_KEY ``` +You can also optionally provide a Map ID with another Dart define: +```bash +flutter run --dart-define MAPS_API_KEY=YOUR_API_KEY --dart-define MAP_ID=YOUR_MAP_ID +``` + The example app demonstrates multiple ways to provide the Maps API key for platforms. ### Android specific API key diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts index f6cf1de3..0e1159dc 100644 --- a/example/android/app/build.gradle.kts +++ b/example/android/app/build.gradle.kts @@ -39,7 +39,7 @@ fun findDartDefineValue(key: String): String? { android { namespace = "com.google.maps.flutter.navigation_example" compileSdk = flutter.compileSdkVersion - ndkVersion = "27.0.12077973" + ndkVersion = "28.2.13676358" compileOptions { // Flag to enable support for the new language APIs @@ -53,6 +53,12 @@ android { jvmTarget = JavaVersion.VERSION_11.toString() } + // Set this to the languages you actually use, otherwise you'll include resource strings + // for all languages supported by the Navigation SDK. + androidResources { + localeFilters.add("en") + } + defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId = "com.google.maps.flutter.navigation_example" @@ -63,8 +69,6 @@ android { versionCode = flutter.versionCode versionName = flutter.versionName - // Set this to the languages you actually use, otherwise you'll include resource strings - // for all languages supported by the Navigation SDK. multiDexEnabled = true testInstrumentationRunner = "pl.leancode.patrol.PatrolJUnitRunner" @@ -73,8 +77,6 @@ android { // test case and uncomment the following line to clear the package data before running tests. // testInstrumentationRunnerArguments["clearPackageData"] = "true" - resourceConfigurations.add("en") - // Extract MAPS_API_KEY from Dart defines or environment variables // and use it as manifest placeholder. val mapsApiKey = System.getenv("MAPS_API_KEY") ?: findDartDefineValue("MAPS_API_KEY") ?: "" @@ -99,9 +101,9 @@ flutter { } dependencies { - implementation("androidx.car.app:app:1.4.0") - implementation("androidx.car.app:app-projected:1.4.0") - implementation("com.google.android.libraries.navigation:navigation:6.2.2") + implementation("androidx.car.app:app:1.7.0") + implementation("androidx.car.app:app-projected:1.7.0") + implementation("com.google.android.libraries.navigation:navigation:7.2.0") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4") androidTestUtil("androidx.test:orchestrator:1.5.1") } diff --git a/example/android/app/src/main/kotlin/com/google/maps/flutter/navigation_example/SampleAndroidAutoScreen.kt b/example/android/app/src/main/kotlin/com/google/maps/flutter/navigation_example/SampleAndroidAutoScreen.kt index 2be97a23..447a585c 100644 --- a/example/android/app/src/main/kotlin/com/google/maps/flutter/navigation_example/SampleAndroidAutoScreen.kt +++ b/example/android/app/src/main/kotlin/com/google/maps/flutter/navigation_example/SampleAndroidAutoScreen.kt @@ -59,11 +59,11 @@ class SampleAndroidAutoScreen(carContext: CarContext): AndroidAutoBaseScreen(car * Converts data received from the Navigation data feed into Android-Auto compatible data * structures. */ - val currentStep: Step = buildStepFromStepInfo(navInfo.currentStep) + val currentStep: Step = buildStepFromStepInfo(navInfo.currentStep!!) val distanceToStep = Distance.create( java.lang.Double.max( - navInfo.distanceToCurrentStepMeters.toDouble(), + navInfo.distanceToCurrentStepMeters?.toDouble() ?: 0.0, 0.0 ), Distance.UNIT_METERS ) @@ -78,14 +78,14 @@ class SampleAndroidAutoScreen(carContext: CarContext): AndroidAutoBaseScreen(car val maneuver: Int = ManeuverConverter.getAndroidAutoManeuverType(stepInfo.maneuver) val maneuverBuilder = Maneuver.Builder(maneuver) if (stepInfo.maneuverBitmap != null) { - val maneuverIcon = IconCompat.createWithBitmap(stepInfo.maneuverBitmap) + val maneuverIcon = IconCompat.createWithBitmap(stepInfo.maneuverBitmap!!) val maneuverCarIcon = CarIcon.Builder(maneuverIcon).build() maneuverBuilder.setIcon(maneuverCarIcon) } val stepBuilder = Step.Builder() - .setRoad(stepInfo.fullRoadName) - .setCue(stepInfo.fullInstructionText) + .setRoad(stepInfo.fullRoadName ?: "") + .setCue(stepInfo.fullInstructionText ?: "") .setManeuver(maneuverBuilder.build()) return stepBuilder.build() } diff --git a/example/integration_test/t02_session_test.dart b/example/integration_test/t02_session_test.dart index 17a1c69e..2560e3d2 100644 --- a/example/integration_test/t02_session_test.dart +++ b/example/integration_test/t02_session_test.dart @@ -28,6 +28,11 @@ void main() { patrol('Test terms and conditions (TOS) dialog acceptance', ( PatrolIntegrationTester $, ) async { + if (!Platform.isIOS) { + // Be sure location is enabled. + await $.native.enableLocation(); + } + // Grant the location permission. await checkLocationDialogAcceptance($); diff --git a/example/integration_test/t04_navigation_ui_test.dart b/example/integration_test/t04_navigation_ui_test.dart index c3694534..3402c2cb 100644 --- a/example/integration_test/t04_navigation_ui_test.dart +++ b/example/integration_test/t04_navigation_ui_test.dart @@ -32,6 +32,9 @@ void main() { /// For testing NavigationUIEnabledChanged bool navigationUIisEnabled = false; + /// For testing PromptVisibilityChanged + bool? promptVisible; + await checkLocationDialogAndTosAcceptance($); /// The events are not tested because there's no reliable way to trigger them currently. @@ -41,6 +44,12 @@ void main() { $.log('Re-center button clicked event: $event.'); } + /// For testing PromptVisibilityChanged + void onPromptVisibilityChanged(bool promptVisible_) { + $.log('Prompt visibility changed event: $promptVisible_.'); + promptVisible = promptVisible_; + } + /// Display navigation view. final Key key = GlobalKey(); await pumpNavigationView( @@ -54,6 +63,7 @@ void main() { navigationUIisEnabled = isEnabled; }, onRecenterButtonClicked: onRecenterButtonClicked, + onPromptVisibilityChanged: onPromptVisibilityChanged, ), ); @@ -257,6 +267,53 @@ void main() { ); } + /// Test enabling and disabling the 3D buildings layer. + for (final bool result in results) { + await viewController.setBuildingsEnabled(result); + final bool isEnabled = await viewController.isBuildingsEnabled(); + expect( + isEnabled, + result, + reason: buildReasonForToggle('BuildingsEnabled', result), + ); + } + + /// Test incident reporting availability. + final bool isIncidentReportingAvailable = + await viewController.isIncidentReportingAvailable(); + $.log('Incident reporting available: $isIncidentReportingAvailable'); + expect( + isIncidentReportingAvailable, + true, + reason: + 'Incident reporting should be available during navigation on tests.', + ); + + /// Test prompt visibility and incident reporting panel. + if (isIncidentReportingAvailable) { + // Reset prompt visibility state + promptVisible = null; + + $.log('Opening incident reporting panel...'); + await viewController.showReportIncidentsPanel(); + await $.pumpAndSettle(timeout: const Duration(seconds: 2)); + + /// Check if prompt visibility event was triggered + waitForValueMatchingPredicate( + $, + () async => promptVisible, + (bool? value) => value == true, + maxTries: 50, // Wait up to 5 seconds (50 * 100ms) + ); + + expect( + promptVisible, + true, + reason: + 'Prompt visibility should be true when incident panel is shown.', + ); + } + await GoogleMapsNavigator.cleanup(); }); } diff --git a/example/integration_test/t06_map_test.dart b/example/integration_test/t06_map_test.dart index 86ba62f6..0744c122 100644 --- a/example/integration_test/t06_map_test.dart +++ b/example/integration_test/t06_map_test.dart @@ -26,6 +26,8 @@ import 'package:flutter/material.dart'; import 'shared.dart'; +const String testMapId = 'DEMO_MAP_ID'; + void main() { final mapTypeVariants = getMapTypeVariants(); patrol('Test map types', (PatrolIntegrationTester $) async { @@ -123,6 +125,48 @@ void main() { expect(await controller.settings.isZoomControlsEnabled(), false); expect(await controller.settings.isMapToolbarEnabled(), false); } + + // Test that view can be created with a mapID. + // Note: mapID cannot be fetched back from the map, so we only test + // that creation succeeds. + final ControllerCompleter + controllerCompleterWithMapId = + ControllerCompleter(); + + switch (mapTypeVariants.currentValue!) { + case TestMapType.mapView: + final Key key = GlobalKey(); + await pumpMapView( + $, + GoogleMapsMapView( + key: key, + mapId: testMapId, + onViewCreated: (GoogleMapViewController viewController) { + controllerCompleterWithMapId.complete(viewController); + }, + ), + ); + break; + case TestMapType.navigationView: + final Key key = GlobalKey(); + await pumpNavigationView( + $, + GoogleMapsNavigationView( + key: key, + mapId: testMapId, + onViewCreated: (GoogleNavigationViewController viewController) { + controllerCompleterWithMapId.complete(viewController); + }, + ), + ); + break; + } + + final GoogleMapViewController controllerWithMapId = + await controllerCompleterWithMapId.future; + + // Verify the controller was created successfully + expect(controllerWithMapId, isNotNull); }, variant: mapTypeVariants); patrol('Test map UI settings', (PatrolIntegrationTester $) async { diff --git a/example/integration_test/t07_event_listener_test.dart b/example/integration_test/t07_event_listener_test.dart index 890c4e4b..97646e46 100644 --- a/example/integration_test/t07_event_listener_test.dart +++ b/example/integration_test/t07_event_listener_test.dart @@ -398,9 +398,12 @@ void main() { }); patrol( - 'Test navigation onRerouting and onGpsAvailability event listeners', + 'Test navigation onRerouting, onGpsAvailability and onGpsAvailabilityChange event listeners', (PatrolIntegrationTester $) async { - final Completer eventReceived = Completer(); + final Completer reroutingEventReceived = Completer(); + final Completer gpsAvailabilityEventReceived = Completer(); + final Completer gpsAvailabilityChangeEventReceived = + Completer(); /// Set up navigation. await startNavigationWithoutDestination($); @@ -409,26 +412,46 @@ void main() { final StreamSubscription onReroutingSubscription = GoogleMapsNavigator.setOnReroutingListener( expectAsync0(() { + $.log('Rerouting event received'); + /// Complete the eventReceived completer only once. - if (!eventReceived.isCompleted) { - eventReceived.complete(); + if (!reroutingEventReceived.isCompleted) { + reroutingEventReceived.complete(); } }, max: -1), ); await $.pumpAndSettle(); - /// The events are not tested because there's currently no reliable way to trigger them. + /// Set up GPS availability listeners with completers. void onGpsAvailability(GpsAvailabilityUpdatedEvent event) { - $.log('GpsAvailabilityEvent: $event'); + $.log('GpsAvailabilityEvent (deprecated): $event'); + if (!gpsAvailabilityEventReceived.isCompleted) { + gpsAvailabilityEventReceived.complete(); + } + } + + void onGpsAvailabilityChange(GpsAvailabilityChangeEvent event) { + $.log( + 'GpsAvailabilityChangeEvent: isGpsLost=${event.isGpsLost}, isGpsValidForNavigation=${event.isGpsValidForNavigation}', + ); + if (!gpsAvailabilityChangeEventReceived.isCompleted) { + gpsAvailabilityChangeEventReceived.complete(); + } } - /// Set up the gpsAvailability listener with the test. + /// Set up both the old (deprecated) and new gpsAvailability listeners. final StreamSubscription onGpsAvailabilitySubscription = await GoogleMapsNavigator.setOnGpsAvailabilityListener( onGpsAvailability, ); + final StreamSubscription + onGpsAvailabilityChangeSubscription = + await GoogleMapsNavigator.setOnGpsAvailabilityChangeListener( + onGpsAvailabilityChange, + ); + /// Simulate location. await GoogleMapsNavigator.simulator.setUserLocation( const LatLng(latitude: 37.790693, longitude: -122.4132157), @@ -463,6 +486,7 @@ void main() { /// Start guidance. await GoogleMapsNavigator.startGuidance(); + await $.pumpAndSettle(); /// Start simulation to a different destination. @@ -474,10 +498,43 @@ void main() { ); await $.pumpAndSettle(); - /// Wait until the event is received and then test cancelling the subscriptions. - await eventReceived.future; - await onReroutingSubscription.cancel(); - await onGpsAvailabilitySubscription.cancel(); + Future cancelSubscriptionsAndResetState() async { + await $.native.enableLocation(); + await onReroutingSubscription.cancel(); + await onGpsAvailabilitySubscription.cancel(); + await onGpsAvailabilityChangeSubscription.cancel(); + } + + /// Wait for rerouting event (this should fire reliably). + await reroutingEventReceived.future.timeout( + const Duration(seconds: 10), + onTimeout: () { + cancelSubscriptionsAndResetState(); + fail('Rerouting event timed out'); + }, + ); + + await $.native.disableLocation(); + + /// Wait for GPS availability events with timeout. + await gpsAvailabilityEventReceived.future.timeout( + const Duration(seconds: 20), + onTimeout: () { + cancelSubscriptionsAndResetState(); + fail('GpsAvailabilityEvent timed out'); + }, + ); + + await gpsAvailabilityChangeEventReceived.future.timeout( + const Duration(seconds: 20), + onTimeout: () { + cancelSubscriptionsAndResetState(); + fail('GpsAvailabilityChangeEvent timed out'); + }, + ); + + /// Cancel all subscriptions. + await cancelSubscriptionsAndResetState(); }, skip: !Platform.isAndroid, ); diff --git a/example/ios/.gitignore b/example/ios/.gitignore index 7a7f9873..1ee65f12 100644 --- a/example/ios/.gitignore +++ b/example/ios/.gitignore @@ -26,6 +26,7 @@ Flutter/flutter_assets/ Flutter/flutter_export_environment.sh ServiceDefinitions.json Runner/GeneratedPluginRegistrant.* +Package.resolved # Exceptions to above rules. !default.mode1v3 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index f8750f15..babaf9c3 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -29,7 +29,6 @@ 9BAC8E60A263AA40A946AFCF /* Pods_Runner_RunnerUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 981DBDBFB0E241F931B1E394 /* Pods_Runner_RunnerUITests.framework */; }; C9CBB6602AD89694007C737E /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 506F57582AD8055D004AC70F /* XCTest.framework */; platformFilter = ios; }; D3A3B5ED895816BDF2A70351 /* Pods_RunnerCarPlay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B269F01B5CD151328AA7F8EB /* Pods_RunnerCarPlay.framework */; }; - DA52652595AF0077243F8014 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64B801515FD75D36D58A86FE /* Pods_Runner.framework */; }; EDFE577D2F64CF5D3712A4E9 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64B801515FD75D36D58A86FE /* Pods_Runner.framework */; }; 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ @@ -143,7 +142,6 @@ files = ( 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, EDFE577D2F64CF5D3712A4E9 /* Pods_Runner.framework in Frameworks */, - DA52652595AF0077243F8014 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 6a17a6f5..00000000 --- a/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,23 +0,0 @@ -{ - "pins" : [ - { - "identity" : "ios-maps-sdk", - "kind" : "remoteSourceControl", - "location" : "https://github.com/googlemaps/ios-maps-sdk", - "state" : { - "revision" : "9c540f3b475a800e947a09b8903b212a6634cf30", - "version" : "10.0.0" - } - }, - { - "identity" : "ios-navigation-sdk", - "kind" : "remoteSourceControl", - "location" : "https://github.com/googlemaps/ios-navigation-sdk", - "state" : { - "revision" : "a3faa12da9a957420da8e1b448022f365fbc8400", - "version" : "10.0.0" - } - } - ], - "version" : 2 -} diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 519c97bf..3c37b8d8 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -18,6 +18,10 @@ $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 + CFBundleLocalizations + + en + CFBundleName google_navigation_flutter_example CFBundlePackageType @@ -45,6 +49,7 @@ location processing remote-notification + audio UILaunchStoryboardName LaunchScreen diff --git a/example/ios/RunnerTests/ConvertTests.swift b/example/ios/RunnerTests/ConvertTests.swift index 4fca29e3..aea3c097 100644 --- a/example/ios/RunnerTests/ConvertTests.swift +++ b/example/ios/RunnerTests/ConvertTests.swift @@ -551,7 +551,8 @@ class ConvertTests: XCTestCase { zoomGesturesEnabled: false, scrollGesturesEnabledDuringRotateOrZoom: false, mapToolbarEnabled: false, - zoomControlsEnabled: false + zoomControlsEnabled: false, + mapId: "test-map-id" ) let navigationViewOptions = @@ -584,6 +585,7 @@ class ConvertTests: XCTestCase { mapOptions.scrollGesturesEnabledDuringRotateOrZoom, configuration.scrollGesturesEnabledDuringRotateOrZoom ) + XCTAssertEqual(mapOptions.mapId, configuration.mapId) } func testConvertPath() { diff --git a/example/lib/main.dart b/example/lib/main.dart index f09e7f44..180bd109 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -22,6 +22,7 @@ import 'package:google_navigation_flutter/google_navigation_flutter.dart'; import 'package:permission_handler/permission_handler.dart'; import 'pages/circles.dart'; import 'pages/pages.dart'; +import 'utils/utils.dart'; import 'widgets/widgets.dart'; /// The list of pages to show in the Google Maps Navigation demo. @@ -106,33 +107,51 @@ class _NavigationDemoState extends State { body: SafeArea( top: false, minimum: const EdgeInsets.all(8.0), - child: ListView.builder( - itemCount: _allPages.length + 1, - itemBuilder: (_, int index) { - if (index == 0) { - return Card( - child: Container( - padding: const EdgeInsets.symmetric(vertical: 4.0), - alignment: Alignment.center, - child: Column( - children: [ - Text( - Platform.isIOS - ? 'Location ${_locationPermitted ? 'granted' : 'denied'} • Notifications ${_notificationsPermitted ? 'granted' : 'denied'}' - : 'Location ${_locationPermitted ? 'granted' : 'denied'} ', - ), - Text('Navigation SDK version: $_navSDKVersion'), - ], - ), + child: Column( + children: [ + Card( + child: Container( + padding: const EdgeInsets.symmetric(vertical: 4.0), + alignment: Alignment.center, + child: Column( + children: [ + Text( + Platform.isIOS + ? 'Location ${_locationPermitted ? 'granted' : 'denied'} • Notifications ${_notificationsPermitted ? 'granted' : 'denied'}' + : 'Location ${_locationPermitted ? 'granted' : 'denied'} ', + ), + Text('Navigation SDK version: $_navSDKVersion'), + Text( + 'Current map ID: ${MapIdManager.instance.mapIdDisplay}', + ), + ], ), - ); - } - return ListTile( - leading: _allPages[index - 1].leading, - title: Text(_allPages[index - 1].title), - onTap: () => _pushPage(context, _allPages[index - 1]), - ); - }, + ), + ), + Expanded( + child: Scrollbar( + thumbVisibility: true, + child: ListView.builder( + itemCount: _allPages.length, + itemBuilder: (_, int index) { + return ListTile( + leading: _allPages[index].leading, + title: Text(_allPages[index].title), + onTap: () => _pushPage(context, _allPages[index]), + ); + }, + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton( + onPressed: + () => showMapIdDialog(context, () => setState(() {})), + child: const Text('Set Map ID'), + ), + ), + ], ), ), ); @@ -140,6 +159,8 @@ class _NavigationDemoState extends State { } void main() { + MapIdManager.instance.initialize(); + final ElevatedButtonThemeData exampleButtonDefaultTheme = ElevatedButtonThemeData( style: ElevatedButton.styleFrom(minimumSize: const Size(160, 36)), diff --git a/example/lib/pages/map.dart b/example/lib/pages/map.dart index fc58ef2c..cad9affb 100644 --- a/example/lib/pages/map.dart +++ b/example/lib/pages/map.dart @@ -113,6 +113,7 @@ class _MapPageState extends ExamplePageState { onViewCreated: _onViewCreated, onMyLocationClicked: _onMyLocationClicked, onMyLocationButtonClicked: _onMyLocationButtonClicked, + mapId: MapIdManager.instance.mapId, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), @@ -165,17 +166,26 @@ class _MapPageState extends ExamplePageState { children: [ ElevatedButton( style: mapTypeStyle, - onPressed: () => setMapStyleDefault(), + onPressed: + MapIdManager.instance.mapId != null + ? null + : () => setMapStyleDefault(), child: const Text('Default style'), ), ElevatedButton( style: mapTypeStyle, - onPressed: () => setMapStyleNight(), + onPressed: + MapIdManager.instance.mapId != null + ? null + : () => setMapStyleNight(), child: const Text('Night style'), ), ElevatedButton( style: mapTypeStyle, - onPressed: () => setMapStyleSepia(), + onPressed: + MapIdManager.instance.mapId != null + ? null + : () => setMapStyleSepia(), child: const Text('Sepia style'), ), ], diff --git a/example/lib/pages/multiple_views.dart b/example/lib/pages/multiple_views.dart index 279c346c..3c4c4324 100644 --- a/example/lib/pages/multiple_views.dart +++ b/example/lib/pages/multiple_views.dart @@ -237,7 +237,10 @@ class _MultiplexState extends ExamplePageState { children: [ SizedBox( height: 200, - child: GoogleMapsMapView(onViewCreated: _onViewCreated), + child: GoogleMapsMapView( + onViewCreated: _onViewCreated, + mapId: MapIdManager.instance.mapId, + ), ), const SizedBox(height: 20), SizedBox( @@ -251,6 +254,7 @@ class _MultiplexState extends ExamplePageState { target: _userLocation!, zoom: 15, ), + mapId: MapIdManager.instance.mapId, ) : const Center( child: Column( diff --git a/example/lib/pages/navigation.dart b/example/lib/pages/navigation.dart index 16fe3bd9..c2b3c77b 100644 --- a/example/lib/pages/navigation.dart +++ b/example/lib/pages/navigation.dart @@ -92,13 +92,14 @@ class _NavigationPageState extends ExamplePageState { int _onRoadSnappedRawLocationUpdatedEventCallCount = 0; int _onTrafficUpdatedEventCallCount = 0; int _onReroutingEventCallCount = 0; - int _onGpsAvailabilityEventCallCount = 0; + int _onGpsAvailabilityChangeEventCallCount = 0; int _onArrivalEventCallCount = 0; int _onSpeedingUpdatedEventCallCount = 0; int _onRecenterButtonClickedEventCallCount = 0; int _onRemainingTimeOrDistanceChangedEventCallCount = 0; int _onNavigationUIEnabledChangedEventCallCount = 0; int _onNewNavigationSessionEventCallCount = 0; + int _onPromptVisibilityChangedEventCallCount = 0; bool _navigationHeaderEnabled = true; bool _navigationFooterEnabled = true; @@ -110,6 +111,7 @@ class _NavigationPageState extends ExamplePageState { bool _trafficIndicentCardsEnabled = false; bool _trafficPromptsEnabled = true; bool _reportIncidentButtonEnabled = true; + bool _buildingsEnabled = true; bool _termsAndConditionsAccepted = false; bool _locationPermissionsAccepted = false; @@ -139,7 +141,8 @@ class _NavigationPageState extends ExamplePageState { StreamSubscription? _speedUpdatedSubscription; StreamSubscription? _onArrivalSubscription; StreamSubscription? _onReRoutingSubscription; - StreamSubscription? _onGpsAvailabilitySubscription; + StreamSubscription? + _onGpsAvailabilityChangeSubscription; StreamSubscription? _trafficUpdatedSubscription; StreamSubscription? _onRouteChangedSubscription; StreamSubscription? @@ -357,9 +360,9 @@ class _NavigationPageState extends ExamplePageState { _onReRoutingSubscription = GoogleMapsNavigator.setOnReroutingListener( _onReroutingEvent, ); - _onGpsAvailabilitySubscription = - await GoogleMapsNavigator.setOnGpsAvailabilityListener( - _onGpsAvailabilityEvent, + _onGpsAvailabilityChangeSubscription = + await GoogleMapsNavigator.setOnGpsAvailabilityChangeListener( + _onGpsAvailabilityChangeEvent, ); _trafficUpdatedSubscription = GoogleMapsNavigator.setTrafficUpdatedListener( _onTrafficUpdatedEvent, @@ -398,8 +401,8 @@ class _NavigationPageState extends ExamplePageState { _onReRoutingSubscription?.cancel(); _onReRoutingSubscription = null; - _onGpsAvailabilitySubscription?.cancel(); - _onGpsAvailabilitySubscription = null; + _onGpsAvailabilityChangeSubscription?.cancel(); + _onGpsAvailabilityChangeSubscription = null; _trafficUpdatedSubscription?.cancel(); _trafficUpdatedSubscription = null; @@ -503,9 +506,10 @@ class _NavigationPageState extends ExamplePageState { }); } - void _onGpsAvailabilityEvent(GpsAvailabilityUpdatedEvent event) { + void _onGpsAvailabilityChangeEvent(GpsAvailabilityChangeEvent event) { + debugPrint('GPS availability change event: $event'); setState(() { - _onGpsAvailabilityEventCallCount += 1; + _onGpsAvailabilityChangeEventCallCount += 1; }); } @@ -575,6 +579,8 @@ class _NavigationPageState extends ExamplePageState { await _navigationViewController!.isTrafficPromptsEnabled(); final bool reportIncidentButtonEnabled = await _navigationViewController!.isReportIncidentButtonEnabled(); + final bool buildingsEnabled = + await _navigationViewController!.isBuildingsEnabled(); setState(() { _navigationHeaderEnabled = navigationHeaderEnabled; @@ -587,6 +593,7 @@ class _NavigationPageState extends ExamplePageState { _trafficIndicentCardsEnabled = trafficIndicentCardsEnabled; _trafficPromptsEnabled = trafficPromptsEnabled; _reportIncidentButtonEnabled = reportIncidentButtonEnabled; + _buildingsEnabled = buildingsEnabled; }); } } @@ -608,6 +615,15 @@ class _NavigationPageState extends ExamplePageState { } } + void _onPromptVisibilityChanged(bool promptVisible) { + if (mounted) { + setState(() { + _onPromptVisibilityChangedEventCallCount += 1; + }); + showMessage('Prompt visibility changed: $promptVisible'); + } + } + Future _startGuidedNavigation() async { assert(_navigationViewController != null); if (!_navigatorInitialized) { @@ -1203,6 +1219,7 @@ class _NavigationPageState extends ExamplePageState { _onRecenterButtonClickedEvent, onNavigationUIEnabledChanged: _onNavigationUIEnabledChanged, + onPromptVisibilityChanged: _onPromptVisibilityChanged, initialCameraPosition: CameraPosition( // Initialize map to user location. target: _userLocation!, @@ -1213,6 +1230,7 @@ class _NavigationPageState extends ExamplePageState { ? NavigationUIEnabledPreference.automatic : NavigationUIEnabledPreference.disabled, initialPadding: const EdgeInsets.all(0), + mapId: MapIdManager.instance.mapId, ) : const Center( child: Column( @@ -1434,8 +1452,12 @@ class _NavigationPageState extends ExamplePageState { if (Platform.isAndroid) Card( child: ListTile( - title: const Text('On GPS availability event call count'), - trailing: Text(_onGpsAvailabilityEventCallCount.toString()), + title: const Text( + 'On GPS availability change event call count', + ), + trailing: Text( + _onGpsAvailabilityChangeEventCallCount.toString(), + ), ), ), Card( @@ -1488,6 +1510,16 @@ class _NavigationPageState extends ExamplePageState { ), ), ), + Card( + child: ListTile( + title: const Text( + 'On prompt visibility changed event call count', + ), + trailing: Text( + _onPromptVisibilityChangedEventCallCount.toString(), + ), + ), + ), ], ), ); @@ -1793,6 +1825,18 @@ class _NavigationPageState extends ExamplePageState { }); }, ), + ExampleSwitch( + title: 'Show 3D buildings', + initialValue: _buildingsEnabled, + onChanged: (bool newValue) async { + await _navigationViewController!.setBuildingsEnabled( + newValue, + ); + setState(() { + _buildingsEnabled = newValue; + }); + }, + ), Text( 'Map left padding: ${_mapPadding.left.toStringAsFixed(0)}', ), @@ -2001,6 +2045,33 @@ class _NavigationPageState extends ExamplePageState { ), child: const Text('Follow my location'), ), + ElevatedButton( + onPressed: () async { + final bool available = + await _navigationViewController! + .isIncidentReportingAvailable(); + if (available) { + await _navigationViewController! + .showReportIncidentsPanel(); + } else { + if (context.mounted) { + showMessage('Incident reporting is not available'); + } + } + }, + child: const Text('Report Incident'), + ), + ElevatedButton( + onPressed: () async { + final bool available = + await _navigationViewController! + .isIncidentReportingAvailable(); + showMessage('Incident reporting available: $available'); + }, + child: const Text( + 'Check incident reporting availability', + ), + ), ], ), const SizedBox(height: 10), diff --git a/example/lib/pages/turn_by_turn.dart b/example/lib/pages/turn_by_turn.dart index 9e5b26bd..3fb630df 100644 --- a/example/lib/pages/turn_by_turn.dart +++ b/example/lib/pages/turn_by_turn.dart @@ -321,7 +321,7 @@ class _TurnByTurnPageState extends ExamplePageState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Step #${stepInfo.stepNumber}', + 'Step #${stepInfo.stepNumber ?? "?"}', style: navInfoTextStyle, ), const SizedBox(height: 10), @@ -333,12 +333,12 @@ class _TurnByTurnPageState extends ExamplePageState { text: 'Road: ', style: TextStyle(fontWeight: FontWeight.bold), ), - TextSpan(text: stepInfo.fullRoadName), + TextSpan(text: stepInfo.fullRoadName ?? ''), const TextSpan( text: '\nInstructions: ', style: TextStyle(fontWeight: FontWeight.bold), ), - TextSpan(text: stepInfo.fullInstructions), + TextSpan(text: stepInfo.fullInstructions ?? ''), ], ), ), diff --git a/example/lib/pages/widget_initialization.dart b/example/lib/pages/widget_initialization.dart index 62e82820..532a24c8 100644 --- a/example/lib/pages/widget_initialization.dart +++ b/example/lib/pages/widget_initialization.dart @@ -158,6 +158,18 @@ class _ViewInitializationPageState style: TextStyle(fontWeight: FontWeight.bold), ), children: [ + ListTile( + title: const Text('Map ID'), + subtitle: Text(MapIdManager.instance.mapIdDisplay), + trailing: ElevatedButton( + onPressed: + () => showMapIdDialog( + context, + () => setState(() {}), + ), + child: const Text('Set'), + ), + ), ExampleDropdownButton( title: 'Map Type', value: _initialMapType, @@ -474,6 +486,7 @@ class _InitializedViewPage extends StatelessWidget { initialPadding: initialPadding, initialNavigationUIEnabledPreference: initialNavigationUIEnabledPreference, + mapId: MapIdManager.instance.mapId, ), ); } diff --git a/example/lib/utils/map_id_manager.dart b/example/lib/utils/map_id_manager.dart new file mode 100644 index 00000000..47a4d8ec --- /dev/null +++ b/example/lib/utils/map_id_manager.dart @@ -0,0 +1,44 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ignore_for_file: public_member_api_docs + +/// Manager class for handling map ID configuration. +class MapIdManager { + MapIdManager._(); + + static final MapIdManager _instance = MapIdManager._(); + static MapIdManager get instance => _instance; + + String? _mapId; + + /// Initialize map ID from dart-define or use default value. + /// This should be called once at app startup. + void initialize() { + // Read from dart-define (compile-time constant) + const String dartDefineMapId = String.fromEnvironment('MAP_ID'); + _mapId = dartDefineMapId.isNotEmpty ? dartDefineMapId : null; + } + + /// Get the current map ID. + String? get mapId => _mapId; + + /// Get display string for map ID (for UI display). + String get mapIdDisplay => _mapId ?? ''; + + /// Set map ID manually (used by the UI button). + void setMapId(String? mapId) { + _mapId = mapId?.trim().isEmpty ?? true ? null : mapId; + } +} diff --git a/example/lib/utils/utils.dart b/example/lib/utils/utils.dart index bdd6abca..75ee70a8 100644 --- a/example/lib/utils/utils.dart +++ b/example/lib/utils/utils.dart @@ -14,6 +14,7 @@ export 'constants.dart'; export 'location_permissions.dart'; +export 'map_id_manager.dart'; export 'overlay_options.dart'; export 'remaining_distance.dart'; export 'remaining_time.dart'; diff --git a/example/lib/widgets/map_id_dialog.dart b/example/lib/widgets/map_id_dialog.dart new file mode 100644 index 00000000..0f8a512f --- /dev/null +++ b/example/lib/widgets/map_id_dialog.dart @@ -0,0 +1,78 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ignore_for_file: public_member_api_docs + +import 'package:flutter/material.dart'; + +import '../utils/utils.dart'; + +/// Shows a dialog to set or clear the Map ID. +Future showMapIdDialog( + BuildContext context, + VoidCallback onMapIdChanged, +) async { + final TextEditingController controller = TextEditingController(); + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Set Map ID'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextField( + controller: controller, + decoration: const InputDecoration( + hintText: 'Enter Map ID (leave empty to clear)', + ), + ), + const SizedBox(height: 16), + const Text( + 'Tip: You can also set this at compile time using:', + style: TextStyle(fontSize: 12, fontStyle: FontStyle.italic), + ), + const SizedBox(height: 4), + const Text( + '--dart-define=MAP_ID=your_map_id', + style: TextStyle(fontSize: 11, fontFamily: 'monospace'), + ), + const SizedBox(height: 8), + const Text( + 'See README for more details on Map ID configuration.', + style: TextStyle(fontSize: 12, fontStyle: FontStyle.italic), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + MapIdManager.instance.setMapId( + controller.text.isEmpty ? null : controller.text, + ); + Navigator.of(context).pop(); + onMapIdChanged(); + }, + child: const Text('Set'), + ), + ], + ); + }, + ); +} diff --git a/example/lib/widgets/widgets.dart b/example/lib/widgets/widgets.dart index 0e7ee9fb..858ce626 100644 --- a/example/lib/widgets/widgets.dart +++ b/example/lib/widgets/widgets.dart @@ -16,6 +16,7 @@ export 'camera_position_editor.dart'; export 'dropdown_button.dart'; export 'latlng_bound_editor.dart'; export 'latlng_editor.dart'; +export 'map_id_dialog.dart'; export 'page.dart'; export 'slider_editor.dart'; export 'switch.dart'; diff --git a/ios/google_navigation_flutter.podspec b/ios/google_navigation_flutter.podspec index 342bb61e..ad0f1a92 100644 --- a/ios/google_navigation_flutter.podspec +++ b/ios/google_navigation_flutter.podspec @@ -15,7 +15,7 @@ A Google Maps Navigation Flutter plugin. s.source = { :path => '.' } s.source_files = 'google_navigation_flutter/Sources/google_navigation_flutter/**/*.swift' s.dependency 'Flutter' - s.dependency 'GoogleNavigation', '10.0.0' + s.dependency 'GoogleNavigation', '10.6.0' s.platform = :ios, '16.0' s.static_framework = true diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/google_navigation_flutter/Package.resolved similarity index 68% rename from example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved rename to ios/google_navigation_flutter/Package.resolved index 6a17a6f5..3e19e318 100644 --- a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/google_navigation_flutter/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/googlemaps/ios-maps-sdk", "state" : { - "revision" : "9c540f3b475a800e947a09b8903b212a6634cf30", - "version" : "10.0.0" + "revision" : "71e09fe4c751d59d7b1e524e998f786b429b1b14", + "version" : "10.6.0" } }, { @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/googlemaps/ios-navigation-sdk", "state" : { - "revision" : "a3faa12da9a957420da8e1b448022f365fbc8400", - "version" : "10.0.0" + "revision" : "bcee02b433831d29d1ac12a62038fd009c333f7b", + "version" : "10.6.0" } } ], diff --git a/ios/google_navigation_flutter/Package.swift b/ios/google_navigation_flutter/Package.swift index d9d553e6..297a2af7 100644 --- a/ios/google_navigation_flutter/Package.swift +++ b/ios/google_navigation_flutter/Package.swift @@ -28,11 +28,11 @@ let package = Package( dependencies: [ .package( url: "https://github.com/googlemaps/ios-navigation-sdk", - exact: "10.0.0" + exact: "10.6.0" ), .package( url: "https://github.com/googlemaps/ios-maps-sdk", - exact: "10.0.0" + exact: "10.6.0" ), ], targets: [ diff --git a/ios/google_navigation_flutter/Sources/google_navigation_flutter/Convert+MapConfiguration.swift b/ios/google_navigation_flutter/Sources/google_navigation_flutter/Convert+MapConfiguration.swift index df1ed27e..6002ba35 100644 --- a/ios/google_navigation_flutter/Sources/google_navigation_flutter/Convert+MapConfiguration.swift +++ b/ios/google_navigation_flutter/Sources/google_navigation_flutter/Convert+MapConfiguration.swift @@ -56,7 +56,8 @@ extension Convert { left: CGFloat(mapOptions.padding?.left ?? 0), bottom: CGFloat(mapOptions.padding?.bottom ?? 0), right: CGFloat(mapOptions.padding?.right ?? 0) - ) + ), + mapId: mapOptions.mapId ) } } diff --git a/ios/google_navigation_flutter/Sources/google_navigation_flutter/Convert.swift b/ios/google_navigation_flutter/Sources/google_navigation_flutter/Convert.swift index f1528d13..6e2d78dd 100644 --- a/ios/google_navigation_flutter/Sources/google_navigation_flutter/Convert.swift +++ b/ios/google_navigation_flutter/Sources/google_navigation_flutter/Convert.swift @@ -132,18 +132,20 @@ enum Convert { static func convertStepInfo(_ stepInfo: GMSNavigationStepInfo) -> StepInfoDto { .init( - distanceFromPrevStepMeters: Int64(stepInfo.distanceFromPrevStepMeters), - timeFromPrevStepSeconds: Int64(stepInfo.timeFromPrevStepSeconds), + distanceFromPrevStepMeters: stepInfo.distanceFromPrevStepMeters > 0 + ? Int64(stepInfo.distanceFromPrevStepMeters) : nil, + timeFromPrevStepSeconds: stepInfo.timeFromPrevStepSeconds > 0 + ? Int64(stepInfo.timeFromPrevStepSeconds) : nil, drivingSide: convertDrivingSide(side: stepInfo.drivingSide), exitNumber: stepInfo.exitNumber, fullInstructions: stepInfo.fullInstructionText, fullRoadName: stepInfo.fullRoadName, simpleRoadName: stepInfo.simpleRoadName, roundaboutTurnNumber: stepInfo - .roundaboutTurnNumber >= 0 ? Int64(stepInfo.roundaboutTurnNumber) : 0, - lanes: [], + .roundaboutTurnNumber >= 0 ? Int64(stepInfo.roundaboutTurnNumber) : nil, + lanes: nil, maneuver: convertManeuver(maneuver: stepInfo.maneuver), - stepNumber: Int64(stepInfo.stepNumber) + stepNumber: stepInfo.stepNumber >= 0 ? Int64(stepInfo.stepNumber) : nil ) } diff --git a/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationSessionManager.swift b/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationSessionManager.swift index 41134c1f..05440346 100644 --- a/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationSessionManager.swift +++ b/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationSessionManager.swift @@ -621,4 +621,14 @@ extension GoogleMapsNavigationSessionManager: GMSNavigatorListener { ) } } + + func navigatorWillPresentPrompt(_ navigator: GMSNavigator) { + // Notify all navigation views about prompt visibility change + _viewRegistry?.sendPromptVisibilityChangedToAllViews(promptVisible: true) + } + + func navigatorDidDismissPrompt(_ navigator: GMSNavigator) { + // Notify all navigation views about prompt visibility change + _viewRegistry?.sendPromptVisibilityChangedToAllViews(promptVisible: false) + } } diff --git a/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationView.swift b/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationView.swift index a2e9016e..e79419fa 100644 --- a/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationView.swift +++ b/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationView.swift @@ -338,6 +338,14 @@ public class GoogleMapsNavigationView: NSObject, FlutterPlatformView, ViewSettle _mapView.isTrafficEnabled } + func isBuildingsEnabled() -> Bool { + _mapView.isBuildingsEnabled + } + + func setBuildingsEnabled(_ enabled: Bool) { + _mapView.isBuildingsEnabled = enabled + } + func showRouteOverview() { _mapView.cameraMode = .overview } @@ -538,6 +546,20 @@ public class GoogleMapsNavigationView: NSObject, FlutterPlatformView, ViewSettle _mapView.settings.isNavigationReportIncidentButtonEnabled = enabled } + func isIncidentReportingAvailable() -> Bool { + return _mapView.isIncidentReportingAvailable + } + + func showReportIncidentsPanel() throws { + Task { + do { + try await _mapView.presentReportIncidentsPanel(nil) + } catch { + // Handle error silently + } + } + } + func isTrafficPromptsEnabled() -> Bool { return _isTrafficPromptsEnabled } @@ -1025,6 +1047,14 @@ extension GoogleMapsNavigationView: GMSMapViewDelegate { ) } } + + func sendPromptVisibilityChangedEvent(promptVisible: Bool) { + getViewEventApi()?.onPromptVisibilityChanged( + viewId: _viewId!, + promptVisible: promptVisible, + completion: { _ in } + ) + } } extension MarkerDto { diff --git a/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationViewMessageHandler.swift b/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationViewMessageHandler.swift index c0acc666..521e540b 100644 --- a/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationViewMessageHandler.swift +++ b/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationViewMessageHandler.swift @@ -422,6 +422,22 @@ class GoogleMapsNavigationViewMessageHandler: MapViewApi { try getView(viewId).isReportIncidentButtonEnabled() } + func isIncidentReportingAvailable(viewId: Int64) throws -> Bool { + try getView(viewId).isIncidentReportingAvailable() + } + + func showReportIncidentsPanel(viewId: Int64) throws { + try getView(viewId).showReportIncidentsPanel() + } + + func isBuildingsEnabled(viewId: Int64) throws -> Bool { + try getView(viewId).isBuildingsEnabled() + } + + func setBuildingsEnabled(viewId: Int64, enabled: Bool) throws { + try getView(viewId).setBuildingsEnabled(enabled) + } + func isTrafficPromptsEnabled(viewId: Int64) throws -> Bool { try getView(viewId).isTrafficPromptsEnabled() } diff --git a/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationViewRegistry.swift b/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationViewRegistry.swift index b298825b..d1c7bb82 100644 --- a/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationViewRegistry.swift +++ b/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationViewRegistry.swift @@ -92,4 +92,14 @@ class GoogleMapsNavigationViewRegistry { self.carPlayView } } + + func sendPromptVisibilityChangedToAllViews(promptVisible: Bool) { + queue.sync { + for view in views.values { + view.sendPromptVisibilityChangedEvent(promptVisible: promptVisible) + } + // Also send to CarPlay view if it exists + carPlayView?.sendPromptVisibilityChangedEvent(promptVisible: promptVisible) + } + } } diff --git a/ios/google_navigation_flutter/Sources/google_navigation_flutter/MapConfiguration.swift b/ios/google_navigation_flutter/Sources/google_navigation_flutter/MapConfiguration.swift index 80aa055a..105b29c7 100644 --- a/ios/google_navigation_flutter/Sources/google_navigation_flutter/MapConfiguration.swift +++ b/ios/google_navigation_flutter/Sources/google_navigation_flutter/MapConfiguration.swift @@ -38,6 +38,7 @@ struct MapConfiguration { var minZoomPreference: Float? var maxZoomPreference: Float? var padding: UIEdgeInsets? + var mapId: String? } extension MapConfiguration { @@ -70,6 +71,9 @@ extension MapConfiguration { func apply(to mapViewOptions: GMSMapViewOptions, withFrame frame: CGRect) { mapViewOptions.camera = cameraPosition mapViewOptions.frame = frame + if let mapId { + mapViewOptions.mapID = GMSMapID(identifier: mapId) + } } // Applies camera position from the configuration to the given GMSMapView. diff --git a/ios/google_navigation_flutter/Sources/google_navigation_flutter/messages.g.swift b/ios/google_navigation_flutter/Sources/google_navigation_flutter/messages.g.swift index de9a469c..6fb465f2 100644 --- a/ios/google_navigation_flutter/Sources/google_navigation_flutter/messages.g.swift +++ b/ios/google_navigation_flutter/Sources/google_navigation_flutter/messages.g.swift @@ -497,6 +497,9 @@ struct MapOptionsDto: Hashable { var cameraTargetBounds: LatLngBoundsDto? = nil /// Specifies the padding for the map. var padding: MapPaddingDto? = nil + /// The map ID for advanced map options eg. cloud-based map styling. + /// This value can only be set on map initialization and cannot be changed afterwards. + var mapId: String? = nil // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> MapOptionsDto? { @@ -514,6 +517,7 @@ struct MapOptionsDto: Hashable { let zoomControlsEnabled = pigeonVar_list[11] as! Bool let cameraTargetBounds: LatLngBoundsDto? = nilOrValue(pigeonVar_list[12]) let padding: MapPaddingDto? = nilOrValue(pigeonVar_list[13]) + let mapId: String? = nilOrValue(pigeonVar_list[14]) return MapOptionsDto( cameraPosition: cameraPosition, @@ -529,7 +533,8 @@ struct MapOptionsDto: Hashable { maxZoomPreference: maxZoomPreference, zoomControlsEnabled: zoomControlsEnabled, cameraTargetBounds: cameraTargetBounds, - padding: padding + padding: padding, + mapId: mapId ) } func toList() -> [Any?] { @@ -548,6 +553,7 @@ struct MapOptionsDto: Hashable { zoomControlsEnabled, cameraTargetBounds, padding, + mapId, ] } static func == (lhs: MapOptionsDto, rhs: MapOptionsDto) -> Bool { @@ -1395,7 +1401,9 @@ struct RoutingOptionsDto: Hashable { /// Generated class from Pigeon that represents data sent in messages. struct NavigationDisplayOptionsDto: Hashable { var showDestinationMarkers: Bool? = nil + /// Deprecated: This option now defaults to true. var showStopSigns: Bool? = nil + /// Deprecated: This option now defaults to true. var showTrafficLights: Bool? = nil // swift-format-ignore: AlwaysUseLowerCamelCase @@ -1642,6 +1650,35 @@ struct SpeedingUpdatedEventDto: Hashable { } } +/// Generated class from Pigeon that represents data sent in messages. +struct GpsAvailabilityChangeEventDto: Hashable { + var isGpsLost: Bool + var isGpsValidForNavigation: Bool + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> GpsAvailabilityChangeEventDto? { + let isGpsLost = pigeonVar_list[0] as! Bool + let isGpsValidForNavigation = pigeonVar_list[1] as! Bool + + return GpsAvailabilityChangeEventDto( + isGpsLost: isGpsLost, + isGpsValidForNavigation: isGpsValidForNavigation + ) + } + func toList() -> [Any?] { + return [ + isGpsLost, + isGpsValidForNavigation, + ] + } + static func == (lhs: GpsAvailabilityChangeEventDto, rhs: GpsAvailabilityChangeEventDto) -> Bool { + return deepEqualsmessages(lhs.toList(), rhs.toList()) + } + func hash(into hasher: inout Hasher) { + deepHashmessages(value: toList(), hasher: &hasher) + } +} + /// Generated class from Pigeon that represents data sent in messages. struct SpeedAlertOptionsThresholdPercentageDto: Hashable { var percentage: Double @@ -1876,43 +1913,43 @@ struct LaneDto: Hashable { /// /// Generated class from Pigeon that represents data sent in messages. struct StepInfoDto: Hashable { - /// Distance in meters from the previous step to this step. - var distanceFromPrevStepMeters: Int64 - /// Time in seconds from the previous step to this step. - var timeFromPrevStepSeconds: Int64 + /// Distance in meters from the previous step to this step if available, otherwise null. + var distanceFromPrevStepMeters: Int64? = nil + /// Time in seconds from the previous step to this step if available, otherwise null. + var timeFromPrevStepSeconds: Int64? = nil /// Whether this step is on a drive-on-right or drive-on-left route. var drivingSide: DrivingSideDto /// The exit number if it exists. var exitNumber: String? = nil - /// The full text of the instruction for this step. - var fullInstructions: String - /// The full road name for this step. - var fullRoadName: String - /// The simplified version of the road name. - var simpleRoadName: String + /// The full text of the instruction for this step if available, otherwise null. + var fullInstructions: String? = nil + /// The full road name for this step if available, otherwise null. + var fullRoadName: String? = nil + /// The simplified version of the road name if available, otherwise null. + var simpleRoadName: String? = nil /// The counted number of the exit to take relative to the location where the - /// roundabout was entered. - var roundaboutTurnNumber: Int64 - /// The list of available lanes at the end of this route step. - var lanes: [LaneDto?] + /// roundabout was entered if available, otherwise null. + var roundaboutTurnNumber: Int64? = nil + /// The list of available lanes at the end of this route step if available, otherwise null. + var lanes: [LaneDto]? = nil /// The maneuver for this step. var maneuver: ManeuverDto - /// The index of the step in the list of all steps in the route. - var stepNumber: Int64 + /// The index of the step in the list of all steps in the route if available, otherwise null. + var stepNumber: Int64? = nil // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> StepInfoDto? { - let distanceFromPrevStepMeters = pigeonVar_list[0] as! Int64 - let timeFromPrevStepSeconds = pigeonVar_list[1] as! Int64 + let distanceFromPrevStepMeters: Int64? = nilOrValue(pigeonVar_list[0]) + let timeFromPrevStepSeconds: Int64? = nilOrValue(pigeonVar_list[1]) let drivingSide = pigeonVar_list[2] as! DrivingSideDto let exitNumber: String? = nilOrValue(pigeonVar_list[3]) - let fullInstructions = pigeonVar_list[4] as! String - let fullRoadName = pigeonVar_list[5] as! String - let simpleRoadName = pigeonVar_list[6] as! String - let roundaboutTurnNumber = pigeonVar_list[7] as! Int64 - let lanes = pigeonVar_list[8] as! [LaneDto?] + let fullInstructions: String? = nilOrValue(pigeonVar_list[4]) + let fullRoadName: String? = nilOrValue(pigeonVar_list[5]) + let simpleRoadName: String? = nilOrValue(pigeonVar_list[6]) + let roundaboutTurnNumber: Int64? = nilOrValue(pigeonVar_list[7]) + let lanes: [LaneDto]? = nilOrValue(pigeonVar_list[8]) let maneuver = pigeonVar_list[9] as! ManeuverDto - let stepNumber = pigeonVar_list[10] as! Int64 + let stepNumber: Int64? = nilOrValue(pigeonVar_list[10]) return StepInfoDto( distanceFromPrevStepMeters: distanceFromPrevStepMeters, @@ -2231,23 +2268,25 @@ private class MessagesPigeonCodecReader: FlutterStandardReader { case 181: return SpeedingUpdatedEventDto.fromList(self.readValue() as! [Any?]) case 182: - return SpeedAlertOptionsThresholdPercentageDto.fromList(self.readValue() as! [Any?]) + return GpsAvailabilityChangeEventDto.fromList(self.readValue() as! [Any?]) case 183: - return SpeedAlertOptionsDto.fromList(self.readValue() as! [Any?]) + return SpeedAlertOptionsThresholdPercentageDto.fromList(self.readValue() as! [Any?]) case 184: + return SpeedAlertOptionsDto.fromList(self.readValue() as! [Any?]) + case 185: return RouteSegmentTrafficDataRoadStretchRenderingDataDto.fromList( self.readValue() as! [Any?]) - case 185: - return RouteSegmentTrafficDataDto.fromList(self.readValue() as! [Any?]) case 186: - return RouteSegmentDto.fromList(self.readValue() as! [Any?]) + return RouteSegmentTrafficDataDto.fromList(self.readValue() as! [Any?]) case 187: - return LaneDirectionDto.fromList(self.readValue() as! [Any?]) + return RouteSegmentDto.fromList(self.readValue() as! [Any?]) case 188: - return LaneDto.fromList(self.readValue() as! [Any?]) + return LaneDirectionDto.fromList(self.readValue() as! [Any?]) case 189: - return StepInfoDto.fromList(self.readValue() as! [Any?]) + return LaneDto.fromList(self.readValue() as! [Any?]) case 190: + return StepInfoDto.fromList(self.readValue() as! [Any?]) + case 191: return NavInfoDto.fromList(self.readValue() as! [Any?]) default: return super.readValue(ofType: type) @@ -2416,33 +2455,36 @@ private class MessagesPigeonCodecWriter: FlutterStandardWriter { } else if let value = value as? SpeedingUpdatedEventDto { super.writeByte(181) super.writeValue(value.toList()) - } else if let value = value as? SpeedAlertOptionsThresholdPercentageDto { + } else if let value = value as? GpsAvailabilityChangeEventDto { super.writeByte(182) super.writeValue(value.toList()) - } else if let value = value as? SpeedAlertOptionsDto { + } else if let value = value as? SpeedAlertOptionsThresholdPercentageDto { super.writeByte(183) super.writeValue(value.toList()) - } else if let value = value as? RouteSegmentTrafficDataRoadStretchRenderingDataDto { + } else if let value = value as? SpeedAlertOptionsDto { super.writeByte(184) super.writeValue(value.toList()) - } else if let value = value as? RouteSegmentTrafficDataDto { + } else if let value = value as? RouteSegmentTrafficDataRoadStretchRenderingDataDto { super.writeByte(185) super.writeValue(value.toList()) - } else if let value = value as? RouteSegmentDto { + } else if let value = value as? RouteSegmentTrafficDataDto { super.writeByte(186) super.writeValue(value.toList()) - } else if let value = value as? LaneDirectionDto { + } else if let value = value as? RouteSegmentDto { super.writeByte(187) super.writeValue(value.toList()) - } else if let value = value as? LaneDto { + } else if let value = value as? LaneDirectionDto { super.writeByte(188) super.writeValue(value.toList()) - } else if let value = value as? StepInfoDto { + } else if let value = value as? LaneDto { super.writeByte(189) super.writeValue(value.toList()) - } else if let value = value as? NavInfoDto { + } else if let value = value as? StepInfoDto { super.writeByte(190) super.writeValue(value.toList()) + } else if let value = value as? NavInfoDto { + super.writeByte(191) + super.writeValue(value.toList()) } else { super.writeValue(value) } @@ -2554,6 +2596,10 @@ protocol MapViewApi { func setTrafficPromptsEnabled(viewId: Int64, enabled: Bool) throws func isReportIncidentButtonEnabled(viewId: Int64) throws -> Bool func setReportIncidentButtonEnabled(viewId: Int64, enabled: Bool) throws + func isIncidentReportingAvailable(viewId: Int64) throws -> Bool + func showReportIncidentsPanel(viewId: Int64) throws + func isBuildingsEnabled(viewId: Int64) throws -> Bool + func setBuildingsEnabled(viewId: Int64, enabled: Bool) throws func getCameraPosition(viewId: Int64) throws -> CameraPositionDto func getVisibleRegion(viewId: Int64) throws -> LatLngBoundsDto func followMyLocation(viewId: Int64, perspective: CameraPerspectiveDto, zoomLevel: Double?) throws @@ -3529,6 +3575,79 @@ class MapViewApiSetup { } else { setReportIncidentButtonEnabledChannel.setMessageHandler(nil) } + let isIncidentReportingAvailableChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.google_navigation_flutter.MapViewApi.isIncidentReportingAvailable\(channelSuffix)", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + isIncidentReportingAvailableChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let viewIdArg = args[0] as! Int64 + do { + let result = try api.isIncidentReportingAvailable(viewId: viewIdArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + isIncidentReportingAvailableChannel.setMessageHandler(nil) + } + let showReportIncidentsPanelChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.google_navigation_flutter.MapViewApi.showReportIncidentsPanel\(channelSuffix)", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + showReportIncidentsPanelChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let viewIdArg = args[0] as! Int64 + do { + try api.showReportIncidentsPanel(viewId: viewIdArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + showReportIncidentsPanelChannel.setMessageHandler(nil) + } + let isBuildingsEnabledChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.google_navigation_flutter.MapViewApi.isBuildingsEnabled\(channelSuffix)", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + isBuildingsEnabledChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let viewIdArg = args[0] as! Int64 + do { + let result = try api.isBuildingsEnabled(viewId: viewIdArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + isBuildingsEnabledChannel.setMessageHandler(nil) + } + let setBuildingsEnabledChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.google_navigation_flutter.MapViewApi.setBuildingsEnabled\(channelSuffix)", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + setBuildingsEnabledChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let viewIdArg = args[0] as! Int64 + let enabledArg = args[1] as! Bool + do { + try api.setBuildingsEnabled(viewId: viewIdArg, enabled: enabledArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + setBuildingsEnabledChannel.setMessageHandler(nil) + } let getCameraPositionChannel = FlutterBasicMessageChannel( name: "dev.flutter.pigeon.google_navigation_flutter.MapViewApi.getCameraPosition\(channelSuffix)", @@ -4564,6 +4683,9 @@ protocol ViewEventApiProtocol { func onNavigationUIEnabledChanged( viewId viewIdArg: Int64, navigationUIEnabled navigationUIEnabledArg: Bool, completion: @escaping (Result) -> Void) + func onPromptVisibilityChanged( + viewId viewIdArg: Int64, promptVisible promptVisibleArg: Bool, + completion: @escaping (Result) -> Void) func onMyLocationClicked( viewId viewIdArg: Int64, completion: @escaping (Result) -> Void) func onMyLocationButtonClicked( @@ -4791,6 +4913,29 @@ class ViewEventApi: ViewEventApiProtocol { } } } + func onPromptVisibilityChanged( + viewId viewIdArg: Int64, promptVisible promptVisibleArg: Bool, + completion: @escaping (Result) -> Void + ) { + let channelName: String = + "dev.flutter.pigeon.google_navigation_flutter.ViewEventApi.onPromptVisibilityChanged\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel( + name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([viewIdArg, promptVisibleArg] as [Any?]) { response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(PigeonError(code: code, message: message, details: details))) + } else { + completion(.success(())) + } + } + } func onMyLocationClicked( viewId viewIdArg: Int64, completion: @escaping (Result) -> Void ) { @@ -5552,6 +5697,10 @@ protocol NavigationSessionEventApiProtocol { /// Android-only event. func onGpsAvailabilityUpdate( available availableArg: Bool, completion: @escaping (Result) -> Void) + /// Android-only event. + func onGpsAvailabilityChange( + event eventArg: GpsAvailabilityChangeEventDto, + completion: @escaping (Result) -> Void) /// Turn-by-Turn navigation events. func onNavInfo( navInfo navInfoArg: NavInfoDto, completion: @escaping (Result) -> Void) @@ -5766,6 +5915,30 @@ class NavigationSessionEventApi: NavigationSessionEventApiProtocol { } } } + /// Android-only event. + func onGpsAvailabilityChange( + event eventArg: GpsAvailabilityChangeEventDto, + completion: @escaping (Result) -> Void + ) { + let channelName: String = + "dev.flutter.pigeon.google_navigation_flutter.NavigationSessionEventApi.onGpsAvailabilityChange\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel( + name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([eventArg] as [Any?]) { response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(PigeonError(code: code, message: message, details: details))) + } else { + completion(.success(())) + } + } + } /// Turn-by-Turn navigation events. func onNavInfo( navInfo navInfoArg: NavInfoDto, completion: @escaping (Result) -> Void diff --git a/lib/src/google_maps_map_view.dart b/lib/src/google_maps_map_view.dart index bd933946..bcda2465 100644 --- a/lib/src/google_maps_map_view.dart +++ b/lib/src/google_maps_map_view.dart @@ -44,6 +44,7 @@ abstract class GoogleMapsBaseMapView extends StatefulWidget { this.initialZoomControlsEnabled = true, this.initialCameraTargetBounds, this.initialPadding, + this.mapId, this.layoutDirection, this.gestureRecognizers = const >{}, this.onRecenterButtonClicked, @@ -153,6 +154,19 @@ abstract class GoogleMapsBaseMapView extends StatefulWidget { /// Null by default (no padding). final EdgeInsets? initialPadding; + /// The map ID for enabling various Google Maps Platform features. + /// + /// A map ID is a unique identifier that represents a single map instance. + /// This value can only be set once during map initialization and cannot be changed afterwards. + /// Map IDs are created in Google Cloud Console and can be used to enable various features + /// such as cloud-based map styling, among others. + /// + /// See https://developers.google.com/maps/documentation/get-map-id + /// for more information about map IDs and how to create them. + /// + /// Null by default (no map ID). + final String? mapId; + /// Which gestures should be forwarded to the PlatformView. /// /// When this set is empty, the map will only handle pointer events for gestures that @@ -259,6 +273,7 @@ class GoogleMapsMapView extends GoogleMapsBaseMapView { super.initialZoomControlsEnabled = true, super.initialCameraTargetBounds, super.initialPadding, + super.mapId, super.layoutDirection, super.gestureRecognizers = const >{}, super.onRecenterButtonClicked, @@ -440,6 +455,7 @@ class GoogleMapsMapViewState extends MapViewState { zoomControlsEnabled: widget.initialZoomControlsEnabled, cameraTargetBounds: widget.initialCameraTargetBounds, padding: widget.initialPadding, + mapId: widget.mapId, ), ), onPlatformViewCreated: _onPlatformViewCreated, diff --git a/lib/src/google_maps_map_view_controller.dart b/lib/src/google_maps_map_view_controller.dart index 55225507..4328077a 100644 --- a/lib/src/google_maps_map_view_controller.dart +++ b/lib/src/google_maps_map_view_controller.dart @@ -172,7 +172,7 @@ class GoogleMapViewController { ); } - /// Is the recenter button enabled. + /// Checks if the recenter button is enabled. Future isRecenterButtonEnabled() { return GoogleMapsNavigationPlatform.instance.viewAPI .isRecenterButtonEnabled(viewId: _viewId); @@ -186,6 +186,23 @@ class GoogleMapViewController { .setRecenterButtonEnabled(viewId: _viewId, enabled: enabled); } + /// Checks if the 3D buildings layer is enabled. + Future isBuildingsEnabled() { + return GoogleMapsNavigationPlatform.instance.viewAPI.isBuildingsEnabled( + viewId: _viewId, + ); + } + + /// Enable or disable the 3D buildings layer. + /// + /// By default, the 3D buildings layer is enabled. + Future setBuildingsEnabled(bool enabled) { + return GoogleMapsNavigationPlatform.instance.viewAPI.setBuildingsEnabled( + viewId: _viewId, + enabled: enabled, + ); + } + /// Returns the minimum zoom level preference from the map view. /// If minimum zoom preference is not set previously, returns minimum possible /// zoom level for the current map type. diff --git a/lib/src/google_maps_navigation_view.dart b/lib/src/google_maps_navigation_view.dart index 39e5e460..412445a5 100644 --- a/lib/src/google_maps_navigation_view.dart +++ b/lib/src/google_maps_navigation_view.dart @@ -60,6 +60,7 @@ class GoogleMapsNavigationView extends GoogleMapsBaseMapView { super.initialZoomControlsEnabled = true, super.initialCameraTargetBounds, super.initialPadding, + super.mapId, this.initialNavigationUIEnabledPreference = NavigationUIEnabledPreference.automatic, super.layoutDirection, @@ -78,6 +79,7 @@ class GoogleMapsNavigationView extends GoogleMapsBaseMapView { super.onPolylineClicked, super.onCircleClicked, this.onNavigationUIEnabledChanged, + this.onPromptVisibilityChanged, super.onMyLocationClicked, super.onMyLocationButtonClicked, super.onCameraMoveStarted, @@ -136,6 +138,9 @@ class GoogleMapsNavigationView extends GoogleMapsBaseMapView { /// On navigation UI enabled changed callback. final OnNavigationUIEnabledChanged? onNavigationUIEnabledChanged; + /// On prompt visibility changed callback. + final OnPromptVisibilityChanged? onPromptVisibilityChanged; + /// Creates a [State] for this [GoogleMapsNavigationView]. @override State createState() => GoogleMapsNavigationViewState(); @@ -170,6 +175,7 @@ class GoogleMapsNavigationViewState zoomControlsEnabled: widget.initialZoomControlsEnabled, cameraTargetBounds: widget.initialCameraTargetBounds, padding: widget.initialPadding, + mapId: widget.mapId, ), navigationViewOptions: NavigationViewOptions( navigationUIEnabledPreference: @@ -214,6 +220,13 @@ class GoogleMapsNavigationViewState ); }); } + if (widget.onPromptVisibilityChanged != null) { + GoogleMapsNavigationPlatform.instance.viewAPI + .getPromptVisibilityChangedEventStream(viewId: viewId) + .listen((PromptVisibilityChangedEvent event) { + widget.onPromptVisibilityChanged?.call(event.promptVisible); + }); + } if (widget.onMyLocationButtonClicked != null) { GoogleMapsNavigationPlatform.instance.viewAPI .getMyLocationButtonClickedEventStream(viewId: viewId) diff --git a/lib/src/google_maps_navigation_view_controller.dart b/lib/src/google_maps_navigation_view_controller.dart index c20ea18a..35cfc823 100644 --- a/lib/src/google_maps_navigation_view_controller.dart +++ b/lib/src/google_maps_navigation_view_controller.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import 'package:meta/meta.dart'; + import '../google_navigation_flutter.dart'; import 'google_navigation_flutter_platform_interface.dart'; @@ -24,7 +26,7 @@ class GoogleNavigationViewController extends GoogleMapViewController { /// [GoogleMapsNavigationView.onViewCreated] callback. GoogleNavigationViewController(super.viewId); - /// Is the navigation trip progress bar enabled. + /// Checks if the navigation trip progress bar is enabled. Future isNavigationTripProgressBarEnabled() { return GoogleMapsNavigationPlatform.instance.viewAPI .isNavigationTripProgressBarEnabled(viewId: getViewId()); @@ -33,6 +35,8 @@ class GoogleNavigationViewController extends GoogleMapViewController { /// Enable or disable the navigation trip progress bar. /// /// By default, the navigation trip progress bar is disabled. + /// This feature is experimental and may change in the future. + @experimental Future setNavigationTripProgressBarEnabled(bool enabled) { return GoogleMapsNavigationPlatform.instance.viewAPI .setNavigationTripProgressBarEnabled( @@ -41,7 +45,7 @@ class GoogleNavigationViewController extends GoogleMapViewController { ); } - /// Is the navigation header enabled. + /// Checks if the navigation header is enabled. Future isNavigationHeaderEnabled() { return GoogleMapsNavigationPlatform.instance.viewAPI .isNavigationHeaderEnabled(viewId: getViewId()); @@ -55,7 +59,7 @@ class GoogleNavigationViewController extends GoogleMapViewController { .setNavigationHeaderEnabled(viewId: getViewId(), enabled: enabled); } - /// Is the navigation footer enabled. + /// Checks if the navigation footer is enabled. Future isNavigationFooterEnabled() { return GoogleMapsNavigationPlatform.instance.viewAPI .isNavigationFooterEnabled(viewId: getViewId()); @@ -117,7 +121,7 @@ class GoogleNavigationViewController extends GoogleMapViewController { .setTrafficIncidentCardsEnabled(viewId: getViewId(), enabled: enabled); } - /// Is the report incident button is shown. + /// Checks if the report incident button is shown. Future isReportIncidentButtonEnabled() { return GoogleMapsNavigationPlatform.instance.viewAPI .isReportIncidentButtonEnabled(viewId: getViewId()); @@ -131,6 +135,28 @@ class GoogleNavigationViewController extends GoogleMapViewController { .setReportIncidentButtonEnabled(viewId: getViewId(), enabled: enabled); } + /// Checks if incident reporting is currently available. + /// + /// Returns true if the user can report incidents at the current time, + /// false otherwise. + /// This feature is experimental and may change in the future. + @experimental + Future isIncidentReportingAvailable() { + return GoogleMapsNavigationPlatform.instance.viewAPI + .isIncidentReportingAvailable(viewId: getViewId()); + } + + /// Presents a panel allowing users to report an incident. + /// + /// This method displays the incident reporting UI where users can select + /// and report various types of incidents along the route. + /// This feature is experimental and may change in the future. + @experimental + Future showReportIncidentsPanel() { + return GoogleMapsNavigationPlatform.instance.viewAPI + .showReportIncidentsPanel(viewId: getViewId()); + } + /// Are the traffic prompts shown. Future isTrafficPromptsEnabled() { return GoogleMapsNavigationPlatform.instance.viewAPI diff --git a/lib/src/google_navigation_flutter.dart b/lib/src/google_navigation_flutter.dart index dc92d3cf..a2bbe355 100644 --- a/lib/src/google_navigation_flutter.dart +++ b/lib/src/google_navigation_flutter.dart @@ -46,9 +46,14 @@ typedef OnArrivalEventCallback = void Function(OnArrivalEvent onArrivalEvent); typedef OnReroutingEventCallback = void Function(); /// Called during GPS availability event. (Android only). +@Deprecated('Use OnGpsAvailabilityChangeEventCallback instead') typedef OnGpsAvailabilityEventCallback = void Function(GpsAvailabilityUpdatedEvent gpsAvailabilityUpdatedEvent); +/// Called during GPS availability change event. (Android only). +typedef OnGpsAvailabilityChangeEventCallback = + void Function(GpsAvailabilityChangeEvent gpsAvailabilityChangeEvent); + /// Called during traffic updated event. (Android only) typedef OnTrafficUpdatedEventCallback = void Function(); @@ -110,6 +115,9 @@ typedef OnCircleClicked = void Function(String circleId); /// Called when the [GoogleNavigationViewController.isNavigationUIEnabled] status changes. typedef OnNavigationUIEnabledChanged = void Function(bool navigationUIEnabled); +/// Called when the visibility of navigation prompts changes. +typedef OnPromptVisibilityChanged = void Function(bool promptVisible); + /// Called during my location clicked event. typedef OnMyLocationClicked = void Function(MyLocationClickedEvent); diff --git a/lib/src/method_channel/auto_view_api.dart b/lib/src/method_channel/auto_view_api.dart index 750c31d8..713e299f 100644 --- a/lib/src/method_channel/auto_view_api.dart +++ b/lib/src/method_channel/auto_view_api.dart @@ -310,10 +310,9 @@ class AutoMapViewAPIImpl { duration, ) .then( - (bool success) => - onFinished != null && Platform.isAndroid - ? onFinished(success) - : null, + (bool success) => onFinished != null && Platform.isAndroid + ? onFinished(success) + : null, ), ); case CameraUpdateType.latLng: @@ -321,10 +320,9 @@ class AutoMapViewAPIImpl { _viewApi .animateCameraToLatLng(cameraUpdate.latLng!.toDto(), duration) .then( - (bool success) => - onFinished != null && Platform.isAndroid - ? onFinished(success) - : null, + (bool success) => onFinished != null && Platform.isAndroid + ? onFinished(success) + : null, ), ); case CameraUpdateType.latLngBounds: @@ -336,10 +334,9 @@ class AutoMapViewAPIImpl { duration, ) .then( - (bool success) => - onFinished != null && Platform.isAndroid - ? onFinished(success) - : null, + (bool success) => onFinished != null && Platform.isAndroid + ? onFinished(success) + : null, ), ); case CameraUpdateType.latLngZoom: @@ -351,10 +348,9 @@ class AutoMapViewAPIImpl { duration, ) .then( - (bool success) => - onFinished != null && Platform.isAndroid - ? onFinished(success) - : null, + (bool success) => onFinished != null && Platform.isAndroid + ? onFinished(success) + : null, ), ); case CameraUpdateType.scrollBy: @@ -366,10 +362,9 @@ class AutoMapViewAPIImpl { duration, ) .then( - (bool success) => - onFinished != null && Platform.isAndroid - ? onFinished(success) - : null, + (bool success) => onFinished != null && Platform.isAndroid + ? onFinished(success) + : null, ), ); case CameraUpdateType.zoomBy: @@ -382,10 +377,9 @@ class AutoMapViewAPIImpl { duration, ) .then( - (bool success) => - onFinished != null && Platform.isAndroid - ? onFinished(success) - : null, + (bool success) => onFinished != null && Platform.isAndroid + ? onFinished(success) + : null, ), ); case CameraUpdateType.zoomTo: @@ -393,10 +387,9 @@ class AutoMapViewAPIImpl { _viewApi .animateCameraToZoom(cameraUpdate.zoom!, duration) .then( - (bool success) => - onFinished != null && Platform.isAndroid - ? onFinished(success) - : null, + (bool success) => onFinished != null && Platform.isAndroid + ? onFinished(success) + : null, ), ); } @@ -494,17 +487,17 @@ class AutoMapViewAPIImpl { required List markerOptions, }) async { // Convert options to pigeon format - final List options = - markerOptions.map((MarkerOptions opt) => opt.toDto()).toList(); + final List options = markerOptions + .map((MarkerOptions opt) => opt.toDto()) + .toList(); // Create marker objects with new ID's - final List markersToAdd = - options - .map( - (MarkerOptionsDto options) => - MarkerDto(markerId: _createMarkerId(), options: options), - ) - .toList(); + final List markersToAdd = options + .map( + (MarkerOptionsDto options) => + MarkerDto(markerId: _createMarkerId(), options: options), + ) + .toList(); // Add markers to map final List markersAdded = await _viewApi.addMarkers( @@ -524,8 +517,9 @@ class AutoMapViewAPIImpl { /// Update markers on the map view. Future> updateMarkers({required List markers}) async { try { - final List markerDtos = - markers.map((Marker marker) => marker.toDto()).toList(); + final List markerDtos = markers + .map((Marker marker) => marker.toDto()) + .toList(); final List updatedMarkers = await _viewApi.updateMarkers( markerDtos, ); @@ -545,8 +539,9 @@ class AutoMapViewAPIImpl { /// Remove markers from map view. Future removeMarkers({required List markers}) async { try { - final List markerDtos = - markers.map((Marker marker) => marker.toDto()).toList(); + final List markerDtos = markers + .map((Marker marker) => marker.toDto()) + .toList(); return await _viewApi.removeMarkers(markerDtos); } on PlatformException catch (error) { if (error.code == 'markerNotFound') { @@ -582,17 +577,17 @@ class AutoMapViewAPIImpl { required List polygonOptions, }) async { // Convert options to pigeon format - final List options = - polygonOptions.map((PolygonOptions opt) => opt.toDto()).toList(); + final List options = polygonOptions + .map((PolygonOptions opt) => opt.toDto()) + .toList(); // Create polygon objects with new ID's - final List polygonsToAdd = - options - .map( - (PolygonOptionsDto options) => - PolygonDto(polygonId: _createPolygonId(), options: options), - ) - .toList(); + final List polygonsToAdd = options + .map( + (PolygonOptionsDto options) => + PolygonDto(polygonId: _createPolygonId(), options: options), + ) + .toList(); // Add polygons to map final List polygonsAdded = await _viewApi.addPolygons( @@ -614,8 +609,9 @@ class AutoMapViewAPIImpl { required List polygons, }) async { try { - final List navigationViewPolygons = - polygons.map((Polygon polygon) => polygon.toDto()).toList(); + final List navigationViewPolygons = polygons + .map((Polygon polygon) => polygon.toDto()) + .toList(); final List updatedPolygons = await _viewApi.updatePolygons( navigationViewPolygons, ); @@ -635,8 +631,9 @@ class AutoMapViewAPIImpl { /// Remove polygons from map view. Future removePolygons({required List polygons}) async { try { - final List navigationViewPolygons = - polygons.map((Polygon polygon) => polygon.toDto()).toList(); + final List navigationViewPolygons = polygons + .map((Polygon polygon) => polygon.toDto()) + .toList(); return await _viewApi.removePolygons(navigationViewPolygons); } on PlatformException catch (error) { if (error.code == 'polygonNotFound') { @@ -667,19 +664,17 @@ class AutoMapViewAPIImpl { required List polylineOptions, }) async { // Convert options to pigeon format - final List options = - polylineOptions.map((PolylineOptions opt) => opt.toDto()).toList(); + final List options = polylineOptions + .map((PolylineOptions opt) => opt.toDto()) + .toList(); // Create polyline objects with new ID's - final List polylinesToAdd = - options - .map( - (PolylineOptionsDto options) => PolylineDto( - polylineId: _createPolylineId(), - options: options, - ), - ) - .toList(); + final List polylinesToAdd = options + .map( + (PolylineOptionsDto options) => + PolylineDto(polylineId: _createPolylineId(), options: options), + ) + .toList(); // Add polylines to map final List polylinesAdded = await _viewApi.addPolylines( @@ -701,10 +696,9 @@ class AutoMapViewAPIImpl { required List polylines, }) async { try { - final List navigationViewPolylines = - polylines - .map((Polyline polyline) => polyline.toNavigationViewPolyline()) - .toList(); + final List navigationViewPolylines = polylines + .map((Polyline polyline) => polyline.toNavigationViewPolyline()) + .toList(); final List updatedPolylines = await _viewApi .updatePolylines(navigationViewPolylines); return updatedPolylines @@ -723,10 +717,9 @@ class AutoMapViewAPIImpl { /// Remove polylines from map view. Future removePolylines({required List polylines}) async { try { - final List navigationViewPolylines = - polylines - .map((Polyline polyline) => polyline.toNavigationViewPolyline()) - .toList(); + final List navigationViewPolylines = polylines + .map((Polyline polyline) => polyline.toNavigationViewPolyline()) + .toList(); return await _viewApi.removePolylines(navigationViewPolylines); } on PlatformException catch (error) { if (error.code == 'polylineNotFound') { @@ -757,17 +750,17 @@ class AutoMapViewAPIImpl { required List options, }) async { // Convert options to pigeon format - final List optionsDto = - options.map((CircleOptions opt) => opt.toDto()).toList(); + final List optionsDto = options + .map((CircleOptions opt) => opt.toDto()) + .toList(); // Create circle objects with new ID's - final List circlesToAdd = - optionsDto - .map( - (CircleOptionsDto options) => - CircleDto(circleId: _createCircleId(), options: options), - ) - .toList(); + final List circlesToAdd = optionsDto + .map( + (CircleOptionsDto options) => + CircleDto(circleId: _createCircleId(), options: options), + ) + .toList(); // Add circles to map final List circlesAdded = await _viewApi.addCircles( @@ -787,8 +780,9 @@ class AutoMapViewAPIImpl { /// Update circles on the map view. Future> updateCircles({required List circles}) async { try { - final List navigationViewCircles = - circles.map((Circle circle) => circle.toDto()).toList(); + final List navigationViewCircles = circles + .map((Circle circle) => circle.toDto()) + .toList(); final List updatedCircles = await _viewApi.updateCircles( navigationViewCircles, ); @@ -809,8 +803,9 @@ class AutoMapViewAPIImpl { /// Remove circles from map view. Future removeCircles({required List circles}) async { try { - final List navigationViewCircles = - circles.map((Circle circle) => circle.toDto()).toList(); + final List navigationViewCircles = circles + .map((Circle circle) => circle.toDto()) + .toList(); return await _viewApi.removeCircles(navigationViewCircles); } on PlatformException catch (error) { if (error.code == 'circleNotFound') { diff --git a/lib/src/method_channel/convert/circle.dart b/lib/src/method_channel/convert/circle.dart index f8998461..8d571522 100644 --- a/lib/src/method_channel/convert/circle.dart +++ b/lib/src/method_channel/convert/circle.dart @@ -47,11 +47,10 @@ extension ConvertCircleOptionsDto on CircleOptionsDto { fillColor: Color(fillColor), strokeColor: Color(strokeColor), strokeWidth: strokeWidth, - strokePattern: - strokePattern - .map((PatternItemDto? e) => e?.toPatternItem()) - .whereType() - .toList(), + strokePattern: strokePattern + .map((PatternItemDto? e) => e?.toPatternItem()) + .whereType() + .toList(), visible: visible, zIndex: zIndex, ); diff --git a/lib/src/method_channel/convert/destinations.dart b/lib/src/method_channel/convert/destinations.dart index 95e54a9a..68bdcb12 100644 --- a/lib/src/method_channel/convert/destinations.dart +++ b/lib/src/method_channel/convert/destinations.dart @@ -22,10 +22,9 @@ import 'navigation_waypoint.dart'; extension ConvertDestinations on Destinations { /// Converts [Destinations] to [DestinationsDto] DestinationsDto toDto() => DestinationsDto( - waypoints: - waypoints.map((NavigationWaypoint e) { - return e.toDto(); - }).toList(), + waypoints: waypoints.map((NavigationWaypoint e) { + return e.toDto(); + }).toList(), displayOptions: displayOptions.toDto(), routingOptions: routingOptions?.toDto(), routeTokenOptions: routeTokenOptions?.toDto(), diff --git a/lib/src/method_channel/convert/navigation.dart b/lib/src/method_channel/convert/navigation.dart index cb0881eb..0132a294 100644 --- a/lib/src/method_channel/convert/navigation.dart +++ b/lib/src/method_channel/convert/navigation.dart @@ -161,18 +161,17 @@ extension ConvertRouteSegmentTrafficDataDto on RouteSegmentTrafficDataDto { return RouteSegmentTrafficDataStatus.unavailable; } }(), - roadStretchRenderingDataList: - roadStretchRenderingDataList - .where( - (RouteSegmentTrafficDataRoadStretchRenderingDataDto? d) => - d != null, - ) - .cast() - .map( - (RouteSegmentTrafficDataRoadStretchRenderingDataDto d) => - d.toRouteSegmentTrafficDataRoadStretchRenderingData(), - ) - .toList(), + roadStretchRenderingDataList: roadStretchRenderingDataList + .where( + (RouteSegmentTrafficDataRoadStretchRenderingDataDto? d) => + d != null, + ) + .cast() + .map( + (RouteSegmentTrafficDataRoadStretchRenderingDataDto d) => + d.toRouteSegmentTrafficDataRoadStretchRenderingData(), + ) + .toList(), ); } @@ -186,15 +185,13 @@ extension ConvertRouteSegmentDto on RouteSegmentDto { longitude: destinationLatLng.longitude, ), destinationWaypoint: destinationWaypoint?.toNavigationWaypoint(), - latLngs: - latLngs - ?.where((LatLngDto? p) => p != null) - .cast() - .map( - (LatLngDto p) => - LatLng(latitude: p.latitude, longitude: p.longitude), - ) - .toList(), + latLngs: latLngs + ?.where((LatLngDto? p) => p != null) + .cast() + .map( + (LatLngDto p) => LatLng(latitude: p.latitude, longitude: p.longitude), + ) + .toList(), trafficData: trafficData?.toRouteSegmentTrafficData(), ); } diff --git a/lib/src/method_channel/convert/navinfo.dart b/lib/src/method_channel/convert/navinfo.dart index a5801ba5..a80c57b2 100644 --- a/lib/src/method_channel/convert/navinfo.dart +++ b/lib/src/method_channel/convert/navinfo.dart @@ -21,11 +21,10 @@ extension ConvertNavInfoDto on NavInfoDto { /// Converts [NavInfoDto] to [NavInfo] NavInfo toNavInfo() => NavInfo( currentStep: currentStep?.toStepInfo(), - remainingSteps: - remainingSteps - .whereType() - .map((StepInfoDto stepinfo) => stepinfo.toStepInfo()) - .toList(), + remainingSteps: remainingSteps + .whereType() + .map((StepInfoDto stepinfo) => stepinfo.toStepInfo()) + .toList(), routeChanged: routeChanged, distanceToCurrentStepMeters: distanceToCurrentStepMeters, distanceToNextDestinationMeters: distanceToNextDestinationMeters, @@ -51,11 +50,7 @@ extension ConvertStepInfoDto on StepInfoDto { simpleRoadName: simpleRoadName, roundaboutTurnNumber: roundaboutTurnNumber, stepNumber: stepNumber, - lanes: - lanes - .whereType() - .map((LaneDto lane) => lane.toLane()) - .toList(), + lanes: lanes?.map((LaneDto lane) => lane.toLane()).toList(), maneuver: maneuver.toManeuver(), ); } @@ -241,14 +236,12 @@ extension ConvertManeuverDto on ManeuverDto { extension ConvertLaneDto on LaneDto { /// Converts [LaneDto] to [Lane] Lane toLane() => Lane( - laneDirections: - laneDirections - .whereType() - .map( - (LaneDirectionDto laneDirection) => - laneDirection.toLaneDirection(), - ) - .toList(), + laneDirections: laneDirections + .whereType() + .map( + (LaneDirectionDto laneDirection) => laneDirection.toLaneDirection(), + ) + .toList(), ); } diff --git a/lib/src/method_channel/convert/polygon.dart b/lib/src/method_channel/convert/polygon.dart index 56167cd7..70cf7734 100644 --- a/lib/src/method_channel/convert/polygon.dart +++ b/lib/src/method_channel/convert/polygon.dart @@ -34,14 +34,12 @@ extension ConvertPolygonOptions on PolygonOptions { PolygonOptionsDto toDto() { return PolygonOptionsDto( points: points.map((LatLng point) => point.toDto()).toList(), - holes: - holes - .map( - (List e) => PolygonHoleDto( - points: e.map((LatLng e) => e.toDto()).toList(), - ), - ) - .toList(), + holes: holes + .map( + (List e) => + PolygonHoleDto(points: e.map((LatLng e) => e.toDto()).toList()), + ) + .toList(), clickable: clickable, fillColor: colorToInt(fillColor)!, geodesic: geodesic, diff --git a/lib/src/method_channel/convert/polyline.dart b/lib/src/method_channel/convert/polyline.dart index 67f42da1..c69e6ca9 100644 --- a/lib/src/method_channel/convert/polyline.dart +++ b/lib/src/method_channel/convert/polyline.dart @@ -38,8 +38,9 @@ extension ConvertPolylineOptions on PolylineOptions { geodesic: geodesic, strokeColor: colorToInt(strokeColor), strokeJointType: strokeJointType?.toStrokeJointTypeDto(), - strokePattern: - strokePattern?.map((PatternItem pi) => pi.toDto()).toList(), + strokePattern: strokePattern + ?.map((PatternItem pi) => pi.toDto()) + .toList(), strokeWidth: strokeWidth, visible: visible, zIndex: zIndex, @@ -100,20 +101,18 @@ extension ConvertPolylineOptionsDto on PolylineOptionsDto { /// Convert [PolylineOptionsDto] to [PolylineOptions]. PolylineOptions toPolylineOptions() { return PolylineOptions( - points: - points - ?.map((LatLngDto? point) => point?.toLatLng()) - .whereType() - .toList(), + points: points + ?.map((LatLngDto? point) => point?.toLatLng()) + .whereType() + .toList(), clickable: clickable, geodesic: geodesic, strokeColor: strokeColor != null ? Color(strokeColor!) : null, strokeJointType: strokeJointType?.toStrokeJointType(), - strokePattern: - strokePattern - ?.map((PatternItemDto? pidto) => pidto?.toPatternItem()) - .whereType() - .toList(), + strokePattern: strokePattern + ?.map((PatternItemDto? pidto) => pidto?.toPatternItem()) + .whereType() + .toList(), strokeWidth: strokeWidth, visible: visible, zIndex: zIndex, diff --git a/lib/src/method_channel/image_api.dart b/lib/src/method_channel/image_api.dart index c79edc5d..af431045 100644 --- a/lib/src/method_channel/image_api.dart +++ b/lib/src/method_channel/image_api.dart @@ -64,8 +64,8 @@ class ImageRegistryAPIImpl { /// Get all registered bitmaps from image registry. Future> getRegisteredImages() async { - final List registeredImages = - await _imageApi.getRegisteredImages(); + final List registeredImages = await _imageApi + .getRegisteredImages(); return registeredImages .whereType() .map((ImageDescriptorDto e) => e.toImageDescriptor()) diff --git a/lib/src/method_channel/map_view_api.dart b/lib/src/method_channel/map_view_api.dart index b6a9e007..34465fed 100644 --- a/lib/src/method_channel/map_view_api.dart +++ b/lib/src/method_channel/map_view_api.dart @@ -17,6 +17,7 @@ import 'dart:io'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; +import 'package:meta/meta.dart'; import '../../google_navigation_flutter.dart'; import '../google_navigation_flutter_platform_interface.dart'; @@ -121,15 +122,15 @@ class MapViewAPIImpl { maxZoomPreference: mapOptions.maxZoomPreference, zoomControlsEnabled: mapOptions.zoomControlsEnabled, cameraTargetBounds: mapOptions.cameraTargetBounds?.toDto(), - padding: - mapOptions.padding != null - ? MapPaddingDto( - top: mapOptions.padding!.top.toInt(), - left: mapOptions.padding!.left.toInt(), - bottom: mapOptions.padding!.bottom.toInt(), - right: mapOptions.padding!.right.toInt(), - ) - : null, + padding: mapOptions.padding != null + ? MapPaddingDto( + top: mapOptions.padding!.top.toInt(), + left: mapOptions.padding!.left.toInt(), + bottom: mapOptions.padding!.bottom.toInt(), + right: mapOptions.padding!.right.toInt(), + ) + : null, + mapId: mapOptions.mapId, ); // Initialize navigation view options if given @@ -153,10 +154,9 @@ class MapViewAPIImpl { // Build ViewCreationMessage return ViewCreationOptionsDto( - mapViewType: - mapViewType == MapViewType.navigation - ? MapViewTypeDto.navigation - : MapViewTypeDto.map, + mapViewType: mapViewType == MapViewType.navigation + ? MapViewTypeDto.navigation + : MapViewTypeDto.map, mapOptions: mapOptionsMessage, navigationViewOptions: navigationOptionsMessage, ); @@ -426,10 +426,9 @@ class MapViewAPIImpl { duration, ) .then( - (bool success) => - onFinished != null && Platform.isAndroid - ? onFinished(success) - : null, + (bool success) => onFinished != null && Platform.isAndroid + ? onFinished(success) + : null, ), ); case CameraUpdateType.latLng: @@ -441,10 +440,9 @@ class MapViewAPIImpl { duration, ) .then( - (bool success) => - onFinished != null && Platform.isAndroid - ? onFinished(success) - : null, + (bool success) => onFinished != null && Platform.isAndroid + ? onFinished(success) + : null, ), ); case CameraUpdateType.latLngBounds: @@ -457,10 +455,9 @@ class MapViewAPIImpl { duration, ) .then( - (bool success) => - onFinished != null && Platform.isAndroid - ? onFinished(success) - : null, + (bool success) => onFinished != null && Platform.isAndroid + ? onFinished(success) + : null, ), ); case CameraUpdateType.latLngZoom: @@ -473,10 +470,9 @@ class MapViewAPIImpl { duration, ) .then( - (bool success) => - onFinished != null && Platform.isAndroid - ? onFinished(success) - : null, + (bool success) => onFinished != null && Platform.isAndroid + ? onFinished(success) + : null, ), ); case CameraUpdateType.scrollBy: @@ -489,10 +485,9 @@ class MapViewAPIImpl { duration, ) .then( - (bool success) => - onFinished != null && Platform.isAndroid - ? onFinished(success) - : null, + (bool success) => onFinished != null && Platform.isAndroid + ? onFinished(success) + : null, ), ); case CameraUpdateType.zoomBy: @@ -506,10 +501,9 @@ class MapViewAPIImpl { duration, ) .then( - (bool success) => - onFinished != null && Platform.isAndroid - ? onFinished(success) - : null, + (bool success) => onFinished != null && Platform.isAndroid + ? onFinished(success) + : null, ), ); case CameraUpdateType.zoomTo: @@ -517,10 +511,9 @@ class MapViewAPIImpl { _viewApi .animateCameraToZoom(viewId, cameraUpdate.zoom!, duration) .then( - (bool success) => - onFinished != null && Platform.isAndroid - ? onFinished(success) - : null, + (bool success) => onFinished != null && Platform.isAndroid + ? onFinished(success) + : null, ), ); } @@ -583,7 +576,7 @@ class MapViewAPIImpl { return _viewApi.followMyLocation(viewId, perspective.toDto(), zoomLevel); } - /// Is the navigation trip progress bar enabled. + /// Checks if the navigation trip progress bar is enabled. Future isNavigationTripProgressBarEnabled({required int viewId}) { return _viewApi.isNavigationTripProgressBarEnabled(viewId); } @@ -596,7 +589,7 @@ class MapViewAPIImpl { return _viewApi.setNavigationTripProgressBarEnabled(viewId, enabled); } - /// Is the navigation header enabled. + /// Checks if the navigation header is enabled. Future isNavigationHeaderEnabled({required int viewId}) { return _viewApi.isNavigationHeaderEnabled(viewId); } @@ -609,7 +602,7 @@ class MapViewAPIImpl { return _viewApi.setNavigationHeaderEnabled(viewId, enabled); } - /// Is the navigation footer enabled. + /// Checks if the navigation footer is enabled. Future isNavigationFooterEnabled({required int viewId}) { return _viewApi.isNavigationFooterEnabled(viewId); } @@ -622,7 +615,7 @@ class MapViewAPIImpl { return _viewApi.setNavigationFooterEnabled(viewId, enabled); } - /// Is the recenter button enabled. + /// Checks if the recenter button is enabled. Future isRecenterButtonEnabled({required int viewId}) { return _viewApi.isRecenterButtonEnabled(viewId); } @@ -635,7 +628,7 @@ class MapViewAPIImpl { return _viewApi.setRecenterButtonEnabled(viewId, enabled); } - /// Is the speed limit displayed. + /// Checks if the speed limit icon is displayed. Future isSpeedLimitIconEnabled({required int viewId}) { return _viewApi.isSpeedLimitIconEnabled(viewId); } @@ -648,7 +641,7 @@ class MapViewAPIImpl { return _viewApi.setSpeedLimitIconEnabled(viewId, enabled); } - /// Is speedometer displayed. + /// Checks if the speedometer is displayed. Future isSpeedometerEnabled({required int viewId}) { return _viewApi.isSpeedometerEnabled(viewId); } @@ -661,7 +654,7 @@ class MapViewAPIImpl { return _viewApi.setSpeedometerEnabled(viewId, enabled); } - /// Is incident cards displayed. + /// Checks if incident cards are displayed. Future isTrafficIncidentCardsEnabled({required int viewId}) { return _viewApi.isTrafficIncidentCardsEnabled(viewId); } @@ -674,7 +667,7 @@ class MapViewAPIImpl { return _viewApi.setTrafficIncidentCardsEnabled(viewId, enabled); } - /// Is the report incident button displayed. + /// Checks if the report incident button displayed. Future isReportIncidentButtonEnabled({required int viewId}) { return _viewApi.isReportIncidentButtonEnabled(viewId); } @@ -687,6 +680,31 @@ class MapViewAPIImpl { return _viewApi.setReportIncidentButtonEnabled(viewId, enabled); } + /// Checks if incident reporting is currently available. + @experimental + Future isIncidentReportingAvailable({required int viewId}) { + return _viewApi.isIncidentReportingAvailable(viewId); + } + + /// Presents a panel allowing users to report an incident. + @experimental + Future showReportIncidentsPanel({required int viewId}) { + return _viewApi.showReportIncidentsPanel(viewId); + } + + /// Checks if 3D buildings layer is enabled. + Future isBuildingsEnabled({required int viewId}) { + return _viewApi.isBuildingsEnabled(viewId); + } + + /// Turns the 3D buildings layer on or off. + Future setBuildingsEnabled({ + required int viewId, + required bool enabled, + }) { + return _viewApi.setBuildingsEnabled(viewId, enabled); + } + /// Are the traffic prompts displayed. Future isTrafficPromptsEnabled({required int viewId}) { return _viewApi.isTrafficPromptsEnabled(viewId); @@ -700,7 +718,7 @@ class MapViewAPIImpl { return _viewApi.setTrafficPromptsEnabled(viewId, enabled); } - /// Is navigation UI enabled. + /// Checks if navigation UI is enabled. Future isNavigationUIEnabled({required int viewId}) { return _viewApi.isNavigationUIEnabled(viewId); } @@ -788,17 +806,17 @@ class MapViewAPIImpl { required List markerOptions, }) async { // Convert options to pigeon format - final List options = - markerOptions.map((MarkerOptions opt) => opt.toDto()).toList(); + final List options = markerOptions + .map((MarkerOptions opt) => opt.toDto()) + .toList(); // Create marker objects with new ID's - final List markersToAdd = - options - .map( - (MarkerOptionsDto options) => - MarkerDto(markerId: _createMarkerId(), options: options), - ) - .toList(); + final List markersToAdd = options + .map( + (MarkerOptionsDto options) => + MarkerDto(markerId: _createMarkerId(), options: options), + ) + .toList(); // Add markers to map final List markersAdded = await _viewApi.addMarkers( @@ -822,8 +840,9 @@ class MapViewAPIImpl { required List markers, }) async { try { - final List markerDtos = - markers.map((Marker marker) => marker.toDto()).toList(); + final List markerDtos = markers + .map((Marker marker) => marker.toDto()) + .toList(); final List updatedMarkers = await _viewApi.updateMarkers( viewId, markerDtos, @@ -847,8 +866,9 @@ class MapViewAPIImpl { required List markers, }) async { try { - final List markerDtos = - markers.map((Marker marker) => marker.toDto()).toList(); + final List markerDtos = markers + .map((Marker marker) => marker.toDto()) + .toList(); return await _viewApi.removeMarkers(viewId, markerDtos); } on PlatformException catch (error) { if (error.code == 'markerNotFound') { @@ -885,17 +905,17 @@ class MapViewAPIImpl { required List polygonOptions, }) async { // Convert options to pigeon format - final List options = - polygonOptions.map((PolygonOptions opt) => opt.toDto()).toList(); + final List options = polygonOptions + .map((PolygonOptions opt) => opt.toDto()) + .toList(); // Create polygon objects with new ID's - final List polygonsToAdd = - options - .map( - (PolygonOptionsDto options) => - PolygonDto(polygonId: _createPolygonId(), options: options), - ) - .toList(); + final List polygonsToAdd = options + .map( + (PolygonOptionsDto options) => + PolygonDto(polygonId: _createPolygonId(), options: options), + ) + .toList(); // Add polygons to map final List polygonsAdded = await _viewApi.addPolygons( @@ -919,8 +939,9 @@ class MapViewAPIImpl { required List polygons, }) async { try { - final List navigationViewPolygons = - polygons.map((Polygon polygon) => polygon.toDto()).toList(); + final List navigationViewPolygons = polygons + .map((Polygon polygon) => polygon.toDto()) + .toList(); final List updatedPolygons = await _viewApi.updatePolygons( viewId, navigationViewPolygons, @@ -944,8 +965,9 @@ class MapViewAPIImpl { required List polygons, }) async { try { - final List navigationViewPolygons = - polygons.map((Polygon polygon) => polygon.toDto()).toList(); + final List navigationViewPolygons = polygons + .map((Polygon polygon) => polygon.toDto()) + .toList(); return await _viewApi.removePolygons(viewId, navigationViewPolygons); } on PlatformException catch (error) { if (error.code == 'polygonNotFound') { @@ -977,19 +999,17 @@ class MapViewAPIImpl { required List polylineOptions, }) async { // Convert options to pigeon format - final List options = - polylineOptions.map((PolylineOptions opt) => opt.toDto()).toList(); + final List options = polylineOptions + .map((PolylineOptions opt) => opt.toDto()) + .toList(); // Create polyline objects with new ID's - final List polylinesToAdd = - options - .map( - (PolylineOptionsDto options) => PolylineDto( - polylineId: _createPolylineId(), - options: options, - ), - ) - .toList(); + final List polylinesToAdd = options + .map( + (PolylineOptionsDto options) => + PolylineDto(polylineId: _createPolylineId(), options: options), + ) + .toList(); // Add polylines to map final List polylinesAdded = await _viewApi.addPolylines( @@ -1013,10 +1033,9 @@ class MapViewAPIImpl { required List polylines, }) async { try { - final List navigationViewPolylines = - polylines - .map((Polyline polyline) => polyline.toNavigationViewPolyline()) - .toList(); + final List navigationViewPolylines = polylines + .map((Polyline polyline) => polyline.toNavigationViewPolyline()) + .toList(); final List updatedPolylines = await _viewApi .updatePolylines(viewId, navigationViewPolylines); return updatedPolylines @@ -1038,10 +1057,9 @@ class MapViewAPIImpl { required List polylines, }) async { try { - final List navigationViewPolylines = - polylines - .map((Polyline polyline) => polyline.toNavigationViewPolyline()) - .toList(); + final List navigationViewPolylines = polylines + .map((Polyline polyline) => polyline.toNavigationViewPolyline()) + .toList(); return await _viewApi.removePolylines(viewId, navigationViewPolylines); } on PlatformException catch (error) { if (error.code == 'polylineNotFound') { @@ -1073,17 +1091,17 @@ class MapViewAPIImpl { required List options, }) async { // Convert options to pigeon format - final List optionsDto = - options.map((CircleOptions opt) => opt.toDto()).toList(); + final List optionsDto = options + .map((CircleOptions opt) => opt.toDto()) + .toList(); // Create circle objects with new ID's - final List circlesToAdd = - optionsDto - .map( - (CircleOptionsDto options) => - CircleDto(circleId: _createCircleId(), options: options), - ) - .toList(); + final List circlesToAdd = optionsDto + .map( + (CircleOptionsDto options) => + CircleDto(circleId: _createCircleId(), options: options), + ) + .toList(); // Add circles to map final List circlesAdded = await _viewApi.addCircles( @@ -1107,8 +1125,9 @@ class MapViewAPIImpl { required List circles, }) async { try { - final List navigationViewCircles = - circles.map((Circle circle) => circle.toDto()).toList(); + final List navigationViewCircles = circles + .map((Circle circle) => circle.toDto()) + .toList(); final List updatedCircles = await _viewApi.updateCircles( viewId, navigationViewCircles, @@ -1133,8 +1152,9 @@ class MapViewAPIImpl { required List circles, }) async { try { - final List navigationViewCircles = - circles.map((Circle circle) => circle.toDto()).toList(); + final List navigationViewCircles = circles + .map((Circle circle) => circle.toDto()) + .toList(); return await _viewApi.removeCircles(viewId, navigationViewCircles); } on PlatformException catch (error) { if (error.code == 'circleNotFound') { @@ -1224,6 +1244,13 @@ class MapViewAPIImpl { return _unwrapEventStream(viewId: viewId); } + /// Get prompt visibility changed event stream from the navigation view. + Stream getPromptVisibilityChangedEventStream({ + required int viewId, + }) { + return _unwrapEventStream(viewId: viewId); + } + /// Get navigation view my location clicked event stream from the navigation view. Stream getMyLocationClickedEventStream({ required int viewId, @@ -1331,6 +1358,13 @@ class ViewEventApiImpl implements ViewEventApi { ); } + @override + void onPromptVisibilityChanged(int viewId, bool promptVisible) { + _viewEventStreamController.add( + _ViewIdEventWrapper(viewId, PromptVisibilityChangedEvent(promptVisible)), + ); + } + @override void onMyLocationClicked(int viewId) { _viewEventStreamController.add( diff --git a/lib/src/method_channel/messages.g.dart b/lib/src/method_channel/messages.g.dart index fcb40666..0d93812a 100644 --- a/lib/src/method_channel/messages.g.dart +++ b/lib/src/method_channel/messages.g.dart @@ -430,6 +430,7 @@ class MapOptionsDto { required this.zoomControlsEnabled, this.cameraTargetBounds, this.padding, + this.mapId, }); /// The initial positioning of the camera in the map view. @@ -475,6 +476,10 @@ class MapOptionsDto { /// Specifies the padding for the map. MapPaddingDto? padding; + /// The map ID for advanced map options eg. cloud-based map styling. + /// This value can only be set on map initialization and cannot be changed afterwards. + String? mapId; + List _toList() { return [ cameraPosition, @@ -491,6 +496,7 @@ class MapOptionsDto { zoomControlsEnabled, cameraTargetBounds, padding, + mapId, ]; } @@ -515,6 +521,7 @@ class MapOptionsDto { zoomControlsEnabled: result[11]! as bool, cameraTargetBounds: result[12] as LatLngBoundsDto?, padding: result[13] as MapPaddingDto?, + mapId: result[14] as String?, ); } @@ -1704,8 +1711,10 @@ class NavigationDisplayOptionsDto { bool? showDestinationMarkers; + /// Deprecated: This option now defaults to true. bool? showStopSigns; + /// Deprecated: This option now defaults to true. bool? showTrafficLights; List _toList() { @@ -2051,6 +2060,50 @@ class SpeedingUpdatedEventDto { int get hashCode => Object.hashAll(_toList()); } +class GpsAvailabilityChangeEventDto { + GpsAvailabilityChangeEventDto({ + required this.isGpsLost, + required this.isGpsValidForNavigation, + }); + + bool isGpsLost; + + bool isGpsValidForNavigation; + + List _toList() { + return [isGpsLost, isGpsValidForNavigation]; + } + + Object encode() { + return _toList(); + } + + static GpsAvailabilityChangeEventDto decode(Object result) { + result as List; + return GpsAvailabilityChangeEventDto( + isGpsLost: result[0]! as bool, + isGpsValidForNavigation: result[1]! as bool, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! GpsAvailabilityChangeEventDto || + other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(encode(), other.encode()); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hashAll(_toList()); +} + class SpeedAlertOptionsThresholdPercentageDto { SpeedAlertOptionsThresholdPercentageDto({ required this.percentage, @@ -2220,9 +2273,8 @@ class RouteSegmentTrafficDataDto { result as List; return RouteSegmentTrafficDataDto( status: result[0]! as RouteSegmentTrafficDataStatusDto, - roadStretchRenderingDataList: - (result[1] as List?)! - .cast(), + roadStretchRenderingDataList: (result[1] as List?)! + .cast(), ); } @@ -2385,24 +2437,24 @@ class LaneDto { /// Information about a single step along a navigation route. class StepInfoDto { StepInfoDto({ - required this.distanceFromPrevStepMeters, - required this.timeFromPrevStepSeconds, + this.distanceFromPrevStepMeters, + this.timeFromPrevStepSeconds, required this.drivingSide, this.exitNumber, - required this.fullInstructions, - required this.fullRoadName, - required this.simpleRoadName, - required this.roundaboutTurnNumber, - required this.lanes, + this.fullInstructions, + this.fullRoadName, + this.simpleRoadName, + this.roundaboutTurnNumber, + this.lanes, required this.maneuver, - required this.stepNumber, + this.stepNumber, }); - /// Distance in meters from the previous step to this step. - int distanceFromPrevStepMeters; + /// Distance in meters from the previous step to this step if available, otherwise null. + int? distanceFromPrevStepMeters; - /// Time in seconds from the previous step to this step. - int timeFromPrevStepSeconds; + /// Time in seconds from the previous step to this step if available, otherwise null. + int? timeFromPrevStepSeconds; /// Whether this step is on a drive-on-right or drive-on-left route. DrivingSideDto drivingSide; @@ -2410,27 +2462,27 @@ class StepInfoDto { /// The exit number if it exists. String? exitNumber; - /// The full text of the instruction for this step. - String fullInstructions; + /// The full text of the instruction for this step if available, otherwise null. + String? fullInstructions; - /// The full road name for this step. - String fullRoadName; + /// The full road name for this step if available, otherwise null. + String? fullRoadName; - /// The simplified version of the road name. - String simpleRoadName; + /// The simplified version of the road name if available, otherwise null. + String? simpleRoadName; /// The counted number of the exit to take relative to the location where the - /// roundabout was entered. - int roundaboutTurnNumber; + /// roundabout was entered if available, otherwise null. + int? roundaboutTurnNumber; - /// The list of available lanes at the end of this route step. - List lanes; + /// The list of available lanes at the end of this route step if available, otherwise null. + List? lanes; /// The maneuver for this step. ManeuverDto maneuver; - /// The index of the step in the list of all steps in the route. - int stepNumber; + /// The index of the step in the list of all steps in the route if available, otherwise null. + int? stepNumber; List _toList() { return [ @@ -2455,17 +2507,17 @@ class StepInfoDto { static StepInfoDto decode(Object result) { result as List; return StepInfoDto( - distanceFromPrevStepMeters: result[0]! as int, - timeFromPrevStepSeconds: result[1]! as int, + distanceFromPrevStepMeters: result[0] as int?, + timeFromPrevStepSeconds: result[1] as int?, drivingSide: result[2]! as DrivingSideDto, exitNumber: result[3] as String?, - fullInstructions: result[4]! as String, - fullRoadName: result[5]! as String, - simpleRoadName: result[6]! as String, - roundaboutTurnNumber: result[7]! as int, - lanes: (result[8] as List?)!.cast(), + fullInstructions: result[4] as String?, + fullRoadName: result[5] as String?, + simpleRoadName: result[6] as String?, + roundaboutTurnNumber: result[7] as int?, + lanes: (result[8] as List?)?.cast(), maneuver: result[9]! as ManeuverDto, - stepNumber: result[10]! as int, + stepNumber: result[10] as int?, ); } @@ -2759,33 +2811,36 @@ class _PigeonCodec extends StandardMessageCodec { } else if (value is SpeedingUpdatedEventDto) { buffer.putUint8(181); writeValue(buffer, value.encode()); - } else if (value is SpeedAlertOptionsThresholdPercentageDto) { + } else if (value is GpsAvailabilityChangeEventDto) { buffer.putUint8(182); writeValue(buffer, value.encode()); - } else if (value is SpeedAlertOptionsDto) { + } else if (value is SpeedAlertOptionsThresholdPercentageDto) { buffer.putUint8(183); writeValue(buffer, value.encode()); - } else if (value is RouteSegmentTrafficDataRoadStretchRenderingDataDto) { + } else if (value is SpeedAlertOptionsDto) { buffer.putUint8(184); writeValue(buffer, value.encode()); - } else if (value is RouteSegmentTrafficDataDto) { + } else if (value is RouteSegmentTrafficDataRoadStretchRenderingDataDto) { buffer.putUint8(185); writeValue(buffer, value.encode()); - } else if (value is RouteSegmentDto) { + } else if (value is RouteSegmentTrafficDataDto) { buffer.putUint8(186); writeValue(buffer, value.encode()); - } else if (value is LaneDirectionDto) { + } else if (value is RouteSegmentDto) { buffer.putUint8(187); writeValue(buffer, value.encode()); - } else if (value is LaneDto) { + } else if (value is LaneDirectionDto) { buffer.putUint8(188); writeValue(buffer, value.encode()); - } else if (value is StepInfoDto) { + } else if (value is LaneDto) { buffer.putUint8(189); writeValue(buffer, value.encode()); - } else if (value is NavInfoDto) { + } else if (value is StepInfoDto) { buffer.putUint8(190); writeValue(buffer, value.encode()); + } else if (value is NavInfoDto) { + buffer.putUint8(191); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -2851,7 +2906,7 @@ class _PigeonCodec extends StandardMessageCodec { return value == null ? null : RouteSegmentTrafficDataRoadStretchRenderingDataStyleDto - .values[value]; + .values[value]; case 146: final int? value = readValue(buffer) as int?; return value == null ? null : ManeuverDto.values[value]; @@ -2930,26 +2985,28 @@ class _PigeonCodec extends StandardMessageCodec { case 181: return SpeedingUpdatedEventDto.decode(readValue(buffer)!); case 182: + return GpsAvailabilityChangeEventDto.decode(readValue(buffer)!); + case 183: return SpeedAlertOptionsThresholdPercentageDto.decode( readValue(buffer)!, ); - case 183: - return SpeedAlertOptionsDto.decode(readValue(buffer)!); case 184: + return SpeedAlertOptionsDto.decode(readValue(buffer)!); + case 185: return RouteSegmentTrafficDataRoadStretchRenderingDataDto.decode( readValue(buffer)!, ); - case 185: - return RouteSegmentTrafficDataDto.decode(readValue(buffer)!); case 186: - return RouteSegmentDto.decode(readValue(buffer)!); + return RouteSegmentTrafficDataDto.decode(readValue(buffer)!); case 187: - return LaneDirectionDto.decode(readValue(buffer)!); + return RouteSegmentDto.decode(readValue(buffer)!); case 188: - return LaneDto.decode(readValue(buffer)!); + return LaneDirectionDto.decode(readValue(buffer)!); case 189: - return StepInfoDto.decode(readValue(buffer)!); + return LaneDto.decode(readValue(buffer)!); case 190: + return StepInfoDto.decode(readValue(buffer)!); + case 191: return NavInfoDto.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -2971,8 +3028,9 @@ class ViewCreationApi { BinaryMessenger? binaryMessenger, String messageChannelSuffix = '', }) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty + ? '.$messageChannelSuffix' + : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -3015,8 +3073,9 @@ class MapViewApi { BinaryMessenger? binaryMessenger, String messageChannelSuffix = '', }) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty + ? '.$messageChannelSuffix' + : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -4470,6 +4529,124 @@ class MapViewApi { } } + Future isIncidentReportingAvailable(int viewId) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.google_navigation_flutter.MapViewApi.isIncidentReportingAvailable$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [viewId], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } + + Future showReportIncidentsPanel(int viewId) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.google_navigation_flutter.MapViewApi.showReportIncidentsPanel$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [viewId], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future isBuildingsEnabled(int viewId) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.google_navigation_flutter.MapViewApi.isBuildingsEnabled$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [viewId], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } + + Future setBuildingsEnabled(int viewId, bool enabled) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.google_navigation_flutter.MapViewApi.setBuildingsEnabled$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [viewId, enabled], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + Future getCameraPosition(int viewId) async { final String pigeonVar_channelName = 'dev.flutter.pigeon.google_navigation_flutter.MapViewApi.getCameraPosition$pigeonVar_messageChannelSuffix'; @@ -5955,8 +6132,9 @@ class ImageRegistryApi { BinaryMessenger? binaryMessenger, String messageChannelSuffix = '', }) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty + ? '.$messageChannelSuffix' + : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -6111,6 +6289,8 @@ abstract class ViewEventApi { void onNavigationUIEnabledChanged(int viewId, bool navigationUIEnabled); + void onPromptVisibilityChanged(int viewId, bool promptVisible); + void onMyLocationClicked(int viewId); void onMyLocationButtonClicked(int viewId); @@ -6126,8 +6306,9 @@ abstract class ViewEventApi { BinaryMessenger? binaryMessenger, String messageChannelSuffix = '', }) { - messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + messageChannelSuffix = messageChannelSuffix.isNotEmpty + ? '.$messageChannelSuffix' + : ''; { final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( @@ -6499,6 +6680,45 @@ abstract class ViewEventApi { }); } } + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.google_navigation_flutter.ViewEventApi.onPromptVisibilityChanged$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.google_navigation_flutter.ViewEventApi.onPromptVisibilityChanged was null.', + ); + final List args = (message as List?)!; + final int? arg_viewId = (args[0] as int?); + assert( + arg_viewId != null, + 'Argument for dev.flutter.pigeon.google_navigation_flutter.ViewEventApi.onPromptVisibilityChanged was null, expected non-null int.', + ); + final bool? arg_promptVisible = (args[1] as bool?); + assert( + arg_promptVisible != null, + 'Argument for dev.flutter.pigeon.google_navigation_flutter.ViewEventApi.onPromptVisibilityChanged was null, expected non-null bool.', + ); + try { + api.onPromptVisibilityChanged(arg_viewId!, arg_promptVisible!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } { final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( @@ -6624,8 +6844,9 @@ class NavigationSessionApi { BinaryMessenger? binaryMessenger, String messageChannelSuffix = '', }) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty + ? '.$messageChannelSuffix' + : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -7629,6 +7850,9 @@ abstract class NavigationSessionEventApi { /// Android-only event. void onGpsAvailabilityUpdate(bool available); + /// Android-only event. + void onGpsAvailabilityChange(GpsAvailabilityChangeEventDto event); + /// Turn-by-Turn navigation events. void onNavInfo(NavInfoDto navInfo); @@ -7641,8 +7865,9 @@ abstract class NavigationSessionEventApi { BinaryMessenger? binaryMessenger, String messageChannelSuffix = '', }) { - messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + messageChannelSuffix = messageChannelSuffix.isNotEmpty + ? '.$messageChannelSuffix' + : ''; { final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( @@ -7929,6 +8154,41 @@ abstract class NavigationSessionEventApi { }); } } + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.google_navigation_flutter.NavigationSessionEventApi.onGpsAvailabilityChange$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.google_navigation_flutter.NavigationSessionEventApi.onGpsAvailabilityChange was null.', + ); + final List args = (message as List?)!; + final GpsAvailabilityChangeEventDto? arg_event = + (args[0] as GpsAvailabilityChangeEventDto?); + assert( + arg_event != null, + 'Argument for dev.flutter.pigeon.google_navigation_flutter.NavigationSessionEventApi.onGpsAvailabilityChange was null, expected non-null GpsAvailabilityChangeEventDto.', + ); + try { + api.onGpsAvailabilityChange(arg_event!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } { final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( @@ -7998,8 +8258,9 @@ class AutoMapViewApi { BinaryMessenger? binaryMessenger, String messageChannelSuffix = '', }) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty + ? '.$messageChannelSuffix' + : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -10206,8 +10467,9 @@ abstract class AutoViewEventApi { BinaryMessenger? binaryMessenger, String messageChannelSuffix = '', }) { - messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + messageChannelSuffix = messageChannelSuffix.isNotEmpty + ? '.$messageChannelSuffix' + : ''; { final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( @@ -10292,8 +10554,9 @@ class NavigationInspector { BinaryMessenger? binaryMessenger, String messageChannelSuffix = '', }) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty + ? '.$messageChannelSuffix' + : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); diff --git a/lib/src/method_channel/session_api.dart b/lib/src/method_channel/session_api.dart index c74093c7..7c60fa5b 100644 --- a/lib/src/method_channel/session_api.dart +++ b/lib/src/method_channel/session_api.dart @@ -248,8 +248,8 @@ class NavigationSessionAPIImpl { @Deprecated('Use setDestinations with an updated list of waypoints instead') Future continueToNextDestination() async { try { - final NavigationWaypointDto? waypointDto = - await _sessionApi.continueToNextDestination(); + final NavigationWaypointDto? waypointDto = await _sessionApi + .continueToNextDestination(); if (waypointDto == null) { return null; } @@ -267,8 +267,8 @@ class NavigationSessionAPIImpl { /// Gets current time and distance left. Future getCurrentTimeAndDistance() async { try { - final NavigationTimeAndDistanceDto timeAndDistance = - await _sessionApi.getCurrentTimeAndDistance(); + final NavigationTimeAndDistanceDto timeAndDistance = await _sessionApi + .getCurrentTimeAndDistance(); return timeAndDistance.toNavigationTimeAndDistance(); } on PlatformException catch (e) { switch (e.code) { @@ -535,8 +535,8 @@ class NavigationSessionAPIImpl { /// Get route segments. Future> getRouteSegments() async { try { - final List routeSegments = - await _sessionApi.getRouteSegments(); + final List routeSegments = await _sessionApi + .getRouteSegments(); return routeSegments .where((RouteSegmentDto? p) => p != null) .cast() @@ -555,8 +555,8 @@ class NavigationSessionAPIImpl { /// Get traveled route. Future> getTraveledRoute() async { try { - final List traveledRoute = - await _sessionApi.getTraveledRoute(); + final List traveledRoute = await _sessionApi + .getTraveledRoute(); return traveledRoute .where((LatLngDto? p) => p != null) .cast() @@ -578,8 +578,8 @@ class NavigationSessionAPIImpl { /// Get current route segment. Future getCurrentRouteSegment() async { try { - final RouteSegmentDto? currentRouteSegment = - await _sessionApi.getCurrentRouteSegment(); + final RouteSegmentDto? currentRouteSegment = await _sessionApi + .getCurrentRouteSegment(); return currentRouteSegment?.toRouteSegment(); } on PlatformException catch (e) { switch (e.code) { @@ -626,12 +626,21 @@ class NavigationSessionAPIImpl { } /// Get navigation on GPS availability update event stream from the navigation session. + @Deprecated('Use getNavigationOnGpsAvailabilityChangeEventStream instead') Stream getNavigationOnGpsAvailabilityUpdateEventStream() { return _sessionEventStreamController.stream .whereType(); } + /// Get navigation on GPS availability change event stream from the navigation session. + /// Android only. + Stream + getNavigationOnGpsAvailabilityChangeEventStream() { + return _sessionEventStreamController.stream + .whereType(); + } + /// Get navigation traffic updated event stream from the navigation session. Stream getNavigationTrafficUpdatedEventStream() { return _sessionEventStreamController.stream @@ -711,6 +720,16 @@ class NavigationSessionEventApiImpl implements NavigationSessionEventApi { ); } + @override + void onGpsAvailabilityChange(GpsAvailabilityChangeEventDto event) { + sessionEventStreamController.add( + GpsAvailabilityChangeEvent( + isGpsLost: event.isGpsLost, + isGpsValidForNavigation: event.isGpsValidForNavigation, + ), + ); + } + @override void onRouteChanged() { sessionEventStreamController.add(_RouteChangedEvent()); diff --git a/lib/src/navigator/google_navigation_flutter_navigator.dart b/lib/src/navigator/google_navigation_flutter_navigator.dart index dde17263..7c3762f5 100644 --- a/lib/src/navigator/google_navigation_flutter_navigator.dart +++ b/lib/src/navigator/google_navigation_flutter_navigator.dart @@ -63,7 +63,8 @@ class GoogleMapsNavigator { // Enable road-snapped location updates if there are subscriptions to them. if ((_roadSnappedLocationUpdatedController?.hasListener ?? false) || (_roadSnappedRawLocationUpdatedController?.hasListener ?? false) || - (_gpsAvailabilityUpdatedController?.hasListener ?? false)) { + (_gpsAvailabilityUpdatedController?.hasListener ?? false) || + (_gpsAvailabilityChangeController?.hasListener ?? false)) { await GoogleMapsNavigationPlatform.instance.navigationSessionAPI .enableRoadSnappedLocationUpdates(); } @@ -208,6 +209,7 @@ class GoogleMapsNavigator { /// // When done with the subscription /// await subscription.cancel(); /// ``` + @Deprecated('Use setOnGpsAvailabilityChangeListener instead') static Future> setOnGpsAvailabilityListener(OnGpsAvailabilityEventCallback listener) async { if (_gpsAvailabilityUpdatedController == null) { @@ -235,6 +237,57 @@ class GoogleMapsNavigator { static StreamController? _gpsAvailabilityUpdatedController; + /// Sets the event channel listener for the GPS availability change events. + /// (Android only). + /// + /// Setting this listener will also register road snapped location listener + /// on native side. + /// + /// DISCLAIMER: This is an EXPERIMENTAL API and its behaviors may be subject + /// to removal or breaking changes in future releases. + /// + /// Returns a [StreamSubscription] for GPS availability change events. + /// This subscription must be canceled using `cancel()` when it is no longer + /// needed to stop receiving events and allow the stream to perform necessary + /// cleanup, such as releasing resources or shutting down event sources. The + /// cleanup is asynchronous, and the `cancel()` method returns a Future that + /// completes once the cleanup is done. + /// + /// Example usage: + /// ```dart + /// final subscription = setOnGpsAvailabilityChangeListener(yourEventHandler); + /// // When done with the subscription + /// await subscription.cancel(); + /// ``` + static Future> + setOnGpsAvailabilityChangeListener( + OnGpsAvailabilityChangeEventCallback listener, + ) async { + if (_gpsAvailabilityChangeController == null) { + _gpsAvailabilityChangeController = + StreamController.broadcast( + onCancel: () { + _disableRoadSnappedLocationUpdatesIfNoActiveListeners(); + }, + onListen: () { + GoogleMapsNavigationPlatform.instance.navigationSessionAPI + .enableRoadSnappedLocationUpdates(); + }, + ); + unawaited( + _gpsAvailabilityChangeController!.addStream( + GoogleMapsNavigationPlatform.instance.navigationSessionAPI + .getNavigationOnGpsAvailabilityChangeEventStream(), + ), + ); + } + + return _gpsAvailabilityChangeController!.stream.listen(listener); + } + + static StreamController? + _gpsAvailabilityChangeController; + /// Sets the event channel listener for the traffic updated events. (Android only) /// /// Returns a [StreamSubscription] for traffic updated events. @@ -468,7 +521,8 @@ class GoogleMapsNavigator { static void _disableRoadSnappedLocationUpdatesIfNoActiveListeners() { if (!(_roadSnappedLocationUpdatedController?.hasListener ?? false) && !(_roadSnappedRawLocationUpdatedController?.hasListener ?? false) && - !(_gpsAvailabilityUpdatedController?.hasListener ?? false)) { + !(_gpsAvailabilityUpdatedController?.hasListener ?? false) && + !(_gpsAvailabilityChangeController?.hasListener ?? false)) { GoogleMapsNavigationPlatform.instance.navigationSessionAPI .disableRoadSnappedLocationUpdates(); } diff --git a/lib/src/types/navigation.dart b/lib/src/types/navigation.dart new file mode 100644 index 00000000..b129b4cc --- /dev/null +++ b/lib/src/types/navigation.dart @@ -0,0 +1,163 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'lat_lng.dart'; +import 'navigation_destinations.dart'; + +/// Type for speed alert severity. +/// {@category Navigation} +enum SpeedAlertSeverity { + /// Unknown severity. + unknown, + + /// Not speeding severity. + notSpeeding, + + /// Minor speeding severity. + minor, + + /// Major speeding severity. + major, +} + +/// SpeedingUpdated event message. +/// {@category Navigation} +class SpeedingUpdatedEvent { + /// Initialize speeding updated event message. + SpeedingUpdatedEvent({ + required this.percentageAboveLimit, + required this.severity, + }); + + /// Percentage above speed limit. + final double percentageAboveLimit; + + /// Severity of the speeding. + final SpeedAlertSeverity severity; + + @override + String toString() => + 'SpeedingUpdatedEvent(' + 'percentageAboveLimit: $percentageAboveLimit, ' + 'severity: $severity' + ')'; +} + +/// RoadSnappedLocationUpdated event message. +/// {@category Navigation} +class RoadSnappedLocationUpdatedEvent { + /// Initialize road snapped location updated event message. + RoadSnappedLocationUpdatedEvent({required this.location}); + + /// Coordinate of the updated location. + final LatLng location; + + @override + String toString() => 'RoadSnappedLocationUpdatedEvent(location: $location)'; +} + +/// RoadSnappedRawLocationUpdated event message (Android only). +/// {@category Navigation} +class RoadSnappedRawLocationUpdatedEvent { + /// Initialize road snapped raw location updated event message. + RoadSnappedRawLocationUpdatedEvent({required this.location}); + + /// Coordinate of the updated location. + final LatLng location; + + @override + String toString() => + 'RoadSnappedRawLocationUpdatedEvent(location: $location)'; +} + +/// GpsAvailabilityUpdated event message (Android only). +/// {@category Navigation} +@Deprecated( + 'Use getNavigationOnGpsAvailabilityChangeEventStream and GpsAvailabilityChangeEvent instead', +) +class GpsAvailabilityUpdatedEvent { + /// Initialize GPS availability updated event message. + GpsAvailabilityUpdatedEvent({required this.available}); + + /// GPS availability. + final bool available; + + @override + String toString() => 'GpsAvailabilityUpdatedEvent(available: $available)'; +} + +/// GpsAvailabilityChange event message (Android only). +/// {@category Navigation} +class GpsAvailabilityChangeEvent { + /// Initialize GPS availability change event message. + GpsAvailabilityChangeEvent({ + required this.isGpsLost, + required this.isGpsValidForNavigation, + }); + + /// Indicates a GPS signal or other sensors good enough for a reasonably certain location have been lost. + /// + /// This state is triggered after a short timeout (10 seconds) and serves as an early warning of potential signal issues. + /// For example, the "Searching for GPS" UI message may be shown when this value is true. + final bool isGpsLost; + + /// Indicates a GPS signal or other sensors are in general good enough for use in navigation. + /// + /// Note that this value takes into account the frequent failure of GPS at the start of nav, + /// and doesn't become true until some time later. + final bool isGpsValidForNavigation; + + @override + String toString() => + 'GpsAvailabilityChangeEvent(' + 'isGpsLost: $isGpsLost, ' + 'isGpsValidForNavigation: $isGpsValidForNavigation' + ')'; +} + +/// Remaining time or distance change event message. +/// {@category Navigation} +class RemainingTimeOrDistanceChangedEvent { + /// Initialize with remaining distance in meters and remaining time in seconds. + RemainingTimeOrDistanceChangedEvent({ + required this.remainingDistance, + required this.remainingTime, + }); + + /// Remaining distance in meters. + final double remainingDistance; + + /// Remaining time in seconds. + final double remainingTime; + + @override + String toString() => + 'RemainingTimeOrDistanceChangedEvent(' + 'remainingDistance: $remainingDistance, ' + 'remainingTime: $remainingTime' + ')'; +} + +/// On arrival event message +/// {@category Navigation} +class OnArrivalEvent { + /// Initialize with arrival waypoint. + OnArrivalEvent({required this.waypoint}); + + /// Arrival waypoint. + final NavigationWaypoint waypoint; + + @override + String toString() => 'OnArrivalEvent(waypoint: $waypoint)'; +} diff --git a/lib/src/types/navigation_destinations.dart b/lib/src/types/navigation_destinations.dart index 22de85c1..e1a87a72 100644 --- a/lib/src/types/navigation_destinations.dart +++ b/lib/src/types/navigation_destinations.dart @@ -200,9 +200,21 @@ class NavigationDisplayOptions { final bool? showDestinationMarkers; /// Show stop signs. + /// + /// Deprecated: This option now defaults to true and will be removed in future + /// versions. + @Deprecated( + 'This option now defaults to true and will be removed in future versions.', + ) final bool? showStopSigns; /// Show traffic lights. + /// + /// Deprecated: This option now defaults to true and will be removed in future + /// versions. + @Deprecated( + 'This option now defaults to true and will be removed in future versions.', + ) final bool? showTrafficLights; @override diff --git a/lib/src/types/navigation_view_types.dart b/lib/src/types/navigation_view_types.dart index 2632a575..8ad6e4da 100644 --- a/lib/src/types/navigation_view_types.dart +++ b/lib/src/types/navigation_view_types.dart @@ -151,6 +151,16 @@ class NavigationUIEnabledChangedEvent { ')'; } +/// Represents prompt visibility changed event in a view. +/// {@category Navigation View} +class PromptVisibilityChangedEvent { + /// Creates a [PromptVisibilityChangedEvent] object. + const PromptVisibilityChangedEvent(this.promptVisible); + + /// Value representing whether prompts are visible or not. + final bool promptVisible; +} + /// Represents the long click position in a Google Maps view. /// {@category Navigation View} /// {@category Map View} diff --git a/lib/src/types/navinfo.dart b/lib/src/types/navinfo.dart index cfc826ec..cbdf2bb6 100644 --- a/lib/src/types/navinfo.dart +++ b/lib/src/types/navinfo.dart @@ -318,11 +318,11 @@ class StepInfo { required this.maneuver, }); - /// Distance in meters from the previous step to this step. - final int distanceFromPrevStepMeters; + /// Distance in meters from the previous step to this step if available, otherwise null. + final int? distanceFromPrevStepMeters; - /// Time in seconds from the previous step to this step. - final int timeFromPrevStepSeconds; + /// Time in seconds from the previous step to this step if available, otherwise null. + final int? timeFromPrevStepSeconds; /// Whether this step is on a drive-on-right or drive-on-left route. final DrivingSide drivingSide; @@ -330,29 +330,29 @@ class StepInfo { /// The exit number if it exists. final String? exitNumber; - /// The full text of the instruction for this step. - final String fullInstructions; + /// The full text of the instruction for this step if available, otherwise null. + final String? fullInstructions; - /// The full road name for this step. - final String fullRoadName; + /// The full road name for this step if available, otherwise null. + final String? fullRoadName; - /// The simplified version of the road name. - final String simpleRoadName; + /// The simplified version of the road name if available, otherwise null. + final String? simpleRoadName; /// The counted number of the exit to take relative to the location where the - /// roundabout was entered. - final int roundaboutTurnNumber; + /// roundabout was entered if available, otherwise null. + final int? roundaboutTurnNumber; - /// The list of available lanes at the end of this route step. + /// The list of available lanes at the end of this route step if available, otherwise null. /// /// Android only. - final List lanes; + final List? lanes; /// The maneuver for this step. final Maneuver maneuver; - /// The index of the step in the list of all steps in the route. - final int stepNumber; + /// The index of the step in the list of all steps in the route if available, otherwise null. + final int? stepNumber; @override String toString() => diff --git a/lib/src/types/simulation.dart b/lib/src/types/simulation.dart index 7f98b57b..6b35f79d 100644 --- a/lib/src/types/simulation.dart +++ b/lib/src/types/simulation.dart @@ -12,87 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -import '../../google_navigation_flutter.dart'; - -/// Type for speed alert severity. -/// {@category Navigation} -enum SpeedAlertSeverity { - /// Unknown severity. - unknown, - - /// Not speeding severity. - notSpeeding, - - /// Minor speeding severity. - minor, - - /// Major speeding severity. - major, -} - -/// SpeedingUpdated event message. -/// {@category Navigation} -class SpeedingUpdatedEvent { - /// Initialize speeding updated event message. - SpeedingUpdatedEvent({ - required this.percentageAboveLimit, - required this.severity, - }); - - /// Percentage above speed limit. - final double percentageAboveLimit; - - /// Severity of the speeding. - final SpeedAlertSeverity severity; - - @override - String toString() => - 'SpeedingUpdatedEvent(' - 'percentageAboveLimit: $percentageAboveLimit, ' - 'severity: $severity' - ')'; -} - -/// RoadSnappedLocationUpdated event message. -/// {@category Navigation} -class RoadSnappedLocationUpdatedEvent { - /// Initialize road snapped location updated event message. - RoadSnappedLocationUpdatedEvent({required this.location}); - - /// Coordinate of the updated location. - final LatLng location; - - @override - String toString() => 'RoadSnappedLocationUpdatedEvent(location: $location)'; -} - -/// RoadSnappedRawLocationUpdated event message (Android only). -/// {@category Navigation} -class RoadSnappedRawLocationUpdatedEvent { - /// Initialize road snapped raw location updated event message. - RoadSnappedRawLocationUpdatedEvent({required this.location}); - - /// Coordinate of the updated location. - final LatLng location; - - @override - String toString() => - 'RoadSnappedRawLocationUpdatedEvent(location: $location)'; -} - -/// GpsAvailabilityUpdated event message (Android only). -/// {@category Navigation} -class GpsAvailabilityUpdatedEvent { - /// Initialize GPS availability updated event message. - GpsAvailabilityUpdatedEvent({required this.available}); - - /// GPS availability. - final bool available; - - @override - String toString() => 'GpsAvailabilityUpdatedEvent(available: $available)'; -} - /// Navigation simulation options. /// {@category Navigation} class SimulationOptions { @@ -105,39 +24,3 @@ class SimulationOptions { @override String toString() => 'SimulationOptions(speedMultiplier: $speedMultiplier)'; } - -/// Remaining time or distance change event message. -/// {@category Navigation} -class RemainingTimeOrDistanceChangedEvent { - /// Initialize with remaining distance in meters and remaining time in seconds. - RemainingTimeOrDistanceChangedEvent({ - required this.remainingDistance, - required this.remainingTime, - }); - - /// Remaining distance in meters. - final double remainingDistance; - - /// Remaining time in seconds. - final double remainingTime; - - @override - String toString() => - 'RemainingTimeOrDistanceChangedEvent(' - 'remainingDistance: $remainingDistance, ' - 'remainingTime: $remainingTime' - ')'; -} - -/// On arrival event message -/// {@category Navigation} -class OnArrivalEvent { - /// Initialize with arrival waypoint. - OnArrivalEvent({required this.waypoint}); - - /// Arrival waypoint. - final NavigationWaypoint waypoint; - - @override - String toString() => 'OnArrivalEvent(waypoint: $waypoint)'; -} diff --git a/lib/src/types/types.dart b/lib/src/types/types.dart index 8a3fd312..49aa7d77 100644 --- a/lib/src/types/types.dart +++ b/lib/src/types/types.dart @@ -17,6 +17,7 @@ export 'images.dart'; export 'lat_lng.dart'; export 'lat_lng_bounds.dart'; export 'markers.dart'; +export 'navigation.dart'; export 'navigation_destinations.dart'; export 'navigation_initialization_params.dart'; export 'navigation_view_types.dart'; diff --git a/lib/src/types/view_initialization_options.dart b/lib/src/types/view_initialization_options.dart index 2b5c07ed..1c14b548 100644 --- a/lib/src/types/view_initialization_options.dart +++ b/lib/src/types/view_initialization_options.dart @@ -110,6 +110,7 @@ class MapOptions { this.zoomControlsEnabled = true, this.cameraTargetBounds, this.padding, + this.mapId, }) : assert( minZoomPreference == null || maxZoomPreference == null || @@ -200,6 +201,19 @@ class MapOptions { /// Null by default (no padding). final EdgeInsets? padding; + /// The map ID for cloud-based map styling. + /// + /// A map ID is a unique identifier that represents a single map instance. + /// This value can only be set at map initialization and cannot be changed afterwards. + /// Map IDs are created in Google Cloud Console and can be used to configure + /// advanced features like cloud-based map styling. + /// + /// See https://developers.google.com/maps/documentation/get-map-id + /// for more information about map IDs and how to create them. + /// + /// Null by default (no map ID). + final String? mapId; + @override String toString() => 'MapOptions(' @@ -216,7 +230,8 @@ class MapOptions { 'maxZoomPreference: $maxZoomPreference, ' 'zoomControlsEnabled: $zoomControlsEnabled, ' 'cameraTargetBounds: $cameraTargetBounds, ' - 'padding: $padding' + 'padding: $padding, ' + 'mapId: $mapId' ')'; } diff --git a/pigeons/messages.dart b/pigeons/messages.dart index e2da527c..3a76be44 100644 --- a/pigeons/messages.dart +++ b/pigeons/messages.dart @@ -53,6 +53,7 @@ class MapOptionsDto { required this.zoomControlsEnabled, required this.cameraTargetBounds, required this.padding, + required this.mapId, }); /// The initial positioning of the camera in the map view. @@ -97,6 +98,10 @@ class MapOptionsDto { /// Specifies the padding for the map. final MapPaddingDto? padding; + + /// The map ID for advanced map options eg. cloud-based map styling. + /// This value can only be set on map initialization and cannot be changed afterwards. + final String? mapId; } /// Determines the initial visibility of the navigation UI on map initialization. @@ -466,6 +471,12 @@ abstract class MapViewApi { bool isReportIncidentButtonEnabled(int viewId); void setReportIncidentButtonEnabled(int viewId, bool enabled); + bool isIncidentReportingAvailable(int viewId); + void showReportIncidentsPanel(int viewId); + + bool isBuildingsEnabled(int viewId); + void setBuildingsEnabled(int viewId, bool enabled); + CameraPositionDto getCameraPosition(int viewId); LatLngBoundsDto getVisibleRegion(int viewId); @@ -597,6 +608,7 @@ abstract class ViewEventApi { void onPolylineClicked(int viewId, String polylineId); void onCircleClicked(int viewId, String circleId); void onNavigationUIEnabledChanged(int viewId, bool navigationUIEnabled); + void onPromptVisibilityChanged(int viewId, bool promptVisible); void onMyLocationClicked(int viewId); void onMyLocationButtonClicked(int viewId); void onCameraChanged( @@ -662,7 +674,17 @@ class NavigationDisplayOptionsDto { ); final bool? showDestinationMarkers; + + /// Deprecated: This option now defaults to true. + @Deprecated( + 'This option now defaults to true and will be removed in Navigation SDK 8.0', + ) final bool? showStopSigns; + + /// Deprecated: This option now defaults to true. + @Deprecated( + 'This option now defaults to true and will be removed in Navigation SDK 8.0', + ) final bool? showTrafficLights; } @@ -752,6 +774,16 @@ class SpeedingUpdatedEventDto { final SpeedAlertSeverityDto severity; } +class GpsAvailabilityChangeEventDto { + GpsAvailabilityChangeEventDto({ + required this.isGpsLost, + required this.isGpsValidForNavigation, + }); + + final bool isGpsLost; + final bool isGpsValidForNavigation; +} + class SpeedAlertOptionsThresholdPercentageDto { SpeedAlertOptionsThresholdPercentageDto({ required this.percentage, @@ -1115,11 +1147,11 @@ class StepInfoDto { required this.maneuver, }); - /// Distance in meters from the previous step to this step. - final int distanceFromPrevStepMeters; + /// Distance in meters from the previous step to this step if available, otherwise null. + final int? distanceFromPrevStepMeters; - /// Time in seconds from the previous step to this step. - final int timeFromPrevStepSeconds; + /// Time in seconds from the previous step to this step if available, otherwise null. + final int? timeFromPrevStepSeconds; /// Whether this step is on a drive-on-right or drive-on-left route. final DrivingSideDto drivingSide; @@ -1127,27 +1159,27 @@ class StepInfoDto { /// The exit number if it exists. final String? exitNumber; - /// The full text of the instruction for this step. - final String fullInstructions; + /// The full text of the instruction for this step if available, otherwise null. + final String? fullInstructions; - /// The full road name for this step. - final String fullRoadName; + /// The full road name for this step if available, otherwise null. + final String? fullRoadName; - /// The simplified version of the road name. - final String simpleRoadName; + /// The simplified version of the road name if available, otherwise null. + final String? simpleRoadName; /// The counted number of the exit to take relative to the location where the - /// roundabout was entered. - final int roundaboutTurnNumber; + /// roundabout was entered if available, otherwise null. + final int? roundaboutTurnNumber; - /// The list of available lanes at the end of this route step. - final List lanes; + /// The list of available lanes at the end of this route step if available, otherwise null. + final List? lanes; /// The maneuver for this step. final ManeuverDto maneuver; - /// The index of the step in the list of all steps in the route. - final int stepNumber; + /// The index of the step in the list of all steps in the route if available, otherwise null. + final int? stepNumber; } /// Contains information about the state of navigation, the current nav step if @@ -1308,6 +1340,9 @@ abstract class NavigationSessionEventApi { /// Android-only event. void onGpsAvailabilityUpdate(bool available); + /// Android-only event. + void onGpsAvailabilityChange(GpsAvailabilityChangeEventDto event); + /// Turn-by-Turn navigation events. void onNavInfo(NavInfoDto navInfo); diff --git a/pubspec.yaml b/pubspec.yaml index 5c34f898..a32b4d54 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,14 +19,15 @@ issue_tracker: https://github.com/googlemaps/flutter-navigation-sdk/issues version: 0.7.0 environment: - sdk: ^3.7.0 - flutter: ">=3.29.0" + sdk: ^3.8.0 + flutter: ">=3.32.0" dependencies: collection: ^1.17.2 flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.16 + meta: any plugin_platform_interface: ^2.1.5 stream_transform: ^2.1.0 diff --git a/test/google_navigation_flutter_test.dart b/test/google_navigation_flutter_test.dart index e67ce6ca..ff19d434 100644 --- a/test/google_navigation_flutter_test.dart +++ b/test/google_navigation_flutter_test.dart @@ -132,8 +132,8 @@ void main() { // Get camera position - final CameraPosition positionOut = - await controller.getCameraPosition(); + final CameraPosition positionOut = await controller + .getCameraPosition(); final VerificationResult result = verify( viewMockApi.getCameraPosition(captureAny), ); @@ -673,6 +673,8 @@ void main() { when(viewMockApi.isMapToolbarEnabled(any)).thenReturn(true); when(viewMockApi.isTrafficPromptsEnabled(any)).thenReturn(true); when(viewMockApi.isReportIncidentButtonEnabled(any)).thenReturn(true); + when(viewMockApi.isIncidentReportingAvailable(any)).thenReturn(true); + when(viewMockApi.isBuildingsEnabled(any)).thenReturn(false); when(viewMockApi.isNavigationHeaderEnabled(any)).thenReturn(true); when(viewMockApi.isNavigationFooterEnabled(any)).thenReturn(true); when(viewMockApi.isSpeedLimitIconEnabled(any)).thenReturn(true); @@ -697,6 +699,7 @@ void main() { expect(await controller.settings.isMapToolbarEnabled(), true); expect(await controller.isTrafficPromptsEnabled(), true); expect(await controller.isReportIncidentButtonEnabled(), true); + expect(await controller.isIncidentReportingAvailable(), true); expect(await controller.isNavigationHeaderEnabled(), true); expect(await controller.isNavigationFooterEnabled(), true); expect(await controller.isSpeedLimitIconEnabled(), true); @@ -719,6 +722,7 @@ void main() { verify(viewMockApi.isMapToolbarEnabled(captureAny)); verify(viewMockApi.isTrafficPromptsEnabled(captureAny)); verify(viewMockApi.isReportIncidentButtonEnabled(captureAny)); + verify(viewMockApi.isIncidentReportingAvailable(captureAny)); verify(viewMockApi.isNavigationHeaderEnabled(captureAny)); verify(viewMockApi.isNavigationFooterEnabled(captureAny)); verify(viewMockApi.isSpeedLimitIconEnabled(captureAny)); @@ -741,6 +745,8 @@ void main() { await controller.settings.setMapToolbarEnabled(true); await controller.setTrafficPromptsEnabled(true); await controller.setReportIncidentButtonEnabled(true); + await controller.showReportIncidentsPanel(); + await controller.setBuildingsEnabled(true); await controller.setNavigationHeaderEnabled(true); await controller.setNavigationFooterEnabled(true); await controller.setSpeedLimitIconEnabled(true); @@ -815,6 +821,11 @@ void main() { ), true, ); + verify(viewMockApi.showReportIncidentsPanel(captureAny)); + verifyEnabled( + verify(viewMockApi.setBuildingsEnabled(captureAny, captureAny)), + true, + ); verifyEnabled( verify( viewMockApi.setNavigationHeaderEnabled(captureAny, captureAny), @@ -852,6 +863,101 @@ void main() { ); }); + test('Test incident reporting APIs', () async { + const int viewId = 1; + final GoogleNavigationViewController controller = + GoogleNavigationViewController(viewId); + + // Mock incident reporting availability and button enabled state + when(viewMockApi.isIncidentReportingAvailable(any)).thenReturn(true); + when( + viewMockApi.isReportIncidentButtonEnabled(any), + ).thenReturn(false); + when(viewMockApi.isBuildingsEnabled(any)).thenReturn(false); + + // Test isIncidentReportingAvailable + final bool isAvailable = await controller + .isIncidentReportingAvailable(); + expect(isAvailable, true); + + // Verify the API was called + final VerificationResult availabilityResult = verify( + viewMockApi.isIncidentReportingAvailable(captureAny), + ); + expect(availabilityResult.captured[0] as int, viewId); + + // Test isReportIncidentButtonEnabled + final bool isButtonEnabled = await controller + .isReportIncidentButtonEnabled(); + expect(isButtonEnabled, false); + + // Verify the API was called + final VerificationResult buttonEnabledResult = verify( + viewMockApi.isReportIncidentButtonEnabled(captureAny), + ); + expect(buttonEnabledResult.captured[0] as int, viewId); + + // Test setReportIncidentButtonEnabled + await controller.setReportIncidentButtonEnabled(true); + + // Verify the API was called with correct parameters + final VerificationResult setButtonEnabledResult = verify( + viewMockApi.setReportIncidentButtonEnabled(captureAny, captureAny), + ); + expect(setButtonEnabledResult.captured[0] as int, viewId); + expect(setButtonEnabledResult.captured[1] as bool, true); + + // Test isBuildingsEnabled + final bool isBuildingsEnabled = await controller.isBuildingsEnabled(); + expect(isBuildingsEnabled, false); + + // Verify the API was called + final VerificationResult buildingsEnabledResult = verify( + viewMockApi.isBuildingsEnabled(captureAny), + ); + expect(buildingsEnabledResult.captured[0] as int, viewId); + + // Test setBuildingsEnabled + await controller.setBuildingsEnabled(true); + + // Verify the API was called with correct parameters + final VerificationResult setBuildingsEnabledResult = verify( + viewMockApi.setBuildingsEnabled(captureAny, captureAny), + ); + expect(setBuildingsEnabledResult.captured[0] as int, viewId); + expect(setBuildingsEnabledResult.captured[1] as bool, true); + + // Test showReportIncidentsPanel + await controller.showReportIncidentsPanel(); + + // Verify the API was called + final VerificationResult showPanelResult = verify( + viewMockApi.showReportIncidentsPanel(captureAny), + ); + expect(showPanelResult.captured[0] as int, viewId); + }); + + test('Test prompt visibility changed event stream', () async { + const int viewId = 1; + + // Test the event stream + final Stream eventStream = + GoogleMapsNavigationPlatform.instance.viewAPI + .getPromptVisibilityChangedEventStream(viewId: viewId); + + // Verify the stream is not null + expect(eventStream, isNotNull); + + // Test that the event can be created + const PromptVisibilityChangedEvent event = + PromptVisibilityChangedEvent(true); + expect(event.promptVisible, true); + + const PromptVisibilityChangedEvent event2 = + PromptVisibilityChangedEvent(false); + expect(event2.promptVisible, false); + }); + test('set padding for map', () async { // Create padding EdgeInsets insets = const EdgeInsets.only( diff --git a/test/google_navigation_flutter_test.mocks.dart b/test/google_navigation_flutter_test.mocks.dart index c5916b3e..8db4e16e 100644 --- a/test/google_navigation_flutter_test.mocks.dart +++ b/test/google_navigation_flutter_test.mocks.dart @@ -741,6 +741,34 @@ class MockTestMapViewApi extends _i1.Mock implements _i3.TestMapViewApi { returnValueForMissingStub: null, ); + @override + bool isIncidentReportingAvailable(int? viewId) => + (super.noSuchMethod( + Invocation.method(#isIncidentReportingAvailable, [viewId]), + returnValue: false, + ) + as bool); + + @override + void showReportIncidentsPanel(int? viewId) => super.noSuchMethod( + Invocation.method(#showReportIncidentsPanel, [viewId]), + returnValueForMissingStub: null, + ); + + @override + bool isBuildingsEnabled(int? viewId) => + (super.noSuchMethod( + Invocation.method(#isBuildingsEnabled, [viewId]), + returnValue: false, + ) + as bool); + + @override + void setBuildingsEnabled(int? viewId, bool? enabled) => super.noSuchMethod( + Invocation.method(#setBuildingsEnabled, [viewId, enabled]), + returnValueForMissingStub: null, + ); + @override _i2.CameraPositionDto getCameraPosition(int? viewId) => (super.noSuchMethod( diff --git a/test/messages_test.g.dart b/test/messages_test.g.dart index cb992607..f0f1f17f 100644 --- a/test/messages_test.g.dart +++ b/test/messages_test.g.dart @@ -191,33 +191,36 @@ class _PigeonCodec extends StandardMessageCodec { } else if (value is SpeedingUpdatedEventDto) { buffer.putUint8(181); writeValue(buffer, value.encode()); - } else if (value is SpeedAlertOptionsThresholdPercentageDto) { + } else if (value is GpsAvailabilityChangeEventDto) { buffer.putUint8(182); writeValue(buffer, value.encode()); - } else if (value is SpeedAlertOptionsDto) { + } else if (value is SpeedAlertOptionsThresholdPercentageDto) { buffer.putUint8(183); writeValue(buffer, value.encode()); - } else if (value is RouteSegmentTrafficDataRoadStretchRenderingDataDto) { + } else if (value is SpeedAlertOptionsDto) { buffer.putUint8(184); writeValue(buffer, value.encode()); - } else if (value is RouteSegmentTrafficDataDto) { + } else if (value is RouteSegmentTrafficDataRoadStretchRenderingDataDto) { buffer.putUint8(185); writeValue(buffer, value.encode()); - } else if (value is RouteSegmentDto) { + } else if (value is RouteSegmentTrafficDataDto) { buffer.putUint8(186); writeValue(buffer, value.encode()); - } else if (value is LaneDirectionDto) { + } else if (value is RouteSegmentDto) { buffer.putUint8(187); writeValue(buffer, value.encode()); - } else if (value is LaneDto) { + } else if (value is LaneDirectionDto) { buffer.putUint8(188); writeValue(buffer, value.encode()); - } else if (value is StepInfoDto) { + } else if (value is LaneDto) { buffer.putUint8(189); writeValue(buffer, value.encode()); - } else if (value is NavInfoDto) { + } else if (value is StepInfoDto) { buffer.putUint8(190); writeValue(buffer, value.encode()); + } else if (value is NavInfoDto) { + buffer.putUint8(191); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -283,7 +286,7 @@ class _PigeonCodec extends StandardMessageCodec { return value == null ? null : RouteSegmentTrafficDataRoadStretchRenderingDataStyleDto - .values[value]; + .values[value]; case 146: final int? value = readValue(buffer) as int?; return value == null ? null : ManeuverDto.values[value]; @@ -362,26 +365,28 @@ class _PigeonCodec extends StandardMessageCodec { case 181: return SpeedingUpdatedEventDto.decode(readValue(buffer)!); case 182: + return GpsAvailabilityChangeEventDto.decode(readValue(buffer)!); + case 183: return SpeedAlertOptionsThresholdPercentageDto.decode( readValue(buffer)!, ); - case 183: - return SpeedAlertOptionsDto.decode(readValue(buffer)!); case 184: + return SpeedAlertOptionsDto.decode(readValue(buffer)!); + case 185: return RouteSegmentTrafficDataRoadStretchRenderingDataDto.decode( readValue(buffer)!, ); - case 185: - return RouteSegmentTrafficDataDto.decode(readValue(buffer)!); case 186: - return RouteSegmentDto.decode(readValue(buffer)!); + return RouteSegmentTrafficDataDto.decode(readValue(buffer)!); case 187: - return LaneDirectionDto.decode(readValue(buffer)!); + return RouteSegmentDto.decode(readValue(buffer)!); case 188: - return LaneDto.decode(readValue(buffer)!); + return LaneDirectionDto.decode(readValue(buffer)!); case 189: - return StepInfoDto.decode(readValue(buffer)!); + return LaneDto.decode(readValue(buffer)!); case 190: + return StepInfoDto.decode(readValue(buffer)!); + case 191: return NavInfoDto.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -492,6 +497,14 @@ abstract class TestMapViewApi { void setReportIncidentButtonEnabled(int viewId, bool enabled); + bool isIncidentReportingAvailable(int viewId); + + void showReportIncidentsPanel(int viewId); + + bool isBuildingsEnabled(int viewId); + + void setBuildingsEnabled(int viewId, bool enabled); + CameraPositionDto getCameraPosition(int viewId); LatLngBoundsDto getVisibleRegion(int viewId); @@ -633,8 +646,9 @@ abstract class TestMapViewApi { BinaryMessenger? binaryMessenger, String messageChannelSuffix = '', }) { - messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + messageChannelSuffix = messageChannelSuffix.isNotEmpty + ? '.$messageChannelSuffix' + : ''; { final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( @@ -2682,6 +2696,173 @@ abstract class TestMapViewApi { }); } } + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.google_navigation_flutter.MapViewApi.isIncidentReportingAvailable$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, ( + Object? message, + ) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.isIncidentReportingAvailable was null.', + ); + final List args = (message as List?)!; + final int? arg_viewId = (args[0] as int?); + assert( + arg_viewId != null, + 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.isIncidentReportingAvailable was null, expected non-null int.', + ); + try { + final bool output = api.isIncidentReportingAvailable( + arg_viewId!, + ); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException( + code: 'error', + message: e.toString(), + ), + ); + } + }); + } + } + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.google_navigation_flutter.MapViewApi.showReportIncidentsPanel$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, ( + Object? message, + ) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.showReportIncidentsPanel was null.', + ); + final List args = (message as List?)!; + final int? arg_viewId = (args[0] as int?); + assert( + arg_viewId != null, + 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.showReportIncidentsPanel was null, expected non-null int.', + ); + try { + api.showReportIncidentsPanel(arg_viewId!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException( + code: 'error', + message: e.toString(), + ), + ); + } + }); + } + } + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.google_navigation_flutter.MapViewApi.isBuildingsEnabled$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, ( + Object? message, + ) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.isBuildingsEnabled was null.', + ); + final List args = (message as List?)!; + final int? arg_viewId = (args[0] as int?); + assert( + arg_viewId != null, + 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.isBuildingsEnabled was null, expected non-null int.', + ); + try { + final bool output = api.isBuildingsEnabled(arg_viewId!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException( + code: 'error', + message: e.toString(), + ), + ); + } + }); + } + } + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.google_navigation_flutter.MapViewApi.setBuildingsEnabled$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler< + Object? + >(pigeonVar_channel, (Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.setBuildingsEnabled was null.', + ); + final List args = (message as List?)!; + final int? arg_viewId = (args[0] as int?); + assert( + arg_viewId != null, + 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.setBuildingsEnabled was null, expected non-null int.', + ); + final bool? arg_enabled = (args[1] as bool?); + assert( + arg_enabled != null, + 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.setBuildingsEnabled was null, expected non-null bool.', + ); + try { + api.setBuildingsEnabled(arg_viewId!, arg_enabled!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } { final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( @@ -3802,8 +3983,8 @@ abstract class TestMapViewApi { arg_viewId != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.addMarkers was null, expected non-null int.', ); - final List? arg_markers = - (args[1] as List?)?.cast(); + final List? arg_markers = (args[1] as List?) + ?.cast(); assert( arg_markers != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.addMarkers was null, expected non-null List.', @@ -3848,8 +4029,8 @@ abstract class TestMapViewApi { arg_viewId != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.updateMarkers was null, expected non-null int.', ); - final List? arg_markers = - (args[1] as List?)?.cast(); + final List? arg_markers = (args[1] as List?) + ?.cast(); assert( arg_markers != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.updateMarkers was null, expected non-null List.', @@ -3894,8 +4075,8 @@ abstract class TestMapViewApi { arg_viewId != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.removeMarkers was null, expected non-null int.', ); - final List? arg_markers = - (args[1] as List?)?.cast(); + final List? arg_markers = (args[1] as List?) + ?.cast(); assert( arg_markers != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.removeMarkers was null, expected non-null List.', @@ -4060,8 +4241,8 @@ abstract class TestMapViewApi { arg_viewId != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.addPolygons was null, expected non-null int.', ); - final List? arg_polygons = - (args[1] as List?)?.cast(); + final List? arg_polygons = (args[1] as List?) + ?.cast(); assert( arg_polygons != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.addPolygons was null, expected non-null List.', @@ -4106,8 +4287,8 @@ abstract class TestMapViewApi { arg_viewId != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.updatePolygons was null, expected non-null int.', ); - final List? arg_polygons = - (args[1] as List?)?.cast(); + final List? arg_polygons = (args[1] as List?) + ?.cast(); assert( arg_polygons != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.updatePolygons was null, expected non-null List.', @@ -4152,8 +4333,8 @@ abstract class TestMapViewApi { arg_viewId != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.removePolygons was null, expected non-null int.', ); - final List? arg_polygons = - (args[1] as List?)?.cast(); + final List? arg_polygons = (args[1] as List?) + ?.cast(); assert( arg_polygons != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.removePolygons was null, expected non-null List.', @@ -4277,8 +4458,8 @@ abstract class TestMapViewApi { arg_viewId != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.addPolylines was null, expected non-null int.', ); - final List? arg_polylines = - (args[1] as List?)?.cast(); + final List? arg_polylines = (args[1] as List?) + ?.cast(); assert( arg_polylines != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.addPolylines was null, expected non-null List.', @@ -4323,8 +4504,8 @@ abstract class TestMapViewApi { arg_viewId != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.updatePolylines was null, expected non-null int.', ); - final List? arg_polylines = - (args[1] as List?)?.cast(); + final List? arg_polylines = (args[1] as List?) + ?.cast(); assert( arg_polylines != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.updatePolylines was null, expected non-null List.', @@ -4369,8 +4550,8 @@ abstract class TestMapViewApi { arg_viewId != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.removePolylines was null, expected non-null int.', ); - final List? arg_polylines = - (args[1] as List?)?.cast(); + final List? arg_polylines = (args[1] as List?) + ?.cast(); assert( arg_polylines != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.removePolylines was null, expected non-null List.', @@ -4494,8 +4675,8 @@ abstract class TestMapViewApi { arg_viewId != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.addCircles was null, expected non-null int.', ); - final List? arg_circles = - (args[1] as List?)?.cast(); + final List? arg_circles = (args[1] as List?) + ?.cast(); assert( arg_circles != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.addCircles was null, expected non-null List.', @@ -4540,8 +4721,8 @@ abstract class TestMapViewApi { arg_viewId != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.updateCircles was null, expected non-null int.', ); - final List? arg_circles = - (args[1] as List?)?.cast(); + final List? arg_circles = (args[1] as List?) + ?.cast(); assert( arg_circles != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.updateCircles was null, expected non-null List.', @@ -4586,8 +4767,8 @@ abstract class TestMapViewApi { arg_viewId != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.removeCircles was null, expected non-null int.', ); - final List? arg_circles = - (args[1] as List?)?.cast(); + final List? arg_circles = (args[1] as List?) + ?.cast(); assert( arg_circles != null, 'Argument for dev.flutter.pigeon.google_navigation_flutter.MapViewApi.removeCircles was null, expected non-null List.', @@ -4797,8 +4978,9 @@ abstract class TestImageRegistryApi { BinaryMessenger? binaryMessenger, String messageChannelSuffix = '', }) { - messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + messageChannelSuffix = messageChannelSuffix.isNotEmpty + ? '.$messageChannelSuffix' + : ''; { final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( @@ -4912,8 +5094,8 @@ abstract class TestImageRegistryApi { Object? message, ) async { try { - final List output = - api.getRegisteredImages(); + final List output = api + .getRegisteredImages(); return [output]; } on PlatformException catch (e) { return wrapResponse(error: e); @@ -5063,8 +5245,9 @@ abstract class TestNavigationSessionApi { BinaryMessenger? binaryMessenger, String messageChannelSuffix = '', }) { - messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + messageChannelSuffix = messageChannelSuffix.isNotEmpty + ? '.$messageChannelSuffix' + : ''; { final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( @@ -5513,8 +5696,8 @@ abstract class TestNavigationSessionApi { Object? message, ) async { try { - final NavigationWaypointDto? output = - api.continueToNextDestination(); + final NavigationWaypointDto? output = api + .continueToNextDestination(); return [output]; } on PlatformException catch (e) { return wrapResponse(error: e); @@ -5545,8 +5728,8 @@ abstract class TestNavigationSessionApi { Object? message, ) async { try { - final NavigationTimeAndDistanceDto output = - api.getCurrentTimeAndDistance(); + final NavigationTimeAndDistanceDto output = api + .getCurrentTimeAndDistance(); return [output]; } on PlatformException catch (e) { return wrapResponse(error: e); diff --git a/test/navigation_types_test.dart b/test/navigation_types_test.dart index e855f9bd..22c9f13c 100644 --- a/test/navigation_types_test.dart +++ b/test/navigation_types_test.dart @@ -64,8 +64,8 @@ void main() { group('NavigationWaypoint tests', () { test('tests Navigation Waypoint conversion from DTO', () { - final NavigationWaypoint targetGmsWaypoint = - targetWaypointDto.toNavigationWaypoint(); + final NavigationWaypoint targetGmsWaypoint = targetWaypointDto + .toNavigationWaypoint(); expect(targetGmsWaypoint.title, targetWaypointDto.title); expect( targetGmsWaypoint.target?.latitude, @@ -85,8 +85,8 @@ void main() { targetWaypointDto.preferredSegmentHeading, ); - final NavigationWaypoint placeIDGmsWaypoint = - placeIDWaypointDto.toNavigationWaypoint(); + final NavigationWaypoint placeIDGmsWaypoint = placeIDWaypointDto + .toNavigationWaypoint(); expect(placeIDGmsWaypoint.title, placeIDWaypointDto.title); expect( placeIDGmsWaypoint.target?.latitude, @@ -180,8 +180,8 @@ void main() { group('Navigation Options tests', () { test('tests Navigation Display options conversion to Pigeon DTO', () { - final NavigationDisplayOptionsDto pigeonDtoDisplayOptions = - displayOptions.toDto(); + final NavigationDisplayOptionsDto pigeonDtoDisplayOptions = displayOptions + .toDto(); expect( pigeonDtoDisplayOptions.showDestinationMarkers, @@ -232,8 +232,9 @@ void main() { }); test('tests Navigation Routing strategy conversion to Pigeon DTO', () { - final RoutingStrategyDto pigeonDtoStrategy = - NavigationRoutingStrategy.defaultBest.toDto(); + final RoutingStrategyDto pigeonDtoStrategy = NavigationRoutingStrategy + .defaultBest + .toDto(); expect( pigeonDtoStrategy.toString().split('.').last, @@ -257,8 +258,8 @@ void main() { group('Navigation tests', () { test('Navigation RouteStatus conversion from Pigeon DTO', () { - final NavigationRouteStatus status = - RouteStatusDto.apiKeyNotAuthorized.toNavigationRouteStatus(); + final NavigationRouteStatus status = RouteStatusDto.apiKeyNotAuthorized + .toNavigationRouteStatus(); expect( status.toString().split('.').last, @@ -267,11 +268,10 @@ void main() { }); test('Navigation time and distance conversion from Pigeon DTO', () { - final NavigationTimeAndDistance td = - NavigationTimeAndDistanceDto( - time: 5.0, - distance: 6.0, - ).toNavigationTimeAndDistance(); + final NavigationTimeAndDistance td = NavigationTimeAndDistanceDto( + time: 5.0, + distance: 6.0, + ).toNavigationTimeAndDistance(); expect(td.time, 5.0); expect(td.distance, 6.0); @@ -361,15 +361,14 @@ void main() { test('Road stretch rendering data from Pigeon DTO', () { final RouteSegmentTrafficDataRoadStretchRenderingDataDto data = RouteSegmentTrafficDataRoadStretchRenderingDataDto( - style: - RouteSegmentTrafficDataRoadStretchRenderingDataStyleDto - .slowerTraffic, + style: RouteSegmentTrafficDataRoadStretchRenderingDataStyleDto + .slowerTraffic, lengthMeters: 500, offsetMeters: 600, ); - final RouteSegmentTrafficDataRoadStretchRenderingData gmsData = - data.toRouteSegmentTrafficDataRoadStretchRenderingData(); + final RouteSegmentTrafficDataRoadStretchRenderingData gmsData = data + .toRouteSegmentTrafficDataRoadStretchRenderingData(); expect(data.lengthMeters, gmsData.lengthMeters); expect(data.offsetMeters, gmsData.offsetMeters); @@ -382,9 +381,8 @@ void main() { test('Road segment traffic data from Pigeon DTO', () { final RouteSegmentTrafficDataRoadStretchRenderingDataDto renderingData = RouteSegmentTrafficDataRoadStretchRenderingDataDto( - style: - RouteSegmentTrafficDataRoadStretchRenderingDataStyleDto - .slowerTraffic, + style: RouteSegmentTrafficDataRoadStretchRenderingDataStyleDto + .slowerTraffic, lengthMeters: 500, offsetMeters: 600, ); @@ -422,9 +420,8 @@ void main() { test('Navigation route segment from Pigeon DTO', () { final RouteSegmentTrafficDataRoadStretchRenderingDataDto renderingData = RouteSegmentTrafficDataRoadStretchRenderingDataDto( - style: - RouteSegmentTrafficDataRoadStretchRenderingDataStyleDto - .slowerTraffic, + style: RouteSegmentTrafficDataRoadStretchRenderingDataStyleDto + .slowerTraffic, lengthMeters: 500, offsetMeters: 600, );