ClinicalLive: 6채널 1열 6행 세로 일렬 + Capture/Auto Capture 토글 제거

회의 결정 2건:

1. 6채널 그래프 layout: 2×3 grid → 1열 6행 세로 일렬
   - 각 그래프가 화면 width 가득 (가독성 ↑)
   - 부모 Column이 이미 verticalScroll을 가지므로 자체 scroll 미적용 (nested scroll 회피)
   - SixChannelGrid 내부 Row 분기 제거 → 단순 Column에 ChannelChart × 6 + 4dp spacing

2. Capture 기능 제거 (필요 없음)
   - autoCapture = true 기본 + 매 사이클 자동 저장이라 Capture 버튼은 사실상 무용
   - 하단 'Capture' 버튼 제거 (이전엔 Pause/Resume 옆에 위치)
   - 'Auto Capture 매 사이클 자동 저장' Switch 토글 제거
   - var autoCapture 상태 → val autoCapture = true 고정
   - 콜백 자동 저장 로직(imuCollector.onComplete 안)은 그대로 — 토글이 없으니 항상 true 경로
   - 상단 doc 코멘트도 'Pause/Resume / Capture / End' → 'Pause/Resume / Discard / End'로 갱신

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 14:45:12 +09:00
parent 4c7d347851
commit fd9078f9a1
@@ -48,9 +48,8 @@ import kotlin.math.min
* - 2×3 grid 로 채널별 실시간 raw ADC 그래프 * - 2×3 grid 로 채널별 실시간 raw ADC 그래프
* - 상단: 세션 정보 (Subject ID / Posture / Step / True Vol / Abdomen) * - 상단: 세션 정보 (Subject ID / Posture / Step / True Vol / Abdomen)
* - 측정 상태 (cycle 번호, 6/6 채널 도착률, IMU 자세/흔들림) * - 측정 상태 (cycle 번호, 6/6 채널 도착률, IMU 자세/흔들림)
* - 하단 컨트롤: Pause/Resume / Capture / End * - 하단 컨트롤: Pause/Resume / Discard / End
* - Capture: 현재 시점 1 사이클을 Clinical 폴더에 저장 * - 측정 중 매 사이클 자동 저장 (autoCapture=true 고정, 회의 결정으로 토글 제거)
* - 자동 Capture 모드: 사이클마다 자동 저장 토글
* - End: 세션 finalize → ClinicalHome 복귀 * - End: 세션 finalize → ClinicalHome 복귀
*/ */
@Composable @Composable
@@ -67,7 +66,8 @@ fun ClinicalLiveView(appState: AppState) {
// 측정 state // 측정 state
var isMeasuring by remember { mutableStateOf(true) } // 시작 시 자동 측정 var isMeasuring by remember { mutableStateOf(true) } // 시작 시 자동 측정
var autoCapture by remember { mutableStateOf(true) } // 매 사이클 자동 저장 // autoCapture는 항상 true — 토글 UI 제거됨 (회의 결정)
val autoCapture = true
var cycleCount by remember { mutableIntStateOf(0) } var cycleCount by remember { mutableIntStateOf(0) }
var capturedCount by remember { mutableIntStateOf(0) } var capturedCount by remember { mutableIntStateOf(0) }
var lastChannels by remember { mutableStateOf<List<PiezoChannelData>>(emptyList()) } var lastChannels by remember { mutableStateOf<List<PiezoChannelData>>(emptyList()) }
@@ -229,44 +229,6 @@ fun ClinicalLiveView(appState: AppState) {
Text(if (isMeasuring) "Pause" else "Resume", color = Color.White, Text(if (isMeasuring) "Pause" else "Resume", color = Color.White,
fontWeight = FontWeight.SemiBold) fontWeight = FontWeight.SemiBold)
} }
// Manual Capture (Auto OFF일 때 의미 있음)
Button(
onClick = {
if (lastChannels.isNotEmpty()) {
ClinicalSessionStore.addCycle(lastChannels, lastImu)
val rawADC = lastChannels.sortedBy { it.channel }.map { it.buffer }
AdcCsvLogger.log(rawADC, null)
if (lastImu.isNotEmpty()) ImuCsvLogger.log(lastImu)
capturedCount++
Toast.makeText(context, "Captured #${capturedCount}",
Toast.LENGTH_SHORT).show()
}
},
enabled = lastChannels.isNotEmpty(),
modifier = Modifier.weight(1f).height(48.dp),
colors = ButtonDefaults.buttonColors(containerColor = MlPrimary)
) {
Icon(Icons.Default.Save, null, tint = Color.White)
Spacer(modifier = Modifier.width(4.dp))
Text("Capture", color = Color.White, fontWeight = FontWeight.SemiBold)
}
}
Spacer(modifier = Modifier.height(4.dp))
// 자동 캡쳐 토글
Row(
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Switch(
checked = autoCapture,
onCheckedChange = { autoCapture = it },
colors = SwitchDefaults.colors(checkedTrackColor = MlPrimary)
)
Spacer(modifier = Modifier.width(8.dp))
Text("Auto Capture (매 사이클 자동 저장)",
fontSize = 12.sp, color = MlSecondaryText)
} }
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
@@ -344,13 +306,14 @@ private fun SixChannelGrid(
Color(0xFF1976D2), Color(0xFF388E3C), Color(0xFFE64A19), Color(0xFF1976D2), Color(0xFF388E3C), Color(0xFFE64A19),
Color(0xFF7B1FA2), Color(0xFF00838F), Color(0xFFD81B60), Color(0xFF7B1FA2), Color(0xFF00838F), Color(0xFFD81B60),
) )
Column(modifier = modifier) { // 회의 결정: 1열 6행 세로 일렬 — 각 채널이 화면 width 가득.
for (row in 0 until 2) { // 외부 부모 Column이 이미 verticalScroll을 가지므로 여기선 nested scroll 피함.
Row(modifier = Modifier.fillMaxWidth()) { Column(
for (col in 0 until 3) { modifier = modifier,
val ch = row * 3 + col verticalArrangement = Arrangement.spacedBy(4.dp)
) {
for (ch in 0 until 6) {
val data = channels.find { it.channel == ch } val data = channels.find { it.channel == ch }
Box(modifier = Modifier.weight(1f).padding(2.dp)) {
ChannelChart( ChannelChart(
channelIdx = ch, channelIdx = ch,
buffer = data?.buffer ?: emptyList(), buffer = data?.buffer ?: emptyList(),
@@ -360,9 +323,6 @@ private fun SixChannelGrid(
} }
} }
} }
}
}
}
/** 한 채널 그래프 (raw ADC waveform + V4.1 ant/post 마커) */ /** 한 채널 그래프 (raw ADC waveform + V4.1 ant/post 마커) */
@Composable @Composable