feat: consent screen, reminder system, medical metrics, sex field, design tokens, code quality
A. Reminder: ReminderService (AlarmManager chain), ReminderSettingsView, bell icon in Piezo top bar, reschedule on catheterization B. Consent: ConsentView (3 mandatory items), onboarding→consent→registration flow, persistent consent storage, sex field in User + Registration Step 2 C. Diary: 24hUV, NUV, NPI, max capacity, day/night frequency metrics card D. Design: DesignTokens.kt, 48dp touch targets, DonutChart semantics, pressScale modifier E. Quality: BuildConfig.DEBUG log wrapping, DateFormatter caching, background I/O for diary store Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import com.example.medilightv2android.BuildConfig
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import java.util.UUID
|
||||
@@ -27,6 +28,9 @@ class BleManager private constructor(private val context: Context) {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "BleManager"
|
||||
private inline fun logd(msg: () -> String) { if (BuildConfig.DEBUG) Log.d(TAG, msg()) }
|
||||
private inline fun logw(msg: () -> String) { if (BuildConfig.DEBUG) Log.w(TAG, msg()) }
|
||||
private inline fun loge(msg: () -> String) { if (BuildConfig.DEBUG) Log.e(TAG, msg()) }
|
||||
|
||||
@Volatile
|
||||
private var instance: BleManager? = null
|
||||
@@ -87,7 +91,7 @@ class BleManager private constructor(private val context: Context) {
|
||||
bluetoothLeScanner = bluetoothAdapter.bluetoothLeScanner
|
||||
|
||||
if (bluetoothLeScanner == null) {
|
||||
Log.e(TAG, "BluetoothLeScanner is null")
|
||||
loge { "BluetoothLeScanner is null" }
|
||||
isScanning.value = false
|
||||
return
|
||||
}
|
||||
@@ -98,9 +102,9 @@ class BleManager private constructor(private val context: Context) {
|
||||
|
||||
try {
|
||||
bluetoothLeScanner?.startScan(null, settings, scanCallback)
|
||||
Log.d(TAG, "BLE scan started")
|
||||
logd { "BLE scan started" }
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to start scan: ${e.message}")
|
||||
loge { "Failed to start scan: ${e.message}" }
|
||||
isScanning.value = false
|
||||
return
|
||||
}
|
||||
@@ -115,7 +119,7 @@ class BleManager private constructor(private val context: Context) {
|
||||
try {
|
||||
bluetoothLeScanner?.stopScan(scanCallback)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to stop scan: ${e.message}")
|
||||
logw { "Failed to stop scan: ${e.message}" }
|
||||
}
|
||||
isScanning.value = false
|
||||
scanTimer?.let { handler.removeCallbacks(it) }
|
||||
@@ -134,13 +138,13 @@ class BleManager private constructor(private val context: Context) {
|
||||
discoveredDevices[existing] = discoveredDevices[existing].copy(rssi = result.rssi)
|
||||
} else {
|
||||
discoveredDevices.add(BleDevice(device, result.rssi))
|
||||
Log.d(TAG, "Discovered: $name (${device.address}) rssi=${result.rssi}")
|
||||
logd { "Discovered: $name (${device.address}) rssi=${result.rssi}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onScanFailed(errorCode: Int) {
|
||||
Log.e(TAG, "Scan failed with error code: $errorCode")
|
||||
loge { "Scan failed with error code: $errorCode" }
|
||||
handler.post {
|
||||
isScanning.value = false
|
||||
connectionError.value = "Scan failed (error $errorCode)"
|
||||
@@ -151,16 +155,16 @@ class BleManager private constructor(private val context: Context) {
|
||||
// Connection
|
||||
fun connect(bleDevice: BleDevice) {
|
||||
stopScan()
|
||||
Log.d(TAG, "Connecting to ${bleDevice.name} (${bleDevice.address})")
|
||||
logd { "Connecting to ${bleDevice.name} (${bleDevice.address})" }
|
||||
|
||||
// Clean up any existing GATT connection
|
||||
bluetoothGatt?.let { gatt ->
|
||||
Log.d(TAG, "Closing existing GATT connection")
|
||||
logd { "Closing existing GATT connection" }
|
||||
try {
|
||||
gatt.disconnect()
|
||||
gatt.close()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Error closing old GATT: ${e.message}")
|
||||
logw { "Error closing old GATT: ${e.message}" }
|
||||
}
|
||||
bluetoothGatt = null
|
||||
txCharacteristic = null
|
||||
@@ -174,9 +178,9 @@ class BleManager private constructor(private val context: Context) {
|
||||
|
||||
try {
|
||||
bluetoothGatt = bleDevice.device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
|
||||
Log.d(TAG, "connectGatt called, gatt=${bluetoothGatt != null}")
|
||||
logd { "connectGatt called, gatt=${bluetoothGatt != null}" }
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "connectGatt failed: ${e.message}")
|
||||
loge { "connectGatt failed: ${e.message}" }
|
||||
handler.post {
|
||||
connectionError.value = "Connection failed: ${e.message}"
|
||||
if (idx >= 0) discoveredDevices[idx] = discoveredDevices[idx].copy(isConnecting = false)
|
||||
@@ -188,7 +192,7 @@ class BleManager private constructor(private val context: Context) {
|
||||
connectionTimer?.let { handler.removeCallbacks(it) }
|
||||
val timeout = Runnable {
|
||||
if (!isConnected.value) {
|
||||
Log.w(TAG, "Connection timeout")
|
||||
logw { "Connection timeout" }
|
||||
bluetoothGatt?.let { gatt ->
|
||||
try {
|
||||
gatt.disconnect()
|
||||
@@ -247,25 +251,25 @@ class BleManager private constructor(private val context: Context) {
|
||||
val characteristic = txCharacteristic
|
||||
val gatt = bluetoothGatt
|
||||
if (characteristic == null) {
|
||||
Log.e(TAG, "sendRaw: txCharacteristic is null!")
|
||||
loge { "sendRaw: txCharacteristic is null!" }
|
||||
return
|
||||
}
|
||||
if (gatt == null) {
|
||||
Log.e(TAG, "sendRaw: bluetoothGatt is null!")
|
||||
loge { "sendRaw: bluetoothGatt is null!" }
|
||||
return
|
||||
}
|
||||
val cmdPreview = if (data.size >= 4) String(data, 0, 4, Charsets.US_ASCII) else "?"
|
||||
Log.d(TAG, "sendRaw: $cmdPreview (${data.size} bytes)")
|
||||
logd { "sendRaw: $cmdPreview (${data.size} bytes)" }
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val result = gatt.writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE)
|
||||
Log.d(TAG, "sendRaw result: $result")
|
||||
logd { "sendRaw result: $result" }
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
characteristic.value = data
|
||||
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
||||
@Suppress("DEPRECATION")
|
||||
val ok = gatt.writeCharacteristic(characteristic)
|
||||
Log.d(TAG, "sendRaw success: $ok")
|
||||
logd { "sendRaw success: $ok" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,7 +332,7 @@ class BleManager private constructor(private val context: Context) {
|
||||
|
||||
private val gattCallback = object : BluetoothGattCallback() {
|
||||
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
|
||||
Log.d(TAG, "onConnectionStateChange: status=$status newState=$newState")
|
||||
logd { "onConnectionStateChange: status=$status newState=$newState" }
|
||||
handler.post {
|
||||
// Cancel connection timeout
|
||||
connectionTimer?.let { handler.removeCallbacks(it) }
|
||||
@@ -336,7 +340,7 @@ class BleManager private constructor(private val context: Context) {
|
||||
|
||||
if (status != BluetoothGatt.GATT_SUCCESS && newState != BluetoothProfile.STATE_CONNECTED) {
|
||||
// Connection failed with error
|
||||
Log.e(TAG, "GATT connection failed with status: $status")
|
||||
loge { "GATT connection failed with status: $status" }
|
||||
bluetoothGatt = null
|
||||
txCharacteristic = null
|
||||
rxCharacteristic = null
|
||||
@@ -354,7 +358,7 @@ class BleManager private constructor(private val context: Context) {
|
||||
|
||||
when (newState) {
|
||||
BluetoothProfile.STATE_CONNECTED -> {
|
||||
Log.d(TAG, "Connected to ${gatt.device.address}")
|
||||
logd { "Connected to ${gatt.device.address}" }
|
||||
isReconnecting.value = false
|
||||
isUserDisconnect = false
|
||||
connectionError.value = null
|
||||
@@ -373,7 +377,7 @@ class BleManager private constructor(private val context: Context) {
|
||||
onConnectionStateChanged?.invoke(true)
|
||||
}
|
||||
BluetoothProfile.STATE_DISCONNECTED -> {
|
||||
Log.d(TAG, "Disconnected from ${gatt.device.address}")
|
||||
logd { "Disconnected from ${gatt.device.address}" }
|
||||
stopBatteryPolling()
|
||||
bluetoothGatt = null
|
||||
txCharacteristic = null
|
||||
@@ -399,21 +403,21 @@ class BleManager private constructor(private val context: Context) {
|
||||
}
|
||||
|
||||
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
|
||||
Log.d(TAG, "onServicesDiscovered: status=$status")
|
||||
logd { "onServicesDiscovered: status=$status" }
|
||||
if (status != BluetoothGatt.GATT_SUCCESS) {
|
||||
handler.post { connectionError.value = "Service discovery failed (status $status)" }
|
||||
return
|
||||
}
|
||||
val service = gatt.getService(SERVICE_UUID)
|
||||
if (service == null) {
|
||||
Log.e(TAG, "NUS service not found!")
|
||||
loge { "NUS service not found!" }
|
||||
handler.post { connectionError.value = "Device service not found" }
|
||||
return
|
||||
}
|
||||
txCharacteristic = service.getCharacteristic(TX_CHAR_UUID)
|
||||
rxCharacteristic = service.getCharacteristic(RX_CHAR_UUID)
|
||||
|
||||
Log.d(TAG, "TX=${txCharacteristic != null}, RX=${rxCharacteristic != null}")
|
||||
logd { "TX=${txCharacteristic != null}, RX=${rxCharacteristic != null}" }
|
||||
|
||||
rxCharacteristic?.let { rx ->
|
||||
gatt.setCharacteristicNotification(rx, true)
|
||||
@@ -428,12 +432,12 @@ class BleManager private constructor(private val context: Context) {
|
||||
gatt.writeDescriptor(descriptor)
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "CCCD descriptor not found for RX characteristic")
|
||||
logw { "CCCD descriptor not found for RX characteristic" }
|
||||
// No CCCD but still mark ready if characteristics exist
|
||||
handler.post {
|
||||
if (txCharacteristic != null && rxCharacteristic != null) {
|
||||
isServiceReady.value = true
|
||||
Log.d(TAG, "Service ready (no CCCD)")
|
||||
logd { "Service ready (no CCCD)" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -442,24 +446,24 @@ class BleManager private constructor(private val context: Context) {
|
||||
handler.post {
|
||||
if (txCharacteristic != null) {
|
||||
isServiceReady.value = true
|
||||
Log.d(TAG, "Service ready (no RX)")
|
||||
logd { "Service ready (no RX)" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
|
||||
Log.d(TAG, "onDescriptorWrite: status=$status uuid=${descriptor.uuid}")
|
||||
logd { "onDescriptorWrite: status=$status uuid=${descriptor.uuid}" }
|
||||
if (descriptor.uuid == CCCD_UUID) {
|
||||
handler.post {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
if (txCharacteristic != null && rxCharacteristic != null) {
|
||||
isServiceReady.value = true
|
||||
startBatteryPolling()
|
||||
Log.d(TAG, "Service ready! (CCCD written)")
|
||||
logd { "Service ready! (CCCD written)" }
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "CCCD write failed with status: $status")
|
||||
loge { "CCCD write failed with status: $status" }
|
||||
connectionError.value = "Notification setup failed (status $status)"
|
||||
}
|
||||
}
|
||||
@@ -486,7 +490,7 @@ class BleManager private constructor(private val context: Context) {
|
||||
|
||||
private fun processReceivedData(data: ByteArray) {
|
||||
val prefix = String(data, 0, minOf(4, data.size), Charsets.US_ASCII)
|
||||
Log.d(TAG, "Received: $prefix (${data.size} bytes), callback=${
|
||||
logd { "Received: $prefix (${data.size} bytes), callback=${
|
||||
when {
|
||||
prefix.startsWith("rcj") -> "onNirsMcjReceived=${onNirsMcjReceived != null}"
|
||||
prefix.startsWith("rta") -> "onNirsPowerOnReceived=${onNirsPowerOnReceived != null}"
|
||||
@@ -494,7 +498,7 @@ class BleManager private constructor(private val context: Context) {
|
||||
prefix.startsWith("rag") -> "onNirsMagReceived=${onNirsMagReceived != null}"
|
||||
else -> "n/a"
|
||||
}
|
||||
}")
|
||||
}" }
|
||||
when (prefix) {
|
||||
"rta:", "rta!" -> onNirsPowerOnReceived?.invoke()
|
||||
"rsh:", "rsh!", "rqq:", "rqq!" -> onNirsSensorActivated?.invoke()
|
||||
@@ -507,7 +511,7 @@ class BleManager private constructor(private val context: Context) {
|
||||
batteryLevel.value = ((millivolts - 3700) * 100 / 500).coerceIn(0, 100)
|
||||
}
|
||||
}
|
||||
else -> Log.w(TAG, "Unknown prefix: '$prefix' hex=${data.take(8).joinToString(" ") { "%02X".format(it) }}")
|
||||
else -> logw { "Unknown prefix: '$prefix' hex=${data.take(8).joinToString(" ") { "%02X".format(it) }}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user