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) ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค. -preview +preview ## ์™œ ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์จ์•ผํ•˜๋‚˜์š”? @@ -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>()?.forEach { + val clustererKey = it["key"] as? String + val screenDistance = it["screenDistance"] as? Double + val minZoom = it["minZoom"] as? Double + val maxZoom = it["maxZoom"] as? Double + val animate = it["animate"] as? Boolean + val markers = (it["markers"] as? ArrayList<*>)?.filterIsInstance>() ?: listOf() + + val clusterer = + Clusterer.Builder().also { cluster -> + if (screenDistance != null) { + cluster.screenDistance(screenDistance) + } + if (minZoom != null) { + cluster.minZoom(max(minZoom.toInt(), 1)) + } + if (maxZoom != null) { + cluster.maxZoom(min(maxZoom.toInt(), 20)) + } + if (animate != null) { + cluster.animate(animate) + } + }.leafMarkerUpdater(RNCNaverMapLeafMarkerUpdater()).build() + + val keyPairs = + markers.associate { marker -> + val identifier = marker["identifier"] as String + val latitude = marker["latitude"] as Double + val longitude = marker["longitude"] as Double + val image = marker["image"] as? Map<*, *> + val width = marker["width"] as? Double + val height = marker["height"] as? Double + + RNCNaverMapClusterKey( + identifier, + LatLng(latitude, longitude), + image, + width, + height, + RNCNaverMapLeafMarkerHolder(identifier, reactAppContext), + ) to null } - } - // endregion - - // region COMMANDS - override fun screenToCoordinate(view: RNCNaverMapViewWrapper?, x: Double, y: Double) = - view.withMap {} - - - override fun coordinateToScreen( - view: RNCNaverMapViewWrapper?, latitude: Double, longitude: Double - ) = view.withMap { } - - - override fun animateCameraTo( - view: RNCNaverMapViewWrapper?, - latitude: Double, - longitude: Double, - duration: Int, - easing: Int, - pivotX: Double, - pivotY: Double, - zoom: Double, - ) = view.withMap { - val update = if (isValidNumber(zoom)) CameraUpdate.scrollAndZoomTo( - LatLng( - latitude, - longitude - ), - zoom, - ) else CameraUpdate.scrollTo(LatLng(latitude, longitude)) - - update - .animate(CameraAnimationUtil.numberToCameraAnimationEasing(easing), duration.toLong()) - .pivot( - PointF(pivotX.toFloat(), pivotY.toFloat()) - ).run { - it.moveCamera(this) - } - } - - override fun animateCameraBy( - view: RNCNaverMapViewWrapper?, - x: Double, - y: Double, - duration: Int, - easing: Int, - pivotX: Double, - pivotY: Double - ) = view.withMap { - CameraUpdate.scrollBy( - PointF( - x.px.toFloat(), - y.px.toFloat(), - ) - ).animate(CameraAnimationUtil.numberToCameraAnimationEasing(easing), duration.toLong()) - .pivot( - PointF(pivotX.toFloat(), pivotY.toFloat()) - ).run { - it.moveCamera(this) - } - } - - override fun animateRegionTo( - view: RNCNaverMapViewWrapper?, - latitude: Double, - longitude: Double, - latitudeDelta: Double, - longitudeDelta: Double, - duration: Int, - easing: Int, - pivotX: Double, - pivotY: Double - ) = view.withMap { - CameraUpdate.fitBounds( - LatLngBounds( - LatLng(latitude, longitude), - LatLng(latitude + latitudeDelta, longitude + longitudeDelta) - ) - ).animate(CameraAnimationUtil.numberToCameraAnimationEasing(easing), duration.toLong()) - .pivot( - PointF(pivotX.toFloat(), pivotY.toFloat()) - ).run { - it.moveCamera(this) - } - } - - override fun cancelAnimation(view: RNCNaverMapViewWrapper?) = view.withMap { - it.cancelTransitions() - } - - override fun setLocationTrackingMode(view: RNCNaverMapViewWrapper?, mode: String?) = - view.withMap { - it.locationTrackingMode = when (mode) { - "NoFollow" -> LocationTrackingMode.NoFollow - "Follow" -> LocationTrackingMode.Follow - "Face" -> LocationTrackingMode.Face - else -> LocationTrackingMode.None - } - } - - - companion object { - const val NAME = "RNCNaverMapView" - } + clusterer.addAll(keyPairs) + clusterer.map = map + clustererHolders[clustererKey!!] = + RNCNaverMapClustererHolder( + clustererKey, + clusterer, + reactAppContext, + keyPairs.map { pair -> pair.key.holder }, + ) + } + } + + // endregion + + // region COMMANDS + override fun screenToCoordinate( + view: RNCNaverMapViewWrapper?, + x: Double, + y: Double, + ) = view.withMap {} + + override fun coordinateToScreen( + view: RNCNaverMapViewWrapper?, + latitude: Double, + longitude: Double, + ) = view.withMap { } + + override fun animateCameraTo( + view: RNCNaverMapViewWrapper?, + latitude: Double, + longitude: Double, + duration: Int, + easing: Int, + pivotX: Double, + pivotY: Double, + zoom: Double, + ) = view.withMap { + val update = + if (isValidNumber(zoom)) { + CameraUpdate.scrollAndZoomTo( + LatLng( + latitude, + longitude, + ), + zoom, + ) + } else { + CameraUpdate.scrollTo(LatLng(latitude, longitude)) + } + + update + .animate(CameraAnimationUtil.numberToCameraAnimationEasing(easing), duration.toLong()) + .pivot( + PointF(pivotX.toFloat(), pivotY.toFloat()), + ).run { + it.moveCamera(this) + } + } + + override fun animateCameraBy( + view: RNCNaverMapViewWrapper?, + x: Double, + y: Double, + duration: Int, + easing: Int, + pivotX: Double, + pivotY: Double, + ) = view.withMap { + CameraUpdate.scrollBy( + PointF( + x.px.toFloat(), + y.px.toFloat(), + ), + ).animate(CameraAnimationUtil.numberToCameraAnimationEasing(easing), duration.toLong()) + .pivot( + PointF(pivotX.toFloat(), pivotY.toFloat()), + ).run { + it.moveCamera(this) + } + } + + override fun animateRegionTo( + view: RNCNaverMapViewWrapper?, + latitude: Double, + longitude: Double, + latitudeDelta: Double, + longitudeDelta: Double, + duration: Int, + easing: Int, + pivotX: Double, + pivotY: Double, + ) = view.withMap { + CameraUpdate.fitBounds( + LatLngBounds( + LatLng(latitude, longitude), + LatLng(latitude + latitudeDelta, longitude + longitudeDelta), + ), + ).animate(CameraAnimationUtil.numberToCameraAnimationEasing(easing), duration.toLong()) + .pivot( + PointF(pivotX.toFloat(), pivotY.toFloat()), + ).run { + it.moveCamera(this) + } + } + + override fun cancelAnimation(view: RNCNaverMapViewWrapper?) = + view.withMap { + it.cancelTransitions() + } + + override fun setLocationTrackingMode( + view: RNCNaverMapViewWrapper?, + mode: String?, + ) = view.withMap { + it.locationTrackingMode = + when (mode) { + "NoFollow" -> LocationTrackingMode.NoFollow + "Follow" -> LocationTrackingMode.Follow + "Face" -> LocationTrackingMode.Face + else -> LocationTrackingMode.None + } + } + + companion object { + const val NAME = "RNCNaverMapView" + } } diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapViewWrapper.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapViewWrapper.kt index b995e7af..b1f54fa9 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapViewWrapper.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapViewWrapper.kt @@ -13,92 +13,99 @@ import com.naver.maps.map.NaverMapOptions @SuppressLint("ViewConstructor") class RNCNaverMapViewWrapper( - val reactContext: ThemedReactContext, - private val mapOptions: NaverMapOptions + val reactContext: ThemedReactContext, + private val mapOptions: NaverMapOptions, ) : - FrameLayout(reactContext), LifecycleEventListener { - var mapView: RNCNaverMapView? = null - private set - private var savedState: Bundle? = Bundle() + FrameLayout(reactContext), LifecycleEventListener { + var mapView: RNCNaverMapView? = null + private set + private var savedState: Bundle? = Bundle() - init { - mapView = RNCNaverMapView(reactContext, mapOptions) - addView(mapView) - } + init { + mapView = RNCNaverMapView(reactContext, mapOptions) + addView(mapView) + } - override fun onAttachedToWindow() { - super.onAttachedToWindow() - mapView?.run { - onCreate(savedState) - onStart() - } - setupLayoutHack() - } - - override fun onDetachedFromWindow() { - mapView?.run { - onSaveInstanceState(savedState ?: run { - Bundle().also { this@RNCNaverMapViewWrapper.savedState = it } - }) - } - super.onDetachedFromWindow() + override fun onAttachedToWindow() { + super.onAttachedToWindow() + mapView?.run { + onCreate(savedState) + onStart() } + setupLayoutHack() + } - fun onDropViewInstance() { - mapView?.run { - onStop() - onDestroy() - } - removeAllViews() - savedState?.clear() - savedState = null - mapView = null + override fun onDetachedFromWindow() { + mapView?.run { + onSaveInstanceState( + savedState ?: run { + Bundle().also { this@RNCNaverMapViewWrapper.savedState = it } + }, + ) } + super.onDetachedFromWindow() + } - // https://github.com/facebook/react-native/issues/17968#issuecomment-457236577 - private fun setupLayoutHack() { - Choreographer.getInstance().postFrameCallback(object : FrameCallback { - override fun doFrame(frameTimeNanos: Long) { - manuallyLayoutChildren() - getViewTreeObserver().dispatchOnGlobalLayout() - if (isAttachedToWindow) Choreographer.getInstance() - .postFrameCallbackDelayed(this, 500) - } - }) + fun onDropViewInstance() { + mapView?.run { + onStop() + onDestroy() } + removeAllViews() + savedState?.clear() + savedState = null + mapView = null + } - private fun manuallyLayoutChildren() { - for (i in 0 until childCount) { - val child = getChildAt(i) - child.measure( - MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY) - ) - child.layout(0, 0, child.measuredWidth, child.measuredHeight) + // https://github.com/facebook/react-native/issues/17968#issuecomment-457236577 + private fun setupLayoutHack() { + Choreographer.getInstance().postFrameCallback( + object : FrameCallback { + override fun doFrame(frameTimeNanos: Long) { + manuallyLayoutChildren() + getViewTreeObserver().dispatchOnGlobalLayout() + if (isAttachedToWindow) { + Choreographer.getInstance() + .postFrameCallbackDelayed(this, 500) + } } - } + }, + ) + } - override fun onHostResume() { - mapView?.onResume() + private fun manuallyLayoutChildren() { + for (i in 0 until childCount) { + val child = getChildAt(i) + child.measure( + MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY), + ) + child.layout(0, 0, child.measuredWidth, child.measuredHeight) } + } - override fun onHostPause() { - mapView?.onPause() - } + override fun onHostResume() { + mapView?.onResume() + } - override fun onHostDestroy() {} + override fun onHostPause() { + mapView?.onPause() + } - companion object { - /** - * A helper to get react tag id by given MapView - */ - @JvmStatic - fun getReactTagFromMapView(mapView: MapView): Int { // It is expected that the mapView is enclosed by [RNCNaverMapViewWrapper] as the first child. - // Therefore, it must have a parent, and the parent ID is the reactTag. - // In exceptional cases, such as receiving MapView messaging after the view has been unmounted, - // the WebView will not have a parent. - // In this case, we simply return -1 to indicate that it was not found. - return (mapView.parent as? View)?.id ?: -1 - } + override fun onHostDestroy() {} + + companion object { + /** + * A helper to get react tag id by given MapView + */ + @JvmStatic + fun getReactTagFromMapView(mapView: MapView): Int { + // It is expected that the mapView is enclosed by [RNCNaverMapViewWrapper] as the first child. + // Therefore, it must have a parent, and the parent ID is the reactTag. + // In exceptional cases, such as receiving MapView messaging after the view has been unmounted, + // the WebView will not have a parent. + // In this case, we simply return -1 to indicate that it was not found. + return (mapView.parent as? View)?.id ?: -1 } -} \ No newline at end of file + } +} diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/RNCNaverMapOverlay.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/RNCNaverMapOverlay.kt index aba14d39..9cf4f314 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/RNCNaverMapOverlay.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/RNCNaverMapOverlay.kt @@ -5,11 +5,12 @@ import com.facebook.react.views.view.ReactViewGroup import com.naver.maps.map.NaverMap import com.naver.maps.map.overlay.Overlay - abstract class RNCNaverMapOverlay(context: Context?) : ReactViewGroup(context) { - abstract val overlay: T - abstract fun addToMap(map: NaverMap) - abstract fun removeFromMap(map: NaverMap) - abstract fun onDropViewInstance() -} + abstract val overlay: T + + abstract fun addToMap(map: NaverMap) + abstract fun removeFromMap(map: NaverMap) + + abstract fun onDropViewInstance() +} diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/circle/RNCNaverMapCircle.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/circle/RNCNaverMapCircle.kt index 473f4e00..523451c3 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/circle/RNCNaverMapCircle.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/circle/RNCNaverMapCircle.kt @@ -10,31 +10,31 @@ import com.naver.maps.map.overlay.CircleOverlay @SuppressLint("ViewConstructor") class RNCNaverMapCircle(private val reactContext: ThemedReactContext) : - RNCNaverMapOverlay(reactContext) { - override val overlay: CircleOverlay by lazy { - CircleOverlay().apply { - setOnClickListener { - reactContext.emitEvent(id) { surfaceId, reactTag -> - NaverMapOverlayTapEvent( - surfaceId, - reactTag - ) - } - true - } + RNCNaverMapOverlay(reactContext) { + override val overlay: CircleOverlay by lazy { + CircleOverlay().apply { + setOnClickListener { + reactContext.emitEvent(id) { surfaceId, reactTag -> + NaverMapOverlayTapEvent( + surfaceId, + reactTag, + ) } + true + } } + } - override fun addToMap(map: NaverMap) { - overlay.map = map - } + override fun addToMap(map: NaverMap) { + overlay.map = map + } - override fun removeFromMap(map: NaverMap) { - overlay.map = null - } + override fun removeFromMap(map: NaverMap) { + overlay.map = null + } - override fun onDropViewInstance() { - overlay.map = null - overlay.onClickListener = null - } -} \ No newline at end of file + override fun onDropViewInstance() { + overlay.map = null + overlay.onClickListener = null + } +} diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/circle/RNCNaverMapCircleManager.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/circle/RNCNaverMapCircleManager.kt index 76bcdd93..df16d57c 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/circle/RNCNaverMapCircleManager.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/circle/RNCNaverMapCircleManager.kt @@ -10,94 +10,122 @@ import com.mjstudio.reactnativenavermap.util.px import com.mjstudio.reactnativenavermap.util.registerDirectEvent import com.naver.maps.map.overlay.CircleOverlay - class RNCNaverMapCircleManager : RNCNaverMapCircleManagerSpec() { - override fun getName(): String { - return NAME - } - - override fun createViewInstance(context: ThemedReactContext): RNCNaverMapCircle { - return RNCNaverMapCircle(context) - } - - override fun onDropViewInstance(view: RNCNaverMapCircle) { - super.onDropViewInstance(view) - view.onDropViewInstance() - } - - override fun getExportedCustomDirectEventTypeConstants(): MutableMap = - (super.getExportedCustomDirectEventTypeConstants() ?: mutableMapOf()).apply { - registerDirectEvent(this, NaverMapOverlayTapEvent.EVENT_NAME) - } - - - private fun RNCNaverMapCircle?.withOverlay(fn: (CircleOverlay) -> Unit) { - this?.overlay?.run(fn) - } - - @ReactProp(name = "coord") - override fun setCoord(view: RNCNaverMapCircle?, value: ReadableMap?) = view.withOverlay { - value.getLatLng()?.run { - it.center = this - } - } - - @ReactProp(name = "zIndexValue") - override fun setZIndexValue(view: RNCNaverMapCircle?, value: Int) = view.withOverlay { - it.zIndex = value - } - - @ReactProp(name = "isHidden") - override fun setIsHidden(view: RNCNaverMapCircle?, value: Boolean) = view.withOverlay { - it.isVisible = !value + override fun getName(): String { + return NAME + } + + override fun createViewInstance(context: ThemedReactContext): RNCNaverMapCircle { + return RNCNaverMapCircle(context) + } + + override fun onDropViewInstance(view: RNCNaverMapCircle) { + super.onDropViewInstance(view) + view.onDropViewInstance() + } + + override fun getExportedCustomDirectEventTypeConstants(): MutableMap = + (super.getExportedCustomDirectEventTypeConstants() ?: mutableMapOf()).apply { + registerDirectEvent(this, NaverMapOverlayTapEvent.EVENT_NAME) } - @ReactProp(name = "minZoom") - override fun setMinZoom(view: RNCNaverMapCircle?, value: Double) = view.withOverlay { - it.minZoom = value - } - - @ReactProp(name = "maxZoom") - override fun setMaxZoom(view: RNCNaverMapCircle?, value: Double) = view.withOverlay { - it.maxZoom = value - } - - @ReactProp(name = "isMinZoomInclusive") - override fun setIsMinZoomInclusive(view: RNCNaverMapCircle?, value: Boolean) = - view.withOverlay { - it.isMinZoomInclusive = value - } - - @ReactProp(name = "isMaxZoomInclusive") - override fun setIsMaxZoomInclusive(view: RNCNaverMapCircle?, value: Boolean) = - view.withOverlay { - it.isMaxZoomInclusive = value - } - - @ReactProp(name = "radius") - override fun setRadius(view: RNCNaverMapCircle?, value: Double) = view.withOverlay { - it.radius = value - } - - @ReactProp(name = "color") - override fun setColor(view: RNCNaverMapCircle?, value: Int) = view.withOverlay { - it.color = value - } - - @ReactProp(name = "outlineWidth") - override fun setOutlineWidth(view: RNCNaverMapCircle?, value: Double) = view.withOverlay { - it.outlineWidth = value.px - } - - @ReactProp(name = "outlineColor") - override fun setOutlineColor(view: RNCNaverMapCircle?, value: Int) = view.withOverlay { - it.outlineColor = value - } - - - // region PROPS - - companion object { - const val NAME = "RNCNaverMapCircle" + private fun RNCNaverMapCircle?.withOverlay(fn: (CircleOverlay) -> Unit) { + this?.overlay?.run(fn) + } + + @ReactProp(name = "coord") + override fun setCoord( + view: RNCNaverMapCircle?, + value: ReadableMap?, + ) = view.withOverlay { + value.getLatLng()?.run { + it.center = this } + } + + @ReactProp(name = "zIndexValue") + override fun setZIndexValue( + view: RNCNaverMapCircle?, + value: Int, + ) = view.withOverlay { + it.zIndex = value + } + + @ReactProp(name = "isHidden") + override fun setIsHidden( + view: RNCNaverMapCircle?, + value: Boolean, + ) = view.withOverlay { + it.isVisible = !value + } + + @ReactProp(name = "minZoom") + override fun setMinZoom( + view: RNCNaverMapCircle?, + value: Double, + ) = view.withOverlay { + it.minZoom = value + } + + @ReactProp(name = "maxZoom") + override fun setMaxZoom( + view: RNCNaverMapCircle?, + value: Double, + ) = view.withOverlay { + it.maxZoom = value + } + + @ReactProp(name = "isMinZoomInclusive") + override fun setIsMinZoomInclusive( + view: RNCNaverMapCircle?, + value: Boolean, + ) = view.withOverlay { + it.isMinZoomInclusive = value + } + + @ReactProp(name = "isMaxZoomInclusive") + override fun setIsMaxZoomInclusive( + view: RNCNaverMapCircle?, + value: Boolean, + ) = view.withOverlay { + it.isMaxZoomInclusive = value + } + + @ReactProp(name = "radius") + override fun setRadius( + view: RNCNaverMapCircle?, + value: Double, + ) = view.withOverlay { + it.radius = value + } + + @ReactProp(name = "color") + override fun setColor( + view: RNCNaverMapCircle?, + value: Int, + ) = view.withOverlay { + it.color = value + } + + @ReactProp(name = "outlineWidth") + override fun setOutlineWidth( + view: RNCNaverMapCircle?, + value: Double, + ) = view.withOverlay { + it.outlineWidth = value.px + } + + @ReactProp(name = "outlineColor") + override fun setOutlineColor( + view: RNCNaverMapCircle?, + value: Int, + ) = view.withOverlay { + it.outlineColor = value + } + + // region PROPS + + companion object { + const val NAME = "RNCNaverMapCircle" + } } diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/OverlayImages.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/OverlayImages.kt index 042ca494..38184718 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/OverlayImages.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/OverlayImages.kt @@ -3,14 +3,17 @@ package com.mjstudio.reactnativenavermap.overlay.marker import com.naver.maps.map.overlay.OverlayImage import java.util.concurrent.ConcurrentHashMap - object OverlayImages { - private val store: MutableMap = ConcurrentHashMap() - fun put(uri: String, image: OverlayImage) { - store[uri] = image - } + private val store: MutableMap = ConcurrentHashMap() + + fun put( + uri: String, + image: OverlayImage, + ) { + store[uri] = image + } - operator fun get(uri: String): OverlayImage? { - return store[uri] - } -} \ No newline at end of file + operator fun get(uri: String): OverlayImage? { + return store[uri] + } +} diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/RNCNaverMapMarker.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/RNCNaverMapMarker.kt index 04ce2c9c..8af6a8ab 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/RNCNaverMapMarker.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/RNCNaverMapMarker.kt @@ -8,15 +8,14 @@ import android.view.View import androidx.core.view.children import com.airbnb.android.react.maps.TrackableView import com.airbnb.android.react.maps.ViewChangesTracker -import com.facebook.drawee.drawable.ScalingUtils import com.facebook.drawee.generic.GenericDraweeHierarchy -import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder import com.facebook.drawee.view.DraweeHolder import com.facebook.react.bridge.ReadableMap import com.facebook.react.uimanager.ThemedReactContext import com.mjstudio.reactnativenavermap.event.NaverMapOverlayTapEvent import com.mjstudio.reactnativenavermap.overlay.RNCNaverMapOverlay import com.mjstudio.reactnativenavermap.util.ImageRequestCanceller +import com.mjstudio.reactnativenavermap.util.createDraweeHierarchy import com.mjstudio.reactnativenavermap.util.emitEvent import com.mjstudio.reactnativenavermap.util.getOverlayImage import com.naver.maps.map.NaverMap @@ -24,136 +23,137 @@ import com.naver.maps.map.overlay.Marker import com.naver.maps.map.overlay.OverlayImage import kotlin.math.max - @SuppressLint("ViewConstructor") class RNCNaverMapMarker(val reactContext: ThemedReactContext) : - RNCNaverMapOverlay(reactContext), TrackableView { - private var imageHolder: DraweeHolder? = null - private var customView: View? = null - private var customViewBitmap: Bitmap? = null - private var lastImage: ReadableMap? = null - private var imageRequestCanceller: ImageRequestCanceller? = null - private var isImageSetFromSubview = false - - init { - imageHolder = DraweeHolder.create(createDraweeHierarchy(), context)?.apply { - onAttach() - } - } - - override fun onDetachedFromWindow() { - imageRequestCanceller?.invoke() - super.onDetachedFromWindow() + RNCNaverMapOverlay(reactContext), TrackableView { + private val imageHolder: DraweeHolder? by lazy { + DraweeHolder.create(createDraweeHierarchy(resources), reactContext)?.apply { + onAttach() } - - override val overlay: Marker by lazy { - Marker().apply { - setOnClickListener { - reactContext.emitEvent(id) { surfaceId, reactTag -> - NaverMapOverlayTapEvent( - surfaceId, - reactTag - ) - } - true - } + } + private var customView: View? = null + private var customViewBitmap: Bitmap? = null + private var lastImage: ReadableMap? = null + private var imageRequestCanceller: ImageRequestCanceller? = null + private var isImageSetFromSubview = false + + override fun onDetachedFromWindow() { + imageRequestCanceller?.invoke() + super.onDetachedFromWindow() + } + + override val overlay: Marker by lazy { + Marker().apply { + setOnClickListener { + reactContext.emitEvent(id) { surfaceId, reactTag -> + NaverMapOverlayTapEvent( + surfaceId, + reactTag, + ) } + true + } } - - override fun addToMap(map: NaverMap) { - overlay.map = map - } - - override fun removeFromMap(map: NaverMap) { - overlay.map = null - } - - override fun onDropViewInstance() { - overlay.map = null - overlay.onClickListener = null - imageHolder?.onDetach() + } + + override fun addToMap(map: NaverMap) { + overlay.map = map + } + + override fun removeFromMap(map: NaverMap) { + overlay.map = null + } + + override fun onDropViewInstance() { + overlay.map = null + overlay.onClickListener = null + imageHolder?.onDetach() + } + + fun setCustomView( + view: View, + index: Int, + ) { + super.addView(view, index) + isImageSetFromSubview = true + if (view.layoutParams == null) { + view.setLayoutParams( + LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT, + ), + ) } - - fun setCustomView(view: View, index: Int) { - super.addView(view, index) - isImageSetFromSubview = true - if (view.layoutParams == null) { - view.setLayoutParams( - LayoutParams( - LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT - ) - ) - } - ViewChangesTracker.getInstance().addMarker(this) - customView = view - updateCustomView() + ViewChangesTracker.getInstance().addMarker(this) + customView = view + updateCustomView() + overlay.alpha = 1f + } + + fun removeCustomView(index: Int) { + customView = null + ViewChangesTracker.getInstance().removeMarker(this) + if (customViewBitmap != null && !customViewBitmap!!.isRecycled) customViewBitmap!!.recycle() + isImageSetFromSubview = false + setImage(lastImage) + super.removeView(children.elementAt(index)) + } + + override fun requestLayout() { + super.requestLayout() + if (childCount == 0 && customView != null) { + customView = null + updateCustomView() } - - fun removeCustomView(index: Int) { - customView = null - ViewChangesTracker.getInstance().removeMarker(this) - if (customViewBitmap != null && !customViewBitmap!!.isRecycled) customViewBitmap!!.recycle() - isImageSetFromSubview = false - setImage(lastImage) - super.removeView(children.elementAt(index)) + } + + private fun updateCustomView() { + if (customViewBitmap == null || customViewBitmap!!.isRecycled || + customViewBitmap?.getWidth() != overlay.width || + customViewBitmap?.getHeight() != overlay.height + ) { + customViewBitmap = + Bitmap.createBitmap( + max(1, overlay.width), + max(1, overlay.height), + Bitmap.Config.ARGB_4444, + ) } - - override fun requestLayout() { - super.requestLayout() - if (childCount == 0 && customView != null) { - customView = null - updateCustomView() - } - } - - private fun updateCustomView() { - if (customViewBitmap == null || customViewBitmap!!.isRecycled || - customViewBitmap?.getWidth() != overlay.width || - customViewBitmap?.getHeight() != overlay.height - ) { - customViewBitmap = Bitmap.createBitmap( - max(1, overlay.width), - max(1, overlay.height), - Bitmap.Config.ARGB_4444 - ) - } - if (customView != null) { - customViewBitmap?.also { bitmap -> - bitmap.eraseColor(Color.TRANSPARENT) - val canvas = Canvas(bitmap) - draw(canvas) - setOverlayImage(OverlayImage.fromBitmap(bitmap)) - } - } - } - - override fun updateCustomForTracking(): Boolean { - return true - } - - override fun update(width: Int, height: Int) { - updateCustomView(); - } - - fun setImage(image: ReadableMap?) { - lastImage = image - if (isImageSetFromSubview) return - imageRequestCanceller?.invoke() - imageRequestCanceller = getOverlayImage(imageHolder!!, context, image) { - setOverlayImage(it) - } - } - - private fun setOverlayImage(image: OverlayImage?) { - overlay.icon = - image ?: OverlayImage.fromBitmap(Bitmap.createBitmap(0, 0, Bitmap.Config.ARGB_8888)) - } - - private fun createDraweeHierarchy(): GenericDraweeHierarchy { - return GenericDraweeHierarchyBuilder(resources) - .setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER) - .setFadeDuration(0) - .build() + if (customView != null) { + customViewBitmap?.also { bitmap -> + bitmap.eraseColor(Color.TRANSPARENT) + val canvas = Canvas(bitmap) + draw(canvas) + setOverlayImage(OverlayImage.fromBitmap(bitmap)) + } } -} \ No newline at end of file + } + + override fun updateCustomForTracking(): Boolean { + return true + } + + override fun update( + width: Int, + height: Int, + ) { + updateCustomView() + } + + fun setImage(image: ReadableMap?) { + lastImage = image + if (isImageSetFromSubview) return + overlay.alpha = 0f + imageRequestCanceller?.invoke() + imageRequestCanceller = + getOverlayImage(imageHolder!!, context, image?.toHashMap()) { + setOverlayImage(it) + overlay.alpha = 1f + } + } + + private fun setOverlayImage(image: OverlayImage?) { + overlay.icon = + image ?: OverlayImage.fromBitmap(Bitmap.createBitmap(0, 0, Bitmap.Config.ARGB_8888)) + } +} diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/RNCNaverMapMarkerManager.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/RNCNaverMapMarkerManager.kt index 40e78a60..0c89c384 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/RNCNaverMapMarkerManager.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/RNCNaverMapMarkerManager.kt @@ -18,191 +18,257 @@ import com.mjstudio.reactnativenavermap.util.registerDirectEvent import com.naver.maps.map.overlay.Marker import com.naver.maps.map.overlay.Marker.SIZE_AUTO - class RNCNaverMapMarkerManager : RNCNaverMapMarkerManagerSpec() { - private var captionKey = DEFAULT_CAPTION_KEY - private var subCaptionKey = DEFAULT_CAPTION_KEY - - override fun getName(): String { - return NAME - } - - override fun createViewInstance(context: ThemedReactContext): RNCNaverMapMarker { - return RNCNaverMapMarker(context) - } - - override fun onDropViewInstance(view: RNCNaverMapMarker) { - super.onDropViewInstance(view) - view.onDropViewInstance() - } - - override fun getExportedCustomDirectEventTypeConstants(): MutableMap = - (super.getExportedCustomDirectEventTypeConstants() ?: mutableMapOf()).apply { - registerDirectEvent(this, NaverMapOverlayTapEvent.EVENT_NAME) - } - - private fun RNCNaverMapMarker?.withOverlay(fn: (Marker) -> Unit) { - this?.overlay?.run(fn) - } - - override fun addView(parent: RNCNaverMapMarker?, child: View, index: Int) { - parent?.setCustomView(child, index) - } - - override fun removeViewAt(parent: RNCNaverMapMarker?, index: Int) { - parent?.removeCustomView(index) - } - - @ReactProp(name = "coord") - override fun setCoord(view: RNCNaverMapMarker?, value: ReadableMap?) = view.withOverlay { - value.getLatLng()?.run { - it.position = this - } - } - - @ReactProp(name = "zIndexValue") - override fun setZIndexValue(view: RNCNaverMapMarker?, value: Int) = view.withOverlay { - it.zIndex = value - } - - @ReactProp(name = "isHidden") - override fun setIsHidden(view: RNCNaverMapMarker?, value: Boolean) = view.withOverlay { - it.isVisible = !value - } + private var captionKey = DEFAULT_CAPTION_KEY + private var subCaptionKey = DEFAULT_CAPTION_KEY - @ReactProp(name = "minZoom") - override fun setMinZoom(view: RNCNaverMapMarker?, value: Double) = view.withOverlay { - it.minZoom = value - } + override fun getName(): String { + return NAME + } - @ReactProp(name = "maxZoom") - override fun setMaxZoom(view: RNCNaverMapMarker?, value: Double) = view.withOverlay { - it.maxZoom = value - } + override fun createViewInstance(context: ThemedReactContext): RNCNaverMapMarker { + return RNCNaverMapMarker(context) + } - @ReactProp(name = "isMinZoomInclusive") - override fun setIsMinZoomInclusive(view: RNCNaverMapMarker?, value: Boolean) = - view.withOverlay { - it.isMinZoomInclusive = value - } - - @ReactProp(name = "isMaxZoomInclusive") - override fun setIsMaxZoomInclusive(view: RNCNaverMapMarker?, value: Boolean) = - view.withOverlay { - it.isMaxZoomInclusive = value - } - - @ReactProp(name = "width") - override fun setWidth(view: RNCNaverMapMarker?, value: Double) = view.withOverlay { - it.width = if (isValidNumber(value)) value.px else SIZE_AUTO - } + override fun onDropViewInstance(view: RNCNaverMapMarker) { + super.onDropViewInstance(view) + view.onDropViewInstance() + } - @ReactProp(name = "height") - override fun setHeight(view: RNCNaverMapMarker?, value: Double) = view.withOverlay { - it.height = if (isValidNumber(value)) value.px else SIZE_AUTO + override fun getExportedCustomDirectEventTypeConstants(): MutableMap = + (super.getExportedCustomDirectEventTypeConstants() ?: mutableMapOf()).apply { + registerDirectEvent(this, NaverMapOverlayTapEvent.EVENT_NAME) } - @ReactProp(name = "anchor") - override fun setAnchor(view: RNCNaverMapMarker?, value: ReadableMap?) = view.withOverlay { - value.getPoint()?.run { - it.anchor = this - } + private fun RNCNaverMapMarker?.withOverlay(fn: (Marker) -> Unit) { + this?.overlay?.run(fn) + } + + override fun addView( + parent: RNCNaverMapMarker?, + child: View, + index: Int, + ) { + parent?.setCustomView(child, index) + } + + override fun removeViewAt( + parent: RNCNaverMapMarker?, + index: Int, + ) { + parent?.removeCustomView(index) + } + + @ReactProp(name = "coord") + override fun setCoord( + view: RNCNaverMapMarker?, + value: ReadableMap?, + ) = view.withOverlay { + value.getLatLng()?.run { + it.position = this } - - @ReactProp(name = "angle") - override fun setAngle(view: RNCNaverMapMarker?, value: Double) = view.withOverlay { - it.angle = value.toFloat() + } + + @ReactProp(name = "zIndexValue") + override fun setZIndexValue( + view: RNCNaverMapMarker?, + value: Int, + ) = view.withOverlay { + it.zIndex = value + } + + @ReactProp(name = "isHidden") + override fun setIsHidden( + view: RNCNaverMapMarker?, + value: Boolean, + ) = view.withOverlay { + it.isVisible = !value + } + + @ReactProp(name = "minZoom") + override fun setMinZoom( + view: RNCNaverMapMarker?, + value: Double, + ) = view.withOverlay { + it.minZoom = value + } + + @ReactProp(name = "maxZoom") + override fun setMaxZoom( + view: RNCNaverMapMarker?, + value: Double, + ) = view.withOverlay { + it.maxZoom = value + } + + @ReactProp(name = "isMinZoomInclusive") + override fun setIsMinZoomInclusive( + view: RNCNaverMapMarker?, + value: Boolean, + ) = view.withOverlay { + it.isMinZoomInclusive = value + } + + @ReactProp(name = "isMaxZoomInclusive") + override fun setIsMaxZoomInclusive( + view: RNCNaverMapMarker?, + value: Boolean, + ) = view.withOverlay { + it.isMaxZoomInclusive = value + } + + @ReactProp(name = "width") + override fun setWidth( + view: RNCNaverMapMarker?, + value: Double, + ) = view.withOverlay { + it.width = if (isValidNumber(value)) value.px else SIZE_AUTO + } + + @ReactProp(name = "height") + override fun setHeight( + view: RNCNaverMapMarker?, + value: Double, + ) = view.withOverlay { + it.height = if (isValidNumber(value)) value.px else SIZE_AUTO + } + + @ReactProp(name = "anchor") + override fun setAnchor( + view: RNCNaverMapMarker?, + value: ReadableMap?, + ) = view.withOverlay { + value.getPoint()?.run { + it.anchor = this } - - @ReactProp(name = "isFlatEnabled") - override fun setIsFlatEnabled(view: RNCNaverMapMarker?, value: Boolean) = view.withOverlay { - it.isFlat = value + } + + @ReactProp(name = "angle") + override fun setAngle( + view: RNCNaverMapMarker?, + value: Double, + ) = view.withOverlay { + it.angle = value.toFloat() + } + + @ReactProp(name = "isFlatEnabled") + override fun setIsFlatEnabled( + view: RNCNaverMapMarker?, + value: Boolean, + ) = view.withOverlay { + it.isFlat = value + } + + @ReactProp(name = "isIconPerspectiveEnabled") + override fun setIsIconPerspectiveEnabled( + view: RNCNaverMapMarker?, + value: Boolean, + ) = view.withOverlay { + it.isIconPerspectiveEnabled = value + } + + @ReactProp(name = "alpha") + override fun setAlpha( + view: RNCNaverMapMarker?, + value: Double, + ) = view.withOverlay { + it.alpha = value.toFloat() + } + + @ReactProp(name = "isHideCollidedSymbols") + override fun setIsHideCollidedSymbols( + view: RNCNaverMapMarker?, + value: Boolean, + ) = view.withOverlay { + it.isHideCollidedSymbols = value + } + + @ReactProp(name = "isHideCollidedMarkers") + override fun setIsHideCollidedMarkers( + view: RNCNaverMapMarker?, + value: Boolean, + ) = view.withOverlay { + it.isHideCollidedMarkers = value + } + + @ReactProp(name = "isHideCollidedCaptions") + override fun setIsHideCollidedCaptions( + view: RNCNaverMapMarker?, + value: Boolean, + ) = view.withOverlay { + it.isHideCollidedCaptions = value + } + + @ReactProp(name = "isForceShowIcon") + override fun setIsForceShowIcon( + view: RNCNaverMapMarker?, + value: Boolean, + ) = view.withOverlay { + it.isForceShowIcon = value + } + + @ReactProp(name = "tintColor") + override fun setTintColor( + view: RNCNaverMapMarker?, + value: Int, + ) = view.withOverlay { + it.iconTintColor = value + } + + @ReactProp(name = "image") + override fun setImage( + view: RNCNaverMapMarker?, + value: ReadableMap?, + ) { + view?.setImage(value) + } + + @ReactProp(name = "caption") + override fun setCaption( + view: RNCNaverMapMarker?, + value: ReadableMap?, + ) = view.withOverlay { + value?.also { map -> + val key = map.getString("key") ?: DEFAULT_CAPTION_KEY + if (key == captionKey) return@also + captionKey = key + + it.captionText = map.getString("text") ?: "" + it.captionRequestedWidth = (map.getDoubleOrNull("requestedWidth") ?: .0).px + it.setCaptionAligns(map.getAlign("align")) + it.captionOffset = (map.getDoubleOrNull("offset") ?: .0).px + it.captionColor = map.getIntOrNull("color") ?: Color.BLACK + it.captionHaloColor = map.getIntOrNull("haloColor") ?: Color.TRANSPARENT + it.captionTextSize = map.getDouble("textSize").toFloat() + it.captionMinZoom = map.getDouble("minZoom") + it.captionMaxZoom = map.getDouble("maxZoom") } - - @ReactProp(name = "isIconPerspectiveEnabled") - override fun setIsIconPerspectiveEnabled(view: RNCNaverMapMarker?, value: Boolean) = - view.withOverlay { - it.isIconPerspectiveEnabled = value - } - - @ReactProp(name = "alpha") - override fun setAlpha(view: RNCNaverMapMarker?, value: Double) = view.withOverlay { - it.alpha = value.toFloat() + } + + @ReactProp(name = "subCaption") + override fun setSubCaption( + view: RNCNaverMapMarker?, + value: ReadableMap?, + ) = view.withOverlay { + value?.also { map -> + val key = map.getString("key") ?: DEFAULT_CAPTION_KEY + if (key == subCaptionKey) return@also + subCaptionKey = key + + it.subCaptionText = map.getString("text") ?: "" + it.subCaptionColor = map.getIntOrNull("color") ?: Color.BLACK + it.subCaptionHaloColor = map.getIntOrNull("haloColor") ?: Color.TRANSPARENT + it.subCaptionTextSize = map.getDouble("textSize").toFloat() + it.subCaptionRequestedWidth = map.getDouble("requestedWidth").px + it.subCaptionMinZoom = map.getDouble("minZoom") + it.subCaptionMaxZoom = map.getDouble("maxZoom") } + } - @ReactProp(name = "isHideCollidedSymbols") - override fun setIsHideCollidedSymbols(view: RNCNaverMapMarker?, value: Boolean) = - view.withOverlay { - it.isHideCollidedSymbols = value - } - - @ReactProp(name = "isHideCollidedMarkers") - override fun setIsHideCollidedMarkers(view: RNCNaverMapMarker?, value: Boolean) = - view.withOverlay { - it.isHideCollidedMarkers = value - } - - @ReactProp(name = "isHideCollidedCaptions") - override fun setIsHideCollidedCaptions(view: RNCNaverMapMarker?, value: Boolean) = - view.withOverlay { - it.isHideCollidedCaptions = value - } - - @ReactProp(name = "isForceShowIcon") - override fun setIsForceShowIcon(view: RNCNaverMapMarker?, value: Boolean) = view.withOverlay { - it.isForceShowIcon = value - } + // region PROPS - @ReactProp(name = "tintColor") - override fun setTintColor(view: RNCNaverMapMarker?, value: Int) = view.withOverlay { - it.iconTintColor = value - } - - @ReactProp(name = "image") - override fun setImage(view: RNCNaverMapMarker?, value: ReadableMap?) { - view?.setImage(value) - } - - @ReactProp(name = "caption") - override fun setCaption(view: RNCNaverMapMarker?, value: ReadableMap?) = view.withOverlay { - value?.also { map -> - val key = map.getString("key") ?: DEFAULT_CAPTION_KEY - if (key == captionKey) return@also - captionKey = key - - it.captionText = map.getString("text") ?: "" - it.captionRequestedWidth = (map.getDoubleOrNull("requestedWidth") ?: .0).px - it.setCaptionAligns(map.getAlign("align")) - it.captionOffset = (map.getDoubleOrNull("offset") ?: .0).px - it.captionColor = map.getIntOrNull("color") ?: Color.BLACK - it.captionHaloColor = map.getIntOrNull("haloColor") ?: Color.TRANSPARENT - it.captionTextSize = map.getDouble("textSize").toFloat() - it.captionMinZoom = map.getDouble("minZoom") - it.captionMaxZoom = map.getDouble("maxZoom") - } - } - - @ReactProp(name = "subCaption") - override fun setSubCaption(view: RNCNaverMapMarker?, value: ReadableMap?) = view.withOverlay { - value?.also { map -> - val key = map.getString("key") ?: DEFAULT_CAPTION_KEY - if (key == subCaptionKey) return@also - subCaptionKey = key - - it.subCaptionText = map.getString("text") ?: "" - it.subCaptionColor = map.getIntOrNull("color") ?: Color.BLACK - it.subCaptionHaloColor = map.getIntOrNull("haloColor") ?: Color.TRANSPARENT - it.subCaptionTextSize = map.getDouble("textSize").toFloat() - it.subCaptionRequestedWidth = map.getDouble("requestedWidth").px - it.subCaptionMinZoom = map.getDouble("minZoom") - it.subCaptionMaxZoom = map.getDouble("maxZoom") - } - } - - // region PROPS - - companion object { - const val NAME = "RNCNaverMapMarker" - const val DEFAULT_CAPTION_KEY = "DEFAULT" - } + companion object { + const val NAME = "RNCNaverMapMarker" + const val DEFAULT_CAPTION_KEY = "DEFAULT" + } } diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/cluster/RNCNaverMapClusterKey.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/cluster/RNCNaverMapClusterKey.kt new file mode 100644 index 00000000..7e177c6b --- /dev/null +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/cluster/RNCNaverMapClusterKey.kt @@ -0,0 +1,17 @@ +package com.mjstudio.reactnativenavermap.overlay.marker.cluster + +import com.naver.maps.geometry.LatLng +import com.naver.maps.map.clustering.ClusteringKey + +internal data class RNCNaverMapClusterKey( + val identifier: String, + val latlng: LatLng, + val image: Map<*, *>? = null, + val width: Double?, + val height: Double?, + val holder: RNCNaverMapLeafMarkerHolder, +) : ClusteringKey { + override fun getPosition(): LatLng { + return latlng + } +} diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/cluster/RNCNaverMapClustererHolder.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/cluster/RNCNaverMapClustererHolder.kt new file mode 100644 index 00000000..b3739068 --- /dev/null +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/cluster/RNCNaverMapClustererHolder.kt @@ -0,0 +1,26 @@ +package com.mjstudio.reactnativenavermap.overlay.marker.cluster + +import com.facebook.drawee.generic.GenericDraweeHierarchy +import com.facebook.drawee.view.DraweeHolder +import com.facebook.react.bridge.ReactApplicationContext +import com.mjstudio.reactnativenavermap.util.createDraweeHierarchy +import com.naver.maps.map.clustering.Clusterer + +internal data class RNCNaverMapClustererHolder internal constructor( + val identifier: String, + val clusterer: Clusterer, + val context: ReactApplicationContext, + val markers: List, +) { + private val imageHolder: DraweeHolder by lazy { + DraweeHolder.create(createDraweeHierarchy(context.resources), context).apply { + onAttach() + } + } + + fun onDetach() { + markers.forEach { it.onDetach() } + clusterer.map = null + imageHolder.onDetach() + } +} diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/cluster/RNCNaverMapLeafMarkerHolder.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/cluster/RNCNaverMapLeafMarkerHolder.kt new file mode 100644 index 00000000..7b2d2edd --- /dev/null +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/cluster/RNCNaverMapLeafMarkerHolder.kt @@ -0,0 +1,21 @@ +package com.mjstudio.reactnativenavermap.overlay.marker.cluster + +import com.facebook.drawee.generic.GenericDraweeHierarchy +import com.facebook.drawee.view.DraweeHolder +import com.facebook.react.bridge.ReactApplicationContext +import com.mjstudio.reactnativenavermap.util.createDraweeHierarchy + +internal data class RNCNaverMapLeafMarkerHolder( + val identifier: String, + val context: ReactApplicationContext, +) { + val imageHolder: DraweeHolder by lazy { + DraweeHolder.create(createDraweeHierarchy(context.resources), context).apply { + onAttach() + } + } + + fun onDetach() { + imageHolder.onDetach() + } +} diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/cluster/RNCNaverMapLeafMarkerUpdater.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/cluster/RNCNaverMapLeafMarkerUpdater.kt new file mode 100644 index 00000000..13bc6fd4 --- /dev/null +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/marker/cluster/RNCNaverMapLeafMarkerUpdater.kt @@ -0,0 +1,45 @@ +package com.mjstudio.reactnativenavermap.overlay.marker.cluster + +import com.mjstudio.reactnativenavermap.util.ImageRequestCanceller +import com.mjstudio.reactnativenavermap.util.getOverlayImage +import com.mjstudio.reactnativenavermap.util.px +import com.naver.maps.map.clustering.DefaultLeafMarkerUpdater +import com.naver.maps.map.clustering.LeafMarkerInfo +import com.naver.maps.map.overlay.Marker +import com.naver.maps.map.util.MarkerIcons + +internal class RNCNaverMapLeafMarkerUpdater : DefaultLeafMarkerUpdater() { + private var imageRequestCanceller: ImageRequestCanceller? = null + + override fun updateLeafMarker( + info: LeafMarkerInfo, + marker: Marker, + ) { + super.updateLeafMarker(info, marker) + + imageRequestCanceller?.invoke() + (info.key as? RNCNaverMapClusterKey)?.let { + ( + id, _, + image, width, height, holder, + ), + -> + if (width != null) { + marker.width = width.px + } + if (height != null) { + marker.height = height.px + } + if (image != null) { + marker.alpha = 0f + imageRequestCanceller = + getOverlayImage(holder.imageHolder, holder.context, image) { + marker.icon = it ?: MarkerIcons.GREEN + marker.alpha = 1f + } + } else { + marker.alpha = 1f + } + } + } +} diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/path/RNCNaverMapPath.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/path/RNCNaverMapPath.kt index d49d74b1..f86620e2 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/path/RNCNaverMapPath.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/path/RNCNaverMapPath.kt @@ -10,31 +10,31 @@ import com.naver.maps.map.overlay.PathOverlay @SuppressLint("ViewConstructor") class RNCNaverMapPath(val reactContext: ThemedReactContext) : - RNCNaverMapOverlay(reactContext) { - override val overlay: PathOverlay by lazy { - PathOverlay().apply { - setOnClickListener { - reactContext.emitEvent(id) { surfaceId, reactTag -> - NaverMapOverlayTapEvent( - surfaceId, - reactTag - ) - } - true - } + RNCNaverMapOverlay(reactContext) { + override val overlay: PathOverlay by lazy { + PathOverlay().apply { + setOnClickListener { + reactContext.emitEvent(id) { surfaceId, reactTag -> + NaverMapOverlayTapEvent( + surfaceId, + reactTag, + ) } + true + } } + } - override fun addToMap(map: NaverMap) { - overlay.map = map - } + override fun addToMap(map: NaverMap) { + overlay.map = map + } - override fun removeFromMap(map: NaverMap) { - overlay.map = null - } + override fun removeFromMap(map: NaverMap) { + overlay.map = null + } - override fun onDropViewInstance() { - overlay.map = null - overlay.onClickListener = null - } -} \ No newline at end of file + override fun onDropViewInstance() { + overlay.map = null + overlay.onClickListener = null + } +} diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/path/RNCNaverMapPathManager.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/path/RNCNaverMapPathManager.kt index 19cf4693..fc9bd07e 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/path/RNCNaverMapPathManager.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/path/RNCNaverMapPathManager.kt @@ -10,135 +10,179 @@ import com.mjstudio.reactnativenavermap.util.px import com.mjstudio.reactnativenavermap.util.registerDirectEvent import com.naver.maps.map.overlay.PathOverlay - class RNCNaverMapPathManager : RNCNaverMapPathManagerSpec() { - override fun getName(): String { - return NAME - } - - override fun createViewInstance(context: ThemedReactContext): RNCNaverMapPath { - return RNCNaverMapPath(context) - } - - override fun onDropViewInstance(view: RNCNaverMapPath) { - super.onDropViewInstance(view) - view.onDropViewInstance() - } - - override fun getExportedCustomDirectEventTypeConstants(): MutableMap = - (super.getExportedCustomDirectEventTypeConstants() ?: mutableMapOf()).apply { - registerDirectEvent(this, NaverMapOverlayTapEvent.EVENT_NAME) - } - - - private fun RNCNaverMapPath?.withOverlay(fn: (PathOverlay) -> Unit) { - this?.overlay?.run(fn) - } - - @ReactProp(name = "zIndexValue") - override fun setZIndexValue(view: RNCNaverMapPath?, value: Int) = view.withOverlay { - it.zIndex = value - } - - @ReactProp(name = "isHidden") - override fun setIsHidden(view: RNCNaverMapPath?, value: Boolean) = view.withOverlay { - it.isVisible = !value - } - - @ReactProp(name = "minZoom") - override fun setMinZoom(view: RNCNaverMapPath?, value: Double) = view.withOverlay { - it.minZoom = value - } - - @ReactProp(name = "maxZoom") - override fun setMaxZoom(view: RNCNaverMapPath?, value: Double) = view.withOverlay { - it.maxZoom = value - } - - @ReactProp(name = "isMinZoomInclusive") - override fun setIsMinZoomInclusive(view: RNCNaverMapPath?, value: Boolean) = - view.withOverlay { - it.isMinZoomInclusive = value - } - - @ReactProp(name = "isMaxZoomInclusive") - override fun setIsMaxZoomInclusive(view: RNCNaverMapPath?, value: Boolean) = - view.withOverlay { - it.isMaxZoomInclusive = value - } - - - @Suppress("UNCHECKED_CAST") - @ReactProp(name = "coords") - override fun setCoords(view: RNCNaverMapPath?, value: ReadableArray?) = view.withOverlay { - it.coords = value?.toArrayList()?.map { coord -> - (coord as Map).getLatLng() - } ?: listOf() - } - - @ReactProp(name = "width") - override fun setWidth(view: RNCNaverMapPath?, value: Double) = view.withOverlay { - it.width = value.px - } - - @ReactProp(name = "outlineWidth") - override fun setOutlineWidth(view: RNCNaverMapPath?, value: Double) = view.withOverlay { - it.outlineWidth = value.px - } - - @ReactProp(name = "passedOutlineColor") - override fun setPassedOutlineColor(view: RNCNaverMapPath?, value: Int) = view.withOverlay { - it.passedOutlineColor = value - } - - - @ReactProp(name = "color") - override fun setColor(view: RNCNaverMapPath?, value: Int) = view.withOverlay { - it.color = value - } - - @ReactProp(name = "passedColor") - override fun setPassedColor(view: RNCNaverMapPath?, value: Int) = view.withOverlay { - it.passedColor = value - } - - - @ReactProp(name = "pattern") - override fun setPatternInterval(view: RNCNaverMapPath?, value: Int) = view.withOverlay { - it.patternInterval = value - } - - @ReactProp(name = "progress") - override fun setProgress(view: RNCNaverMapPath?, value: Double) = view.withOverlay { - it.progress = value - } - - @ReactProp(name = "outlineColor") - override fun setOutlineColor(view: RNCNaverMapPath?, value: Int) = view.withOverlay { - it.outlineColor = value - } - - @ReactProp(name = "isHideCollidedSymbols") - override fun setIsHideCollidedSymbols(view: RNCNaverMapPath?, value: Boolean) = - view.withOverlay { - it.isHideCollidedSymbols = value - } - - @ReactProp(name = "isHideCollidedMarkers") - override fun setIsHideCollidedMarkers(view: RNCNaverMapPath?, value: Boolean) = - view.withOverlay { - it.isHideCollidedMarkers = value - } - - @ReactProp(name = "isHideCollidedCaptions") - override fun setIsHideCollidedCaptions(view: RNCNaverMapPath?, value: Boolean) = - view.withOverlay { - it.isHideCollidedCaptions = value - } - - // region PROPS - - companion object { - const val NAME = "RNCNaverMapPath" - } + override fun getName(): String { + return NAME + } + + override fun createViewInstance(context: ThemedReactContext): RNCNaverMapPath { + return RNCNaverMapPath(context) + } + + override fun onDropViewInstance(view: RNCNaverMapPath) { + super.onDropViewInstance(view) + view.onDropViewInstance() + } + + override fun getExportedCustomDirectEventTypeConstants(): MutableMap = + (super.getExportedCustomDirectEventTypeConstants() ?: mutableMapOf()).apply { + registerDirectEvent(this, NaverMapOverlayTapEvent.EVENT_NAME) + } + + private fun RNCNaverMapPath?.withOverlay(fn: (PathOverlay) -> Unit) { + this?.overlay?.run(fn) + } + + @ReactProp(name = "zIndexValue") + override fun setZIndexValue( + view: RNCNaverMapPath?, + value: Int, + ) = view.withOverlay { + it.zIndex = value + } + + @ReactProp(name = "isHidden") + override fun setIsHidden( + view: RNCNaverMapPath?, + value: Boolean, + ) = view.withOverlay { + it.isVisible = !value + } + + @ReactProp(name = "minZoom") + override fun setMinZoom( + view: RNCNaverMapPath?, + value: Double, + ) = view.withOverlay { + it.minZoom = value + } + + @ReactProp(name = "maxZoom") + override fun setMaxZoom( + view: RNCNaverMapPath?, + value: Double, + ) = view.withOverlay { + it.maxZoom = value + } + + @ReactProp(name = "isMinZoomInclusive") + override fun setIsMinZoomInclusive( + view: RNCNaverMapPath?, + value: Boolean, + ) = view.withOverlay { + it.isMinZoomInclusive = value + } + + @ReactProp(name = "isMaxZoomInclusive") + override fun setIsMaxZoomInclusive( + view: RNCNaverMapPath?, + value: Boolean, + ) = view.withOverlay { + it.isMaxZoomInclusive = value + } + + @Suppress("UNCHECKED_CAST") + @ReactProp(name = "coords") + override fun setCoords( + view: RNCNaverMapPath?, + value: ReadableArray?, + ) = view.withOverlay { + it.coords = value?.toArrayList()?.map { coord -> + (coord as Map).getLatLng() + } ?: listOf() + } + + @ReactProp(name = "width") + override fun setWidth( + view: RNCNaverMapPath?, + value: Double, + ) = view.withOverlay { + it.width = value.px + } + + @ReactProp(name = "outlineWidth") + override fun setOutlineWidth( + view: RNCNaverMapPath?, + value: Double, + ) = view.withOverlay { + it.outlineWidth = value.px + } + + @ReactProp(name = "passedOutlineColor") + override fun setPassedOutlineColor( + view: RNCNaverMapPath?, + value: Int, + ) = view.withOverlay { + it.passedOutlineColor = value + } + + @ReactProp(name = "color") + override fun setColor( + view: RNCNaverMapPath?, + value: Int, + ) = view.withOverlay { + it.color = value + } + + @ReactProp(name = "passedColor") + override fun setPassedColor( + view: RNCNaverMapPath?, + value: Int, + ) = view.withOverlay { + it.passedColor = value + } + + @ReactProp(name = "pattern") + override fun setPatternInterval( + view: RNCNaverMapPath?, + value: Int, + ) = view.withOverlay { + it.patternInterval = value + } + + @ReactProp(name = "progress") + override fun setProgress( + view: RNCNaverMapPath?, + value: Double, + ) = view.withOverlay { + it.progress = value + } + + @ReactProp(name = "outlineColor") + override fun setOutlineColor( + view: RNCNaverMapPath?, + value: Int, + ) = view.withOverlay { + it.outlineColor = value + } + + @ReactProp(name = "isHideCollidedSymbols") + override fun setIsHideCollidedSymbols( + view: RNCNaverMapPath?, + value: Boolean, + ) = view.withOverlay { + it.isHideCollidedSymbols = value + } + + @ReactProp(name = "isHideCollidedMarkers") + override fun setIsHideCollidedMarkers( + view: RNCNaverMapPath?, + value: Boolean, + ) = view.withOverlay { + it.isHideCollidedMarkers = value + } + + @ReactProp(name = "isHideCollidedCaptions") + override fun setIsHideCollidedCaptions( + view: RNCNaverMapPath?, + value: Boolean, + ) = view.withOverlay { + it.isHideCollidedCaptions = value + } + + // region PROPS + + companion object { + const val NAME = "RNCNaverMapPath" + } } diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/polygon/RNCNaverMapPolygon.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/polygon/RNCNaverMapPolygon.kt index 936a0f48..4bdb8d9c 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/polygon/RNCNaverMapPolygon.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/polygon/RNCNaverMapPolygon.kt @@ -10,31 +10,31 @@ import com.naver.maps.map.overlay.PolygonOverlay @SuppressLint("ViewConstructor") class RNCNaverMapPolygon(val reactContext: ThemedReactContext) : - RNCNaverMapOverlay(reactContext) { - override val overlay: PolygonOverlay by lazy { - PolygonOverlay().apply { - setOnClickListener { - reactContext.emitEvent(id) { surfaceId, reactTag -> - NaverMapOverlayTapEvent( - surfaceId, - reactTag - ) - } - true - } + RNCNaverMapOverlay(reactContext) { + override val overlay: PolygonOverlay by lazy { + PolygonOverlay().apply { + setOnClickListener { + reactContext.emitEvent(id) { surfaceId, reactTag -> + NaverMapOverlayTapEvent( + surfaceId, + reactTag, + ) } + true + } } + } - override fun addToMap(map: NaverMap) { - overlay.map = map - } + override fun addToMap(map: NaverMap) { + overlay.map = map + } - override fun removeFromMap(map: NaverMap) { - overlay.map = null - } + override fun removeFromMap(map: NaverMap) { + overlay.map = null + } - override fun onDropViewInstance() { - overlay.map = null - overlay.onClickListener = null - } -} \ No newline at end of file + override fun onDropViewInstance() { + overlay.map = null + overlay.onClickListener = null + } +} diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/polygon/RNCNaverMapPolygonManager.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/polygon/RNCNaverMapPolygonManager.kt index 5df53617..0ebb6293 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/polygon/RNCNaverMapPolygonManager.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/polygon/RNCNaverMapPolygonManager.kt @@ -10,102 +10,126 @@ import com.mjstudio.reactnativenavermap.util.px import com.mjstudio.reactnativenavermap.util.registerDirectEvent import com.naver.maps.map.overlay.PolygonOverlay - class RNCNaverMapPolygonManager : RNCNaverMapPolygonManagerSpec() { - override fun getName(): String { - return NAME - } - - override fun createViewInstance(context: ThemedReactContext): RNCNaverMapPolygon { - return RNCNaverMapPolygon(context) - } - - override fun onDropViewInstance(view: RNCNaverMapPolygon) { - super.onDropViewInstance(view) - view.onDropViewInstance() - } - - override fun getExportedCustomDirectEventTypeConstants(): MutableMap = - (super.getExportedCustomDirectEventTypeConstants() ?: mutableMapOf()).apply { - registerDirectEvent(this, NaverMapOverlayTapEvent.EVENT_NAME) - } - - - private fun RNCNaverMapPolygon?.withOverlay(fn: (PolygonOverlay) -> Unit) { - this?.overlay?.run(fn) - } - - @ReactProp(name = "zIndexValue") - override fun setZIndexValue(view: RNCNaverMapPolygon?, value: Int) = view.withOverlay { - it.zIndex = value - } - - @ReactProp(name = "isHidden") - override fun setIsHidden(view: RNCNaverMapPolygon?, value: Boolean) = view.withOverlay { - it.isVisible = !value - } - - @ReactProp(name = "minZoom") - override fun setMinZoom(view: RNCNaverMapPolygon?, value: Double) = view.withOverlay { - it.minZoom = value - } - - @ReactProp(name = "maxZoom") - override fun setMaxZoom(view: RNCNaverMapPolygon?, value: Double) = view.withOverlay { - it.maxZoom = value + override fun getName(): String { + return NAME + } + + override fun createViewInstance(context: ThemedReactContext): RNCNaverMapPolygon { + return RNCNaverMapPolygon(context) + } + + override fun onDropViewInstance(view: RNCNaverMapPolygon) { + super.onDropViewInstance(view) + view.onDropViewInstance() + } + + override fun getExportedCustomDirectEventTypeConstants(): MutableMap = + (super.getExportedCustomDirectEventTypeConstants() ?: mutableMapOf()).apply { + registerDirectEvent(this, NaverMapOverlayTapEvent.EVENT_NAME) } - @ReactProp(name = "isMinZoomInclusive") - override fun setIsMinZoomInclusive(view: RNCNaverMapPolygon?, value: Boolean) = - view.withOverlay { - it.isMinZoomInclusive = value - } - - @ReactProp(name = "isMaxZoomInclusive") - override fun setIsMaxZoomInclusive(view: RNCNaverMapPolygon?, value: Boolean) = - view.withOverlay { - it.isMaxZoomInclusive = value - } - - - @Suppress("UNCHECKED_CAST") - @ReactProp(name = "geometries") - override fun setGeometries(view: RNCNaverMapPolygon?, value: ReadableMap?) = view.withOverlay { - it.coords = value?.getArray("coords")?.toArrayList()?.map { coord -> + private fun RNCNaverMapPolygon?.withOverlay(fn: (PolygonOverlay) -> Unit) { + this?.overlay?.run(fn) + } + + @ReactProp(name = "zIndexValue") + override fun setZIndexValue( + view: RNCNaverMapPolygon?, + value: Int, + ) = view.withOverlay { + it.zIndex = value + } + + @ReactProp(name = "isHidden") + override fun setIsHidden( + view: RNCNaverMapPolygon?, + value: Boolean, + ) = view.withOverlay { + it.isVisible = !value + } + + @ReactProp(name = "minZoom") + override fun setMinZoom( + view: RNCNaverMapPolygon?, + value: Double, + ) = view.withOverlay { + it.minZoom = value + } + + @ReactProp(name = "maxZoom") + override fun setMaxZoom( + view: RNCNaverMapPolygon?, + value: Double, + ) = view.withOverlay { + it.maxZoom = value + } + + @ReactProp(name = "isMinZoomInclusive") + override fun setIsMinZoomInclusive( + view: RNCNaverMapPolygon?, + value: Boolean, + ) = view.withOverlay { + it.isMinZoomInclusive = value + } + + @ReactProp(name = "isMaxZoomInclusive") + override fun setIsMaxZoomInclusive( + view: RNCNaverMapPolygon?, + value: Boolean, + ) = view.withOverlay { + it.isMaxZoomInclusive = value + } + + @Suppress("UNCHECKED_CAST") + @ReactProp(name = "geometries") + override fun setGeometries( + view: RNCNaverMapPolygon?, + value: ReadableMap?, + ) = view.withOverlay { + it.coords = value?.getArray("coords")?.toArrayList()?.map { coord -> + (coord as Map).getLatLng() + } ?: listOf() + + value?.getArray("holes")?.toArrayList()?.run { + it.holes = + this.filter { hole -> + (hole is List<*>) && hole.size >= 3 + }.map { hole -> + (hole as List<*>).map { coord -> (coord as Map).getLatLng() - } ?: listOf() - - value?.getArray("holes")?.toArrayList()?.run { - it.holes = this.filter { hole -> - (hole is List<*>) && hole.size >= 3 - }.map { hole -> - (hole as List<*>).map { coord -> - (coord as Map).getLatLng() - } - } + } } } - - - @ReactProp(name = "color") - override fun setColor(view: RNCNaverMapPolygon?, value: Int) = view.withOverlay { - it.color = value - } - - @ReactProp(name = "outlineWidth") - override fun setOutlineWidth(view: RNCNaverMapPolygon?, value: Double) = view.withOverlay { - it.outlineWidth = value.px - } - - @ReactProp(name = "outlineColor") - override fun setOutlineColor(view: RNCNaverMapPolygon?, value: Int) = view.withOverlay { - it.outlineColor = value - } - - - // region PROPS - - companion object { - const val NAME = "RNCNaverMapPolygon" - } + } + + @ReactProp(name = "color") + override fun setColor( + view: RNCNaverMapPolygon?, + value: Int, + ) = view.withOverlay { + it.color = value + } + + @ReactProp(name = "outlineWidth") + override fun setOutlineWidth( + view: RNCNaverMapPolygon?, + value: Double, + ) = view.withOverlay { + it.outlineWidth = value.px + } + + @ReactProp(name = "outlineColor") + override fun setOutlineColor( + view: RNCNaverMapPolygon?, + value: Int, + ) = view.withOverlay { + it.outlineColor = value + } + + // region PROPS + + companion object { + const val NAME = "RNCNaverMapPolygon" + } } diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/polyline/RNCNaverMapPolyline.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/polyline/RNCNaverMapPolyline.kt index 19b08fe1..6126dc74 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/polyline/RNCNaverMapPolyline.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/polyline/RNCNaverMapPolyline.kt @@ -10,31 +10,31 @@ import com.naver.maps.map.overlay.PolylineOverlay @SuppressLint("ViewConstructor") class RNCNaverMapPolyline(val reactContext: ThemedReactContext) : - RNCNaverMapOverlay(reactContext) { - override val overlay: PolylineOverlay by lazy { - PolylineOverlay().apply { - setOnClickListener { - reactContext.emitEvent(id) { surfaceId, reactTag -> - NaverMapOverlayTapEvent( - surfaceId, - reactTag - ) - } - true - } + RNCNaverMapOverlay(reactContext) { + override val overlay: PolylineOverlay by lazy { + PolylineOverlay().apply { + setOnClickListener { + reactContext.emitEvent(id) { surfaceId, reactTag -> + NaverMapOverlayTapEvent( + surfaceId, + reactTag, + ) } + true + } } + } - override fun addToMap(map: NaverMap) { - overlay.map = map - } + override fun addToMap(map: NaverMap) { + overlay.map = map + } - override fun removeFromMap(map: NaverMap) { - overlay.map = null - } + override fun removeFromMap(map: NaverMap) { + overlay.map = null + } - override fun onDropViewInstance() { - overlay.map = null - overlay.onClickListener = null - } -} \ No newline at end of file + override fun onDropViewInstance() { + overlay.map = null + overlay.onClickListener = null + } +} diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/polyline/RNCNaverMapPolylineManager.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/polyline/RNCNaverMapPolylineManager.kt index 5212d819..1dce642b 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/polyline/RNCNaverMapPolylineManager.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/overlay/polyline/RNCNaverMapPolylineManager.kt @@ -10,112 +10,143 @@ import com.mjstudio.reactnativenavermap.util.px import com.mjstudio.reactnativenavermap.util.registerDirectEvent import com.naver.maps.map.overlay.PolylineOverlay - class RNCNaverMapPolylineManager : RNCNaverMapPolylineManagerSpec() { - override fun getName(): String { - return NAME - } - - override fun createViewInstance(context: ThemedReactContext): RNCNaverMapPolyline { - return RNCNaverMapPolyline(context) - } - - override fun onDropViewInstance(view: RNCNaverMapPolyline) { - super.onDropViewInstance(view) - view.onDropViewInstance() - } - - override fun getExportedCustomDirectEventTypeConstants(): MutableMap = - (super.getExportedCustomDirectEventTypeConstants() ?: mutableMapOf()).apply { - registerDirectEvent(this, NaverMapOverlayTapEvent.EVENT_NAME) - } - - - private fun RNCNaverMapPolyline?.withOverlay(fn: (PolylineOverlay) -> Unit) { - this?.overlay?.run(fn) - } - - @ReactProp(name = "zIndexValue") - override fun setZIndexValue(view: RNCNaverMapPolyline?, value: Int) = view.withOverlay { - it.zIndex = value - } - - @ReactProp(name = "isHidden") - override fun setIsHidden(view: RNCNaverMapPolyline?, value: Boolean) = view.withOverlay { - it.isVisible = !value - } - - @ReactProp(name = "minZoom") - override fun setMinZoom(view: RNCNaverMapPolyline?, value: Double) = view.withOverlay { - it.minZoom = value - } - - @ReactProp(name = "maxZoom") - override fun setMaxZoom(view: RNCNaverMapPolyline?, value: Double) = view.withOverlay { - it.maxZoom = value - } - - @ReactProp(name = "isMinZoomInclusive") - override fun setIsMinZoomInclusive(view: RNCNaverMapPolyline?, value: Boolean) = - view.withOverlay { - it.isMinZoomInclusive = value - } - - @ReactProp(name = "isMaxZoomInclusive") - override fun setIsMaxZoomInclusive(view: RNCNaverMapPolyline?, value: Boolean) = - view.withOverlay { - it.isMaxZoomInclusive = value - } - - - @Suppress("UNCHECKED_CAST") - @ReactProp(name = "coords") - override fun setCoords(view: RNCNaverMapPolyline?, value: ReadableArray?) = view.withOverlay { - it.coords = value?.toArrayList()?.map { coord -> - (coord as Map).getLatLng() - } ?: listOf() - } - - @ReactProp(name = "width") - override fun setWidth(view: RNCNaverMapPolyline?, value: Double) = view.withOverlay { - it.width = value.px - } - - - @ReactProp(name = "color") - override fun setColor(view: RNCNaverMapPolyline?, value: Int) = view.withOverlay { - it.color = value + override fun getName(): String { + return NAME + } + + override fun createViewInstance(context: ThemedReactContext): RNCNaverMapPolyline { + return RNCNaverMapPolyline(context) + } + + override fun onDropViewInstance(view: RNCNaverMapPolyline) { + super.onDropViewInstance(view) + view.onDropViewInstance() + } + + override fun getExportedCustomDirectEventTypeConstants(): MutableMap = + (super.getExportedCustomDirectEventTypeConstants() ?: mutableMapOf()).apply { + registerDirectEvent(this, NaverMapOverlayTapEvent.EVENT_NAME) } - @ReactProp(name = "pattern") - override fun setPattern(view: RNCNaverMapPolyline?, value: ReadableArray?) = view.withOverlay { - (value?.toArrayList() as? List<*>)?.run { - it.setPattern(*this.map { (it as Number).toInt() }.toIntArray()) - } - } - - @ReactProp(name = "capType") - override fun setCapType(view: RNCNaverMapPolyline?, value: String?) = view.withOverlay { - it.capType = when (value) { - "Butt" -> PolylineOverlay.LineCap.Butt - "Square" -> PolylineOverlay.LineCap.Square - else -> PolylineOverlay.LineCap.Round - } - } - - @ReactProp(name = "joinType") - override fun setJoinType(view: RNCNaverMapPolyline?, value: String?) = view.withOverlay { - it.joinType = when (value) { - "Bevel" -> PolylineOverlay.LineJoin.Bevel - "Miter" -> PolylineOverlay.LineJoin.Miter - else -> PolylineOverlay.LineJoin.Round - } - } - - - // region PROPS - - companion object { - const val NAME = "RNCNaverMapPolyline" + private fun RNCNaverMapPolyline?.withOverlay(fn: (PolylineOverlay) -> Unit) { + this?.overlay?.run(fn) + } + + @ReactProp(name = "zIndexValue") + override fun setZIndexValue( + view: RNCNaverMapPolyline?, + value: Int, + ) = view.withOverlay { + it.zIndex = value + } + + @ReactProp(name = "isHidden") + override fun setIsHidden( + view: RNCNaverMapPolyline?, + value: Boolean, + ) = view.withOverlay { + it.isVisible = !value + } + + @ReactProp(name = "minZoom") + override fun setMinZoom( + view: RNCNaverMapPolyline?, + value: Double, + ) = view.withOverlay { + it.minZoom = value + } + + @ReactProp(name = "maxZoom") + override fun setMaxZoom( + view: RNCNaverMapPolyline?, + value: Double, + ) = view.withOverlay { + it.maxZoom = value + } + + @ReactProp(name = "isMinZoomInclusive") + override fun setIsMinZoomInclusive( + view: RNCNaverMapPolyline?, + value: Boolean, + ) = view.withOverlay { + it.isMinZoomInclusive = value + } + + @ReactProp(name = "isMaxZoomInclusive") + override fun setIsMaxZoomInclusive( + view: RNCNaverMapPolyline?, + value: Boolean, + ) = view.withOverlay { + it.isMaxZoomInclusive = value + } + + @Suppress("UNCHECKED_CAST") + @ReactProp(name = "coords") + override fun setCoords( + view: RNCNaverMapPolyline?, + value: ReadableArray?, + ) = view.withOverlay { + it.coords = value?.toArrayList()?.map { coord -> + (coord as Map).getLatLng() + } ?: listOf() + } + + @ReactProp(name = "width") + override fun setWidth( + view: RNCNaverMapPolyline?, + value: Double, + ) = view.withOverlay { + it.width = value.px + } + + @ReactProp(name = "color") + override fun setColor( + view: RNCNaverMapPolyline?, + value: Int, + ) = view.withOverlay { + it.color = value + } + + @ReactProp(name = "pattern") + override fun setPattern( + view: RNCNaverMapPolyline?, + value: ReadableArray?, + ) = view.withOverlay { + (value?.toArrayList() as? List<*>)?.run { + it.setPattern(*this.map { (it as Number).toInt() }.toIntArray()) } + } + + @ReactProp(name = "capType") + override fun setCapType( + view: RNCNaverMapPolyline?, + value: String?, + ) = view.withOverlay { + it.capType = + when (value) { + "Butt" -> PolylineOverlay.LineCap.Butt + "Square" -> PolylineOverlay.LineCap.Square + else -> PolylineOverlay.LineCap.Round + } + } + + @ReactProp(name = "joinType") + override fun setJoinType( + view: RNCNaverMapPolyline?, + value: String?, + ) = view.withOverlay { + it.joinType = + when (value) { + "Bevel" -> PolylineOverlay.LineJoin.Bevel + "Miter" -> PolylineOverlay.LineJoin.Miter + else -> PolylineOverlay.LineJoin.Round + } + } + + // region PROPS + + companion object { + const val NAME = "RNCNaverMapPolyline" + } } diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/util/ArgUtil.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/util/ArgUtil.kt index 5432cb35..43b73835 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/util/ArgUtil.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/util/ArgUtil.kt @@ -8,99 +8,100 @@ import com.naver.maps.geometry.LatLngBounds import com.naver.maps.map.overlay.Align internal fun ReadableMap?.getLatLng(): LatLng? { - val latitude = this?.getDoubleOrNull("latitude") - val longitude = this?.getDoubleOrNull("longitude") - if (isValidNumber(latitude) && isValidNumber(longitude)) { - return LatLng(latitude!!, longitude!!) - } - return null + val latitude = this?.getDoubleOrNull("latitude") + val longitude = this?.getDoubleOrNull("longitude") + if (isValidNumber(latitude) && isValidNumber(longitude)) { + return LatLng(latitude!!, longitude!!) + } + return null } internal fun Map.getLatLng(): LatLng? { - val latitude = getValue("latitude") - val longitude = getValue("longitude") - if (isValidNumber(latitude) && isValidNumber(longitude)) { - return LatLng((latitude as Number).toDouble(), (longitude as Number).toDouble()) - } - return null + val latitude = getValue("latitude") + val longitude = getValue("longitude") + if (isValidNumber(latitude) && isValidNumber(longitude)) { + return LatLng((latitude as Number).toDouble(), (longitude as Number).toDouble()) + } + return null } internal fun ReadableMap?.getDoubleOrNull(key: String): Double? { - if (this?.hasKey(key) == true) { - return this.getDouble(key) - } - return null + if (this?.hasKey(key) == true) { + return this.getDouble(key) + } + return null } internal fun ReadableMap.getIntOrNull(key: String): Int? { - if (hasKey(key)) { - return this.getInt(key) - } - return null + if (hasKey(key)) { + return this.getInt(key) + } + return null } internal fun ReadableMap.getAlign(key: String): Align { - if (!hasKey(key)) { - return Align.Bottom - } - return when (getInt(key)) { - 0 -> Align.Center - 1 -> Align.Left - 2 -> Align.Right - 3 -> Align.Top - 5 -> Align.TopLeft - 6 -> Align.TopRight - 7 -> Align.BottomRight - 8 -> Align.BottomLeft - else -> Align.Bottom - } + if (!hasKey(key)) { + return Align.Bottom + } + return when (getInt(key)) { + 0 -> Align.Center + 1 -> Align.Left + 2 -> Align.Right + 3 -> Align.Top + 5 -> Align.TopLeft + 6 -> Align.TopRight + 7 -> Align.BottomRight + 8 -> Align.BottomLeft + else -> Align.Bottom + } } internal fun ReadableMap?.getLatLngBoundsOrNull(): LatLngBounds? { - return getRegion()?.convertToBounds() + return getRegion()?.convertToBounds() } internal fun ReadableMap?.getRegion(): RNCNaverMapRegion? { - if (this?.hasKey("latitude") == true && this.hasKey("longitude") && this.hasKey("latitudeDelta") && this.hasKey( - "longitudeDelta" - ) - ) { - val lat = getDouble("latitude") - val lng = getDouble("longitude") - val latDelta = getDouble("latitudeDelta") - val lngDelta = getDouble("longitudeDelta") - if (!isValidNumber(lat)) return null - return RNCNaverMapRegion(lat, lng, latDelta, lngDelta) - } - return null + if (this?.hasKey("latitude") == true && this.hasKey("longitude") && this.hasKey("latitudeDelta") && + this.hasKey( + "longitudeDelta", + ) + ) { + val lat = getDouble("latitude") + val lng = getDouble("longitude") + val latDelta = getDouble("latitudeDelta") + val lngDelta = getDouble("longitudeDelta") + if (!isValidNumber(lat)) return null + return RNCNaverMapRegion(lat, lng, latDelta, lngDelta) + } + return null } internal fun ReadableMap?.getPoint(): PointF? { - if (this?.hasKey("x") == true && this.hasKey("y")) { - val x = getDouble("x") - val y = getDouble("y") - return PointF(x.toFloat(), y.toFloat()) - } - return null + if (this?.hasKey("x") == true && this.hasKey("y")) { + val x = getDouble("x") + val y = getDouble("y") + return PointF(x.toFloat(), y.toFloat()) + } + return null } internal fun isValidNumber(value: Double?): Boolean { - if (value == null) return false - val invalid = -123123123.0 - return !(value < invalid + 1 && value > invalid - 1) + if (value == null) return false + val invalid = -123123123.0 + return !(value < invalid + 1 && value > invalid - 1) } internal fun isValidNumber(value: Int?): Boolean { - if (value == null) return false - return isValidNumber(value.toDouble()) + if (value == null) return false + return isValidNumber(value.toDouble()) } internal fun isValidNumber(value: Any?): Boolean { - if (value == null || value !is Number) return false - return isValidNumber(value.toDouble()) + if (value == null || value !is Number) return false + return isValidNumber(value.toDouble()) } internal val Double?.px: Int - get() = if (this == null) 0 else PixelUtil.toPixelFromDIP(this).toInt() + get() = if (this == null) 0 else PixelUtil.toPixelFromDIP(this).toInt() internal val Int?.px: Int - get() = if (this == null) 0 else PixelUtil.toPixelFromDIP(toDouble()).toInt() \ No newline at end of file + get() = if (this == null) 0 else PixelUtil.toPixelFromDIP(toDouble()).toInt() diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/util/CameraAnimationUtil.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/util/CameraAnimationUtil.kt index 98623518..fdf56e20 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/util/CameraAnimationUtil.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/util/CameraAnimationUtil.kt @@ -3,10 +3,11 @@ package com.mjstudio.reactnativenavermap.util import com.naver.maps.map.CameraAnimation internal object CameraAnimationUtil { - internal fun numberToCameraAnimationEasing(value: Int) = when (value) { - 1 -> CameraAnimation.None - 2 -> CameraAnimation.Linear - 3 -> CameraAnimation.Fly - else -> CameraAnimation.Easing + internal fun numberToCameraAnimationEasing(value: Int) = + when (value) { + 1 -> CameraAnimation.None + 2 -> CameraAnimation.Linear + 3 -> CameraAnimation.Fly + else -> CameraAnimation.Easing } -} \ No newline at end of file +} diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/util/DirectEventUtils.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/util/DirectEventUtils.kt new file mode 100644 index 00000000..2c4ff158 --- /dev/null +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/util/DirectEventUtils.kt @@ -0,0 +1,8 @@ +package com.mjstudio.reactnativenavermap.util + +internal fun registerDirectEvent( + map: MutableMap, + name: String, +) { + map[name] = mapOf("registrationName" to name) +} diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/util/ImageUtil.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/util/ImageUtil.kt index 4d1b9a50..7e8b5229 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/util/ImageUtil.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/util/ImageUtil.kt @@ -2,19 +2,21 @@ package com.mjstudio.reactnativenavermap.util import android.annotation.SuppressLint import android.content.Context +import android.content.res.Resources import android.graphics.Bitmap import android.graphics.drawable.Animatable import android.net.Uri import com.facebook.common.references.CloseableReference import com.facebook.drawee.backends.pipeline.Fresco import com.facebook.drawee.controller.BaseControllerListener +import com.facebook.drawee.drawable.ScalingUtils import com.facebook.drawee.generic.GenericDraweeHierarchy +import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder import com.facebook.drawee.view.DraweeHolder import com.facebook.imagepipeline.image.CloseableImage import com.facebook.imagepipeline.image.CloseableStaticBitmap import com.facebook.imagepipeline.image.ImageInfo import com.facebook.imagepipeline.request.ImageRequestBuilder -import com.facebook.react.bridge.ReadableMap import com.mjstudio.reactnativenavermap.overlay.marker.OverlayImages import com.naver.maps.map.overlay.OverlayImage import com.naver.maps.map.util.MarkerIcons @@ -22,114 +24,124 @@ import com.naver.maps.map.util.MarkerIcons internal typealias ImageRequestCanceller = () -> Unit internal fun getOverlayImage( - imageHolder: DraweeHolder, - context: Context, - map: ReadableMap?, - callback: (OverlayImage?) -> Unit + imageHolder: DraweeHolder, + context: Context, + map: Map<*, *>?, + callback: (OverlayImage?) -> Unit, ): ImageRequestCanceller { - if (map == null) { - callback(null) - return {} - } + if (map == null) { + callback(null) + return {} + } - val symbol = map.getString("symbol") ?: "" + val symbol = map["symbol"]?.toString() ?: "" - if (symbol.isNotEmpty()) { - callback( - when (symbol) { - "blue" -> MarkerIcons.BLUE - "gray" -> MarkerIcons.GRAY - "green" -> MarkerIcons.GREEN - "lightblue" -> MarkerIcons.LIGHTBLUE - "pink" -> MarkerIcons.PINK - "red" -> MarkerIcons.RED - "yellow" -> MarkerIcons.YELLOW - "black" -> MarkerIcons.BLACK - "lowDensityCluster" -> MarkerIcons.CLUSTER_LOW_DENSITY - "mediumDensityCluster" -> MarkerIcons.CLUSTER_MEDIUM_DENSITY - "highDensityCluster" -> MarkerIcons.CLUSTER_HIGH_DENSITY - else -> null - } - ) - return {} - } + if (symbol.isNotEmpty()) { + callback( + when (symbol) { + "blue" -> MarkerIcons.BLUE + "gray" -> MarkerIcons.GRAY + "green" -> MarkerIcons.GREEN + "lightblue" -> MarkerIcons.LIGHTBLUE + "pink" -> MarkerIcons.PINK + "red" -> MarkerIcons.RED + "yellow" -> MarkerIcons.YELLOW + "black" -> MarkerIcons.BLACK + "lowDensityCluster" -> MarkerIcons.CLUSTER_LOW_DENSITY + "mediumDensityCluster" -> MarkerIcons.CLUSTER_MEDIUM_DENSITY + "highDensityCluster" -> MarkerIcons.CLUSTER_HIGH_DENSITY + else -> null + }, + ) + return {} + } - val rnAssetUri = map.getString("rnAssetUri") ?: "" - // rnAssetUri starts with http if dev environment(metro server) - // todo - check how handled in release - val httpUri = map.getString("httpUri") ?: if (rnAssetUri.startsWith("http")) rnAssetUri else "" - val assetName = map.getString("assetName") ?: "" - val reuseIdentifier = map.getString("reuseIdentifier") ?: "" - debugE(rnAssetUri, httpUri, assetName, reuseIdentifier) + val rnAssetUri = map["rnAssetUri"]?.toString() ?: "" + // rnAssetUri starts with http if dev environment(metro server) + // todo - check how handled in release + val httpUri = map["httpUri"]?.toString() ?: if (rnAssetUri.startsWith("http")) rnAssetUri else "" + val assetName = map["assetName"]?.toString() ?: "" + val reuseIdentifier = map["reuseIdentifier"]?.toString() ?: "" - /** - * http, https, asset, file all works - */ - if (httpUri.isNotEmpty()) { - val key = reuseIdentifier.ifEmpty { httpUri } - val imageRequest = ImageRequestBuilder - .newBuilderWithSource(Uri.parse(httpUri)) - .build() - val dataSource = - Fresco.getImagePipeline() - .fetchDecodedImage(imageRequest, context) - val controller = Fresco.newDraweeControllerBuilder() - .setImageRequest(imageRequest) - .setControllerListener(object : BaseControllerListener() { - override fun onFinalImageSet( - id: String, - imageInfo: ImageInfo?, - animatable: Animatable? - ) { - var imageReference: CloseableReference? = null - var overlayImage: OverlayImage? = null - try { - imageReference = dataSource.result - if (imageReference != null) { - val image = imageReference.get() - if (image is CloseableStaticBitmap) { - var bitmap: Bitmap? = image.underlyingBitmap - if (bitmap != null) { - bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true) - overlayImage = OverlayImage.fromBitmap(bitmap) - OverlayImages.put(key, overlayImage) - } - } - } - } finally { - dataSource.close() - if (imageReference != null) { - CloseableReference.closeSafely(imageReference) - } - callback(overlayImage) + /** + * http, https, asset, file all works + */ + if (httpUri.isNotEmpty()) { + val key = reuseIdentifier.ifEmpty { httpUri } + val imageRequest = + ImageRequestBuilder + .newBuilderWithSource(Uri.parse(httpUri)) + .build() + val dataSource = + Fresco.getImagePipeline() + .fetchDecodedImage(imageRequest, context) + val controller = + Fresco.newDraweeControllerBuilder() + .setImageRequest(imageRequest) + .setControllerListener( + object : BaseControllerListener() { + override fun onFinalImageSet( + id: String, + imageInfo: ImageInfo?, + animatable: Animatable?, + ) { + var imageReference: CloseableReference? = null + var overlayImage: OverlayImage? = null + try { + imageReference = dataSource.result + if (imageReference != null) { + val image = imageReference.get() + if (image is CloseableStaticBitmap) { + var bitmap: Bitmap? = image.underlyingBitmap + if (bitmap != null) { + bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true) + overlayImage = OverlayImage.fromBitmap(bitmap) + OverlayImages.put(key, overlayImage) } + } } - }) - .setOldController(imageHolder.controller) - .build() - imageHolder.setController(controller) - return { - dataSource.close() - } - } - - if (rnAssetUri.isNotEmpty() || assetName.isNotEmpty()) { - val name = rnAssetUri.ifEmpty { assetName } - val key = reuseIdentifier.ifEmpty { name } - callback(OverlayImage.fromResource(getDrawableWithName(context, name)).also { - OverlayImages.put(key, it) - }) - return {} - } - - - + } finally { + dataSource.close() + if (imageReference != null) { + CloseableReference.closeSafely(imageReference) + } + callback(overlayImage) + } + } + }, + ) + .setOldController(imageHolder.controller) + .build() + imageHolder.setController(controller) + return controller::onDetach + } - callback(null) + if (rnAssetUri.isNotEmpty() || assetName.isNotEmpty()) { + val name = rnAssetUri.ifEmpty { assetName } + val key = reuseIdentifier.ifEmpty { name } + callback( + OverlayImage.fromResource(getDrawableWithName(context, name)).also { + OverlayImages.put(key, it) + }, + ) return {} + } + + callback(null) + return {} } @SuppressLint("DiscouragedApi") -private fun getDrawableWithName(context: Context, name: String): Int { - return context.resources.getIdentifier(name, "drawable", context.packageName) -} \ No newline at end of file +internal fun getDrawableWithName( + context: Context, + name: String, +): Int { + return context.resources.getIdentifier(name, "drawable", context.packageName) +} + +internal fun createDraweeHierarchy(resources: Resources): GenericDraweeHierarchy { + return GenericDraweeHierarchyBuilder(resources) + .setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER) + .setFadeDuration(0) + .build() +} diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/util/LogUtil.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/util/LogUtil.kt index 64dae08d..a80a2602 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/util/LogUtil.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/util/LogUtil.kt @@ -3,14 +3,17 @@ package com.mjstudio.reactnativenavermap.util import android.util.Log import com.mjstudio.reactnativenavermap.BuildConfig -private fun debugE(tag: String, message: Any?) { - if (BuildConfig.DEBUG) Log.e(tag, "โญ๏ธ" + message.toString()) +private fun debugE( + tag: String, + message: Any?, +) { + if (BuildConfig.DEBUG) Log.e(tag, "โญ๏ธ" + message.toString()) } internal fun debugE(vararg message: Any?) { - var str = "" - for (i in message) { - str += i.toString() + ", " - } - debugE("RNCNaverMapView", str) -} \ No newline at end of file + var str = "" + for (i in message) { + str += i.toString() + ", " + } + debugE("RNCNaverMapView", str) +} diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/util/RNCNaverMapRegion.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/util/RNCNaverMapRegion.kt index 49b3ee53..c34aea26 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/util/RNCNaverMapRegion.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/util/RNCNaverMapRegion.kt @@ -4,17 +4,18 @@ import com.naver.maps.geometry.LatLng import com.naver.maps.geometry.LatLngBounds internal data class RNCNaverMapRegion( - val latitude: Double, - val longitude: Double, - val latitudeDelta: Double, - val longitudeDelta: Double, + val latitude: Double, + val longitude: Double, + val latitudeDelta: Double, + val longitudeDelta: Double, ) { - fun convertToBounds(): LatLngBounds { - return LatLngBounds( - LatLng(latitude, longitude), LatLng( - latitude + latitudeDelta, - longitude + longitudeDelta - ) - ) - } + fun convertToBounds(): LatLngBounds { + return LatLngBounds( + LatLng(latitude, longitude), + LatLng( + latitude + latitudeDelta, + longitude + longitudeDelta, + ), + ) + } } diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/util/RectUtil.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/util/RectUtil.kt index 1ecbb856..186f1ffd 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/util/RectUtil.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/util/RectUtil.kt @@ -5,19 +5,18 @@ import com.facebook.react.bridge.ReadableMap import kotlin.math.roundToInt internal object RectUtil { - internal fun getRect(padding: ReadableMap?, density: Float, defaultValue: Double = 0.0): Rect? { - var left = 0 - var top = 0 - var right = 0 - var bottom = 0 - if (padding != null) { - top = ((padding.getDoubleOrNull("top") ?: defaultValue) * density).roundToInt() - right = ((padding.getDoubleOrNull("right") ?: defaultValue) * density).roundToInt() - bottom = ((padding.getDoubleOrNull("bottom") ?: defaultValue) * density).roundToInt() - left = ((padding.getDoubleOrNull("left") ?: defaultValue) * density).roundToInt() - return Rect(left, top, right, bottom) - } - return null + internal fun getRect( + padding: ReadableMap?, + density: Float, + defaultValue: Double = 0.0, + ): Rect? { + if (padding != null) { + val top = ((padding.getDoubleOrNull("top") ?: defaultValue) * density).roundToInt() + val right = ((padding.getDoubleOrNull("right") ?: defaultValue) * density).roundToInt() + val bottom = ((padding.getDoubleOrNull("bottom") ?: defaultValue) * density).roundToInt() + val left = ((padding.getDoubleOrNull("left") ?: defaultValue) * density).roundToInt() + return Rect(left, top, right, bottom) } + return null + } } - diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/util/ViewEventEmitter.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/util/ViewEventEmitter.kt index d908d558..a048f887 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/util/ViewEventEmitter.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/util/ViewEventEmitter.kt @@ -5,10 +5,10 @@ import com.facebook.react.uimanager.UIManagerHelper import com.facebook.react.uimanager.events.Event internal fun > ReactContext.emitEvent( - reactTag: Int, - callback: (surfaceId: Int, reactTag: Int) -> T + reactTag: Int, + callback: (surfaceId: Int, reactTag: Int) -> T, ) { - val surfaceId = UIManagerHelper.getSurfaceId(this) - UIManagerHelper.getEventDispatcherForReactTag(this, reactTag) - ?.dispatchEvent(callback(surfaceId, reactTag)) -} \ No newline at end of file + val surfaceId = UIManagerHelper.getSurfaceId(this) + UIManagerHelper.getEventDispatcherForReactTag(this, reactTag) + ?.dispatchEvent(callback(surfaceId, reactTag)) +} diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/util/registerDirectEvent.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/util/registerDirectEvent.kt deleted file mode 100644 index 1b4f566b..00000000 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/util/registerDirectEvent.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.mjstudio.reactnativenavermap.util - -internal fun registerDirectEvent(map: MutableMap, name: String) { - map[name] = mapOf("registrationName" to name) -} \ No newline at end of file diff --git a/android/src/newarch/RNCNaverMapCircleManagerSpec.kt b/android/src/newarch/RNCNaverMapCircleManagerSpec.kt index 39758f3b..f8ea2899 100644 --- a/android/src/newarch/RNCNaverMapCircleManagerSpec.kt +++ b/android/src/newarch/RNCNaverMapCircleManagerSpec.kt @@ -6,15 +6,16 @@ import com.facebook.react.uimanager.ViewManagerDelegate import com.facebook.react.viewmanagers.RNCNaverMapCircleManagerDelegate import com.facebook.react.viewmanagers.RNCNaverMapCircleManagerInterface -abstract class RNCNaverMapCircleManagerSpec : SimpleViewManager(), - RNCNaverMapCircleManagerInterface { - private val mDelegate: ViewManagerDelegate +abstract class RNCNaverMapCircleManagerSpec : + SimpleViewManager(), + RNCNaverMapCircleManagerInterface { + private val mDelegate: ViewManagerDelegate - init { - mDelegate = RNCNaverMapCircleManagerDelegate(this) - } + init { + mDelegate = RNCNaverMapCircleManagerDelegate(this) + } - override fun getDelegate(): ViewManagerDelegate? { - return mDelegate - } + override fun getDelegate(): ViewManagerDelegate? { + return mDelegate + } } diff --git a/android/src/newarch/RNCNaverMapMarkerManagerSpec.kt b/android/src/newarch/RNCNaverMapMarkerManagerSpec.kt index c95b7faa..ae6693de 100644 --- a/android/src/newarch/RNCNaverMapMarkerManagerSpec.kt +++ b/android/src/newarch/RNCNaverMapMarkerManagerSpec.kt @@ -6,15 +6,16 @@ import com.facebook.react.uimanager.ViewManagerDelegate import com.facebook.react.viewmanagers.RNCNaverMapMarkerManagerDelegate import com.facebook.react.viewmanagers.RNCNaverMapMarkerManagerInterface -abstract class RNCNaverMapMarkerManagerSpec : ViewGroupManager(), - RNCNaverMapMarkerManagerInterface { - private val mDelegate: ViewManagerDelegate +abstract class RNCNaverMapMarkerManagerSpec : + ViewGroupManager(), + RNCNaverMapMarkerManagerInterface { + private val mDelegate: ViewManagerDelegate - init { - mDelegate = RNCNaverMapMarkerManagerDelegate(this) - } + init { + mDelegate = RNCNaverMapMarkerManagerDelegate(this) + } - override fun getDelegate(): ViewManagerDelegate? { - return mDelegate - } + override fun getDelegate(): ViewManagerDelegate? { + return mDelegate + } } diff --git a/android/src/newarch/RNCNaverMapPathManagerSpec.kt b/android/src/newarch/RNCNaverMapPathManagerSpec.kt index 5478431f..6c90a49d 100644 --- a/android/src/newarch/RNCNaverMapPathManagerSpec.kt +++ b/android/src/newarch/RNCNaverMapPathManagerSpec.kt @@ -6,15 +6,16 @@ import com.facebook.react.uimanager.ViewManagerDelegate import com.facebook.react.viewmanagers.RNCNaverMapPathManagerDelegate import com.facebook.react.viewmanagers.RNCNaverMapPathManagerInterface -abstract class RNCNaverMapPathManagerSpec : SimpleViewManager(), - RNCNaverMapPathManagerInterface { - private val mDelegate: ViewManagerDelegate +abstract class RNCNaverMapPathManagerSpec : + SimpleViewManager(), + RNCNaverMapPathManagerInterface { + private val mDelegate: ViewManagerDelegate - init { - mDelegate = RNCNaverMapPathManagerDelegate(this) - } + init { + mDelegate = RNCNaverMapPathManagerDelegate(this) + } - override fun getDelegate(): ViewManagerDelegate? { - return mDelegate - } + override fun getDelegate(): ViewManagerDelegate? { + return mDelegate + } } diff --git a/android/src/newarch/RNCNaverMapPolygonManagerSpec.kt b/android/src/newarch/RNCNaverMapPolygonManagerSpec.kt index 28f0c912..137698b6 100644 --- a/android/src/newarch/RNCNaverMapPolygonManagerSpec.kt +++ b/android/src/newarch/RNCNaverMapPolygonManagerSpec.kt @@ -6,15 +6,16 @@ import com.facebook.react.uimanager.ViewManagerDelegate import com.facebook.react.viewmanagers.RNCNaverMapPolygonManagerDelegate import com.facebook.react.viewmanagers.RNCNaverMapPolygonManagerInterface -abstract class RNCNaverMapPolygonManagerSpec : SimpleViewManager(), - RNCNaverMapPolygonManagerInterface { - private val mDelegate: ViewManagerDelegate +abstract class RNCNaverMapPolygonManagerSpec : + SimpleViewManager(), + RNCNaverMapPolygonManagerInterface { + private val mDelegate: ViewManagerDelegate - init { - mDelegate = RNCNaverMapPolygonManagerDelegate(this) - } + init { + mDelegate = RNCNaverMapPolygonManagerDelegate(this) + } - override fun getDelegate(): ViewManagerDelegate? { - return mDelegate - } + override fun getDelegate(): ViewManagerDelegate? { + return mDelegate + } } diff --git a/android/src/newarch/RNCNaverMapPolylineManagerSpec.kt b/android/src/newarch/RNCNaverMapPolylineManagerSpec.kt index d1d934ef..112f04a3 100644 --- a/android/src/newarch/RNCNaverMapPolylineManagerSpec.kt +++ b/android/src/newarch/RNCNaverMapPolylineManagerSpec.kt @@ -6,15 +6,16 @@ import com.facebook.react.uimanager.ViewManagerDelegate import com.facebook.react.viewmanagers.RNCNaverMapPolylineManagerDelegate import com.facebook.react.viewmanagers.RNCNaverMapPolylineManagerInterface -abstract class RNCNaverMapPolylineManagerSpec : SimpleViewManager(), - RNCNaverMapPolylineManagerInterface { - private val mDelegate: ViewManagerDelegate +abstract class RNCNaverMapPolylineManagerSpec : + SimpleViewManager(), + RNCNaverMapPolylineManagerInterface { + private val mDelegate: ViewManagerDelegate - init { - mDelegate = RNCNaverMapPolylineManagerDelegate(this) - } + init { + mDelegate = RNCNaverMapPolylineManagerDelegate(this) + } - override fun getDelegate(): ViewManagerDelegate? { - return mDelegate - } + override fun getDelegate(): ViewManagerDelegate? { + return mDelegate + } } diff --git a/android/src/newarch/RNCNaverMapViewManagerSpec.kt b/android/src/newarch/RNCNaverMapViewManagerSpec.kt index 0643d02d..901c0136 100644 --- a/android/src/newarch/RNCNaverMapViewManagerSpec.kt +++ b/android/src/newarch/RNCNaverMapViewManagerSpec.kt @@ -6,15 +6,16 @@ import com.facebook.react.uimanager.ViewManagerDelegate import com.facebook.react.viewmanagers.RNCNaverMapViewManagerDelegate import com.facebook.react.viewmanagers.RNCNaverMapViewManagerInterface -abstract class RNCNaverMapViewManagerSpec : ViewGroupManager(), - RNCNaverMapViewManagerInterface { - private val mDelegate: ViewManagerDelegate +abstract class RNCNaverMapViewManagerSpec : + ViewGroupManager(), + RNCNaverMapViewManagerInterface { + private val mDelegate: ViewManagerDelegate - init { - mDelegate = RNCNaverMapViewManagerDelegate(this) - } + init { + mDelegate = RNCNaverMapViewManagerDelegate(this) + } - override fun getDelegate(): ViewManagerDelegate? { - return mDelegate - } + override fun getDelegate(): ViewManagerDelegate? { + return mDelegate + } } diff --git a/android/src/oldarch/RNCNaverMapCircleManagerSpec.kt b/android/src/oldarch/RNCNaverMapCircleManagerSpec.kt index c414c6be..5beb2151 100644 --- a/android/src/oldarch/RNCNaverMapCircleManagerSpec.kt +++ b/android/src/oldarch/RNCNaverMapCircleManagerSpec.kt @@ -4,21 +4,63 @@ import android.view.View import com.facebook.react.bridge.ReadableMap import com.facebook.react.uimanager.SimpleViewManager - internal interface RNCNaverMapCircleManagerInterface { - fun setCoord(view: T, value: ReadableMap?) - fun setZIndexValue(view: T, value: Int) - fun setIsHidden(view: T, value: Boolean) - fun setMinZoom(view: T, value: Double) - fun setMaxZoom(view: T, value: Double) - fun setIsMinZoomInclusive(view: T, value: Boolean) - fun setIsMaxZoomInclusive(view: T, value: Boolean) - fun setRadius(view: T, value: Double) - fun setColor(view: T, value: Int) - fun setOutlineColor(view: T, value: Int) - fun setOutlineWidth(view: T, value: Double) -} + fun setCoord( + view: T, + value: ReadableMap?, + ) + + fun setZIndexValue( + view: T, + value: Int, + ) + + fun setIsHidden( + view: T, + value: Boolean, + ) + + fun setMinZoom( + view: T, + value: Double, + ) + + fun setMaxZoom( + view: T, + value: Double, + ) + fun setIsMinZoomInclusive( + view: T, + value: Boolean, + ) + + fun setIsMaxZoomInclusive( + view: T, + value: Boolean, + ) + + fun setRadius( + view: T, + value: Double, + ) + + fun setColor( + view: T, + value: Int, + ) + + fun setOutlineColor( + view: T, + value: Int, + ) + + fun setOutlineWidth( + view: T, + value: Double, + ) +} -abstract class RNCNaverMapCircleManagerSpec : SimpleViewManager(), - RNCNaverMapCircleManagerInterface +abstract class RNCNaverMapCircleManagerSpec : + SimpleViewManager(), + RNCNaverMapCircleManagerInterface diff --git a/android/src/oldarch/RNCNaverMapMarkerManagerSpec.kt b/android/src/oldarch/RNCNaverMapMarkerManagerSpec.kt index 71666f69..6ba26c6e 100644 --- a/android/src/oldarch/RNCNaverMapMarkerManagerSpec.kt +++ b/android/src/oldarch/RNCNaverMapMarkerManagerSpec.kt @@ -4,32 +4,118 @@ import android.view.ViewGroup import com.facebook.react.bridge.ReadableMap import com.facebook.react.uimanager.ViewGroupManager - internal interface RNCNaverMapMarkerManagerInterface { - fun setCoord(view: T, value: ReadableMap?) - fun setZIndexValue(view: T, value: Int) - fun setIsHidden(view: T, value: Boolean) - fun setMinZoom(view: T, value: Double) - fun setMaxZoom(view: T, value: Double) - fun setIsMinZoomInclusive(view: T, value: Boolean) - fun setIsMaxZoomInclusive(view: T, value: Boolean) - fun setWidth(view: T, value: Double) - fun setHeight(view: T, value: Double) - fun setAnchor(view: T, value: ReadableMap?) - fun setAngle(view: T, value: Double) - fun setIsFlatEnabled(view: T, value: Boolean) - fun setIsIconPerspectiveEnabled(view: T, value: Boolean) - fun setAlpha(view: T, value: Double) - fun setIsHideCollidedSymbols(view: T, value: Boolean) - fun setIsHideCollidedMarkers(view: T, value: Boolean) - fun setIsHideCollidedCaptions(view: T, value: Boolean) - fun setIsForceShowIcon(view: T, value: Boolean) - fun setTintColor(view: T, value: Int) - fun setImage(view: T, value: ReadableMap?) - fun setCaption(view: T, value: ReadableMap?) - fun setSubCaption(view: T, value: ReadableMap?) -} + fun setCoord( + view: T, + value: ReadableMap?, + ) + + fun setZIndexValue( + view: T, + value: Int, + ) + + fun setIsHidden( + view: T, + value: Boolean, + ) + + fun setMinZoom( + view: T, + value: Double, + ) + + fun setMaxZoom( + view: T, + value: Double, + ) + + fun setIsMinZoomInclusive( + view: T, + value: Boolean, + ) + + fun setIsMaxZoomInclusive( + view: T, + value: Boolean, + ) + + fun setWidth( + view: T, + value: Double, + ) + + fun setHeight( + view: T, + value: Double, + ) + + fun setAnchor( + view: T, + value: ReadableMap?, + ) + + fun setAngle( + view: T, + value: Double, + ) + fun setIsFlatEnabled( + view: T, + value: Boolean, + ) + + fun setIsIconPerspectiveEnabled( + view: T, + value: Boolean, + ) + + fun setAlpha( + view: T, + value: Double, + ) + + fun setIsHideCollidedSymbols( + view: T, + value: Boolean, + ) + + fun setIsHideCollidedMarkers( + view: T, + value: Boolean, + ) + + fun setIsHideCollidedCaptions( + view: T, + value: Boolean, + ) + + fun setIsForceShowIcon( + view: T, + value: Boolean, + ) + + fun setTintColor( + view: T, + value: Int, + ) + + fun setImage( + view: T, + value: ReadableMap?, + ) + + fun setCaption( + view: T, + value: ReadableMap?, + ) + + fun setSubCaption( + view: T, + value: ReadableMap?, + ) +} -abstract class RNCNaverMapMarkerManagerSpec : ViewGroupManager(), - RNCNaverMapMarkerManagerInterface +abstract class RNCNaverMapMarkerManagerSpec : + ViewGroupManager(), + RNCNaverMapMarkerManagerInterface diff --git a/android/src/oldarch/RNCNaverMapPathManagerSpec.kt b/android/src/oldarch/RNCNaverMapPathManagerSpec.kt index e0543cce..00e14c13 100644 --- a/android/src/oldarch/RNCNaverMapPathManagerSpec.kt +++ b/android/src/oldarch/RNCNaverMapPathManagerSpec.kt @@ -4,28 +4,98 @@ import android.view.View import com.facebook.react.bridge.ReadableArray import com.facebook.react.uimanager.SimpleViewManager - internal interface RNCNaverMapPathManagerInterface { - fun setZIndexValue(view: T, value: Int) - fun setIsHidden(view: T, value: Boolean) - fun setMinZoom(view: T, value: Double) - fun setMaxZoom(view: T, value: Double) - fun setIsMinZoomInclusive(view: T, value: Boolean) - fun setIsMaxZoomInclusive(view: T, value: Boolean) - fun setCoords(view: T, value: ReadableArray?) - fun setWidth(view: T, value: Double) - fun setOutlineWidth(view: T, value: Double) - fun setPatternInterval(view: T, value: Int) - fun setProgress(view: T, value: Double) - fun setColor(view: T, value: Int) - fun setPassedColor(view: T, value: Int) - fun setOutlineColor(view: T, value: Int) - fun setPassedOutlineColor(view: T, value: Int) - fun setIsHideCollidedSymbols(view: T, value: Boolean) - fun setIsHideCollidedMarkers(view: T, value: Boolean) - fun setIsHideCollidedCaptions(view: T, value: Boolean) -} + fun setZIndexValue( + view: T, + value: Int, + ) + + fun setIsHidden( + view: T, + value: Boolean, + ) + + fun setMinZoom( + view: T, + value: Double, + ) + + fun setMaxZoom( + view: T, + value: Double, + ) + + fun setIsMinZoomInclusive( + view: T, + value: Boolean, + ) + + fun setIsMaxZoomInclusive( + view: T, + value: Boolean, + ) + + fun setCoords( + view: T, + value: ReadableArray?, + ) + + fun setWidth( + view: T, + value: Double, + ) + + fun setOutlineWidth( + view: T, + value: Double, + ) + fun setPatternInterval( + view: T, + value: Int, + ) + + fun setProgress( + view: T, + value: Double, + ) + + fun setColor( + view: T, + value: Int, + ) + + fun setPassedColor( + view: T, + value: Int, + ) + + fun setOutlineColor( + view: T, + value: Int, + ) + + fun setPassedOutlineColor( + view: T, + value: Int, + ) + + fun setIsHideCollidedSymbols( + view: T, + value: Boolean, + ) + + fun setIsHideCollidedMarkers( + view: T, + value: Boolean, + ) + + fun setIsHideCollidedCaptions( + view: T, + value: Boolean, + ) +} -abstract class RNCNaverMapPathManagerSpec : SimpleViewManager(), - RNCNaverMapPathManagerInterface +abstract class RNCNaverMapPathManagerSpec : + SimpleViewManager(), + RNCNaverMapPathManagerInterface diff --git a/android/src/oldarch/RNCNaverMapPolygonManagerSpec.kt b/android/src/oldarch/RNCNaverMapPolygonManagerSpec.kt index 7407e78d..5ea151b6 100644 --- a/android/src/oldarch/RNCNaverMapPolygonManagerSpec.kt +++ b/android/src/oldarch/RNCNaverMapPolygonManagerSpec.kt @@ -4,20 +4,58 @@ import android.view.View import com.facebook.react.bridge.ReadableMap import com.facebook.react.uimanager.SimpleViewManager - internal interface RNCNaverMapPolygonManagerInterface { - fun setZIndexValue(view: T, value: Int) - fun setIsHidden(view: T, value: Boolean) - fun setMinZoom(view: T, value: Double) - fun setMaxZoom(view: T, value: Double) - fun setIsMinZoomInclusive(view: T, value: Boolean) - fun setIsMaxZoomInclusive(view: T, value: Boolean) - fun setGeometries(view: T, value: ReadableMap?) - fun setColor(view: T, value: Int) - fun setOutlineColor(view: T, value: Int) - fun setOutlineWidth(view: T, value: Double) -} + fun setZIndexValue( + view: T, + value: Int, + ) + + fun setIsHidden( + view: T, + value: Boolean, + ) + + fun setMinZoom( + view: T, + value: Double, + ) + + fun setMaxZoom( + view: T, + value: Double, + ) + + fun setIsMinZoomInclusive( + view: T, + value: Boolean, + ) + fun setIsMaxZoomInclusive( + view: T, + value: Boolean, + ) + + fun setGeometries( + view: T, + value: ReadableMap?, + ) + + fun setColor( + view: T, + value: Int, + ) + + fun setOutlineColor( + view: T, + value: Int, + ) + + fun setOutlineWidth( + view: T, + value: Double, + ) +} -abstract class RNCNaverMapPolygonManagerSpec : SimpleViewManager(), - RNCNaverMapPolygonManagerInterface +abstract class RNCNaverMapPolygonManagerSpec : + SimpleViewManager(), + RNCNaverMapPolygonManagerInterface diff --git a/android/src/oldarch/RNCNaverMapPolylineManagerSpec.kt b/android/src/oldarch/RNCNaverMapPolylineManagerSpec.kt index 5816bac8..cc2b97ff 100644 --- a/android/src/oldarch/RNCNaverMapPolylineManagerSpec.kt +++ b/android/src/oldarch/RNCNaverMapPolylineManagerSpec.kt @@ -4,22 +4,68 @@ import android.view.View import com.facebook.react.bridge.ReadableArray import com.facebook.react.uimanager.SimpleViewManager - internal interface RNCNaverMapPolylineManagerInterface { - fun setZIndexValue(view: T, value: Int) - fun setIsHidden(view: T, value: Boolean) - fun setMinZoom(view: T, value: Double) - fun setMaxZoom(view: T, value: Double) - fun setIsMinZoomInclusive(view: T, value: Boolean) - fun setIsMaxZoomInclusive(view: T, value: Boolean) - fun setCoords(view: T, value: ReadableArray?) - fun setWidth(view: T, value: Double) - fun setColor(view: T, value: Int) - fun setPattern(view: T, value: ReadableArray?) - fun setCapType(view: T, value: String?) - fun setJoinType(view: T, value: String?) -} + fun setZIndexValue( + view: T, + value: Int, + ) + + fun setIsHidden( + view: T, + value: Boolean, + ) + + fun setMinZoom( + view: T, + value: Double, + ) + + fun setMaxZoom( + view: T, + value: Double, + ) + + fun setIsMinZoomInclusive( + view: T, + value: Boolean, + ) + + fun setIsMaxZoomInclusive( + view: T, + value: Boolean, + ) + fun setCoords( + view: T, + value: ReadableArray?, + ) + + fun setWidth( + view: T, + value: Double, + ) + + fun setColor( + view: T, + value: Int, + ) + + fun setPattern( + view: T, + value: ReadableArray?, + ) + + fun setCapType( + view: T, + value: String?, + ) + + fun setJoinType( + view: T, + value: String?, + ) +} -abstract class RNCNaverMapPolylineManagerSpec : SimpleViewManager(), - RNCNaverMapPolylineManagerInterface +abstract class RNCNaverMapPolylineManagerSpec : + SimpleViewManager(), + RNCNaverMapPolylineManagerInterface diff --git a/android/src/oldarch/RNCNaverMapViewManagerSpec.kt b/android/src/oldarch/RNCNaverMapViewManagerSpec.kt index 9e3ca92b..917c8d67 100644 --- a/android/src/oldarch/RNCNaverMapViewManagerSpec.kt +++ b/android/src/oldarch/RNCNaverMapViewManagerSpec.kt @@ -7,135 +7,290 @@ import com.facebook.react.bridge.ReadableMap import com.facebook.react.uimanager.ViewGroupManager internal interface RNCNaverMapViewManagerInterface { - fun setMapType(view: T, value: String?) - fun setLayerGroups(view: T, value: Int) - fun setInitialCamera(view: T, value: ReadableMap?) - fun setCamera(view: T, value: ReadableMap?) - fun setInitialRegion(view: T, value: ReadableMap?) - fun setRegion(view: T, value: ReadableMap?) - fun setAnimationDuration(view: T, value: Int) - fun setAnimationEasing(view: T, value: Int) - fun setIsIndoorEnabled(view: T, value: Boolean) - fun setIsNightModeEnabled(view: T, value: Boolean) - fun setIsLiteModeEnabled(view: T, value: Boolean) - fun setLightness(view: T, value: Double) - fun setBuildingHeight(view: T, value: Double) - fun setSymbolScale(view: T, value: Double) - fun setSymbolPerspectiveRatio(view: T, value: Double) - fun setMapPadding(view: T, value: ReadableMap?) - fun setMinZoom(view: T, value: Double) - fun setMaxZoom(view: T, value: Double) - fun setIsShowCompass(view: T, value: Boolean) - fun setIsShowScaleBar(view: T, value: Boolean) - fun setIsShowZoomControls(view: T, value: Boolean) - fun setIsShowIndoorLevelPicker(view: T, value: Boolean) - fun setIsShowLocationButton(view: T, value: Boolean) - fun setLogoAlign(view: T, value: String?) - fun setLogoMargin(view: T, value: ReadableMap?) - fun setExtent(view: T, value: ReadableMap?) - fun setIsScrollGesturesEnabled(view: T, value: Boolean) - fun setIsZoomGesturesEnabled(view: T, value: Boolean) - fun setIsTiltGesturesEnabled(view: T, value: Boolean) - fun setIsRotateGesturesEnabled(view: T, value: Boolean) - fun setIsUseTextureViewAndroid(view: T, value: Boolean) - fun setIsStopGesturesEnabled(view: T, value: Boolean) - fun setLocale(view: T, value: String?) - - fun screenToCoordinate(view: T, x: Double, y: Double) - fun coordinateToScreen(view: T, latitude: Double, longitude: Double) - fun animateCameraTo( - view: T, - latitude: Double, - longitude: Double, - duration: Int, - easing: Int, - pivotX: Double, - pivotY: Double, - zoom: Double, - ) - - fun animateCameraBy( - view: T, - x: Double, - y: Double, - duration: Int, - easing: Int, - pivotX: Double, - pivotY: Double - ) - - fun animateRegionTo( - view: T, - latitude: Double, - longitude: Double, - latitudeDelta: Double, - longitudeDelta: Double, - duration: Int, - easing: Int, - pivotX: Double, - pivotY: Double - ) - - fun cancelAnimation(view: T) - - fun setLocationTrackingMode(view: T, mode: String?) + fun setMapType( + view: T, + value: String?, + ) + + fun setLayerGroups( + view: T, + value: Int, + ) + + fun setInitialCamera( + view: T, + value: ReadableMap?, + ) + + fun setCamera( + view: T, + value: ReadableMap?, + ) + + fun setInitialRegion( + view: T, + value: ReadableMap?, + ) + + fun setRegion( + view: T, + value: ReadableMap?, + ) + + fun setAnimationDuration( + view: T, + value: Int, + ) + + fun setAnimationEasing( + view: T, + value: Int, + ) + + fun setIsIndoorEnabled( + view: T, + value: Boolean, + ) + + fun setIsNightModeEnabled( + view: T, + value: Boolean, + ) + + fun setIsLiteModeEnabled( + view: T, + value: Boolean, + ) + + fun setLightness( + view: T, + value: Double, + ) + + fun setBuildingHeight( + view: T, + value: Double, + ) + + fun setSymbolScale( + view: T, + value: Double, + ) + + fun setSymbolPerspectiveRatio( + view: T, + value: Double, + ) + + fun setMapPadding( + view: T, + value: ReadableMap?, + ) + + fun setMinZoom( + view: T, + value: Double, + ) + + fun setMaxZoom( + view: T, + value: Double, + ) + + fun setIsShowCompass( + view: T, + value: Boolean, + ) + + fun setIsShowScaleBar( + view: T, + value: Boolean, + ) + + fun setIsShowZoomControls( + view: T, + value: Boolean, + ) + + fun setIsShowIndoorLevelPicker( + view: T, + value: Boolean, + ) + + fun setIsShowLocationButton( + view: T, + value: Boolean, + ) + + fun setLogoAlign( + view: T, + value: String?, + ) + + fun setLogoMargin( + view: T, + value: ReadableMap?, + ) + + fun setExtent( + view: T, + value: ReadableMap?, + ) + + fun setIsScrollGesturesEnabled( + view: T, + value: Boolean, + ) + + fun setIsZoomGesturesEnabled( + view: T, + value: Boolean, + ) + + fun setIsTiltGesturesEnabled( + view: T, + value: Boolean, + ) + + fun setIsRotateGesturesEnabled( + view: T, + value: Boolean, + ) + + fun setIsUseTextureViewAndroid( + view: T, + value: Boolean, + ) + + fun setIsStopGesturesEnabled( + view: T, + value: Boolean, + ) + + fun setLocale( + view: T, + value: String?, + ) + + fun screenToCoordinate( + view: T, + x: Double, + y: Double, + ) + + fun coordinateToScreen( + view: T, + latitude: Double, + longitude: Double, + ) + + fun animateCameraTo( + view: T, + latitude: Double, + longitude: Double, + duration: Int, + easing: Int, + pivotX: Double, + pivotY: Double, + zoom: Double, + ) + + fun animateCameraBy( + view: T, + x: Double, + y: Double, + duration: Int, + easing: Int, + pivotX: Double, + pivotY: Double, + ) + + fun animateRegionTo( + view: T, + latitude: Double, + longitude: Double, + latitudeDelta: Double, + longitudeDelta: Double, + duration: Int, + easing: Int, + pivotX: Double, + pivotY: Double, + ) + + fun cancelAnimation(view: T) + + fun setLocationTrackingMode( + view: T, + mode: String?, + ) + + fun setClusters( + view: T, + value: ReadableMap?, + ) } -abstract class RNCNaverMapViewManagerSpec : ViewGroupManager(), - RNCNaverMapViewManagerInterface { - override fun receiveCommand( - view: T, - commandId: String?, - _args: ReadableArray? - ) { - val args = _args ?: Arguments.createArray() - when (commandId) { - "screenToCoordinate" -> screenToCoordinate( - view, - args.getDouble(0), - args.getDouble(1) - ) - - "coordinateToScreen" -> coordinateToScreen( - view, - args.getDouble(0), - args.getDouble(1) - ) - - "animateCameraTo" -> animateCameraTo( - view, - args.getDouble(0), - args.getDouble(1), - args.getInt(2), - args.getInt(3), - args.getDouble(4), - args.getDouble(5), - args.getDouble(6), - ) - - "animateCameraBy" -> animateCameraBy( - view, - args.getDouble(0), - args.getDouble(1), - args.getInt(2), - args.getInt(3), - args.getDouble(4), - args.getDouble(5) - ) - - "animateRegionTo" -> animateRegionTo( - view, - args.getDouble(0), - args.getDouble(1), - args.getDouble(2), - args.getDouble(3), - args.getInt(4), - args.getInt(5), - args.getDouble(6), - args.getDouble(7) - ) - - "cancelAnimation" -> cancelAnimation(view) - } +abstract class RNCNaverMapViewManagerSpec : + ViewGroupManager(), + RNCNaverMapViewManagerInterface { + override fun receiveCommand( + view: T, + commandId: String?, + _args: ReadableArray?, + ) { + val args = _args ?: Arguments.createArray() + when (commandId) { + "screenToCoordinate" -> + screenToCoordinate( + view, + args.getDouble(0), + args.getDouble(1), + ) + + "coordinateToScreen" -> + coordinateToScreen( + view, + args.getDouble(0), + args.getDouble(1), + ) + + "animateCameraTo" -> + animateCameraTo( + view, + args.getDouble(0), + args.getDouble(1), + args.getInt(2), + args.getInt(3), + args.getDouble(4), + args.getDouble(5), + args.getDouble(6), + ) + + "animateCameraBy" -> + animateCameraBy( + view, + args.getDouble(0), + args.getDouble(1), + args.getInt(2), + args.getInt(3), + args.getDouble(4), + args.getDouble(5), + ) + + "animateRegionTo" -> + animateRegionTo( + view, + args.getDouble(0), + args.getDouble(1), + args.getDouble(2), + args.getDouble(3), + args.getInt(4), + args.getInt(5), + args.getDouble(6), + args.getDouble(7), + ) + + "cancelAnimation" -> cancelAnimation(view) } + } } diff --git a/example/android/.editorconfig b/example/android/.editorconfig new file mode 100644 index 00000000..63c55363 --- /dev/null +++ b/example/android/.editorconfig @@ -0,0 +1,11 @@ +# editorconfig +root = true + +[*.{kt,kts}] +ktlint_code_style = ktlint_official +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 7b7a5fea..1a642b52 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -8,7 +8,7 @@ PODS: - hermes-engine/Pre-built (= 0.73.6) - hermes-engine/Pre-built (0.73.6) - libevent (2.1.12) - - mj-studio-react-native-naver-map (1.1.1): + - mj-studio-react-native-naver-map (1.2.0): - glog - hermes-engine - NMapsMap (= 3.18.0) @@ -1344,7 +1344,7 @@ SPEC CHECKSUMS: glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 hermes-engine: 9cecf9953a681df7556b8cc9c74905de8f3293c0 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 - mj-studio-react-native-naver-map: 9b1687ae09f9eaa1f8f998aeb29e760b12ef7f48 + mj-studio-react-native-naver-map: 24dd6008ef02989642b7215ffa0ac9f5f9e1af0e NMapsGeometry: 53c573ead66466681cf123f99f698dc8071a4b83 NMapsMap: 36dc18a1f5f315121b33fccd2398c7a702bf0fc8 RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0 diff --git a/example/src/App.tsx b/example/src/App.tsx index a1a27e0d..951337a8 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,24 +1,25 @@ -import React, { useRef, useState, useEffect } from 'react'; +import React, { useRef, useState, useEffect, useMemo } from 'react'; import { View, Platform, Text } from 'react-native'; import { type MapType, type NaverMapViewRef, type Camera, + NaverMapView, + type ClusterMarkerProp, + NaverMapPathOverlay, NaverMapMarkerOverlay, NaverMapCircleOverlay, NaverMapPolygonOverlay, - NaverMapView, - NaverMapPathOverlay, } from '@mj-studio/react-native-naver-map'; import { Toggle, Btn, Range } from './component/components'; -import { formatJson } from '@mj-studio/js-util'; import { request, PERMISSIONS, requestLocationAccuracy, requestMultiple, } from 'react-native-permissions'; +import { formatJson, generateArray } from '@mj-studio/js-util'; // const jejuRegion: Region = { // latitude: 33.20530773, @@ -105,55 +106,35 @@ export default function App() { } }, []); - return ( - - console.log('initialized!')} - // onOptionChanged={() => console.log('Option Changed!')} - // onCameraChanged={(args) => - // console.log(`Camera Changed: ${formatJson(args)}`) - // } - onTapMap={(args) => console.log(`Map Tapped: ${formatJson(args)}`)} - > - console.log(1)} - anchor={{ x: 0.5, y: 1 }} - /> + const [hash, setHash] = useState(0); + const clusterers = useMemo< + { + markers: ClusterMarkerProp[]; + screenDistance?: number; + minZoom?: number; + maxZoom?: number; + animate?: boolean; + }[] + >(() => { + return generateArray(6).map((i) => { + return { + markers: generateArray(50).map((j) => ({ + image: { + httpUri: `https://picsum.photos/seed/${hash}-${i}-${j}/32/32`, + }, + width: 64, + height: 64, + latitude: Cameras.Jeju.latitude + Math.random(), + longitude: Cameras.Jeju.longitude + Math.random(), + identifier: `${hash}-${i}-${j}`, + })), + }; + }); + }, [hash]); + + const renderOverlays = () => { + return ( + <> console.log(1)} anchor={{ x: 0.5, y: 1 }} caption={{ @@ -218,7 +199,7 @@ export default function App() { }} width={100} height={100} - image={{ httpUri: 'https://picsum.photos/100/100' }} + image={{ httpUri: 'https://picsum.photos/1000/1201' }} /> - + + ); + }; + console.log(renderOverlays); + + return ( + + console.log('initialized!')} + onOptionChanged={() => console.log('Option Changed!')} + // onCameraChanged={(args) => + // console.log(`Camera Changed: ${formatJson(args)}`) + // } + onTapMap={(args) => console.log(`Map Tapped: ${formatJson(args)}`)} + clusters={clusterers} + /> + setHash((h) => h + 1)} /> diff --git a/ios/Overlay/Cluster/RNCNaverMapClusterKey.h b/ios/Overlay/Cluster/RNCNaverMapClusterKey.h new file mode 100644 index 00000000..d799b90a --- /dev/null +++ b/ios/Overlay/Cluster/RNCNaverMapClusterKey.h @@ -0,0 +1,33 @@ +// +// RNCNaverMapClusterKey.h +// DoubleConversion +// +// Created by mj on 4/18/24. +// + +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RNCNaverMapClusterKey : NSObject + +@property(nonatomic, strong) NSString* identifier; +@property(nonatomic, strong) NMGLatLng* position; +@property(nonatomic, weak) RCTBridge* bridge; +@property(nonatomic, strong) NSDictionary* image; +@property(nonatomic, assign) double width; +@property(nonatomic, assign) double height; + ++ (instancetype)markerKeyWithIdentifier:(nonnull NSString*)identifier + position:(nonnull NMGLatLng*)position + bridge:(RCTBridge*)bridge + image:(nonnull NSDictionary*)image + width:(double)width + height:(double)height; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Overlay/Cluster/RNCNaverMapClusterKey.mm b/ios/Overlay/Cluster/RNCNaverMapClusterKey.mm new file mode 100644 index 00000000..ce3f3211 --- /dev/null +++ b/ios/Overlay/Cluster/RNCNaverMapClusterKey.mm @@ -0,0 +1,70 @@ +// +// RNCNaverMapClusterKey.m +// DoubleConversion +// +// Created by mj on 4/18/24. +// + +#import "RNCNaverMapClusterKey.h" + +@implementation RNCNaverMapClusterKey + ++ (instancetype)markerKeyWithIdentifier:(NSString*)identifier + position:(NMGLatLng*)position + bridge:(RCTBridge*)bridge + image:(NSDictionary*)image + width:(double)width + height:(double)height { + return [[RNCNaverMapClusterKey alloc] initWithIdentifier:identifier + position:position + bridge:bridge + image:image + width:width + height:height]; +} + +- (instancetype)initWithIdentifier:(nonnull NSString*)identifier + position:(nonnull NMGLatLng*)position + bridge:(RCTBridge*)bridge + image:(nonnull NSDictionary*)image + width:(double)width + height:(double)height { + if (self = [super init]) { + _identifier = identifier; + _position = position; + _bridge = bridge; + _image = image; + _width = width; + _height = height; + } + + return self; +} + +- (BOOL)isEqual:(id)object { + if (self == object) { + return YES; + } + + if (object == nil || [self class] != [object class]) { + return NO; + } + + RNCNaverMapClusterKey* key = object; + return [key.identifier isEqualToString:self.identifier]; +} + +- (NSUInteger)hash { + return self.identifier.hash; +} + +- (nonnull id)copyWithZone:(NSZone*)zone { + return [[[self class] alloc] initWithIdentifier:[self.identifier copy] + position:[self.position copy] + bridge:self.bridge + image:[self.image copy] + width:self.width + height:self.height]; +} + +@end diff --git a/ios/Overlay/Cluster/RNCNaverMapClusterMarkerUpdater.h b/ios/Overlay/Cluster/RNCNaverMapClusterMarkerUpdater.h new file mode 100644 index 00000000..c2162db9 --- /dev/null +++ b/ios/Overlay/Cluster/RNCNaverMapClusterMarkerUpdater.h @@ -0,0 +1,21 @@ +// +// RNCNaverMapClusterMarkerUpdater.m +// mj-studio-react-native-naver-map +// +// Created by mj on 4/18/24. +// +#import "RNCNaverMapClusterKey.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +@interface RNCNaverMapClusterMarkerUpdater : NMCDefaultClusterMarkerUpdater +@end diff --git a/ios/Overlay/Cluster/RNCNaverMapClusterMarkerUpdater.mm b/ios/Overlay/Cluster/RNCNaverMapClusterMarkerUpdater.mm new file mode 100644 index 00000000..40f4e8d8 --- /dev/null +++ b/ios/Overlay/Cluster/RNCNaverMapClusterMarkerUpdater.mm @@ -0,0 +1,33 @@ +// +// RNCNaverMapClusterMarkerUpdater.m +// mj-studio-react-native-naver-map +// +// Created by mj on 4/18/24. +// +#import "RNCNaverMapClusterMarkerUpdater.h" +#import "RNCNaverMapClusterKey.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +@implementation RNCNaverMapClusterMarkerUpdater +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-selector-name" +- (void)updateClusterMarker:(NMCClusterMarkerInfo*)info:(NMFMarker*)marker { + [super updateClusterMarker:info:marker]; + if (info.size < 3) { + marker.iconImage = NMF_MARKER_IMAGE_CLUSTER_LOW_DENSITY; + } else { + marker.iconImage = NMF_MARKER_IMAGE_CLUSTER_MEDIUM_DENSITY; + } +} +#pragma clang diagnostic pop +@end diff --git a/ios/Overlay/Cluster/RNCNaverMapLeafMarkerUpdater.h b/ios/Overlay/Cluster/RNCNaverMapLeafMarkerUpdater.h new file mode 100644 index 00000000..c0c3193b --- /dev/null +++ b/ios/Overlay/Cluster/RNCNaverMapLeafMarkerUpdater.h @@ -0,0 +1,25 @@ +// +// RNCNaverMapLeafMarkerUpdater.h +// mj-studio-react-native-naver-map +// +// Created by mj on 4/18/24. +// + +#import "RNCNaverMapClusterKey.h" +#import "Utils.h" +#import +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RNCNaverMapLeafMarkerUpdater : NMCDefaultLeafMarkerUpdater +@property(nonatomic, weak, nullable) NMCClusterer* clusterer; + +- init:(NSMutableDictionary*)markerImageRequestCanceler; +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Overlay/Cluster/RNCNaverMapLeafMarkerUpdater.mm b/ios/Overlay/Cluster/RNCNaverMapLeafMarkerUpdater.mm new file mode 100644 index 00000000..7f97e7f0 --- /dev/null +++ b/ios/Overlay/Cluster/RNCNaverMapLeafMarkerUpdater.mm @@ -0,0 +1,62 @@ +// +// RNCNaverMapLeafMarkerUpdater.m +// mj-studio-react-native-naver-map +// +// Created by mj on 4/18/24. +// + +#import "RNCNaverMapLeafMarkerUpdater.h" + +@implementation RNCNaverMapLeafMarkerUpdater { + __weak NSMutableDictionary* _markerImageRequestCanceler; +} + +- (id)init:(nonnull NSMutableDictionary*)markerImageRequestCanceler { + if (self = [super init]) { + _markerImageRequestCanceler = markerImageRequestCanceler; + } + return self; +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-selector-name" +- (void)updateLeafMarker:(NMCLeafMarkerInfo* _Nonnull)info:(NMFMarker* _Nonnull)marker { + [super updateLeafMarker:info:marker]; + + RNCNaverMapClusterKey* key = (RNCNaverMapClusterKey*)info.key; + + NSString* identifier = key.identifier; + NSDictionary* image = key.image; + + __weak NMFMarker* weakMarker = marker; + + if (key.width > 0 && isValidNumber(key.width)) { + marker.width = key.width; + } + if (key.height > 0 && isValidNumber(key.height)) { + marker.height = key.height; + } + + if (key.bridge) { + marker.alpha = 0; + if (_markerImageRequestCanceler[identifier]) { + _markerImageRequestCanceler[identifier](); + _markerImageRequestCanceler[identifier] = nil; + } + _markerImageRequestCanceler[identifier] = + [Utils getImage:key.bridge + json:image + callback:^(NMFOverlayImage* overlayImage) { + dispatch_async(dispatch_get_main_queue(), ^() { + if (weakMarker) { + weakMarker.alpha = 1; + weakMarker.iconImage = !overlayImage ? NMF_MARKER_IMAGE_GREEN : overlayImage; + } + [_markerImageRequestCanceler removeObjectForKey:identifier]; + }); + }]; + } +} +#pragma clang diagnostic pop + +@end diff --git a/ios/Overlay/Marker/RNCNaverMapMarker.mm b/ios/Overlay/Marker/RNCNaverMapMarker.mm index 8b37c647..010ea4ee 100644 --- a/ios/Overlay/Marker/RNCNaverMapMarker.mm +++ b/ios/Overlay/Marker/RNCNaverMapMarker.mm @@ -87,8 +87,10 @@ - (void)insertReactSubview:(UIView*)subview atIndex:(NSInteger)atIndex { _imageCanceller = nil; } _isImageSetFromSubview = YES; + _inner.alpha = 0; // prevent default image is set after this logic in old arch dispatch_async(dispatch_get_main_queue(), [self, subview]() { + self.inner.alpha = 1; self.inner.iconImage = [NMFOverlayImage overlayImageWithImage:[self captureView:subview]]; }); } @@ -150,6 +152,7 @@ - (void)setImage:(NSDictionary*)image { if (_isImageSetFromSubview) { return; } + _inner.alpha = 0; // Cancel pending request if (_imageCanceller) { @@ -160,8 +163,10 @@ - (void)setImage:(NSDictionary*)image { _imageCanceller = [Utils getImage:[self bridge] json:image callback:^(NMFOverlayImage* image) { - dispatch_async(dispatch_get_main_queue(), - [self, image]() { self.inner.iconImage = image; }); + dispatch_async(dispatch_get_main_queue(), [self, image]() { + self.inner.alpha = 1; + self.inner.iconImage = image; + }); }]; } diff --git a/ios/RNCNaverMapView.h b/ios/RNCNaverMapView.h index a208b0b7..bd9f8ab3 100644 --- a/ios/RNCNaverMapView.h +++ b/ios/RNCNaverMapView.h @@ -11,6 +11,7 @@ #import #import +#import "FnUtil.h" #import "RCTFabricComponentsPlugins.h" #import "RNCNaverMapViewImpl.h" #import "Utils.h" diff --git a/ios/RNCNaverMapView.mm b/ios/RNCNaverMapView.mm index a577852f..ba79175f 100644 --- a/ios/RNCNaverMapView.mm +++ b/ios/RNCNaverMapView.mm @@ -179,6 +179,43 @@ - (void)updateProps:(Props::Shared const&)props oldProps:(Props::Shared const&)o } } + if (prev.clusters.key != next.clusters.key) { + NSMutableArray* arr = [NSMutableArray new]; + for (const auto& c : next.clusters.clusters) { + NSMutableArray* m = [NSMutableArray new]; + + for (const auto& marker : c.markers) { + [m addObject:@{ + @"identifier" : getNsStr(marker.identifier), + @"latitude" : @(marker.latitude), + @"longitude" : @(marker.longitude), + @"width" : @(marker.width), + @"height" : @(marker.height), + @"image" : @{ + @"reuseIdentifier" : getNsStr(marker.image.reuseIdentifier), + @"assetName" : getNsStr(marker.image.assetName), + @"httpUri" : getNsStr(marker.image.httpUri), + @"rnAssetUri" : getNsStr(marker.image.rnAssetUri), + @"symbol" : getNsStr(marker.image.symbol), + }, + }]; + } + + [arr addObject:@{ + @"key" : getNsStr(c.key), + @"screenDistance" : @(c.screenDistance), + @"minZoom" : @(c.minZoom), + @"maxZoom" : @(c.maxZoom), + @"animate" : @(c.animate), + @"markers" : m, + }]; + } + _view.clusters = @{ + @"key" : getNsStr(next.clusters.key), + @"clusters" : arr, + }; + } + [super updateProps:props oldProps:oldProps]; } diff --git a/ios/RNCNaverMapViewImpl.h b/ios/RNCNaverMapViewImpl.h index 34aa84e9..e54eecfd 100644 --- a/ios/RNCNaverMapViewImpl.h +++ b/ios/RNCNaverMapViewImpl.h @@ -12,6 +12,9 @@ #import "MacroUtil.h" #import "RCTConvert+NMFMapView.h" #import "RNCNaverMapCircle.h" +#import "RNCNaverMapClusterKey.h" +#import "RNCNaverMapClusterMarkerUpdater.h" +#import "RNCNaverMapLeafMarkerUpdater.h" #import "RNCNaverMapMarker.h" #import "RNCNaverMapPath.h" #import "RNCNaverMapPolygon.h" @@ -20,13 +23,21 @@ #import #import #import +#import +#import +#import +#import #import #import +#import #import #import #import #import +#import #import +#import +#import #import #import #import @@ -51,6 +62,7 @@ using namespace facebook::react; #endif > +@property(nonatomic, weak) RCTBridge* bridge; @property(nonatomic, assign) NMFMapType mapType; @property(nonatomic, assign) NSInteger layerGroups; @property(nonatomic, copy) NSDictionary* camera; @@ -83,6 +95,7 @@ using namespace facebook::react; @property(nonatomic, assign) BOOL isRotateGesturesEnabled; @property(nonatomic, assign) BOOL isStopGesturesEnabled; @property(nonatomic, strong) NSString* locale; +@property(nonatomic, strong) NSDictionary* clusters; @property(nonatomic, copy) RCTDirectEventBlock onInitialized; @property(nonatomic, copy) RCTDirectEventBlock onOptionChanged; diff --git a/ios/RNCNaverMapViewImpl.mm b/ios/RNCNaverMapViewImpl.mm index 07864c01..5c518b03 100644 --- a/ios/RNCNaverMapViewImpl.mm +++ b/ios/RNCNaverMapViewImpl.mm @@ -7,6 +7,12 @@ #import "RNCNaverMapViewImpl.h" +#ifdef RCT_NEW_ARCH_ENABLED +@interface RCTBridge (Private) ++ (RCTBridge*)currentBridge; +@end +#endif + NMFCameraUpdateAnimation getEasingAnimation(int easing) { if (easing == 1) { return NMFCameraUpdateAnimationNone; @@ -32,6 +38,11 @@ @implementation RNCNaverMapViewImpl { BOOL _initialCameraSet; BOOL _isFirstCameraAnimationRun; + NSMutableDictionary* _clustererRecord; + NSMutableDictionary* _clustererMarkerIdentifiers; + NSMutableDictionary* _clusterMarkerImageRequestCancelers; + NSString* _lastClustersPropKey; + // Array to manually track RN subviews // // AIRMap implicitly creates subviews that aren't regular RN children @@ -43,11 +54,26 @@ @implementation RNCNaverMapViewImpl { // https://github.com/facebook/react-native/blob/v0.16.0/Libraries/Text/RCTTextField.m#L20 NSMutableArray* _reactSubviews; } + +/** + https://github.com/software-mansion/react-native-screens/blob/a8bb418a8428befbb264ef958a5d7f7ea743048a/ios/RNSScreenStackHeaderSubview.mm#L100 + */ +- (RCTBridge*)bridge { +#ifdef RCT_NEW_ARCH_ENABLED + return [RCTBridge currentBridge]; +#else + return _bridge; +#endif +} + // MARK: - INIT & SETUP - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { _reactSubviews = [NSMutableArray new]; + _clustererRecord = [NSMutableDictionary new]; + _clustererMarkerIdentifiers = [NSMutableDictionary new]; + _clusterMarkerImageRequestCancelers = [NSMutableDictionary new]; [self.mapView addCameraDelegate:self]; [self.mapView setTouchDelegate:self]; @@ -66,6 +92,22 @@ - (instancetype)initWithFrame:(CGRect)frame { return self; } +- (void)dealloc { + NSArray* allKeys = [_clustererRecord allKeys]; + for (id clustererKey in allKeys) { + _clustererRecord[clustererKey].mapView = nil; + for (id markerIdentifier in _clustererMarkerIdentifiers[clustererKey]) { + if (_clusterMarkerImageRequestCancelers[markerIdentifier]) { + _clusterMarkerImageRequestCancelers[markerIdentifier](); + [_clusterMarkerImageRequestCancelers removeObjectForKey:markerIdentifier]; + } + } + } + [_clustererMarkerIdentifiers removeAllObjects]; + [_clustererRecord removeAllObjects]; + [_reactSubviews removeAllObjects]; +} + //- (NSArray > *)reactSubviews { // return std::dynamic_pointer_cast> *>(_reactSubviews); //} @@ -264,6 +306,95 @@ - (void)setExtent:(RNCNaverMapRegion*)extent { self.mapView.extent = [extent convertToNMGLatLngBounds]; } +- (void)setClusters:(NSDictionary*)clusters { + NSString* propKey = clusters[@"key"]; + + if ([_lastClustersPropKey isEqualToString:propKey]) { + return; + } + _lastClustersPropKey = propKey; + + NSArray* value = clusters[@"clusters"]; + + // removeClustererFor mutates _clusterRecord. So get all keys first. + for (id prevKey in [_clustererRecord allKeys]) { + [self removeClustererFor:prevKey]; + } + for (NSDictionary* clusterer in value) { + [self addClusterer:clusterer]; + } +} + +- (void)addClusterer:(nonnull NSDictionary*)dict { + + NSString* clustererKey = dict[@"key"]; + // double screenDistance = clamp([dict[@"screenDistance"] doubleValue], 1, 69); + double minZoom = clamp([dict[@"minZoom"] doubleValue], 1, 20); + double maxZoom = clamp([dict[@"maxZoom"] doubleValue], 1, 20); + BOOL animate = [dict[@"animate"] boolValue]; + NSDictionary* markers = dict[@"markers"]; + + NSMutableDictionary* markerDict = [NSMutableDictionary new]; + + NSMutableArray* markerIdentifiers = [NSMutableArray new]; + _clustererMarkerIdentifiers[clustererKey] = markerIdentifiers; + + for (NSDictionary* marker : markers) { + NSString* identifier = marker[@"identifier"]; + double latitude = [marker[@"latitude"] doubleValue]; + double longitude = [marker[@"longitude"] doubleValue]; + double width = [marker[@"width"] doubleValue]; + double height = [marker[@"height"] doubleValue]; + NSDictionary* image = marker[@"image"]; + RNCNaverMapClusterKey* markerKey = + [RNCNaverMapClusterKey markerKeyWithIdentifier:identifier + position:NMGLatLngMake(latitude, longitude) + bridge:[self bridge] + image:image + width:width + height:height]; + markerDict[markerKey] = [NSNull null]; + + [markerIdentifiers addObject:identifier]; + } + + NMCBuilder* builder = [[NMCBuilder alloc] init]; + // todo screenDistance not works. idk why + // builder.screenDistance = screenDistance; + builder.minZoom = minZoom; + builder.maxZoom = maxZoom; + builder.animate = animate; + + // RNCNaverMapClusterMarkerUpdater* clusterMarkerUpdater = + // [[RNCNaverMapClusterMarkerUpdater alloc] init]; + RNCNaverMapLeafMarkerUpdater* leafMarkerUpdater = + [[RNCNaverMapLeafMarkerUpdater alloc] init:_clusterMarkerImageRequestCancelers]; + // builder.clusterMarkerUpdater = clusterMarkerUpdater; + builder.leafMarkerUpdater = leafMarkerUpdater; + + NMCClusterer* clusterer = [builder build]; + leafMarkerUpdater.clusterer = clusterer; + [clusterer addAll:markerDict]; + + _clustererRecord[clustererKey] = clusterer; + clusterer.mapView = self.mapView; +} + +- (void)removeClustererFor:(nonnull NSString*)key { + NMCClusterer* clusterer = _clustererRecord[key]; + clusterer.mapView = nil; + + for (id markerIdentifier in _clustererMarkerIdentifiers[key]) { + if (_clusterMarkerImageRequestCancelers[markerIdentifier]) { + _clusterMarkerImageRequestCancelers[markerIdentifier](); + [_clusterMarkerImageRequestCancelers removeObjectForKey:markerIdentifier]; + } + } + + [_clustererMarkerIdentifiers removeObjectForKey:key]; + [_clustererRecord removeObjectForKey:key]; +} + // MARK: - EVENT - (void)mapViewOptionChanged:(NMFMapView*)mapView { diff --git a/ios/RNCNaverMapViewManager.mm b/ios/RNCNaverMapViewManager.mm index a839420c..3671561c 100644 --- a/ios/RNCNaverMapViewManager.mm +++ b/ios/RNCNaverMapViewManager.mm @@ -22,6 +22,7 @@ + (BOOL)requiresMainQueueSetup { - (UIView*)view { RNCNaverMapViewImpl* ret = [[RNCNaverMapViewImpl alloc] init]; + ret.bridge = self.bridge; return ret; } @@ -60,6 +61,7 @@ - (UIView*)view { RCT_EXPORT_VIEW_PROPERTY(isRotateGesturesEnabled, BOOL) RCT_EXPORT_VIEW_PROPERTY(isStopGesturesEnabled, BOOL) RCT_EXPORT_VIEW_PROPERTY(locale, NSString) +RCT_EXPORT_VIEW_PROPERTY(clusters, NSArray) // event RCT_EXPORT_VIEW_PROPERTY(onInitialized, RCTDirectEventBlock) diff --git a/ios/Util/FnUtil.h b/ios/Util/FnUtil.h index 45b34dfd..e4931451 100644 --- a/ios/Util/FnUtil.h +++ b/ios/Util/FnUtil.h @@ -58,4 +58,8 @@ static inline BOOL isNotEmptyString(NSString* str) { return ![str isKindOfClass:[NSNull class]] && str && str.length > 0; } +static inline double clamp(double a, double b, double c) { + return MIN(MAX(a, b), c); +} + #endif /* ifndef FnUtil_h */ diff --git a/package.json b/package.json index 33f8e6ca..43daccd6 100644 --- a/package.json +++ b/package.json @@ -29,31 +29,40 @@ ], "scripts": { "example": "yarn workspace @mj-studio/react-native-naver-map-example", + "dev": "yarn example start", "test": "jest", "typecheck": "tsc --noEmit", - "lint": "eslint \"**/*.{js,ts,tsx}\"", - "format:clang": "./script/clang-format.sh", - "lint:clang": "./script/clang-lint.sh", - "t": "yarn lint && yarn typecheck && yarn lint:clang", + "lint": "./script/lint.sh", + "t": "yarn lint", + "lint:js": "eslint \"**/*.{js,ts,tsx}\"", + "format": "yarn format:android && yarn format:ios", + "format:ios": "./script/clang-format.sh", + "lint:ios": "./script/clang-lint.sh", + "format:android": "./script/ktlint-format.sh", + "lint:android": "./script/ktlint-lint.sh", "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib", "prepare": "husky", "release": "./script/release.sh", "codegen:android": "cd example/android && ./gradlew generateCodegenArtifactsFromSchema", "codegen:ios": "node example/node_modules/react-native/scripts/generate-codegen-artifacts.js --path example/ --outputPath example/ios", "codegen": "yarn codegen:android && yarn codegen:ios", - "studio": "studio $(pwd)/android", - "studio:example": "studio $(pwd)/example/android", + "studio": "studio $(pwd)/example/android", "xcode": "open $(pwd)/example/ios/NaverMapExample.xcworkspace", "pod:old": "cd example/ios && bundle install && bundle exec pod install", "pod:new": "cd example/ios && bundle install && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install", "prepack": "yarn build:expo-config-plugin && yarn build:docs && bob build", "build:expo-config-plugin": "tsc --project ./expo-config-plugin", - "build:docs": "rm -rf docs && typedoc" + "build:docs": "rm -rf docs && typedoc", + "old:pod": "./script/arch-convert.sh false true", + "new:pod": "./script/arch-convert.sh true true", + "old": "./script/arch-convert.sh false false", + "new": "./script/arch-convert.sh true false" }, "keywords": [ "react-native", - "ios", - "android" + "naver-map", + "naver", + "react-native-naver-map" ], "repository": { "type": "git", @@ -76,6 +85,7 @@ "@release-it/conventional-changelog": "^5.0.0", "@types/invariant": "^2.2.37", "@types/jest": "^29.5.5", + "@types/object-hash": "^3", "@types/react": "^18.2.44", "commitlint": "^17.0.2", "del-cli": "^5.1.0", @@ -134,44 +144,49 @@ }, "plugins": { "@release-it/conventional-changelog": { - "header": "# Changelog", - "preset": "conventionalcommits" - } - } - }, - "eslintConfig": { - "root": true, - "extends": [ - "@react-native", - "prettier" - ], - "rules": { - "prettier/prettier": [ - "error", - { - "quoteProps": "consistent", - "singleQuote": true, - "tabWidth": 2, - "trailingComma": "es5", - "useTabs": false + "preset": { + "name": "conventionalcommits", + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Chores" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "style", + "section": "Style Code" + }, + { + "type": "refactor", + "section": "Refactor" + }, + { + "type": "perf", + "section": "Performance Enhance" + }, + { + "type": "test", + "section": "Tests" + } + ] + }, + "writerOpts": { + "groupBy": "scope" } - ], - "react-native/no-inline-styles": "off" + } } }, - "eslintIgnore": [ - "node_modules/", - "lib/", - "expo-config-plugin", - "docs/" - ], - "prettier": { - "quoteProps": "consistent", - "singleQuote": true, - "tabWidth": 2, - "trailingComma": "es5", - "useTabs": false - }, "react-native-builder-bob": { "source": "src", "output": "lib", @@ -192,6 +207,7 @@ "jsSrcsDir": "src" }, "dependencies": { - "invariant": "^2.2.4" + "invariant": "^2.2.4", + "object-hash": "^3.0.0" } } diff --git a/script/arch-convert.sh b/script/arch-convert.sh new file mode 100755 index 00000000..fa015533 --- /dev/null +++ b/script/arch-convert.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# Gradle property to change +PROPERTY="newArchEnabled" + +# New value from first command line argument +NEW_VALUE="$1" +POD="$2" + +# Validate new value is either "true" or "false" +if [ "${NEW_VALUE}" != "true" ] && [ "${NEW_VALUE}" != "false" ]; then + echo "Error: The argument should be either 'true' or 'false'" + exit 1 +fi + +# Gradle properties file +FILE="example/android/gradle.properties" + +# Create a backup of the original file +# cp ${FILE} ${FILE}.bak + +# Use 'sed' to replace the property value +sed -i '' -e "s/${PROPERTY}=.*/${PROPERTY}=${NEW_VALUE}/" ${FILE} + +if [[ $POD == 'true' ]]; then + if [[ $NEW_VALUE == 'true' ]]; then + yarn pod:new + else + yarn pod:old + fi +fi + + + diff --git a/script/clang-format.sh b/script/clang-format.sh index 7d0b85f8..5bf0164c 100755 --- a/script/clang-format.sh +++ b/script/clang-format.sh @@ -2,7 +2,7 @@ if which clang-format >/dev/null; then find ios -type f \( -name "*.h" -o -name "*.cpp" -o -name "*.m" -o -name "*.mm" \) -print0 | while read -d $'\0' file; do - echo "โญ๏ธclang-format $file" + echo "๐Ÿฆ‹ clang-format $file" clang-format -i "$file" done else diff --git a/script/clang-lint.sh b/script/clang-lint.sh index 42edd773..7a4d2f5a 100755 --- a/script/clang-lint.sh +++ b/script/clang-lint.sh @@ -2,7 +2,7 @@ if which clang-format >/dev/null; then find ios -type f \( -name "*.h" -o -name "*.cpp" -o -name "*.m" -o -name "*.mm" \) -print0 | while read -d $'\0' file; do - echo "โญ๏ธclang-lint $file" + echo "๐Ÿฆ‹ clang-lint $file" clang-format --dry-run -Werror "$file" if [[ $? = 1 ]]; then echo "โŒ clang lint failed on '$file'" diff --git a/script/ktlint-format.sh b/script/ktlint-format.sh new file mode 100755 index 00000000..602aa5ce --- /dev/null +++ b/script/ktlint-format.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +if which ktlint >/dev/null; then + echo "๐ŸŒŠ ktlint android" + ktlint --color --format --relative --editorconfig=example/android/.editorconfig android +else + echo "warning: ktlint not installed, download from https://pinterest.github.io/ktlint/latest/install/setup/" +fi diff --git a/script/ktlint-lint.sh b/script/ktlint-lint.sh new file mode 100755 index 00000000..71ce5286 --- /dev/null +++ b/script/ktlint-lint.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +if which ktlint >/dev/null; then + echo "๐ŸŒŠ ktlint android" + ktlint --relative --color --editorconfig=example/android/.editorconfig android +else + echo "warning: ktlint not installed, download from https://pinterest.github.io/ktlint/latest/install/setup/" +fi diff --git a/script/lint.sh b/script/lint.sh new file mode 100755 index 00000000..53af57f8 --- /dev/null +++ b/script/lint.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -e +echo 'โš’๏ธ eslint .' +yarn lint:js +yarn lint:ios +yarn lint:android +echo '๐Ÿ‹ typescript .' +yarn typecheck \ No newline at end of file diff --git a/src/component/NaverMapCircleOverlay.tsx b/src/component/NaverMapCircleOverlay.tsx index 3bd6a392..7f76f2e5 100644 --- a/src/component/NaverMapCircleOverlay.tsx +++ b/src/component/NaverMapCircleOverlay.tsx @@ -33,8 +33,8 @@ export const NaverMapCircleOverlay = ({ longitude, zIndex = Const.Z_SHAPE, isHidden = false, - minZoom = 0, - maxZoom = 1000, + minZoom = Const.MIN_ZOOM, + maxZoom = Const.MAX_ZOOM, isMinZoomInclusive = true, isMaxZoomInclusive = true, diff --git a/src/component/NaverMapMarkerOverlay.tsx b/src/component/NaverMapMarkerOverlay.tsx index 963f40fc..fc4c1114 100644 --- a/src/component/NaverMapMarkerOverlay.tsx +++ b/src/component/NaverMapMarkerOverlay.tsx @@ -18,6 +18,7 @@ import { } from '../internal/Util'; import type { Point } from '../types/Point'; import type { MarkerImageProp } from '../types/MarkerImageProp'; +import hash from 'object-hash'; export interface CaptionType { /** ์บก์…˜์œผ๋กœ ํ‘œ์‹œํ•  ํ…์ŠคํŠธ๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -59,11 +60,15 @@ export interface CaptionType { /** * ํŠน์ • ์คŒ ๋ ˆ๋ฒจ์—์„œ๋งŒ ์บก์…˜์ด ๋‚˜ํƒ€๋‚˜๋„๋ก ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. * ์นด๋ฉ”๋ผ์˜ ์คŒ ๋ ˆ๋ฒจ์ด minZoom๊ณผ maxZoom ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋ฉด ์บก์…˜์ด ์ˆจ๊ฒจ์ง€๊ณ  ์•„์ด์ฝ˜๋งŒ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. + * + * @default 0 */ minZoom?: Double; /** * ํŠน์ • ์คŒ ๋ ˆ๋ฒจ์—์„œ๋งŒ ์บก์…˜์ด ๋‚˜ํƒ€๋‚˜๋„๋ก ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. * ์นด๋ฉ”๋ผ์˜ ์คŒ ๋ ˆ๋ฒจ์ด minZoom๊ณผ maxZoom ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋ฉด ์บก์…˜์ด ์ˆจ๊ฒจ์ง€๊ณ  ์•„์ด์ฝ˜๋งŒ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. + * + * @default 21 */ maxZoom?: Double; } @@ -101,19 +106,23 @@ export interface SubCaptionType { /** * ํŠน์ • ์คŒ ๋ ˆ๋ฒจ์—์„œ๋งŒ ์บก์…˜์ด ๋‚˜ํƒ€๋‚˜๋„๋ก ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. * ์นด๋ฉ”๋ผ์˜ ์คŒ ๋ ˆ๋ฒจ์ด minZoom๊ณผ maxZoom ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋ฉด ์บก์…˜์ด ์ˆจ๊ฒจ์ง€๊ณ  ์•„์ด์ฝ˜๋งŒ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. + * + * @default 0 */ minZoom?: Double; /** * ํŠน์ • ์คŒ ๋ ˆ๋ฒจ์—์„œ๋งŒ ์บก์…˜์ด ๋‚˜ํƒ€๋‚˜๋„๋ก ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. * ์นด๋ฉ”๋ผ์˜ ์คŒ ๋ ˆ๋ฒจ์ด minZoom๊ณผ maxZoom ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋ฉด ์บก์…˜์ด ์ˆจ๊ฒจ์ง€๊ณ  ์•„์ด์ฝ˜๋งŒ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. + * + * @default 21 */ maxZoom?: Double; } const defaultCaptionProps = { text: '', textSize: 12, - minZoom: 0, - maxZoom: 9999, + minZoom: Const.MIN_ZOOM, + maxZoom: Const.MAX_ZOOM, color: 'black', haloColor: 'transparent', requestedWidth: 0, @@ -121,8 +130,8 @@ const defaultCaptionProps = { const defaultSubCaptionProps = { text: '', textSize: 10, - minZoom: 0, - maxZoom: 9999, + minZoom: Const.MIN_ZOOM, + maxZoom: Const.MAX_ZOOM, color: 'black', haloColor: 'transparent', requestedWidth: 0, @@ -312,8 +321,8 @@ export const NaverMapMarkerOverlay = ({ longitude, zIndex = Const.Z_MARKER, isHidden = false, - minZoom = 0, - maxZoom = 1000, + minZoom = Const.MIN_ZOOM, + maxZoom = Const.MAX_ZOOM, isMinZoomInclusive = true, isMaxZoomInclusive = true, @@ -362,7 +371,7 @@ export const NaverMapMarkerOverlay = ({ } satisfies Omit; return Object.assign(inner, { - key: JSON.stringify(inner), + key: hash(inner), }); }, [caption]); @@ -379,7 +388,7 @@ export const NaverMapMarkerOverlay = ({ }; return Object.assign(inner, { - key: JSON.stringify(inner), + key: hash(inner), }); }, [subCaption]); diff --git a/src/component/NaverMapPathOverlay.tsx b/src/component/NaverMapPathOverlay.tsx index 2012bf87..a0e224d6 100644 --- a/src/component/NaverMapPathOverlay.tsx +++ b/src/component/NaverMapPathOverlay.tsx @@ -94,8 +94,8 @@ export interface NaverMapPathOverlayProps extends BaseOverlayProps { export const NaverMapPathOverlay = ({ zIndex = Const.Z_SHAPE, isHidden = false, - minZoom = 0, - maxZoom = 1000, + minZoom = Const.MIN_ZOOM, + maxZoom = Const.MAX_ZOOM, isMinZoomInclusive = true, isMaxZoomInclusive = true, diff --git a/src/component/NaverMapPolygonOverlay.tsx b/src/component/NaverMapPolygonOverlay.tsx index 8194b596..f1469c60 100644 --- a/src/component/NaverMapPolygonOverlay.tsx +++ b/src/component/NaverMapPolygonOverlay.tsx @@ -46,8 +46,8 @@ export interface NaverMapPolygonOverlayProps extends BaseOverlayProps { export const NaverMapPolygonOverlay = ({ zIndex = Const.Z_SHAPE, isHidden = false, - minZoom = 0, - maxZoom = 1000, + minZoom = Const.MIN_ZOOM, + maxZoom = Const.MAX_ZOOM, isMinZoomInclusive = true, isMaxZoomInclusive = true, diff --git a/src/component/NaverMapPolylineOverlay.tsx b/src/component/NaverMapPolylineOverlay.tsx index ce5704f0..4b03aa10 100644 --- a/src/component/NaverMapPolylineOverlay.tsx +++ b/src/component/NaverMapPolylineOverlay.tsx @@ -60,8 +60,8 @@ export interface NaverMapPolylineOverlayProps extends BaseOverlayProps { export const NaverMapPolylineOverlay = ({ zIndex = Const.Z_SHAPE, isHidden = false, - minZoom = 0, - maxZoom = 1000, + minZoom = Const.MIN_ZOOM, + maxZoom = Const.MAX_ZOOM, isMinZoomInclusive = true, isMaxZoomInclusive = true, diff --git a/src/component/NaverMapView.tsx b/src/component/NaverMapView.tsx index 48f6dfb5..a257efe5 100644 --- a/src/component/NaverMapView.tsx +++ b/src/component/NaverMapView.tsx @@ -1,6 +1,8 @@ import { default as NativeNaverMapView, Commands, + type NativeClustersProp, + type NativeClusterProp, } from '../spec/RNCNaverMapViewNativeComponent'; import React, { @@ -8,6 +10,7 @@ import React, { type ForwardedRef, useImperativeHandle, useRef, + useMemo, } from 'react'; import { type ViewProps, type NativeSyntheticEvent } from 'react-native'; import type { MapType } from '../types/MapType'; @@ -24,9 +27,12 @@ import { cameraEasingToNumber, cameraChangeReasonFromNumber, createCameraInstance, + convertJsImagePropToNativeProp, } from '../internal/Util'; import type { CameraMoveBaseParams } from '../types/CameraMoveBaseParams'; import type { CameraAnimationEasing } from '../types/CameraAnimationEasing'; +import type { ClusterMarkerProp } from '../types/ClusterMarkerProp'; +import hash from 'object-hash'; /** * @category Hell @@ -336,6 +342,40 @@ export interface NaverMapViewProps extends ViewProps { */ locale?: string; + /** + * ํ•œ ํ™”๋ฉด์— ๋Œ€๋Ÿ‰์˜ ๋งˆ์ปค๊ฐ€ ๋…ธ์ถœ๋˜๋ฉด ์„ฑ๋Šฅ์ด ์ €ํ•˜๋  ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์—ฌ๋Ÿฌ ๋งˆ์ปค๊ฐ€ ๊ฒน์ณ ๋‚˜ํƒ€๋‚˜๋ฏ€๋กœ ์‹œ์ธ์„ฑ์ด ๋–จ์–ด์ง‘๋‹ˆ๋‹ค. + * ๋งˆ์ปค์˜ ๊ฒน์นจ ์ฒ˜๋ฆฌ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ฉด ์‹œ์ธ์„ฑ์„ ์ผ๋ถ€ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์œผ๋‚˜ ๊ฒน์นจ ์ฒ˜๋ฆฌ๋กœ ์ธํ•ด ๊ฐ€๋ ค์ง„ ๋งˆ์ปค์˜ ์ •๋ณด๋ฅผ ์•Œ ์ˆ˜ ์—†์œผ๋ฉฐ, ์„ฑ๋Šฅ๋„ ์—ฌ์ „ํžˆ ์ €ํ•˜๋ฉ๋‹ˆ๋‹ค. + * ๋งˆ์ปค ํด๋Ÿฌ์Šคํ„ฐ๋ง ๊ธฐ๋Šฅ์„ ์ด์šฉํ•˜๋ฉด ์นด๋ฉ”๋ผ์˜ ์คŒ ๋ ˆ๋ฒจ์— ๋”ฐ๋ผ ๊ทผ์ ‘ํ•œ ๋งˆ์ปค๋ฅผ ํด๋Ÿฌ์Šคํ„ฐ๋งํ•ด ์„ฑ๋Šฅ๊ณผ ์‹œ์ธ์„ฑ์„ ๋ชจ๋‘ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + */ + clusters?: { + markers: ClusterMarkerProp[]; + screenDistance?: number; + /** + * ํด๋Ÿฌ์Šคํ„ฐ๋งํ•  ์ตœ์†Œ ์คŒ ๋ ˆ๋ฒจ. + * + * ์นด๋ฉ”๋ผ์˜ ์คŒ ๋ ˆ๋ฒจ์ด ์ตœ์†Œ ์คŒ ๋ ˆ๋ฒจ๋ณด๋‹ค ๋‚ฎ๋‹ค๋ฉด ๋‘ ๋ฐ์ดํ„ฐ๊ฐ€ ํ™”๋ฉด์ƒ ๊ธฐ์ค€ ๊ฑฐ๋ฆฌ๋ณด๋‹ค ๊ฐ€๊น๋”๋ผ๋„ ๋” ์ด์ƒ ํด๋Ÿฌ์Šคํ„ฐ๋ง๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + * ์˜ˆ๋ฅผ ๋“ค์–ด, ํด๋Ÿฌ์Šคํ„ฐ๋งํ•  ์ตœ์†Œ ์คŒ ๋ ˆ๋ฒจ์ด 4๋ผ๋ฉด, ์นด๋ฉ”๋ผ์˜ ์คŒ ๋ ˆ๋ฒจ์„ 3๋ ˆ๋ฒจ ์ดํ•˜๋กœ ์ถ•์†Œํ•˜๋”๋ผ๋„ 4๋ ˆ๋ฒจ์˜ ํด๋Ÿฌ์Šคํ„ฐ๊ฐ€ ๋” ์ด์ƒ ํด๋Ÿฌ์Šคํ„ฐ๋ง๋˜์ง€ ์•Š๊ณ  ๊ทธ๋Œ€๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. + * + * @default 0 + */ + minZoom?: number; + /** + * ํด๋Ÿฌ์Šคํ„ฐ๋งํ•  ์ตœ๋Œ€ ์คŒ ๋ ˆ๋ฒจ. + * + * ์นด๋ฉ”๋ผ์˜ ์คŒ ๋ ˆ๋ฒจ์ด ์ตœ๋Œ€ ์คŒ ๋ ˆ๋ฒจ๋ณด๋‹ค ๋†’๋‹ค๋ฉด ๋‘ ๋ฐ์ดํ„ฐ๊ฐ€ ํ™”๋ฉด์ƒ ๊ธฐ์ค€ ๊ฑฐ๋ฆฌ๋ณด๋‹ค ๊ฐ€๊น๋”๋ผ๋„ ๋” ์ด์ƒ ํด๋Ÿฌ์Šคํ„ฐ๋ง๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + * ์˜ˆ๋ฅผ ๋“ค์–ด, ํด๋Ÿฌ์Šคํ„ฐ๋งํ•  ์ตœ๋Œ€ ์คŒ ๋ ˆ๋ฒจ์ด 16์ด๋ผ๋ฉด, ์นด๋ฉ”๋ผ์˜ ์คŒ ๋ ˆ๋ฒจ์„ 17๋ ˆ๋ฒจ ์ด์ƒ์œผ๋กœ ํ™•๋Œ€ํ•˜๋ฉด ๋ชจ๋“  ๋ฐ์ดํ„ฐ๊ฐ€ ํด๋Ÿฌ์Šคํ„ฐ๋ง๋˜์ง€ ์•Š๊ณ  ๋‚ฑ๊ฐœ๋กœ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํด๋Ÿฌ์Šคํ„ฐ๋งํ•  ์ตœ๋Œ€ ์คŒ ๋ ˆ๋ฒจ์ด ์ง€๋„์˜ ์ตœ๋Œ€ ์คŒ ๋ ˆ๋ฒจ๋ณด๋‹ค ํฌ๊ฑฐ๋‚˜ ๊ฐ™๋‹ค๋ฉด ์นด๋ฉ”๋ผ๋ฅผ ์ตœ๋Œ€ ์คŒ ๋ ˆ๋ฒจ๋กœ ํ™•๋Œ€ํ•˜๋”๋ผ๋„ ์ผ๋ถ€ ๋ฐ์ดํ„ฐ๋Š” ์—ฌ์ „ํžˆ ํด๋Ÿฌ์Šคํ„ฐ๋ง๋œ ์ฑ„ ๋” ์ด์ƒ ํŽผ์ณ์ง€์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + * + * @default 21 + */ + maxZoom?: number; + /** + * ์นด๋ฉ”๋ผ ํ™•๋Œ€/์ถ•์†Œ์‹œ ํด๋Ÿฌ์Šคํ„ฐ๊ฐ€ ํŽผ์ณ์ง€๋Š”/ํ•ฉ์ณ์ง€๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ ์šฉํ• ์ง€ ์—ฌ๋ถ€. + * + * @default true + */ + animate?: boolean; + }[]; + /** * ์ง€๋„ ๊ฐ์ฒด๊ฐ€ ์ดˆ๊ธฐํ™”๊ฐ€ ์™„๋ฃŒ๋œ ๋’ค์— ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. * @@ -428,6 +468,28 @@ function clamp(v: number, min: number, max: number): number { return Math.min(max, Math.max(min, v)); } +const southKoreaExtent: Region = { + latitude: 31.43, + longitude: 122.37, + latitudeDelta: 44.35 - 31.43, + longitudeDelta: 132 - 122.37, +}; + +const nullRegion: Region = { + latitude: Const.NULL_NUMBER, + longitude: Const.NULL_NUMBER, + latitudeDelta: Const.NULL_NUMBER, + longitudeDelta: Const.NULL_NUMBER, +}; + +const nullCamera: Camera = { + latitude: Const.NULL_NUMBER, + longitude: Const.NULL_NUMBER, + zoom: Const.NULL_NUMBER, + tilt: Const.NULL_NUMBER, + bearing: Const.NULL_NUMBER, +}; + export const NaverMapView = forwardRef( ( { @@ -478,11 +540,52 @@ export const NaverMapView = forwardRef( isStopGesturesEnabled = true, isUseTextureViewAndroid = false, locale, + clusters, ...rest }: NaverMapViewProps, ref: ForwardedRef ) => { + const _clusters = useMemo(() => { + if (!clusters || clusters.length === 0) { + return { key: '', clusters: [] }; + } + let propKey = ''; + const ret: NativeClusterProp[] = []; + for (const { + animate = true, + markers, + // eslint-disable-next-line @typescript-eslint/no-shadow + minZoom = Const.MIN_ZOOM, + // eslint-disable-next-line @typescript-eslint/no-shadow + maxZoom = Const.MAX_ZOOM, + screenDistance = Const.DEFAULT_SCREEN_DISTANCE, + } of clusters) { + const key = hash([animate, maxZoom, minZoom, screenDistance, markers]); + + ret.push({ + key, + animate, + markers: markers.map((m) => ({ + ...m, + image: convertJsImagePropToNativeProp( + m.image ?? { symbol: 'green' } + ), + })), + maxZoom, + minZoom, + screenDistance, + }); + + propKey += `${key}---`; + } + + return { + key: hash(propKey), + clusters: ret, + }; + }, [clusters]); + const innerRef = useRef(null); const onCameraChanged = useStableCallback( @@ -614,6 +717,7 @@ export const NaverMapView = forwardRef( }), [] ); + return ( ); diff --git a/src/index.tsx b/src/index.tsx index a1075b09..f018c080 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -44,3 +44,4 @@ export * from './types/JoinType'; export * from './types/LocationTrackingMode'; export * from './types/CameraMoveBaseParams'; export * from './types/Point'; +export * from './types/ClusterMarkerProp'; diff --git a/src/internal/util/Const.ts b/src/internal/util/Const.ts index 3a890b14..1980d72a 100644 --- a/src/internal/util/Const.ts +++ b/src/internal/util/Const.ts @@ -20,6 +20,9 @@ export const Const = { Z_SHAPE: -200000, Z_GROUND: -300000, Z_BACKGROUND: -400000, + MIN_ZOOM: 0, + MAX_ZOOM: 21, + DEFAULT_SCREEN_DISTANCE: 70, } satisfies (Record & {}) & { DEFAULT_EASING: CameraAnimationEasing; }; diff --git a/src/spec/RNCNaverMapViewNativeComponent.ts b/src/spec/RNCNaverMapViewNativeComponent.ts index 2222f812..f07a398a 100644 --- a/src/spec/RNCNaverMapViewNativeComponent.ts +++ b/src/spec/RNCNaverMapViewNativeComponent.ts @@ -13,6 +13,24 @@ import React from 'react'; * [comments](https://github.com/reactwg/react-native-new-architecture/discussions/91#discussioncomment-4282452) */ +type Coord = { + latitude: Double; + longitude: Double; +}; +type NativeImageProp = Readonly<{ + symbol?: string; + rnAssetUri?: string; + httpUri?: string; + assetName?: string; + reuseIdentifier?: string; +}>; + +type ClusterMarker = Coord & { + identifier: string; + image?: NativeImageProp; + width?: Double; + height?: Double; +}; type Camera = { latitude: Double; longitude: Double; @@ -27,6 +45,18 @@ type Region = { longitudeDelta: Double; }; type LogoAlign = 'TopLeft' | 'TopRight' | 'BottomLeft' | 'BottomRight'; +export type NativeClusterProp = { + key: string; + markers: ClusterMarker[]; + screenDistance?: Double; + minZoom?: Double; + maxZoom?: Double; + animate?: boolean; +}; +export type NativeClustersProp = Readonly<{ + key: string; + clusters: ReadonlyArray; +}>; //////////////////// @@ -94,6 +124,8 @@ interface Props extends ViewProps { locale?: string; + clusters?: NativeClustersProp; + onInitialized?: DirectEventHandler>; onOptionChanged?: DirectEventHandler>; onCameraChanged?: DirectEventHandler< diff --git a/src/types/BaseOverlayProps.ts b/src/types/BaseOverlayProps.ts index 5224b8a9..3f6a4425 100644 --- a/src/types/BaseOverlayProps.ts +++ b/src/types/BaseOverlayProps.ts @@ -23,7 +23,7 @@ export interface BaseOverlayProps { /** * ์ง€๋„์— ๋ณด์ด๋Š” ์ตœ๋Œ€ ์คŒ ๋ ˆ๋ฒจ์ž…๋‹ˆ๋‹ค. * - * @default 9999 + * @default 21 */ maxZoom?: number; /** ์ตœ์†Œ ์คŒ ๋ ˆ๋ฒจ์ด ํฌํ•จ๋  ๋•Œ๋„ ๋ณด์ด๋Š” ์ง€ ์—ฌ๋ถ€์ž…๋‹ˆ๋‹ค. */ diff --git a/src/types/ClusterMarkerProp.ts b/src/types/ClusterMarkerProp.ts new file mode 100644 index 00000000..c30a999a --- /dev/null +++ b/src/types/ClusterMarkerProp.ts @@ -0,0 +1,8 @@ +import type { Coord, MarkerImageProp } from '@mj-studio/react-native-naver-map'; + +export interface ClusterMarkerProp extends Coord { + identifier: string; + image?: MarkerImageProp; + width?: number; + height?: number; +} diff --git a/yarn.lock b/yarn.lock index 4bb48d5c..f88fe860 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2488,6 +2488,7 @@ __metadata: "@release-it/conventional-changelog": "npm:^5.0.0" "@types/invariant": "npm:^2.2.37" "@types/jest": "npm:^29.5.5" + "@types/object-hash": "npm:^3" "@types/react": "npm:^18.2.44" commitlint: "npm:^17.0.2" del-cli: "npm:^5.1.0" @@ -2497,6 +2498,7 @@ __metadata: husky: "npm:^9.0.11" invariant: "npm:^2.2.4" jest: "npm:^29.7.0" + object-hash: "npm:^3.0.0" prettier: "npm:^3.0.3" react: "npm:18.2.0" react-native: "npm:0.73.6" @@ -3420,6 +3422,13 @@ __metadata: languageName: node linkType: hard +"@types/object-hash@npm:^3": + version: 3.0.6 + resolution: "@types/object-hash@npm:3.0.6" + checksum: 10/2c7979d4e540af817b99c09fb4c2fed1c0b0e14342df474d8dcde4165a82c440b038341fd66fe998d9f86acdd5cccc65ba8092315e39e7c2114f945fa70aaa56 + languageName: node + linkType: hard + "@types/parse-json@npm:^4.0.0": version: 4.0.2 resolution: "@types/parse-json@npm:4.0.2" @@ -10215,6 +10224,13 @@ __metadata: languageName: node linkType: hard +"object-hash@npm:^3.0.0": + version: 3.0.0 + resolution: "object-hash@npm:3.0.0" + checksum: 10/f498d456a20512ba7be500cef4cf7b3c183cc72c65372a549c9a0e6dd78ce26f375e9b1315c07592d3fde8f10d5019986eba35970570d477ed9a2a702514432a + languageName: node + linkType: hard + "object-inspect@npm:^1.13.1": version: 1.13.1 resolution: "object-inspect@npm:1.13.1"