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 771c578..27995f6 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 @@ -36,6 +36,7 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign @@ -120,8 +121,15 @@ fun PlacementGuideView(appState: AppState) { var sweepLastDetected by remember { mutableIntStateOf(0) } var sweepEverSawAll by remember { mutableStateOf(false) } - // 화면 진입 시 전체 상태 리셋 + // 화면 진입 시 전체 상태 리셋. clinical alignment session 이면 V2, 그 외 V1 자동 강제. LaunchedEffect(Unit) { + val sess = com.example.medilightv2android.services.ClinicalSessionStore.currentSession + val isClinicalAlign = sess != null && + sess.mode == com.example.medilightv2android.models.SessionMode.ALIGNMENT + com.example.medilightv2android.managers.GreenZoneConstants.alignmentAlgo = + if (isClinicalAlign) com.example.medilightv2android.managers.AlignmentAlgo.V2 + else com.example.medilightv2android.managers.AlignmentAlgo.V1 + v2Aligner.reset() isContinuousMode = false waitingForStart = true placementPhase = PlacementPhase.VERTICAL @@ -224,13 +232,25 @@ fun PlacementGuideView(appState: AppState) { .padding(horizontal = 24.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - // ─── (1) Hint 1 — 시작 전 안내 (칸은 상시 유지, 텍스트는 Start 누르면 사라짐) ─── + // ─── (1) Hint 1 — 시작 전 안내 (칸 상시 유지, "pubic bone" 빨강 강조) ─── val hint1Bg = Color(0xFFFFF3E0) + val hint1Text = remember(waitingForStart) { + if (waitingForStart) buildAnnotatedString { + val full = "Place VesiScan above the pubic bone, then press Start" + val key = "pubic bone" + val idx = full.indexOf(key, ignoreCase = true) + if (idx >= 0) { + append(full.substring(0, idx)) + withStyle(SpanStyle(color = Color.Red, fontWeight = FontWeight.ExtraBold)) { + append(full.substring(idx, idx + key.length)) + } + append(full.substring(idx + key.length)) + } else append(full) + } else AnnotatedString(" ") // 공백 — 칸 높이 유지 + } Text( - text = if (waitingForStart) - "Place VesiScan above the pubic bone, then press Start" - else " ", // 공백 — 칸 높이 유지 - fontSize = 26.sp, // 태블릿: 더 크게 + text = hint1Text, + fontSize = 26.sp, fontWeight = FontWeight.Bold, color = Color.Black, textAlign = TextAlign.Center, @@ -277,8 +297,10 @@ fun PlacementGuideView(appState: AppState) { // 화살표 깜빡임 — alpha 1.0 ↔ 0.85 (최솟값 0.85 → body 일러스트 살색 안 비침) // 이전 0.35 만큼 떨어지면 skin 톤이 비쳐 보이던 사용자 피드백 반영. + // 화살표 깜빡임 — alpha 1.0 ↔ 0.0 (완전 사라짐), 빨강 색. + // 사용자 피드백: "붉은색/사라짐 으로 깜빡임" val arrowAlpha by animateFloatAsState( - targetValue = if (scanCount % 2 == 0) 1f else 0.85f, + targetValue = if (scanCount % 2 == 0) 1f else 0f, animationSpec = tween(durationMillis = 220), label = "arrowBlink" ) @@ -397,7 +419,7 @@ fun PlacementGuideView(appState: AppState) { } if (placement != null) { val tint = androidx.compose.ui.graphics.ColorFilter.tint( - Color(0xFFE64A19).copy(alpha = arrowAlpha) + Color.Red.copy(alpha = arrowAlpha) ) rotate(degrees = placement.rot, pivot = Offset(placement.cxa, placement.cya)) { drawImage( @@ -428,18 +450,32 @@ fun PlacementGuideView(appState: AppState) { horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.padding(vertical = 4.dp) ) { - val currentStepIdx = when (placementPhase) { - PlacementPhase.VERTICAL -> 0 - PlacementPhase.LATERAL -> 1 - PlacementPhase.GREEN -> 2 + // V2 활성 시 4-stage step indicator, 아니면 V1 의 3-stage. + val isV2Active = com.example.medilightv2android.managers.GreenZoneConstants.alignmentAlgo == + com.example.medilightv2android.managers.AlignmentAlgo.V2 + val (currentStepIdx, totalSteps) = if (isV2Active) { + val idx = when (v2Aligner.phase) { + com.example.medilightv2android.managers.AlignGuide4Stage.Phase4.INITIAL_ACCUM -> 0 + com.example.medilightv2android.managers.AlignGuide4Stage.Phase4.VERTICAL_CLIMB -> 1 + com.example.medilightv2android.managers.AlignGuide4Stage.Phase4.CH3_STABILIZE -> 2 + com.example.medilightv2android.managers.AlignGuide4Stage.Phase4.LR_BALANCE -> 3 + } + idx to 4 + } else { + val idx = when (placementPhase) { + PlacementPhase.VERTICAL -> 0 + PlacementPhase.LATERAL -> 1 + PlacementPhase.GREEN -> 2 + } + idx to 3 } Text( - text = "Step ${currentStepIdx + 1}/3", - fontSize = 28.sp, fontWeight = FontWeight.Bold, color = Color.Black // 22 → 28 + text = "Step ${currentStepIdx + 1}/$totalSteps", + fontSize = 28.sp, fontWeight = FontWeight.Bold, color = Color.Black ) Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { - repeat(3) { i -> + repeat(totalSteps) { i -> val isDone = i < currentStepIdx val isCurrent = i == currentStepIdx val dotSize = if (isCurrent) 16.dp else 10.dp // 12/8 → 16/10 @@ -488,10 +524,10 @@ fun PlacementGuideView(appState: AppState) { Spacer(modifier = Modifier.height(8.dp)) - // ─── (5) Hint 2 — 측정 중 방향 안내 (항상 표시, 레이아웃 안정) ─── + // ─── (5) Hint 2 — 측정 중 방향 안내 (waitingForStart 시 공백, pubic bone 중복 X) ─── Text( - text = displayHint, - fontSize = 28.sp, // 22 → 28 (태블릿) + text = if (waitingForStart) AnnotatedString(" ") else displayHint, + fontSize = 28.sp, fontWeight = FontWeight.Bold, color = Color.Black, textAlign = TextAlign.Center,