fix: 도넛차트 recomposition 버그 + Spot 롱프레스 자동측정 + BV 4채널(CH0~CH3)

- bladderLevel.copy() 할당으로 Compose recomposition 트리거 (도넛차트 실시간 갱신)
- Spot 버튼: Button→Box+detectTapGestures (탭=단일, 롱프레스=30초 자동측정)
- startAutoMeasure() + LaunchedEffect 30초 반복 루프
- BV 추정 CH0~CH4(5ch)→CH0~CH3(4ch) 세로 채널만 사용

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-22 17:23:46 +09:00
parent 5cc2321ff4
commit d8a725b21b
@@ -8,6 +8,8 @@ import androidx.compose.material3.OutlinedTextField
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
@@ -109,10 +111,10 @@ fun PiezoMonitoringView(appState: AppState) {
if (validChannels.isNotEmpty()) {
val analysisResult = analyzer.analyzeMultiChannel(validChannels)
// Build walls list for BV estimation (5 channels)
val walls: MutableList<Pair<Int, Int>?> = MutableList(5) { null }
// Build walls list for BV estimation (CH0~CH3, 세로 4채널만)
val walls: MutableList<Pair<Int, Int>?> = MutableList(4) { null }
for (ch in analysisResult.channels) {
if (ch.channel < 5 && ch.result != null) {
if (ch.channel < 4 && ch.result != null) {
walls[ch.channel] = Pair(ch.result.ant, ch.result.post)
}
}
@@ -142,7 +144,7 @@ fun PiezoMonitoringView(appState: AppState) {
val maxVol = appState.piezoSettings.maxVolume.toDouble()
val pct = if (maxVol > 0) (lastVolumeMl / maxVol).coerceAtMost(1.0) else 0.0
val level = (pct * BladderLevel.MAX_LEVEL).toInt().coerceIn(1, BladderLevel.MAX_LEVEL)
appState.bladderLevel.currentLevel = level
appState.bladderLevel = appState.bladderLevel.copy(currentLevel = level)
}
isMeasuring = false
measureRetryCount = 0
@@ -183,10 +185,25 @@ fun PiezoMonitoringView(appState: AppState) {
isMeasuring = false
}
fun startAutoMeasure() {
isAutoMeasuring = true
measure()
}
fun stopAutoMeasure() {
isAutoMeasuring = false
}
// Auto-measure: repeat every 30s while active
LaunchedEffect(isAutoMeasuring) {
if (isAutoMeasuring) {
while (isAutoMeasuring) {
kotlinx.coroutines.delay(30_000)
if (isAutoMeasuring && !isMeasuring) measure()
}
}
}
// Setup disconnect handling
LaunchedEffect(Unit) {
bleManager.onUnexpectedDisconnect = {
@@ -634,44 +651,46 @@ fun PiezoMonitoringView(appState: AppState) {
Text("Catheterize", fontSize = 16.sp, fontWeight = FontWeight.SemiBold, color = MlPrimary)
}
// Spot button
Button(
onClick = {
if (isAutoMeasuring) stopAutoMeasure()
else if (!isMeasuring) measure()
},
modifier = Modifier.weight(1f).height(56.dp),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent),
contentPadding = PaddingValues(),
enabled = !isMeasuring || isAutoMeasuring
) {
Box(
modifier = Modifier.fillMaxSize().background(
// Spot button (tap = single, long press = auto 30s)
Box(
modifier = Modifier
.weight(1f)
.height(56.dp)
.clip(RoundedCornerShape(16.dp))
.background(
Brush.horizontalGradient(
if (isAutoMeasuring) listOf(Color.Red, Color(0xFFFF9500))
else urgency.gradientColors
),
RoundedCornerShape(16.dp)
),
contentAlignment = Alignment.Center
) {
Row(verticalAlignment = Alignment.CenterVertically) {
if (isMeasuring && !isAutoMeasuring) {
CircularProgressIndicator(color = Color.White, modifier = Modifier.size(20.dp), strokeWidth = 2.dp)
}
Spacer(modifier = Modifier.width(8.dp))
Text(
when {
isAutoMeasuring -> "Stop"
isMeasuring -> "Measuring..."
else -> "Spot"
},
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
color = Color.White
)
)
.pointerInput(isAutoMeasuring, isMeasuring) {
detectTapGestures(
onTap = {
if (isAutoMeasuring) stopAutoMeasure()
else if (!isMeasuring) measure()
},
onLongPress = {
if (!isAutoMeasuring && !isMeasuring) startAutoMeasure()
}
)
},
contentAlignment = Alignment.Center
) {
Row(verticalAlignment = Alignment.CenterVertically) {
if (isMeasuring && !isAutoMeasuring) {
CircularProgressIndicator(color = Color.White, modifier = Modifier.size(20.dp), strokeWidth = 2.dp)
}
Spacer(modifier = Modifier.width(8.dp))
Text(
when {
isAutoMeasuring -> "Stop"
isMeasuring -> "Measuring..."
else -> "Spot"
},
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
color = Color.White
)
}
}
}