diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index a2f47b6..6cac27a 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -1,2 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/src/main/java/com/weightscalebridge/MainApplication.kt b/android/src/main/java/com/weightscalebridge/MainApplication.kt
new file mode 100644
index 0000000..f4f3a38
--- /dev/null
+++ b/android/src/main/java/com/weightscalebridge/MainApplication.kt
@@ -0,0 +1,6 @@
+// Inside the getPackages() method
+override fun getPackages(): List {
+ val packages = PackageList(this).packages
+ packages.add(WeightScalePackage()) // Add this line
+ return packages
+}
\ No newline at end of file
diff --git a/android/src/main/java/com/weightscalebridge/WeightScaleBridgeModule.kt b/android/src/main/java/com/weightscalebridge/WeightScaleBridgeModule.kt
index 9285a00..8fcb905 100644
--- a/android/src/main/java/com/weightscalebridge/WeightScaleBridgeModule.kt
+++ b/android/src/main/java/com/weightscalebridge/WeightScaleBridgeModule.kt
@@ -2,22 +2,192 @@ package com.weightscalebridge
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.annotations.ReactModule
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothManager
+import android.bluetooth.le.*
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.CountDownTimer
+import androidx.core.content.ContextCompat
+import com.facebook.react.bridge.*
+import com.facebook.react.modules.core.DeviceEventManagerModule
+import kotlin.math.abs
-@ReactModule(name = WeightScaleBridgeModule.NAME)
-class WeightScaleBridgeModule(reactContext: ReactApplicationContext) :
- NativeWeightScaleBridgeSpec(reactContext) {
+class WeightScaleModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
- override fun getName(): String {
- return NAME
- }
+ override fun getName(): String = "WeightScale"
- // Example method
- // See https://reactnative.dev/docs/native-modules-android
- override fun multiply(a: Double, b: Double): Double {
- return a * b
- }
+ private var bluetoothAdapter: BluetoothAdapter? = null
+ private var scanner: BluetoothLeScanner? = null
+ private var isScanning = false
+ private var timer: CountDownTimer? = null
- companion object {
- const val NAME = "WeightScaleBridge"
- }
-}
+ private var candidateKg: Double = Double.NaN
+ private var stableSince: Long = 0L
+
+ private val STABLE_WINDOW_MS = 4000L
+ private val WEIGHT_TOL = 0.1
+ private val MIN_VALID_KG = 5.0
+ private val MAX_VALID_KG = 250.0
+ private val MEASURE_WINDOW_MS = 20000L // 20 seconds timeout
+ private val TARGET_MAC = "34:5C:F3:40:2C:D8" // Your scale MAC
+
+ private fun sendEvent(eventName: String, params: WritableMap) {
+ reactApplicationContext
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
+ .emit(eventName, params)
+ }
+
+ @ReactMethod
+ fun startMeasuring() {
+ if (isScanning) return
+
+ val bluetoothManager = reactApplicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
+ bluetoothAdapter = bluetoothManager.adapter
+
+ if (bluetoothAdapter == null || !bluetoothAdapter!!.isEnabled) {
+ val params = Arguments.createMap().apply {
+ putString("message", "Bluetooth is not enabled. Please turn it on in settings.")
+ }
+ sendEvent("onScaleError", params)
+ return
+ }
+
+ if (!hasRequiredPermissions()) {
+ val params = Arguments.createMap().apply {
+ putString("message", "Required permissions not granted.")
+ }
+ sendEvent("onScaleError", params)
+ return
+ }
+
+ scanner = bluetoothAdapter!!.bluetoothLeScanner
+
+ // Reset stability tracking
+ candidateKg = Double.NaN
+ stableSince = 0L
+ isScanning = true
+
+ // Send initial status
+ sendEvent("onStatusChange", Arguments.createMap().apply { putString("status", "scanning") })
+
+ // Use filter for your specific scale MAC (more efficient)
+ val scanFilter = ScanFilter.Builder()
+ .setDeviceAddress(TARGET_MAC)
+ .build()
+ val scanFilters = listOf(scanFilter)
+
+ val scanSettings = ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
+ .build()
+
+ try {
+ scanner!!.startScan(scanFilters, scanSettings, scanCallback)
+ } catch (e: Exception) {
+ val params = Arguments.createMap().apply { putString("message", e.message ?: "Scan start failed") }
+ sendEvent("onScaleError", params)
+ stopMeasuring()
+ return
+ }
+
+ // 20-second timeout
+ timer = object : CountDownTimer(MEASURE_WINDOW_MS, 1000) {
+ override fun onTick(millisUntilFinished: Long) {
+ // Optional: send remaining seconds if needed
+ }
+
+ override fun onFinish() {
+ stopMeasuring()
+ sendEvent("onStatusChange", Arguments.createMap().apply { putString("status", "timeout") })
+ }
+ }.start()
+ }
+
+ @ReactMethod
+ fun stopMeasuring() {
+ if (!isScanning) return
+
+ try {
+ scanner?.stopScan(scanCallback)
+ } catch (e: Exception) {
+ // Ignore
+ }
+ timer?.cancel()
+ timer = null
+ isScanning = false
+
+ sendEvent("onStatusChange", Arguments.createMap().apply { putString("status", "stopped") })
+ }
+
+ private val scanCallback = object : ScanCallback() {
+ override fun onScanResult(callbackType: Int, result: ScanResult?) {
+ result ?: return
+ val scanRecord = result.scanRecord ?: return
+ val manufacturerData = scanRecord.manufacturerSpecificData
+
+ if (manufacturerData != null && manufacturerData.size() > 0) {
+ for (i in 0 until manufacturerData.size()) {
+ val data = manufacturerData.valueAt(i) ?: continue
+ if (data.size >= 2) {
+ val tenths = ((data[0].toInt() and 0xFF) shl 8) or (data[1].toInt() and 0xFF)
+ val kg = tenths / 10.0
+ if (kg in MIN_VALID_KG..MAX_VALID_KG) {
+ processWeight(kg)
+ }
+ }
+ }
+ }
+ }
+
+ override fun onScanFailed(errorCode: Int) {
+ val params = Arguments.createMap().apply {
+ putString("message", "Scan failed with code: $errorCode")
+ }
+ sendEvent("onScaleError", params)
+ stopMeasuring()
+ }
+ }
+
+ private fun processWeight(kg: Double) {
+ // Send live weight update
+ val liveParams = Arguments.createMap().apply { putDouble("weight", kg) }
+ sendEvent("onLiveWeight", liveParams)
+
+ // Update status to measuring
+ sendEvent("onStatusChange", Arguments.createMap().apply { putString("status", "measuring") })
+
+ val now = System.currentTimeMillis()
+
+ // Stability detection
+ if (candidateKg.isNaN() || abs(kg - candidateKg) > WEIGHT_TOL) {
+ candidateKg = kg
+ stableSince = now
+ } else if (now - stableSince >= STABLE_WINDOW_MS) {
+ // Stable weight achieved
+ val stableParams = Arguments.createMap().apply { putDouble("weight", candidateKg) }
+ sendEvent("onStableWeight", stableParams)
+ sendEvent("onStatusChange", Arguments.createMap().apply { putString("status", "stable") })
+ stopMeasuring()
+ }
+ }
+
+ private fun hasRequiredPermissions(): Boolean {
+ val perms = mutableListOf()
+ perms.add(android.Manifest.permission.ACCESS_FINE_LOCATION)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ perms.add(android.Manifest.permission.BLUETOOTH_SCAN)
+ perms.add(android.Manifest.permission.BLUETOOTH_CONNECT)
+ }
+
+ return perms.all {
+ ContextCompat.checkSelfPermission(reactApplicationContext, it) == PackageManager.PERMISSION_GRANTED
+ }
+ }
+
+ override fun onCatalystInstanceDestroy() {
+ super.onCatalystInstanceDestroy()
+ stopMeasuring() // Cleanup when RN instance destroys
+ }
+}
\ No newline at end of file
diff --git a/android/src/main/java/com/weightscalebridge/WeightScaleBridgePackage.kt b/android/src/main/java/com/weightscalebridge/WeightScaleBridgePackage.kt
index b5a984a..364be9b 100644
--- a/android/src/main/java/com/weightscalebridge/WeightScaleBridgePackage.kt
+++ b/android/src/main/java/com/weightscalebridge/WeightScaleBridgePackage.kt
@@ -1,33 +1,16 @@
-package com.weightscalebridge
+package com.cureselect.weightpluse
-import com.facebook.react.BaseReactPackage
+import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
-import com.facebook.react.module.model.ReactModuleInfo
-import com.facebook.react.module.model.ReactModuleInfoProvider
-import java.util.HashMap
+import com.facebook.react.uimanager.ViewManager
-class WeightScaleBridgePackage : BaseReactPackage() {
- override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
- return if (name == WeightScaleBridgeModule.NAME) {
- WeightScaleBridgeModule(reactContext)
- } else {
- null
+class WeightScalePackage : ReactPackage {
+ override fun createNativeModules(reactContext: ReactApplicationContext): List {
+ return listOf(WeightScaleModule(reactContext))
}
- }
- override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
- return ReactModuleInfoProvider {
- val moduleInfos: MutableMap = HashMap()
- moduleInfos[WeightScaleBridgeModule.NAME] = ReactModuleInfo(
- WeightScaleBridgeModule.NAME,
- WeightScaleBridgeModule.NAME,
- false, // canOverrideExistingModule
- false, // needsEagerInit
- false, // isCxxModule
- true // isTurboModule
- )
- moduleInfos
+ override fun createViewManagers(reactContext: ReactApplicationContext): List> {
+ return emptyList()
}
- }
-}
+}
\ No newline at end of file