ux: Sensor Alignment 4종 개선 (demo-final) — feature/tab-navigation 와 동등
1) 배너 거짓 양성 — BLE 데이터 수신 중인데 "기기 응답 없음" 뜨던 문제 - BleManager.lastBleRxAt 추가, reb 수신마다 갱신 - FirmwareWarningBanner: 최근 5초 내 RX 있으면 timedOut 무시 - 1초 tick 으로 자동 재평가 2) Sensor Alignment 타이틀 26 → 32sp ExtraBold 3) 본문 레이아웃 재배치 기존: Step → Hint → 일러스트 변경: Step → 일러스트 (위로) → Hint (그림 아래, 22sp) 4) PNG 화살표 더 크게 + 살색 안 비치는 깜빡임 - arrowDrawW 90 → 130 (44% 키움, 두꺼움 효과) - alpha 1.0 ↔ 0.85 (was 1.0 ↔ 0.35) — 살색 비침 차단 - tween 180 → 220ms 추가: - mls 로그 파서 16bit BE 수정 (feature 와 동일 — 이전엔 항상 "mode 0" 표시) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -85,6 +85,9 @@ class BleManager private constructor(private val context: Context) {
|
|||||||
val firmwareVersion = mutableStateOf("") // "VBTFW0116"
|
val firmwareVersion = mutableStateOf("") // "VBTFW0116"
|
||||||
val hardwareVersion = mutableStateOf("") // "VBTHW0100"
|
val hardwareVersion = mutableStateOf("") // "VBTHW0100"
|
||||||
val serialNumber = mutableStateOf("") // "VBT26040001"
|
val serialNumber = mutableStateOf("") // "VBT26040001"
|
||||||
|
/** 마지막으로 의미있는 BLE RX (reb 등) 가 도착한 시각 ms.
|
||||||
|
* FirmwareWarningBanner false-positive 차단용. */
|
||||||
|
val lastBleRxAt = mutableStateOf(0L)
|
||||||
|
|
||||||
/** "VBTFW0116" → 116, 형식 안 맞으면 null. */
|
/** "VBTFW0116" → 116, 형식 안 맞으면 null. */
|
||||||
fun parseFirmwareBuild(raw: String): Int? {
|
fun parseFirmwareBuild(raw: String): Int? {
|
||||||
@@ -460,7 +463,11 @@ class BleManager private constructor(private val context: Context) {
|
|||||||
"mag" -> "NIRS gain query"
|
"mag" -> "NIRS gain query"
|
||||||
"mcj" -> "NIRS MCJ query"
|
"mcj" -> "NIRS MCJ query"
|
||||||
"mid" -> "device info query"
|
"mid" -> "device info query"
|
||||||
"mls" -> "LED mode ${if (data.size > 4) data[4].toInt() and 0xFF else "?"}"
|
// mls 는 buildCommandBE 로 16bit BE 인코딩 → data[4]=High, data[5]=Low
|
||||||
|
"mls" -> "LED mode ${
|
||||||
|
if (data.size > 5) ((data[4].toInt() and 0xFF) shl 8) or (data[5].toInt() and 0xFF)
|
||||||
|
else "?"
|
||||||
|
}"
|
||||||
"msp" -> "IMU query"
|
"msp" -> "IMU query"
|
||||||
"mbb" -> "full measurement (battery+IMU+temp+6ch)"
|
"mbb" -> "full measurement (battery+IMU+temp+6ch)"
|
||||||
else -> ""
|
else -> ""
|
||||||
@@ -1130,6 +1137,7 @@ class BleManager private constructor(private val context: Context) {
|
|||||||
val chNum = if (data.size > 5) data[5].toInt() and 0xFF else -1
|
val chNum = if (data.size > 5) data[5].toInt() and 0xFF else -1
|
||||||
debugLogger.rx("reb", data.size, "session=$chSession ch=$chNum ${samples}samples")
|
debugLogger.rx("reb", data.size, "session=$chSession ch=$chNum ${samples}samples")
|
||||||
piezoCollector.addPacket(data)
|
piezoCollector.addPacket(data)
|
||||||
|
lastBleRxAt.value = System.currentTimeMillis() // 기기 alive 마크
|
||||||
}
|
}
|
||||||
"red:" -> {
|
"red:" -> {
|
||||||
debugLogger.rx("red", data.size, "continuation")
|
debugLogger.rx("red", data.size, "continuation")
|
||||||
|
|||||||
+15
-1
@@ -40,6 +40,7 @@ import kotlinx.coroutines.delay
|
|||||||
fun FirmwareWarningBanner(bleManager: BleManager, modifier: Modifier = Modifier) {
|
fun FirmwareWarningBanner(bleManager: BleManager, modifier: Modifier = Modifier) {
|
||||||
val fw by bleManager.firmwareVersion
|
val fw by bleManager.firmwareVersion
|
||||||
val isConnected by bleManager.isConnected
|
val isConnected by bleManager.isConnected
|
||||||
|
val lastRxAt by bleManager.lastBleRxAt
|
||||||
|
|
||||||
// 미연결 상태에선 펌웨어 배너 일절 노출 X. dev bypass / demo mode / 이전 세션
|
// 미연결 상태에선 펌웨어 배너 일절 노출 X. dev bypass / demo mode / 이전 세션
|
||||||
// 잔여 firmwareVersion 값으로 인한 false positive 방지.
|
// 잔여 firmwareVersion 값으로 인한 false positive 방지.
|
||||||
@@ -57,8 +58,21 @@ fun FirmwareWarningBanner(bleManager: BleManager, modifier: Modifier = Modifier)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deviceAlive 재계산용 매초 tick
|
||||||
|
var tick by remember { mutableStateOf(0) }
|
||||||
|
LaunchedEffect(isConnected) {
|
||||||
|
while (isConnected) {
|
||||||
|
delay(1000)
|
||||||
|
tick++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Suppress("UNUSED_EXPRESSION") tick
|
||||||
|
// 최근 5초 내 BLE 데이터 수신 있으면 기기 alive — "기기 응답 없음" false positive 차단.
|
||||||
|
val deviceAlive = lastRxAt > 0 && (System.currentTimeMillis() - lastRxAt) < 5_000
|
||||||
|
|
||||||
val outdated = bleManager.isFirmwareOutdated
|
val outdated = bleManager.isFirmwareOutdated
|
||||||
if (!outdated && !timedOut) return
|
val showTimedOut = timedOut && !deviceAlive
|
||||||
|
if (!outdated && !showTimedOut) return
|
||||||
|
|
||||||
val bgColor = if (outdated) Color(0xFFFF9500) else Color(0xFFD32F2F)
|
val bgColor = if (outdated) Color(0xFFFF9500) else Color(0xFFD32F2F)
|
||||||
val title = if (outdated) "펌웨어 업데이트 필요" else "기기 응답 없음"
|
val title = if (outdated) "펌웨어 업데이트 필요" else "기기 응답 없음"
|
||||||
|
|||||||
+23
-21
@@ -199,10 +199,10 @@ fun PlacementGuideView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// (B) 타이틀 — Box.Center로 화면 절대 가운데 (← 폭과 무관)
|
// (B) 타이틀 — Box.Center로 화면 절대 가운데 (← 폭과 무관). 26→32sp 강조.
|
||||||
Text(
|
Text(
|
||||||
text = "Sensor Alignment",
|
text = "Sensor Alignment",
|
||||||
fontSize = 26.sp,
|
fontSize = 32.sp,
|
||||||
fontWeight = FontWeight.ExtraBold,
|
fontWeight = FontWeight.ExtraBold,
|
||||||
color = Color.Black
|
color = Color.Black
|
||||||
)
|
)
|
||||||
@@ -322,26 +322,15 @@ fun PlacementGuideView(appState: AppState) {
|
|||||||
append(rawHint)
|
append(rawHint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 회의 피드백: hint 글자는 검정으로 통일 + 키우기. 박스 배경 색은 의미 유지(노랑=조정 필요/초록=Green).
|
// 본문 순서 (2026-06-16 변경): Step → 일러스트 → Hint (그림 아래)
|
||||||
Text(
|
// hint Text 본체는 Body Box 뒤로 이동. 값(displayHint 등) 은 위에서 계산.
|
||||||
text = displayHint,
|
|
||||||
fontSize = 26.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = Color.Black,
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(hintBg.copy(alpha = 0.7f), RoundedCornerShape(14.dp))
|
|
||||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
|
||||||
// 화살표 깜빡임 — 매 scan refresh 마다 alpha 토글 → 업데이트 가시화.
|
// 화살표 깜빡임 — alpha 1.0 ↔ 0.85 (최솟값 0.85 → body 일러스트 살색 안 비침)
|
||||||
// (사용자 요청: "refreshing 될 때마다 깜빡이게 — 덥데이트 되는지 확인")
|
// 이전 0.35 만큼 떨어지면 skin 톤이 비쳐 보이던 사용자 피드백 반영.
|
||||||
val arrowAlpha by animateFloatAsState(
|
val arrowAlpha by animateFloatAsState(
|
||||||
targetValue = if (scanCount % 2 == 0) 1f else 0.35f,
|
targetValue = if (scanCount % 2 == 0) 1f else 0.85f,
|
||||||
animationSpec = tween(durationMillis = 180),
|
animationSpec = tween(durationMillis = 220),
|
||||||
label = "arrowBlink"
|
label = "arrowBlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -427,7 +416,7 @@ fun PlacementGuideView(appState: AppState) {
|
|||||||
// arrow_transparent.png 는 우향(→) 132×112. 회전으로 4방향 처리.
|
// arrow_transparent.png 는 우향(→) 132×112. 회전으로 4방향 처리.
|
||||||
// tint = deep orange 700 (#E64A19) — 일러스트 톤 맞춤 + alpha 로 blink.
|
// tint = deep orange 700 (#E64A19) — 일러스트 톤 맞춤 + alpha 로 blink.
|
||||||
if (!greenZone) {
|
if (!greenZone) {
|
||||||
val arrowDrawW = 90f
|
val arrowDrawW = 130f // 90 → 130 (44% 키움, 두꺼움 효과)
|
||||||
val arrowDrawH = arrowDrawW * (arrowBitmap.height.toFloat() / arrowBitmap.width.toFloat())
|
val arrowDrawH = arrowDrawW * (arrowBitmap.height.toFloat() / arrowBitmap.width.toFloat())
|
||||||
val sensorHalfH = h * 0.08f
|
val sensorHalfH = h * 0.08f
|
||||||
val gap = 20f
|
val gap = 20f
|
||||||
@@ -482,7 +471,20 @@ fun PlacementGuideView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// (힌트 텍스트는 Step 아래, 몸 위에 배치됨)
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
// ── 가이드 hint — 일러스트 *아래* 로 이동 (사용자 피드백) ──
|
||||||
|
Text(
|
||||||
|
text = displayHint,
|
||||||
|
fontSize = 22.sp, // 26 → 22 (그림이 메인, hint 보조)
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color.Black,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(hintBg.copy(alpha = 0.7f), RoundedCornerShape(14.dp))
|
||||||
|
.padding(horizontal = 16.dp, vertical = 10.dp)
|
||||||
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user