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
30 changes: 25 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
# rn-mapbox-toolkit
# 🌍 rn-mapbox-toolkit

React native binding for mapbox
**React native binding for mapbox**

## Installation
---

## 🚀 Installation

```sh
```bash
npm install rn-mapbox-toolkit
```

---

## Usage
## 🧑‍💻 Usage

Inside `example/App.tsx` multiple screen exist with diverse example for each component

```tsx
// See example/App.tsx for full usage demonstration
```

---

## ✅ Features

| Feature | Android | iOS |
| --------------------------- | :-----: | :-: |
| MapView & event | ✅ | ❌ |
| Camera | ✅ | ❌ |


---
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.rnmapboxtoolkit.fabric
import android.annotation.SuppressLint
import android.util.Log
import com.facebook.react.uimanager.ThemedReactContext
import com.mapbox.maps.CameraBoundsOptions
import com.mapbox.maps.MapboxMap
import com.mapbox.maps.plugin.animation.CameraAnimatorsFactory
import com.mapbox.maps.plugin.animation.easeTo
import com.mapbox.maps.plugin.animation.flyTo
Expand All @@ -15,6 +18,19 @@ class RnMapboxToolkitCamera(context: ThemedReactContext) : AbstractMapFeature(co
const val TAG = "RnMapboxToolkitCamera"
}

/**
* Default settings for bounds for avoid each new state is preserved when props change
*/
private var pendingBounds = PendingBounds()

data class PendingBounds(
var maxPitch: Double? = null,
var minPitch: Double? = null,
var minZoom: Double? = null,
var maxZoom: Double? = null
)


override fun addToMap(mapView: RnMapboxToolkitView) {
super.addToMap(mapView)
}
Expand Down Expand Up @@ -53,7 +69,9 @@ class RnMapboxToolkitCamera(context: ThemedReactContext) : AbstractMapFeature(co
animationJson.toAnimationOptions()
}

mMapView?.getMapboxMap()?.flyTo(cameraOptions, animOptions)
withMapView { mapView ->
mapView.getMapboxMap()?.flyTo(cameraOptions, animOptions)
}
}

fun easeTo(cameraOptions: String, animationOptions: String?) {
Expand All @@ -65,6 +83,43 @@ class RnMapboxToolkitCamera(context: ThemedReactContext) : AbstractMapFeature(co
animationJson.toAnimationOptions()
}

mMapView?.getMapboxMap()?.easeTo(cameraOptions, animOptions)
withMapView { mapView ->
mapView.getMapboxMap()?.easeTo(cameraOptions, animOptions)
}
}

fun setMaxPitch(maxPitch: Double) {
pendingBounds.maxPitch = maxPitch
updateBounds()
}

fun setMinPitch(minPitch: Double) {
pendingBounds.minPitch = minPitch
updateBounds()
}

fun setMinZoom(minZoom: Double) {
pendingBounds.minZoom = minZoom
updateBounds()
}

fun setMaxZoom(maxZoom: Double) {
pendingBounds.maxZoom = maxZoom
updateBounds()
}

private fun updateBounds() {
val builder = CameraBoundsOptions.Builder()

pendingBounds.maxPitch?.let { builder.maxPitch(it) }
pendingBounds.minPitch?.let { builder.minPitch(it) }
pendingBounds.minZoom?.let { builder.minZoom(it) }
pendingBounds.maxZoom?.let { builder.maxZoom(it) }

withMapView { it ->
it.getMapboxMap()?.setBounds(builder.build())
}
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,34 @@ class RnMapboxToolkitCameraManager :
return RnMapboxToolkitCamera(context)
}

override fun setMinZoom(
view: RnMapboxToolkitCamera?,
minZoom: Double
) {
view?.setMinZoom(minZoom)
}

override fun setMaxZoom(
view: RnMapboxToolkitCamera?,
maxZoom: Double
) {
view?.setMaxZoom(maxZoom)
}

override fun setMinPitch(
view: RnMapboxToolkitCamera?,
minPitch: Double
) {
view?.setMinPitch(minPitch)
}

override fun setMaxPitch(
view: RnMapboxToolkitCamera?,
maxPitch: Double
) {
view?.setMaxPitch(maxPitch)
}

override fun flyTo(view: RnMapboxToolkitCamera, cameraOptions: String, animationOptions: String?) {
view.flyTo(cameraOptions, animationOptions)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package rnmapboxtoolkit.example

import android.os.Bundle;
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
Expand All @@ -19,4 +20,9 @@ class MainActivity : ReactActivity() {
*/
override fun createReactActivityDelegate(): ReactActivityDelegate =
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(null)
}

}
6 changes: 5 additions & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@
},
"dependencies": {
"@react-native/new-app-screen": "0.81.0",
"@react-navigation/elements": "^2.6.4",
"@react-navigation/native": "^7.1.17",
"@react-navigation/native-stack": "^7.3.26",
"react": "19.1.0",
"react-native": "0.81.0",
"react-native-safe-area-context": "^5.5.2"
"react-native-safe-area-context": "^5.6.1",
"react-native-screens": "^4.16.0"
},
"devDependencies": {
"@babel/core": "^7.25.2",
Expand Down
131 changes: 19 additions & 112 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,119 +1,26 @@
import React from 'react';
import { Button, StyleSheet } from 'react-native';
import {
Camera,
type CameraRef,
MapView,
type MapViewRef,
} from 'rn-mapbox-toolkit';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import ScreenList from './GroupScreen';
import MapSettings from './screens/MapSettings';
import MapCamera from './screens/MapCamera';
import MapEventsListener from './screens/MapEventsListener';

export default function App() {
const mapRef = React.useRef<MapViewRef | null>(null);
const cameraRef = React.useRef<CameraRef | null>(null);

const handleCameraZoom = async () => {
try {
const zoom = await cameraRef.current?.getZoomLevel();
console.log(zoom);
} catch (error) {
console.error('An error occured', error);
}
};

const handleGetZoom = async () => {
try {
const zoomLevel = await mapRef.current?.getZoomLevel();
console.log(zoomLevel);
} catch (error) {
console.error('An error occured', error);
}
};

const handleFlyTo = async () => {
try {
await cameraRef.current?.flyTo(
{
center: { longitude: 2.333333, latitude: 48.866667 },
},
{
duration: 5000,
startDelay: 2000,
}
);
} catch (error) {
console.error('An error occured', error);
}
};
const Stack = createNativeStackNavigator();

export default function App() {
return (
<>
<MapView
ref={mapRef}
style={style.mapContainer}
styleUrl="dark-v11"
showScaleBar={true}
scaleBarOptions={{
isMetricUnits: true,
position: 3,
marginRight: 30,
}}
logoOptions={{
enabled: true,
position: 2,
}}
attributionOptions={{
position: 2,
}}
compassOptions={{
enabled: true,
visibility: true,
fadeWhenFacingNorth: false,
position: 1,
marginRight: 80,
}}
gestureOptions={{
doubleTapToZoomInEnabled: true,
}}
onMapIdle={(e) =>
console.log(
'onMapIdle',
JSON.stringify(e.nativeEvent.properties, null, 2)
)
}
onMapLoaded={() => console.log('onMapLoaded trigger')}
onStyleDataLoaded={(e) => console.log(e.nativeEvent.properties.type)}
onStyleLoaded={() => console.log('onStyleLoaded trigger')}
onMapLoadingError={(e) =>
console.log('onMapLoadingError', e.nativeEvent.properties.type)
}
onSourceAdded={(e) =>
console.log('onSourceAdded', e.nativeEvent.properties.sourceId)
}
onStyleImageMissing={(e) =>
console.log('onStyleImageMissing', e.nativeEvent.properties.imageId)
}
// onRenderFrameStarted={() => console.log('onRenderFrameStarted')}
// onRenderFrameFinished={() => console.log('onRenderFrameFinished')}
onSourceRemoved={(e) =>
console.log('onSourceRemoved', e.nativeEvent.properties.sourceId)
}
onMapClick={(e) => console.log('onMapClick', e.nativeEvent.properties)}
onMapLongClick={(e) =>
console.log('onMapLongClick', e.nativeEvent.properties)
}
>
<Camera ref={cameraRef} />
</MapView>

<Button title="FlyTo" onPress={handleFlyTo} />
<Button title="Retrieve zoom" onPress={handleGetZoom} />
<Button title="Retrieve camera zoom" onPress={handleCameraZoom} />
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={ScreenList} />
<Stack.Screen name="MapSettings" component={MapSettings} />
<Stack.Screen
name="MapEventsListener"
component={MapEventsListener}
/>
<Stack.Screen name="MapCamera" component={MapCamera} />
</Stack.Navigator>
</NavigationContainer>
</>
);
}

const style = StyleSheet.create({
mapContainer: {
flex: 1,
},
});
59 changes: 59 additions & 0 deletions example/src/GroupScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useNavigation } from '@react-navigation/native';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';

const ScreenList = () => {
const navigation = useNavigation();

const onPress = (route: string) => {
navigation.navigate(route as never);
};
return (
<>
{SCREENS.map((sc, index) => (
<View key={`${sc.label}-${index}`} style={styles.exampleListItemBorder}>
<TouchableOpacity onPress={() => onPress(sc.label)}>
<View style={styles.exampleListItem}>
<Text style={styles.exampleListLabel}>{sc.label}</Text>
</View>
</TouchableOpacity>
</View>
))}
</>
);
};

export default ScreenList;

const styles = StyleSheet.create({
exampleList: {
flex: 1,
},
exampleListItem: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 16,
paddingVertical: 16,
},
exampleListItemBorder: {
borderBottomColor: '#ccc',
borderBottomWidth: StyleSheet.hairlineWidth,
},
exampleListLabel: {
fontSize: 18,
},
});

const SCREENS = [
{
label: 'MapSettings',
route: 'MapSettings',
},
{
label: 'MapEventsListener',
route: 'MapEventsListener',
},
{
label: 'MapCamera',
route: 'MapCamera',
},
];
Loading
Loading