refactor(ui): Design Token 중앙화 + AlertModalDialog 컴포넌트화 (Step 1~3)
초 고수 앱 디자이너 코드 리뷰 결과 반영. 저비용/저리스크 항목만 우선 진행,
대형 파일 분해 등은 별도 로드맵.
Step 1: Design Token (Color.kt + Theme.kt)
- Color.kt 에 semantic token 추가: MlCritical / MlWarning / MlSuccess /
MlInfo / MlNeutral / MlAccentPurple + ContainerAlpha / ChipAlpha 상수
- Theme.kt LightColorScheme 를 Purple40 → MlPrimary 로 override.
기존에는 Material3 default 가 Purple 이라 Button 등 커스텀 UI 와
색상 불일치 발생. brand 통일.
Step 2: AlertModalDialog 컴포넌트화 (ui/components/AlertModalDialog.kt)
- 3개 알람 다이얼로그 (Wakeup 주황, Walking 빨강, Detach 주황) 가
100% 동일 구조 (배경색만 다름) 로 각각 40~50줄 반복.
- 재사용 가능한 AlertModalDialog(backgroundColor, icon, title,
subtitle, detail, confirmLabel, onConfirm, widthFraction, iconSize,
titleSize, dismissible) 로 통합.
- dismissible=false 가 default (의료 알람은 사용자 명시 확인 필요).
- PiezoMonitoringView (Wakeup + Walking) / PlacementGuideView (Detach)
3곳 모두 새 컴포넌트로 교체. 코드량 ~120줄 감소.
Step 3: LabdbAutoRetry 배너 semantic token 적용
- ClinicalHomeView 자동 업로드 배너의 Color(0xFF1976D2) → MlInfo,
Color(0xFFFF9800) → MlWarning, Color(0xFF4CAF50) → MlSuccess.
- ChipAlpha (0.12f) 로 배경 alpha 통일.
효과:
- 신규 배너/Dialog 는 반드시 semantic token 사용 → 색상 일관성 관리 용이.
- 색상 변경 시 Color.kt 한 파일만 수정하면 전역 반영.
- AlertModalDialog 로 신규 알람 추가 시 10~15줄로 가능 (이전 50줄+).
미완료 (별도 로드맵):
- 기존 파일의 나머지 매직 hex 색상 (~300개) → semantic token 마이그레이션.
광범위 변경 + 실기 회귀 위험 있어 순차 진행 필요.
- 대형 파일 분해 (PiezoMonitoringView 2900L, PlacementGuideView 2200L).
- ViewModel 도입, BLE 콜백 → Flow 이전.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,139 @@
|
|||||||
|
package com.medithings.vesiscan.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
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.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전면 모달 알람 다이얼로그 — semantic 배경색 위 아이콘/타이틀/서브/설명 + 확인 버튼.
|
||||||
|
*
|
||||||
|
* 통합 대상 (2026-07-10):
|
||||||
|
* - Wakeup Alarm (주황, LYING → NON_LYING/WALKING 전이)
|
||||||
|
* - Walking Alarm (빨강, WALKING 전이 즉시 낙상 경고)
|
||||||
|
* - Detach Alert (주황, 센서 분리)
|
||||||
|
*
|
||||||
|
* 사용 예:
|
||||||
|
* ```
|
||||||
|
* if (showWakeupAlarm) {
|
||||||
|
* AlertModalDialog(
|
||||||
|
* backgroundColor = MlWarning,
|
||||||
|
* icon = Icons.Default.WbSunny,
|
||||||
|
* title = stringResource(R.string.wakeup_detected_title),
|
||||||
|
* subtitle = stringResource(R.string.wakeup_safely_get_up),
|
||||||
|
* detail = stringResource(R.string.alarm_lying_duration, mins, secs),
|
||||||
|
* onConfirm = { showWakeupAlarm = false },
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 접근성:
|
||||||
|
* - 기본은 `dismissOnBackPress = false / dismissOnClickOutside = false`
|
||||||
|
* (의료 알람은 사용자 명시적 확인 필요). 필요 시 [dismissible]=true 로 override.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun AlertModalDialog(
|
||||||
|
backgroundColor: Color,
|
||||||
|
icon: ImageVector,
|
||||||
|
title: String,
|
||||||
|
subtitle: String? = null,
|
||||||
|
detail: String? = null,
|
||||||
|
confirmLabel: String,
|
||||||
|
onConfirm: () -> Unit,
|
||||||
|
widthFraction: Float = 0.92f,
|
||||||
|
iconSize: androidx.compose.ui.unit.Dp = 56.dp,
|
||||||
|
titleSize: androidx.compose.ui.unit.TextUnit = 28.sp,
|
||||||
|
dismissible: Boolean = false,
|
||||||
|
) {
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = { if (dismissible) onConfirm() },
|
||||||
|
properties = DialogProperties(
|
||||||
|
dismissOnBackPress = dismissible,
|
||||||
|
dismissOnClickOutside = dismissible,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(widthFraction)
|
||||||
|
.clip(RoundedCornerShape(24.dp))
|
||||||
|
.background(backgroundColor)
|
||||||
|
.padding(horizontal = 28.dp, vertical = 36.dp),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.White,
|
||||||
|
modifier = Modifier.size(iconSize),
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
fontSize = titleSize,
|
||||||
|
fontWeight = FontWeight.ExtraBold,
|
||||||
|
color = Color.White,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
if (subtitle != null) {
|
||||||
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
|
Text(
|
||||||
|
text = subtitle,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = Color.White.copy(alpha = 0.9f),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (detail != null) {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = detail,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = Color.White.copy(alpha = 0.85f),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
Button(
|
||||||
|
onClick = onConfirm,
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color.White,
|
||||||
|
contentColor = backgroundColor,
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(56.dp),
|
||||||
|
shape = RoundedCornerShape(14.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = confirmLabel,
|
||||||
|
fontSize = 20.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ package com.medithings.vesiscan.ui.theme
|
|||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
// Material theme colors
|
// Material theme colors (legacy — Material3 template default. 실사용 X, LightColorScheme 를 MlPrimary 로 override.)
|
||||||
val Purple80 = Color(0xFFD0BCFF)
|
val Purple80 = Color(0xFFD0BCFF)
|
||||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||||
val Pink80 = Color(0xFFEFB8C8)
|
val Pink80 = Color(0xFFEFB8C8)
|
||||||
@@ -10,7 +10,7 @@ val Purple40 = Color(0xFF6650a4)
|
|||||||
val PurpleGrey40 = Color(0xFF625b71)
|
val PurpleGrey40 = Color(0xFF625b71)
|
||||||
val Pink40 = Color(0xFF7D5260)
|
val Pink40 = Color(0xFF7D5260)
|
||||||
|
|
||||||
// MediLight theme colors
|
// ─────────── Brand colors ───────────
|
||||||
val MlPrimary = Color(0xFF008FFF)
|
val MlPrimary = Color(0xFF008FFF)
|
||||||
val MlTeal = Color(0xFF00DB9F)
|
val MlTeal = Color(0xFF00DB9F)
|
||||||
val MlBackground = Color(0xFFF5F8FF)
|
val MlBackground = Color(0xFFF5F8FF)
|
||||||
@@ -18,3 +18,27 @@ val MlCardBackground = Color.White
|
|||||||
val MlSecondaryText = Color(0xFF80808C)
|
val MlSecondaryText = Color(0xFF80808C)
|
||||||
val MlInputBackground = Color(0xFFF5F8FF)
|
val MlInputBackground = Color(0xFFF5F8FF)
|
||||||
val MlInputBorder = Color(0xFF00DB9F)
|
val MlInputBorder = Color(0xFF00DB9F)
|
||||||
|
|
||||||
|
// ─────────── Semantic tokens (2026-07-10) ───────────
|
||||||
|
// 매직 hex 코드가 각 파일에 흩어져 있어 일관성 관리 어려움 → semantic 이름으로 통일.
|
||||||
|
// 새 UI 코드는 반드시 아래 토큰을 사용. 기존 코드는 점진적으로 마이그레이션.
|
||||||
|
|
||||||
|
/** Critical / danger — 위치 놓침, 기기 응답 없음, 낙상 위험, walking alarm 등. */
|
||||||
|
val MlCritical = Color(0xFFD32F2F)
|
||||||
|
/** Warning — 배터리 낮음, firmware 구버전, mtb 응답 지연, 자동 업로드 일부 실패, wakeup 알람 등. */
|
||||||
|
val MlWarning = Color(0xFFFF9800)
|
||||||
|
/** Success — GREEN zone, 정렬 완료, 자동 업로드 성공, NON_LYING posture chip 등. */
|
||||||
|
val MlSuccess = Color(0xFF4CAF50)
|
||||||
|
/** Info — 자동 업로드 진행 중, "확인 중" 등의 중립 정보. */
|
||||||
|
val MlInfo = Color(0xFF1976D2)
|
||||||
|
/** Neutral — UNKNOWN posture, disable 상태의 회색 아이콘 등. */
|
||||||
|
val MlNeutral = Color(0xFF616161)
|
||||||
|
/** Purple accent — dev toggle, alignment algo selector 등 dev 전용 UI. */
|
||||||
|
val MlAccentPurple = Color(0xFF7C3AED)
|
||||||
|
|
||||||
|
/** Container / background alpha helpers.
|
||||||
|
* 카드/배너 배경으로 semantic 색 사용 시 알파 12~15% 가 일관적.
|
||||||
|
* 사용 예: `MlWarning.copy(alpha = ContainerAlpha)`. */
|
||||||
|
const val ContainerAlpha = 0.15f
|
||||||
|
/** 배지/inline 강조 (chip 배경 등). */
|
||||||
|
const val ChipAlpha = 0.12f
|
||||||
|
|||||||
@@ -3,21 +3,25 @@ package com.medithings.vesiscan.ui.theme
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.lightColorScheme
|
import androidx.compose.material3.lightColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
// 2026-07-10: Purple40 기반 Material3 default 를 MediLight 브랜드 색상으로 override.
|
||||||
|
// 기존에는 colorScheme.primary = Purple40 (Material 템플릿 기본) 이지만 실제 UI 는
|
||||||
|
// MlPrimary 사용 → MaterialTheme 를 참조하는 default 컴포넌트 (예: Button 배경) 와
|
||||||
|
// 커스텀 UI 사이에 색상 불일치 발생했음. brand 색상으로 통일.
|
||||||
private val LightColorScheme = lightColorScheme(
|
private val LightColorScheme = lightColorScheme(
|
||||||
primary = Purple40,
|
primary = MlPrimary,
|
||||||
secondary = PurpleGrey40,
|
|
||||||
tertiary = Pink40
|
|
||||||
|
|
||||||
/* Other default colors to override
|
|
||||||
background = Color(0xFFFFFBFE),
|
|
||||||
surface = Color(0xFFFFFBFE),
|
|
||||||
onPrimary = Color.White,
|
onPrimary = Color.White,
|
||||||
|
secondary = MlTeal,
|
||||||
onSecondary = Color.White,
|
onSecondary = Color.White,
|
||||||
|
tertiary = MlAccentPurple,
|
||||||
onTertiary = Color.White,
|
onTertiary = Color.White,
|
||||||
|
background = MlBackground,
|
||||||
onBackground = Color(0xFF1C1B1F),
|
onBackground = Color(0xFF1C1B1F),
|
||||||
|
surface = MlCardBackground,
|
||||||
onSurface = Color(0xFF1C1B1F),
|
onSurface = Color(0xFF1C1B1F),
|
||||||
*/
|
error = MlCritical,
|
||||||
|
onError = Color.White,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -144,25 +144,28 @@ fun ClinicalHomeView(appState: AppState) {
|
|||||||
|
|
||||||
// 2026-07-09: 자동 업로드 진행/결과 배너.
|
// 2026-07-09: 자동 업로드 진행/결과 배너.
|
||||||
if (autoUploadPending > 0 || autoUploadingName != null) {
|
if (autoUploadPending > 0 || autoUploadingName != null) {
|
||||||
|
// 2026-07-10: semantic token 사용 (MlInfo = 진행 중 파랑).
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
.clip(RoundedCornerShape(10.dp))
|
.clip(RoundedCornerShape(10.dp))
|
||||||
.background(Color(0xFF1976D2).copy(alpha = 0.12f))
|
.background(com.medithings.vesiscan.ui.theme.MlInfo.copy(
|
||||||
|
alpha = com.medithings.vesiscan.ui.theme.ChipAlpha))
|
||||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
modifier = Modifier.size(16.dp),
|
modifier = Modifier.size(16.dp),
|
||||||
strokeWidth = 2.dp,
|
strokeWidth = 2.dp,
|
||||||
color = Color(0xFF1976D2)
|
color = com.medithings.vesiscan.ui.theme.MlInfo
|
||||||
)
|
)
|
||||||
Spacer(Modifier.width(10.dp))
|
Spacer(Modifier.width(10.dp))
|
||||||
Column {
|
Column {
|
||||||
Text("자동 업로드 진행 중 · 남은 세션 $autoUploadPending",
|
Text("자동 업로드 진행 중 · 남은 세션 $autoUploadPending",
|
||||||
fontSize = 13.sp, fontWeight = FontWeight.SemiBold,
|
fontSize = 13.sp, fontWeight = FontWeight.SemiBold,
|
||||||
color = Color(0xFF1976D2))
|
color = com.medithings.vesiscan.ui.theme.MlInfo)
|
||||||
autoUploadingName?.let {
|
autoUploadingName?.let {
|
||||||
Text(it, fontSize = 11.sp, color = Color(0xFF1976D2).copy(alpha = 0.8f),
|
Text(it, fontSize = 11.sp,
|
||||||
|
color = com.medithings.vesiscan.ui.theme.MlInfo.copy(alpha = 0.8f),
|
||||||
maxLines = 1)
|
maxLines = 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,7 +174,9 @@ fun ClinicalHomeView(appState: AppState) {
|
|||||||
} else if (autoUploadResult != null) {
|
} else if (autoUploadResult != null) {
|
||||||
val msg = autoUploadResult ?: ""
|
val msg = autoUploadResult ?: ""
|
||||||
val hasFail = msg.contains("failed")
|
val hasFail = msg.contains("failed")
|
||||||
val bg = if (hasFail) Color(0xFFFF9800) else Color(0xFF4CAF50)
|
// 2026-07-10: semantic token 사용 (MlWarning 실패 / MlSuccess 완료).
|
||||||
|
val bg = if (hasFail) com.medithings.vesiscan.ui.theme.MlWarning
|
||||||
|
else com.medithings.vesiscan.ui.theme.MlSuccess
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
.clip(RoundedCornerShape(10.dp))
|
.clip(RoundedCornerShape(10.dp))
|
||||||
|
|||||||
+24
-88
@@ -156,99 +156,35 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
// 직전 walking 상태 추적 — false → true 전이 시 critical 알람
|
// 직전 walking 상태 추적 — false → true 전이 시 critical 알람
|
||||||
var prevIsWalking by remember { mutableStateOf(false) }
|
var prevIsWalking by remember { mutableStateOf(false) }
|
||||||
// Wakeup alarm (warning, 주황) — LYING → NON_LYING/WALKING 전이
|
// Wakeup alarm (warning, 주황) — LYING → NON_LYING/WALKING 전이
|
||||||
|
// 2026-07-10: 3개 알람 Dialog 를 AlertModalDialog 로 통합 (ui/components/AlertModalDialog.kt).
|
||||||
if (showWakeupAlarm) {
|
if (showWakeupAlarm) {
|
||||||
androidx.compose.ui.window.Dialog(
|
val mins = wakeupAlarmElapsedSec / 60
|
||||||
onDismissRequest = { },
|
val secs = wakeupAlarmElapsedSec % 60
|
||||||
properties = androidx.compose.ui.window.DialogProperties(
|
com.medithings.vesiscan.ui.components.AlertModalDialog(
|
||||||
dismissOnBackPress = false, dismissOnClickOutside = false
|
backgroundColor = com.medithings.vesiscan.ui.theme.MlWarning,
|
||||||
)
|
icon = Icons.Default.Warning,
|
||||||
) {
|
title = stringResource(R.string.alarm_patient_woke_up),
|
||||||
Box(
|
subtitle = stringResource(R.string.alarm_patient_woke_up_en),
|
||||||
modifier = Modifier
|
detail = stringResource(R.string.alarm_lying_duration, mins, secs),
|
||||||
.fillMaxWidth(0.92f)
|
confirmLabel = stringResource(R.string.confirm),
|
||||||
.clip(RoundedCornerShape(24.dp))
|
onConfirm = { showWakeupAlarm = false },
|
||||||
.background(Color(0xFFFF9800)) // Material Orange 500
|
)
|
||||||
.padding(horizontal = 28.dp, vertical = 36.dp),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
|
||||||
Icon(Icons.Default.Warning, contentDescription = null,
|
|
||||||
tint = Color.White, modifier = Modifier.size(56.dp))
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
Text(stringResource(R.string.alarm_patient_woke_up), fontSize = 28.sp,
|
|
||||||
fontWeight = FontWeight.ExtraBold, color = Color.White,
|
|
||||||
textAlign = TextAlign.Center)
|
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
|
||||||
Text(stringResource(R.string.alarm_patient_woke_up_en), fontSize = 16.sp,
|
|
||||||
fontWeight = FontWeight.Medium, color = Color.White.copy(alpha = 0.9f),
|
|
||||||
textAlign = TextAlign.Center)
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
val mins = wakeupAlarmElapsedSec / 60
|
|
||||||
val secs = wakeupAlarmElapsedSec % 60
|
|
||||||
Text(stringResource(R.string.alarm_lying_duration, mins, secs),
|
|
||||||
fontSize = 14.sp, color = Color.White.copy(alpha = 0.85f))
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
Button(
|
|
||||||
onClick = { showWakeupAlarm = false },
|
|
||||||
colors = ButtonDefaults.buttonColors(
|
|
||||||
containerColor = Color.White, contentColor = Color(0xFFFF9800)
|
|
||||||
),
|
|
||||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
|
||||||
shape = RoundedCornerShape(14.dp)
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.confirm), fontSize = 20.sp, fontWeight = FontWeight.Bold)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walking alarm (critical, 빨강) — NON_LYING/LYING → WALKING 전이 (보행 시작)
|
// Walking alarm (critical, 빨강) — NON_LYING/LYING → WALKING 전이 (보행 시작)
|
||||||
if (showWalkingAlarm) {
|
if (showWalkingAlarm) {
|
||||||
androidx.compose.ui.window.Dialog(
|
com.medithings.vesiscan.ui.components.AlertModalDialog(
|
||||||
onDismissRequest = { },
|
backgroundColor = com.medithings.vesiscan.ui.theme.MlCritical,
|
||||||
properties = androidx.compose.ui.window.DialogProperties(
|
icon = Icons.Default.DirectionsWalk,
|
||||||
dismissOnBackPress = false, dismissOnClickOutside = false
|
title = stringResource(R.string.alarm_patient_walking),
|
||||||
)
|
subtitle = stringResource(R.string.alarm_patient_walking_en),
|
||||||
) {
|
detail = stringResource(R.string.alarm_immediate_check),
|
||||||
Box(
|
confirmLabel = stringResource(R.string.confirm),
|
||||||
modifier = Modifier
|
onConfirm = { showWalkingAlarm = false },
|
||||||
.fillMaxWidth(0.94f)
|
widthFraction = 0.94f,
|
||||||
.clip(RoundedCornerShape(24.dp))
|
iconSize = 64.dp,
|
||||||
.background(Color(0xFFD32F2F))
|
titleSize = 30.sp,
|
||||||
.padding(horizontal = 28.dp, vertical = 36.dp),
|
)
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
|
||||||
Icon(Icons.Default.DirectionsWalk, contentDescription = null,
|
|
||||||
tint = Color.White, modifier = Modifier.size(64.dp))
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
Text(stringResource(R.string.alarm_patient_walking), fontSize = 30.sp,
|
|
||||||
fontWeight = FontWeight.ExtraBold, color = Color.White,
|
|
||||||
textAlign = TextAlign.Center)
|
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
|
||||||
Text(stringResource(R.string.alarm_patient_walking_en),
|
|
||||||
fontSize = 16.sp, fontWeight = FontWeight.Medium,
|
|
||||||
color = Color.White.copy(alpha = 0.92f),
|
|
||||||
textAlign = TextAlign.Center)
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
Text(stringResource(R.string.alarm_immediate_check),
|
|
||||||
fontSize = 14.sp, color = Color.White.copy(alpha = 0.9f),
|
|
||||||
textAlign = TextAlign.Center)
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
Button(
|
|
||||||
onClick = { showWalkingAlarm = false },
|
|
||||||
colors = ButtonDefaults.buttonColors(
|
|
||||||
containerColor = Color.White, contentColor = Color(0xFFD32F2F)
|
|
||||||
),
|
|
||||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
|
||||||
shape = RoundedCornerShape(14.dp)
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.confirm), fontSize = 20.sp, fontWeight = FontWeight.Bold)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val screenWidth = LocalConfiguration.current.screenWidthDp
|
val screenWidth = LocalConfiguration.current.screenWidthDp
|
||||||
|
|||||||
+11
-43
@@ -208,50 +208,18 @@ fun PlacementGuideView(appState: AppState) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// 센서 분리 알림 (주황 warning) — wakeup 알람과 동일 패턴
|
// 센서 분리 알림 (주황 warning) — wakeup 알람과 동일 패턴
|
||||||
|
// 2026-07-10: AlertModalDialog 로 통합 (ui/components/AlertModalDialog.kt).
|
||||||
if (showDetachAlert) {
|
if (showDetachAlert) {
|
||||||
androidx.compose.ui.window.Dialog(
|
com.medithings.vesiscan.ui.components.AlertModalDialog(
|
||||||
onDismissRequest = { },
|
backgroundColor = com.medithings.vesiscan.ui.theme.MlWarning,
|
||||||
properties = androidx.compose.ui.window.DialogProperties(
|
icon = Icons.Default.Warning,
|
||||||
dismissOnBackPress = false, dismissOnClickOutside = false
|
title = stringResource(R.string.sensor_detached_title),
|
||||||
)
|
subtitle = stringResource(R.string.sensor_detached_subtitle),
|
||||||
) {
|
detail = stringResource(R.string.sensor_detached_desc),
|
||||||
Box(
|
confirmLabel = stringResource(R.string.confirm),
|
||||||
modifier = Modifier
|
onConfirm = { showDetachAlert = false },
|
||||||
.fillMaxWidth(0.92f)
|
titleSize = 26.sp,
|
||||||
.clip(RoundedCornerShape(24.dp))
|
)
|
||||||
.background(Color(0xFFFF9800)) // Material Orange 500
|
|
||||||
.padding(horizontal = 28.dp, vertical = 36.dp),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
|
||||||
Icon(Icons.Default.Warning, contentDescription = null,
|
|
||||||
tint = Color.White, modifier = Modifier.size(56.dp))
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
Text(stringResource(R.string.sensor_detached_title), fontSize = 26.sp,
|
|
||||||
fontWeight = FontWeight.ExtraBold, color = Color.White,
|
|
||||||
textAlign = TextAlign.Center)
|
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
|
||||||
Text(stringResource(R.string.sensor_detached_subtitle), fontSize = 16.sp,
|
|
||||||
fontWeight = FontWeight.Medium, color = Color.White.copy(alpha = 0.9f),
|
|
||||||
textAlign = TextAlign.Center)
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
Text(stringResource(R.string.sensor_detached_desc),
|
|
||||||
fontSize = 14.sp, color = Color.White.copy(alpha = 0.85f),
|
|
||||||
textAlign = TextAlign.Center)
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
Button(
|
|
||||||
onClick = { showDetachAlert = false },
|
|
||||||
colors = ButtonDefaults.buttonColors(
|
|
||||||
containerColor = Color.White, contentColor = Color(0xFFFF9800)
|
|
||||||
),
|
|
||||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
|
||||||
shape = RoundedCornerShape(14.dp)
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.confirm), fontSize = 20.sp, fontWeight = FontWeight.Bold)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
|
|||||||
Reference in New Issue
Block a user