Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,18 @@

import android.annotation.SuppressLint;
import android.app.Activity;
import android.util.Log;
import androidx.core.util.Supplier;
import com.facebook.react.bridge.UiThreadUtil;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.CameraPerspective;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.Circle;
import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.FollowMyLocationOptions;
import com.google.android.gms.maps.model.GroundOverlay;
import com.google.android.gms.maps.model.GroundOverlayOptions;
import com.google.android.gms.maps.model.LatLng;
Expand All @@ -42,6 +45,7 @@
import java.util.Map;

public class MapViewController implements INavigationViewControllerProperties {
private static final String TAG = "MapViewController";
private GoogleMap mGoogleMap;
private Supplier<Activity> activitySupplier;
private INavigationViewCallback mNavigationViewCallback;
Expand Down Expand Up @@ -883,23 +887,35 @@ public void setMinZoomLevel(float minZoomLevel) {
return;
}

// Get the effective max zoom for comparison
float maxZoom =
minZoomLevelPreference = minZoomLevel;

// Reset both preferences first so the new min/max pair is always applied
// atomically. Without this, Fabric can deliver minZoomLevel and maxZoomLevel
// prop updates in any order, causing a transient state where min > max.
mGoogleMap.resetMinMaxZoomPreference();

float effectiveMin = (minZoomLevel < 0.0f) ? mGoogleMap.getMinZoomLevel() : minZoomLevel;
float effectiveMax =
(maxZoomLevelPreference != null && maxZoomLevelPreference >= 0.0f)
? maxZoomLevelPreference
: mGoogleMap.getMaxZoomLevel();

// Validate that min is not greater than max (unless using -1 sentinel)
if (minZoomLevel >= 0.0f && minZoomLevel > maxZoom) {
throw new IllegalArgumentException(
"Minimum zoom level cannot be greater than maximum zoom level");
if (effectiveMin > effectiveMax) {
Log.w(
TAG,
"minZoomLevel ("
+ effectiveMin
+ ") is greater than maxZoomLevel ("
+ effectiveMax
+ "). Ignoring zoom level constraints.");
return;
}

minZoomLevelPreference = minZoomLevel;

// Use map's current minZoomLevel if -1 is provided
float effectiveMin = (minZoomLevel < 0.0f) ? mGoogleMap.getMinZoomLevel() : minZoomLevel;
mGoogleMap.setMinZoomPreference(effectiveMin);

if (maxZoomLevelPreference != null) {
mGoogleMap.setMaxZoomPreference(effectiveMax);
}
}

@Override
Expand All @@ -908,23 +924,35 @@ public void setMaxZoomLevel(float maxZoomLevel) {
return;
}

// Get the effective min zoom for comparison
float minZoom =
maxZoomLevelPreference = maxZoomLevel;

// Reset both preferences first so the new min/max pair is always applied
// atomically. Without this, Fabric can deliver minZoomLevel and maxZoomLevel
// prop updates in any order, causing a transient state where min > max.
mGoogleMap.resetMinMaxZoomPreference();

float effectiveMax = (maxZoomLevel < 0.0f) ? mGoogleMap.getMaxZoomLevel() : maxZoomLevel;
float effectiveMin =
(minZoomLevelPreference != null && minZoomLevelPreference >= 0.0f)
? minZoomLevelPreference
: mGoogleMap.getMinZoomLevel();

// Validate that max is not less than min (unless using -1 sentinel)
if (maxZoomLevel >= 0.0f && maxZoomLevel < minZoom) {
throw new IllegalArgumentException(
"Maximum zoom level cannot be less than minimum zoom level");
if (effectiveMin > effectiveMax) {
Log.w(
TAG,
"minZoomLevel ("
+ effectiveMin
+ ") is greater than maxZoomLevel ("
+ effectiveMax
+ "). Ignoring zoom level constraints.");
return;
}

maxZoomLevelPreference = maxZoomLevel;

// Use map's current maxZoomLevel if -1 is provided
float effectiveMax = (maxZoomLevel < 0.0f) ? mGoogleMap.getMaxZoomLevel() : maxZoomLevel;
mGoogleMap.setMaxZoomPreference(effectiveMax);

if (minZoomLevelPreference != null) {
mGoogleMap.setMinZoomPreference(effectiveMin);
}
}

public void setZoomGesturesEnabled(boolean enabled) {
Expand Down Expand Up @@ -1005,16 +1033,27 @@ public void resetMinMaxZoomLevel() {
return;
}

minZoomLevelPreference = null;
maxZoomLevelPreference = null;
mGoogleMap.resetMinMaxZoomPreference();
}

@SuppressLint("MissingPermission")
public void setFollowingPerspective(int jsValue) {
public void setFollowingPerspective(int jsValue, float zoomLevel) {
if (mGoogleMap == null) {
return;
}

mGoogleMap.followMyLocation(EnumTranslationUtil.getCameraPerspectiveFromJsValue(jsValue));
@CameraPerspective
int perspective = EnumTranslationUtil.getCameraPerspectiveFromJsValue(jsValue);

if (zoomLevel >= 0.0f) {
FollowMyLocationOptions options =
FollowMyLocationOptions.builder().setZoomLevel(zoomLevel).build();
mGoogleMap.followMyLocation(perspective, options);
} else {
mGoogleMap.followMyLocation(perspective);
}
}

public void setPadding(int top, int left, int bottom, int right) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,14 +393,15 @@ public void setMyLocationEnabled(boolean enabled) {
}

@Override
public void setFollowingPerspective(double perspective) {
public void setFollowingPerspective(double perspective, double zoomLevel) {
int jsValue = (int) perspective;
float zoom = (float) zoomLevel;
UiThreadUtil.runOnUiThread(
() -> {
if (mMapViewController == null) {
return;
}
mMapViewController.setFollowingPerspective(jsValue);
mMapViewController.setFollowingPerspective(jsValue, zoom);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,8 @@ public void setNavigationUIEnabled(String nativeID, boolean enabled, final Promi
}

@Override
public void setFollowingPerspective(String nativeID, double perspective, final Promise promise) {
public void setFollowingPerspective(
String nativeID, double perspective, double zoomLevel, final Promise promise) {
UiThreadUtil.runOnUiThread(
() -> {
IMapViewFragment fragment = mNavViewManager.getFragmentByNativeId(nativeID);
Expand All @@ -415,7 +416,9 @@ public void setFollowingPerspective(String nativeID, double perspective, final P
}

if (fragment instanceof INavViewFragment) {
fragment.getMapController().setFollowingPerspective((int) perspective);
fragment
.getMapController()
.setFollowingPerspective((int) perspective, (float) zoomLevel);
promise.resolve(null);
} else {
promise.reject(JsErrors.NOT_NAV_VIEW_ERROR_CODE, JsErrors.NOT_NAV_VIEW_ERROR_MESSAGE);
Expand Down
7 changes: 7 additions & 0 deletions example/e2e/map.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,11 @@ describe('Map view tests', () => {
await expectNoErrors();
await expectSuccess();
});

it('MT10 - test min and max zoom level constraints', async () => {
await selectTestByName('testMinMaxZoomLevels');
await waitForTestToFinish();
await expectNoErrors();
await expectSuccess();
});
});
8 changes: 8 additions & 0 deletions example/e2e/navigation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,12 @@ describe('Navigation tests', () => {
await expectNoErrors();
await expectSuccess();
});

it('NT09 - test setFollowingPerspective with zoom level options', async () => {
await selectTestByName('testSetFollowingPerspective');
await agreeToTermsAndConditions();
await waitForTestToFinish();
await expectNoErrors();
await expectSuccess();
});
});
32 changes: 32 additions & 0 deletions example/src/screens/IntegrationTestsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ import {
testStartGuidanceWithoutDestinations,
testRouteTokenOptionsValidation,
testMapStyle,
testMinMaxZoomLevels,
testSetFollowingPerspective,
NO_ERRORS_DETECTED_LABEL,
} from './integration_tests/integration_test';

Expand Down Expand Up @@ -127,6 +129,12 @@ const IntegrationTestsScreen = () => {
boolean | undefined
>(undefined);
const [mapStyle, setMapStyle] = useState<string | undefined>(undefined);
const [minZoomLevel, setMinZoomLevel] = useState<number | undefined>(
undefined
);
const [maxZoomLevel, setMaxZoomLevel] = useState<number | undefined>(
undefined
);

const onMapReady = useCallback(async () => {
try {
Expand Down Expand Up @@ -236,6 +244,8 @@ const IntegrationTestsScreen = () => {
setZoomControlsEnabled,
setMapToolbarEnabled,
setMapStyle,
setMinZoomLevel,
setMaxZoomLevel,
};
};

Expand Down Expand Up @@ -305,6 +315,12 @@ const IntegrationTestsScreen = () => {
case 'testMapStyle':
await testMapStyle(getTestTools());
break;
case 'testMinMaxZoomLevels':
await testMinMaxZoomLevels(getTestTools());
break;
case 'testSetFollowingPerspective':
await testSetFollowingPerspective(getTestTools());
break;
default:
resetTestState();
break;
Expand Down Expand Up @@ -347,6 +363,8 @@ const IntegrationTestsScreen = () => {
zoomControlsEnabled={zoomControlsEnabled}
mapToolbarEnabled={mapToolbarEnabled}
mapStyle={mapStyle}
minZoomLevel={minZoomLevel}
maxZoomLevel={maxZoomLevel}
/>
</View>
<View style={{ flex: 4 }}>
Expand Down Expand Up @@ -518,6 +536,20 @@ const IntegrationTestsScreen = () => {
}}
testID="testMapStyle"
/>
<ExampleAppButton
title="testMinMaxZoomLevels"
onPress={() => {
runTest('testMinMaxZoomLevels');
}}
testID="testMinMaxZoomLevels"
/>
<ExampleAppButton
title="testSetFollowingPerspective"
onPress={() => {
runTest('testSetFollowingPerspective');
}}
testID="testSetFollowingPerspective"
/>
</OverlayModal>
</View>
);
Expand Down
Loading
Loading