ux: 사내 테스트 피드백 3건 — 배터리 경고 / Voiding Reminder 리네임 / 설정 overlay scrim
1) 배터리 잔량 경고 (BatteryWarningBanner 신규)
임계: 15% 이하 주황 / 5% 이하 빨강 / 미연결 또는 0% 숨김.
PlacementGuide + PiezoMonitoring 상단(FirmwareWarningBanner 옆) 노출.
2) Catheter Threshold → Voiding Reminder 리네임 + ml 자동환산
"방광이 이 정도 차오르면 알림" 보조설명.
숫자 표시: "80%" 큰 글씨 + "≈ 400 mL (of 500 mL)" 보조.
슬라이더/스테퍼는 그대로 (level 1~10 모델 유지, 10% 단위).
3) Settings overlay tap-outside-to-dismiss
- 뒤 dim Box 에 clickable { showSettings = false } — 밖 탭으로 닫힘
- Card 자체에도 no-op clickable — 카드 빈 영역 탭이 뒤 dim 으로
전파되어 의도치 않게 닫히는 거 방지
- 두 modifier 모두 indication = null → 리플 없음 (iOS modal scrim 동작)
덤으로 배경(액션 버튼 등) 으로 터치 새서 voiding 잘못 기록되던
현상도 해결.
This commit is contained in:
+69
@@ -0,0 +1,69 @@
|
||||
package com.example.medilightv2android.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.BatteryAlert
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.example.medilightv2android.ble.BleManager
|
||||
|
||||
/**
|
||||
* 배터리 잔량 경고 배너 — PlacementGuide / PiezoMonitoring 상단.
|
||||
*
|
||||
* 임계:
|
||||
* - 15% 이하 → 주황 경고 ("기기 배터리 부족 — 곧 충전하세요")
|
||||
* - 5% 이하 → 빨강 위급 ("기기 배터리 위급 — 측정이 갑자기 중단될 수 있음")
|
||||
* - 그 외 / 미연결 / 0% (아직 미수신) → 숨김
|
||||
*/
|
||||
@Composable
|
||||
fun BatteryWarningBanner(bleManager: BleManager, modifier: Modifier = Modifier) {
|
||||
val isConnected by bleManager.isConnected
|
||||
val batt by bleManager.batteryLevel
|
||||
if (!isConnected) return
|
||||
if (batt <= 0) return // 응답 받기 전엔 0 → false positive 방지
|
||||
if (batt > LOW_BATTERY_THRESHOLD) return
|
||||
|
||||
val critical = batt <= CRITICAL_BATTERY_THRESHOLD
|
||||
val bgColor = if (critical) Color(0xFFD32F2F) else Color(0xFFFF9500)
|
||||
val title = if (critical) "기기 배터리 위급" else "기기 배터리 부족"
|
||||
val subtitle = if (critical)
|
||||
"현재 ${batt}% · 측정이 갑자기 중단될 수 있습니다. 즉시 충전하세요."
|
||||
else
|
||||
"현재 ${batt}% · 곧 충전이 필요합니다."
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 6.dp)
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.background(bgColor)
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(Icons.Default.BatteryAlert, "Battery", tint = Color.White)
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(title, fontSize = 13.sp, fontWeight = FontWeight.Bold, color = Color.White)
|
||||
Text(subtitle, fontSize = 11.sp, color = Color.White.copy(alpha = 0.9f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const val LOW_BATTERY_THRESHOLD = 15
|
||||
const val CRITICAL_BATTERY_THRESHOLD = 5
|
||||
+11
@@ -951,6 +951,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
|
||||
// 펌웨어 구버전 경고
|
||||
com.example.medilightv2android.ui.components.FirmwareWarningBanner(bleManager)
|
||||
com.example.medilightv2android.ui.components.BatteryWarningBanner(bleManager)
|
||||
|
||||
val scrollState = rememberScrollState()
|
||||
LaunchedEffect(showSettings) {
|
||||
@@ -1307,10 +1308,15 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
|
||||
// ── Floating Settings Overlay ──
|
||||
if (showSettings) {
|
||||
// 뒤 dim — 탭하면 닫힘 + 뒤 컨텐츠로 터치 새는 거 차단 (iOS modal scrim 동작).
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black.copy(alpha = 0.3f))
|
||||
.clickable(
|
||||
interactionSource = remember { androidx.compose.foundation.interaction.MutableInteractionSource() },
|
||||
indication = null
|
||||
) { showSettings = false }
|
||||
)
|
||||
Card(
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
@@ -1321,6 +1327,11 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||
.align(Alignment.TopCenter)
|
||||
.shadow(12.dp, RoundedCornerShape(16.dp))
|
||||
// 카드 자체 공백 영역 탭 — 뒤 dim 까지 전파 안 되게 흡수 (의도치 않은 closing 방지).
|
||||
.clickable(
|
||||
interactionSource = remember { androidx.compose.foundation.interaction.MutableInteractionSource() },
|
||||
indication = null
|
||||
) { /* no-op — 소비만 */ }
|
||||
) {
|
||||
// 폰/태블릿 자동 분기 — fontScale은 360dp 기준 1.0~1.8 (line 118 정의)
|
||||
val isTablet = screenWidth >= 600
|
||||
|
||||
+2
@@ -193,6 +193,8 @@ fun PlacementGuideView(appState: AppState) {
|
||||
|
||||
// 펌웨어 구버전 경고
|
||||
com.example.medilightv2android.ui.components.FirmwareWarningBanner(bleManager)
|
||||
// 배터리 잔량 경고 (<15%)
|
||||
com.example.medilightv2android.ui.components.BatteryWarningBanner(bleManager)
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
Column(
|
||||
|
||||
+14
-9
@@ -158,6 +158,9 @@ private fun MaxVolumeCard(appState: AppState) {
|
||||
@Composable
|
||||
private fun CatheterThresholdCard(appState: AppState) {
|
||||
val threshold = appState.bladderLevel.catheterizationThreshold
|
||||
val maxVol = appState.piezoSettings.maxVolume
|
||||
val pct = threshold * 10 // level 1~10 → 10~100%
|
||||
val ml = (maxVol * pct / 100) // 자동 환산
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
@@ -167,19 +170,21 @@ private fun CatheterThresholdCard(appState: AppState) {
|
||||
modifier = Modifier.padding(horizontal = 20.dp, vertical = 18.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text("Catheter Threshold", fontSize = 28.sp, fontWeight = FontWeight.ExtraBold)
|
||||
Text("Voiding Reminder", fontSize = 28.sp, fontWeight = FontWeight.ExtraBold)
|
||||
Text("방광이 이 정도 차오르면 알림",
|
||||
fontSize = 12.sp, color = MlSecondaryText,
|
||||
modifier = Modifier.padding(top = 2.dp))
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Row(verticalAlignment = Alignment.Bottom) {
|
||||
Text("Level", fontSize = 20.sp, fontWeight = FontWeight.SemiBold, color = MlSecondaryText,
|
||||
modifier = Modifier.padding(bottom = 8.dp))
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text("$threshold", fontSize = 48.sp,
|
||||
Text("$pct", fontSize = 48.sp,
|
||||
fontWeight = FontWeight.ExtraBold, color = MlPrimary)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text("/10", fontSize = 20.sp, fontWeight = FontWeight.SemiBold, color = MlSecondaryText,
|
||||
modifier = Modifier.padding(bottom = 8.dp))
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("%", fontSize = 24.sp, fontWeight = FontWeight.Bold, color = MlPrimary,
|
||||
modifier = Modifier.padding(bottom = 6.dp))
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text("≈ $ml mL (of $maxVol mL)",
|
||||
fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = MlSecondaryText)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(32.dp)) {
|
||||
StepperButton(icon = Icons.Default.Remove) {
|
||||
appState.updateCatheterizationThreshold((threshold - 1).coerceIn(1, 10))
|
||||
|
||||
Reference in New Issue
Block a user