diff --git a/tomtom-navigation-core-examples/gradle/libs.versions.toml b/tomtom-navigation-core-examples/gradle/libs.versions.toml index f8ee5ea..da3a38d 100644 --- a/tomtom-navigation-core-examples/gradle/libs.versions.toml +++ b/tomtom-navigation-core-examples/gradle/libs.versions.toml @@ -2,6 +2,7 @@ androidx_compat = "1.6.1" androidx_constraintlayout = "2.1.4" tomtom_sdk = "1.21.0" +lifecycle_version = "2.8.7" [libraries] locationProvider = { module = "com.tomtom.sdk.location:provider-default", version.ref = "tomtom_sdk" } @@ -14,9 +15,12 @@ routePlannerOnline = { module = "com.tomtom.sdk.routing:route-planner-online", v routeReplannerOnline = { module = "com.tomtom.sdk.navigation:route-replanner-online", version.ref = "tomtom_sdk" } androidXCompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx_compat" } androidXConstraintLayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx_constraintlayout" } +androidXLifecycleViewmodelKtx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle_version" } +androidXLifecycleLivedataKtx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycle_version" } +androidXFragmentKtx = { module = "androidx.fragment:fragment-ktx", version = "1.8.5" } [bundles] -androidCommon = ["androidXCompat", "androidXConstraintLayout"] +androidCommon = ["androidXCompat", "androidXConstraintLayout", "androidXLifecycleViewmodelKtx", "androidXLifecycleLivedataKtx", "androidXFragmentKtx"] [plugins] androidApplication = { id = "com.android.application", version = "8.7.2" } diff --git a/tomtom-navigation-core-examples/usecase/src/main/AndroidManifest.xml b/tomtom-navigation-core-examples/usecase/src/main/AndroidManifest.xml index 6c0f302..b77cada 100644 --- a/tomtom-navigation-core-examples/usecase/src/main/AndroidManifest.xml +++ b/tomtom-navigation-core-examples/usecase/src/main/AndroidManifest.xml @@ -22,11 +22,9 @@ android:label="Navigation App" tools:targetApi="33" android:theme="@style/Theme.App"> - + android:exported="true"> diff --git a/tomtom-navigation-core-examples/usecase/src/main/kotlin/com/example/usecase/MainActivity.kt b/tomtom-navigation-core-examples/usecase/src/main/kotlin/com/example/usecase/MainActivity.kt index 434ddf2..e774d8c 100644 --- a/tomtom-navigation-core-examples/usecase/src/main/kotlin/com/example/usecase/MainActivity.kt +++ b/tomtom-navigation-core-examples/usecase/src/main/kotlin/com/example/usecase/MainActivity.kt @@ -17,24 +17,16 @@ import android.content.pm.PackageManager import android.os.Bundle import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import com.example.usecase.BuildConfig.TOMTOM_API_KEY -import com.tomtom.sdk.datamanagement.navigationtile.NavigationTileStore -import com.tomtom.sdk.datamanagement.navigationtile.NavigationTileStoreConfiguration -import com.tomtom.sdk.location.DefaultLocationProviderFactory -import com.tomtom.sdk.location.GeoLocation import com.tomtom.sdk.location.GeoPoint -import com.tomtom.sdk.location.LocationProvider -import com.tomtom.sdk.location.OnLocationUpdateListener -import com.tomtom.sdk.location.mapmatched.MapMatchedLocationProviderFactory -import com.tomtom.sdk.location.simulation.SimulationLocationProvider -import com.tomtom.sdk.location.simulation.strategy.InterpolationStrategy import com.tomtom.sdk.map.display.MapOptions import com.tomtom.sdk.map.display.TomTomMap -import com.tomtom.sdk.map.display.camera.CameraTrackingMode import com.tomtom.sdk.map.display.camera.CameraChangeListener import com.tomtom.sdk.map.display.camera.CameraOptions +import com.tomtom.sdk.map.display.camera.CameraTrackingMode import com.tomtom.sdk.map.display.common.screen.Padding import com.tomtom.sdk.map.display.gesture.MapLongClickListener import com.tomtom.sdk.map.display.location.LocationMarkerOptions @@ -43,29 +35,10 @@ import com.tomtom.sdk.map.display.route.RouteClickListener import com.tomtom.sdk.map.display.route.RouteOptions import com.tomtom.sdk.map.display.ui.MapFragment import com.tomtom.sdk.map.display.ui.currentlocation.CurrentLocationButton.VisibilityPolicy -import com.tomtom.sdk.navigation.ActiveRouteChangedListener -import com.tomtom.sdk.navigation.ProgressUpdatedListener import com.tomtom.sdk.navigation.RoutePlan -import com.tomtom.sdk.navigation.TomTomNavigation -import com.tomtom.sdk.navigation.online.Configuration -import com.tomtom.sdk.navigation.online.OnlineTomTomNavigationFactory -import com.tomtom.sdk.navigation.routereplanner.RouteReplanner -import com.tomtom.sdk.navigation.routereplanner.online.OnlineRouteReplannerFactory import com.tomtom.sdk.navigation.ui.NavigationFragment import com.tomtom.sdk.navigation.ui.NavigationUiOptions -import com.tomtom.sdk.routing.RoutePlanner -import com.tomtom.sdk.routing.RoutePlanningCallback -import com.tomtom.sdk.routing.RoutePlanningResponse -import com.tomtom.sdk.routing.RoutingFailure -import com.tomtom.sdk.routing.options.Itinerary -import com.tomtom.sdk.routing.options.RoutePlanningOptions -import com.tomtom.sdk.routing.options.guidance.ExtendedSections -import com.tomtom.sdk.routing.options.guidance.GuidanceOptions -import com.tomtom.sdk.routing.options.guidance.InstructionPhoneticsType -import com.tomtom.sdk.routing.online.OnlineRoutePlanner import com.tomtom.sdk.routing.route.Route -import com.tomtom.sdk.vehicle.Vehicle -import com.tomtom.sdk.vehicle.VehicleProviderFactory /** * This example shows how to build a simple navigation application using the TomTom Navigation SDK for Android. @@ -77,46 +50,96 @@ import com.tomtom.sdk.vehicle.VehicleProviderFactory **/ class MainActivity : AppCompatActivity() { + private var _navigationFragment: NavigationFragment? = null + private val navigationFragment: NavigationFragment + get() = + _navigationFragment + ?: throw IllegalStateException("Navigation fragment was not initialized") private lateinit var mapFragment: MapFragment private lateinit var tomTomMap: TomTomMap - private lateinit var navigationTileStore: NavigationTileStore - private lateinit var locationProvider: LocationProvider - private lateinit var onLocationUpdateListener: OnLocationUpdateListener - private lateinit var routePlanner: RoutePlanner - private var route: Route? = null - private lateinit var routePlanningOptions: RoutePlanningOptions - private lateinit var tomTomNavigation: TomTomNavigation - private lateinit var navigationFragment: NavigationFragment private val apiKey = TOMTOM_API_KEY + private val viewModel: MainViewModel by viewModels() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - initLocationProvider() - ensureLocationPermissions { - // Enables the location provider to start receiving location updates. - locationProvider.enable() + // Enables the location provider to start receiving location updates only if we are in browsing mode. + if (savedInstanceState == null) { + viewModel.mapLocationProvider.enable() + } } + createNavigationFragment() initMap { - /** - * The LocationProvider itself only reports location changes. - * It does not interact internally with the map or navigation. - * Therefore, to show the user’s location on the map you have to set the LocationProvider to the TomTomMap. - * The TomTomMap will then use the LocationProvider to show a location marker on the map. - */ - tomTomMap.setLocationProvider(locationProvider) - - showUserLocation() + if(!viewModel.isNavigationRunning()) { + /** + * The LocationProvider itself only reports location changes. + * It does not interact internally with the map or navigation. + * Therefore, to show the user’s location on the map you have to set the LocationProvider to the TomTomMap. + * The TomTomMap will then use the LocationProvider to show a location marker on the map. + */ + tomTomMap.setLocationProvider(viewModel.mapLocationProvider) + showUserLocation() + } else { + setMapNavigationPadding() + /** + * When already navigating the MapMatchedLocationProvider is set to the TomTomMap. + * It may happen when there is screen rotation. + */ + tomTomMap.setLocationProvider(viewModel.mapMatchedLocationProvider) + } setUpMapListeners() + setUpViewModelObservers() + } + + } + + private fun setUpViewModelObservers() { + viewModel.route.observe(this) { + tomTomMap.removeRoutes() + drawRoute(it) + if (!viewModel.isNavigationRunning()) { + tomTomMap.zoomToRoutes(ZOOM_TO_ROUTE_PADDING) + } } + viewModel.distanceAlongRoute.observe(this) { + tomTomMap.routes.firstOrNull()?.progress = it + } + viewModel.routingFailure.observe(this) { + Toast.makeText(this@MainActivity, it.message, Toast.LENGTH_SHORT).show() + } + } + + override fun onStart() { + super.onStart() + _navigationFragment?.addNavigationListener(navigationListener) + if (::tomTomMap.isInitialized && tomTomMap.getLocationProvider() == null) { + if (viewModel.isNavigationRunning()) { + tomTomMap.setLocationProvider(viewModel.mapMatchedLocationProvider) + } else { + tomTomMap.setLocationProvider(viewModel.mapLocationProvider) + } + } + } - initNavigationTileStore() - initRouting() - initNavigation() + override fun onStop() { + super.onStop() + _navigationFragment?.removeNavigationListener(navigationListener) + // onCleared in ViewModel is called before onDestroy in Activity so the clean up + // has to be done in onStop - https://issuetracker.google.com/issues/363903522 + if(::tomTomMap.isInitialized) { + tomTomMap.setLocationProvider(null) + } + } + + override fun onDestroy() { + tomTomMap.removeRouteClickListener(routeClickListener) + tomTomMap.removeMapLongClickListener(mapLongClickListener) + super.onDestroy() } /** @@ -130,62 +153,22 @@ class MainActivity : AppCompatActivity() { * after which the [onMapReady] callback is triggered. */ private fun initMap(onMapReady: () -> Unit) { - val mapOptions = MapOptions(mapKey = apiKey) - mapFragment = MapFragment.newInstance(mapOptions) - - supportFragmentManager.beginTransaction() - .replace(R.id.map_container, mapFragment) - .commit() - + mapFragment = supportFragmentManager.findFragmentByTag(MAP_FRAGMENT_TAG)?.let { + it as MapFragment + } ?: run { + val mapOptions = MapOptions(mapKey = apiKey) + MapFragment.newInstance(mapOptions).also { + supportFragmentManager.beginTransaction() + .replace(R.id.map_container, it, MAP_FRAGMENT_TAG) + .commit() + } + } mapFragment.getMapAsync { map -> tomTomMap = map onMapReady() } } - /** - * The SDK provides a [NavigationTileStore] class that is used between different modules to get tile data based - * on the online map. - */ - private fun initNavigationTileStore() { - navigationTileStore = NavigationTileStore.create( - context = applicationContext, - navigationTileStoreConfig = NavigationTileStoreConfiguration( - apiKey = apiKey - ) - ) - } - - /** - * The SDK provides a [LocationProvider] interface that is used between different modules to get location updates. - * This examples uses the default [LocationProvider]. - * Under the hood, the engine uses Android’s system location services. - */ - private fun initLocationProvider() { - locationProvider = DefaultLocationProviderFactory.create(context = applicationContext) - } - - /** - * Plans the route by initializing by using the online route planner and default route replanner. - */ - private fun initRouting() { - routePlanner = OnlineRoutePlanner.create(context = applicationContext, apiKey = apiKey) - } - - /** - * To use navigation in the application, start by by initialising the navigation configuration. - */ - private fun initNavigation() { - val configuration = Configuration( - context = applicationContext, - navigationTileStore = navigationTileStore, - locationProvider = locationProvider, - routePlanner = routePlanner, - vehicleProvider = VehicleProviderFactory.create(vehicle = Vehicle.Car()) - ) - tomTomNavigation = OnlineTomTomNavigationFactory.create(configuration) - } - /** * The application must use the device’s location services, which requires the appropriate permissions. */ @@ -205,13 +188,12 @@ class MainActivity : AppCompatActivity() { */ private fun showUserLocation() { // zoom to current location at city level - onLocationUpdateListener = OnLocationUpdateListener { location -> + viewModel.location.observe(this) { val locationMarker = LocationMarkerOptions(type = LocationMarkerOptions.Type.Pointer) tomTomMap.enableLocationMarker(locationMarker) - tomTomMap.moveCamera(CameraOptions(location.position, zoom = 8.0)) - locationProvider.removeOnLocationUpdateListener(onLocationUpdateListener) + tomTomMap.moveCamera(CameraOptions(it.position, zoom = 8.0)) } - locationProvider.addOnLocationUpdateListener(onLocationUpdateListener) + viewModel.registerLocationUpdatesListener() } /** @@ -238,68 +220,24 @@ class MainActivity : AppCompatActivity() { true } - /** - * Checks whether navigation is currently running. - */ - private fun isNavigationRunning(): Boolean = tomTomNavigation.navigationSnapshot != null - - /** * Used to start navigation based on a tapped route, if navigation is not already running. * - Hide the location button * - Then start the navigation using the selected route. */ private val routeClickListener = RouteClickListener { - if (!isNavigationRunning()) { - route?.let { route -> + if (!viewModel.isNavigationRunning()) { + viewModel.route.value?.let { route -> mapFragment.currentLocationButton.visibilityPolicy = VisibilityPolicy.Invisible startNavigation(route) } } } - /** - * Used to calculate a route using the following parameters: - * - InstructionPhoneticsType - This specifies whether to include phonetic transcriptions in the response. - * - ExtendedSections - This specifies whether to include extended guidance sections in the response, such as sections of type road shield, lane, and speed limit. - */ private fun calculateRouteTo(destination: GeoPoint) { val userLocation = tomTomMap.currentLocation?.position ?: return - val itinerary = Itinerary(origin = userLocation, destination = destination) - routePlanningOptions = RoutePlanningOptions( - itinerary = itinerary, - guidanceOptions = GuidanceOptions( - phoneticsType = InstructionPhoneticsType.Ipa, - extendedSections = ExtendedSections.All - ), - vehicle = Vehicle.Car() - ) - routePlanner.planRoute(routePlanningOptions, routePlanningCallback) - } - - /** - * The RoutePlanningCallback itself has three methods. - * - The `onFailure()` method is triggered if the request fails. - * - The `onSuccess()` method returns RoutePlanningResponse containing the routing results. - * - The `onRoutePlanned()` method is triggered when each route is successfully calculated. - * - * This example draws the first retrieved route on the map. - * You can show the overview of the added routes using the TomTomMap.zoomToRoutes(Int) method. - * Note that its padding parameter is expressed in pixels. - */ - private val routePlanningCallback = object : RoutePlanningCallback { - override fun onSuccess(result: RoutePlanningResponse) { - route = result.routes.first() - drawRoute(route!!) - tomTomMap.zoomToRoutes(ZOOM_TO_ROUTE_PADDING) - } - - override fun onFailure(failure: RoutingFailure) { - Toast.makeText(this@MainActivity, failure.message, Toast.LENGTH_SHORT).show() - } - - override fun onRoutePlanned(route: Route) = Unit + viewModel.calculateRoute(userLocation, destination) } /** @@ -333,20 +271,26 @@ class MainActivity : AppCompatActivity() { /** * Used to start navigation by - * - initializing the NavigationFragment to display the UI navigation information, + * - display the UI navigation information, * - passing the Route object along which the navigation will be done, and RoutePlanningOptions used during the route planning, * - handling the updates to the navigation states using the NavigationListener. * Note that you have to set the previously-created TomTom Navigation object to the NavigationFragment before using it. */ - private fun startNavigation(route: Route) { - initNavigationFragment() - navigationFragment.setTomTomNavigation(tomTomNavigation) - val routePlan = RoutePlan(route, routePlanningOptions) - navigationFragment.startNavigation(routePlan) - navigationFragment.addNavigationListener(navigationListener) - tomTomNavigation.addProgressUpdatedListener(progressUpdatedListener) - tomTomNavigation.addActiveRouteChangedListener(activeRouteChangedListener) + displayNavigationFragment() + navigationFragment.setTomTomNavigation(viewModel.tomTomNavigation) + viewModel.navigationStart(route) + tomTomMap.setLocationProvider(viewModel.mapMatchedLocationProvider) + navigationFragment.startNavigation(RoutePlan(route, viewModel.routePlanningOptions)) + } + + /** + * Used to display the UI navigation information, + */ + private fun displayNavigationFragment() { + supportFragmentManager.beginTransaction() + .replace(R.id.navigation_fragment_container, navigationFragment, NAVIGATION_FRAGMENT_TAG) + .commitNow() } /** @@ -361,8 +305,6 @@ class MainActivity : AppCompatActivity() { tomTomMap.addCameraChangeListener(cameraChangeListener) tomTomMap.cameraTrackingMode = CameraTrackingMode.FollowRouteDirection tomTomMap.enableLocationMarker(LocationMarkerOptions(LocationMarkerOptions.Type.Chevron)) - setMapMatchedLocationProvider() - setSimulationLocationProviderToNavigation(route!!) setMapNavigationPadding() } @@ -371,40 +313,16 @@ class MainActivity : AppCompatActivity() { } } - /** - * Used to initialize the NavigationFragment to display the UI navigation information, - */ - private fun initNavigationFragment() { - if (!::navigationFragment.isInitialized) { - navigationFragment = NavigationFragment.newInstance( + private fun createNavigationFragment() { + _navigationFragment = + supportFragmentManager.findFragmentByTag(NAVIGATION_FRAGMENT_TAG) as? NavigationFragment + if (_navigationFragment == null) { + _navigationFragment = NavigationFragment.newInstance( NavigationUiOptions( keepInBackground = true ) ) } - supportFragmentManager.beginTransaction() - .replace(R.id.navigation_fragment_container, navigationFragment) - .commitNow() - } - - private val progressUpdatedListener = ProgressUpdatedListener { - tomTomMap.routes.first().progress = it.distanceAlongRoute - } - - private val activeRouteChangedListener = ActiveRouteChangedListener { route -> - tomTomMap.removeRoutes() - drawRoute(route) - } - - /** - * Use the SimulationLocationProvider for testing purposes. - */ - private fun setSimulationLocationProviderToNavigation(route: Route) { - val routeGeoLocations = route.geometry.map { GeoLocation(it) } - val simulationStrategy = InterpolationStrategy(routeGeoLocations) - locationProvider = SimulationLocationProvider.create(strategy = simulationStrategy) - tomTomNavigation.locationProvider = locationProvider - locationProvider.enable() } /** @@ -418,15 +336,10 @@ class MainActivity : AppCompatActivity() { VisibilityPolicy.InvisibleWhenRecentered tomTomMap.removeCameraChangeListener(cameraChangeListener) tomTomMap.cameraTrackingMode = CameraTrackingMode.None - tomTomMap.enableLocationMarker(LocationMarkerOptions(LocationMarkerOptions.Type.Pointer)) resetMapPadding() - navigationFragment.removeNavigationListener(navigationListener) - tomTomNavigation.removeProgressUpdatedListener(progressUpdatedListener) - tomTomNavigation.removeActiveRouteChangedListener(activeRouteChangedListener) clearMap() - initLocationProvider() - tomTomMap.setLocationProvider(locationProvider) - locationProvider.enable() + tomTomMap.setLocationProvider(viewModel.mapLocationProvider) + viewModel.navigationStopped() showUserLocation() } @@ -435,7 +348,10 @@ class MainActivity : AppCompatActivity() { */ private fun setMapNavigationPadding() { val paddingBottom = resources.getDimensionPixelOffset(R.dimen.map_padding_bottom) - val padding = Padding(0, 0, 0, paddingBottom) + val paddingLeft = resources.getDimensionPixelOffset(R.dimen.map_padding_left) + val paddingRight = resources.getDimensionPixelOffset(R.dimen.map_padding_right) + val paddingTop = resources.getDimensionPixelOffset(R.dimen.map_padding_top) + val padding = Padding(paddingLeft, paddingTop, paddingRight, paddingBottom) tomTomMap.setPadding(padding) } @@ -443,16 +359,6 @@ class MainActivity : AppCompatActivity() { tomTomMap.setPadding(Padding(0, 0, 0, 0)) } - /** - * Once navigation is started, the camera is set to follow the user position, and the location indicator is changed to a chevron. - * To match raw location updates to the routes, create a LocationProvider instance using MapMatchedLocationProviderFactory and set it to the TomTomMap. - */ - private fun setMapMatchedLocationProvider() { - val mapMatchedLocationProvider = MapMatchedLocationProviderFactory.create(tomTomNavigation) - tomTomMap.setLocationProvider(mapMatchedLocationProvider) - mapMatchedLocationProvider.enable() - } - /** * * The method removes all polygons, circles, routes, and markers that were previously added to the map. @@ -504,18 +410,9 @@ class MainActivity : AppCompatActivity() { ) ) - override fun onDestroy() { - tomTomMap.setLocationProvider(null) - if (::navigationFragment.isInitialized) { - supportFragmentManager.beginTransaction().remove(navigationFragment).commitNowAllowingStateLoss() - } - super.onDestroy() - tomTomNavigation.close() - navigationTileStore.close() - locationProvider.close() - } - companion object { private const val ZOOM_TO_ROUTE_PADDING = 100 + private const val NAVIGATION_FRAGMENT_TAG = "NAVIGATION_FRAGMENT_TAG" + private const val MAP_FRAGMENT_TAG = "MAP_FRAGMENT_TAG" } } diff --git a/tomtom-navigation-core-examples/usecase/src/main/kotlin/com/example/usecase/MainViewModel.kt b/tomtom-navigation-core-examples/usecase/src/main/kotlin/com/example/usecase/MainViewModel.kt new file mode 100644 index 0000000..38f607b --- /dev/null +++ b/tomtom-navigation-core-examples/usecase/src/main/kotlin/com/example/usecase/MainViewModel.kt @@ -0,0 +1,237 @@ +package com.example.usecase + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.example.usecase.BuildConfig.TOMTOM_API_KEY +import com.tomtom.quantity.Distance +import com.tomtom.sdk.datamanagement.navigationtile.NavigationTileStore +import com.tomtom.sdk.datamanagement.navigationtile.NavigationTileStoreConfiguration +import com.tomtom.sdk.location.DefaultLocationProviderFactory +import com.tomtom.sdk.location.GeoLocation +import com.tomtom.sdk.location.GeoPoint +import com.tomtom.sdk.location.LocationProvider +import com.tomtom.sdk.location.OnLocationUpdateListener +import com.tomtom.sdk.location.mapmatched.MapMatchedLocationProviderFactory +import com.tomtom.sdk.location.simulation.SimulationLocationProvider +import com.tomtom.sdk.location.simulation.strategy.InterpolationStrategy +import com.tomtom.sdk.navigation.ActiveRouteChangedListener +import com.tomtom.sdk.navigation.ProgressUpdatedListener +import com.tomtom.sdk.navigation.TomTomNavigation +import com.tomtom.sdk.navigation.online.Configuration +import com.tomtom.sdk.navigation.online.OnlineTomTomNavigationFactory +import com.tomtom.sdk.routing.RoutePlanner +import com.tomtom.sdk.routing.RoutePlanningCallback +import com.tomtom.sdk.routing.RoutePlanningResponse +import com.tomtom.sdk.routing.RoutingFailure +import com.tomtom.sdk.routing.online.OnlineRoutePlanner +import com.tomtom.sdk.routing.options.Itinerary +import com.tomtom.sdk.routing.options.RoutePlanningOptions +import com.tomtom.sdk.routing.options.guidance.ExtendedSections +import com.tomtom.sdk.routing.options.guidance.GuidanceOptions +import com.tomtom.sdk.routing.options.guidance.InstructionPhoneticsType +import com.tomtom.sdk.routing.route.Route +import com.tomtom.sdk.vehicle.Vehicle +import com.tomtom.sdk.vehicle.VehicleProviderFactory + +class MainViewModel(application: Application): AndroidViewModel(application), OnLocationUpdateListener { + + private val applicationContext = application.applicationContext + private val apiKey = TOMTOM_API_KEY + + lateinit var mapLocationProvider: LocationProvider + private set + private lateinit var navigationLocationProvider: LocationProvider + private var _mapMatchedLocationProvider: LocationProvider? = null + val mapMatchedLocationProvider: LocationProvider + get() = checkNotNull(_mapMatchedLocationProvider) { + "MapMatchedLocationProvider is not initialized" + } + + private lateinit var routePlanner: RoutePlanner + private lateinit var navigationTileStore: NavigationTileStore + lateinit var tomTomNavigation: TomTomNavigation + private set + + private val _location = MutableLiveData() + val location: LiveData + get() = _location + private val _routingFailure = MutableLiveData() + val routingFailure: LiveData + get() = _routingFailure + + private val _distanceAlongRoute = MutableLiveData() + val distanceAlongRoute: LiveData + get() = _distanceAlongRoute + + private val _route = MutableLiveData() + val route: LiveData + get() = _route + + private var _routePlanningOptions: RoutePlanningOptions? = null + val routePlanningOptions: RoutePlanningOptions + get() = checkNotNull(_routePlanningOptions) { + "RoutePlanningOptions is not initialized" + } + + init { + initMapLocationProvider() + initNavigationTileStore() + initRouting() + initNavigation() + } + + /** + * The SDK provides a [NavigationTileStore] class that is used between different modules to get tile data based + * on the online map. + */ + private fun initNavigationTileStore() { + navigationTileStore = NavigationTileStore.create( + context = applicationContext, + navigationTileStoreConfig = NavigationTileStoreConfiguration( + apiKey = apiKey + ) + ) + } + + /** + * The SDK provides a [LocationProvider] interface that is used between different modules to get location updates. + * This examples uses the default [LocationProvider]. + * Under the hood, the engine uses Android’s system location services. + */ + private fun initMapLocationProvider() { + mapLocationProvider = DefaultLocationProviderFactory.create(context = applicationContext) + } + + /** + * Plans the route by initializing by using the online route planner and default route replanner. + */ + private fun initRouting() { + routePlanner = OnlineRoutePlanner.create(context = applicationContext, apiKey = apiKey) + } + + /** + * To use navigation in the application, start by by initialising the navigation configuration. + */ + private fun initNavigation() { + navigationLocationProvider = DefaultLocationProviderFactory.create(context = applicationContext) + val configuration = Configuration( + context = applicationContext, + navigationTileStore = navigationTileStore, + locationProvider = navigationLocationProvider, + routePlanner = routePlanner, + vehicleProvider = VehicleProviderFactory.create(vehicle = Vehicle.Car()) + ) + tomTomNavigation = OnlineTomTomNavigationFactory.create(configuration) + } + + override fun onLocationUpdate(location: GeoLocation) { + _location.value = location + mapLocationProvider.removeOnLocationUpdateListener(this) + } + + fun registerLocationUpdatesListener() { + mapLocationProvider.addOnLocationUpdateListener(this) + } + + /** + * Used to calculate a route using the following parameters: + * - InstructionPhoneticsType - This specifies whether to include phonetic transcriptions in the response. + * - ExtendedSections - This specifies whether to include extended guidance sections in the response, such as sections of type road shield, lane, and speed limit. + */ + fun calculateRoute(origin: GeoPoint, destination: GeoPoint) { + val itinerary = Itinerary(origin = origin, destination = destination) + _routePlanningOptions = RoutePlanningOptions( + itinerary = itinerary, + guidanceOptions = GuidanceOptions( + phoneticsType = InstructionPhoneticsType.Ipa, + extendedSections = ExtendedSections.All + ), + vehicle = Vehicle.Car() + ) + routePlanner.planRoute(routePlanningOptions, routePlanningCallback) + } + + /** + * The RoutePlanningCallback itself has three methods. + * - The `onFailure()` method is triggered if the request fails. + * - The `onSuccess()` method returns RoutePlanningResponse containing the routing results. + * - The `onRoutePlanned()` method is triggered when each route is successfully calculated. + * + * This example draws the first retrieved route on the map. + * You can show the overview of the added routes using the TomTomMap.zoomToRoutes(Int) method. + * Note that its padding parameter is expressed in pixels. + */ + private val routePlanningCallback = object : RoutePlanningCallback { + override fun onSuccess(result: RoutePlanningResponse) { + _route.value = result.routes.first() + } + + override fun onFailure(failure: RoutingFailure) { + _routingFailure.value = failure + } + + override fun onRoutePlanned(route: Route) = Unit + } + + private val progressUpdatedListener = ProgressUpdatedListener { + _distanceAlongRoute.value = it.distanceAlongRoute + } + + private val activeRouteChangedListener = ActiveRouteChangedListener { route -> + _route.value = route + } + + fun navigationStart(route: Route) { + tomTomNavigation.addProgressUpdatedListener(progressUpdatedListener) + tomTomNavigation.addActiveRouteChangedListener(activeRouteChangedListener) + setSimulationLocationProviderToNavigation(route) + setUpMapMatchedLocationProvider() + } + + /** + * Use the SimulationLocationProvider for testing purposes. + */ + private fun setSimulationLocationProviderToNavigation(route: Route) { + val routeGeoLocations = route.geometry.map { GeoLocation(it) } + val simulationStrategy = InterpolationStrategy(routeGeoLocations) + val oldNavigationLocationProvider = navigationLocationProvider + navigationLocationProvider = SimulationLocationProvider.create(strategy = simulationStrategy) + tomTomNavigation.locationProvider = navigationLocationProvider + navigationLocationProvider.enable() + oldNavigationLocationProvider.close() + } + + /** + * Once navigation is started, the camera is set to follow the user position, and the location indicator is changed to a chevron. + * To match raw location updates to the routes, create a LocationProvider instance using MapMatchedLocationProviderFactory and set it to the TomTomMap. + */ + private fun setUpMapMatchedLocationProvider() { + _mapMatchedLocationProvider = MapMatchedLocationProviderFactory.create(tomTomNavigation) + _mapMatchedLocationProvider?.enable() + mapLocationProvider.disable() + } + + + fun navigationStopped() { + tomTomNavigation.removeProgressUpdatedListener(progressUpdatedListener) + tomTomNavigation.removeActiveRouteChangedListener(activeRouteChangedListener) + mapLocationProvider.enable() + navigationLocationProvider.disable() + _mapMatchedLocationProvider?.close() + } + /** + * Checks whether navigation is currently running. + */ + fun isNavigationRunning(): Boolean = tomTomNavigation.navigationSnapshot != null + + override fun onCleared() { + super.onCleared() + _mapMatchedLocationProvider?.close() + tomTomNavigation.close() + navigationLocationProvider.close() + navigationTileStore.close() + mapLocationProvider.close() + } +} \ No newline at end of file diff --git a/tomtom-navigation-core-examples/usecase/src/main/res/values-land/dimens.xml b/tomtom-navigation-core-examples/usecase/src/main/res/values-land/dimens.xml new file mode 100644 index 0000000..2cba77a --- /dev/null +++ b/tomtom-navigation-core-examples/usecase/src/main/res/values-land/dimens.xml @@ -0,0 +1,18 @@ + + + + + 30dp + 60dp + 300dp + 0dp + \ No newline at end of file diff --git a/tomtom-navigation-core-examples/usecase/src/main/res/values/dimens.xml b/tomtom-navigation-core-examples/usecase/src/main/res/values/dimens.xml index 61f4012..95c1e0c 100644 --- a/tomtom-navigation-core-examples/usecase/src/main/res/values/dimens.xml +++ b/tomtom-navigation-core-examples/usecase/src/main/res/values/dimens.xml @@ -11,5 +11,8 @@ --> - 263.0dp + 263dp + 0dp + 0dp + 0dp \ No newline at end of file