From 39aa963d4036631a1cfc9e984ac6bb09a5cea571 Mon Sep 17 00:00:00 2001 From: Zhandr Date: Mon, 30 Sep 2024 18:28:42 +0500 Subject: [PATCH 01/46] replaced viewmodel to simple class realisation replaced viewmodel to simple class realisation added access token into constructor added userlocation to calculation method added mapready callback added mapready callback remove unused commented code fix kotlin version fix kotlin version adding context to viewmodel added context as argument to initialize viewmodel added context as argument to initialize viewmodel decreased kotlin version and buildtoolsversion added arguments in calculateRoute function map and routing on one screen making mapBoxMap variable public removing route when canceling navigation removing route when canceling navigation fixed branch name and PR title Push to new repo map and routing on one screen map and routing on one screen map and routing on one screen Made mapview and route navigation in one single view (Activity) Made mapview and route navigation in one single view (Activity) --- README.md | 2 +- app/build.gradle | 9 +- .../NavigationViewOrientationTest.java | 103 -- .../java/testapp/NavigationViewTest.java | 37 - .../OnNavigationReadyIdlingResource.java | 5 + .../testapp/NavigationUIActivity.kt | 242 +++-- .../res/layout/activity_navigation_ui.xml | 57 +- build.gradle | 7 +- gradle/wrapper/gradle-wrapper.properties | 1 + libandroid-navigation-ui/build.gradle | 155 +-- .../ui/v5/MapboxNavigationActivity.java | 5 + .../navigation/ui/v5/NavigationLauncher.java | 12 +- .../navigation/ui/v5/NavigationRouteView.kt | 986 ++++++++++++++++++ .../navigation/ui/v5/NavigationView.java | 12 +- .../ui/v5/NavigationViewEventDispatcher.java | 1 + .../navigation/ui/v5/NavigationViewModel.java | 40 +- .../ui/v5/NavigationViewSubscriber.java | 2 +- .../ui/v5/OnNavigationReadyCallback.java | 2 + .../ui/v5/camera/NavigationCamera.java | 4 + .../ui/v5/map/MapLayerInteractor.java | 8 +- .../navigation/ui/v5/route/MapRouteArrow.java | 20 +- .../navigation/ui/v5/route/MapRouteLine.java | 19 +- .../ui/v5/route/RouteConstants.java | 6 + .../res/layout/navigation_view_layout.xml | 131 ++- .../main/res/layout/summary_peek_layout.xml | 2 +- .../src/main/res/values/developer-config.xml | 9 + .../src/main/res/values/styles.xml | 8 + libandroid-navigation/build.gradle | 31 + libandroid-navigation/proguard-consumer.pro | 1 + .../src/main/AndroidManifest.xml | 2 +- .../MapboxNavigationNotification.java | 12 +- .../v5/navigation/NavigationMapRoute.java | 2 +- .../v5/utils/DistanceFormatter.java | 2 +- .../navigation/v5/utils/ManeuverMap.java | 2 +- .../navigation/v5/utils/ManeuverUtils.java | 2 +- .../v5/utils/time/TimeFormatter.java | 2 +- .../v5/utils/DistanceFormatterTest.java | 2 +- settings.gradle | 10 + 38 files changed, 1507 insertions(+), 446 deletions(-) delete mode 100644 app/src/androidTest/java/testapp/NavigationViewOrientationTest.java delete mode 100644 app/src/androidTest/java/testapp/NavigationViewTest.java create mode 100644 libandroid-navigation-ui/src/main/java/com/mapbox/services/android/navigation/ui/v5/NavigationRouteView.kt create mode 100644 libandroid-navigation-ui/src/main/res/values/developer-config.xml diff --git a/README.md b/README.md index 59df22d59..73ba297c3 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ With this SDK you can implement turn-by-turn navigation in your own Android app - No Telemetry -## Why have we forked +## Why have we forked¬ 1. Mapbox decided to put a closed-source component to their navigation SDK and introduced a non-open-source license. Maplibre wants an open-source solution. 2. Mapbox decided to put telemetry in their SDK. We couldn't turn this off without adjusting the source. diff --git a/app/build.gradle b/app/build.gradle index f16ba96cf..8a2d818a3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,6 +22,8 @@ android { targetCompatibility JavaVersion.VERSION_17 } + namespace 'com.mapbox.services.android.navigation.testapp' + defaultConfig { applicationId "com.mapbox.services.android.navigation.testapp" minSdkVersion androidVersions.minSdkVersion @@ -29,7 +31,11 @@ android { versionCode 1 versionName "0.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - multiDexEnabled true + multiDexEnabled false + } + + buildFeatures { + buildConfig true } buildTypes { @@ -53,6 +59,7 @@ android { buildFeatures { viewBinding = true + buildConfig = true } lintOptions { diff --git a/app/src/androidTest/java/testapp/NavigationViewOrientationTest.java b/app/src/androidTest/java/testapp/NavigationViewOrientationTest.java deleted file mode 100644 index 9001f67ed..000000000 --- a/app/src/androidTest/java/testapp/NavigationViewOrientationTest.java +++ /dev/null @@ -1,103 +0,0 @@ -package testapp; - -import android.content.res.Configuration; -import androidx.test.espresso.ViewAction; - -import com.mapbox.services.android.navigation.testapp.R; -import com.mapbox.services.android.navigation.testapp.test.TestNavigationActivity; -import com.mapbox.services.android.navigation.ui.v5.map.NavigationMapboxMap; - -import org.junit.Test; - -import testapp.activity.BaseNavigationActivityTest; - -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.action.ViewActions.swipeUp; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.isRoot; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static junit.framework.Assert.assertFalse; -import static testapp.action.OrientationChangeAction.orientationLandscape; -import static testapp.action.OrientationChangeAction.orientationPortrait; - -public class NavigationViewOrientationTest extends BaseNavigationActivityTest { - - @Override - protected Class getActivityClass() { - return TestNavigationActivity.class; - } - - @Test - public void onOrientationLandscape_navigationContinuesRunning() { - if (checkOrientation(Configuration.ORIENTATION_LANDSCAPE)) { - return; - } - validateTestSetup(); - - changeOrientation(orientationLandscape()); - } - - @Test - public void onOrientationPortrait_navigationContinuesRunning() { - if (checkOrientation(Configuration.ORIENTATION_PORTRAIT)) { - return; - } - validateTestSetup(); - - changeOrientation(orientationPortrait()); - } - - @Test - public void onOrientationChange_recenterBtnStateIsRestore() { - if (checkOrientation(Configuration.ORIENTATION_LANDSCAPE)) { - return; - } - validateTestSetup(); - - onView(withId(R.id.routeOverviewBtn)).perform(click()); - changeOrientation(orientationLandscape()); - onView(withId(R.id.recenterBtn)).check(matches(isDisplayed())); - } - - @Test - public void onOrientationChange_cameraTrackingIsRestore() { - if (checkOrientation(Configuration.ORIENTATION_LANDSCAPE)) { - return; - } - validateTestSetup(); - - onView(withId(R.id.navigationMapView)).perform(swipeUp()); - changeOrientation(orientationLandscape()); - - NavigationMapboxMap navigationMapboxMap = getNavigationView().retrieveNavigationMapboxMap(); - boolean isTrackingEnabled = navigationMapboxMap.retrieveCamera().isTrackingEnabled(); - assertFalse(isTrackingEnabled); - } - - @Test - public void onOrientationChange_waynameVisibilityIsRestored() { - if (checkOrientation(Configuration.ORIENTATION_LANDSCAPE)) { - return; - } - validateTestSetup(); - - onView(withId(R.id.navigationMapView)).perform(swipeUp()); - changeOrientation(orientationLandscape()); - - NavigationMapboxMap navigationMapboxMap = getNavigationView().retrieveNavigationMapboxMap(); - boolean isWaynameVisible = navigationMapboxMap.isWaynameVisible(); - assertFalse(isWaynameVisible); - } - - // TODO create test rule for this to conditionally ignore - private boolean checkOrientation(int testedOrientation) { - int orientation = getNavigationView().getContext().getResources().getConfiguration().orientation; - return orientation == testedOrientation; - } - - private void changeOrientation(ViewAction newOrientation) { - onView(isRoot()).perform(newOrientation); - } -} diff --git a/app/src/androidTest/java/testapp/NavigationViewTest.java b/app/src/androidTest/java/testapp/NavigationViewTest.java deleted file mode 100644 index 16465de64..000000000 --- a/app/src/androidTest/java/testapp/NavigationViewTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package testapp; - -import com.mapbox.services.android.navigation.testapp.test.TestNavigationActivity; -import com.mapbox.services.android.navigation.ui.v5.map.NavigationMapboxMap; -import com.mapbox.services.android.navigation.v5.navigation.MapboxNavigation; - -import org.junit.Test; - -import testapp.activity.BaseNavigationActivityTest; - -import static junit.framework.Assert.assertNotNull; - -public class NavigationViewTest extends BaseNavigationActivityTest { - - @Override - protected Class getActivityClass() { - return TestNavigationActivity.class; - } - - @Test - public void onInitialization_navigationMapboxMapIsNotNull() { - validateTestSetup(); - - NavigationMapboxMap navigationMapboxMap = getNavigationView().retrieveNavigationMapboxMap(); - - assertNotNull(navigationMapboxMap); - } - - @Test - public void onNavigationStart_mapboxNavigationIsNotNull() { - validateTestSetup(); - - MapboxNavigation mapboxNavigation = getNavigationView().retrieveMapboxNavigation(); - - assertNotNull(mapboxNavigation); - } -} diff --git a/app/src/androidTest/java/testapp/utils/OnNavigationReadyIdlingResource.java b/app/src/androidTest/java/testapp/utils/OnNavigationReadyIdlingResource.java index ba64b4c1d..a2039ce9e 100644 --- a/app/src/androidTest/java/testapp/utils/OnNavigationReadyIdlingResource.java +++ b/app/src/androidTest/java/testapp/utils/OnNavigationReadyIdlingResource.java @@ -85,6 +85,11 @@ public void onNavigationReady(boolean isRunning) { transitionToIdle(); } + @Override + public void onMapReadyCallback() { + // Intentional empty + } + private void fetchRoute(Context context) { Point origin = Point.fromLngLat(-77.033987, 38.900123); Point destination = Point.fromLngLat(-77.044818, 38.848942); diff --git a/app/src/main/java/com/mapbox/services/android/navigation/testapp/NavigationUIActivity.kt b/app/src/main/java/com/mapbox/services/android/navigation/testapp/NavigationUIActivity.kt index 7b3b793fb..202544fde 100644 --- a/app/src/main/java/com/mapbox/services/android/navigation/testapp/NavigationUIActivity.kt +++ b/app/src/main/java/com/mapbox/services/android/navigation/testapp/NavigationUIActivity.kt @@ -2,8 +2,10 @@ package com.mapbox.services.android.navigation.testapp import android.annotation.SuppressLint import android.os.Bundle -import android.view.View +import android.os.Parcelable +import android.preference.PreferenceManager import android.widget.Toast +import androidx.activity.ComponentActivity import androidx.appcompat.app.AppCompatActivity import com.google.android.material.snackbar.Snackbar import com.mapbox.api.directions.v5.DirectionsCriteria @@ -22,10 +24,14 @@ import com.mapbox.mapboxsdk.maps.Style import com.mapbox.services.android.navigation.testapp.databinding.ActivityNavigationUiBinding import com.mapbox.services.android.navigation.ui.v5.NavigationLauncher import com.mapbox.services.android.navigation.ui.v5.NavigationLauncherOptions +import com.mapbox.services.android.navigation.ui.v5.NavigationViewOptions +import com.mapbox.services.android.navigation.ui.v5.OnNavigationReadyCallback +import com.mapbox.services.android.navigation.ui.v5.listeners.NavigationListener import com.mapbox.services.android.navigation.ui.v5.route.NavigationRoute -import com.mapbox.services.android.navigation.v5.milestone.* import com.mapbox.services.android.navigation.v5.models.DirectionsRoute -import com.mapbox.services.android.navigation.v5.navigation.* +import com.mapbox.services.android.navigation.v5.navigation.MapboxNavigationOptions +import com.mapbox.services.android.navigation.v5.navigation.NavigationConstants +import com.mapbox.services.android.navigation.v5.navigation.NavigationMapRoute import com.mapbox.turf.TurfConstants import com.mapbox.turf.TurfMeasurement import okhttp3.Request @@ -35,10 +41,9 @@ import retrofit2.Response import timber.log.Timber class NavigationUIActivity : - AppCompatActivity(), - OnMapReadyCallback, - MapboxMap.OnMapClickListener { - private lateinit var mapboxMap: MapboxMap + ComponentActivity(), + MapboxMap.OnMapClickListener, OnNavigationReadyCallback, + NavigationListener { // Navigation related variables private var route: DirectionsRoute? = null @@ -61,69 +66,43 @@ class NavigationUIActivity : binding = ActivityNavigationUiBinding.inflate(layoutInflater) setContentView(binding.root) - binding.mapView.apply { - onCreate(savedInstanceState) - getMapAsync(this@NavigationUIActivity) - } + binding.navigationView.onCreate(this, savedInstanceState, this) - binding.startRouteButton.setOnClickListener { - route?.let { route -> - val userLocation = mapboxMap.locationComponent.lastKnownLocation ?: return@let - - val options = NavigationLauncherOptions.builder() - .directionsRoute(route) - .shouldSimulateRoute(simulateRoute) - .initialMapCameraPosition(CameraPosition.Builder().target(LatLng(userLocation.latitude, userLocation.longitude)).build()) - .lightThemeResId(R.style.TestNavigationViewLight) - .darkThemeResId(R.style.TestNavigationViewDark) - .build() - NavigationLauncher.startNavigation(this@NavigationUIActivity, options) - } - } - - binding.simulateRouteSwitch.setOnCheckedChangeListener { _, checked -> - simulateRoute = checked - } +// binding.mapView.apply { +// onCreate(savedInstanceState) +// getMapAsync(this@NavigationUIActivity) +// } +// binding.simulateRouteSwitch.setOnCheckedChangeListener { _, checked -> +// simulateRoute = checked +// } - binding.clearPoints.setOnClickListener { - if (::mapboxMap.isInitialized) { - mapboxMap.markers.forEach { - mapboxMap.removeMarker(it) - } - } - destination = null - waypoint = null - it.visibility = View.GONE - binding.startRouteLayout.visibility = View.GONE - - navigationMapRoute?.removeRoute() - } - } - - override fun onMapReady(mapboxMap: MapboxMap) { - this.mapboxMap = mapboxMap - mapboxMap.setStyle(Style.Builder().fromUri(getString(R.string.map_style_light))) { style -> - enableLocationComponent(style) + binding.startRouteButton.setOnClickListener { + val points = mutableListOf>() + points.add(Pair(76.930137, 43.230361)) + points.add(Pair(76.928316, 43.236109)) + points.add(Pair(76.920187, 43.236783)) + binding.navigationView.calculateRoute(points, Pair(76.930137, 43.230361), getString(R.string.mapbox_access_token)) } - - navigationMapRoute = NavigationMapRoute( - binding.mapView, - mapboxMap - ) - - mapboxMap.addOnMapClickListener(this) - Snackbar.make( - findViewById(R.id.container), - "Tap map to place waypoint", - Snackbar.LENGTH_LONG, - ).show() +// binding.clearPoints.setOnClickListener { +// if (::mapboxMap.isInitialized) { +// mapboxMap.markers.forEach { +// mapboxMap.removeMarker(it) +// } +// } +// destination = null +// waypoint = null +// it.visibility = View.GONE +// binding.startRouteLayout.visibility = View.GONE +// +// navigationMapRoute?.removeRoute() +// } } @SuppressWarnings("MissingPermission") private fun enableLocationComponent(style: Style) { // Get an instance of the component - locationComponent = mapboxMap.locationComponent +// locationComponent = mapboxMap.locationComponent locationComponent?.let { // Activate with a built LocationComponentActivationOptions object @@ -143,46 +122,46 @@ class NavigationUIActivity : } override fun onMapClick(point: LatLng): Boolean { - var addMarker = true - when { - destination == null -> destination = Point.fromLngLat(point.longitude, point.latitude) - waypoint == null -> waypoint = Point.fromLngLat(point.longitude, point.latitude) - else -> { - Toast.makeText(this, "Only 2 waypoints supported", Toast.LENGTH_LONG).show() - addMarker = false - } - } - - if (addMarker) { - mapboxMap.addMarker(MarkerOptions().position(point)) - binding.clearPoints.visibility = View.VISIBLE - } - calculateRoute() +// var addMarker = true +// when { +// destination == null -> destination = Point.fromLngLat(point.longitude, point.latitude) +// waypoint == null -> waypoint = Point.fromLngLat(point.longitude, point.latitude) +// else -> { +// Toast.makeText(this, "Only 2 waypoints supported", Toast.LENGTH_LONG).show() +// addMarker = false +// } +// } +// +// if (addMarker) { +// mapboxMap.addMarker(MarkerOptions().position(point)) +// binding.clearPoints.visibility = View.VISIBLE +// } +// calculateRoute() return true } private fun calculateRoute() { - binding.startRouteLayout.visibility = View.GONE - val userLocation = mapboxMap.locationComponent.lastKnownLocation +// binding.startRouteLayout.visibility = View.GONE +// val userLocation = mapboxMap.locationComponent.lastKnownLocation val destination = destination - if (userLocation == null) { - Timber.d("calculateRoute: User location is null, therefore, origin can't be set.") - return - } +// if (userLocation == null) { +// Timber.d("calculateRoute: User location is null, therefore, origin can't be set.") +// return +// } if (destination == null) { return } - val origin = Point.fromLngLat(userLocation.longitude, userLocation.latitude) - if (TurfMeasurement.distance(origin, destination, TurfConstants.UNIT_METERS) < 50) { - binding.startRouteLayout.visibility = View.GONE - return - } +// val origin = Point.fromLngLat(userLocation.longitude, userLocation.latitude) +// if (TurfMeasurement.distance(origin, destination, TurfConstants.UNIT_METERS) < 50) { +// binding.startRouteLayout.visibility = View.GONE +// return +// } val navigationRouteBuilder = NavigationRoute.builder(this).apply { this.accessToken(getString(R.string.mapbox_access_token)) - this.origin(origin) +// this.origin(origin) this.destination(destination) this.voiceUnits(DirectionsCriteria.METRIC) this.alternatives(true) @@ -192,6 +171,20 @@ class NavigationUIActivity : this.baseUrl(getString(R.string.base_url)) } + + navigationRouteBuilder.addWaypoint( + Point.fromLngLat(76.930137, 43.230361) + ) + + navigationRouteBuilder.addWaypoint( + Point.fromLngLat(76.928316, 43.236109) + ) + + navigationRouteBuilder.addWaypoint( + Point.fromLngLat(76.920187, 43.236783) + ) + + navigationRouteBuilder.build().getRoute(object : Callback { override fun onResponse( call: Call, @@ -200,10 +193,13 @@ class NavigationUIActivity : Timber.d("Url: %s", (call.request() as Request).url.toString()) response.body()?.let { response -> if (response.routes().isNotEmpty()) { - val maplibreResponse = com.mapbox.services.android.navigation.v5.models.DirectionsResponse.fromJson(response.toJson()); + val maplibreResponse = + com.mapbox.services.android.navigation.v5.models.DirectionsResponse.fromJson( + response.toJson() + ); this@NavigationUIActivity.route = maplibreResponse.routes().first() navigationMapRoute?.addRoutes(maplibreResponse.routes()) - binding.startRouteLayout.visibility = View.VISIBLE +// binding.startRouteLayout.visibility = View.VISIBLE } } @@ -217,39 +213,85 @@ class NavigationUIActivity : override fun onResume() { super.onResume() - binding.mapView.onResume() + binding.navigationView.onResume() } override fun onPause() { super.onPause() - binding.mapView.onPause() + binding.navigationView.onPause() } override fun onStart() { super.onStart() - binding.mapView.onStart() + binding.navigationView.onStart() } override fun onStop() { super.onStop() - binding.mapView.onStop() + binding.navigationView.onStop() } override fun onLowMemory() { super.onLowMemory() - binding.mapView.onLowMemory() + binding.navigationView.onLowMemory() } override fun onDestroy() { super.onDestroy() - if (::mapboxMap.isInitialized) { - mapboxMap.removeOnMapClickListener(this) - } - binding.mapView.onDestroy() +// if (::mapboxMap.isInitialized) { +// mapboxMap.removeOnMapClickListener(this) +// } +// binding.navigationView.onDestroy() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - binding.mapView.onSaveInstanceState(outState) + binding.navigationView.onSaveInstanceState(outState) + } + + override fun onNavigationReady(isRunning: Boolean) { + val options = NavigationViewOptions.builder() + options.navigationListener(this) + extractRoute(options) + extractConfiguration(options) + options.navigationOptions(MapboxNavigationOptions.builder().build()) + binding.navigationView.startNavigation(options.build()) + } + + override fun onCancelNavigation() { + finishNavigation() + } + + override fun onNavigationFinished() { + finishNavigation() + } + + override fun onNavigationRunning() { + // Intentionally empty + } + + override fun onMapReadyCallback() { + // Intentionally empty + } + + private fun extractRoute(options: NavigationViewOptions.Builder) { + val route = NavigationLauncher.extractRoute(this) + options.directionsRoute(route) + } + + private fun extractConfiguration(options: NavigationViewOptions.Builder) { + val preferences = PreferenceManager.getDefaultSharedPreferences(this) + options.shouldSimulateRoute( + preferences.getBoolean( + NavigationConstants.NAVIGATION_VIEW_SIMULATE_ROUTE, + false + ) + ) + } + + private fun finishNavigation() { +// NavigationLauncher.cleanUpPreferences(this) + binding.navigationView.onDestroy() +// finish() } } diff --git a/app/src/main/res/layout/activity_navigation_ui.xml b/app/src/main/res/layout/activity_navigation_ui.xml index eabd7890d..a9593b15f 100644 --- a/app/src/main/res/layout/activity_navigation_ui.xml +++ b/app/src/main/res/layout/activity_navigation_ui.xml @@ -6,32 +6,16 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - + app:layout_constraintTop_toTopOf="parent"/> @@ -53,8 +37,8 @@ android:id="@+id/simulateRouteSwitch" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/simulate_route" - android:textColor="@color/white" + android:text="simulate_route" + android:textColor="@color/mapbox_navigation_route_upcoming_maneuver_arrow_color" app:switchPadding="4dp" />