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/WeightScaleBridgeModule.kt b/android/src/main/java/com/weightscalebridge/WeightScaleBridgeModule.kt index 9285a00..f3a6dc6 100644 --- a/android/src/main/java/com/weightscalebridge/WeightScaleBridgeModule.kt +++ b/android/src/main/java/com/weightscalebridge/WeightScaleBridgeModule.kt @@ -1,23 +1,193 @@ package com.weightscalebridge -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.module.annotations.ReactModule +import android.Manifest +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothManager +import android.bluetooth.le.BluetoothLeScanner +import android.bluetooth.le.ScanCallback +import android.bluetooth.le.ScanResult +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.pow +import kotlin.math.round -@ReactModule(name = WeightScaleBridgeModule.NAME) -class WeightScaleBridgeModule(reactContext: ReactApplicationContext) : - NativeWeightScaleBridgeSpec(reactContext) { +class WeightScaleModule( + private val reactContext: ReactApplicationContext +) : ReactContextBaseJavaModule(reactContext) { - override fun getName(): String { - return NAME + private var bluetoothAdapter: BluetoothAdapter? = null + private var scanner: BluetoothLeScanner? = null + private var isScanning = false + private var timer: CountDownTimer? = null + private var currentWeight = 0.0 + + override fun getName(): String = "WeightScaleBridge" + + init { + val manager = + reactContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager + bluetoothAdapter = manager.adapter + scanner = bluetoothAdapter?.bluetoothLeScanner } - // Example method - // See https://reactnative.dev/docs/native-modules-android - override fun multiply(a: Double, b: Double): Double { - return a * b + private fun sendEvent(name: String, params: WritableMap?) { + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + .emit(name, params) } - companion object { - const val NAME = "WeightScaleBridge" + @ReactMethod + fun isBluetoothEnabled(promise: Promise) { + try { + val enabled = bluetoothAdapter?.isEnabled ?: false + promise.resolve(enabled) + } catch (e: Exception) { + promise.reject("BLUETOOTH_CHECK_ERROR", e.message, e) + } } -} + + @ReactMethod + fun startScanning(promise: Promise) { + try { + if (!hasPermissions()) { + promise.reject("PERMISSION", "Bluetooth permissions not granted") + return + } + + if (bluetoothAdapter?.isEnabled != true) { + promise.reject("BLUETOOTH", "Bluetooth disabled") + return + } + + if (isScanning) { + promise.resolve(true) + return + } + + isScanning = true + currentWeight = 0.0 + + scanner?.startScan(scanCallback) + + sendEvent( + "onWeightScaleStatus", + Arguments.createMap().apply { putString("status", "scanning") } + ) + + timer = object : CountDownTimer(20000, 1000) { + override fun onTick(ms: Long) {} + override fun onFinish() { + stopScanning(null) + } + }.start() + + promise.resolve(true) + } catch (e: Exception) { + promise.reject("SCAN_ERROR", e.message, e) + } + } + + @ReactMethod + fun stopScanning(promise: Promise?) { + try { + scanner?.stopScan(scanCallback) + } catch (_: Exception) {} + + timer?.cancel() + isScanning = false + + sendEvent( + "onWeightScaleStatus", + Arguments.createMap().apply { putString("status", "stopped") } + ) + + promise?.resolve(true) + } + + private val scanCallback = object : ScanCallback() { + override fun onScanResult(type: Int, result: ScanResult) { + val record = result.scanRecord ?: return + val data = record.manufacturerSpecificData + + if (data.size() == 0) return + + val raw = data.valueAt(0) + if (raw.size < 2) return + + val weight = (((raw[0].toInt() and 0xff) shl 8) or + (raw[1].toInt() and 0xff)) / 10.0 + + currentWeight = weight + + sendEvent( + "onWeightData", + Arguments.createMap().apply { + putDouble("weight", weight) + putBoolean("isStable", true) + } + ) + + sendEvent( + "onWeightScaleStatus", + Arguments.createMap().apply { + putString("status", "complete") + putString("message", "$weight kg") + } + ) + + stopScanning(null) + } + + override fun onScanFailed(errorCode: Int) { + super.onScanFailed(errorCode) + + sendEvent( + "onWeightScaleStatus", + Arguments.createMap().apply { + putString("status", "error") + putString("message", "Scan failed with code: $errorCode") + } + ) + + isScanning = false + } + } + + @ReactMethod + fun calculateBMI(weight: Double, heightCm: Double, promise: Promise) { + try { + if (heightCm <= 0) { + promise.reject("INVALID_HEIGHT", "Height must be greater than 0") + return + } + + val bmi = weight / (heightCm / 100).pow(2) + promise.resolve(round(bmi * 10) / 10) + } catch (e: Exception) { + promise.reject("BMI_ERROR", e.message, e) + } + } + + private fun hasPermissions(): Boolean { + val perms = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + arrayOf( + Manifest.permission.BLUETOOTH_SCAN, + Manifest.permission.BLUETOOTH_CONNECT + ) + else + arrayOf( + Manifest.permission.ACCESS_FINE_LOCATION + ) + + return perms.all { + ContextCompat.checkSelfPermission(reactContext, it) == + PackageManager.PERMISSION_GRANTED + } + } +} \ 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..90fd8c0 100644 --- a/android/src/main/java/com/weightscalebridge/WeightScaleBridgePackage.kt +++ b/android/src/main/java/com/weightscalebridge/WeightScaleBridgePackage.kt @@ -1,33 +1,19 @@ package com.weightscalebridge -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 WeightScaleBridgePackage : 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> = emptyList() } diff --git a/src/index.tsx b/src/index.tsx index 34b188d..c3d4088 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,5 +1,26 @@ -import WeightScaleBridge from './NativeWeightScaleBridge'; +import { NativeModules, Platform } from 'react-native'; -export function multiply(a: number, b: number): number { - return WeightScaleBridge.multiply(a, b); +const LINKING_ERROR = + `The package 'react-native-weight-scale-bridge' doesn't seem to be linked. Make sure:\n\n` + + Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + + '- You rebuilt the app after installing the package\n' + + '- You are not using Expo Go\n'; + +// Changed to match the native module name +const WeightScaleBridge = NativeModules.WeightScaleBridge; + +if (!WeightScaleBridge) { + throw new Error(LINKING_ERROR); } + +export const startScanning = () => + WeightScaleBridge.startScanning(); + +export const stopScanning = () => + WeightScaleBridge.stopScanning(); + +export const calculateBMI = (weight: number, heightCm: number) => + WeightScaleBridge.calculateBMI(weight, heightCm); + +export const isBluetoothEnabled = () => + WeightScaleBridge.isBluetoothEnabled(); \ No newline at end of file