@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.
Files changed (42) hide show
  1. package/ExpofpCrowdconnected.podspec +26 -0
  2. package/LICENSE +20 -0
  3. package/README.md +169 -0
  4. package/android/build.gradle +86 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/com/expofpcrowdconnected/ExpoFpAzimuthProvider.kt +94 -0
  8. package/android/src/main/java/com/expofpcrowdconnected/ExpoFpCrowdConnectedLocationProviderSettings.kt +136 -0
  9. package/android/src/main/java/com/expofpcrowdconnected/ExpoFpPosition.kt +96 -0
  10. package/android/src/main/java/com/expofpcrowdconnected/ExpoFpProviderInfo.kt +27 -0
  11. package/android/src/main/java/com/expofpcrowdconnected/ExpofpCrowdconnectedPackage.kt +33 -0
  12. package/android/src/main/java/com/expofpcrowdconnected/RCTCrowdConnectedLocationProviderModule.kt +504 -0
  13. package/ios/ExpoFpLocationProvider/ExpoFpCrowdConnectedLocationProviderSettings.swift +116 -0
  14. package/ios/ExpoFpLocationProvider/ExpoFpCrowdConnectedNavigationType.swift +14 -0
  15. package/ios/ExpoFpLocationProvider/ExpoFpError.swift +28 -0
  16. package/ios/ExpoFpLocationProvider/ExpoFpFloorType.swift +39 -0
  17. package/ios/ExpoFpLocationProvider/ExpoFpLocationSettings.swift +18 -0
  18. package/ios/ExpoFpLocationProvider/ExpoFpPosition.swift +42 -0
  19. package/ios/ExpoFpLocationProvider/ExpoFpProviderInfo.swift +12 -0
  20. package/ios/Extensions/Encodable+Extensions.swift +28 -0
  21. package/ios/Extensions/LocationTrackingMode+Extensions.swift +16 -0
  22. package/ios/Extensions/NSDictionary+Extensions.swift +21 -0
  23. package/ios/RCTCrowdConnectedLocationProvider.m +24 -0
  24. package/ios/RCTCrowdConnectedLocationProvider.swift +252 -0
  25. package/lib/module/crowdconnected-location-provider-native-module.js +38 -0
  26. package/lib/module/crowdconnected-location-provider-native-module.js.map +1 -0
  27. package/lib/module/index.js +5 -0
  28. package/lib/module/index.js.map +1 -0
  29. package/lib/module/package.json +1 -0
  30. package/lib/module/types.js +2 -0
  31. package/lib/module/types.js.map +1 -0
  32. package/lib/typescript/package.json +1 -0
  33. package/lib/typescript/src/crowdconnected-location-provider-native-module.d.ts +4 -0
  34. package/lib/typescript/src/crowdconnected-location-provider-native-module.d.ts.map +1 -0
  35. package/lib/typescript/src/index.d.ts +3 -0
  36. package/lib/typescript/src/index.d.ts.map +1 -0
  37. package/lib/typescript/src/types.d.ts +40 -0
  38. package/lib/typescript/src/types.d.ts.map +1 -0
  39. package/package.json +154 -0
  40. package/src/crowdconnected-location-provider-native-module.tsx +57 -0
  41. package/src/index.tsx +2 -0
  42. 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
+ [![npm](https://img.shields.io/npm/v/@expofp/react-native-efp-crowdconnected)](https://www.npmjs.com/package/@expofp/react-native-efp-crowdconnected)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@expofp/react-native-efp-crowdconnected)](https://www.npmjs.com/package/@expofp/react-native-efp-crowdconnected)
7
+ [![license](https://img.shields.io/npm/l/@expofp/react-native-efp-crowdconnected)](./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,5 @@
1
+ ExpofpCrowdconnected_kotlinVersion=2.0.21
2
+ ExpofpCrowdconnected_minSdkVersion=24
3
+ ExpofpCrowdconnected_targetSdkVersion=34
4
+ ExpofpCrowdconnected_compileSdkVersion=35
5
+ ExpofpCrowdconnected_ndkVersion=27.1.12297006
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -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
+ }