diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 00000000..d57496ff
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,4 @@
+node_modules/
+lib/
+expo-config-plugin/
+docs/
\ No newline at end of file
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 00000000..de764dc6
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,20 @@
+{
+ "root": true,
+ "extends": [
+ "@react-native",
+ "prettier"
+ ],
+ "rules": {
+ "prettier/prettier": [
+ "error",
+ {
+ "quoteProps": "consistent",
+ "singleQuote": true,
+ "tabWidth": 2,
+ "trailingComma": "es5",
+ "useTabs": false
+ }
+ ],
+ "react-native/no-inline-styles": "off"
+ }
+}
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2b24cda7..a3d94ef7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -25,9 +25,6 @@ jobs:
- name: Lint files
run: yarn lint
- - name: Typecheck files
- run: yarn typecheck
-
- name: Codegen
run: yarn codegen
diff --git a/.gitignore b/.gitignore
index c4336795..197792c7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -82,4 +82,6 @@ example/android/app/src/main/res/values/secret.xml
example/ios/Secret.xcconfig
.env
-docs/
\ No newline at end of file
+docs/
+
+example/.watchman-*
\ No newline at end of file
diff --git a/.husky/pre-commit b/.husky/pre-commit
index 49afc4bd..2e50ef6c 100644
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,6 +1,2 @@
echo '๐ฅ pre-commit hook running...'
-yarn lint
-yarn typecheck
-yarn lint:clang
-yarn build:expo-config-plugin
-git add expo-config-plugin
+yarn lint
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..30d1e016
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,7 @@
+{
+ "quoteProps": "consistent",
+ "singleQuote": true,
+ "tabWidth": 2,
+ "trailingComma": "es5",
+ "useTabs": false
+}
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e634a030..920bcdca 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,6 +4,50 @@ Contributions are always welcome, no matter how large or small!
We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project. Before contributing, please read the [code of conduct](./CODE_OF_CONDUCT.md).
+### Scripts
+
+The `package.json` file contains various scripts for common tasks:
+
+**Installiation, Build**
+
+- `yarn`: setup project by installing dependencies.
+- `yarn prepack`: build package (including docs, expo config plugin)
+- `yarn build:docs`: build documentation at `./docs`.
+- `yarn build:expo-config-plugin`: build expo config plugin.
+
+**Validation**
+
+- `yarn lint`: lint files with ESLint, ClangFormat, Ktlint, TypeScript
+- `yarn t`: alias for lint
+- `yarn test`: run unit tests with Jest
+- `yarn format:ios`: run formatter with ClangFormat, SwiftFormat for iOS codes
+- `yarn format:android`: run formatter with Ktlint for Android codes
+
+**Example App Build, Manipluations**
+
+- `yarn example start`: start the Metro server for the example app.
+- `yarn example android`: run the example app on Android.
+- `yarn example ios`: run the example app on iOS.
+- `yarn codegen:{android,ios}`: generate codegen output for development typing (this should be clean for running example app, prevetning redelcaration compile error)
+
+**Architecture Convert**
+
+- `new`: convert example project to new architecture
+- `old`: convert example project to old architecture
+- `new:pod`: convert example project to new architecture with pod install
+- `old:pod`: convert example project to old architecture with pod install
+
+**Util**
+
+- `yarn studio`: open android studio for example project
+- `yarn xcode`: open xcode for example project
+
+**Codegen**
+
+- `yarn codegen`: generate codegen spec for all platform
+- `yarn codegen:android`: generate android codegen spec
+- `yarn codegen:ios`: generate ios codegen spec
+
## Development workflow
This project is a monorepo managed using [Yarn workspaces](https://yarnpkg.com/features/workspaces). It contains the following packages:
@@ -163,25 +207,6 @@ You can check generated docs with `yarn build:docs` command.
The documentation is published on push main branch automatically.
-### Scripts
-
-The `package.json` file contains various scripts for common tasks:
-
-- `yarn`: setup project by installing dependencies.
-- `yarn typecheck`: type-check files with TypeScript.
-- `yarn lint`: lint files with ESLint.
-- `yarn format:clang`: format files with clang-format.
-- `yarn lint:clang`: lint files with clang-format.
-- `yarn test`: run unit tests with Jest.
-- `yarn example start`: start the Metro server for the example app.
-- `yarn example android`: run the example app on Android.
-- `yarn example ios`: run the example app on iOS.
-- `yarn codegen:{android,ios}`: generate codegen output for development typing (this should be clean for running example app, prevetning redelcaration compile error)
-- `yarn studio`: open android studio
-- `yarn studio:example`: open android studio for example project
-- `yarn xcode`: open xcode for example project
-- `yarn build:docs`: build documentation at `./docs`.
-
### Sending a pull request
> **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github).
diff --git a/README.md b/README.md
index 92b21d2e..20241fc3 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@
๋ฆฌ์กํธ ๋ค์ดํฐ๋ธ [Naver Map](https://www.ncloud.com/product/applicationService/maps) ์ปดํฌ๋ํธ์
๋๋ค.
-
+
## ์ ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์จ์ผํ๋์?
@@ -383,7 +383,7 @@ yarn add react-native-permissions
```
`react-native-permission`์ ๊ฐ ํ๋ซํผ๋ณ ์ค์ ๋ฐฉ๋ฒ์ [์ฌ์ฉ๋ฒ](https://github.com/zoontek/react-native-permissions#setup)์ ์ง์ ์ฐธ๊ณ ํด
-`Podfile(iOS)`, `AndroidManifest.xml(Android)` ๋ฅผ ์ ์ ํ ๋ณ๊ฒฝํด์ฃผ์๊ธธ ๋ฐ๋๋๋ค.
+`Podfile(iOS)`, `AndroidManifest.xml(Android)` ๋ฅผ ์ ์ ํ ๋ณ๊ฒฝํด์ฃผ์๊ธธ ๋ฐ๋๋๋ค.
#### iOS
@@ -485,7 +485,7 @@ useEffect(() => {
/**
* Note: Foreground permissions should be granted before asking for the background permissions
* (your app can't obtain background permission without foreground permission).
- */
+ */
if(granted) {
await Location.requestBackgroundPermissionsAsync();
}
@@ -532,7 +532,7 @@ useEffect(() => {
> [!TIP]
> `reuseIdentifier`๋ ์ ๋ฌํ์ง ์์๋ ๋ชจ๋ ์๋์ผ๋ก ์บ์ฑ์ด ๋ฉ๋๋ค.
->
+>
> ๋๋๋ก์ด๋ฉด ๋ง์ปค๋ ๋ชจ๋ `width`, `height` prop์ ์ฌ์ฉํด์ผํฉ๋๋ค. 2๋ฒ ํ์
์ ๊ฒฝ์ฐ ํ์ฌ debug/release ๋น๋์ ํฌ๊ธฐ๊ฐ `width`, `height`์์ด ๋ค๋ฅด๊ฒ ๋์ค๋ ํ์์ด ์์ต๋๋ค.
> release์์๋ ์ ๋๋ก ๋์ต๋๋ค.
@@ -571,7 +571,7 @@ image={{httpUri: 'https://example.com/image.png'}}
iOS(new arch)์์ ํ์ฌ View๋ค์ `collapsable=false`๋ฅผ ์ค์ ํด์ผ ๋์ํฉ๋๋ค.
> [!TIP]
-> ๋ง์ปค์ ์๊น์๋ฅผ ๋ฐ๊ฟ์ผ ํ๋ค๋ฉด ๊ทธ๊ฒ์ ๋ํ ์์กด์ฑ๋ค์ ์ ์ผ ์์ ์์์ `key`๋ก ์ ๋ฌํด์ผํฉ๋๋ค.
+> ๋ง์ปค์ ์๊น์๋ฅผ ๋ฐ๊ฟ์ผ ํ๋ค๋ฉด ๊ทธ๊ฒ์ ๋ํ ์์กด์ฑ๋ค์ ์ ์ผ ์์ ์์์ `key`๋ก ์ ๋ฌํด์ผํฉ๋๋ค.
```tsx
@@ -607,8 +607,6 @@ iOS์์ ๋จ์ํ `UIView`๋ฅผ `UIImage`๋ก ์บ๋ฒ์ค์ ๊ทธ๋ ค ํ์ํด์ค๋
| Prop | iOS | Android |
|--------------------------|-----|---------|
| isLogoInteractionEnabled | โ | โ |
-| isUseTextureViewAndroid | โ | โ
|
-| markerClustering | ๐ฆ | ๐ฆ |
| fpsLimit | ๐ฆ | ๐ฆ |
| gestureFrictions | ๐ฆ | ๐ฆ |
@@ -626,7 +624,6 @@ iOS์์ ๋จ์ํ `UIView`๋ฅผ `UIImage`๋ก ์บ๋ฒ์ค์ ๊ทธ๋ ค ํ์ํด์ค๋
|----------------------------|-----|---------|
| screenToCoordinate | ๐ฆ | ๐ฆ |
| coordinateToScreen | ๐ฆ | ๐ฆ |
-| clusterMarkers | ๐ฆ | ๐ฆ |
### Marker Common
@@ -672,8 +669,8 @@ iOS์์ ๋จ์ํ `UIView`๋ฅผ `UIImage`๋ก ์บ๋ฒ์ค์ ๊ทธ๋ ค ํ์ํด์ค๋
- [x] Release (23.04.11)
- [x] Support Expo with config plugin (23.04.12)
- [x] Docs
-- [ ] Implement Clustering <- ๐ฅ
-- [ ] Implement MutlPath, Arrow, Geometry Overlays
+- [x] Implement Clustering (23.04.24)
+- [ ] Implement MutlPath, Arrow, Geometry Overlays <- ๐ฅ
## Contributing
diff --git a/android/build.gradle b/android/build.gradle
index 21e46f78..2fb4e732 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -110,7 +110,7 @@ dependencies {
implementation "com.facebook.react:react-native:+"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "com.naver.maps:map-sdk:${getExtOrDefault("sdkVersion")}"
- implementation "com.google.android.gms:play-services-location:${getExtOrDefault("locationServviceVersion")}"
+ implementation "com.google.android.gms:play-services-location:${getExtOrDefault("locationServiceVersion")}"
}
if (isNewArchitectureEnabled()) {
diff --git a/android/gradle.properties b/android/gradle.properties
index e22ccb3b..29a238e8 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -4,4 +4,4 @@ NaverMap_targetSdkVersion=31
NaverMap_compileSdkVersion=31
NaverMap_ndkversion=21.4.7075529
NaverMap_sdkVersion=3.18.0
-NaverMap_locationServviceVersion=21.2.0
+NaverMap_locationServiceVersion=21.2.0
diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/RNCNaverMapPackage.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/RNCNaverMapPackage.kt
index 8c583a05..da5f181a 100644
--- a/android/src/main/java/com/mjstudio/reactnativenavermap/RNCNaverMapPackage.kt
+++ b/android/src/main/java/com/mjstudio/reactnativenavermap/RNCNaverMapPackage.kt
@@ -11,20 +11,19 @@ import com.mjstudio.reactnativenavermap.overlay.path.RNCNaverMapPathManager
import com.mjstudio.reactnativenavermap.overlay.polygon.RNCNaverMapPolygonManager
import com.mjstudio.reactnativenavermap.overlay.polyline.RNCNaverMapPolylineManager
-class NaverMapViewPackage : ReactPackage {
- override fun createViewManagers(reactContext: ReactApplicationContext): List> {
- return mutableListOf>().apply {
- add(RNCNaverMapViewManager())
- add(RNCNaverMapMarkerManager())
- add(RNCNaverMapCircleManager())
- add(RNCNaverMapPolygonManager())
- add(RNCNaverMapPolylineManager())
- add(RNCNaverMapPathManager())
- }
- }
-
- override fun createNativeModules(reactContext: ReactApplicationContext): List {
- return emptyList()
+class RNCNaverMapPackage : ReactPackage {
+ override fun createViewManagers(reactContext: ReactApplicationContext): List> {
+ return mutableListOf>().apply {
+ add(RNCNaverMapViewManager())
+ add(RNCNaverMapMarkerManager())
+ add(RNCNaverMapCircleManager())
+ add(RNCNaverMapPolygonManager())
+ add(RNCNaverMapPolylineManager())
+ add(RNCNaverMapPathManager())
}
+ }
+ override fun createNativeModules(reactContext: ReactApplicationContext): List {
+ return emptyList()
+ }
}
diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapCameraChangeEvent.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapCameraChangeEvent.kt
index 002ee7b5..4d19d3ba 100644
--- a/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapCameraChangeEvent.kt
+++ b/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapCameraChangeEvent.kt
@@ -5,29 +5,32 @@ import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event
class NaverMapCameraChangeEvent(
- surfaceId: Int,
- viewId: Int,
- private val latitude: Double,
- private val longitude: Double,
- private val zoom: Double,
- private val tilt: Double,
- private val bearing: Double,
- private val reason: Int,
+ surfaceId: Int,
+ viewId: Int,
+ private val latitude: Double,
+ private val longitude: Double,
+ private val zoom: Double,
+ private val tilt: Double,
+ private val bearing: Double,
+ private val reason: Int,
) : Event(surfaceId, viewId) {
- override fun getEventName(): String = EVENT_NAME
- override fun canCoalesce(): Boolean = false
- override fun getCoalescingKey(): Short = 0
- override fun getEventData(): WritableMap = Arguments.createMap().apply {
- putDouble("latitude", latitude)
- putDouble("longitude", longitude)
- putDouble("zoom", zoom)
- putDouble("tilt", tilt)
- putDouble("bearing", bearing)
- putInt("reason", reason)
- }
+ override fun getEventName(): String = EVENT_NAME
+
+ override fun canCoalesce(): Boolean = false
+
+ override fun getCoalescingKey(): Short = 0
- companion object {
- const val EVENT_NAME = "onCameraChanged"
+ override fun getEventData(): WritableMap =
+ Arguments.createMap().apply {
+ putDouble("latitude", latitude)
+ putDouble("longitude", longitude)
+ putDouble("zoom", zoom)
+ putDouble("tilt", tilt)
+ putDouble("bearing", bearing)
+ putInt("reason", reason)
}
-}
+ companion object {
+ const val EVENT_NAME = "onCameraChanged"
+ }
+}
diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapInitializeEvent.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapInitializeEvent.kt
index 0c98f78a..2859a7c8 100644
--- a/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapInitializeEvent.kt
+++ b/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapInitializeEvent.kt
@@ -5,13 +5,15 @@ import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event
class NaverMapInitializeEvent(surfaceId: Int, viewId: Int) : Event(surfaceId, viewId) {
- override fun getEventName(): String = EVENT_NAME
- override fun canCoalesce(): Boolean = false
- override fun getCoalescingKey(): Short = 0
- override fun getEventData(): WritableMap = Arguments.createMap()
+ override fun getEventName(): String = EVENT_NAME
- companion object {
- const val EVENT_NAME = "onInitialized"
- }
-}
+ override fun canCoalesce(): Boolean = false
+
+ override fun getCoalescingKey(): Short = 0
+ override fun getEventData(): WritableMap = Arguments.createMap()
+
+ companion object {
+ const val EVENT_NAME = "onInitialized"
+ }
+}
diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapOptionChangeEvent.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapOptionChangeEvent.kt
index bb052a18..5de95104 100644
--- a/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapOptionChangeEvent.kt
+++ b/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapOptionChangeEvent.kt
@@ -5,13 +5,15 @@ import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event
class NaverMapOptionChangeEvent(surfaceId: Int, viewId: Int) : Event(surfaceId, viewId) {
- override fun getEventName(): String = EVENT_NAME
- override fun canCoalesce(): Boolean = false
- override fun getCoalescingKey(): Short = 0
- override fun getEventData(): WritableMap = Arguments.createMap()
+ override fun getEventName(): String = EVENT_NAME
- companion object {
- const val EVENT_NAME = "onOptionChanged"
- }
-}
+ override fun canCoalesce(): Boolean = false
+
+ override fun getCoalescingKey(): Short = 0
+ override fun getEventData(): WritableMap = Arguments.createMap()
+
+ companion object {
+ const val EVENT_NAME = "onOptionChanged"
+ }
+}
diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapOverlayTapEvent.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapOverlayTapEvent.kt
index 0f281e1b..09c9899a 100644
--- a/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapOverlayTapEvent.kt
+++ b/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapOverlayTapEvent.kt
@@ -5,16 +5,18 @@ import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event
class NaverMapOverlayTapEvent(
- surfaceId: Int,
- viewId: Int,
+ surfaceId: Int,
+ viewId: Int,
) : Event(surfaceId, viewId) {
- override fun getEventName(): String = EVENT_NAME
- override fun canCoalesce(): Boolean = false
- override fun getCoalescingKey(): Short = 0
- override fun getEventData(): WritableMap = Arguments.createMap()
+ override fun getEventName(): String = EVENT_NAME
- companion object {
- const val EVENT_NAME = "onTapOverlay"
- }
-}
+ override fun canCoalesce(): Boolean = false
+
+ override fun getCoalescingKey(): Short = 0
+ override fun getEventData(): WritableMap = Arguments.createMap()
+
+ companion object {
+ const val EVENT_NAME = "onTapOverlay"
+ }
+}
diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapTapEvent.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapTapEvent.kt
index e775d61f..987610ad 100644
--- a/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapTapEvent.kt
+++ b/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapTapEvent.kt
@@ -5,25 +5,28 @@ import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event
class NaverMapTapEvent(
- surfaceId: Int,
- viewId: Int,
- private val latitude: Double,
- private val longitude: Double,
- private val x: Double,
- private val y: Double,
+ surfaceId: Int,
+ viewId: Int,
+ private val latitude: Double,
+ private val longitude: Double,
+ private val x: Double,
+ private val y: Double,
) : Event(surfaceId, viewId) {
- override fun getEventName(): String = EVENT_NAME
- override fun canCoalesce(): Boolean = false
- override fun getCoalescingKey(): Short = 0
- override fun getEventData(): WritableMap = Arguments.createMap().apply {
- putDouble("latitude", latitude)
- putDouble("longitude", longitude)
- putDouble("x", x)
- putDouble("y", y)
- }
+ override fun getEventName(): String = EVENT_NAME
+
+ override fun canCoalesce(): Boolean = false
+
+ override fun getCoalescingKey(): Short = 0
- companion object {
- const val EVENT_NAME = "onTapMap"
+ override fun getEventData(): WritableMap =
+ Arguments.createMap().apply {
+ putDouble("latitude", latitude)
+ putDouble("longitude", longitude)
+ putDouble("x", x)
+ putDouble("y", y)
}
-}
+ companion object {
+ const val EVENT_NAME = "onTapMap"
+ }
+}
diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapView.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapView.kt
index 5db76c8e..1ed6f1b4 100644
--- a/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapView.kt
+++ b/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapView.kt
@@ -20,159 +20,159 @@ import com.naver.maps.map.NaverMap
import com.naver.maps.map.NaverMapOptions
import com.naver.maps.map.util.FusedLocationSource
-
@SuppressLint("ViewConstructor")
class RNCNaverMapView(
- private val reactContext: ThemedReactContext,
- private val mapOptions: NaverMapOptions
+ private val reactContext: ThemedReactContext,
+ private val mapOptions: NaverMapOptions,
) :
- MapView(reactContext, mapOptions) {
-
- private var attacherGroup: ViewAttacherGroup? = null
- private var map: NaverMap? = null
- private var isAttached = false
- val overlays = mutableListOf>()
-
- private val reactTag: Int
- get() = RNCNaverMapViewWrapper.getReactTagFromMapView(this)
-
- var isInitialCameraOrRegionSet = false
-
- init {
- getMapAsync {
- map = it
- reactContext.emitEvent(reactTag) { surfaceId, reactTag ->
- NaverMapInitializeEvent(surfaceId, reactTag)
- }
-
-
- it.addOnOptionChangeListener {
- reactContext.emitEvent(reactTag) { surfaceId, reactTag ->
- NaverMapOptionChangeEvent(surfaceId, reactTag)
- }
- }
-
- it.addOnCameraChangeListener { reason, animated ->
- reactContext.emitEvent(reactTag) { surfaceId, reactTag ->
- NaverMapCameraChangeEvent(
- surfaceId,
- reactTag,
- it.cameraPosition.target.latitude,
- it.cameraPosition.target.longitude,
- it.cameraPosition.zoom,
- it.cameraPosition.tilt,
- it.cameraPosition.bearing,
- when (reason) {
- REASON_GESTURE -> 1
- REASON_CONTROL -> 2
- REASON_LOCATION -> 3
- else -> 0
- },
- )
- }
- }
-
- it.setOnMapClickListener { pointF, latLng ->
- reactContext.emitEvent(reactTag) { surfaceId, reactTag ->
- NaverMapTapEvent(
- surfaceId,
- reactTag,
- latLng.latitude,
- latLng.longitude,
- pointF.x.toDouble(),
- pointF.y.toDouble(),
- )
- }
- }
+ MapView(reactContext, mapOptions) {
+ private var attacherGroup: ViewAttacherGroup? = null
+ private var map: NaverMap? = null
+ private var isAttached = false
+ val overlays = mutableListOf>()
+
+ private val reactTag: Int
+ get() = RNCNaverMapViewWrapper.getReactTagFromMapView(this)
+
+ var isInitialCameraOrRegionSet = false
+
+ init {
+ getMapAsync {
+ map = it
+ reactContext.emitEvent(reactTag) { surfaceId, reactTag ->
+ NaverMapInitializeEvent(surfaceId, reactTag)
+ }
+
+ it.addOnOptionChangeListener {
+ reactContext.emitEvent(reactTag) { surfaceId, reactTag ->
+ NaverMapOptionChangeEvent(surfaceId, reactTag)
}
-
- // Set up a parent view for triggering visibility in subviews that depend on it.
- // Mainly ReactImageView depends on Fresco which depends on onVisibilityChanged() event
- attacherGroup = ViewAttacherGroup(reactContext)
- val attacherLayoutParams = LayoutParams(0, 0)
- attacherLayoutParams.width = 0
- attacherLayoutParams.height = 0
- attacherLayoutParams.leftMargin = 99999999
- attacherLayoutParams.topMargin = 99999999
- attacherGroup!!.setLayoutParams(attacherLayoutParams)
- addView(attacherGroup)
- }
-
- override fun onAttachedToWindow() {
- super.onAttachedToWindow()
- isAttached = true
+ }
+
+ it.addOnCameraChangeListener { reason, animated ->
+ reactContext.emitEvent(reactTag) { surfaceId, reactTag ->
+ NaverMapCameraChangeEvent(
+ surfaceId,
+ reactTag,
+ it.cameraPosition.target.latitude,
+ it.cameraPosition.target.longitude,
+ it.cameraPosition.zoom,
+ it.cameraPosition.tilt,
+ it.cameraPosition.bearing,
+ when (reason) {
+ REASON_GESTURE -> 1
+ REASON_CONTROL -> 2
+ REASON_LOCATION -> 3
+ else -> 0
+ },
+ )
+ }
+ }
+
+ it.setOnMapClickListener { pointF, latLng ->
+ reactContext.emitEvent(reactTag) { surfaceId, reactTag ->
+ NaverMapTapEvent(
+ surfaceId,
+ reactTag,
+ latLng.latitude,
+ latLng.longitude,
+ pointF.x.toDouble(),
+ pointF.y.toDouble(),
+ )
+ }
+ }
}
- override fun onDetachedFromWindow() {
- isAttached = false
- super.onDetachedFromWindow()
+ // Set up a parent view for triggering visibility in subviews that depend on it.
+ // Mainly ReactImageView depends on Fresco which depends on onVisibilityChanged() event
+ attacherGroup = ViewAttacherGroup(reactContext)
+ val attacherLayoutParams = LayoutParams(0, 0)
+ attacherLayoutParams.width = 0
+ attacherLayoutParams.height = 0
+ attacherLayoutParams.leftMargin = 99999999
+ attacherLayoutParams.topMargin = 99999999
+ attacherGroup!!.setLayoutParams(attacherLayoutParams)
+ addView(attacherGroup)
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ isAttached = true
+ }
+
+ override fun onDetachedFromWindow() {
+ isAttached = false
+ super.onDetachedFromWindow()
+ }
+
+ fun withMap(callback: (map: NaverMap) -> Unit) {
+ map?.also(callback) ?: run { getMapAsync(callback) }
+ }
+
+ fun addOverlay(
+ child: View,
+ index: Int,
+ ) = withMap { map ->
+ when (child) {
+ is RNCNaverMapMarker -> {
+ child.addToMap(map)
+ overlays.add(index, child)
+ val visibility: Int = child.visibility
+ child.visibility = INVISIBLE
+ (child.parent as? ViewGroup)?.removeView(child)
+ // Add to the parent group
+ attacherGroup!!.addView(child)
+ child.visibility = visibility
+ }
+
+ is RNCNaverMapOverlay<*> -> {
+ child.addToMap(map)
+ overlays.add(index, child)
+ }
+
+ else -> {
+ addView(child, index)
+ }
}
-
- fun withMap(callback: (map: NaverMap) -> Unit) {
- map?.also(callback) ?: run { getMapAsync(callback) }
+ }
+
+ override fun onDestroy() {
+ removeAllViews()
+ overlays.forEach {
+ if (map != null) {
+ it.removeFromMap(map!!)
+ }
}
-
- fun addOverlay(child: View, index: Int) = withMap { map ->
- when (child) {
- is RNCNaverMapMarker -> {
- child.addToMap(map)
- overlays.add(index, child)
- val visibility: Int = child.visibility
- child.visibility = INVISIBLE
- (child.parent as? ViewGroup)?.removeView(child)
- // Add to the parent group
- attacherGroup!!.addView(child)
- child.visibility = visibility
- }
-
- is RNCNaverMapOverlay<*> -> {
- child.addToMap(map)
- overlays.add(index, child)
- }
-
- else -> {
- addView(child, index);
- }
+ overlays.clear()
+ map = null
+ attacherGroup = null
+ super.onDestroy()
+ }
+
+ fun removeOverlay(index: Int) =
+ withMap { map ->
+ when (val child = overlays.removeAt(index)) {
+ is RNCNaverMapMarker -> {
+ child.removeFromMap(map)
+ attacherGroup?.removeView(child)
}
- }
- override fun onDestroy() {
- removeAllViews()
- overlays.forEach {
- if (map != null) {
- it.removeFromMap(map!!)
- }
+ is RNCNaverMapOverlay<*> -> {
+ child.removeFromMap(map)
}
- overlays.clear()
- map = null
- attacherGroup = null
- super.onDestroy()
- }
-
- fun removeOverlay(index: Int) = withMap { map ->
- when (val child = overlays.removeAt(index)) {
- is RNCNaverMapMarker -> {
- child.removeFromMap(map)
- attacherGroup?.removeView(child)
- }
-
- is RNCNaverMapOverlay<*> -> {
- child.removeFromMap(map)
- }
-
- else -> {
- removeView(child)
- }
+ else -> {
+ removeView(child)
}
+ }
}
- fun setupLocationSource() {
- reactContext.currentActivity?.also { activity ->
- val source = FusedLocationSource(activity, 100)
- withMap {
- it.locationSource = source
- }
- }
+ fun setupLocationSource() {
+ reactContext.currentActivity?.also { activity ->
+ val source = FusedLocationSource(activity, 100)
+ withMap {
+ it.locationSource = source
+ }
}
+ }
}
diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapViewManager.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapViewManager.kt
index 972a2f0b..de149348 100644
--- a/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapViewManager.kt
+++ b/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapViewManager.kt
@@ -4,6 +4,7 @@ import android.graphics.PointF
import android.view.Gravity
import android.view.View
import com.airbnb.android.react.maps.SizeReportingShadowNode
+import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.uimanager.LayoutShadowNode
import com.facebook.react.uimanager.ReactStylesDiffMap
@@ -15,6 +16,10 @@ import com.mjstudio.reactnativenavermap.event.NaverMapCameraChangeEvent
import com.mjstudio.reactnativenavermap.event.NaverMapInitializeEvent
import com.mjstudio.reactnativenavermap.event.NaverMapOptionChangeEvent
import com.mjstudio.reactnativenavermap.event.NaverMapTapEvent
+import com.mjstudio.reactnativenavermap.overlay.marker.cluster.RNCNaverMapClusterKey
+import com.mjstudio.reactnativenavermap.overlay.marker.cluster.RNCNaverMapClustererHolder
+import com.mjstudio.reactnativenavermap.overlay.marker.cluster.RNCNaverMapLeafMarkerHolder
+import com.mjstudio.reactnativenavermap.overlay.marker.cluster.RNCNaverMapLeafMarkerUpdater
import com.mjstudio.reactnativenavermap.util.CameraAnimationUtil
import com.mjstudio.reactnativenavermap.util.RectUtil
import com.mjstudio.reactnativenavermap.util.getDoubleOrNull
@@ -43,467 +48,658 @@ import com.naver.maps.map.NaverMap.MapType.None
import com.naver.maps.map.NaverMap.MapType.Satellite
import com.naver.maps.map.NaverMap.MapType.Terrain
import com.naver.maps.map.NaverMapOptions
+import com.naver.maps.map.clustering.Clusterer
import java.util.Locale
-
+import kotlin.math.max
+import kotlin.math.min
class RNCNaverMapViewManager : RNCNaverMapViewManagerSpec() {
- override fun getName(): String {
- return NAME
- }
-
- private var initialMapOptions: NaverMapOptions? = null
- private var animationDuration = 0
- private var animationEasing = CameraAnimationUtil.numberToCameraAnimationEasing(0)
- private var isFirstCameraMoving = true
-
-
- override fun createViewInstance(
- reactTag: Int,
- reactContext: ThemedReactContext,
- initialProps: ReactStylesDiffMap?,
- stateWrapper: StateWrapper?
- ): RNCNaverMapViewWrapper {
- initialMapOptions = NaverMapOptions().apply {
- useTextureView(
- initialProps?.getBoolean("isUseTextureViewAndroid", false) ?: false
- )
- initialProps?.getString("locale")?.also { locale ->
- locale(Locale.forLanguageTag(locale))
- }
- }
- return super.createViewInstance(reactTag, reactContext, initialProps, stateWrapper)
- }
-
-
- override fun createViewInstance(reactContext: ThemedReactContext): RNCNaverMapViewWrapper {
- return RNCNaverMapViewWrapper(reactContext, initialMapOptions ?: NaverMapOptions()).also {
- reactContext.addLifecycleEventListener(it)
- }
- }
-
- override fun onDropViewInstance(view: RNCNaverMapViewWrapper) {
- super.onDropViewInstance(view)
- view.onDropViewInstance()
- view.reactContext.removeLifecycleEventListener(view)
- }
-
- override fun getExportedCustomDirectEventTypeConstants(): MutableMap =
- (super.getExportedCustomDirectEventTypeConstants() ?: mutableMapOf()).apply {
- registerDirectEvent(this, NaverMapInitializeEvent.EVENT_NAME)
- registerDirectEvent(this, NaverMapOptionChangeEvent.EVENT_NAME)
- registerDirectEvent(this, NaverMapCameraChangeEvent.EVENT_NAME)
- registerDirectEvent(this, NaverMapTapEvent.EVENT_NAME)
- }
-
- private fun RNCNaverMapViewWrapper?.withMapView(callback: (mapView: RNCNaverMapView) -> Unit) {
- this?.mapView?.run(callback)
- }
-
- private fun RNCNaverMapViewWrapper?.withMap(callback: (map: NaverMap) -> Unit) {
- this?.mapView?.withMap(callback)
- }
-
- override fun needsCustomLayoutForChildren(): Boolean = true
-
- override fun addView(parent: RNCNaverMapViewWrapper?, child: View, index: Int) {
- parent?.withMapView {
- it.addOverlay(child, index)
- }
- }
-
- override fun getChildCount(parent: RNCNaverMapViewWrapper): Int {
- return parent.mapView?.overlays?.size ?: 0
- }
-
- override fun getChildAt(parent: RNCNaverMapViewWrapper?, index: Int): View? {
- return parent?.mapView?.overlays?.get(index)
- }
-
- override fun removeViewAt(parent: RNCNaverMapViewWrapper?, index: Int) {
- parent?.withMapView {
- it.removeOverlay(index)
- }
- }
-
- override fun createShadowNodeInstance(): LayoutShadowNode {
- // A custom shadow node is needed in order to pass back the width/height of the map to the
- // view manager so that it can start applying camera moves with bounds.
- return SizeReportingShadowNode()
- }
-
- // region PROPS
-
- @ReactProp(name = "mapType")
- override fun setMapType(view: RNCNaverMapViewWrapper?, value: String?) {
- view.withMap {
- it.mapType = when (value) {
- "Basic" -> Basic
- "Navi" -> Navi
- "Satellite" -> Satellite
- "Hybrid" -> Hybrid
- "Terrain" -> Terrain
- "NaviHybrid" -> NaviHybrid
- "None" -> None
- else -> Basic
- }
- }
- }
-
- @ReactProp(name = "layerGroups")
- override fun setLayerGroups(view: RNCNaverMapViewWrapper?, value: Int) = view.withMap {
- val building = value and (1 shl 0) != 0
- val traffic = value and (1 shl 1) != 0
- val transit = value and (1 shl 2) != 0
- val bicycle = value and (1 shl 3) != 0
- val mountain = value and (1 shl 4) != 0
- val cadastral = value and (1 shl 5) != 0
-
- if (it.isLayerGroupEnabled(LAYER_GROUP_BUILDING) != building) {
- it.setLayerGroupEnabled(LAYER_GROUP_BUILDING, building)
- }
-
- if (it.isLayerGroupEnabled(LAYER_GROUP_TRAFFIC) != traffic) {
- it.setLayerGroupEnabled(LAYER_GROUP_TRAFFIC, traffic)
- }
-
- if (it.isLayerGroupEnabled(LAYER_GROUP_TRANSIT) != transit) {
- it.setLayerGroupEnabled(LAYER_GROUP_TRANSIT, transit)
- }
-
- if (it.isLayerGroupEnabled(LAYER_GROUP_BICYCLE) != bicycle) {
- it.setLayerGroupEnabled(LAYER_GROUP_BICYCLE, bicycle)
- }
- if (it.isLayerGroupEnabled(LAYER_GROUP_MOUNTAIN) != mountain) {
- it.setLayerGroupEnabled(LAYER_GROUP_MOUNTAIN, mountain)
- }
-
- if (it.isLayerGroupEnabled(LAYER_GROUP_CADASTRAL) != cadastral) {
- it.setLayerGroupEnabled(LAYER_GROUP_CADASTRAL, cadastral)
- }
- }
-
- @ReactProp(name = "initialCamera")
- override fun setInitialCamera(view: RNCNaverMapViewWrapper?, value: ReadableMap?) =
- view.withMapView {
- if (!it.isInitialCameraOrRegionSet) {
- if (isValidNumber(value?.getDoubleOrNull("latitude"))) {
- it.isInitialCameraOrRegionSet = true
- setCamera(view, value)
- }
- }
- }
-
- @ReactProp(name = "camera")
- override fun setCamera(view: RNCNaverMapViewWrapper?, value: ReadableMap?) = view.withMap {
- value?.getLatLng()?.also { latlng ->
- val zoom = value.getDoubleOrNull("zoom") ?: it.cameraPosition.zoom
- val tilt = value.getDoubleOrNull("tilt") ?: it.cameraPosition.tilt
- val bearing = value.getDoubleOrNull("bearing") ?: it.cameraPosition.bearing
-
- it.moveCamera(
- CameraUpdate.toCameraPosition(
- CameraPosition(
- latlng,
- zoom,
- tilt,
- bearing,
- )
- ).apply {
- if (animationDuration > 0 && !isFirstCameraMoving) {
- animate(animationEasing, animationDuration.toLong())
- }
- }
- )
- isFirstCameraMoving = false
- }
- }
-
-
- @ReactProp(name = "initialRegion")
- override fun setInitialRegion(view: RNCNaverMapViewWrapper?, value: ReadableMap?) {
- if (isValidNumber(value?.getDoubleOrNull("latitude"))) {
- view.withMapView {
- if (!it.isInitialCameraOrRegionSet) {
- it.isInitialCameraOrRegionSet = true
- setRegion(view, value)
- }
- }
+ override fun getName(): String {
+ return NAME
+ }
+
+ private var initialMapOptions: NaverMapOptions? = null
+ private var animationDuration = 0
+ private var animationEasing = CameraAnimationUtil.numberToCameraAnimationEasing(0)
+ private var isFirstCameraMoving = true
+ private var lastClustersPropKey = "NOT_SET"
+
+ private val clustererHolders = mutableMapOf()
+
+ private lateinit var reactAppContext: ReactApplicationContext
+
+ override fun createViewInstance(
+ reactTag: Int,
+ reactContext: ThemedReactContext,
+ initialProps: ReactStylesDiffMap?,
+ stateWrapper: StateWrapper?,
+ ): RNCNaverMapViewWrapper {
+ reactAppContext = reactContext.reactApplicationContext
+ initialMapOptions =
+ NaverMapOptions().apply {
+ useTextureView(
+ initialProps?.getBoolean("isUseTextureViewAndroid", false) ?: false,
+ )
+ initialProps?.getString("locale")?.also { locale ->
+ locale(Locale.forLanguageTag(locale))
}
- }
-
- @ReactProp(name = "region")
- override fun setRegion(view: RNCNaverMapViewWrapper?, value: ReadableMap?) = view.withMap {
- value.getLatLngBoundsOrNull()?.run {
- val update = CameraUpdate.fitBounds(this).apply {
- if (animationDuration > 0 && !isFirstCameraMoving) {
- animate(animationEasing, animationDuration.toLong())
- }
- }
- it.moveCamera(update)
- isFirstCameraMoving = false
+ }
+ return super.createViewInstance(reactTag, reactContext, initialProps, stateWrapper)
+ }
+
+ override fun createViewInstance(reactContext: ThemedReactContext): RNCNaverMapViewWrapper {
+ return RNCNaverMapViewWrapper(reactContext, initialMapOptions ?: NaverMapOptions()).also {
+ reactContext.addLifecycleEventListener(it)
+ }
+ }
+
+ override fun onDropViewInstance(view: RNCNaverMapViewWrapper) {
+ view.onDropViewInstance()
+ view.reactContext.removeLifecycleEventListener(view)
+ clustererHolders.forEach { (_, u) -> u.onDetach() }
+ super.onDropViewInstance(view)
+ }
+
+ override fun getExportedCustomDirectEventTypeConstants(): MutableMap =
+ (super.getExportedCustomDirectEventTypeConstants() ?: mutableMapOf()).apply {
+ registerDirectEvent(this, NaverMapInitializeEvent.EVENT_NAME)
+ registerDirectEvent(this, NaverMapOptionChangeEvent.EVENT_NAME)
+ registerDirectEvent(this, NaverMapCameraChangeEvent.EVENT_NAME)
+ registerDirectEvent(this, NaverMapTapEvent.EVENT_NAME)
+ }
+
+ private fun RNCNaverMapViewWrapper?.withMapView(callback: (mapView: RNCNaverMapView) -> Unit) {
+ this?.mapView?.run(callback)
+ }
+
+ private fun RNCNaverMapViewWrapper?.withMap(callback: (map: NaverMap) -> Unit) {
+ this?.mapView?.withMap(callback)
+ }
+
+ override fun needsCustomLayoutForChildren(): Boolean = true
+
+ override fun addView(
+ parent: RNCNaverMapViewWrapper?,
+ child: View,
+ index: Int,
+ ) {
+ parent?.withMapView {
+ it.addOverlay(child, index)
+ }
+ }
+
+ override fun getChildCount(parent: RNCNaverMapViewWrapper): Int {
+ return parent.mapView?.overlays?.size ?: 0
+ }
+
+ override fun getChildAt(
+ parent: RNCNaverMapViewWrapper?,
+ index: Int,
+ ): View? {
+ return parent?.mapView?.overlays?.get(index)
+ }
+
+ override fun removeViewAt(
+ parent: RNCNaverMapViewWrapper?,
+ index: Int,
+ ) {
+ parent?.withMapView {
+ it.removeOverlay(index)
+ }
+ }
+
+ override fun createShadowNodeInstance(): LayoutShadowNode {
+ // A custom shadow node is needed in order to pass back the width/height of the map to the
+ // view manager so that it can start applying camera moves with bounds.
+ return SizeReportingShadowNode()
+ }
+
+ // region PROPS
+
+ @ReactProp(name = "mapType")
+ override fun setMapType(
+ view: RNCNaverMapViewWrapper?,
+ value: String?,
+ ) {
+ view.withMap {
+ it.mapType =
+ when (value) {
+ "Basic" -> Basic
+ "Navi" -> Navi
+ "Satellite" -> Satellite
+ "Hybrid" -> Hybrid
+ "Terrain" -> Terrain
+ "NaviHybrid" -> NaviHybrid
+ "None" -> None
+ else -> Basic
}
}
+ }
- @ReactProp(name = "animationDuration")
- override fun setAnimationDuration(view: RNCNaverMapViewWrapper?, value: Int) {
- animationDuration = value
- }
+ @ReactProp(name = "layerGroups")
+ override fun setLayerGroups(
+ view: RNCNaverMapViewWrapper?,
+ value: Int,
+ ) = view.withMap {
+ val building = value and (1 shl 0) != 0
+ val traffic = value and (1 shl 1) != 0
+ val transit = value and (1 shl 2) != 0
+ val bicycle = value and (1 shl 3) != 0
+ val mountain = value and (1 shl 4) != 0
+ val cadastral = value and (1 shl 5) != 0
- @ReactProp(name = "animationEasing")
- override fun setAnimationEasing(view: RNCNaverMapViewWrapper?, value: Int) {
- animationEasing = CameraAnimationUtil.numberToCameraAnimationEasing(value)
+ if (it.isLayerGroupEnabled(LAYER_GROUP_BUILDING) != building) {
+ it.setLayerGroupEnabled(LAYER_GROUP_BUILDING, building)
}
- @ReactProp(name = "isIndoorEnabled")
- override fun setIsIndoorEnabled(view: RNCNaverMapViewWrapper?, value: Boolean) = view.withMap {
- it.isIndoorEnabled = value
+ if (it.isLayerGroupEnabled(LAYER_GROUP_TRAFFIC) != traffic) {
+ it.setLayerGroupEnabled(LAYER_GROUP_TRAFFIC, traffic)
}
- @ReactProp(name = "isNightModeEnabled")
- override fun setIsNightModeEnabled(view: RNCNaverMapViewWrapper?, value: Boolean) =
- view.withMap {
- it.isNightModeEnabled = value
- }
-
- @ReactProp(name = "isLiteModeEnabled")
- override fun setIsLiteModeEnabled(view: RNCNaverMapViewWrapper?, value: Boolean) =
- view.withMap {
- it.isLiteModeEnabled = value
- }
-
- @ReactProp(name = "lightness")
- override fun setLightness(view: RNCNaverMapViewWrapper?, value: Double) = view.withMap {
- it.lightness = value.toFloat()
- }
-
- @ReactProp(name = "buildingHeight")
- override fun setBuildingHeight(view: RNCNaverMapViewWrapper?, value: Double) = view.withMap {
- it.buildingHeight = value.toFloat()
+ if (it.isLayerGroupEnabled(LAYER_GROUP_TRANSIT) != transit) {
+ it.setLayerGroupEnabled(LAYER_GROUP_TRANSIT, transit)
}
- @ReactProp(name = "symbolScale")
- override fun setSymbolScale(view: RNCNaverMapViewWrapper?, value: Double) = view.withMap {
- it.symbolScale = value.toFloat()
- }
-
- @ReactProp(name = "symbolPerspectiveRatio")
- override fun setSymbolPerspectiveRatio(view: RNCNaverMapViewWrapper?, value: Double) =
- view.withMap {
- it.symbolPerspectiveRatio = value.toFloat()
- }
-
- @ReactProp(name = "mapPadding")
- override fun setMapPadding(view: RNCNaverMapViewWrapper?, value: ReadableMap?) =
- view.withMapView {
- RectUtil.getRect(value, it.resources.displayMetrics.density, defaultValue = 0.0)?.run {
- it.withMap { map ->
- map.setContentPadding(left, top, right, bottom)
- }
- }
- }
-
-
- @ReactProp(name = "minZoom")
- override fun setMinZoom(view: RNCNaverMapViewWrapper?, value: Double) = view.withMap {
- it.minZoom = value
+ if (it.isLayerGroupEnabled(LAYER_GROUP_BICYCLE) != bicycle) {
+ it.setLayerGroupEnabled(LAYER_GROUP_BICYCLE, bicycle)
}
-
- @ReactProp(name = "maxZoom")
- override fun setMaxZoom(view: RNCNaverMapViewWrapper?, value: Double) = view.withMap {
- it.maxZoom = value
+ if (it.isLayerGroupEnabled(LAYER_GROUP_MOUNTAIN) != mountain) {
+ it.setLayerGroupEnabled(LAYER_GROUP_MOUNTAIN, mountain)
}
- @ReactProp(name = "isShowCompass")
- override fun setIsShowCompass(view: RNCNaverMapViewWrapper?, value: Boolean) = view.withMap {
- it.uiSettings.isCompassEnabled = value
+ if (it.isLayerGroupEnabled(LAYER_GROUP_CADASTRAL) != cadastral) {
+ it.setLayerGroupEnabled(LAYER_GROUP_CADASTRAL, cadastral)
}
+ }
- @ReactProp(name = "isShowScaleBar")
- override fun setIsShowScaleBar(view: RNCNaverMapViewWrapper?, value: Boolean) = view.withMap {
- it.uiSettings.isScaleBarEnabled = value
+ @ReactProp(name = "initialCamera")
+ override fun setInitialCamera(
+ view: RNCNaverMapViewWrapper?,
+ value: ReadableMap?,
+ ) = view.withMapView {
+ if (!it.isInitialCameraOrRegionSet) {
+ if (isValidNumber(value?.getDoubleOrNull("latitude"))) {
+ it.isInitialCameraOrRegionSet = true
+ setCamera(view, value)
+ }
}
+ }
- @ReactProp(name = "isShowZoomControls")
- override fun setIsShowZoomControls(view: RNCNaverMapViewWrapper?, value: Boolean) =
- view.withMap {
- it.uiSettings.isZoomControlEnabled = value
- }
-
- @ReactProp(name = "isShowIndoorLevelPicker")
- override fun setIsShowIndoorLevelPicker(view: RNCNaverMapViewWrapper?, value: Boolean) =
- view.withMap {
- it.uiSettings.isIndoorLevelPickerEnabled = value
- }
-
- @ReactProp(name = "isShowLocationButton")
- override fun setIsShowLocationButton(view: RNCNaverMapViewWrapper?, value: Boolean) =
- view.withMapView {
- it.setupLocationSource()
- it.withMap { map ->
- map.uiSettings.isLocationButtonEnabled = value
- }
- }
-
- @ReactProp(name = "logoAlign")
- override fun setLogoAlign(view: RNCNaverMapViewWrapper?, value: String?) = view.withMap {
- it.uiSettings.logoGravity = when (value) {
- "TopLeft" -> Gravity.TOP or Gravity.LEFT
- "TopRight" -> Gravity.TOP or Gravity.RIGHT
- "BottomRight" -> Gravity.BOTTOM or Gravity.RIGHT
- else -> Gravity.BOTTOM or Gravity.LEFT
- }
- }
+ @ReactProp(name = "camera")
+ override fun setCamera(
+ view: RNCNaverMapViewWrapper?,
+ value: ReadableMap?,
+ ) = view.withMap {
+ value?.getLatLng()?.also { latlng ->
+ val zoom = value.getDoubleOrNull("zoom") ?: it.cameraPosition.zoom
+ val tilt = value.getDoubleOrNull("tilt") ?: it.cameraPosition.tilt
+ val bearing = value.getDoubleOrNull("bearing") ?: it.cameraPosition.bearing
- @ReactProp(name = "logoMargin")
- override fun setLogoMargin(view: RNCNaverMapViewWrapper?, value: ReadableMap?) =
- view.withMapView {
- RectUtil.getRect(value, it.resources.displayMetrics.density, defaultValue = 0.0)?.run {
- it.withMap { map ->
- map.uiSettings.setLogoMargin(left, top, right, bottom)
- }
- }
+ it.moveCamera(
+ CameraUpdate.toCameraPosition(
+ CameraPosition(
+ latlng,
+ zoom,
+ tilt,
+ bearing,
+ ),
+ ).apply {
+ if (animationDuration > 0 && !isFirstCameraMoving) {
+ animate(animationEasing, animationDuration.toLong())
+ }
+ },
+ )
+ isFirstCameraMoving = false
+ }
+ }
+
+ @ReactProp(name = "initialRegion")
+ override fun setInitialRegion(
+ view: RNCNaverMapViewWrapper?,
+ value: ReadableMap?,
+ ) {
+ if (isValidNumber(value?.getDoubleOrNull("latitude"))) {
+ view.withMapView {
+ if (!it.isInitialCameraOrRegionSet) {
+ it.isInitialCameraOrRegionSet = true
+ setRegion(view, value)
}
-
- @ReactProp(name = "extent")
- override fun setExtent(view: RNCNaverMapViewWrapper?, value: ReadableMap?) = view.withMap {
- value.getLatLngBoundsOrNull()?.run {
- it.extent = this
+ }
+ }
+ }
+
+ @ReactProp(name = "region")
+ override fun setRegion(
+ view: RNCNaverMapViewWrapper?,
+ value: ReadableMap?,
+ ) = view.withMap {
+ value.getLatLngBoundsOrNull()?.run {
+ val update =
+ CameraUpdate.fitBounds(this).apply {
+ if (animationDuration > 0 && !isFirstCameraMoving) {
+ animate(animationEasing, animationDuration.toLong())
+ }
}
- }
-
- @ReactProp(name = "isScrollGesturesEnabled")
- override fun setIsScrollGesturesEnabled(view: RNCNaverMapViewWrapper?, value: Boolean) =
- view.withMap { it.uiSettings.isScrollGesturesEnabled = value }
-
- @ReactProp(name = "isZoomGesturesEnabled")
- override fun setIsZoomGesturesEnabled(view: RNCNaverMapViewWrapper?, value: Boolean) =
- view.withMap { it.uiSettings.isZoomGesturesEnabled = value }
-
- @ReactProp(name = "isTiltGesturesEnabled")
- override fun setIsTiltGesturesEnabled(view: RNCNaverMapViewWrapper?, value: Boolean) =
- view.withMap { it.uiSettings.isTiltGesturesEnabled = value }
-
- @ReactProp(name = "isRotateGesturesEnabled")
- override fun setIsRotateGesturesEnabled(view: RNCNaverMapViewWrapper?, value: Boolean) =
- view.withMap { it.uiSettings.isRotateGesturesEnabled = value }
-
- @ReactProp(name = "isStopGesturesEnabled")
- override fun setIsStopGesturesEnabled(view: RNCNaverMapViewWrapper?, value: Boolean) =
- view.withMap { it.uiSettings.isStopGesturesEnabled = value }
-
- @ReactProp(name = "isUseTextureViewAndroid")
- override fun setIsUseTextureViewAndroid(view: RNCNaverMapViewWrapper?, value: Boolean) {
- // don't implement this
- }
-
- @ReactProp(name = "locale")
- override fun setLocale(view: RNCNaverMapViewWrapper?, value: String?) = view.withMap {
- if (value != null) {
- it.locale = Locale.forLanguageTag(value)
+ it.moveCamera(update)
+ isFirstCameraMoving = false
+ }
+ }
+
+ @ReactProp(name = "animationDuration")
+ override fun setAnimationDuration(
+ view: RNCNaverMapViewWrapper?,
+ value: Int,
+ ) {
+ animationDuration = value
+ }
+
+ @ReactProp(name = "animationEasing")
+ override fun setAnimationEasing(
+ view: RNCNaverMapViewWrapper?,
+ value: Int,
+ ) {
+ animationEasing = CameraAnimationUtil.numberToCameraAnimationEasing(value)
+ }
+
+ @ReactProp(name = "isIndoorEnabled")
+ override fun setIsIndoorEnabled(
+ view: RNCNaverMapViewWrapper?,
+ value: Boolean,
+ ) = view.withMap {
+ it.isIndoorEnabled = value
+ }
+
+ @ReactProp(name = "isNightModeEnabled")
+ override fun setIsNightModeEnabled(
+ view: RNCNaverMapViewWrapper?,
+ value: Boolean,
+ ) = view.withMap {
+ it.isNightModeEnabled = value
+ }
+
+ @ReactProp(name = "isLiteModeEnabled")
+ override fun setIsLiteModeEnabled(
+ view: RNCNaverMapViewWrapper?,
+ value: Boolean,
+ ) = view.withMap {
+ it.isLiteModeEnabled = value
+ }
+
+ @ReactProp(name = "lightness")
+ override fun setLightness(
+ view: RNCNaverMapViewWrapper?,
+ value: Double,
+ ) = view.withMap {
+ it.lightness = value.toFloat()
+ }
+
+ @ReactProp(name = "buildingHeight")
+ override fun setBuildingHeight(
+ view: RNCNaverMapViewWrapper?,
+ value: Double,
+ ) = view.withMap {
+ it.buildingHeight = value.toFloat()
+ }
+
+ @ReactProp(name = "symbolScale")
+ override fun setSymbolScale(
+ view: RNCNaverMapViewWrapper?,
+ value: Double,
+ ) = view.withMap {
+ it.symbolScale = value.toFloat()
+ }
+
+ @ReactProp(name = "symbolPerspectiveRatio")
+ override fun setSymbolPerspectiveRatio(
+ view: RNCNaverMapViewWrapper?,
+ value: Double,
+ ) = view.withMap {
+ it.symbolPerspectiveRatio = value.toFloat()
+ }
+
+ @ReactProp(name = "mapPadding")
+ override fun setMapPadding(
+ view: RNCNaverMapViewWrapper?,
+ value: ReadableMap?,
+ ) = view.withMapView {
+ RectUtil.getRect(value, it.resources.displayMetrics.density, defaultValue = 0.0)?.run {
+ it.withMap { map ->
+ map.setContentPadding(left, top, right, bottom)
+ }
+ }
+ }
+
+ @ReactProp(name = "minZoom")
+ override fun setMinZoom(
+ view: RNCNaverMapViewWrapper?,
+ value: Double,
+ ) = view.withMap {
+ it.minZoom = value
+ }
+
+ @ReactProp(name = "maxZoom")
+ override fun setMaxZoom(
+ view: RNCNaverMapViewWrapper?,
+ value: Double,
+ ) = view.withMap {
+ it.maxZoom = value
+ }
+
+ @ReactProp(name = "isShowCompass")
+ override fun setIsShowCompass(
+ view: RNCNaverMapViewWrapper?,
+ value: Boolean,
+ ) = view.withMap {
+ it.uiSettings.isCompassEnabled = value
+ }
+
+ @ReactProp(name = "isShowScaleBar")
+ override fun setIsShowScaleBar(
+ view: RNCNaverMapViewWrapper?,
+ value: Boolean,
+ ) = view.withMap {
+ it.uiSettings.isScaleBarEnabled = value
+ }
+
+ @ReactProp(name = "isShowZoomControls")
+ override fun setIsShowZoomControls(
+ view: RNCNaverMapViewWrapper?,
+ value: Boolean,
+ ) = view.withMap {
+ it.uiSettings.isZoomControlEnabled = value
+ }
+
+ @ReactProp(name = "isShowIndoorLevelPicker")
+ override fun setIsShowIndoorLevelPicker(
+ view: RNCNaverMapViewWrapper?,
+ value: Boolean,
+ ) = view.withMap {
+ it.uiSettings.isIndoorLevelPickerEnabled = value
+ }
+
+ @ReactProp(name = "isShowLocationButton")
+ override fun setIsShowLocationButton(
+ view: RNCNaverMapViewWrapper?,
+ value: Boolean,
+ ) = view.withMapView {
+ it.setupLocationSource()
+ it.withMap { map ->
+ map.uiSettings.isLocationButtonEnabled = value
+ }
+ }
+
+ @ReactProp(name = "logoAlign")
+ override fun setLogoAlign(
+ view: RNCNaverMapViewWrapper?,
+ value: String?,
+ ) = view.withMap {
+ it.uiSettings.logoGravity =
+ when (value) {
+ "TopLeft" -> Gravity.TOP or Gravity.LEFT
+ "TopRight" -> Gravity.TOP or Gravity.RIGHT
+ "BottomRight" -> Gravity.BOTTOM or Gravity.RIGHT
+ else -> Gravity.BOTTOM or Gravity.LEFT
+ }
+ }
+
+ @ReactProp(name = "logoMargin")
+ override fun setLogoMargin(
+ view: RNCNaverMapViewWrapper?,
+ value: ReadableMap?,
+ ) = view.withMapView {
+ RectUtil.getRect(value, it.resources.displayMetrics.density, defaultValue = 0.0)?.run {
+ it.withMap { map ->
+ map.uiSettings.setLogoMargin(left, top, right, bottom)
+ }
+ }
+ }
+
+ @ReactProp(name = "extent")
+ override fun setExtent(
+ view: RNCNaverMapViewWrapper?,
+ value: ReadableMap?,
+ ) = view.withMap {
+ value.getLatLngBoundsOrNull()?.run {
+ it.extent = this
+ }
+ }
+
+ @ReactProp(name = "isScrollGesturesEnabled")
+ override fun setIsScrollGesturesEnabled(
+ view: RNCNaverMapViewWrapper?,
+ value: Boolean,
+ ) = view.withMap { it.uiSettings.isScrollGesturesEnabled = value }
+
+ @ReactProp(name = "isZoomGesturesEnabled")
+ override fun setIsZoomGesturesEnabled(
+ view: RNCNaverMapViewWrapper?,
+ value: Boolean,
+ ) = view.withMap { it.uiSettings.isZoomGesturesEnabled = value }
+
+ @ReactProp(name = "isTiltGesturesEnabled")
+ override fun setIsTiltGesturesEnabled(
+ view: RNCNaverMapViewWrapper?,
+ value: Boolean,
+ ) = view.withMap { it.uiSettings.isTiltGesturesEnabled = value }
+
+ @ReactProp(name = "isRotateGesturesEnabled")
+ override fun setIsRotateGesturesEnabled(
+ view: RNCNaverMapViewWrapper?,
+ value: Boolean,
+ ) = view.withMap { it.uiSettings.isRotateGesturesEnabled = value }
+
+ @ReactProp(name = "isStopGesturesEnabled")
+ override fun setIsStopGesturesEnabled(
+ view: RNCNaverMapViewWrapper?,
+ value: Boolean,
+ ) = view.withMap { it.uiSettings.isStopGesturesEnabled = value }
+
+ @ReactProp(name = "isUseTextureViewAndroid")
+ override fun setIsUseTextureViewAndroid(
+ view: RNCNaverMapViewWrapper?,
+ value: Boolean,
+ ) {
+ // don't implement this
+ }
+
+ @ReactProp(name = "locale")
+ override fun setLocale(
+ view: RNCNaverMapViewWrapper?,
+ value: String?,
+ ) = view.withMap {
+ if (value != null) {
+ it.locale = Locale.forLanguageTag(value)
+ }
+ }
+
+ @ReactProp(name = "clusters")
+ override fun setClusters(
+ view: RNCNaverMapViewWrapper?,
+ value: ReadableMap?,
+ ) = view.withMap { map ->
+ if (value == null) {
+ return@withMap
+ }
+ val propKey = value.getString("key") ?: ""
+
+ if (propKey == lastClustersPropKey) {
+ return@withMap
+ }
+ lastClustersPropKey = propKey
+
+ // remove all at now
+ clustererHolders.forEach { (_, clusterer) ->
+ clusterer.onDetach()
+ }
+ clustererHolders.clear()
+
+ value.getArray("clusters")?.toArrayList()?.filterIsInstance