@lugg/maps 0.2.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/LICENSE +20 -0
  2. package/LuggMaps.podspec +23 -0
  3. package/README.md +119 -0
  4. package/android/build.gradle +78 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/com/luggmaps/LuggMapsGoogleMapView.kt +438 -0
  8. package/android/src/main/java/com/luggmaps/LuggMapsGoogleMapViewManager.kt +144 -0
  9. package/android/src/main/java/com/luggmaps/LuggMapsMarkerView.kt +134 -0
  10. package/android/src/main/java/com/luggmaps/LuggMapsMarkerViewManager.kt +69 -0
  11. package/android/src/main/java/com/luggmaps/LuggMapsPackage.kt +13 -0
  12. package/android/src/main/java/com/luggmaps/LuggMapsPolylineView.kt +79 -0
  13. package/android/src/main/java/com/luggmaps/LuggMapsPolylineViewManager.kt +71 -0
  14. package/android/src/main/java/com/luggmaps/LuggMapsWrapperView.kt +39 -0
  15. package/android/src/main/java/com/luggmaps/LuggMapsWrapperViewManager.kt +25 -0
  16. package/android/src/main/java/com/luggmaps/core/PolylineAnimator.kt +180 -0
  17. package/android/src/main/java/com/luggmaps/events/CameraIdleEvent.kt +21 -0
  18. package/android/src/main/java/com/luggmaps/events/CameraMoveEvent.kt +28 -0
  19. package/app.plugin.js +1 -0
  20. package/ios/LuggMapsAppleMapView.h +16 -0
  21. package/ios/LuggMapsAppleMapView.mm +544 -0
  22. package/ios/LuggMapsGoogleMapView.h +13 -0
  23. package/ios/LuggMapsGoogleMapView.mm +439 -0
  24. package/ios/LuggMapsMarkerView.h +29 -0
  25. package/ios/LuggMapsMarkerView.mm +154 -0
  26. package/ios/LuggMapsPolylineView.h +27 -0
  27. package/ios/LuggMapsPolylineView.mm +116 -0
  28. package/ios/LuggMapsWrapperView.h +9 -0
  29. package/ios/LuggMapsWrapperView.mm +36 -0
  30. package/ios/core/GMSPolylineAnimator.h +11 -0
  31. package/ios/core/GMSPolylineAnimator.m +151 -0
  32. package/ios/core/MKPolylineAnimator.h +12 -0
  33. package/ios/core/MKPolylineAnimator.m +252 -0
  34. package/ios/core/PolylineAnimatorBase.h +22 -0
  35. package/ios/core/PolylineAnimatorBase.m +35 -0
  36. package/ios/events/CameraIdleEvent.h +24 -0
  37. package/ios/events/CameraMoveEvent.h +26 -0
  38. package/ios/extensions/MKMapView+Zoom.h +19 -0
  39. package/ios/extensions/MKMapView+Zoom.m +45 -0
  40. package/lib/module/MapView.js +87 -0
  41. package/lib/module/MapView.js.map +1 -0
  42. package/lib/module/MapView.types.js +4 -0
  43. package/lib/module/MapView.types.js.map +1 -0
  44. package/lib/module/Marker.js +34 -0
  45. package/lib/module/Marker.js.map +1 -0
  46. package/lib/module/Marker.types.js +4 -0
  47. package/lib/module/Marker.types.js.map +1 -0
  48. package/lib/module/Polyline.js +30 -0
  49. package/lib/module/Polyline.js.map +1 -0
  50. package/lib/module/Polyline.types.js +4 -0
  51. package/lib/module/Polyline.types.js.map +1 -0
  52. package/lib/module/fabric/LuggMapsAppleMapViewNativeComponent.ts +73 -0
  53. package/lib/module/fabric/LuggMapsGoogleMapViewNativeComponent.ts +74 -0
  54. package/lib/module/fabric/LuggMapsMarkerViewNativeComponent.ts +25 -0
  55. package/lib/module/fabric/LuggMapsPolylineViewNativeComponent.ts +19 -0
  56. package/lib/module/fabric/LuggMapsWrapperViewNativeComponent.ts +8 -0
  57. package/lib/module/index.js +6 -0
  58. package/lib/module/index.js.map +1 -0
  59. package/lib/module/package.json +1 -0
  60. package/lib/module/types.js +2 -0
  61. package/lib/module/types.js.map +1 -0
  62. package/lib/typescript/package.json +1 -0
  63. package/lib/typescript/plugin/src/index.d.ts +16 -0
  64. package/lib/typescript/plugin/src/index.d.ts.map +1 -0
  65. package/lib/typescript/plugin/src/withMapsAndroid.d.ts +6 -0
  66. package/lib/typescript/plugin/src/withMapsAndroid.d.ts.map +1 -0
  67. package/lib/typescript/plugin/src/withMapsIOS.d.ts +6 -0
  68. package/lib/typescript/plugin/src/withMapsIOS.d.ts.map +1 -0
  69. package/lib/typescript/src/MapView.d.ts +12 -0
  70. package/lib/typescript/src/MapView.d.ts.map +1 -0
  71. package/lib/typescript/src/MapView.types.d.ts +102 -0
  72. package/lib/typescript/src/MapView.types.d.ts.map +1 -0
  73. package/lib/typescript/src/Marker.d.ts +6 -0
  74. package/lib/typescript/src/Marker.d.ts.map +1 -0
  75. package/lib/typescript/src/Marker.types.d.ts +32 -0
  76. package/lib/typescript/src/Marker.types.d.ts.map +1 -0
  77. package/lib/typescript/src/Polyline.d.ts +6 -0
  78. package/lib/typescript/src/Polyline.d.ts.map +1 -0
  79. package/lib/typescript/src/Polyline.types.d.ts +24 -0
  80. package/lib/typescript/src/Polyline.types.d.ts.map +1 -0
  81. package/lib/typescript/src/fabric/LuggMapsAppleMapViewNativeComponent.d.ts +47 -0
  82. package/lib/typescript/src/fabric/LuggMapsAppleMapViewNativeComponent.d.ts.map +1 -0
  83. package/lib/typescript/src/fabric/LuggMapsGoogleMapViewNativeComponent.d.ts +48 -0
  84. package/lib/typescript/src/fabric/LuggMapsGoogleMapViewNativeComponent.d.ts.map +1 -0
  85. package/lib/typescript/src/fabric/LuggMapsMarkerViewNativeComponent.d.ts +20 -0
  86. package/lib/typescript/src/fabric/LuggMapsMarkerViewNativeComponent.d.ts.map +1 -0
  87. package/lib/typescript/src/fabric/LuggMapsPolylineViewNativeComponent.d.ts +15 -0
  88. package/lib/typescript/src/fabric/LuggMapsPolylineViewNativeComponent.d.ts.map +1 -0
  89. package/lib/typescript/src/fabric/LuggMapsWrapperViewNativeComponent.d.ts +6 -0
  90. package/lib/typescript/src/fabric/LuggMapsWrapperViewNativeComponent.d.ts.map +1 -0
  91. package/lib/typescript/src/index.d.ts +8 -0
  92. package/lib/typescript/src/index.d.ts.map +1 -0
  93. package/lib/typescript/src/types.d.ts +28 -0
  94. package/lib/typescript/src/types.d.ts.map +1 -0
  95. package/package.json +200 -0
  96. package/plugin/build/index.d.ts +15 -0
  97. package/plugin/build/index.js +13 -0
  98. package/plugin/build/withMapsAndroid.d.ts +5 -0
  99. package/plugin/build/withMapsAndroid.js +15 -0
  100. package/plugin/build/withMapsIOS.d.ts +5 -0
  101. package/plugin/build/withMapsIOS.js +27 -0
  102. package/src/MapView.tsx +111 -0
  103. package/src/MapView.types.ts +110 -0
  104. package/src/Marker.tsx +31 -0
  105. package/src/Marker.types.ts +32 -0
  106. package/src/Polyline.tsx +32 -0
  107. package/src/Polyline.types.ts +24 -0
  108. package/src/fabric/LuggMapsAppleMapViewNativeComponent.ts +73 -0
  109. package/src/fabric/LuggMapsGoogleMapViewNativeComponent.ts +74 -0
  110. package/src/fabric/LuggMapsMarkerViewNativeComponent.ts +25 -0
  111. package/src/fabric/LuggMapsPolylineViewNativeComponent.ts +19 -0
  112. package/src/fabric/LuggMapsWrapperViewNativeComponent.ts +8 -0
  113. package/src/index.ts +13 -0
  114. package/src/types.ts +30 -0
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 lodev09
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
@@ -0,0 +1,23 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "LuggMaps"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ s.platforms = { :ios => min_ios_version_supported }
14
+ s.source = { :git => "https://github.com/lugg/maps.git", :tag => "#{s.version}" }
15
+
16
+ s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
17
+ s.private_header_files = "ios/**/*.h"
18
+
19
+ s.dependency "GoogleMaps"
20
+ s.frameworks = "MapKit"
21
+
22
+ install_modules_dependencies(s)
23
+ end
package/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # @lugg/maps
2
+
3
+ React Native Fabric maps library for iOS and Android.
4
+
5
+ - Google Maps (iOS & Android)
6
+ - Apple Maps (iOS only)
7
+
8
+ ## Installation
9
+
10
+ ```sh
11
+ npm install @lugg/maps
12
+ ```
13
+
14
+ ### Expo
15
+
16
+ Add the plugin to your `app.json`:
17
+
18
+ ```json
19
+ {
20
+ "expo": {
21
+ "plugins": [
22
+ [
23
+ "@lugg/maps",
24
+ {
25
+ "iosGoogleMapsApiKey": "YOUR_IOS_API_KEY",
26
+ "androidGoogleMapsApiKey": "YOUR_ANDROID_API_KEY"
27
+ }
28
+ ]
29
+ ]
30
+ }
31
+ }
32
+ ```
33
+
34
+ ### Bare React Native
35
+
36
+ #### iOS
37
+
38
+ Add your Google Maps API key to `AppDelegate.swift`:
39
+
40
+ ```swift
41
+ import GoogleMaps
42
+
43
+ // In application(_:didFinishLaunchingWithOptions:)
44
+ GMSServices.provideAPIKey("YOUR_API_KEY")
45
+ ```
46
+
47
+ #### Android
48
+
49
+ Add your Google Maps API key to `AndroidManifest.xml`:
50
+
51
+ ```xml
52
+ <application>
53
+ <meta-data
54
+ android:name="com.google.android.geo.API_KEY"
55
+ android:value="YOUR_API_KEY" />
56
+ </application>
57
+ ```
58
+
59
+ ## Usage
60
+
61
+ ```tsx
62
+ import { MapView, Marker, Polyline } from '@lugg/maps';
63
+
64
+ <MapView
65
+ style={{ flex: 1 }}
66
+ provider="google"
67
+ initialCoordinate={{ latitude: 37.7749, longitude: -122.4194 }}
68
+ initialZoom={12}
69
+ >
70
+ <Marker
71
+ coordinate={{ latitude: 37.7749, longitude: -122.4194 }}
72
+ title="San Francisco"
73
+ />
74
+ <Polyline
75
+ coordinates={[
76
+ { latitude: 37.7749, longitude: -122.4194 },
77
+ { latitude: 37.8049, longitude: -122.4094 },
78
+ ]}
79
+ strokeWidth={3}
80
+ />
81
+ </MapView>
82
+ ```
83
+
84
+ ## Components
85
+
86
+ - [MapView](docs/MAPVIEW.md) - Main map component
87
+ - [Marker](docs/MARKER.md) - Map markers
88
+ - [Polyline](docs/POLYLINE.md) - Draw lines on the map
89
+
90
+ ## Types
91
+
92
+ ```ts
93
+ interface Coordinate {
94
+ latitude: number;
95
+ longitude: number;
96
+ }
97
+
98
+ interface Point {
99
+ x: number;
100
+ y: number;
101
+ }
102
+
103
+ interface EdgeInsets {
104
+ top: number;
105
+ left: number;
106
+ bottom: number;
107
+ right: number;
108
+ }
109
+ ```
110
+
111
+ ## Contributing
112
+
113
+ - [Development workflow](CONTRIBUTING.md#development-workflow)
114
+ - [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
115
+ - [Code of conduct](CODE_OF_CONDUCT.md)
116
+
117
+ ## License
118
+
119
+ MIT
@@ -0,0 +1,78 @@
1
+ buildscript {
2
+ ext.getExtOrDefault = {name ->
3
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['LuggMaps_' + name]
4
+ }
5
+
6
+ repositories {
7
+ google()
8
+ mavenCentral()
9
+ }
10
+
11
+ dependencies {
12
+ classpath "com.android.tools.build:gradle:8.7.2"
13
+ // noinspection DifferentKotlinGradleVersion
14
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
15
+ }
16
+ }
17
+
18
+
19
+ apply plugin: "com.android.library"
20
+ apply plugin: "kotlin-android"
21
+
22
+ apply plugin: "com.facebook.react"
23
+
24
+ def getExtOrIntegerDefault(name) {
25
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["LuggMaps_" + name]).toInteger()
26
+ }
27
+
28
+ android {
29
+ namespace "com.luggmaps"
30
+
31
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
32
+
33
+ defaultConfig {
34
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
35
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
36
+ }
37
+
38
+ buildFeatures {
39
+ buildConfig true
40
+ }
41
+
42
+ buildTypes {
43
+ release {
44
+ minifyEnabled false
45
+ }
46
+ }
47
+
48
+ lintOptions {
49
+ disable "GradleCompatible"
50
+ }
51
+
52
+ compileOptions {
53
+ sourceCompatibility JavaVersion.VERSION_1_8
54
+ targetCompatibility JavaVersion.VERSION_1_8
55
+ }
56
+
57
+ sourceSets {
58
+ main {
59
+ java.srcDirs += [
60
+ "generated/java",
61
+ "generated/jni"
62
+ ]
63
+ }
64
+ }
65
+ }
66
+
67
+ repositories {
68
+ mavenCentral()
69
+ google()
70
+ }
71
+
72
+ def kotlin_version = getExtOrDefault("kotlinVersion")
73
+
74
+ dependencies {
75
+ implementation "com.facebook.react:react-android"
76
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
77
+ implementation "com.google.android.gms:play-services-maps:19.2.0"
78
+ }
@@ -0,0 +1,5 @@
1
+ LuggMaps_kotlinVersion=2.0.21
2
+ LuggMaps_minSdkVersion=24
3
+ LuggMaps_targetSdkVersion=34
4
+ LuggMaps_compileSdkVersion=35
5
+ LuggMaps_ndkVersion=27.1.12297006
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,438 @@
1
+ package com.luggmaps
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.util.Log
5
+ import android.view.View
6
+ import android.view.ViewGroup
7
+ import com.facebook.react.uimanager.PixelUtil.dpToPx
8
+ import com.facebook.react.uimanager.ThemedReactContext
9
+ import com.facebook.react.util.RNLog
10
+ import com.facebook.react.views.view.ReactViewGroup
11
+ import com.google.android.gms.maps.CameraUpdateFactory
12
+ import com.google.android.gms.maps.GoogleMap
13
+ import com.google.android.gms.maps.GoogleMapOptions
14
+ import com.google.android.gms.maps.MapView
15
+ import com.google.android.gms.maps.OnMapReadyCallback
16
+ import com.google.android.gms.maps.model.AdvancedMarker
17
+ import com.google.android.gms.maps.model.AdvancedMarkerOptions
18
+ import com.google.android.gms.maps.model.AdvancedMarkerOptions.CollisionBehavior
19
+ import com.google.android.gms.maps.model.LatLng
20
+ import com.google.android.gms.maps.model.PolylineOptions
21
+ import com.luggmaps.core.PolylineAnimator
22
+
23
+ interface LuggMapsGoogleMapViewEventDelegate {
24
+ fun onCameraMove(
25
+ view: LuggMapsGoogleMapView,
26
+ latitude: Double,
27
+ longitude: Double,
28
+ zoom: Float,
29
+ dragging: Boolean
30
+ )
31
+ fun onCameraIdle(view: LuggMapsGoogleMapView, latitude: Double, longitude: Double, zoom: Float)
32
+ }
33
+
34
+ @SuppressLint("ViewConstructor")
35
+ class LuggMapsGoogleMapView(private val reactContext: ThemedReactContext) :
36
+ ReactViewGroup(reactContext),
37
+ OnMapReadyCallback,
38
+ LuggMapsMarkerViewDelegate,
39
+ LuggMapsPolylineViewDelegate,
40
+ GoogleMap.OnCameraMoveStartedListener,
41
+ GoogleMap.OnCameraMoveListener,
42
+ GoogleMap.OnCameraIdleListener {
43
+
44
+ var eventDelegate: LuggMapsGoogleMapViewEventDelegate? = null
45
+ private var mapView: MapView? = null
46
+ private var mapWrapperView: LuggMapsWrapperView? = null
47
+ private var googleMap: GoogleMap? = null
48
+ private var isMapReady = false
49
+ private var isDragging = false
50
+ private var mapId: String = DEMO_MAP_ID
51
+ private val pendingMarkerViews = mutableListOf<LuggMapsMarkerView>()
52
+ private val pendingPolylineViews = mutableListOf<LuggMapsPolylineView>()
53
+ private val polylineAnimators = mutableMapOf<LuggMapsPolylineView, PolylineAnimator>()
54
+
55
+ // Initial camera settings
56
+ private var initialLatitude: Double = 37.78
57
+ private var initialLongitude: Double = -122.43
58
+ private var initialZoom: Float = 14f
59
+
60
+ // UI settings
61
+ private var zoomEnabled: Boolean = true
62
+ private var scrollEnabled: Boolean = true
63
+ private var rotateEnabled: Boolean = true
64
+ private var pitchEnabled: Boolean = true
65
+
66
+ // Padding
67
+ private var paddingTop: Int = 0
68
+ private var paddingLeft: Int = 0
69
+ private var paddingBottom: Int = 0
70
+ private var paddingRight: Int = 0
71
+
72
+ // region View Lifecycle
73
+
74
+ override fun addView(child: View?, index: Int) {
75
+ super.addView(child, index)
76
+ when (child) {
77
+ is LuggMapsWrapperView -> mapWrapperView = child
78
+
79
+ is LuggMapsMarkerView -> {
80
+ child.delegate = this
81
+ syncMarkerView(child)
82
+ }
83
+
84
+ is LuggMapsPolylineView -> {
85
+ child.delegate = this
86
+ syncPolylineView(child)
87
+ }
88
+ }
89
+ }
90
+
91
+ override fun removeViewAt(index: Int) {
92
+ val view = getChildAt(index)
93
+ if (view is LuggMapsMarkerView) {
94
+ Log.d(TAG, "removing markerView: ${view.name}")
95
+ view.marker?.remove()
96
+ view.marker = null
97
+ } else if (view is LuggMapsPolylineView) {
98
+ polylineAnimators[view]?.destroy()
99
+ polylineAnimators.remove(view)
100
+ view.polyline?.remove()
101
+ view.polyline = null
102
+ }
103
+ super.removeViewAt(index)
104
+ }
105
+
106
+ override fun onAttachedToWindow() {
107
+ super.onAttachedToWindow()
108
+ if (mapView == null && mapWrapperView != null) {
109
+ initializeMap()
110
+ }
111
+ }
112
+
113
+ fun onDropViewInstance() {
114
+ Log.d(TAG, "dropping mapView instance")
115
+ pendingMarkerViews.clear()
116
+ pendingPolylineViews.clear()
117
+ polylineAnimators.values.forEach { it.destroy() }
118
+ polylineAnimators.clear()
119
+ googleMap?.clear()
120
+ googleMap = null
121
+ isMapReady = false
122
+ mapView?.onPause()
123
+ mapView?.onDestroy()
124
+ mapView = null
125
+ mapWrapperView = null
126
+ }
127
+
128
+ // endregion
129
+
130
+ // region Map Initialization
131
+
132
+ private fun initializeMap() {
133
+ if (mapView != null || mapWrapperView == null) return
134
+
135
+ val options = GoogleMapOptions().mapId(mapId)
136
+ mapView = MapView(context, options).also { view ->
137
+ view.onCreate(null)
138
+ view.onResume()
139
+ view.getMapAsync(this)
140
+ mapWrapperView?.addView(
141
+ view,
142
+ LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
143
+ )
144
+ }
145
+ }
146
+
147
+ override fun onMapReady(map: GoogleMap) {
148
+ googleMap = map
149
+ isMapReady = true
150
+
151
+ val position = LatLng(initialLatitude, initialLongitude)
152
+ map.moveCamera(CameraUpdateFactory.newLatLngZoom(position, initialZoom))
153
+
154
+ map.setOnCameraMoveStartedListener(this)
155
+ map.setOnCameraMoveListener(this)
156
+ map.setOnCameraIdleListener(this)
157
+
158
+ applyUiSettings()
159
+ applyPadding()
160
+ processPendingMarkers()
161
+ processPendingPolylines()
162
+ }
163
+
164
+ override fun onCameraMoveStarted(reason: Int) {
165
+ isDragging = reason == GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE
166
+ }
167
+
168
+ override fun onCameraMove() {
169
+ val map = googleMap ?: return
170
+ val position = map.cameraPosition
171
+ eventDelegate?.onCameraMove(this, position.target.latitude, position.target.longitude, position.zoom, isDragging)
172
+ }
173
+
174
+ override fun onCameraIdle() {
175
+ val map = googleMap ?: return
176
+ val position = map.cameraPosition
177
+ isDragging = false
178
+ eventDelegate?.onCameraIdle(this, position.target.latitude, position.target.longitude, position.zoom)
179
+ }
180
+
181
+ private fun applyUiSettings() {
182
+ googleMap?.uiSettings?.apply {
183
+ isZoomGesturesEnabled = zoomEnabled
184
+ isScrollGesturesEnabled = scrollEnabled
185
+ isRotateGesturesEnabled = rotateEnabled
186
+ isTiltGesturesEnabled = pitchEnabled
187
+ }
188
+ }
189
+
190
+ private fun applyPadding() {
191
+ googleMap?.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom)
192
+ }
193
+
194
+ // endregion
195
+
196
+ // region PolylineViewDelegate
197
+
198
+ override fun polylineViewDidUpdate(polylineView: LuggMapsPolylineView) {
199
+ syncPolylineView(polylineView)
200
+ }
201
+
202
+ // endregion
203
+
204
+ // region MarkerViewDelegate
205
+
206
+ override fun markerViewDidLayout(markerView: LuggMapsMarkerView) {
207
+ if (googleMap == null) {
208
+ if (!pendingMarkerViews.contains(markerView)) {
209
+ pendingMarkerViews.add(markerView)
210
+ }
211
+ return
212
+ }
213
+
214
+ if (markerView.hasCustomView) {
215
+ // Recreate marker with custom view
216
+ markerView.marker?.remove()
217
+ addMarkerViewToMap(markerView)
218
+ } else {
219
+ syncMarkerView(markerView)
220
+ }
221
+ }
222
+
223
+ override fun markerViewDidUpdate(markerView: LuggMapsMarkerView) {
224
+ syncMarkerView(markerView)
225
+ }
226
+
227
+ // endregion
228
+
229
+ // region Marker Management
230
+
231
+ private fun syncMarkerView(markerView: LuggMapsMarkerView) {
232
+ if (googleMap == null) {
233
+ if (!pendingMarkerViews.contains(markerView)) {
234
+ pendingMarkerViews.add(markerView)
235
+ }
236
+ return
237
+ }
238
+
239
+ if (markerView.marker == null) {
240
+ // Custom views need layout first before adding to map
241
+ if (markerView.hasCustomView) return
242
+ addMarkerViewToMap(markerView)
243
+ return
244
+ }
245
+
246
+ markerView.marker?.apply {
247
+ position = LatLng(markerView.latitude, markerView.longitude)
248
+ title = markerView.title
249
+ snippet = markerView.description
250
+ setAnchor(markerView.anchorX, markerView.anchorY)
251
+ if (!markerView.hasCustomView) {
252
+ iconView = null
253
+ }
254
+ }
255
+ }
256
+
257
+ private fun processPendingMarkers() {
258
+ if (googleMap == null) return
259
+
260
+ Log.d(TAG, "processing pending markers ${pendingMarkerViews.size}")
261
+ pendingMarkerViews.forEach { addMarkerViewToMap(it) }
262
+ pendingMarkerViews.clear()
263
+ }
264
+
265
+ private fun addMarkerViewToMap(markerView: LuggMapsMarkerView) {
266
+ val map = googleMap ?: run {
267
+ RNLog.w(reactContext, "LuggMaps: addMarkerViewToMap called without a map")
268
+ return
269
+ }
270
+
271
+ val position = LatLng(markerView.latitude, markerView.longitude)
272
+ val iconView = markerView.iconView
273
+
274
+ (iconView.parent as? ViewGroup)?.removeView(iconView)
275
+
276
+ val options = AdvancedMarkerOptions()
277
+ .position(position)
278
+ .title(markerView.title)
279
+ .snippet(markerView.description)
280
+ .collisionBehavior(CollisionBehavior.REQUIRED)
281
+
282
+ Log.d(TAG, "adding marker: ${markerView.name} customview: ${markerView.hasCustomView}")
283
+ if (markerView.hasCustomView) {
284
+ options.iconView(iconView)
285
+ }
286
+
287
+ val marker = map.addMarker(options) as AdvancedMarker
288
+ marker.setAnchor(markerView.anchorX, markerView.anchorY)
289
+
290
+ markerView.marker = marker
291
+ }
292
+
293
+ // endregion
294
+
295
+ // region Polyline Management
296
+
297
+ private fun syncPolylineView(polylineView: LuggMapsPolylineView) {
298
+ if (googleMap == null) {
299
+ if (!pendingPolylineViews.contains(polylineView)) {
300
+ pendingPolylineViews.add(polylineView)
301
+ }
302
+ return
303
+ }
304
+
305
+ if (polylineView.polyline == null) {
306
+ addPolylineViewToMap(polylineView)
307
+ return
308
+ }
309
+
310
+ polylineView.polyline?.width = polylineView.strokeWidth.dpToPx()
311
+
312
+ polylineAnimators[polylineView]?.apply {
313
+ coordinates = polylineView.coordinates
314
+ strokeColors = polylineView.strokeColors
315
+ strokeWidth = polylineView.strokeWidth.dpToPx()
316
+ animated = polylineView.animated
317
+ update()
318
+ }
319
+ }
320
+
321
+ private fun processPendingPolylines() {
322
+ if (googleMap == null) return
323
+
324
+ pendingPolylineViews.forEach { addPolylineViewToMap(it) }
325
+ pendingPolylineViews.clear()
326
+ }
327
+
328
+ private fun addPolylineViewToMap(polylineView: LuggMapsPolylineView) {
329
+ val map = googleMap ?: return
330
+
331
+ val options = PolylineOptions()
332
+ .width(polylineView.strokeWidth.dpToPx())
333
+
334
+ val polyline = map.addPolyline(options)
335
+ polylineView.polyline = polyline
336
+
337
+ val animator = PolylineAnimator().apply {
338
+ this.polyline = polyline
339
+ coordinates = polylineView.coordinates
340
+ strokeColors = polylineView.strokeColors
341
+ strokeWidth = polylineView.strokeWidth.dpToPx()
342
+ animated = polylineView.animated
343
+ update()
344
+ }
345
+
346
+ polylineAnimators[polylineView] = animator
347
+ }
348
+
349
+ // endregion
350
+
351
+ // region Property Setters
352
+
353
+ fun setMapId(value: String?) {
354
+ if (value.isNullOrEmpty()) return
355
+
356
+ if (mapView != null) {
357
+ RNLog.w(reactContext, "LuggMaps: mapId cannot be changed after map is initialized")
358
+ return
359
+ }
360
+
361
+ mapId = value
362
+ }
363
+
364
+ fun setInitialCoordinate(latitude: Double, longitude: Double) {
365
+ initialLatitude = latitude
366
+ initialLongitude = longitude
367
+ }
368
+
369
+ fun setInitialZoom(zoom: Double) {
370
+ initialZoom = zoom.toFloat()
371
+ }
372
+
373
+ fun setZoomEnabled(enabled: Boolean) {
374
+ zoomEnabled = enabled
375
+ googleMap?.uiSettings?.isZoomGesturesEnabled = enabled
376
+ }
377
+
378
+ fun setScrollEnabled(enabled: Boolean) {
379
+ scrollEnabled = enabled
380
+ googleMap?.uiSettings?.isScrollGesturesEnabled = enabled
381
+ }
382
+
383
+ fun setRotateEnabled(enabled: Boolean) {
384
+ rotateEnabled = enabled
385
+ googleMap?.uiSettings?.isRotateGesturesEnabled = enabled
386
+ }
387
+
388
+ fun setPitchEnabled(enabled: Boolean) {
389
+ pitchEnabled = enabled
390
+ googleMap?.uiSettings?.isTiltGesturesEnabled = enabled
391
+ }
392
+
393
+ fun setMapPadding(top: Int, left: Int, bottom: Int, right: Int) {
394
+ paddingTop = top
395
+ paddingLeft = left
396
+ paddingBottom = bottom
397
+ paddingRight = right
398
+ applyPadding()
399
+ }
400
+
401
+ // endregion
402
+
403
+ // region Commands
404
+
405
+ fun moveCamera(latitude: Double, longitude: Double, zoom: Double, duration: Int) {
406
+ val map = googleMap ?: return
407
+ val position = LatLng(latitude, longitude)
408
+ val cameraUpdate = CameraUpdateFactory.newLatLngZoom(position, zoom.toFloat())
409
+ when {
410
+ duration < 0 -> map.animateCamera(cameraUpdate)
411
+ duration > 0 -> map.animateCamera(cameraUpdate, duration, null)
412
+ else -> map.moveCamera(cameraUpdate)
413
+ }
414
+ }
415
+
416
+ fun fitCoordinates(coordinates: List<LatLng>, padding: Int, duration: Int) {
417
+ val map = googleMap ?: return
418
+ if (coordinates.isEmpty()) return
419
+
420
+ val boundsBuilder = com.google.android.gms.maps.model.LatLngBounds.Builder()
421
+ coordinates.forEach { boundsBuilder.include(it) }
422
+ val bounds = boundsBuilder.build()
423
+
424
+ val cameraUpdate = CameraUpdateFactory.newLatLngBounds(bounds, padding.toFloat().dpToPx().toInt())
425
+ when {
426
+ duration < 0 -> map.animateCamera(cameraUpdate)
427
+ duration > 0 -> map.animateCamera(cameraUpdate, duration, null)
428
+ else -> map.moveCamera(cameraUpdate)
429
+ }
430
+ }
431
+
432
+ // endregion
433
+
434
+ companion object {
435
+ private const val TAG = "LuggMaps"
436
+ const val DEMO_MAP_ID = "DEMO_MAP_ID"
437
+ }
438
+ }