UI: 페어링 다이얼로그 + 태블릿 폰트스케일 + Placement 힌트/버튼 확대
- DeviceScanView: 페어링 안내 다이얼로그 추가 (bonding 완료 대기 중 표시) - PiezoMonitoringView: fontScale(screenWidth/360) 적용, 도넛 비율 조정, 버튼 높이/텍스트 확대, InfoCard 폰트스케일, Void/Catheterization 텍스트 - PlacementGuideView: 힌트 카드 28sp + 패딩 확대, Start Alignment/ Start Scanning 버튼 96dp + 28sp, 센서 BiasAlignment 0.55 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+26
-9
@@ -95,21 +95,28 @@ fun DeviceScanView(appState: AppState) {
|
||||
}
|
||||
|
||||
// 서비스 준비 + 페어링(bonding) 완료 확인 후 다음 화면
|
||||
var showPairingHint by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(isServiceReady) {
|
||||
if (isServiceReady) {
|
||||
connectingDeviceAddress = null
|
||||
// 페어링 완료 대기 (최대 30초, 500ms 간격 폴링)
|
||||
var waited = 0
|
||||
while (!bleManager.isBonded && waited < 30000) {
|
||||
kotlinx.coroutines.delay(500)
|
||||
waited += 500
|
||||
}
|
||||
if (bleManager.isBonded) {
|
||||
// 이미 페어링됨 → 바로 진행
|
||||
appState.deviceConnected()
|
||||
} else {
|
||||
// 페어링 거부/타임아웃 → 연결 해제
|
||||
bleManager.disconnect()
|
||||
bleManager.connectionError.value = "Pairing required. Please pair the device."
|
||||
// 페어링 필요 → 안내 표시 + 대기
|
||||
showPairingHint = true
|
||||
var waited = 0
|
||||
while (!bleManager.isBonded && waited < 30000) {
|
||||
kotlinx.coroutines.delay(500)
|
||||
waited += 500
|
||||
}
|
||||
showPairingHint = false
|
||||
if (bleManager.isBonded) {
|
||||
appState.deviceConnected()
|
||||
} else {
|
||||
bleManager.disconnect()
|
||||
bleManager.connectionError.value = "Pairing required. Please pair the device."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,6 +127,16 @@ fun DeviceScanView(appState: AppState) {
|
||||
|
||||
DisposableEffect(Unit) { onDispose { bleManager.stopScan() } }
|
||||
|
||||
// 페어링 안내 다이얼로그
|
||||
if (showPairingHint) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {},
|
||||
title = { Text("Pairing Required", fontWeight = FontWeight.Bold) },
|
||||
text = { Text("A Bluetooth pairing request will appear.\nTap 'Pair', then enter PIN if prompted.") },
|
||||
confirmButton = {}
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
||||
+18
-15
@@ -92,6 +92,8 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
|
||||
val screenWidth = LocalConfiguration.current.screenWidthDp
|
||||
val isCompact = screenWidth < 400
|
||||
// 태블릿(600dp+) 글자 스케일: 폰=1.0, 태블릿=~1.6
|
||||
val fontScale = (screenWidth / 360f).coerceIn(1.0f, 1.8f)
|
||||
|
||||
val isDemoMode = appState.isDemoMode
|
||||
val urgency = appState.bladderLevel.urgency
|
||||
@@ -769,7 +771,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
cornerRadius = androidx.compose.ui.geometry.CornerRadius(2f))
|
||||
}
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("$batteryLvl%", fontSize = 12.sp, fontWeight = FontWeight.Bold, color = batteryColor)
|
||||
Text("$batteryLvl%", fontSize = (12 * fontScale).sp, fontWeight = FontWeight.Bold, color = batteryColor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -789,7 +791,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
) {
|
||||
Icon(Icons.Default.LocalHospital, "Catheter", tint = MlPrimary, modifier = Modifier.size(16.dp))
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("$cathCount", fontSize = 13.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||
Text("$cathCount", fontSize = (13 * fontScale).sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||
}
|
||||
}
|
||||
if (showAddCatheter) {
|
||||
@@ -862,7 +864,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
val statusColor = urgency.accentColor
|
||||
Text(
|
||||
text = statusText,
|
||||
fontSize = 14.sp,
|
||||
fontSize = (14 * fontScale).sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = statusColor,
|
||||
modifier = Modifier
|
||||
@@ -873,7 +875,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
|
||||
// Donut chart with Bladdy — 화면 비율 기반
|
||||
val screenWidthDp = screenWidth.dp
|
||||
val donutRatio = if (appState.isDevMode) 0.65f else 0.85f
|
||||
val donutRatio = if (appState.isDevMode) 0.65f else 0.75f
|
||||
val donutSize = screenWidthDp * donutRatio
|
||||
val lineWidthRatio = if (appState.isDevMode) 0.07f else 0.08f
|
||||
val donutLineWidth = donutSize * lineWidthRatio
|
||||
@@ -917,7 +919,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
}
|
||||
}
|
||||
|
||||
// Info cards (Fill/Level은 maxVolume 기준)
|
||||
// Info cards (Fill/Level은 maxVolume 기준)ㅋ
|
||||
val maxVol = appState.piezoSettings.maxVolume.toDouble()
|
||||
// fill/level은 maxVolumeMl(세션 최대) 기준
|
||||
val fillPct = if (maxVol > 0 && maxVolumeMl > 0) (maxVolumeMl / maxVol * 100).coerceAtMost(100.0) else 0.0
|
||||
@@ -934,7 +936,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
}
|
||||
|
||||
// Control buttons: Void/Catheterize / Auto / Spot
|
||||
val btnHeight = (screenWidth * 0.15f).dp.coerceIn(50.dp, 70.dp)
|
||||
val btnHeight = (screenWidth * 0.18f).dp.coerceIn(56.dp, 80.dp)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(if (appState.isDevMode) 8.dp else 10.dp)) {
|
||||
// Void / Catheterize — 즉시 카테터 1개 감소 + 기록
|
||||
Button(
|
||||
@@ -952,7 +954,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
android.widget.Toast.makeText(context, "Recorded", android.widget.Toast.LENGTH_SHORT).show()
|
||||
},
|
||||
enabled = !isSpotInProgress && !isAutoMeasuring,
|
||||
modifier = Modifier.weight(1.3f).height(btnHeight),
|
||||
modifier = Modifier.weight(1f).height(btnHeight),
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MlPrimary.copy(alpha = 0.1f),
|
||||
@@ -960,8 +962,8 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
)
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text("Voided", fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = MlPrimary)
|
||||
Text("Catheterized", fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = MlPrimary)
|
||||
Text("Void/", fontSize = (13 * fontScale).sp, fontWeight = FontWeight.SemiBold, color = MlPrimary)
|
||||
Text("Catheterization", fontSize = (13 * fontScale).sp, fontWeight = FontWeight.SemiBold, color = MlPrimary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -979,9 +981,9 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
)
|
||||
) {
|
||||
if (isAutoMeasuring) {
|
||||
Text("Stop Scan", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = Color.White)
|
||||
Text("Stop Scan", fontSize = (14 * fontScale).sp, fontWeight = FontWeight.SemiBold, color = Color.White)
|
||||
} else {
|
||||
Text("Auto Scan", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = Color.White)
|
||||
Text("Auto Scan", fontSize = (14 * fontScale).sp, fontWeight = FontWeight.SemiBold, color = Color.White)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1032,7 +1034,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
if (isSpotInProgress) {
|
||||
CircularProgressIndicator(color = Color.White, modifier = Modifier.size(18.dp), strokeWidth = 2.dp)
|
||||
} else {
|
||||
Text("Single Scan", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = Color.White)
|
||||
Text("Single Scan", fontSize = (14 * fontScale).sp, fontWeight = FontWeight.SemiBold, color = Color.White)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1788,19 +1790,20 @@ private fun InfoCard(title: String, value: String, unit: String, color: Color, m
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = MlCardBackground)
|
||||
) {
|
||||
val fs = (LocalConfiguration.current.screenWidthDp / 360f).coerceIn(1.0f, 1.8f)
|
||||
Column(
|
||||
modifier = Modifier.padding(vertical = 14.dp).fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.Bottom) {
|
||||
Text(value, fontSize = 20.sp, fontWeight = FontWeight.Bold, color = Color(0xFF1C1B1F))
|
||||
Text(value, fontSize = (20 * fs).sp, fontWeight = FontWeight.Bold, color = Color(0xFF1C1B1F))
|
||||
if (unit.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.width(3.dp))
|
||||
Text(unit, fontSize = 12.sp, color = MlSecondaryText, modifier = Modifier.padding(bottom = 2.dp))
|
||||
Text(unit, fontSize = (12 * fs).sp, color = MlSecondaryText, modifier = Modifier.padding(bottom = 2.dp))
|
||||
}
|
||||
}
|
||||
Text(title, fontSize = 11.sp, fontWeight = FontWeight.SemiBold, color = MlSecondaryText)
|
||||
Text(title, fontSize = (11 * fs).sp, fontWeight = FontWeight.SemiBold, color = MlSecondaryText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-12
@@ -235,14 +235,14 @@ fun PlacementGuideView(appState: AppState) {
|
||||
}
|
||||
Text(
|
||||
text = displayHint,
|
||||
fontSize = 15.sp,
|
||||
fontSize = 28.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = hintFg,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(hintBg, RoundedCornerShape(12.dp))
|
||||
.padding(12.dp)
|
||||
.background(hintBg, RoundedCornerShape(16.dp))
|
||||
.padding(horizontal = 20.dp, vertical = 18.dp)
|
||||
)
|
||||
|
||||
// ── 하복부 Canvas 실루엣 + 센서 + 방향 가이드 ──
|
||||
@@ -401,7 +401,7 @@ fun PlacementGuideView(appState: AppState) {
|
||||
contentDescription = "Sensor",
|
||||
modifier = Modifier
|
||||
.size(width = sensorW, height = sensorH)
|
||||
.align(androidx.compose.ui.BiasAlignment(0f, 0.44f)),
|
||||
.align(androidx.compose.ui.BiasAlignment(0f, 0.55f)),
|
||||
contentScale = androidx.compose.ui.layout.ContentScale.Fit,
|
||||
alpha = 0.95f
|
||||
)
|
||||
@@ -802,11 +802,11 @@ fun PlacementGuideView(appState: AppState) {
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp),
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
.height(96.dp),
|
||||
shape = RoundedCornerShape(18.dp),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFFF9800))
|
||||
) {
|
||||
Text("Start Alignment", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Color.White)
|
||||
Text("Start Alignment", fontSize = 28.sp, fontWeight = FontWeight.Bold, color = Color.White)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -819,16 +819,16 @@ fun PlacementGuideView(appState: AppState) {
|
||||
enabled = isLocked, // GREEN(Optimal)일 때만 활성
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp),
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
.height(96.dp),
|
||||
shape = RoundedCornerShape(18.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (isLocked) Color(0xFF4CAF50) else MlPrimary,
|
||||
disabledContainerColor = Color.Gray.copy(alpha = 0.3f)
|
||||
)
|
||||
) {
|
||||
Icon(Icons.Filled.CheckCircle, contentDescription = null, tint = Color.White)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text("Start Scanning", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Color.White)
|
||||
Icon(Icons.Filled.CheckCircle, contentDescription = null, tint = Color.White, modifier = Modifier.size(32.dp))
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Text("Start Scanning", fontSize = 28.sp, fontWeight = FontWeight.Bold, color = Color.White)
|
||||
}
|
||||
|
||||
// Skip button
|
||||
|
||||
Reference in New Issue
Block a user