@expofp/react-native-efp-crowdconnected 0.0.2-alpha.1
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/ExpofpCrowdconnected.podspec +26 -0
- package/LICENSE +20 -0
- package/README.md +169 -0
- package/android/build.gradle +86 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/expofpcrowdconnected/ExpoFpAzimuthProvider.kt +94 -0
- package/android/src/main/java/com/expofpcrowdconnected/ExpoFpCrowdConnectedLocationProviderSettings.kt +136 -0
- package/android/src/main/java/com/expofpcrowdconnected/ExpoFpPosition.kt +96 -0
- package/android/src/main/java/com/expofpcrowdconnected/ExpoFpProviderInfo.kt +27 -0
- package/android/src/main/java/com/expofpcrowdconnected/ExpofpCrowdconnectedPackage.kt +33 -0
- package/android/src/main/java/com/expofpcrowdconnected/RCTCrowdConnectedLocationProviderModule.kt +504 -0
- package/ios/ExpoFpLocationProvider/ExpoFpCrowdConnectedLocationProviderSettings.swift +116 -0
- package/ios/ExpoFpLocationProvider/ExpoFpCrowdConnectedNavigationType.swift +14 -0
- package/ios/ExpoFpLocationProvider/ExpoFpError.swift +28 -0
- package/ios/ExpoFpLocationProvider/ExpoFpFloorType.swift +39 -0
- package/ios/ExpoFpLocationProvider/ExpoFpLocationSettings.swift +18 -0
- package/ios/ExpoFpLocationProvider/ExpoFpPosition.swift +42 -0
- package/ios/ExpoFpLocationProvider/ExpoFpProviderInfo.swift +12 -0
- package/ios/Extensions/Encodable+Extensions.swift +28 -0
- package/ios/Extensions/LocationTrackingMode+Extensions.swift +16 -0
- package/ios/Extensions/NSDictionary+Extensions.swift +21 -0
- package/ios/RCTCrowdConnectedLocationProvider.m +24 -0
- package/ios/RCTCrowdConnectedLocationProvider.swift +252 -0
- package/lib/module/crowdconnected-location-provider-native-module.js +38 -0
- package/lib/module/crowdconnected-location-provider-native-module.js.map +1 -0
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/crowdconnected-location-provider-native-module.d.ts +4 -0
- package/lib/typescript/src/crowdconnected-location-provider-native-module.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +3 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +40 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +154 -0
- package/src/crowdconnected-location-provider-native-module.tsx +57 -0
- package/src/index.tsx +2 -0
- package/src/types.ts +49 -0
|
@@ -0,0 +1,26 @@
|
|
|
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 = "ExpofpCrowdconnected"
|
|
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/expofp/react-native-crowdconnected.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = "ios/**/*.{h,m,mm,cpp,swift}"
|
|
17
|
+
s.private_header_files = "ios/**/*.h"
|
|
18
|
+
|
|
19
|
+
s.dependency 'CrowdConnectedShared', '2.2.1'
|
|
20
|
+
s.dependency 'CrowdConnectedCore', '2.2.1'
|
|
21
|
+
s.dependency 'CrowdConnectedIPS', '2.2.1'
|
|
22
|
+
s.dependency 'CrowdConnectedCoreBluetooth', '2.2.1'
|
|
23
|
+
s.dependency 'CrowdConnectedGeo', '2.2.1'
|
|
24
|
+
|
|
25
|
+
install_modules_dependencies(s)
|
|
26
|
+
end
|
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ExpoFP
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# @expofp/react-native-efp-crowdconnected
|
|
2
|
+
|
|
3
|
+
React Native Turbo Module for CrowdConnected location tracking and indoor positioning services. Provides cross-platform access to GPS, IPS (Indoor Positioning System), and Bluetooth-based positioning on iOS and Android.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@expofp/react-native-efp-crowdconnected)
|
|
6
|
+
[](https://www.npmjs.com/package/@expofp/react-native-efp-crowdconnected)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- 📍 **Multi-positioning support**: GPS, Indoor Positioning (IPS), and Bluetooth
|
|
12
|
+
- 📱 **Cross-platform**: Native iOS (Swift) and Android (Kotlin) implementations
|
|
13
|
+
- ⚡ **React Native New Architecture**: Built as a Turbo Module for optimal performance
|
|
14
|
+
- 🔔 **Event-based**: Real-time location updates via event listeners
|
|
15
|
+
- 🔄 **Background support**: Optional background location updates
|
|
16
|
+
- 🎯 **Heading tracking**: Compass/bearing information
|
|
17
|
+
- 📦 **TypeScript first**: Full type safety with generated type definitions
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- React Native 0.71+
|
|
22
|
+
- React 18+
|
|
23
|
+
- iOS 12.0+ (CrowdConnected SDK v2.2.1)
|
|
24
|
+
- Android 7+ (API 24+)
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @expofp/react-native-efp-crowdconnected
|
|
30
|
+
# or
|
|
31
|
+
yarn add @expofp/react-native-efp-crowdconnected
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### iOS
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd ios && pod install
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Android
|
|
41
|
+
|
|
42
|
+
No additional setup required. CocoaPods/Gradle will handle dependencies.
|
|
43
|
+
|
|
44
|
+
## Quick Start
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { CrowdConnectedLocationProvider } from '@expofp/react-native-efp-crowdconnected';
|
|
48
|
+
|
|
49
|
+
// Initialize
|
|
50
|
+
await CrowdConnectedLocationProvider.setup({
|
|
51
|
+
appKey: 'your-app-key',
|
|
52
|
+
token: 'your-token',
|
|
53
|
+
secret: 'your-secret',
|
|
54
|
+
navigationType: 'all', // 'all' | 'GEO' | 'IPS'
|
|
55
|
+
isBackgroundUpdateEnabled: false,
|
|
56
|
+
isBluetoothEnabled: true,
|
|
57
|
+
isHeadingEnabled: true,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Listen to location updates
|
|
61
|
+
const unsubscribe = CrowdConnectedLocationProvider.onLocationChange((position) => {
|
|
62
|
+
console.log('Location:', position);
|
|
63
|
+
// { lat: number, lng: number, x?: number, y?: number, z?: string | number, angle?: number }
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Listen to errors
|
|
67
|
+
const unsubscribeError = CrowdConnectedLocationProvider.onError((error) => {
|
|
68
|
+
console.error('Location error:', error);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Start tracking
|
|
72
|
+
CrowdConnectedLocationProvider.startUpdatingLocation();
|
|
73
|
+
|
|
74
|
+
// Check if location tracking is active
|
|
75
|
+
const isUpdating = await CrowdConnectedLocationProvider.isLocationUpdating();
|
|
76
|
+
|
|
77
|
+
// Stop tracking
|
|
78
|
+
CrowdConnectedLocationProvider.stopUpdatingLocation();
|
|
79
|
+
|
|
80
|
+
// Cleanup
|
|
81
|
+
unsubscribe();
|
|
82
|
+
unsubscribeError();
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## API Reference
|
|
86
|
+
|
|
87
|
+
### `setup(settings: ExpoFpLocationSettings): Promise<string>`
|
|
88
|
+
|
|
89
|
+
Initializes the CrowdConnected SDK with configuration.
|
|
90
|
+
|
|
91
|
+
**Parameters:**
|
|
92
|
+
- `appKey` - CrowdConnected application key
|
|
93
|
+
- `token` - Authentication token
|
|
94
|
+
- `secret` - Authentication secret
|
|
95
|
+
- `navigationType` - Positioning type: `'all'` (GPS + IPS), `'GEO'` (GPS only), `'IPS'` (indoor only)
|
|
96
|
+
- `isBackgroundUpdateEnabled` - Enable background location updates
|
|
97
|
+
- `isBluetoothEnabled` - Enable Bluetooth-based positioning
|
|
98
|
+
- `isHeadingEnabled` - Enable compass/heading tracking
|
|
99
|
+
- `aliases?` - Custom location aliases (optional)
|
|
100
|
+
|
|
101
|
+
**Returns:** Promise resolving to initialization status
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### `startUpdatingLocation(): void`
|
|
106
|
+
|
|
107
|
+
Starts location tracking.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### `stopUpdatingLocation(): void`
|
|
112
|
+
|
|
113
|
+
Stops location tracking.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
### `isLocationUpdating(): Promise<boolean>`
|
|
118
|
+
|
|
119
|
+
Checks if location tracking is currently active.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### `onLocationChange(callback: (position: ExpoFpPosition) => void): Unsubscribe`
|
|
124
|
+
|
|
125
|
+
Subscribes to location updates.
|
|
126
|
+
|
|
127
|
+
**Position object:**
|
|
128
|
+
```typescript
|
|
129
|
+
{
|
|
130
|
+
lat?: number; // Latitude
|
|
131
|
+
lng?: number; // Longitude
|
|
132
|
+
x?: number; // X coordinate (indoor)
|
|
133
|
+
y?: number; // Y coordinate (indoor)
|
|
134
|
+
z?: string|number; // Z coordinate (floor/level)
|
|
135
|
+
angle?: number; // Heading in degrees
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Returns:** Unsubscribe function
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
### `onError(callback: (error: Error) => void): Unsubscribe`
|
|
144
|
+
|
|
145
|
+
Subscribes to location errors.
|
|
146
|
+
|
|
147
|
+
**Returns:** Unsubscribe function
|
|
148
|
+
|
|
149
|
+
## Development
|
|
150
|
+
|
|
151
|
+
### Setup
|
|
152
|
+
```bash
|
|
153
|
+
yarn install
|
|
154
|
+
yarn prepare
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Commands
|
|
158
|
+
- `yarn lint` - Run ESLint
|
|
159
|
+
- `yarn typecheck` - Check TypeScript types
|
|
160
|
+
- `yarn test` - Run Jest tests
|
|
161
|
+
- `yarn example ios` - Run iOS example app
|
|
162
|
+
- `yarn example android` - Run Android example app
|
|
163
|
+
- `yarn clean` - Clean build artifacts
|
|
164
|
+
|
|
165
|
+
## Support
|
|
166
|
+
|
|
167
|
+
- 📖 [Documentation](https://developer.expofp.com)
|
|
168
|
+
- 🐛 [Report Issues](https://github.com/expofp/react-native-crowdconnected/issues)
|
|
169
|
+
- 💬 [Discussions](https://github.com/expofp/react-native-crowdconnected/discussions)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.getExtOrDefault = {name ->
|
|
3
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['ExpofpCrowdconnected_' + 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["ExpofpCrowdconnected_" + name]).toInteger()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
android {
|
|
29
|
+
namespace "com.expofpcrowdconnected"
|
|
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
|
+
// CrowdConnected SDK Maven repository
|
|
71
|
+
maven { url "https://maven2.crowdconnected.net/" }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
def kotlin_version = getExtOrDefault("kotlinVersion")
|
|
75
|
+
|
|
76
|
+
dependencies {
|
|
77
|
+
implementation "com.facebook.react:react-android"
|
|
78
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
79
|
+
|
|
80
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2"
|
|
81
|
+
|
|
82
|
+
implementation "net.crowdconnected.android.core:android-core:2.1.0"
|
|
83
|
+
implementation "net.crowdconnected.android.ips:android-ips:2.1.0"
|
|
84
|
+
implementation "net.crowdconnected.android.geo:android-geo:2.1.0"
|
|
85
|
+
implementation "net.crowdconnected.android.background:android-background:2.1.0"
|
|
86
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
package com.expofpcrowdconnected
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.hardware.Sensor
|
|
5
|
+
import android.hardware.SensorEvent
|
|
6
|
+
import android.hardware.SensorEventListener
|
|
7
|
+
import android.hardware.SensorManager
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Provides device heading/azimuth using Android sensors
|
|
11
|
+
* Uses rotation vector sensor with exponential moving average for smoothing
|
|
12
|
+
*/
|
|
13
|
+
class ExpoFpAzimuthProvider(
|
|
14
|
+
private val context: Context,
|
|
15
|
+
private val onAzimuthChanged: (Double) -> Unit
|
|
16
|
+
) : SensorEventListener {
|
|
17
|
+
|
|
18
|
+
private var sensorManager: SensorManager? = null
|
|
19
|
+
private var rotation: Sensor? = null
|
|
20
|
+
|
|
21
|
+
// Current azimuth value in degrees (0-360), null until first reading
|
|
22
|
+
@Volatile
|
|
23
|
+
var azimuth: Double? = null
|
|
24
|
+
private set
|
|
25
|
+
|
|
26
|
+
// Reusable arrays to avoid allocations on each sensor event
|
|
27
|
+
private val rMat = FloatArray(9)
|
|
28
|
+
private val ori = FloatArray(3)
|
|
29
|
+
private var ema: Double? = null
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Start listening for sensor updates
|
|
33
|
+
* Returns true if sensor is available and started successfully, false otherwise
|
|
34
|
+
*/
|
|
35
|
+
fun start(): Boolean {
|
|
36
|
+
sensorManager =
|
|
37
|
+
context.getSystemService(Context.SENSOR_SERVICE) as? SensorManager ?: return false
|
|
38
|
+
rotation = sensorManager?.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR) ?: return false
|
|
39
|
+
return sensorManager?.registerListener(
|
|
40
|
+
this,
|
|
41
|
+
rotation,
|
|
42
|
+
SensorManager.SENSOR_DELAY_GAME
|
|
43
|
+
) == true
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Stop listening for sensor updates
|
|
48
|
+
*/
|
|
49
|
+
fun stop() {
|
|
50
|
+
sensorManager?.unregisterListener(this, rotation)
|
|
51
|
+
sensorManager = null
|
|
52
|
+
rotation = null
|
|
53
|
+
ema = null
|
|
54
|
+
azimuth = null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
override fun onSensorChanged(event: SensorEvent?) {
|
|
58
|
+
if (event?.sensor?.type != Sensor.TYPE_ROTATION_VECTOR) return
|
|
59
|
+
|
|
60
|
+
// Get rotation matrix from rotation vector
|
|
61
|
+
SensorManager.getRotationMatrixFromVector(rMat, event.values)
|
|
62
|
+
val values = SensorManager.getOrientation(rMat, ori)
|
|
63
|
+
|
|
64
|
+
// Extract azimuth (rotation around Z-axis) and convert to degrees
|
|
65
|
+
val raw = Math.toDegrees(values[0].toDouble())
|
|
66
|
+
|
|
67
|
+
// Normalize to 0-360 range
|
|
68
|
+
val normalized = ((raw % 360.0) + 360.0) % 360.0
|
|
69
|
+
|
|
70
|
+
// Apply exponential moving average for smoothing
|
|
71
|
+
val alpha = 0.2
|
|
72
|
+
ema = when (val e = ema) {
|
|
73
|
+
null -> normalized
|
|
74
|
+
else -> {
|
|
75
|
+
val delta = minimalAngleDiff(e, normalized)
|
|
76
|
+
(e + alpha * delta + 360.0) % 360.0
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
azimuth = ema
|
|
80
|
+
ema?.let { onAzimuthChanged(it) }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) = Unit
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Calculate minimal angle difference handling circular nature of angles
|
|
87
|
+
*/
|
|
88
|
+
private fun minimalAngleDiff(a: Double, b: Double): Double {
|
|
89
|
+
var d = (b - a) % 360.0
|
|
90
|
+
if (d > 180) d -= 360
|
|
91
|
+
if (d < -180) d += 360
|
|
92
|
+
return d
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
package com.expofpcrowdconnected
|
|
2
|
+
|
|
3
|
+
import android.os.Build
|
|
4
|
+
import com.facebook.react.bridge.ReadableMap
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Navigation type for the location provider
|
|
8
|
+
*/
|
|
9
|
+
enum class ExpoFpCrowdConnectedNavigationType {
|
|
10
|
+
ALL, // Both IPS and GEO
|
|
11
|
+
GEO, // GPS/outdoor only
|
|
12
|
+
IPS; // Indoor positioning only
|
|
13
|
+
|
|
14
|
+
companion object {
|
|
15
|
+
fun fromString(value: String?): ExpoFpCrowdConnectedNavigationType {
|
|
16
|
+
return when (value?.uppercase()) {
|
|
17
|
+
"ALL" -> ALL
|
|
18
|
+
"GEO" -> GEO
|
|
19
|
+
"IPS" -> IPS
|
|
20
|
+
else -> ALL
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Settings for CrowdConnected location provider
|
|
28
|
+
* Maps to ExpoFpLocationSettings TypeScript interface
|
|
29
|
+
*/
|
|
30
|
+
data class ExpoFpCrowdConnectedLocationProviderSettings(
|
|
31
|
+
val appKey: String,
|
|
32
|
+
val token: String,
|
|
33
|
+
val secret: String,
|
|
34
|
+
val navigationType: ExpoFpCrowdConnectedNavigationType = ExpoFpCrowdConnectedNavigationType.ALL,
|
|
35
|
+
val isBackgroundUpdateEnabled: Boolean = false,
|
|
36
|
+
val isBluetoothEnabled: Boolean = false,
|
|
37
|
+
val isHeadingEnabled: Boolean = false,
|
|
38
|
+
val aliases: Map<String, String> = emptyMap()
|
|
39
|
+
) {
|
|
40
|
+
/**
|
|
41
|
+
* Validate settings - throws IllegalArgumentException if invalid
|
|
42
|
+
* Ensures required credentials are not blank and validates platform-specific requirements
|
|
43
|
+
*/
|
|
44
|
+
fun validate() {
|
|
45
|
+
// Validate required fields are not blank
|
|
46
|
+
if (appKey.isBlank()) {
|
|
47
|
+
throw IllegalArgumentException("appKey cannot be blank")
|
|
48
|
+
}
|
|
49
|
+
if (token.isBlank()) {
|
|
50
|
+
throw IllegalArgumentException("token cannot be blank")
|
|
51
|
+
}
|
|
52
|
+
if (secret.isBlank()) {
|
|
53
|
+
throw IllegalArgumentException("secret cannot be blank")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Validate background requirements
|
|
57
|
+
if (isBackgroundUpdateEnabled) {
|
|
58
|
+
// Android 8.0 (API 26) required for foreground service
|
|
59
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
60
|
+
throw IllegalArgumentException(
|
|
61
|
+
"Background location tracking requires Android 8.0 (API 26) or higher"
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Android 10 (API 29) required for background location permission
|
|
66
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
67
|
+
// Note: ACCESS_BACKGROUND_LOCATION permission must be requested at runtime
|
|
68
|
+
// This is checked separately in missingPermissions() function
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
companion object {
|
|
74
|
+
/**
|
|
75
|
+
* Parse settings from React Native ReadableMap
|
|
76
|
+
*/
|
|
77
|
+
fun fromReadableMap(map: ReadableMap): ExpoFpCrowdConnectedLocationProviderSettings {
|
|
78
|
+
// Required fields - trim whitespace for consistency
|
|
79
|
+
val appKey = map.getString("appKey")
|
|
80
|
+
?.trim()
|
|
81
|
+
?: throw IllegalArgumentException("appKey is required")
|
|
82
|
+
val token = map.getString("token")
|
|
83
|
+
?.trim()
|
|
84
|
+
?: throw IllegalArgumentException("token is required")
|
|
85
|
+
val secret = map.getString("secret")
|
|
86
|
+
?.trim()
|
|
87
|
+
?: throw IllegalArgumentException("secret is required")
|
|
88
|
+
|
|
89
|
+
// Optional fields
|
|
90
|
+
val navigationType = ExpoFpCrowdConnectedNavigationType.fromString(
|
|
91
|
+
map.getString("navigationType")
|
|
92
|
+
)
|
|
93
|
+
val isBackgroundUpdateEnabled = if (map.hasKey("isBackgroundUpdateEnabled")) {
|
|
94
|
+
map.getBoolean("isBackgroundUpdateEnabled")
|
|
95
|
+
} else false
|
|
96
|
+
|
|
97
|
+
val isBluetoothEnabled = if (map.hasKey("isBluetoothEnabled")) {
|
|
98
|
+
map.getBoolean("isBluetoothEnabled")
|
|
99
|
+
} else false
|
|
100
|
+
|
|
101
|
+
val isHeadingEnabled = if (map.hasKey("isHeadingEnabled")) {
|
|
102
|
+
map.getBoolean("isHeadingEnabled")
|
|
103
|
+
} else false
|
|
104
|
+
|
|
105
|
+
// Parse aliases map
|
|
106
|
+
val aliases = if (map.hasKey("aliases")) {
|
|
107
|
+
val aliasesMap = map.getMap("aliases")
|
|
108
|
+
val result = mutableMapOf<String, String>()
|
|
109
|
+
aliasesMap?.toHashMap()?.forEach { (key, value) ->
|
|
110
|
+
if (value is String) {
|
|
111
|
+
result[key] = value
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
result
|
|
115
|
+
} else {
|
|
116
|
+
emptyMap()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
val settings = ExpoFpCrowdConnectedLocationProviderSettings(
|
|
120
|
+
appKey = appKey,
|
|
121
|
+
token = token,
|
|
122
|
+
secret = secret,
|
|
123
|
+
navigationType = navigationType,
|
|
124
|
+
isBackgroundUpdateEnabled = isBackgroundUpdateEnabled,
|
|
125
|
+
isBluetoothEnabled = isBluetoothEnabled,
|
|
126
|
+
isHeadingEnabled = isHeadingEnabled,
|
|
127
|
+
aliases = aliases
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
// Validate settings before returning
|
|
131
|
+
settings.validate()
|
|
132
|
+
|
|
133
|
+
return settings
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
package com.expofpcrowdconnected
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
import net.crowdconnected.android.core.LocationType
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Floor type - can be either an index (number) or a name (string)
|
|
9
|
+
*/
|
|
10
|
+
sealed class ExpoFpFloorType {
|
|
11
|
+
data class Index(val value: Int) : ExpoFpFloorType()
|
|
12
|
+
data class Name(val value: String) : ExpoFpFloorType()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Position data matching ExpoFpPosition TypeScript interface
|
|
17
|
+
* Supports both IPS coordinates (x, y, z) and GPS coordinates (lat, lng)
|
|
18
|
+
*/
|
|
19
|
+
data class ExpoFpPosition(
|
|
20
|
+
val x: Double? = null, // IPS pixel X coordinate
|
|
21
|
+
val y: Double? = null, // IPS pixel Y coordinate
|
|
22
|
+
val z: ExpoFpFloorType? = null, // Floor information
|
|
23
|
+
val angle: Double? = null, // Heading in degrees (0-360)
|
|
24
|
+
val lat: Double? = null, // Latitude for GEO
|
|
25
|
+
val lng: Double? = null // Longitude for GEO
|
|
26
|
+
) {
|
|
27
|
+
/**
|
|
28
|
+
* Update position with new heading angle
|
|
29
|
+
*/
|
|
30
|
+
fun updateHeading(newAngle: Double): ExpoFpPosition {
|
|
31
|
+
return this.copy(angle = newAngle)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Convert to React Native WritableMap for sending to JavaScript
|
|
36
|
+
*/
|
|
37
|
+
fun toWritableMap(): WritableMap {
|
|
38
|
+
return Arguments.createMap().apply {
|
|
39
|
+
x?.let { putDouble("x", it) }
|
|
40
|
+
y?.let { putDouble("y", it) }
|
|
41
|
+
|
|
42
|
+
// Handle floor type (can be string or number)
|
|
43
|
+
when (val floor = z) {
|
|
44
|
+
is ExpoFpFloorType.Index -> putInt("z", floor.value)
|
|
45
|
+
is ExpoFpFloorType.Name -> putString("z", floor.value)
|
|
46
|
+
null -> {} // Don't include z if not present
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
angle?.let { putDouble("angle", it) }
|
|
50
|
+
lat?.let { putDouble("lat", it) }
|
|
51
|
+
lng?.let { putDouble("lng", it) }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
companion object {
|
|
56
|
+
/**
|
|
57
|
+
* Create ExpoFpPosition from CrowdConnected position data
|
|
58
|
+
* Position data mapping: IPS has x/y, GEO has lat/lng, both are mutually exclusive
|
|
59
|
+
*/
|
|
60
|
+
fun fromCrowdConnected(
|
|
61
|
+
locationType: LocationType?,
|
|
62
|
+
xPixels: Double?,
|
|
63
|
+
yPixels: Double?,
|
|
64
|
+
floor: Int?,
|
|
65
|
+
latitude: Double?,
|
|
66
|
+
longitude: Double?,
|
|
67
|
+
angle: Double? = null
|
|
68
|
+
): ExpoFpPosition {
|
|
69
|
+
val z: ExpoFpFloorType? = floor?.let { ExpoFpFloorType.Index(it) }
|
|
70
|
+
val type = locationType?.toString()?.uppercase().orEmpty()
|
|
71
|
+
|
|
72
|
+
return when (type) {
|
|
73
|
+
"IPS" -> ExpoFpPosition(
|
|
74
|
+
x = xPixels,
|
|
75
|
+
y = yPixels,
|
|
76
|
+
z = z,
|
|
77
|
+
angle = angle,
|
|
78
|
+
lat = null,
|
|
79
|
+
lng = null
|
|
80
|
+
)
|
|
81
|
+
else -> {
|
|
82
|
+
// GEO and unknown types: only lat/lng, no x/y
|
|
83
|
+
// GEO location type uses lat/lng only
|
|
84
|
+
ExpoFpPosition(
|
|
85
|
+
x = null,
|
|
86
|
+
y = null,
|
|
87
|
+
z = z,
|
|
88
|
+
angle = angle,
|
|
89
|
+
lat = latitude,
|
|
90
|
+
lng = longitude
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package com.expofpcrowdconnected
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Provider information matching ExpoFpProviderInfo TypeScript interface
|
|
8
|
+
* Contains current state of the location provider
|
|
9
|
+
*/
|
|
10
|
+
data class ExpoFpProviderInfo(
|
|
11
|
+
val deviceId: String? = null,
|
|
12
|
+
val isLocationUpdating: Boolean = false
|
|
13
|
+
) {
|
|
14
|
+
/**
|
|
15
|
+
* Convert to React Native WritableMap for sending to JavaScript
|
|
16
|
+
*/
|
|
17
|
+
fun toWritableMap(): WritableMap {
|
|
18
|
+
return Arguments.createMap().apply {
|
|
19
|
+
if (deviceId != null) {
|
|
20
|
+
putString("deviceId", deviceId)
|
|
21
|
+
} else {
|
|
22
|
+
putNull("deviceId")
|
|
23
|
+
}
|
|
24
|
+
putBoolean("isLocationUpdating", isLocationUpdating)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|