Skip to content
Merged
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 @@ -19,7 +19,7 @@
public interface IMapViewFragment extends IMapViewProperties {
MapViewController getMapController();

void setMapStyle(String url);
void setMapStyle(String mapStyle);

GoogleMap getGoogleMap();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,10 @@
import com.google.android.gms.maps.model.PolygonOptions;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.PolylineOptions;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;

public class MapViewController implements INavigationViewControllerProperties {
private GoogleMap mGoogleMap;
Expand All @@ -67,8 +60,6 @@ public class MapViewController implements INavigationViewControllerProperties {
private final Map<String, String> groundOverlayNativeIdToEffectiveId = new HashMap<>();
private final Map<String, String> circleNativeIdToEffectiveId = new HashMap<>();

private String style = "";

// Zoom level preferences (-1 means use map's current value)
private Float minZoomLevelPreference = null;
private Float maxZoomLevelPreference = null;
Expand Down Expand Up @@ -770,25 +761,20 @@ public Map<String, GroundOverlay> getGroundOverlayMap() {
return groundOverlayMap;
}

public void setMapStyle(String url) {
Executors.newSingleThreadExecutor()
.execute(
() -> {
try {
style = fetchJsonFromUrl(url);
} catch (IOException e) {
throw new RuntimeException(e);
}

Activity activity = activitySupplier.get();
if (activity != null) {
activity.runOnUiThread(
() -> {
MapStyleOptions options = new MapStyleOptions(style);
mGoogleMap.setMapStyle(options);
});
}
});
public void setMapStyle(String styleJson) {
Activity activity = activitySupplier.get();
if (activity != null) {
activity.runOnUiThread(
() -> {
if (styleJson == null || styleJson.isEmpty()) {
// Reset to default map style
mGoogleMap.setMapStyle(null);
} else {
MapStyleOptions options = new MapStyleOptions(styleJson);
mGoogleMap.setMapStyle(options);
}
});
}
}

/** Moves the position of the camera to the specified location. */
Expand Down Expand Up @@ -1037,29 +1023,6 @@ public void setPadding(int top, int left, int bottom, int right) {
}
}

private String fetchJsonFromUrl(String urlString) throws IOException {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");

int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream inputStream = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
reader.close();
inputStream.close();
return stringBuilder.toString();
} else {
// Handle error response
throw new IOException("Error response: " + responseCode);
}
}

private LatLng createLatLng(Map<String, Object> map) {
Double lat = null;
Double lng = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ public MapViewController getMapController() {
return mMapViewController;
}

public void setMapStyle(String url) {
mMapViewController.setMapStyle(url);
public void setMapStyle(String mapStyle) {
mMapViewController.setMapStyle(mapStyle);
}

public GoogleMap getGoogleMap() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,12 @@ public void setMapType(double mapType) {

@Override
public void setMapStyle(String mapStyle) {
String url = mapStyle;
UiThreadUtil.runOnUiThread(
() -> {
if (mMapViewController == null) {
return;
}
mMapViewController.setMapStyle(url);
mMapViewController.setMapStyle(mapStyle);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ public MapViewController getMapController() {
return mMapViewController;
}

public void setMapStyle(String url) {
mMapViewController.setMapStyle(url);
public void setMapStyle(String mapStyle) {
mMapViewController.setMapStyle(mapStyle);
}

public void applyStylingOptions() {
Expand Down
4 changes: 2 additions & 2 deletions example/.detoxrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ module.exports = {
simulator: {
type: 'ios.simulator',
device: {
type: 'iPhone 16 Pro',
os: 'iOS 18.6',
type: 'iPhone 17 Pro',
os: 'iOS 26.4',
},
},
attached: {
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 @@ -82,4 +82,11 @@ describe('Map view tests', () => {
await expectNoErrors();
await expectSuccess();
});

it('MT09 - test setting map style via JSON', async () => {
await selectTestByName('testMapStyle');
await waitForTestToFinish();
await expectNoErrors();
await expectSuccess();
});
});
13 changes: 13 additions & 0 deletions example/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,17 @@ post_install do |installer|
:mac_catalyst_enabled => false,
# :ccache_enabled => true
)

# Fix fmt compilation issue with Xcode 26.4+ (stricter consteval enforcement)
# fmt/base.h unconditionally redefines FMT_USE_CONSTEVAL based on __cplusplus,
# so preprocessor defines are overwritten. Compiling fmt in C++17 mode ensures
# FMT_CPLUSPLUS (201703L) < 201709L → FMT_USE_CONSTEVAL = 0.
# Fixed in React Native 0.85+, can be removed after upgrading.
installer.pods_project.targets.each do |target|
if target.name == 'fmt'
target.build_configurations.each do |config|
config.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'c++17'
end
end
end
end
12 changes: 12 additions & 0 deletions example/src/controls/mapsControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ export interface MapControlsProps {
readonly onZoomControlsEnabledChange?: (enabled: boolean) => void;
readonly zoomGesturesEnabled?: boolean;
readonly onZoomGesturesEnabledChange?: (enabled: boolean) => void;
// Map style
readonly mapStyleEnabled?: boolean;
readonly onMapStyleEnabledChange?: (enabled: boolean) => void;
}

export const defaultZoom: number = 15;
Expand Down Expand Up @@ -103,6 +106,8 @@ const MapsControls: React.FC<MapControlsProps> = ({
onZoomControlsEnabledChange,
zoomGesturesEnabled = true,
onZoomGesturesEnabledChange,
mapStyleEnabled = false,
onMapStyleEnabledChange,
}) => {
const mapTypeOptions = ['None', 'Normal', 'Satellite', 'Terrain', 'Hybrid'];
const colorSchemeOptions = ['Follow System', 'Light', 'Dark'];
Expand Down Expand Up @@ -517,6 +522,13 @@ const MapsControls: React.FC<MapControlsProps> = ({
onPress={toggleCustomPadding}
/>
</View>
<View style={ControlStyles.rowContainer}>
<Text style={ControlStyles.rowLabel}>JSON Styling (Night mode)</Text>
<ExampleAppButton
title={mapStyleEnabled ? 'Disable' : 'Enable'}
onPress={() => onMapStyleEnabledChange?.(!mapStyleEnabled)}
/>
</View>
</Accordion>

{/* Location & UI */}
Expand Down
14 changes: 14 additions & 0 deletions example/src/screens/IntegrationTestsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
testNavigationStateGuards,
testStartGuidanceWithoutDestinations,
testRouteTokenOptionsValidation,
testMapStyle,
NO_ERRORS_DETECTED_LABEL,
} from './integration_tests/integration_test';

Expand Down Expand Up @@ -124,6 +125,7 @@ const IntegrationTestsScreen = () => {
const [mapToolbarEnabled, setMapToolbarEnabled] = useState<
boolean | undefined
>(undefined);
const [mapStyle, setMapStyle] = useState<string | undefined>(undefined);

const onMapReady = useCallback(async () => {
try {
Expand Down Expand Up @@ -231,6 +233,7 @@ const IntegrationTestsScreen = () => {
setZoomGesturesEnabled,
setZoomControlsEnabled,
setMapToolbarEnabled,
setMapStyle,
};
};

Expand Down Expand Up @@ -297,6 +300,9 @@ const IntegrationTestsScreen = () => {
case 'testRouteTokenOptionsValidation':
await testRouteTokenOptionsValidation(getTestTools());
break;
case 'testMapStyle':
await testMapStyle(getTestTools());
break;
default:
resetTestState();
break;
Expand Down Expand Up @@ -338,6 +344,7 @@ const IntegrationTestsScreen = () => {
zoomGesturesEnabled={zoomGesturesEnabled}
zoomControlsEnabled={zoomControlsEnabled}
mapToolbarEnabled={mapToolbarEnabled}
mapStyle={mapStyle}
/>
</View>
<View style={{ flex: 4 }}>
Expand Down Expand Up @@ -502,6 +509,13 @@ const IntegrationTestsScreen = () => {
}}
testID="testRouteTokenOptionsValidation"
/>
<ExampleAppButton
title="testMapStyle"
onPress={() => {
runTest('testMapStyle');
}}
testID="testMapStyle"
/>
</OverlayModal>
</View>
);
Expand Down
11 changes: 11 additions & 0 deletions example/src/screens/NavigationScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import OverlayModal from '../helpers/overlayModal';
import { showSnackbar, Snackbar } from '../helpers/snackbar';
import { CommonStyles, MapStyles } from '../styles/components';
import { MapStylingOptions } from '../styles/mapStyling';
import { NIGHT_MODE_STYLE } from '../styles/mapStyles';
import usePermissions from '../checkPermissions';

enum OverlayType {
Expand Down Expand Up @@ -92,6 +93,13 @@ const NavigationScreen = () => {
const [zoomControlsEnabled, setZoomControlsEnabled] = useState(true);
const [zoomGesturesEnabled, setZoomGesturesEnabled] = useState(true);

// Custom map style (JSON string)
const [mapStyle, setMapStyle] = useState<string | undefined>(undefined);

const handleMapStyleEnabledChange = (enabled: boolean) => {
setMapStyle(enabled ? NIGHT_MODE_STYLE : undefined);
};

// Navigation UI state
const [tripProgressBarEnabled, setTripProgressBarEnabled] = useState(false);
const [trafficPromptsEnabled, setTrafficPromptsEnabled] = useState(true);
Expand Down Expand Up @@ -349,6 +357,7 @@ const NavigationScreen = () => {
tiltGesturesEnabled={tiltGesturesEnabled}
zoomControlsEnabled={zoomControlsEnabled}
zoomGesturesEnabled={zoomGesturesEnabled}
mapStyle={mapStyle}
navigationUIEnabledPreference={0} // 0 = AUTOMATIC
tripProgressBarEnabled={tripProgressBarEnabled}
trafficPromptsEnabled={trafficPromptsEnabled}
Expand Down Expand Up @@ -443,6 +452,8 @@ const NavigationScreen = () => {
onZoomControlsEnabledChange={setZoomControlsEnabled}
zoomGesturesEnabled={zoomGesturesEnabled}
onZoomGesturesEnabledChange={setZoomGesturesEnabled}
mapStyleEnabled={mapStyle !== undefined}
onMapStyleEnabledChange={handleMapStyleEnabledChange}
/>
</OverlayModal>
)}
Expand Down
32 changes: 32 additions & 0 deletions example/src/screens/integration_tests/integration_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
} from '@googlemaps/react-native-navigation-sdk';
import { Platform } from 'react-native';
import { delay, roundDown } from './utils';
import { NIGHT_MODE_STYLE } from '../../styles/mapStyles';

interface TestTools {
navigationController: NavigationController;
Expand Down Expand Up @@ -55,6 +56,7 @@ interface TestTools {
setZoomGesturesEnabled: (enabled: boolean | undefined) => void;
setZoomControlsEnabled: (enabled: boolean | undefined) => void;
setMapToolbarEnabled: (enabled: boolean | undefined) => void;
setMapStyle: (style: string | undefined) => void;
}

const NAVIGATOR_NOT_READY_ERROR_CODE = 'NO_NAVIGATOR_ERROR_CODE';
Expand Down Expand Up @@ -1480,3 +1482,33 @@ export const testRouteTokenOptionsValidation = async (testTools: TestTools) => {
});
await initializeNavigation(navigationController, failTest);
};

/**
* Test that mapStyle prop can be set with valid JSON without errors.
* This verifies the fix for issue #548 where mapStyle was incorrectly
* treated as a URL on Android instead of a JSON string.
*/
export const testMapStyle = async (testTools: TestTools) => {
const { mapViewController, passTest, failTest, setMapStyle } = testTools;

if (!mapViewController) {
return failTest('mapViewController was expected to exist');
}

try {
// Set a valid JSON map style (night mode)
setMapStyle(NIGHT_MODE_STYLE);

// Give time for the style to be applied
await delay(500);

// Reset to default style
setMapStyle(undefined);

await delay(200);

passTest();
} catch (error) {
failTest(`Failed to set mapStyle: ${error}`);
}
};
Loading
Loading