react-native-nitro-location-tracking 0.1.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 (101) hide show
  1. package/LICENSE +20 -0
  2. package/NitroLocationTracking.podspec +29 -0
  3. package/README.md +39 -0
  4. package/android/CMakeLists.txt +24 -0
  5. package/android/build.gradle +122 -0
  6. package/android/src/main/AndroidManifest.xml +18 -0
  7. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  8. package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/ConnectionManager.kt +137 -0
  9. package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/LocationEngine.kt +93 -0
  10. package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/LocationForegroundService.kt +65 -0
  11. package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/NativeDBWriter.kt +80 -0
  12. package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/NitroLocationTracking.kt +180 -0
  13. package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/NitroLocationTrackingPackage.kt +22 -0
  14. package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/NotificationService.kt +75 -0
  15. package/ios/ConnectionManager.swift +144 -0
  16. package/ios/LocationEngine.swift +146 -0
  17. package/ios/NativeDBWriter.swift +121 -0
  18. package/ios/NitroLocationTracking.swift +127 -0
  19. package/ios/NotificationService.swift +31 -0
  20. package/lib/module/LocationSmoother.js +33 -0
  21. package/lib/module/LocationSmoother.js.map +1 -0
  22. package/lib/module/NitroLocationTracking.nitro.js +4 -0
  23. package/lib/module/NitroLocationTracking.nitro.js.map +1 -0
  24. package/lib/module/bearing.js +19 -0
  25. package/lib/module/bearing.js.map +1 -0
  26. package/lib/module/db.js +234 -0
  27. package/lib/module/db.js.map +1 -0
  28. package/lib/module/index.js +68 -0
  29. package/lib/module/index.js.map +1 -0
  30. package/lib/module/package.json +1 -0
  31. package/lib/typescript/package.json +1 -0
  32. package/lib/typescript/src/LocationSmoother.d.ts +19 -0
  33. package/lib/typescript/src/LocationSmoother.d.ts.map +1 -0
  34. package/lib/typescript/src/NitroLocationTracking.nitro.d.ts +59 -0
  35. package/lib/typescript/src/NitroLocationTracking.nitro.d.ts.map +1 -0
  36. package/lib/typescript/src/bearing.d.ts +9 -0
  37. package/lib/typescript/src/bearing.d.ts.map +1 -0
  38. package/lib/typescript/src/db.d.ts +1 -0
  39. package/lib/typescript/src/db.d.ts.map +1 -0
  40. package/lib/typescript/src/index.d.ts +21 -0
  41. package/lib/typescript/src/index.d.ts.map +1 -0
  42. package/nitro.json +17 -0
  43. package/nitrogen/generated/android/c++/JAccuracyLevel.hpp +61 -0
  44. package/nitrogen/generated/android/c++/JConnectionConfig.hpp +81 -0
  45. package/nitrogen/generated/android/c++/JConnectionState.hpp +61 -0
  46. package/nitrogen/generated/android/c++/JFunc_void_ConnectionState.hpp +77 -0
  47. package/nitrogen/generated/android/c++/JFunc_void_LocationData.hpp +77 -0
  48. package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
  49. package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
  50. package/nitrogen/generated/android/c++/JHybridNitroLocationTrackingSpec.cpp +179 -0
  51. package/nitrogen/generated/android/c++/JHybridNitroLocationTrackingSpec.hpp +83 -0
  52. package/nitrogen/generated/android/c++/JLocationConfig.hpp +91 -0
  53. package/nitrogen/generated/android/c++/JLocationData.hpp +81 -0
  54. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolocationtracking/AccuracyLevel.kt +24 -0
  55. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolocationtracking/ConnectionConfig.kt +56 -0
  56. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolocationtracking/ConnectionState.kt +24 -0
  57. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolocationtracking/Func_void_ConnectionState.kt +80 -0
  58. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolocationtracking/Func_void_LocationData.kt +80 -0
  59. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolocationtracking/Func_void_bool.kt +80 -0
  60. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolocationtracking/Func_void_std__string.kt +80 -0
  61. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolocationtracking/HybridNitroLocationTrackingSpec.kt +146 -0
  62. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolocationtracking/LocationConfig.kt +62 -0
  63. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolocationtracking/LocationData.kt +56 -0
  64. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolocationtracking/nitrolocationtrackingOnLoad.kt +35 -0
  65. package/nitrogen/generated/android/nitrolocationtracking+autolinking.cmake +81 -0
  66. package/nitrogen/generated/android/nitrolocationtracking+autolinking.gradle +27 -0
  67. package/nitrogen/generated/android/nitrolocationtrackingOnLoad.cpp +52 -0
  68. package/nitrogen/generated/android/nitrolocationtrackingOnLoad.hpp +25 -0
  69. package/nitrogen/generated/ios/NitroLocationTracking+autolinking.rb +60 -0
  70. package/nitrogen/generated/ios/NitroLocationTracking-Swift-Cxx-Bridge.cpp +73 -0
  71. package/nitrogen/generated/ios/NitroLocationTracking-Swift-Cxx-Bridge.hpp +231 -0
  72. package/nitrogen/generated/ios/NitroLocationTracking-Swift-Cxx-Umbrella.hpp +61 -0
  73. package/nitrogen/generated/ios/NitroLocationTrackingAutolinking.mm +33 -0
  74. package/nitrogen/generated/ios/NitroLocationTrackingAutolinking.swift +26 -0
  75. package/nitrogen/generated/ios/c++/HybridNitroLocationTrackingSpecSwift.cpp +11 -0
  76. package/nitrogen/generated/ios/c++/HybridNitroLocationTrackingSpecSwift.hpp +206 -0
  77. package/nitrogen/generated/ios/swift/AccuracyLevel.swift +44 -0
  78. package/nitrogen/generated/ios/swift/ConnectionConfig.swift +59 -0
  79. package/nitrogen/generated/ios/swift/ConnectionState.swift +44 -0
  80. package/nitrogen/generated/ios/swift/Func_void_ConnectionState.swift +46 -0
  81. package/nitrogen/generated/ios/swift/Func_void_LocationData.swift +46 -0
  82. package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
  83. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  84. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  85. package/nitrogen/generated/ios/swift/HybridNitroLocationTrackingSpec.swift +72 -0
  86. package/nitrogen/generated/ios/swift/HybridNitroLocationTrackingSpec_cxx.swift +362 -0
  87. package/nitrogen/generated/ios/swift/LocationConfig.swift +69 -0
  88. package/nitrogen/generated/ios/swift/LocationData.swift +59 -0
  89. package/nitrogen/generated/shared/c++/AccuracyLevel.hpp +80 -0
  90. package/nitrogen/generated/shared/c++/ConnectionConfig.hpp +107 -0
  91. package/nitrogen/generated/shared/c++/ConnectionState.hpp +80 -0
  92. package/nitrogen/generated/shared/c++/HybridNitroLocationTrackingSpec.cpp +38 -0
  93. package/nitrogen/generated/shared/c++/HybridNitroLocationTrackingSpec.hpp +92 -0
  94. package/nitrogen/generated/shared/c++/LocationConfig.hpp +117 -0
  95. package/nitrogen/generated/shared/c++/LocationData.hpp +107 -0
  96. package/package.json +174 -0
  97. package/src/LocationSmoother.ts +46 -0
  98. package/src/NitroLocationTracking.nitro.ts +79 -0
  99. package/src/bearing.ts +22 -0
  100. package/src/db.ts +232 -0
  101. package/src/index.tsx +92 -0
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 alisherrahimov
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,29 @@
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 = "NitroLocationTracking"
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/alisherrahimov/react-native-nitro-location-tracking.git", :tag => "#{s.version}" }
15
+
16
+ s.source_files = [
17
+ "ios/**/*.{swift}",
18
+ "ios/**/*.{m,mm}",
19
+ "cpp/**/*.{hpp,cpp}",
20
+ ]
21
+
22
+ s.dependency 'React-jsi'
23
+ s.dependency 'React-callinvoker'
24
+
25
+ load 'nitrogen/generated/ios/NitroLocationTracking+autolinking.rb'
26
+ add_nitrogen_files(s)
27
+
28
+ install_modules_dependencies(s)
29
+ end
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # react-native-nitro-location-tracking
2
+
3
+
4
+
5
+ ## Installation
6
+
7
+
8
+ ```sh
9
+ npm install react-native-nitro-location-tracking react-native-nitro-modules
10
+
11
+ > `react-native-nitro-modules` is required as this library relies on [Nitro Modules](https://nitro.margelo.com/).
12
+ ```
13
+
14
+
15
+ ## Usage
16
+
17
+
18
+ ```js
19
+ import { multiply } from 'react-native-nitro-location-tracking';
20
+
21
+ // ...
22
+
23
+ const result = multiply(3, 7);
24
+ ```
25
+
26
+
27
+ ## Contributing
28
+
29
+ - [Development workflow](CONTRIBUTING.md#development-workflow)
30
+ - [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
31
+ - [Code of conduct](CODE_OF_CONDUCT.md)
32
+
33
+ ## License
34
+
35
+ MIT
36
+
37
+ ---
38
+
39
+ Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
@@ -0,0 +1,24 @@
1
+ project(nitrolocationtracking)
2
+ cmake_minimum_required(VERSION 3.9.0)
3
+
4
+ set(PACKAGE_NAME nitrolocationtracking)
5
+ set(CMAKE_VERBOSE_MAKEFILE ON)
6
+ set(CMAKE_CXX_STANDARD 20)
7
+
8
+ # Define C++ library and add all sources
9
+ add_library(${PACKAGE_NAME} SHARED src/main/cpp/cpp-adapter.cpp)
10
+
11
+ # Add Nitrogen specs :)
12
+ include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/nitrolocationtracking+autolinking.cmake)
13
+
14
+ # Set up local includes
15
+ include_directories("src/main/cpp" "../cpp")
16
+
17
+ find_library(LOG_LIB log)
18
+
19
+ # Link all libraries together
20
+ target_link_libraries(
21
+ ${PACKAGE_NAME}
22
+ ${LOG_LIB}
23
+ android # <-- Android core
24
+ )
@@ -0,0 +1,122 @@
1
+ buildscript {
2
+ ext.NitroLocationTracking = [
3
+ kotlinVersion: "2.0.21",
4
+ minSdkVersion: 24,
5
+ compileSdkVersion: 36,
6
+ targetSdkVersion: 36
7
+ ]
8
+
9
+ ext.getExtOrDefault = { prop ->
10
+ if (rootProject.ext.has(prop)) {
11
+ return rootProject.ext.get(prop)
12
+ }
13
+
14
+ return NitroLocationTracking[prop]
15
+ }
16
+
17
+ repositories {
18
+ google()
19
+ mavenCentral()
20
+ }
21
+
22
+ dependencies {
23
+ classpath "com.android.tools.build:gradle:8.7.2"
24
+ // noinspection DifferentKotlinGradleVersion
25
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
26
+ }
27
+ }
28
+
29
+ def reactNativeArchitectures() {
30
+ def value = rootProject.getProperties().get("reactNativeArchitectures")
31
+ return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
32
+ }
33
+
34
+ apply plugin: "com.android.library"
35
+ apply plugin: "kotlin-android"
36
+ apply from: '../nitrogen/generated/android/nitrolocationtracking+autolinking.gradle'
37
+
38
+ apply plugin: "com.facebook.react"
39
+
40
+ android {
41
+ namespace "com.margelo.nitro.nitrolocationtracking"
42
+
43
+ compileSdkVersion getExtOrDefault("compileSdkVersion")
44
+
45
+ defaultConfig {
46
+ minSdkVersion getExtOrDefault("minSdkVersion")
47
+ targetSdkVersion getExtOrDefault("targetSdkVersion")
48
+
49
+ externalNativeBuild {
50
+ cmake {
51
+ cppFlags "-frtti -fexceptions -Wall -fstack-protector-all"
52
+ arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
53
+ abiFilters (*reactNativeArchitectures())
54
+
55
+ buildTypes {
56
+ debug {
57
+ cppFlags "-O1 -g"
58
+ }
59
+ release {
60
+ cppFlags "-O2"
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+
67
+ externalNativeBuild {
68
+ cmake {
69
+ path "CMakeLists.txt"
70
+ }
71
+ }
72
+
73
+ packagingOptions {
74
+ excludes = [
75
+ "META-INF",
76
+ "META-INF/**",
77
+ "**/libc++_shared.so",
78
+ "**/libfbjni.so",
79
+ "**/libjsi.so",
80
+ "**/libfolly_json.so",
81
+ "**/libfolly_runtime.so",
82
+ "**/libglog.so",
83
+ "**/libhermes.so",
84
+ "**/libhermes-executor-debug.so",
85
+ "**/libhermes_executor.so",
86
+ "**/libreactnative.so",
87
+ "**/libreactnativejni.so",
88
+ "**/libturbomodulejsijni.so",
89
+ "**/libreact_nativemodule_core.so",
90
+ "**/libjscexecutor.so"
91
+ ]
92
+ }
93
+
94
+ buildFeatures {
95
+ buildConfig true
96
+ prefab true
97
+ }
98
+
99
+ buildTypes {
100
+ release {
101
+ minifyEnabled false
102
+ }
103
+ }
104
+
105
+ lint {
106
+ disable "GradleCompatible"
107
+ }
108
+
109
+ compileOptions {
110
+ sourceCompatibility JavaVersion.VERSION_1_8
111
+ targetCompatibility JavaVersion.VERSION_1_8
112
+ }
113
+ }
114
+
115
+ dependencies {
116
+ implementation "com.facebook.react:react-android"
117
+ implementation project(":react-native-nitro-modules")
118
+ implementation "com.google.android.gms:play-services-location:21.3.0"
119
+ implementation "com.squareup.okhttp3:okhttp:4.12.0"
120
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0"
121
+ implementation "androidx.core:core-ktx:1.15.0"
122
+ }
@@ -0,0 +1,18 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
3
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
4
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
5
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
6
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
7
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
8
+ <uses-permission android:name="android.permission.INTERNET" />
9
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
10
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
11
+
12
+ <application>
13
+ <service
14
+ android:name=".LocationForegroundService"
15
+ android:foregroundServiceType="location"
16
+ android:exported="false" />
17
+ </application>
18
+ </manifest>
@@ -0,0 +1,6 @@
1
+ #include <jni.h>
2
+ #include "nitrolocationtrackingOnLoad.hpp"
3
+
4
+ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
5
+ return margelo::nitro::nitrolocationtracking::initialize(vm);
6
+ }
@@ -0,0 +1,137 @@
1
+ package com.margelo.nitro.nitrolocationtracking
2
+
3
+ import okhttp3.*
4
+ import okhttp3.MediaType.Companion.toMediaType
5
+ import okhttp3.RequestBody.Companion.toRequestBody
6
+ import org.json.JSONArray
7
+ import org.json.JSONObject
8
+ import java.util.concurrent.TimeUnit
9
+
10
+ class ConnectionManager {
11
+ private var client = OkHttpClient.Builder()
12
+ .readTimeout(0, TimeUnit.MILLISECONDS).build()
13
+ private var webSocket: WebSocket? = null
14
+ private var config: ConnectionConfig? = null
15
+ private var reconnectAttempts = 0
16
+ private var connected = false
17
+
18
+ var onStateChange: ((ConnectionState) -> Unit)? = null
19
+ var onMessage: ((String) -> Unit)? = null
20
+ var dbWriter: NativeDBWriter? = null
21
+
22
+ val isConnected: Boolean get() = connected
23
+
24
+ private val handler = android.os.Handler(android.os.Looper.getMainLooper())
25
+ private var syncRunnable: Runnable? = null
26
+
27
+ fun configure(config: ConnectionConfig) {
28
+ this.config = config
29
+ }
30
+
31
+ fun connect() {
32
+ val cfg = config ?: return
33
+ val request = Request.Builder().url(cfg.wsUrl)
34
+ .addHeader("Authorization", "Bearer ${cfg.authToken}").build()
35
+
36
+ webSocket = client.newWebSocket(request, object : WebSocketListener() {
37
+ override fun onOpen(ws: WebSocket, r: Response) {
38
+ connected = true
39
+ reconnectAttempts = 0
40
+ onStateChange?.invoke(ConnectionState.CONNECTED)
41
+ startSyncTimer()
42
+ }
43
+
44
+ override fun onMessage(ws: WebSocket, text: String) {
45
+ onMessage?.invoke(text)
46
+ }
47
+
48
+ override fun onFailure(ws: WebSocket, t: Throwable, r: Response?) {
49
+ handleDisconnect()
50
+ }
51
+
52
+ override fun onClosed(ws: WebSocket, code: Int, reason: String) {
53
+ connected = false
54
+ onStateChange?.invoke(ConnectionState.DISCONNECTED)
55
+ }
56
+ })
57
+ }
58
+
59
+ fun disconnect() {
60
+ syncRunnable?.let { handler.removeCallbacks(it) }
61
+ webSocket?.close(1000, "bye")
62
+ connected = false
63
+ onStateChange?.invoke(ConnectionState.DISCONNECTED)
64
+ }
65
+
66
+ fun send(msg: String) {
67
+ webSocket?.send(msg)
68
+ }
69
+
70
+ fun getState(): ConnectionState {
71
+ return if (connected) ConnectionState.CONNECTED else ConnectionState.DISCONNECTED
72
+ }
73
+
74
+ private fun handleDisconnect() {
75
+ val cfg = config ?: return
76
+ connected = false
77
+ onStateChange?.invoke(ConnectionState.RECONNECTING)
78
+ if (reconnectAttempts >= cfg.maxReconnectAttempts.toInt()) {
79
+ onStateChange?.invoke(ConnectionState.DISCONNECTED)
80
+ return
81
+ }
82
+ reconnectAttempts++
83
+ handler.postDelayed({ connect() }, cfg.reconnectIntervalMs.toLong())
84
+ }
85
+
86
+ private fun startSyncTimer() {
87
+ val cfg = config ?: return
88
+ syncRunnable = object : Runnable {
89
+ override fun run() {
90
+ flushQueue()
91
+ handler.postDelayed(this, cfg.syncIntervalMs.toLong())
92
+ }
93
+ }
94
+ handler.postDelayed(syncRunnable!!, cfg.syncIntervalMs.toLong())
95
+ }
96
+
97
+ fun flushQueue(): Boolean {
98
+ val cfg = config ?: return false
99
+ val db = dbWriter ?: return false
100
+ val queued = db.getUnsyncedBatch(cfg.batchSize.toInt())
101
+ if (queued.isEmpty()) return true
102
+
103
+ val ids = queued.map { it.first }
104
+ val arr = JSONArray()
105
+ for (pair in queued) {
106
+ val loc = pair.second
107
+ val obj = JSONObject()
108
+ obj.put("latitude", loc.latitude)
109
+ obj.put("longitude", loc.longitude)
110
+ obj.put("altitude", loc.altitude)
111
+ obj.put("speed", loc.speed)
112
+ obj.put("bearing", loc.bearing)
113
+ obj.put("accuracy", loc.accuracy)
114
+ obj.put("timestamp", loc.timestamp)
115
+ arr.put(obj)
116
+ }
117
+
118
+ if (connected) {
119
+ send(arr.toString())
120
+ db.markSynced(ids)
121
+ } else {
122
+ val body = arr.toString().toRequestBody("application/json".toMediaType())
123
+ val req = Request.Builder()
124
+ .url("${cfg.restUrl}/locations/batch")
125
+ .addHeader("Authorization", "Bearer ${cfg.authToken}")
126
+ .post(body).build()
127
+ client.newCall(req).enqueue(object : Callback {
128
+ override fun onResponse(call: Call, response: Response) {
129
+ if (response.isSuccessful) db.markSynced(ids)
130
+ }
131
+
132
+ override fun onFailure(call: Call, e: java.io.IOException) {}
133
+ })
134
+ }
135
+ return true
136
+ }
137
+ }
@@ -0,0 +1,93 @@
1
+ package com.margelo.nitro.nitrolocationtracking
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.content.Context
5
+ import android.location.Location
6
+ import android.os.Looper
7
+ import com.google.android.gms.location.*
8
+ import kotlin.coroutines.resume
9
+
10
+ class LocationEngine(private val context: Context) {
11
+ private val fusedClient =
12
+ LocationServices.getFusedLocationProviderClient(context)
13
+ private var locationCallback: LocationCallback? = null
14
+
15
+ var onLocation: ((LocationData) -> Unit)? = null
16
+ var onMotionChange: ((Boolean) -> Unit)? = null
17
+ var dbWriter: NativeDBWriter? = null
18
+ var currentRideId: String? = null
19
+ private var lastSpeed = 0f
20
+ private var tracking = false
21
+
22
+ val isTracking: Boolean get() = tracking
23
+
24
+ @SuppressLint("MissingPermission")
25
+ fun start(config: LocationConfig) {
26
+ val priority = when (config.desiredAccuracy) {
27
+ AccuracyLevel.HIGH -> Priority.PRIORITY_HIGH_ACCURACY
28
+ AccuracyLevel.BALANCED -> Priority.PRIORITY_BALANCED_POWER_ACCURACY
29
+ AccuracyLevel.LOW -> Priority.PRIORITY_LOW_POWER
30
+ }
31
+ val request = LocationRequest.Builder(priority, config.intervalMs.toLong())
32
+ .setMinUpdateDistanceMeters(config.distanceFilter.toFloat())
33
+ .setMinUpdateIntervalMillis(config.fastestIntervalMs.toLong())
34
+ .build()
35
+
36
+ locationCallback = object : LocationCallback() {
37
+ override fun onLocationResult(result: LocationResult) {
38
+ result.lastLocation?.let { processLocation(it) }
39
+ }
40
+ }
41
+ fusedClient.requestLocationUpdates(
42
+ request, locationCallback!!, Looper.getMainLooper())
43
+ tracking = true
44
+ }
45
+
46
+ fun stop() {
47
+ locationCallback?.let { fusedClient.removeLocationUpdates(it) }
48
+ tracking = false
49
+ }
50
+
51
+ @SuppressLint("MissingPermission")
52
+ fun getCurrentLocation(callback: (LocationData?) -> Unit) {
53
+ fusedClient.lastLocation.addOnSuccessListener { location ->
54
+ if (location != null) {
55
+ callback(locationToData(location))
56
+ } else {
57
+ callback(null)
58
+ }
59
+ }.addOnFailureListener {
60
+ callback(null)
61
+ }
62
+ }
63
+
64
+ suspend fun getCurrentLocationSuspend(): LocationData? {
65
+ return kotlin.coroutines.suspendCoroutine { cont ->
66
+ getCurrentLocation { data ->
67
+ cont.resume(data)
68
+ }
69
+ }
70
+ }
71
+
72
+ private fun processLocation(location: Location) {
73
+ val data = locationToData(location)
74
+ // dbWriter?.insert(data, currentRideId)
75
+ onLocation?.invoke(data)
76
+
77
+ val isMoving = location.speed > 0.5f
78
+ if (isMoving != (lastSpeed > 0.5f)) onMotionChange?.invoke(isMoving)
79
+ lastSpeed = location.speed
80
+ }
81
+
82
+ private fun locationToData(location: Location): LocationData {
83
+ return LocationData(
84
+ latitude = location.latitude,
85
+ longitude = location.longitude,
86
+ altitude = location.altitude,
87
+ speed = location.speed.toDouble(),
88
+ bearing = location.bearing.toDouble(),
89
+ accuracy = location.accuracy.toDouble(),
90
+ timestamp = location.time.toDouble()
91
+ )
92
+ }
93
+ }
@@ -0,0 +1,65 @@
1
+ package com.margelo.nitro.nitrolocationtracking
2
+
3
+ import android.app.*
4
+ import android.content.Intent
5
+ import android.os.IBinder
6
+ import android.os.Build
7
+ import androidx.core.app.NotificationCompat
8
+
9
+ class LocationForegroundService : Service() {
10
+ companion object {
11
+ const val CHANNEL_ID = "nitro_location_channel"
12
+ const val NOTIFICATION_ID = 77001
13
+ const val ACTION_START = "com.nitrolocation.START"
14
+ const val ACTION_STOP = "com.nitrolocation.STOP"
15
+ const val ACTION_UPDATE = "com.nitrolocation.UPDATE"
16
+ }
17
+
18
+ override fun onCreate() {
19
+ super.onCreate()
20
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
21
+ val channel = NotificationChannel(
22
+ CHANNEL_ID,
23
+ "Location Tracking",
24
+ NotificationManager.IMPORTANCE_LOW
25
+ )
26
+ (getSystemService(NOTIFICATION_SERVICE) as NotificationManager)
27
+ .createNotificationChannel(channel)
28
+ }
29
+ }
30
+
31
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
32
+ val title = intent?.getStringExtra("title") ?: "Location Active"
33
+ val text = intent?.getStringExtra("text") ?: "Tracking your location"
34
+ when (intent?.action) {
35
+ ACTION_START -> startForeground(
36
+ NOTIFICATION_ID, buildNotification(title, text))
37
+ ACTION_STOP -> {
38
+ stopForeground(STOP_FOREGROUND_REMOVE)
39
+ stopSelf()
40
+ }
41
+ ACTION_UPDATE -> (getSystemService(NOTIFICATION_SERVICE)
42
+ as NotificationManager).notify(
43
+ NOTIFICATION_ID, buildNotification(title, text))
44
+ }
45
+ return START_STICKY
46
+ }
47
+
48
+ private fun buildNotification(title: String, text: String): Notification {
49
+ val pi = PendingIntent.getActivity(
50
+ this, 0,
51
+ packageManager.getLaunchIntentForPackage(packageName),
52
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
53
+ )
54
+ return NotificationCompat.Builder(this, CHANNEL_ID)
55
+ .setContentTitle(title)
56
+ .setContentText(text)
57
+ .setSmallIcon(android.R.drawable.ic_menu_mylocation)
58
+ .setOngoing(true)
59
+ .setContentIntent(pi)
60
+ .setPriority(NotificationCompat.PRIORITY_LOW)
61
+ .build()
62
+ }
63
+
64
+ override fun onBind(intent: Intent?): IBinder? = null
65
+ }
@@ -0,0 +1,80 @@
1
+ package com.margelo.nitro.nitrolocationtracking
2
+
3
+ import android.content.ContentValues
4
+ import android.content.Context
5
+ import android.database.sqlite.SQLiteDatabase
6
+ import android.database.sqlite.SQLiteOpenHelper
7
+ import java.util.UUID
8
+
9
+ class NativeDBWriter(context: Context) :
10
+ SQLiteOpenHelper(context, "nitro_location.db", null, 1) {
11
+
12
+ override fun onCreate(db: SQLiteDatabase) {
13
+ db.execSQL("""
14
+ CREATE TABLE IF NOT EXISTS locations (
15
+ id TEXT PRIMARY KEY,
16
+ latitude REAL NOT NULL, longitude REAL NOT NULL,
17
+ altitude REAL, speed REAL, bearing REAL, accuracy REAL,
18
+ timestamp INTEGER NOT NULL, ride_id TEXT,
19
+ synced INTEGER DEFAULT 0, retry_count INTEGER DEFAULT 0,
20
+ created_at INTEGER DEFAULT (strftime('%s','now') * 1000)
21
+ )
22
+ """)
23
+ db.execSQL("CREATE INDEX IF NOT EXISTS idx_locations_synced ON locations(synced)")
24
+ db.execSQL("CREATE INDEX IF NOT EXISTS idx_locations_timestamp ON locations(timestamp)")
25
+ db.execSQL("CREATE INDEX IF NOT EXISTS idx_locations_ride_id ON locations(ride_id)")
26
+ db.execSQL("PRAGMA journal_mode = WAL")
27
+ }
28
+
29
+ override fun onUpgrade(db: SQLiteDatabase, old: Int, new: Int) {}
30
+
31
+ fun insert(location: LocationData, rideId: String? = null) {
32
+ writableDatabase.insert("locations", null, ContentValues().apply {
33
+ put("id", UUID.randomUUID().toString())
34
+ put("latitude", location.latitude)
35
+ put("longitude", location.longitude)
36
+ put("altitude", location.altitude)
37
+ put("speed", location.speed)
38
+ put("bearing", location.bearing)
39
+ put("accuracy", location.accuracy)
40
+ put("timestamp", location.timestamp.toLong())
41
+ put("ride_id", rideId)
42
+ put("synced", 0)
43
+ })
44
+ }
45
+
46
+ fun getUnsyncedBatch(limit: Int): List<Pair<String, LocationData>> {
47
+ val list = mutableListOf<Pair<String, LocationData>>()
48
+ readableDatabase.query("locations", null, "synced = 0",
49
+ null, null, null, "timestamp ASC", "$limit").use {
50
+ while (it.moveToNext()) {
51
+ val id = it.getString(it.getColumnIndexOrThrow("id"))
52
+ val data = LocationData(
53
+ latitude = it.getDouble(it.getColumnIndexOrThrow("latitude")),
54
+ longitude = it.getDouble(it.getColumnIndexOrThrow("longitude")),
55
+ altitude = it.getDouble(it.getColumnIndexOrThrow("altitude")),
56
+ speed = it.getDouble(it.getColumnIndexOrThrow("speed")),
57
+ bearing = it.getDouble(it.getColumnIndexOrThrow("bearing")),
58
+ accuracy = it.getDouble(it.getColumnIndexOrThrow("accuracy")),
59
+ timestamp = it.getLong(it.getColumnIndexOrThrow("timestamp")).toDouble()
60
+ )
61
+ list.add(Pair(id, data))
62
+ }
63
+ }
64
+ return list
65
+ }
66
+
67
+ fun markSynced(ids: List<String>) {
68
+ val ph = ids.joinToString(",") { "?" }
69
+ writableDatabase.execSQL(
70
+ "UPDATE locations SET synced = 1 WHERE id IN ($ph)",
71
+ ids.toTypedArray())
72
+ }
73
+
74
+ fun clearOldSynced() {
75
+ writableDatabase.execSQL("""
76
+ DELETE FROM locations WHERE synced = 1
77
+ AND timestamp < (strftime('%s','now') * 1000 - 86400000)
78
+ """)
79
+ }
80
+ }