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:
+11
-51
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user