diff --git a/.claude/settings.local.json b/.claude/settings.local.json index e4a876c..f58ed7b 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,7 +1,10 @@ { "permissions": { "allow": [ - "Bash(git push -u origin master)" + "Bash(git push -u origin master)", + "Bash(git pull:*)", + "Bash(./gradlew assembleDebug)", + "Bash(xargs grep:*)" ] } } diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/connection/DeviceScanView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/connection/DeviceScanView.kt index 89c885a..35e3f98 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/views/connection/DeviceScanView.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/views/connection/DeviceScanView.kt @@ -45,7 +45,7 @@ fun DeviceScanView(appState: AppState) { var permissionsGranted by remember { mutableStateOf(false) } val filteredDevices = bleManager.discoveredDevices - .filter { it.name.isNotBlank() } + .filter { it.name.startsWith("2025") || it.name.startsWith("VBT") } .sortedByDescending { it.rssi } val isConnected by bleManager.isConnected diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PlacementGuideView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PlacementGuideView.kt index 56699ce..ac11e9a 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PlacementGuideView.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PlacementGuideView.kt @@ -396,8 +396,9 @@ fun PlacementGuideView(appState: AppState) { } /** - * Canvas-based sensor layout visualization. - * Draws body outline, bladder outline, and 6 sensor dots with labels. + * Canvas-based sensor layout on body — sensors overlaid on abdomen/bladder. + * Actual hardware spec proportions (cross array 4+2). + * Scale: 1mm ≈ 8px, centered on body. */ @Composable private fun SensorLayoutCanvas( @@ -409,119 +410,149 @@ private fun SensorLayoutCanvas( val redColor = Color.Red.copy(alpha = 0.5f) val orangeColor = Color(0xFFFF9800).copy(alpha = 0.6f) + val chColors = listOf( + Color(0xFFFF6B6B), // CH0 + Color(0xFFFFB347), // CH1 + Color(0xFF87CF3E), // CH2 + Color(0xFF4FC3F7), // CH3 + Color(0xFF64B5F6), // CH4 + Color(0xFFCE93D8) // CH5 + ) + + val s = 16f // scale: 1mm = 16px + + data class SensorSpec(val ch: Int, val xMm: Float, val yMm: Float, val tilt: Float, val wMm: Float, val hMm: Float) + val sensors = listOf( + SensorSpec(3, 0f, 0f, 0f, 12f, 6f), + SensorSpec(2, 0f, 7f, 0f, 12f, 6f), + SensorSpec(1, 0f, 14f, 0f, 12f, 6f), + SensorSpec(0, 0f, 21f, 0f, 12f, 6f), + SensorSpec(4, -10f, 9.85f, -5f, 6f, 12f), + SensorSpec(5, 10f, 9.85f, 5f, 6f, 12f) + ) + + val arrayMidMm = 10.5f + Box( modifier = Modifier .fillMaxWidth() - .height(400.dp), + .height(420.dp), contentAlignment = Alignment.Center ) { Canvas( modifier = Modifier - .width(340.dp) - .height(400.dp) + .fillMaxWidth() + .height(420.dp) ) { - val centerX = size.width / 2 - val centerY = size.height / 2 + val cx = size.width / 2 + val cy = size.height / 2 - // Body outline (rounded rectangle) — enlarged + // Body outline (abdomen area) drawRoundRect( - color = Color.Gray.copy(alpha = 0.2f), - topLeft = Offset(centerX - 140f, centerY - 185f), - size = Size(280f, 370f), - cornerRadius = CornerRadius(40f), + color = Color.Gray.copy(alpha = 0.15f), + topLeft = Offset(cx - 200f, cy - 200f), + size = Size(400f, 400f), + cornerRadius = CornerRadius(50f), style = Stroke(width = 2f) ) - // Bladder outline (dashed ellipse) — enlarged + // Bladder outline (behind sensors) drawOval( - color = Color.Cyan.copy(alpha = 0.3f), - topLeft = Offset(centerX - 85f, centerY - 80f), - size = Size(170f, 140f), + color = Color.Cyan.copy(alpha = 0.2f), + topLeft = Offset(cx - 140f, cy - 110f), + size = Size(280f, 220f), style = Stroke( - width = 1.5f, + width = 2f, pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 6f)) ) ) - - // Sensor positions — spread out more - data class SensorPos(val ch: Int, val x: Float, val y: Float, val angle: Float) - val sensors = listOf( - SensorPos(3, 0f, -120f, -10f), - SensorPos(2, 0f, -40f, 0f), - SensorPos(1, 0f, 40f, 10f), - SensorPos(0, 0f, 120f, 20f), - SensorPos(4, -110f, -40f, 10f), - SensorPos(5, 110f, -40f, 10f) + drawOval( + color = Color.Cyan.copy(alpha = 0.05f), + topLeft = Offset(cx - 140f, cy - 110f), + size = Size(280f, 220f) ) - for (s in sensors) { - val detected = if (s.ch < channelDetected.size) channelDetected[s.ch] else false - val color = if (detected) greenColor else redColor - val sx = centerX + s.x - val sy = centerY + s.y + // Draw sensors centered on body + for (spec in sensors) { + val detected = if (spec.ch < channelDetected.size) channelDetected[spec.ch] else false + val color = if (detected) chColors[spec.ch] else chColors[spec.ch].copy(alpha = 0.25f) + val border = if (detected) chColors[spec.ch] else Color.Gray.copy(alpha = 0.4f) + val px = cx + spec.xMm * s + val py = cy + (spec.yMm - arrayMidMm) * s + val pw = spec.wMm * s + val ph = spec.hMm * s - rotate(degrees = s.angle, pivot = Offset(sx, sy)) { + rotate(degrees = spec.tilt, pivot = Offset(px, py)) { drawRoundRect( - color = color, - topLeft = Offset(sx - 20f, sy - 10f), - size = Size(40f, 20f), - cornerRadius = CornerRadius(4f) + color = color.copy(alpha = 0.4f), + topLeft = Offset(px - pw / 2, py - ph / 2), + size = Size(pw, ph), + cornerRadius = CornerRadius(6f) + ) + drawRoundRect( + color = border, + topLeft = Offset(px - pw / 2, py - ph / 2), + size = Size(pw, ph), + cornerRadius = CornerRadius(6f), + style = Stroke(width = 3f) ) } } - // Direction arrow overlay + // Direction arrows if (directionIcon.isNotEmpty() && !isLocked) { - val arrowLength = 40f + val al = 45f when { directionIcon.contains("up") -> { - val ax = centerX; val ay = centerY - 175f - drawLine(orangeColor, Offset(ax, ay + arrowLength), Offset(ax, ay), strokeWidth = 4f) - drawLine(orangeColor, Offset(ax - 12f, ay + 12f), Offset(ax, ay), strokeWidth = 4f) - drawLine(orangeColor, Offset(ax + 12f, ay + 12f), Offset(ax, ay), strokeWidth = 4f) + val ax = cx; val ay = cy - 190f + drawLine(orangeColor, Offset(ax, ay + al), Offset(ax, ay), strokeWidth = 4f) + drawLine(orangeColor, Offset(ax - 14f, ay + 14f), Offset(ax, ay), strokeWidth = 4f) + drawLine(orangeColor, Offset(ax + 14f, ay + 14f), Offset(ax, ay), strokeWidth = 4f) } directionIcon.contains("down") -> { - val ax = centerX; val ay = centerY + 175f - drawLine(orangeColor, Offset(ax, ay - arrowLength), Offset(ax, ay), strokeWidth = 4f) - drawLine(orangeColor, Offset(ax - 12f, ay - 12f), Offset(ax, ay), strokeWidth = 4f) - drawLine(orangeColor, Offset(ax + 12f, ay - 12f), Offset(ax, ay), strokeWidth = 4f) + val ax = cx; val ay = cy + 190f + drawLine(orangeColor, Offset(ax, ay - al), Offset(ax, ay), strokeWidth = 4f) + drawLine(orangeColor, Offset(ax - 14f, ay - 14f), Offset(ax, ay), strokeWidth = 4f) + drawLine(orangeColor, Offset(ax + 14f, ay - 14f), Offset(ax, ay), strokeWidth = 4f) } directionIcon.contains("left") -> { - val ax = centerX - 170f; val ay = centerY - drawLine(orangeColor, Offset(ax + arrowLength, ay), Offset(ax, ay), strokeWidth = 4f) - drawLine(orangeColor, Offset(ax + 12f, ay - 12f), Offset(ax, ay), strokeWidth = 4f) - drawLine(orangeColor, Offset(ax + 12f, ay + 12f), Offset(ax, ay), strokeWidth = 4f) + val ax = cx - 190f; val ay = cy + drawLine(orangeColor, Offset(ax + al, ay), Offset(ax, ay), strokeWidth = 4f) + drawLine(orangeColor, Offset(ax + 14f, ay - 14f), Offset(ax, ay), strokeWidth = 4f) + drawLine(orangeColor, Offset(ax + 14f, ay + 14f), Offset(ax, ay), strokeWidth = 4f) } directionIcon.contains("right") -> { - val ax = centerX + 170f; val ay = centerY - drawLine(orangeColor, Offset(ax - arrowLength, ay), Offset(ax, ay), strokeWidth = 4f) - drawLine(orangeColor, Offset(ax - 12f, ay - 12f), Offset(ax, ay), strokeWidth = 4f) - drawLine(orangeColor, Offset(ax - 12f, ay + 12f), Offset(ax, ay), strokeWidth = 4f) + val ax = cx + 190f; val ay = cy + drawLine(orangeColor, Offset(ax - al, ay), Offset(ax, ay), strokeWidth = 4f) + drawLine(orangeColor, Offset(ax - 14f, ay - 14f), Offset(ax, ay), strokeWidth = 4f) + drawLine(orangeColor, Offset(ax - 14f, ay + 14f), Offset(ax, ay), strokeWidth = 4f) } } } } - // Overlay text labels — repositioned for larger layout - data class LabelPos(val ch: Int, val x: Int, val y: Int, val label: String) - val labels = listOf( - LabelPos(3, 0, -120, "CH3\n-10\u00B0"), - LabelPos(2, 0, -40, "CH2\n0\u00B0"), - LabelPos(1, 0, 40, "CH1\n+10\u00B0"), - LabelPos(0, 0, 120, "CH0\n+20\u00B0"), - LabelPos(4, -110, -40, "CH4\nL 5\u00B0"), - LabelPos(5, 110, -40, "CH5\nR 5\u00B0") + // Channel labels next to sensors + data class LabelInfo(val ch: Int, val xMm: Float, val yMm: Float, val label: String) + val labelSpecs = listOf( + LabelInfo(3, 0f, 0f, "CH3 · 30\u00B0"), + LabelInfo(2, 0f, 7f, "CH2 · 20\u00B0"), + LabelInfo(1, 0f, 14f, "CH1 · 10\u00B0"), + LabelInfo(0, 0f, 21f, "CH0 · 0\u00B0"), + LabelInfo(4, -10f, 9.85f, "CH4"), + LabelInfo(5, 10f, 9.85f, "CH5") ) - for (lbl in labels) { + for (lbl in labelSpecs) { val detected = if (lbl.ch < channelDetected.size) channelDetected[lbl.ch] else false + val yOff = (lbl.yMm - arrayMidMm) * s + val xOff = if (lbl.ch == 4) (lbl.xMm * s - 60f) + else if (lbl.ch == 5) (lbl.xMm * s + 60f) + else (12f * s / 2 + 12f) Text( text = lbl.label, - fontSize = 9.sp, + fontSize = 11.sp, fontWeight = FontWeight.SemiBold, - color = if (detected) Color(0xFF4CAF50) else Color.Gray, - textAlign = TextAlign.Center, - lineHeight = 11.sp, - modifier = Modifier.offset(x = lbl.x.dp, y = (lbl.y + 18).dp) + color = if (detected) chColors[lbl.ch] else Color.Gray.copy(alpha = 0.6f), + modifier = Modifier.offset(x = xOff.dp, y = yOff.dp) ) } } @@ -544,19 +575,22 @@ private fun updateDirectionGuide(detected: List): Pair } // Vertical guidance + // CH0=위(배꼽), CH3=아래(치골) + // CH0✅ CH3❌ → 방광이 센서보다 위 → 센서를 위로 + // CH0❌ CH3✅ → 방광이 센서보다 아래 → 센서를 아래로 if (!topDetected && bottomDetected) { - return "Move sensor DOWN" to "arrow.down" + return "Move sensor UP" to "arrow.up" } if (topDetected && !bottomDetected) { - return "Move sensor UP" to "arrow.up" + return "Move sensor DOWN" to "arrow.down" } // Horizontal guidance if (!leftDetected && rightDetected) { - return "Move sensor RIGHT" to "arrow.right" + return "Move sensor LEFT" to "arrow.left" } if (leftDetected && !rightDetected) { - return "Move sensor LEFT" to "arrow.left" + return "Move sensor RIGHT" to "arrow.right" } // No signal