react-native-google-maps-plus 0.1.0 → 1.0.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.
- package/GoogleMapsNitro.podspec +34 -0
- package/LICENSE +20 -0
- package/README.md +40 -0
- package/android/CMakeLists.txt +32 -0
- package/android/build.gradle +135 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +8 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/googlemapsnitro/Color.kt +65 -0
- package/android/src/main/java/com/googlemapsnitro/GoogleMapsNitroPackage.kt +35 -0
- package/android/src/main/java/com/googlemapsnitro/GoogleMapsNitroViewImpl.kt +720 -0
- package/android/src/main/java/com/googlemapsnitro/HybridGoogleMapsNitroModule.kt +22 -0
- package/android/src/main/java/com/googlemapsnitro/HybridGoogleMapsNitroView.kt +337 -0
- package/android/src/main/java/com/googlemapsnitro/LocationHandler.kt +205 -0
- package/android/src/main/java/com/googlemapsnitro/MapMarker.kt +145 -0
- package/android/src/main/java/com/googlemapsnitro/MapPolygon.kt +36 -0
- package/android/src/main/java/com/googlemapsnitro/MapPolyline.kt +59 -0
- package/android/src/main/java/com/googlemapsnitro/PermissionHandler.kt +116 -0
- package/android/src/main/java/com/googlemapsnitro/PlayServicesHandler.kt +25 -0
- package/ios/Color.swift +109 -0
- package/ios/GoogleMapNitroViewImpl.swift +590 -0
- package/ios/HybridGoogleMapsNitroModule.swift +27 -0
- package/ios/HybridGoogleMapsNitroView.swift +348 -0
- package/ios/LocationHandler.swift +205 -0
- package/ios/MapHelper.swift +18 -0
- package/ios/MapMarker.swift +207 -0
- package/ios/MapPolygon.swift +55 -0
- package/ios/MapPolyline.swift +83 -0
- package/ios/PermissionHandler.swift +73 -0
- package/lib/module/GoogleMapsNitroModule.nitro.js +4 -0
- package/lib/module/GoogleMapsNitroModule.nitro.js.map +1 -0
- package/lib/module/GoogleMapsNitroView.nitro.js +4 -0
- package/lib/module/GoogleMapsNitroView.nitro.js.map +1 -0
- package/lib/module/index.js +8 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +78 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/GoogleMapsNitroModule.nitro.d.ts +12 -0
- package/lib/typescript/src/GoogleMapsNitroModule.nitro.d.ts.map +1 -0
- package/lib/typescript/src/GoogleMapsNitroView.nitro.d.ts +34 -0
- package/lib/typescript/src/GoogleMapsNitroView.nitro.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +7 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +113 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/nitro.json +28 -0
- package/package.json +13 -3
- package/src/GoogleMapsNitroModule.nitro.ts +13 -0
- package/src/GoogleMapsNitroView.nitro.ts +78 -0
- package/src/index.tsx +24 -0
- package/src/types.ts +174 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import CoreLocation
|
|
2
|
+
import Foundation
|
|
3
|
+
import GoogleMaps
|
|
4
|
+
import NitroModules
|
|
5
|
+
|
|
6
|
+
final class HybridGoogleMapsNitroView: HybridGoogleMapsNitroViewSpec {
|
|
7
|
+
|
|
8
|
+
private let permissionHandler: PermissionHandler
|
|
9
|
+
private let locationHandler: LocationHandler
|
|
10
|
+
|
|
11
|
+
private let impl: GoogleMapsNitroViewImpl
|
|
12
|
+
|
|
13
|
+
var view: UIView {
|
|
14
|
+
return impl
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private var currentCustomMapStyle: String = ""
|
|
18
|
+
private let markerOptions = MapMarkerOptions()
|
|
19
|
+
private let polylineOptions = MapPolylineOptions()
|
|
20
|
+
private let polygonOptions = MapPolygonOptions()
|
|
21
|
+
|
|
22
|
+
override init() {
|
|
23
|
+
self.permissionHandler = PermissionHandler()
|
|
24
|
+
self.locationHandler = LocationHandler()
|
|
25
|
+
self.impl = GoogleMapsNitroViewImpl(
|
|
26
|
+
locationHandler: locationHandler,
|
|
27
|
+
markerOptions: markerOptions
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@MainActor
|
|
32
|
+
var buildingEnabled: Bool {
|
|
33
|
+
get { impl.buildingEnabled }
|
|
34
|
+
set { impl.buildingEnabled = newValue }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@MainActor
|
|
38
|
+
var trafficEnabled: Bool {
|
|
39
|
+
get { impl.trafficEnabled }
|
|
40
|
+
set { impl.trafficEnabled = newValue }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@MainActor
|
|
44
|
+
var customMapStyle: String {
|
|
45
|
+
get { currentCustomMapStyle }
|
|
46
|
+
set {
|
|
47
|
+
currentCustomMapStyle = newValue
|
|
48
|
+
impl.customMapStyle = try? GMSMapStyle(jsonString: newValue)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@MainActor
|
|
53
|
+
var initialCamera: RNCamera {
|
|
54
|
+
get { mapCameraPositionToCamera(impl.initialCamera) }
|
|
55
|
+
set { impl.initialCamera = mapCameraToGMSCamera(newValue) }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@MainActor
|
|
59
|
+
var userInterfaceStyle: RNUserInterfaceStyle {
|
|
60
|
+
get { mapUIUserInterfaceStyletoUserInterfaceStyle(impl.userInterfaceStyle) }
|
|
61
|
+
set {
|
|
62
|
+
impl.userInterfaceStyle = mapUserInterfaceStyleToUIUserInterfaceStyle(
|
|
63
|
+
newValue
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@MainActor
|
|
69
|
+
var minZoomLevel: Double {
|
|
70
|
+
get { impl.minZoomLevel }
|
|
71
|
+
set { impl.minZoomLevel = newValue }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@MainActor
|
|
75
|
+
var maxZoomLevel: Double {
|
|
76
|
+
get { impl.maxZoomLevel }
|
|
77
|
+
set { impl.maxZoomLevel = newValue }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@MainActor
|
|
81
|
+
var mapPadding: RNMapPadding {
|
|
82
|
+
get { impl.mapPadding }
|
|
83
|
+
set { impl.mapPadding = newValue }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@MainActor
|
|
87
|
+
var markers: [RNMarker] = [] {
|
|
88
|
+
didSet {
|
|
89
|
+
let prevById = Dictionary(
|
|
90
|
+
oldValue.map { ($0.id, $0) },
|
|
91
|
+
uniquingKeysWith: { _, new in new }
|
|
92
|
+
)
|
|
93
|
+
let nextById = Dictionary(
|
|
94
|
+
markers.map { ($0.id, $0) },
|
|
95
|
+
uniquingKeysWith: { _, new in new }
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
let removed = Set(prevById.keys).subtracting(nextById.keys)
|
|
99
|
+
withCATransaction(disableActions: true) {
|
|
100
|
+
|
|
101
|
+
removed.forEach {
|
|
102
|
+
impl.removeMarker(id: $0)
|
|
103
|
+
markerOptions.cancelIconTask($0)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (id, next) in nextById {
|
|
107
|
+
if let prev = prevById[id] {
|
|
108
|
+
if !prev.markerEquals(next) {
|
|
109
|
+
impl.updateMarker(id: id) { m in
|
|
110
|
+
self.markerOptions.updateMarker(prev, next, m)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
markerOptions.buildIconAsync(next.id, next) { icon in
|
|
115
|
+
guard let icon else { return }
|
|
116
|
+
let marker = self.markerOptions.build(next, icon: icon)
|
|
117
|
+
self.impl.addMarker(id: id, marker: marker)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@MainActor
|
|
126
|
+
var polylines: [RNPolyline] = [] {
|
|
127
|
+
didSet {
|
|
128
|
+
let prevById = Dictionary(
|
|
129
|
+
oldValue.map { ($0.id, $0) },
|
|
130
|
+
uniquingKeysWith: { _, new in new }
|
|
131
|
+
)
|
|
132
|
+
let nextById = Dictionary(
|
|
133
|
+
polylines.map { ($0.id, $0) },
|
|
134
|
+
uniquingKeysWith: { _, new in new }
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
let removed = Set(prevById.keys).subtracting(nextById.keys)
|
|
138
|
+
removed.forEach { impl.removePolyline(id: $0) }
|
|
139
|
+
|
|
140
|
+
for (id, next) in nextById {
|
|
141
|
+
if let prev = prevById[id] {
|
|
142
|
+
if !prev.polylineEquals(next) {
|
|
143
|
+
impl.updatePolyline(id: id) { gms in
|
|
144
|
+
prev.updatePolyline(next, gms)
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
impl.addPolyline(
|
|
148
|
+
id: id,
|
|
149
|
+
polyline: polylineOptions.buildPolyline(next)
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
@MainActor
|
|
158
|
+
var polygons: [RNPolygon] = [] {
|
|
159
|
+
didSet {
|
|
160
|
+
let prevById = Dictionary(
|
|
161
|
+
oldValue.map { ($0.id, $0) },
|
|
162
|
+
uniquingKeysWith: { _, new in new }
|
|
163
|
+
)
|
|
164
|
+
let nextById = Dictionary(
|
|
165
|
+
polygons.map { ($0.id, $0) },
|
|
166
|
+
uniquingKeysWith: { _, new in new }
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
let removed = Set(prevById.keys).subtracting(nextById.keys)
|
|
170
|
+
removed.forEach { impl.removePolygon(id: $0) }
|
|
171
|
+
|
|
172
|
+
for (id, next) in nextById {
|
|
173
|
+
if let prev = prevById[id] {
|
|
174
|
+
if !prev.polygonEquals(next) {
|
|
175
|
+
impl.updatePolygon(id: id) { pg in
|
|
176
|
+
prev.updatePolygon(next, pg)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
impl.addPolygon(id: id, polygon: polygonOptions.buildPolygon(next))
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
func setCamera(camera: RNCamera, animated: Bool?, durationMS: Double?) {
|
|
187
|
+
onMain {
|
|
188
|
+
self.impl.setCamera(
|
|
189
|
+
camera: camera,
|
|
190
|
+
animated: animated ?? true,
|
|
191
|
+
durationMS: durationMS ?? 3000
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
func setCameraToCoordinates(
|
|
197
|
+
coordinates: [RNLatLng],
|
|
198
|
+
padding: RNMapPadding?,
|
|
199
|
+
animated: Bool?,
|
|
200
|
+
durationMS: Double?
|
|
201
|
+
) {
|
|
202
|
+
onMain {
|
|
203
|
+
self.impl.setCameraToCoordinates(
|
|
204
|
+
coordinates: coordinates,
|
|
205
|
+
padding: padding ?? RNMapPadding(0, 0, 0, 0),
|
|
206
|
+
animated: animated ?? true,
|
|
207
|
+
durationMS: durationMS ?? 3000
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
var onMapError: ((RNMapErrorCode) -> Void)? {
|
|
213
|
+
get { impl.onMapError }
|
|
214
|
+
set { impl.onMapError = newValue }
|
|
215
|
+
}
|
|
216
|
+
var onMapReady: ((Bool) -> Void)? {
|
|
217
|
+
get { impl.onMapReady }
|
|
218
|
+
set { impl.onMapReady = newValue }
|
|
219
|
+
}
|
|
220
|
+
var onLocationUpdate: ((RNLocation) -> Void)? {
|
|
221
|
+
get { impl.onLocationUpdate }
|
|
222
|
+
set { impl.onLocationUpdate = newValue }
|
|
223
|
+
}
|
|
224
|
+
var onLocationError: ((_ error: RNLocationErrorCode) -> Void)? {
|
|
225
|
+
get { impl.onLocationError }
|
|
226
|
+
set { impl.onLocationError = newValue }
|
|
227
|
+
}
|
|
228
|
+
var onMapPress: ((RNLatLng) -> Void)? {
|
|
229
|
+
get { impl.onMapPress }
|
|
230
|
+
set { impl.onMapPress = newValue }
|
|
231
|
+
}
|
|
232
|
+
var onMarkerPress: ((String) -> Void)? {
|
|
233
|
+
get { impl.onMarkerPress }
|
|
234
|
+
set { impl.onMarkerPress = newValue }
|
|
235
|
+
}
|
|
236
|
+
var onCameraChangeStart: ((RNRegion, RNCamera, Bool) -> Void)? {
|
|
237
|
+
get { impl.onCameraChangeStart }
|
|
238
|
+
set { impl.onCameraChangeStart = newValue }
|
|
239
|
+
}
|
|
240
|
+
var onCameraChange: ((RNRegion, RNCamera, Bool) -> Void)? {
|
|
241
|
+
get { impl.onCameraChange }
|
|
242
|
+
set { impl.onCameraChange = newValue }
|
|
243
|
+
}
|
|
244
|
+
var onCameraChangeComplete: ((RNRegion, RNCamera, Bool) -> Void)? {
|
|
245
|
+
get { impl.onCameraChangeComplete }
|
|
246
|
+
set { impl.onCameraChangeComplete = newValue }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
func showLocationDialog() {
|
|
250
|
+
locationHandler.showLocationDialog()
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
func openLocationSettings() {
|
|
254
|
+
locationHandler.openLocationSettings()
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
func requestLocationPermission()
|
|
258
|
+
-> NitroModules.Promise<RNLocationPermissionResult> {
|
|
259
|
+
return permissionHandler.requestLocationPermission()
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
func isGooglePlayServicesAvailable() -> Bool {
|
|
263
|
+
/// not supported
|
|
264
|
+
return true
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private func mapCameraToGMSCamera(_ c: RNCamera) -> GMSCameraPosition {
|
|
268
|
+
let current = impl.currentCamera
|
|
269
|
+
let center = CLLocationCoordinate2D(
|
|
270
|
+
latitude: c.center?.latitude ?? current.target.latitude,
|
|
271
|
+
longitude: c.center?.longitude ?? current.target.longitude
|
|
272
|
+
)
|
|
273
|
+
let z = Float(c.zoom ?? Double(current.zoom))
|
|
274
|
+
let b = c.bearing ?? current.bearing
|
|
275
|
+
let t = c.tilt ?? current.viewingAngle
|
|
276
|
+
|
|
277
|
+
return GMSCameraPosition.camera(
|
|
278
|
+
withTarget: center,
|
|
279
|
+
zoom: z,
|
|
280
|
+
bearing: b,
|
|
281
|
+
viewingAngle: t
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private func mapCameraPositionToCamera(_ cp: GMSCameraPosition)
|
|
286
|
+
-> RNCamera {
|
|
287
|
+
return RNCamera(
|
|
288
|
+
center: RNLatLng(
|
|
289
|
+
latitude: cp.target.latitude,
|
|
290
|
+
longitude: cp.target.longitude
|
|
291
|
+
),
|
|
292
|
+
zoom: Double(cp.zoom),
|
|
293
|
+
bearing: cp.bearing,
|
|
294
|
+
tilt: cp.viewingAngle
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
func mapUserInterfaceStyleToUIUserInterfaceStyle(
|
|
299
|
+
_ style: RNUserInterfaceStyle
|
|
300
|
+
)
|
|
301
|
+
-> UIUserInterfaceStyle {
|
|
302
|
+
switch style {
|
|
303
|
+
case .light: return .light
|
|
304
|
+
case .dark: return .dark
|
|
305
|
+
case .default: return .unspecified
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
func mapUIUserInterfaceStyletoUserInterfaceStyle(
|
|
310
|
+
_ uiStyle: UIUserInterfaceStyle
|
|
311
|
+
) -> RNUserInterfaceStyle {
|
|
312
|
+
switch uiStyle {
|
|
313
|
+
case .light: return .light
|
|
314
|
+
case .dark: return .dark
|
|
315
|
+
case .unspecified: return .default
|
|
316
|
+
@unknown default: return .default
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
extension UIUserInterfaceStyle {
|
|
322
|
+
init?(fromString string: String) {
|
|
323
|
+
switch string.lowercased() {
|
|
324
|
+
case "light": self = .light
|
|
325
|
+
case "dark": self = .dark
|
|
326
|
+
case "default": self = .unspecified
|
|
327
|
+
default: return nil
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
var stringValue: String {
|
|
332
|
+
switch self {
|
|
333
|
+
case .light: return "light"
|
|
334
|
+
case .dark: return "dark"
|
|
335
|
+
case .unspecified: return "default"
|
|
336
|
+
@unknown default: return "default"
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
@inline(__always)
|
|
342
|
+
func onMain(_ block: @escaping () -> Void) {
|
|
343
|
+
if Thread.isMainThread {
|
|
344
|
+
block()
|
|
345
|
+
} else {
|
|
346
|
+
DispatchQueue.main.async { block() }
|
|
347
|
+
}
|
|
348
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import CoreLocation
|
|
2
|
+
import Foundation
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
final class LocationHandler: NSObject, CLLocationManagerDelegate {
|
|
6
|
+
|
|
7
|
+
private let manager = CLLocationManager()
|
|
8
|
+
private var priority: Int = Priority.highAccuracy.rawValue
|
|
9
|
+
private var interval: TimeInterval = 5.0
|
|
10
|
+
private var minUpdateInterval: TimeInterval = 5.0
|
|
11
|
+
private var distanceFilterMeters: CLLocationDistance = kCLDistanceFilterNone
|
|
12
|
+
|
|
13
|
+
var onUpdate: ((CLLocation) -> Void)?
|
|
14
|
+
var onError: ((_ error: RNLocationErrorCode) -> Void)?
|
|
15
|
+
|
|
16
|
+
private var lastEmit: Date?
|
|
17
|
+
|
|
18
|
+
override init() {
|
|
19
|
+
super.init()
|
|
20
|
+
manager.delegate = self
|
|
21
|
+
manager.pausesLocationUpdatesAutomatically = true
|
|
22
|
+
manager.activityType = .other
|
|
23
|
+
applyPriority()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func setPriority(_ priority: Int) {
|
|
27
|
+
self.priority = priority
|
|
28
|
+
applyPriority()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func setInterval(_ seconds: Int) {
|
|
32
|
+
self.interval = max(0, TimeInterval(seconds))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func setFastestInterval(_ seconds: Int) {
|
|
36
|
+
self.minUpdateInterval = max(0, TimeInterval(seconds))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func setDistanceFilter(_ meters: Double) {
|
|
40
|
+
self.distanceFilterMeters = meters >= 0 ? meters : kCLDistanceFilterNone
|
|
41
|
+
manager.distanceFilter = distanceFilterMeters
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func showLocationDialog() {
|
|
45
|
+
DispatchQueue.main.async {
|
|
46
|
+
guard let vc = Self.topMostViewController() else { return }
|
|
47
|
+
let title =
|
|
48
|
+
Bundle.main.object(forInfoDictionaryKey: "LocationNotAvailableTitle")
|
|
49
|
+
as? String
|
|
50
|
+
let message =
|
|
51
|
+
Bundle.main.object(forInfoDictionaryKey: "LocationNotAvailableMessage")
|
|
52
|
+
as? String
|
|
53
|
+
let cancelButton =
|
|
54
|
+
Bundle.main.object(forInfoDictionaryKey: "CancelButton") as? String
|
|
55
|
+
let openLocationSettingsButton =
|
|
56
|
+
Bundle.main.object(forInfoDictionaryKey: "OpenLocationAlertButton")
|
|
57
|
+
as? String
|
|
58
|
+
|
|
59
|
+
let alert = UIAlertController(
|
|
60
|
+
title: title ?? "Location not available",
|
|
61
|
+
message: message ?? "Please check your location settings.",
|
|
62
|
+
preferredStyle: .alert
|
|
63
|
+
)
|
|
64
|
+
alert.addAction(
|
|
65
|
+
UIAlertAction(title: cancelButton ?? "Cancel", style: .cancel)
|
|
66
|
+
)
|
|
67
|
+
alert.addAction(
|
|
68
|
+
UIAlertAction(
|
|
69
|
+
title: openLocationSettingsButton ?? "Open settings",
|
|
70
|
+
style: .default
|
|
71
|
+
) { _ in
|
|
72
|
+
self.openLocationSettings()
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
vc.present(alert, animated: true, completion: nil)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
func start() {
|
|
80
|
+
stop()
|
|
81
|
+
startUpdates()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func stop() {
|
|
85
|
+
manager.stopUpdatingLocation()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func openLocationSettings() {
|
|
89
|
+
DispatchQueue.main.async {
|
|
90
|
+
let openSettings = {
|
|
91
|
+
if #available(iOS 18.3, *) {
|
|
92
|
+
guard
|
|
93
|
+
let url = URL(
|
|
94
|
+
string: UIApplication.openDefaultApplicationsSettingsURLString
|
|
95
|
+
)
|
|
96
|
+
else {
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
|
100
|
+
} else {
|
|
101
|
+
guard let url = URL(string: UIApplication.openSettingsURLString)
|
|
102
|
+
else {
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
openSettings()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private func applyPriority() {
|
|
114
|
+
guard let p = Priority(rawValue: priority) else {
|
|
115
|
+
manager.desiredAccuracy = kCLLocationAccuracyBest
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
switch p {
|
|
119
|
+
case .highAccuracy:
|
|
120
|
+
manager.desiredAccuracy = kCLLocationAccuracyBest
|
|
121
|
+
case .balanced:
|
|
122
|
+
manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
|
|
123
|
+
case .lowPower:
|
|
124
|
+
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
|
|
125
|
+
case .passive:
|
|
126
|
+
manager.desiredAccuracy = kCLLocationAccuracyKilometer
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private func startUpdates() {
|
|
131
|
+
manager.distanceFilter = distanceFilterMeters
|
|
132
|
+
manager.startUpdatingLocation()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private func shouldEmit(now: Date) -> Bool {
|
|
136
|
+
if let last = lastEmit {
|
|
137
|
+
let delta = now.timeIntervalSince(last)
|
|
138
|
+
if delta < minUpdateInterval { return false }
|
|
139
|
+
}
|
|
140
|
+
return true
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
func locationManager(
|
|
144
|
+
_ manager: CLLocationManager,
|
|
145
|
+
didFailWithError error: Error
|
|
146
|
+
) {
|
|
147
|
+
let code: RNLocationErrorCode
|
|
148
|
+
|
|
149
|
+
if let clError = error as? CLError {
|
|
150
|
+
switch clError.code {
|
|
151
|
+
case .denied:
|
|
152
|
+
code = RNLocationErrorCode.permissionDenied
|
|
153
|
+
case .locationUnknown, .network:
|
|
154
|
+
code = RNLocationErrorCode.positionUnavailable
|
|
155
|
+
default:
|
|
156
|
+
code = RNLocationErrorCode.internalError
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
code = RNLocationErrorCode.internalError
|
|
160
|
+
}
|
|
161
|
+
onError?(code)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
func locationManager(
|
|
165
|
+
_ manager: CLLocationManager,
|
|
166
|
+
didUpdateLocations locations: [CLLocation]
|
|
167
|
+
) {
|
|
168
|
+
guard let loc = locations.last else { return }
|
|
169
|
+
let now = Date()
|
|
170
|
+
|
|
171
|
+
if shouldEmit(now: now) {
|
|
172
|
+
lastEmit = now
|
|
173
|
+
onUpdate?(loc)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private static func topMostViewController() -> UIViewController? {
|
|
178
|
+
let scenes = UIApplication.shared.connectedScenes
|
|
179
|
+
.compactMap { $0 as? UIWindowScene }
|
|
180
|
+
.filter { $0.activationState == .foregroundActive }
|
|
181
|
+
|
|
182
|
+
guard
|
|
183
|
+
let window = scenes.flatMap({ $0.windows }).first(where: {
|
|
184
|
+
$0.isKeyWindow
|
|
185
|
+
}),
|
|
186
|
+
var top = window.rootViewController
|
|
187
|
+
else { return nil }
|
|
188
|
+
|
|
189
|
+
while let presented = top.presentedViewController { top = presented }
|
|
190
|
+
return top
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
extension LocationHandler {
|
|
196
|
+
enum Priority: Int {
|
|
197
|
+
case highAccuracy = 100
|
|
198
|
+
/// Android: PRIORITY_BALANCED_POWER_ACCURACY
|
|
199
|
+
case balanced = 102
|
|
200
|
+
/// Android: PRIORITY_LOW_POWER
|
|
201
|
+
case lowPower = 104
|
|
202
|
+
/// Android: PRIORITY_PASSIVE
|
|
203
|
+
case passive = 105
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import QuartzCore
|
|
2
|
+
|
|
3
|
+
@inline(__always)
|
|
4
|
+
func withCATransaction(
|
|
5
|
+
disableActions: Bool = true,
|
|
6
|
+
duration: CFTimeInterval? = nil,
|
|
7
|
+
timingFunction: CAMediaTimingFunction? = nil,
|
|
8
|
+
completion: (() -> Void)? = nil,
|
|
9
|
+
_ body: () -> Void
|
|
10
|
+
) {
|
|
11
|
+
CATransaction.begin()
|
|
12
|
+
if disableActions { CATransaction.setDisableActions(true) }
|
|
13
|
+
if let d = duration { CATransaction.setAnimationDuration(d) }
|
|
14
|
+
if let tf = timingFunction { CATransaction.setAnimationTimingFunction(tf) }
|
|
15
|
+
if let c = completion { CATransaction.setCompletionBlock(c) }
|
|
16
|
+
body()
|
|
17
|
+
CATransaction.commit()
|
|
18
|
+
}
|