Compare commits
2 Commits
7681e46449
...
eagle-weig
| Author | SHA1 | Date | |
|---|---|---|---|
| c14d41b345 | |||
| 88a89bcbd3 |
@@ -1,2 +1,14 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,23 +1,193 @@
|
|||||||
package com.weightscalebridge
|
package com.weightscalebridge
|
||||||
|
|
||||||
import com.facebook.react.bridge.ReactApplicationContext
|
import android.Manifest
|
||||||
import com.facebook.react.module.annotations.ReactModule
|
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 WeightScaleModule(
|
||||||
class WeightScaleBridgeModule(reactContext: ReactApplicationContext) :
|
private val reactContext: ReactApplicationContext
|
||||||
NativeWeightScaleBridgeSpec(reactContext) {
|
) : ReactContextBaseJavaModule(reactContext) {
|
||||||
|
|
||||||
override fun getName(): String {
|
private var bluetoothAdapter: BluetoothAdapter? = null
|
||||||
return NAME
|
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
|
private fun sendEvent(name: String, params: WritableMap?) {
|
||||||
// See https://reactnative.dev/docs/native-modules-android
|
reactContext
|
||||||
override fun multiply(a: Double, b: Double): Double {
|
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
||||||
return a * b
|
.emit(name, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
@ReactMethod
|
||||||
const val NAME = "WeightScaleBridge"
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,33 +1,19 @@
|
|||||||
package com.weightscalebridge
|
package com.weightscalebridge
|
||||||
|
|
||||||
import com.facebook.react.BaseReactPackage
|
import com.facebook.react.ReactPackage
|
||||||
import com.facebook.react.bridge.NativeModule
|
import com.facebook.react.bridge.NativeModule
|
||||||
import com.facebook.react.bridge.ReactApplicationContext
|
import com.facebook.react.bridge.ReactApplicationContext
|
||||||
import com.facebook.react.module.model.ReactModuleInfo
|
import com.facebook.react.uimanager.ViewManager
|
||||||
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
||||||
import java.util.HashMap
|
|
||||||
|
|
||||||
class WeightScaleBridgePackage : BaseReactPackage() {
|
class WeightScaleBridgePackage : ReactPackage {
|
||||||
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
||||||
return if (name == WeightScaleBridgeModule.NAME) {
|
override fun createNativeModules(
|
||||||
WeightScaleBridgeModule(reactContext)
|
reactContext: ReactApplicationContext
|
||||||
} else {
|
): List<NativeModule> {
|
||||||
null
|
return listOf(WeightScaleModule(reactContext))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
override fun createViewManagers(
|
||||||
return ReactModuleInfoProvider {
|
reactContext: ReactApplicationContext
|
||||||
val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
|
): List<ViewManager<*, *>> = emptyList()
|
||||||
moduleInfos[WeightScaleBridgeModule.NAME] = ReactModuleInfo(
|
|
||||||
WeightScaleBridgeModule.NAME,
|
|
||||||
WeightScaleBridgeModule.NAME,
|
|
||||||
false, // canOverrideExistingModule
|
|
||||||
false, // needsEagerInit
|
|
||||||
false, // isCxxModule
|
|
||||||
true // isTurboModule
|
|
||||||
)
|
|
||||||
moduleInfos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,26 @@
|
|||||||
import WeightScaleBridge from './NativeWeightScaleBridge';
|
import { NativeModules, Platform } from 'react-native';
|
||||||
|
|
||||||
export function multiply(a: number, b: number): number {
|
const LINKING_ERROR =
|
||||||
return WeightScaleBridge.multiply(a, b);
|
`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();
|
||||||
Reference in New Issue
Block a user